From c62afd3d02e63b2cb3d6b644cb16aaa48183c7f5 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 11:44:04 +0800 Subject: [PATCH 01/28] fix activity display profile migration --- ...activity_display_profile_badge_sources.sql | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 server/admin/migrations/041_activity_display_profile_badge_sources.sql diff --git a/server/admin/migrations/041_activity_display_profile_badge_sources.sql b/server/admin/migrations/041_activity_display_profile_badge_sources.sql new file mode 100644 index 00000000..3b740c99 --- /dev/null +++ b/server/admin/migrations/041_activity_display_profile_badge_sources.sql @@ -0,0 +1,29 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- activity-service 处理 RoomGiftSent 时会写入用户等级展示资料;老库缺少徽章来源等级列会导致 MQ 消费重试。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'hyapp_activity' AND TABLE_NAME = 'user_level_display_profiles' AND COLUMN_NAME = 'wealth_badge_source_level') = 0, + 'ALTER TABLE hyapp_activity.user_level_display_profiles ADD COLUMN wealth_badge_source_level INT NOT NULL DEFAULT 0 COMMENT ''财富徽章来源等级'' AFTER wealth_badge_resource_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'hyapp_activity' AND TABLE_NAME = 'user_level_display_profiles' AND COLUMN_NAME = 'game_badge_source_level') = 0, + 'ALTER TABLE hyapp_activity.user_level_display_profiles ADD COLUMN game_badge_source_level INT NOT NULL DEFAULT 0 COMMENT ''游戏徽章来源等级'' AFTER game_badge_resource_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'hyapp_activity' AND TABLE_NAME = 'user_level_display_profiles' AND COLUMN_NAME = 'charm_badge_source_level') = 0, + 'ALTER TABLE hyapp_activity.user_level_display_profiles ADD COLUMN charm_badge_source_level INT NOT NULL DEFAULT 0 COMMENT ''魅力徽章来源等级'' AFTER charm_badge_resource_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; From 3eabd78808c79773adbdfd48a5b49dbac1d9adaf Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 11:48:35 +0800 Subject: [PATCH 02/28] enable notice mq consumers in test config --- services/notice-service/configs/config.docker.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/notice-service/configs/config.docker.yaml b/services/notice-service/configs/config.docker.yaml index d106f437..3436dc1b 100644 --- a/services/notice-service/configs/config.docker.yaml +++ b/services/notice-service/configs/config.docker.yaml @@ -57,12 +57,12 @@ rocketmq: secret_key: "" namespace: "" wallet_outbox: - enabled: false + enabled: true topic: "hyapp_wallet_outbox" consumer_group: "hyapp-notice-wallet-outbox" consumer_max_reconsume_times: 16 room_outbox: - enabled: false + enabled: true topic: "hyapp_room_outbox" consumer_group: "hyapp-notice-room-outbox" consumer_max_reconsume_times: 16 From 5d2c39f8db000ea50ed2bf151f9aec445a375355 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 12:00:45 +0800 Subject: [PATCH 03/28] enable mq outbox defaults --- .../configs/config.docker.yaml | 2 +- .../configs/config.tencent.example.yaml | 1 + services/activity-service/configs/config.yaml | 21 ++++++++++--------- .../internal/config/config_test.go | 10 ++++++--- services/game-service/configs/config.yaml | 8 +++---- services/notice-service/configs/config.yaml | 12 +++++------ .../internal/config/config_test.go | 15 ++++++++----- services/room-service/configs/config.yaml | 18 ++++++++-------- .../internal/config/config_test.go | 7 ++++--- .../statistics-service/configs/config.yaml | 12 +++++------ .../user-service/configs/config.docker.yaml | 6 +++--- services/user-service/configs/config.yaml | 20 +++++++++--------- services/wallet-service/configs/config.yaml | 16 +++++++------- .../internal/config/config_test.go | 10 ++++++--- 14 files changed, 87 insertions(+), 71 deletions(-) diff --git a/services/activity-service/configs/config.docker.yaml b/services/activity-service/configs/config.docker.yaml index 926876ef..dd06506e 100644 --- a/services/activity-service/configs/config.docker.yaml +++ b/services/activity-service/configs/config.docker.yaml @@ -15,7 +15,7 @@ wallet_service_addr: "wallet-service:13004" room_service_addr: "room-service:13001" mysql_auto_migrate: true first_recharge_reward_worker: - enabled: false + enabled: true cumulative_recharge_reward_worker: enabled: true red_packet_broadcast_worker: diff --git a/services/activity-service/configs/config.tencent.example.yaml b/services/activity-service/configs/config.tencent.example.yaml index c39902e7..762118ea 100644 --- a/services/activity-service/configs/config.tencent.example.yaml +++ b/services/activity-service/configs/config.tencent.example.yaml @@ -39,6 +39,7 @@ tencent_im: admin_user_sig_ttl: "24h" endpoint: "adminapisgp.im.qcloud.com" group_type: "ChatRoom" + group_id_prefix: "" request_timeout: "5s" broadcast: enabled: true diff --git a/services/activity-service/configs/config.yaml b/services/activity-service/configs/config.yaml index 29049def..e4ed4067 100644 --- a/services/activity-service/configs/config.yaml +++ b/services/activity-service/configs/config.yaml @@ -15,11 +15,11 @@ wallet_service_addr: "127.0.0.1:13004" room_service_addr: "127.0.0.1:13001" mysql_auto_migrate: true first_recharge_reward_worker: - enabled: false + enabled: true cumulative_recharge_reward_worker: - enabled: false + enabled: true red_packet_broadcast_worker: - enabled: false + enabled: true lucky_gift_worker: enabled: true worker_poll_interval: "1s" @@ -39,6 +39,7 @@ tencent_im: admin_user_sig_ttl: "24h" endpoint: "adminapisgp.im.qcloud.com" group_type: "ChatRoom" + group_id_prefix: "" request_timeout: "5s" broadcast: enabled: true @@ -49,28 +50,28 @@ broadcast: worker_max_retry: 8 ensure_groups_on_startup: true rocketmq: - # 本地默认关闭,room-service 直接 gRPC 投递 activity;开启后消费 room_outbox MQ fanout。 - enabled: false - name_servers: [] + # 本地默认走 RocketMQ fanout,和 Docker/testbox/线上保持同一条 outbox 链路。 + enabled: true + name_servers: ["127.0.0.1:19876"] name_server_domain: "" access_key: "" secret_key: "" namespace: "" room_outbox: - enabled: false + enabled: true topic: "hyapp_room_outbox" consumer_group: "hyapp-activity-room-outbox" consumer_max_reconsume_times: 16 wallet_outbox: - enabled: false + enabled: true topic: "hyapp_wallet_outbox" - realtime_topic: "" + realtime_topic: "hyapp_wallet_realtime_outbox" first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" consumer_max_reconsume_times: 16 user_outbox: - enabled: false + enabled: true topic: "hyapp_user_outbox" consumer_group: "hyapp-activity-user-region-broadcast" consumer_max_reconsume_times: 16 diff --git a/services/activity-service/internal/config/config_test.go b/services/activity-service/internal/config/config_test.go index ab30e4ce..6cfcab6e 100644 --- a/services/activity-service/internal/config/config_test.go +++ b/services/activity-service/internal/config/config_test.go @@ -2,13 +2,17 @@ package config import "testing" -func TestLoadLocalKeepsRocketMQDisabled(t *testing.T) { +func TestLoadLocalEnablesOutboxMQConsumers(t *testing.T) { cfg, err := Load("../../configs/config.yaml") if err != nil { t.Fatalf("Load local config failed: %v", err) } - if cfg.RocketMQ.Enabled || cfg.RocketMQ.RoomOutbox.Enabled || cfg.RocketMQ.UserOutbox.Enabled { - t.Fatalf("local config must not require RocketMQ: %+v", cfg.RocketMQ) + // activity 的充值、红包、礼物和区域播报都依赖 MQ fanout,本地默认必须和测试/线上一致。 + if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || !cfg.RocketMQ.UserOutbox.Enabled { + t.Fatalf("local config must enable all activity outbox MQ consumers: %+v", cfg.RocketMQ) + } + if !cfg.FirstRechargeRewardWorker.Enabled || !cfg.CumulativeRechargeRewardWorker.Enabled || !cfg.RedPacketBroadcastWorker.Enabled { + t.Fatalf("local config must enable wallet outbox workers: first=%+v cumulative=%+v red_packet=%+v", cfg.FirstRechargeRewardWorker, cfg.CumulativeRechargeRewardWorker, cfg.RedPacketBroadcastWorker) } } diff --git a/services/game-service/configs/config.yaml b/services/game-service/configs/config.yaml index ef89384d..7cf66f6a 100644 --- a/services/game-service/configs/config.yaml +++ b/services/game-service/configs/config.yaml @@ -9,8 +9,8 @@ user_service_addr: "127.0.0.1:13005" activity_service_addr: "127.0.0.1:13006" launch_session_ttl: "15m" rocketmq: - enabled: false - name_servers: [] + enabled: true + name_servers: ["127.0.0.1:19876"] name_server_domain: "" access_key: "" secret_key: "" @@ -18,11 +18,11 @@ rocketmq: send_timeout: "3s" retry: 2 game_outbox: - enabled: false + enabled: true topic: "hyapp_game_outbox" producer_group: "hyapp-game-outbox-producer" outbox_worker: - enabled: false + enabled: true poll_interval: "1s" batch_size: 100 publish_timeout: "3s" diff --git a/services/notice-service/configs/config.yaml b/services/notice-service/configs/config.yaml index 0778af28..025da590 100644 --- a/services/notice-service/configs/config.yaml +++ b/services/notice-service/configs/config.yaml @@ -49,25 +49,25 @@ cp_notice_worker: max_backoff: 5m rocketmq: - # 本地默认关闭;开启后 notice 只消费 owner outbox MQ fanout,仍写 notice_delivery_events 做幂等。 - enabled: false - name_servers: [] + # notice 只消费 owner outbox MQ fanout,仍写 notice_delivery_events 做幂等。 + enabled: true + name_servers: ["127.0.0.1:19876"] name_server_domain: "" access_key: "" secret_key: "" namespace: "" wallet_outbox: - enabled: false + enabled: true topic: "hyapp_wallet_outbox" consumer_group: "hyapp-notice-wallet-outbox" consumer_max_reconsume_times: 16 room_outbox: - enabled: false + enabled: true topic: "hyapp_room_outbox" consumer_group: "hyapp-notice-room-outbox" consumer_max_reconsume_times: 16 user_outbox: - enabled: false + enabled: true topic: "hyapp_user_outbox" consumer_group: "hyapp-notice-cp-user-outbox" consumer_max_reconsume_times: 16 diff --git a/services/notice-service/internal/config/config_test.go b/services/notice-service/internal/config/config_test.go index 7b42fdab..60b30179 100644 --- a/services/notice-service/internal/config/config_test.go +++ b/services/notice-service/internal/config/config_test.go @@ -2,13 +2,18 @@ package config import "testing" -func TestLoadLocalKeepsRocketMQDisabled(t *testing.T) { +func TestLoadLocalEnablesOutboxMQConsumers(t *testing.T) { cfg, err := Load("../../configs/config.yaml") if err != nil { t.Fatalf("Load local config failed: %v", err) } - if cfg.RocketMQ.Enabled || cfg.RocketMQ.WalletOutbox.Enabled || cfg.RocketMQ.RoomOutbox.Enabled { - t.Fatalf("local config must not require RocketMQ: %+v", cfg.RocketMQ) + // 本地默认也走 MQ fanout,避免本地、testbox、线上三套 outbox 语义漂移。 + if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || !cfg.RocketMQ.UserOutbox.Enabled { + t.Fatalf("local config must enable all notice outbox MQ consumers: %+v", cfg.RocketMQ) + } + // 直扫 owner DB 的老 worker 已被启动校验禁用,只保留配置参数给离线修复工具复用。 + if cfg.WalletNoticeWorker.Enabled || cfg.RoomNoticeWorker.Enabled || cfg.CPNoticeWorker.Enabled { + t.Fatalf("local config must keep legacy DB poll workers disabled: wallet=%+v room=%+v cp=%+v", cfg.WalletNoticeWorker, cfg.RoomNoticeWorker, cfg.CPNoticeWorker) } } @@ -17,8 +22,8 @@ func TestLoadTencentExampleEnablesOutboxMQConsumers(t *testing.T) { if err != nil { t.Fatalf("Load tencent example failed: %v", err) } - if cfg.WalletNoticeWorker.Enabled || cfg.RoomNoticeWorker.Enabled { - t.Fatalf("tencent example must keep legacy DB poll workers disabled: wallet=%+v room=%+v", cfg.WalletNoticeWorker, cfg.RoomNoticeWorker) + if cfg.WalletNoticeWorker.Enabled || cfg.RoomNoticeWorker.Enabled || cfg.CPNoticeWorker.Enabled { + t.Fatalf("tencent example must keep legacy DB poll workers disabled: wallet=%+v room=%+v cp=%+v", cfg.WalletNoticeWorker, cfg.RoomNoticeWorker, cfg.CPNoticeWorker) } if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || cfg.RocketMQ.WalletOutbox.Topic == "" || cfg.RocketMQ.WalletOutbox.ConsumerGroup == "" { t.Fatalf("tencent example must configure wallet outbox MQ consumer: %+v", cfg.RocketMQ) diff --git a/services/room-service/configs/config.yaml b/services/room-service/configs/config.yaml index a1e2984b..4c850139 100644 --- a/services/room-service/configs/config.yaml +++ b/services/room-service/configs/config.yaml @@ -54,9 +54,9 @@ mic_publish_timeout: "15s" mic_publish_scan_interval: "1s" room_rocket_launch_scan_interval: "1s" rocketmq: - # 本地默认关闭;开启后 room_outbox 可投 MQ,火箭倒计时用延迟消息到点唤醒。 - enabled: false - name_servers: [] + # 本地默认走 RocketMQ fanout,和 Docker/testbox/线上保持同一条 outbox 链路。 + enabled: true + name_servers: ["127.0.0.1:19876"] name_server_domain: "" access_key: "" secret_key: "" @@ -64,27 +64,27 @@ rocketmq: send_timeout: "3s" retry: 2 room_outbox: - enabled: false + enabled: true topic: "hyapp_room_outbox" producer_group: "hyapp-room-outbox-producer" - tencent_im_consumer_enabled: false + tencent_im_consumer_enabled: true tencent_im_consumer_group: "hyapp-room-im-bridge" consumer_max_reconsume_times: 16 rocket_launch: - enabled: false + enabled: true topic: "hyapp_room_rocket_launch" producer_group: "hyapp-room-rocket-launch-producer" consumer_group: "hyapp-room-rocket-launch" consumer_max_reconsume_times: 16 user_outbox: - enabled: false + enabled: true topic: "hyapp_user_outbox" consumer_group: "hyapp-room-user-region-sync" consumer_max_reconsume_times: 16 outbox_worker: - # room_outbox 是腾讯云 IM 和 activity-service 的异步投递通道;失败退避重试,超过上限转死信。 + # room_outbox 是腾讯云 IM 和 activity-service 的异步投递通道;本地也走 MQ,避免和测试/线上链路漂移。 enabled: true - publish_mode: "direct" + publish_mode: "mq" poll_interval: "1s" batch_size: 100 publish_timeout: "3s" diff --git a/services/room-service/internal/config/config_test.go b/services/room-service/internal/config/config_test.go index 72a806ec..c8d70bae 100644 --- a/services/room-service/internal/config/config_test.go +++ b/services/room-service/internal/config/config_test.go @@ -34,11 +34,12 @@ func TestLoad(t *testing.T) { if cfg.WalletServiceAddr != "127.0.0.1:13004" { t.Fatalf("unexpected wallet addr: %s", cfg.WalletServiceAddr) } - if !cfg.OutboxWorker.Enabled || cfg.OutboxWorker.PublishMode != OutboxPublishModeDirect || cfg.OutboxWorker.PollInterval != time.Second || cfg.OutboxWorker.BatchSize != 100 || cfg.OutboxWorker.PublishTimeout != 3*time.Second || cfg.OutboxWorker.RetryStrategy != "exponential_backoff" || cfg.OutboxWorker.MaxRetryCount != 10 || cfg.OutboxWorker.InitialBackoff != 5*time.Second || cfg.OutboxWorker.MaxBackoff != 5*time.Minute { + if !cfg.OutboxWorker.Enabled || cfg.OutboxWorker.PublishMode != OutboxPublishModeMQ || cfg.OutboxWorker.PollInterval != time.Second || cfg.OutboxWorker.BatchSize != 100 || cfg.OutboxWorker.PublishTimeout != 3*time.Second || cfg.OutboxWorker.RetryStrategy != "exponential_backoff" || cfg.OutboxWorker.MaxRetryCount != 10 || cfg.OutboxWorker.InitialBackoff != 5*time.Second || cfg.OutboxWorker.MaxBackoff != 5*time.Minute { t.Fatalf("unexpected outbox worker config: %+v", cfg.OutboxWorker) } - if cfg.RocketMQ.Enabled || cfg.RocketMQ.RoomOutbox.Enabled || cfg.RocketMQ.RocketLaunch.Enabled { - t.Fatalf("local config must not require RocketMQ: %+v", cfg.RocketMQ) + // 本地默认也走 room_outbox MQ、IM bridge 和火箭延迟唤醒,避免本地直投和线上 MQ 语义分叉。 + if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || !cfg.RocketMQ.RoomOutbox.TencentIMConsumerEnabled || !cfg.RocketMQ.RocketLaunch.Enabled || !cfg.RocketMQ.UserOutbox.Enabled { + t.Fatalf("local config must enable room MQ fanout and IM bridge: %+v", cfg.RocketMQ) } } diff --git a/services/statistics-service/configs/config.yaml b/services/statistics-service/configs/config.yaml index c313274f..aa4fd4e6 100644 --- a/services/statistics-service/configs/config.yaml +++ b/services/statistics-service/configs/config.yaml @@ -6,8 +6,8 @@ query_http_addr: ":13010" mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_statistics?parseTime=true&charset=utf8mb4&loc=UTC&multiStatements=true" activity_mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_activity?parseTime=true&charset=utf8mb4&loc=UTC" rocketmq: - enabled: false - name_servers: [] + enabled: true + name_servers: ["127.0.0.1:19876"] name_server_domain: "" access_key: "" secret_key: "" @@ -15,22 +15,22 @@ rocketmq: namespace: "" vip_channel: false wallet_outbox: - enabled: false + enabled: true topic: "hyapp_wallet_outbox" consumer_group: "hyapp-statistics-wallet-outbox" consumer_max_reconsume_times: 16 room_outbox: - enabled: false + enabled: true topic: "hyapp_room_outbox" consumer_group: "hyapp-statistics-room-outbox" consumer_max_reconsume_times: 16 user_outbox: - enabled: false + enabled: true topic: "hyapp_user_outbox" consumer_group: "hyapp-statistics-user-outbox" consumer_max_reconsume_times: 16 game_outbox: - enabled: false + enabled: true topic: "hyapp_game_outbox" consumer_group: "hyapp-statistics-game-outbox" consumer_max_reconsume_times: 16 diff --git a/services/user-service/configs/config.docker.yaml b/services/user-service/configs/config.docker.yaml index 0bb10ef0..76826927 100644 --- a/services/user-service/configs/config.docker.yaml +++ b/services/user-service/configs/config.docker.yaml @@ -24,9 +24,9 @@ third_party: allowed_sign_in_providers: - "google.com" invite_recharge_worker: - enabled: false + enabled: true mic_time_worker: - enabled: false + enabled: true cp_relationship_worker: enabled: true consumer_group: "hyapp-user-cp-room-outbox" @@ -44,7 +44,7 @@ rocketmq: secret_key: "" namespace: "" wallet_outbox: - enabled: false + enabled: true topic: "hyapp_wallet_outbox" consumer_group: "hyapp-user-invite-wallet-outbox" consumer_max_reconsume_times: 16 diff --git a/services/user-service/configs/config.yaml b/services/user-service/configs/config.yaml index 146456b1..e09796fd 100644 --- a/services/user-service/configs/config.yaml +++ b/services/user-service/configs/config.yaml @@ -24,37 +24,37 @@ third_party: allowed_sign_in_providers: - "google.com" invite_recharge_worker: - enabled: false + enabled: true mic_time_worker: - enabled: false + enabled: true cp_relationship_worker: - enabled: false + enabled: true consumer_group: "hyapp-user-cp-room-outbox" outbox_worker: - enabled: false + enabled: true poll_interval: "1s" batch_size: 100 publish_timeout: "3s" rocketmq: - # 本地默认关闭;线上由 wallet/room outbox 投 MQ,各 consumer group 独立幂等消费。 - enabled: false - name_servers: [] + # 本地默认走 RocketMQ fanout,和 Docker/testbox/线上保持同一条 outbox 链路。 + enabled: true + name_servers: ["127.0.0.1:19876"] name_server_domain: "" access_key: "" secret_key: "" namespace: "" wallet_outbox: - enabled: false + enabled: true topic: "hyapp_wallet_outbox" consumer_group: "hyapp-user-invite-wallet-outbox" consumer_max_reconsume_times: 16 room_outbox: - enabled: false + enabled: true topic: "hyapp_room_outbox" consumer_group: "hyapp-user-mictime-room-outbox" consumer_max_reconsume_times: 16 user_outbox: - enabled: false + enabled: true topic: "hyapp_user_outbox" producer_group: "hyapp-user-outbox-producer" login_risk: diff --git a/services/wallet-service/configs/config.yaml b/services/wallet-service/configs/config.yaml index 1d6580af..06e51aa7 100644 --- a/services/wallet-service/configs/config.yaml +++ b/services/wallet-service/configs/config.yaml @@ -18,16 +18,16 @@ red_packet_expiry_worker: poll_interval: "5s" batch_size: 50 projection_worker: - enabled: false + enabled: true poll_interval: "60s" batch_size: 10 lock_ttl: "30s" consumer_group: "hyapp-wallet-projection" consumer_max_reconsume_times: 16 rocketmq: - # 本地默认关闭;线上由 wallet-service outbox worker 投递账务事实到 MQ。 - enabled: false - name_servers: [] + # 本地默认走 RocketMQ fanout,和 Docker/testbox/线上保持同一条 outbox 链路。 + enabled: true + name_servers: ["127.0.0.1:19876"] name_server_domain: "" access_key: "" secret_key: "" @@ -35,11 +35,11 @@ rocketmq: send_timeout: "3s" retry: 2 wallet_outbox: - enabled: false + enabled: true topic: "hyapp_wallet_outbox" producer_group: "hyapp-wallet-outbox-producer" realtime_outbox: - enabled: false + enabled: true topic: "hyapp_wallet_realtime_outbox" producer_group: "hyapp-wallet-realtime-outbox-producer" google_play: @@ -52,7 +52,7 @@ google_play: http_timeout: "10s" consume_enabled: true outbox_worker: - enabled: false + enabled: true poll_interval: "1s" batch_size: 100 concurrency: 1 @@ -61,7 +61,7 @@ outbox_worker: initial_backoff: "5s" max_backoff: "5m" realtime_outbox_worker: - enabled: false + enabled: true poll_interval: "500ms" batch_size: 50 concurrency: 2 diff --git a/services/wallet-service/internal/config/config_test.go b/services/wallet-service/internal/config/config_test.go index 4b13530c..73516c24 100644 --- a/services/wallet-service/internal/config/config_test.go +++ b/services/wallet-service/internal/config/config_test.go @@ -2,13 +2,17 @@ package config import "testing" -func TestLoadLocalKeepsOutboxMQDisabled(t *testing.T) { +func TestLoadLocalEnablesOutboxMQ(t *testing.T) { cfg, err := Load("../../configs/config.yaml") if err != nil { t.Fatalf("Load local config failed: %v", err) } - if cfg.RocketMQ.Enabled || cfg.RocketMQ.WalletOutbox.Enabled || cfg.RocketMQ.RealtimeOutbox.Enabled { - t.Fatalf("local config must not require RocketMQ: %+v", cfg.RocketMQ) + // 本地默认发布普通钱包事实和实时红包事实,链路和 Docker/testbox/线上保持一致。 + if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || !cfg.RocketMQ.RealtimeOutbox.Enabled { + t.Fatalf("local config must enable wallet outbox MQ producers: %+v", cfg.RocketMQ) + } + if !cfg.OutboxWorker.Enabled || !cfg.RealtimeOutboxWorker.Enabled || !cfg.ProjectionWorker.Enabled { + t.Fatalf("local config must enable wallet outbox workers: outbox=%+v realtime=%+v projection=%+v", cfg.OutboxWorker, cfg.RealtimeOutboxWorker, cfg.ProjectionWorker) } if cfg.OutboxWorker.Concurrency != 1 || cfg.RealtimeOutboxWorker.Concurrency != 2 { t.Fatalf("local outbox worker defaults mismatch: outbox=%+v realtime=%+v", cfg.OutboxWorker, cfg.RealtimeOutboxWorker) From 7f16956b7d374970e680c8454ae1cb829c46c6c3 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 13:14:25 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E7=A4=BC=E7=89=A9=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/modules/resource/handler.go | 8 + .../modules/resource/operator_test.go | 142 ++++++++++++ .../internal/modules/resource/request.go | 209 +++++++++++++++++- .../storage/mysql/cp_breakup_fee_test.go | 76 ------- 4 files changed, 353 insertions(+), 82 deletions(-) delete mode 100644 services/wallet-service/internal/storage/mysql/cp_breakup_fee_test.go diff --git a/server/admin/internal/modules/resource/handler.go b/server/admin/internal/modules/resource/handler.go index 323bc86e..f06a284e 100644 --- a/server/admin/internal/modules/resource/handler.go +++ b/server/admin/internal/modules/resource/handler.go @@ -95,6 +95,10 @@ func (h *Handler) CreateResource(c *gin.Context) { response.BadRequest(c, err.Error()) return } + if err := validateResourceMetadata(req); err != nil { + response.BadRequest(c, err.Error()) + return + } resp, err := h.wallet.CreateResource(c.Request.Context(), req.createProto(c)) if err != nil { response.BadRequest(c, err.Error()) @@ -203,6 +207,10 @@ func (h *Handler) UpdateResource(c *gin.Context) { response.BadRequest(c, err.Error()) return } + if err := validateResourceMetadata(req); err != nil { + response.BadRequest(c, err.Error()) + return + } protoReq := req.updateProto(c, resourceID) resp, err := h.wallet.UpdateResource(c.Request.Context(), protoReq) if err != nil { diff --git a/server/admin/internal/modules/resource/operator_test.go b/server/admin/internal/modules/resource/operator_test.go index a60a2c9b..5464fd7b 100644 --- a/server/admin/internal/modules/resource/operator_test.go +++ b/server/admin/internal/modules/resource/operator_test.go @@ -2,6 +2,7 @@ package resource import ( "encoding/json" + "strconv" "strings" "testing" ) @@ -150,6 +151,131 @@ func TestProfileCardResourceMetadataKeepsLayoutOnly(t *testing.T) { } } +func TestResourceMetadataKeepsMP4AlphaLayoutForAnimatedResources(t *testing.T) { + for _, resourceType := range []string{"gift", "vehicle", "avatar_frame", "chat_bubble", "floating_screen", "mic_seat_animation"} { + metadata := resourceMetadataJSON(resourceType, "", mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true), "", "") + payload := resourceMetadataPayload{} + if err := json.Unmarshal([]byte(metadata), &payload); err != nil { + t.Fatalf("%s mp4 metadata json mismatch: %v", resourceType, err) + } + if payload.AnimationFormat != resourceAnimationFormatMP4 || payload.MP4AlphaLayout == nil { + t.Fatalf("%s mp4 metadata should be kept: %s", resourceType, metadata) + } + if payload.MP4AlphaLayout.AlphaLayout != mp4AlphaLayoutAlphaLeftRGBRight || payload.MP4AlphaLayout.RGBFrame[0] != 750 { + t.Fatalf("%s mp4 layout mismatch: %+v", resourceType, payload.MP4AlphaLayout) + } + } +} + +func TestResourceMetadataKeepsNormalMP4Layout(t *testing.T) { + metadata := resourceMetadataJSON("gift", "", `{ + "animation_format": "mp4", + "mp4_alpha_layout": { + "alpha_layout": "normal", + "video_w": 750, + "video_h": 1334, + "rgb_frame": [1, 2, 3, 4], + "alpha_frame": null, + "confirmed": true, + "detect_version": 1 + } + }`, "", "") + payload := resourceMetadataPayload{} + if err := json.Unmarshal([]byte(metadata), &payload); err != nil { + t.Fatalf("normal mp4 metadata json mismatch: %v", err) + } + if payload.MP4AlphaLayout == nil || payload.MP4AlphaLayout.AlphaLayout != mp4AlphaLayoutNormal { + t.Fatalf("normal mp4 metadata should be kept: %s", metadata) + } + if payload.MP4AlphaLayout.AlphaFrame != nil || payload.MP4AlphaLayout.RGBFrame[2] != 750 || payload.MP4AlphaLayout.RGBFrame[3] != 1334 { + t.Fatalf("normal mp4 layout should be normalized to full frame: %+v", payload.MP4AlphaLayout) + } +} + +func TestProfileCardResourceMetadataKeepsLayoutAndMP4AlphaLayout(t *testing.T) { + metadata := resourceMetadataJSON(resourceTypeProfileCard, "", `{ + "profile_card_layout": { + "source_width": 1136, + "source_height": 1680, + "color_content_width": 750, + "content_top": 117, + "content_bottom": 1666, + "content_height": 1550, + "content_top_ratio": 0, + "content_height_ratio": 0, + "detect_version": 99 + }, + "mp4_alpha_layout": { + "alpha_layout": "rgb_left_alpha_right", + "video_w": 1472, + "video_h": 1328, + "rgb_frame": [0, 0, 736, 1328], + "alpha_frame": [736, 0, 736, 1328], + "confirmed": true, + "detect_version": 1 + } + }`, "", "") + payload := resourceMetadataPayload{} + if err := json.Unmarshal([]byte(metadata), &payload); err != nil { + t.Fatalf("profile card mp4 metadata json mismatch: %v", err) + } + if payload.ProfileCardLayout == nil || payload.ProfileCardLayout.ContentTop != 117 { + t.Fatalf("profile card layout should be kept: %+v", payload.ProfileCardLayout) + } + if payload.MP4AlphaLayout == nil || payload.MP4AlphaLayout.AlphaLayout != mp4AlphaLayoutRGBLeftAlphaRight { + t.Fatalf("profile card mp4 layout should be kept: %+v", payload.MP4AlphaLayout) + } +} + +func TestValidateResourceMetadataRejectsInvalidMP4AlphaLayout(t *testing.T) { + cases := []struct { + name string + req resourceRequest + }{ + { + name: "unconfirmed", + req: resourceRequest{ + ResourceType: "gift", + MetadataJSON: mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", false), + }, + }, + { + name: "bad rect", + req: resourceRequest{ + ResourceType: "gift", + MetadataJSON: `{ + "mp4_alpha_layout": { + "alpha_layout": "alpha_left_rgb_right", + "video_w": 1500, + "video_h": 1334, + "rgb_frame": [750, 0, 800, 1334], + "alpha_frame": [0, 0, 750, 1334], + "confirmed": true, + "detect_version": 1 + } + }`, + }, + }, + { + name: "unsupported type", + req: resourceRequest{ + ResourceType: resourceTypeBadge, + MetadataJSON: mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true), + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if err := validateResourceMetadata(tc.req); err == nil { + t.Fatalf("invalid mp4 metadata should be rejected") + } + }) + } + if err := validateResourceMetadata(resourceRequest{ResourceType: "gift", MetadataJSON: mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true)}); err != nil { + t.Fatalf("valid mp4 metadata rejected: %v", err) + } +} + func TestProfileCardResourceMetadataRejectsInvalidLayout(t *testing.T) { metadata := resourceMetadataJSON(resourceTypeProfileCard, "", `{ "profile_card_layout": { @@ -182,3 +308,19 @@ func TestValidateBadgeResourceRequiresBadgeForm(t *testing.T) { t.Fatalf("non-badge resource should not require badge form: %v", err) } } + +func mp4AlphaLayoutMetadataJSON(alphaLayout string, confirmed bool) string { + return `{ + "animation_format": "mp4", + "mp4_alpha_layout": { + "alpha_layout": "` + alphaLayout + `", + "video_w": 1500, + "video_h": 1334, + "rgb_frame": [750, 0, 750, 1334], + "alpha_frame": [0, 0, 750, 1334], + "confirmed": ` + strconv.FormatBool(confirmed) + `, + "detect_version": 1 + }, + "debug": true + }` +} diff --git a/server/admin/internal/modules/resource/request.go b/server/admin/internal/modules/resource/request.go index fa896263..f54d7f91 100644 --- a/server/admin/internal/modules/resource/request.go +++ b/server/admin/internal/modules/resource/request.go @@ -24,6 +24,20 @@ const ( resourceTypeProfileCard = "profile_card" ) +const ( + resourceAnimationFormatMP4 = "mp4" +) + +const ( + mp4AlphaLayoutNormal = "normal" + mp4AlphaLayoutAlphaLeftRGBRight = "alpha_left_rgb_right" + mp4AlphaLayoutRGBLeftAlphaRight = "rgb_left_alpha_right" + mp4AlphaLayoutAlphaTopRGBBottom = "alpha_top_rgb_bottom" + mp4AlphaLayoutRGBTopAlphaBottom = "rgb_top_alpha_bottom" + mp4AlphaLayoutCustom = "custom" + mp4AlphaLayoutDetectVersion int64 = 1 +) + const ( resourcePriceTypeCoin = "coin" resourcePriceTypeFree = "free" @@ -88,6 +102,23 @@ type badgeMetadataPayload struct { DefaultSlot string `json:"default_slot,omitempty"` } +type resourceMetadataPayload struct { + AnimationFormat string `json:"animation_format,omitempty"` + MP4AlphaLayout *mp4AlphaLayoutMetadataPayload `json:"mp4_alpha_layout,omitempty"` + ProfileCardLayout *profileCardLayoutMetadataPayload `json:"profile_card_layout,omitempty"` +} + +type mp4AlphaLayoutMetadataPayload struct { + AlphaLayout string `json:"alpha_layout"` + VideoWidth int64 `json:"video_w"` + VideoHeight int64 `json:"video_h"` + RGBFrame []int64 `json:"rgb_frame"` + AlphaFrame []int64 `json:"alpha_frame"` + Confirmed bool `json:"confirmed"` + Confidence float64 `json:"confidence,omitempty"` + DetectVersion int64 `json:"detect_version"` +} + type profileCardMetadataPayload struct { ProfileCardLayout *profileCardLayoutMetadataPayload `json:"profile_card_layout,omitempty"` } @@ -559,6 +590,39 @@ func validateResourceBadgeForm(req resourceRequest) error { return nil } +func validateResourceMetadata(req resourceRequest) error { + resourceType := normalizeResourceType(req.ResourceType) + payload, fields, err := parseResourceMetadataPayload(req.MetadataJSON) + if err != nil { + return fmt.Errorf("资源元数据格式不正确") + } + if payload == nil { + return nil + } + if rawFormat, ok := fields["animation_format"]; ok && len(rawFormat) > 0 { + animationFormat := strings.ToLower(strings.TrimSpace(payload.AnimationFormat)) + if animationFormat != "" && animationFormat != resourceAnimationFormatMP4 { + return fmt.Errorf("资源动效格式不支持") + } + } + if _, ok := fields["mp4_alpha_layout"]; !ok { + return nil + } + if !resourceAllowsAnimationMetadata(resourceType) { + return fmt.Errorf("当前资源类型不支持 MP4 透明布局") + } + layout, err := sanitizeMP4AlphaLayoutMetadata(payload.MP4AlphaLayout) + if err != nil { + return err + } + // MP4 透明布局是客户端合成协议,后台只能保存人工确认后的显式矩形, + // 否则 Flutter 会重新落回不稳定的自动识别路径,黑场或弱 mask 素材仍会展示错误。 + if !layout.Confirmed { + return fmt.Errorf("请确认 MP4 透明布局") + } + return nil +} + func resourceGrantStrategy(resourceType string) string { switch resourceType { case resourceTypeCoin: @@ -573,14 +637,25 @@ func resourceGrantStrategy(resourceType string) string { } func resourceMetadataJSON(resourceType string, badgeForm string, metadataJSON string, badgeKind string, levelTrack string) string { - switch resourceType { - case resourceTypeBadge: + if resourceType == resourceTypeBadge { return badgeMetadataJSON(badgeForm, badgeKind, levelTrack) - case resourceTypeProfileCard: - return profileCardMetadataJSON(metadataJSON) - default: + } + + rawPayload, _, err := parseResourceMetadataPayload(metadataJSON) + if err != nil || rawPayload == nil { return "{}" } + payload := resourceMetadataPayload{} + if resourceType == resourceTypeProfileCard { + payload.ProfileCardLayout = sanitizeProfileCardLayoutMetadata(rawPayload.ProfileCardLayout) + } + if resourceAllowsAnimationMetadata(resourceType) { + if layout, err := sanitizeMP4AlphaLayoutMetadata(rawPayload.MP4AlphaLayout); err == nil && layout != nil && layout.Confirmed { + payload.AnimationFormat = resourceAnimationFormatMP4 + payload.MP4AlphaLayout = layout + } + } + return marshalResourceMetadataPayload(payload) } func badgeMetadataJSON(badgeForm string, badgeKind string, levelTrack string) string { @@ -611,7 +686,7 @@ func profileCardMetadataJSON(metadataJSON string) string { if raw == "" { return "{}" } - payload := profileCardMetadataPayload{} + payload := resourceMetadataPayload{} if err := json.Unmarshal([]byte(raw), &payload); err != nil { return "{}" } @@ -627,6 +702,128 @@ func profileCardMetadataJSON(metadataJSON string) string { return string(body) } +func parseResourceMetadataPayload(metadataJSON string) (*resourceMetadataPayload, map[string]json.RawMessage, error) { + raw := strings.TrimSpace(metadataJSON) + if raw == "" { + return nil, nil, nil + } + fields := map[string]json.RawMessage{} + if err := json.Unmarshal([]byte(raw), &fields); err != nil { + return nil, nil, err + } + payload := resourceMetadataPayload{} + if err := json.Unmarshal([]byte(raw), &payload); err != nil { + return nil, nil, err + } + return &payload, fields, nil +} + +func marshalResourceMetadataPayload(payload resourceMetadataPayload) string { + if payload.AnimationFormat == "" && payload.MP4AlphaLayout == nil && payload.ProfileCardLayout == nil { + return "{}" + } + body, err := json.Marshal(payload) + if err != nil { + return "{}" + } + return string(body) +} + +func sanitizeMP4AlphaLayoutMetadata(layout *mp4AlphaLayoutMetadataPayload) (*mp4AlphaLayoutMetadataPayload, error) { + if layout == nil { + return nil, fmt.Errorf("MP4 透明布局参数不完整") + } + videoWidth := layout.VideoWidth + videoHeight := layout.VideoHeight + if videoWidth <= 0 || videoHeight <= 0 { + return nil, fmt.Errorf("MP4 视频宽高不正确") + } + alphaLayout := normalizeMP4AlphaLayout(layout.AlphaLayout) + if alphaLayout == "" { + return nil, fmt.Errorf("MP4 透明布局类型不支持") + } + detectVersion := layout.DetectVersion + if detectVersion <= 0 { + detectVersion = mp4AlphaLayoutDetectVersion + } + confidence := layout.Confidence + if confidence < 0 { + confidence = 0 + } + if confidence > 1 { + confidence = 1 + } + + if alphaLayout == mp4AlphaLayoutNormal { + // normal 也是显式布局:RGB 使用完整视频帧,alpha_frame 固定为 null, + // 这样 native 层能明确跳过 split-alpha 猜测,普通 MP4 不再被误判。 + return &mp4AlphaLayoutMetadataPayload{ + AlphaLayout: mp4AlphaLayoutNormal, + VideoWidth: videoWidth, + VideoHeight: videoHeight, + RGBFrame: []int64{0, 0, videoWidth, videoHeight}, + AlphaFrame: nil, + Confirmed: layout.Confirmed, + Confidence: confidence, + DetectVersion: detectVersion, + }, nil + } + if !validMP4Frame(layout.RGBFrame, videoWidth, videoHeight) || !validMP4Frame(layout.AlphaFrame, videoWidth, videoHeight) { + return nil, fmt.Errorf("MP4 透明布局矩形不正确") + } + // 透明 MP4 的 RGB 与 alpha 矩形直接影响客户端 shader 取样,后端只做边界归一化, + // 不把字段重算成左右或上下默认值,避免覆盖前端人工确认过的 custom 细节。 + return &mp4AlphaLayoutMetadataPayload{ + AlphaLayout: alphaLayout, + VideoWidth: videoWidth, + VideoHeight: videoHeight, + RGBFrame: append([]int64(nil), layout.RGBFrame[:4]...), + AlphaFrame: append([]int64(nil), layout.AlphaFrame[:4]...), + Confirmed: layout.Confirmed, + Confidence: confidence, + DetectVersion: detectVersion, + }, nil +} + +func validMP4Frame(frame []int64, videoWidth int64, videoHeight int64) bool { + if len(frame) < 4 { + return false + } + left, top, width, height := frame[0], frame[1], frame[2], frame[3] + if left < 0 || top < 0 || width <= 0 || height <= 0 { + return false + } + return left+width <= videoWidth && top+height <= videoHeight +} + +func normalizeMP4AlphaLayout(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case mp4AlphaLayoutNormal: + return mp4AlphaLayoutNormal + case mp4AlphaLayoutAlphaLeftRGBRight: + return mp4AlphaLayoutAlphaLeftRGBRight + case mp4AlphaLayoutRGBLeftAlphaRight: + return mp4AlphaLayoutRGBLeftAlphaRight + case mp4AlphaLayoutAlphaTopRGBBottom: + return mp4AlphaLayoutAlphaTopRGBBottom + case mp4AlphaLayoutRGBTopAlphaBottom: + return mp4AlphaLayoutRGBTopAlphaBottom + case mp4AlphaLayoutCustom: + return mp4AlphaLayoutCustom + default: + return "" + } +} + +func resourceAllowsAnimationMetadata(resourceType string) bool { + switch resourceType { + case "gift", "vehicle", "avatar_frame", "chat_bubble", "floating_screen", resourceTypeProfileCard: + return true + default: + return strings.HasPrefix(resourceType, "mic_seat_") + } +} + func sanitizeProfileCardLayoutMetadata(layout *profileCardLayoutMetadataPayload) *profileCardLayoutMetadataPayload { if layout == nil { return nil diff --git a/services/wallet-service/internal/storage/mysql/cp_breakup_fee_test.go b/services/wallet-service/internal/storage/mysql/cp_breakup_fee_test.go deleted file mode 100644 index 1dadeabf..00000000 --- a/services/wallet-service/internal/storage/mysql/cp_breakup_fee_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package mysql_test - -import ( - "context" - "testing" - - "hyapp/pkg/appcode" - "hyapp/pkg/xerr" - "hyapp/services/wallet-service/internal/domain/ledger" - "hyapp/services/wallet-service/internal/testutil/mysqltest" -) - -func TestDebitCPBreakupFeeDebitsCoinAndReplaysIdempotently(t *testing.T) { - repo := mysqltest.NewRepository(t) - ctx := appcode.WithContext(context.Background(), appcode.Default) - repo.SetBalance(7001, 1000) - - command := ledger.CPBreakupFeeCommand{ - AppCode: appcode.Default, - CommandID: "cp_break_fee_1", - UserID: 7001, - RelationshipID: "cp_rel:7001:7002", - RelationType: "cp", - Amount: 88, - } - first, err := repo.DebitCPBreakupFee(ctx, command) - if err != nil { - t.Fatalf("debit cp breakup fee failed: %v", err) - } - if first.CoinSpent != 88 || first.CoinBalanceAfter != 912 || first.Balance.AvailableAmount != 912 { - t.Fatalf("receipt mismatch: %+v", first) - } - if repo.CountRows("wallet_transactions", "command_id = ?", command.CommandID) != 1 { - t.Fatalf("expected one wallet transaction") - } - if repo.CountRows("wallet_entries", "transaction_id = ?", first.TransactionID) != 1 { - t.Fatalf("expected one wallet entry") - } - - second, err := repo.DebitCPBreakupFee(ctx, command) - if err != nil { - t.Fatalf("debit cp breakup fee replay failed: %v", err) - } - if second.TransactionID != first.TransactionID || second.CoinBalanceAfter != first.CoinBalanceAfter { - t.Fatalf("idempotent replay mismatch: first=%+v second=%+v", first, second) - } - if repo.CountRows("wallet_entries", "transaction_id = ?", first.TransactionID) != 1 { - t.Fatalf("idempotent replay must not create another entry") - } - - command.Amount = 89 - if _, err := repo.DebitCPBreakupFee(ctx, command); xerr.CodeOf(err) != xerr.LedgerConflict { - t.Fatalf("same command with different amount must conflict, got %v", err) - } -} - -func TestDebitCPBreakupFeeRejectsInsufficientBalance(t *testing.T) { - repo := mysqltest.NewRepository(t) - ctx := appcode.WithContext(context.Background(), appcode.Default) - repo.SetBalance(7101, 10) - - _, err := repo.DebitCPBreakupFee(ctx, ledger.CPBreakupFeeCommand{ - AppCode: appcode.Default, - CommandID: "cp_break_fee_insufficient", - UserID: 7101, - RelationshipID: "cp_rel:7101:7102", - RelationType: "sister", - Amount: 20, - }) - if xerr.CodeOf(err) != xerr.InsufficientBalance { - t.Fatalf("insufficient balance must be rejected, got %v", err) - } - if repo.CountRows("wallet_transactions", "command_id = ?", "cp_break_fee_insufficient") != 0 { - t.Fatalf("insufficient balance must not create transaction") - } -} From 3bb3f3364fd99910974e136fba896e0574ae45f3 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 13:41:32 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E6=89=B9=E9=87=8F=E9=87=8D=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/modules/resource/handler.go | 178 ++++++++++++++++++ .../internal/modules/resource/handler_test.go | 169 +++++++++++++++++ .../internal/modules/resource/request.go | 9 + .../admin/internal/modules/resource/routes.go | 1 + 4 files changed, 357 insertions(+) create mode 100644 server/admin/internal/modules/resource/handler_test.go diff --git a/server/admin/internal/modules/resource/handler.go b/server/admin/internal/modules/resource/handler.go index f06a284e..dfe9d604 100644 --- a/server/admin/internal/modules/resource/handler.go +++ b/server/admin/internal/modules/resource/handler.go @@ -222,6 +222,71 @@ func (h *Handler) UpdateResource(c *gin.Context) { response.OK(c, resource) } +func (h *Handler) UpdateMP4ResourceLayouts(c *gin.Context) { + var req resourceMP4LayoutBatchRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "MP4 布局参数不正确") + return + } + items, err := normalizeMP4LayoutBatchItems(req.Items) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + + ctx, cancel := h.walletRequestContext(c) + defer cancel() + type preparedMP4LayoutUpdate struct { + resource *walletv1.Resource + metadataJSON string + } + prepared := make([]preparedMP4LayoutUpdate, 0, len(items)) + for _, item := range items { + resp, err := h.wallet.GetResource(ctx, &walletv1.GetResourceRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + ResourceId: item.ResourceID, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + current := resp.GetResource() + if current.GetResourceId() == 0 { + response.BadRequest(c, "资源不存在") + return + } + // 批量接口只更新真实 MP4 动效资源;非 MP4 行按产品要求自动跳过, + // 这样前端全选资源后提交到后端,也不会把图片、SVGA、PAG 的 metadata 写坏。 + if !resourceAnimationSourceIsMP4(current) { + continue + } + metadataJSON, err := mp4LayoutMetadataForResource(current, item.MetadataJSON) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + prepared = append(prepared, preparedMP4LayoutUpdate{resource: current, metadataJSON: metadataJSON}) + } + + updated := make([]resourceDTO, 0, len(prepared)) + updatedIDs := make([]string, 0, len(prepared)) + for _, item := range prepared { + // wallet-service 只有全量资源更新 RPC,所以这里用当前资源还原所有业务字段, + // 只替换 metadata_json,避免批量重算 MP4 时顺手覆盖价格、状态、资产或经理赠送配置。 + resp, err := h.wallet.UpdateResource(ctx, updateResourceMP4MetadataProto(c, item.resource, item.metadataJSON)) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + resource := resourceFromProto(resp.GetResource()) + updated = append(updated, resource) + updatedIDs = append(updatedIDs, fmt.Sprintf("%d", resource.ResourceID)) + } + h.auditLog(c, "update-resource-mp4-layouts", "resources", "batch", "success", strings.Join(updatedIDs, ",")) + response.OK(c, updated) +} + func (h *Handler) EnableResource(c *gin.Context) { h.setResourceStatus(c, "active") } @@ -732,6 +797,119 @@ func actorID(c *gin.Context) int64 { return int64(shared.ActorFromContext(c).UserID) } +func normalizeMP4LayoutBatchItems(items []resourceMP4LayoutUpdateRequest) ([]resourceMP4LayoutUpdateRequest, error) { + const maxMP4LayoutBatchSize = 50 + if len(items) == 0 { + return nil, fmt.Errorf("请选择要更新的 MP4 资源") + } + if len(items) > maxMP4LayoutBatchSize { + return nil, fmt.Errorf("单次最多更新 %d 个 MP4 资源", maxMP4LayoutBatchSize) + } + seen := map[int64]struct{}{} + normalized := make([]resourceMP4LayoutUpdateRequest, 0, len(items)) + for _, item := range items { + if item.ResourceID <= 0 { + return nil, fmt.Errorf("资源 ID 不正确") + } + if _, ok := seen[item.ResourceID]; ok { + continue + } + seen[item.ResourceID] = struct{}{} + normalized = append(normalized, resourceMP4LayoutUpdateRequest{ + ResourceID: item.ResourceID, + MetadataJSON: strings.TrimSpace(item.MetadataJSON), + }) + } + if len(normalized) == 0 { + return nil, fmt.Errorf("请选择要更新的 MP4 资源") + } + return normalized, nil +} + +func resourceAnimationSourceIsMP4(item *walletv1.Resource) bool { + if item == nil { + return false + } + return resourceSourceIsMP4(item.GetAnimationUrl()) || resourceSourceIsMP4(item.GetAssetUrl()) +} + +func resourceSourceIsMP4(value string) bool { + source := strings.TrimSpace(value) + if source == "" { + return false + } + if before, _, ok := strings.Cut(source, "?"); ok { + source = before + } + if before, _, ok := strings.Cut(source, "#"); ok { + source = before + } + return strings.HasSuffix(strings.ToLower(source), ".mp4") +} + +func mp4LayoutMetadataForResource(current *walletv1.Resource, metadataJSON string) (string, error) { + incoming, fields, err := parseResourceMetadataPayload(metadataJSON) + if err != nil { + return "", fmt.Errorf("资源元数据格式不正确") + } + if incoming == nil { + return "", fmt.Errorf("MP4 透明布局参数不完整") + } + if _, ok := fields["mp4_alpha_layout"]; !ok { + return "", fmt.Errorf("MP4 透明布局参数不完整") + } + if err := validateResourceMetadata(resourceRequest{ + ResourceType: current.GetResourceType(), + MetadataJSON: metadataJSON, + }); err != nil { + return "", err + } + layout, err := sanitizeMP4AlphaLayoutMetadata(incoming.MP4AlphaLayout) + if err != nil { + return "", err + } + // 这个接口是“只更新 MP4 布局”的窄接口,所以其它 metadata 不信任前端请求; + // 资料卡布局从当前资源保留,MP4 布局使用本次重算结果,最终再走统一 marshal 过滤空字段。 + merged := resourceMetadataPayload{ + AnimationFormat: resourceAnimationFormatMP4, + MP4AlphaLayout: layout, + } + if current.GetResourceType() == resourceTypeProfileCard { + currentPayload, _, _ := parseResourceMetadataPayload(current.GetMetadataJson()) + if currentPayload != nil { + merged.ProfileCardLayout = sanitizeProfileCardLayoutMetadata(currentPayload.ProfileCardLayout) + } + } + return marshalResourceMetadataPayload(merged), nil +} + +func updateResourceMP4MetadataProto(c *gin.Context, current *walletv1.Resource, metadataJSON string) *walletv1.UpdateResourceRequest { + managerGrantEnabled := current.GetManagerGrantEnabled() + return &walletv1.UpdateResourceRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + ResourceId: current.GetResourceId(), + ResourceCode: current.GetResourceCode(), + ResourceType: current.GetResourceType(), + Name: current.GetName(), + Status: current.GetStatus(), + Grantable: current.GetGrantable(), + GrantStrategy: current.GetGrantStrategy(), + WalletAssetType: current.GetWalletAssetType(), + WalletAssetAmount: current.GetWalletAssetAmount(), + UsageScopes: append([]string(nil), current.GetUsageScopes()...), + AssetUrl: current.GetAssetUrl(), + PreviewUrl: current.GetPreviewUrl(), + AnimationUrl: current.GetAnimationUrl(), + MetadataJson: metadataJSON, + SortOrder: current.GetSortOrder(), + OperatorUserId: actorID(c), + ManagerGrantEnabled: &managerGrantEnabled, + PriceType: current.GetPriceType(), + CoinPrice: current.GetCoinPrice(), + } +} + func parseID(c *gin.Context, name string) (int64, bool) { raw := strings.TrimSpace(c.Param(name)) value, err := strconv.ParseInt(raw, 10, 64) diff --git a/server/admin/internal/modules/resource/handler_test.go b/server/admin/internal/modules/resource/handler_test.go new file mode 100644 index 00000000..7d674ecc --- /dev/null +++ b/server/admin/internal/modules/resource/handler_test.go @@ -0,0 +1,169 @@ +package resource + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/integration/walletclient" + "hyapp-admin-server/internal/middleware" + walletv1 "hyapp.local/api/proto/wallet/v1" + + "github.com/gin-gonic/gin" +) + +func TestUpdateMP4ResourceLayoutsPreservesResourceFields(t *testing.T) { + wallet := &mockResourceWallet{resources: map[int64]*walletv1.Resource{ + 11: { + AppCode: "lalu", + ResourceId: 11, + ResourceCode: "profile_card_star", + ResourceType: resourceTypeProfileCard, + Name: "星光资料卡", + Status: "active", + Grantable: true, + GrantStrategy: "extend_expiry", + WalletAssetType: "RESOURCE", + WalletAssetAmount: 1, + UsageScopes: []string{resourceTypeProfileCard}, + AssetUrl: "https://cdn.example.test/profile_card.png", + PreviewUrl: "https://cdn.example.test/profile_card_cover.png", + AnimationUrl: "https://cdn.example.test/profile_card.mp4?token=1", + MetadataJson: `{"profile_card_layout":{"source_width":1136,"source_height":1680,"color_content_width":750,"content_top":117,"content_bottom":1666,"content_height":1550,"detect_version":1}}`, + SortOrder: 9, + ManagerGrantEnabled: true, + PriceType: resourcePriceTypeCoin, + CoinPrice: 100, + }, + }} + router := newResourceHandlerTestRouter(New(wallet, nil, nil, time.Second, nil)) + metadataJSON := `{ + "animation_format":"mp4", + "mp4_alpha_layout":{ + "alpha_layout":"alpha_left_rgb_right", + "video_w":1500, + "video_h":1334, + "rgb_frame":[750,0,750,1334], + "alpha_frame":[0,0,750,1334], + "confirmed":true, + "detect_version":1 + }, + "debug":true + }` + body := fmt.Sprintf(`{"items":[{"resourceId":11,"metadataJson":%q}]}`, metadataJSON) + + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPut, "/admin/resources/mp4-layouts", strings.NewReader(body)) + request.Header.Set("Content-Type", "application/json") + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("batch mp4 layout update status mismatch: %d %s", recorder.Code, recorder.Body.String()) + } + if len(wallet.updates) != 1 { + t.Fatalf("expected one wallet update, got %d", len(wallet.updates)) + } + update := wallet.updates[0] + if update.GetResourceId() != 11 || update.GetName() != "星光资料卡" || update.GetStatus() != "active" || update.GetCoinPrice() != 100 { + t.Fatalf("resource fields should be preserved: %+v", update) + } + if update.ManagerGrantEnabled == nil || !update.GetManagerGrantEnabled() || update.GetOperatorUserId() != 7 { + t.Fatalf("operator and manager grant should be preserved: %+v", update) + } + if !strings.Contains(update.GetMetadataJson(), `"profile_card_layout"`) || !strings.Contains(update.GetMetadataJson(), `"mp4_alpha_layout"`) { + t.Fatalf("profile card and mp4 metadata should both be kept: %s", update.GetMetadataJson()) + } + if strings.Contains(update.GetMetadataJson(), "debug") { + t.Fatalf("debug metadata should be dropped: %s", update.GetMetadataJson()) + } +} + +func TestUpdateMP4ResourceLayoutsIgnoresNonMP4Resource(t *testing.T) { + wallet := &mockResourceWallet{resources: map[int64]*walletv1.Resource{ + 12: { + AppCode: "lalu", + ResourceId: 12, + ResourceCode: "gift_rose", + ResourceType: "gift", + Name: "玫瑰", + Status: "active", + AnimationUrl: "https://cdn.example.test/gift.svga", + MetadataJson: "{}", + GrantStrategy: "increase_quantity", + PriceType: resourcePriceTypeCoin, + CoinPrice: 10, + }, + }} + router := newResourceHandlerTestRouter(New(wallet, nil, nil, time.Second, nil)) + body := fmt.Sprintf(`{"items":[{"resourceId":12,"metadataJson":%q}]}`, mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true)) + + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPut, "/admin/resources/mp4-layouts", strings.NewReader(body)) + request.Header.Set("Content-Type", "application/json") + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("non-mp4 resource should be ignored, got %d %s", recorder.Code, recorder.Body.String()) + } + if len(wallet.updates) != 0 { + t.Fatalf("non-mp4 resource should not update wallet") + } +} + +func newResourceHandlerTestRouter(handler *Handler) *gin.Engine { + gin.SetMode(gin.TestMode) + router := gin.New() + router.Use(func(c *gin.Context) { + c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "lalu")) + c.Set(middleware.ContextRequestID, "resource-handler-test") + c.Set(middleware.ContextUserID, uint(7)) + c.Set(middleware.ContextUsername, "tester") + c.Next() + }) + router.PUT("/admin/resources/mp4-layouts", handler.UpdateMP4ResourceLayouts) + return router +} + +type mockResourceWallet struct { + walletclient.Client + resources map[int64]*walletv1.Resource + updates []*walletv1.UpdateResourceRequest +} + +func (m *mockResourceWallet) GetResource(ctx context.Context, req *walletv1.GetResourceRequest) (*walletv1.GetResourceResponse, error) { + resource := m.resources[req.GetResourceId()] + if resource == nil { + return nil, fmt.Errorf("resource not found") + } + return &walletv1.GetResourceResponse{Resource: resource}, nil +} + +func (m *mockResourceWallet) UpdateResource(ctx context.Context, req *walletv1.UpdateResourceRequest) (*walletv1.ResourceResponse, error) { + m.updates = append(m.updates, req) + return &walletv1.ResourceResponse{Resource: &walletv1.Resource{ + AppCode: req.GetAppCode(), + ResourceId: req.GetResourceId(), + ResourceCode: req.GetResourceCode(), + ResourceType: req.GetResourceType(), + Name: req.GetName(), + Status: req.GetStatus(), + Grantable: req.GetGrantable(), + GrantStrategy: req.GetGrantStrategy(), + WalletAssetType: req.GetWalletAssetType(), + WalletAssetAmount: req.GetWalletAssetAmount(), + UsageScopes: req.GetUsageScopes(), + AssetUrl: req.GetAssetUrl(), + PreviewUrl: req.GetPreviewUrl(), + AnimationUrl: req.GetAnimationUrl(), + MetadataJson: req.GetMetadataJson(), + SortOrder: req.GetSortOrder(), + ManagerGrantEnabled: req.GetManagerGrantEnabled(), + PriceType: req.GetPriceType(), + CoinPrice: req.GetCoinPrice(), + }}, nil +} diff --git a/server/admin/internal/modules/resource/request.go b/server/admin/internal/modules/resource/request.go index f54d7f91..d31d0362 100644 --- a/server/admin/internal/modules/resource/request.go +++ b/server/admin/internal/modules/resource/request.go @@ -78,6 +78,15 @@ type resourceRequest struct { SortOrder int32 `json:"sortOrder"` } +type resourceMP4LayoutBatchRequest struct { + Items []resourceMP4LayoutUpdateRequest `json:"items"` +} + +type resourceMP4LayoutUpdateRequest struct { + ResourceID int64 `json:"resourceId"` + MetadataJSON string `json:"metadataJson"` +} + type emojiPackRequest struct { Name string `json:"name"` Category string `json:"category"` diff --git a/server/admin/internal/modules/resource/routes.go b/server/admin/internal/modules/resource/routes.go index 2a38cb70..7799377a 100644 --- a/server/admin/internal/modules/resource/routes.go +++ b/server/admin/internal/modules/resource/routes.go @@ -13,6 +13,7 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.GET("/admin/resources", middleware.RequirePermission("resource:view"), h.ListResources) protected.POST("/admin/resources", middleware.RequirePermission("resource:create"), h.CreateResource) + protected.PUT("/admin/resources/mp4-layouts", middleware.RequirePermission("resource:update"), h.UpdateMP4ResourceLayouts) protected.GET("/admin/resources/:resource_id", middleware.RequirePermission("resource:view"), h.GetResource) protected.PUT("/admin/resources/:resource_id", middleware.RequirePermission("resource:update"), h.UpdateResource) protected.POST("/admin/resources/:resource_id/enable", middleware.RequirePermission("resource:update"), h.EnableResource) From 0bb719d1b8f9bfd1243fde273420600c47353f32 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 13:53:23 +0800 Subject: [PATCH 06/28] =?UTF-8?q?=E6=89=B9=E9=87=8F=E9=87=8D=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/admin/internal/modules/resource/handler_test.go | 6 +++--- server/admin/internal/modules/resource/routes.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/admin/internal/modules/resource/handler_test.go b/server/admin/internal/modules/resource/handler_test.go index 7d674ecc..a6306e71 100644 --- a/server/admin/internal/modules/resource/handler_test.go +++ b/server/admin/internal/modules/resource/handler_test.go @@ -58,7 +58,7 @@ func TestUpdateMP4ResourceLayoutsPreservesResourceFields(t *testing.T) { body := fmt.Sprintf(`{"items":[{"resourceId":11,"metadataJson":%q}]}`, metadataJSON) recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodPut, "/admin/resources/mp4-layouts", strings.NewReader(body)) + request := httptest.NewRequest(http.MethodPut, "/admin/resources/mp4-layouts/batch", strings.NewReader(body)) request.Header.Set("Content-Type", "application/json") router.ServeHTTP(recorder, request) @@ -103,7 +103,7 @@ func TestUpdateMP4ResourceLayoutsIgnoresNonMP4Resource(t *testing.T) { body := fmt.Sprintf(`{"items":[{"resourceId":12,"metadataJson":%q}]}`, mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true)) recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodPut, "/admin/resources/mp4-layouts", strings.NewReader(body)) + request := httptest.NewRequest(http.MethodPut, "/admin/resources/mp4-layouts/batch", strings.NewReader(body)) request.Header.Set("Content-Type", "application/json") router.ServeHTTP(recorder, request) @@ -125,7 +125,7 @@ func newResourceHandlerTestRouter(handler *Handler) *gin.Engine { c.Set(middleware.ContextUsername, "tester") c.Next() }) - router.PUT("/admin/resources/mp4-layouts", handler.UpdateMP4ResourceLayouts) + router.PUT("/admin/resources/mp4-layouts/batch", handler.UpdateMP4ResourceLayouts) return router } diff --git a/server/admin/internal/modules/resource/routes.go b/server/admin/internal/modules/resource/routes.go index 7799377a..1cfa2df7 100644 --- a/server/admin/internal/modules/resource/routes.go +++ b/server/admin/internal/modules/resource/routes.go @@ -13,7 +13,7 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.GET("/admin/resources", middleware.RequirePermission("resource:view"), h.ListResources) protected.POST("/admin/resources", middleware.RequirePermission("resource:create"), h.CreateResource) - protected.PUT("/admin/resources/mp4-layouts", middleware.RequirePermission("resource:update"), h.UpdateMP4ResourceLayouts) + protected.PUT("/admin/resources/mp4-layouts/batch", middleware.RequirePermission("resource:update"), h.UpdateMP4ResourceLayouts) protected.GET("/admin/resources/:resource_id", middleware.RequirePermission("resource:view"), h.GetResource) protected.PUT("/admin/resources/:resource_id", middleware.RequirePermission("resource:update"), h.UpdateResource) protected.POST("/admin/resources/:resource_id/enable", middleware.RequirePermission("resource:update"), h.EnableResource) From b2362eefe8f2b6262e36e723897c0c7f43790561 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 14:27:06 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E5=B0=8F=E6=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/gamemanagement/sync_games.go | 90 +++++++++++++++++++ .../modules/gamemanagement/sync_games_test.go | 36 ++++++++ 2 files changed, 126 insertions(+) diff --git a/server/admin/internal/modules/gamemanagement/sync_games.go b/server/admin/internal/modules/gamemanagement/sync_games.go index 14f80450..ab3f695b 100644 --- a/server/admin/internal/modules/gamemanagement/sync_games.go +++ b/server/admin/internal/modules/gamemanagement/sync_games.go @@ -28,6 +28,7 @@ const ( adapterZeeOneV1 = "zeeone_v1" adapterVivaGamesV1 = "vivagames_v1" adapterReyouV1 = "reyou_v1" + adapterYomiV4 = "yomi_v4" defaultGameStatus = "disabled" defaultGameCategory = "casino" defaultLaunchMode = "full_screen" @@ -137,6 +138,8 @@ func fetchProviderGameSyncPlan(ctx context.Context, platform *gamev1.GamePlatfor return fetchVivaGamesGameSyncPlan(platform, req) case adapterReyouV1: return fetchReyouGameSyncPlan(platform, req) + case adapterYomiV4: + return fetchYomiGameSyncPlan(platform, req) default: // 不在配置文件写死游戏:每家厂商新增“列表 API”时只加一个 adapter fetcher,后台入口保持不变。 return providerGameSyncPlan{}, fmt.Errorf("当前适配器暂未接入厂商游戏列表 API: %s", platform.GetAdapterType()) @@ -199,6 +202,14 @@ type reyouSyncConfig struct { GameCovers map[string]string `json:"game_covers"` } +type yomiSyncConfig struct { + // Yomi V4 文档只提供启动鉴权地址,没有游戏列表 API;后台通过 game_names 维护截图里的 gameUid 清单。 + GameNames map[string]string `json:"game_names"` + GameURLs map[string]string `json:"game_urls"` + GameIcons map[string]string `json:"game_icons"` + GameCovers map[string]string `json:"game_covers"` +} + func fetchZeeOneGameSyncPlan(platform *gamev1.GamePlatform, req syncGamesRequest) (providerGameSyncPlan, error) { config, err := decodeZeeOneSyncConfig(platform.GetAdapterConfigJson()) if err != nil { @@ -355,6 +366,21 @@ func fetchReyouGameSyncPlan(platform *gamev1.GamePlatform, req syncGamesRequest) }, nil } +func fetchYomiGameSyncPlan(platform *gamev1.GamePlatform, req syncGamesRequest) (providerGameSyncPlan, error) { + config, err := decodeYomiSyncConfig(platform.GetAdapterConfigJson()) + if err != nil { + return providerGameSyncPlan{}, err + } + games, gameURLs := yomiCatalogItems(platform.GetPlatformCode(), config, req) + if len(games) == 0 { + return providerGameSyncPlan{}, fmt.Errorf("小游游戏列表为空:请在 adapterConfigJson.game_names 配置游戏 ID 到名称的映射") + } + return providerGameSyncPlan{ + Games: games, + GameURLs: gameURLs, + }, nil +} + func decodeReyouSyncConfig(raw string) (reyouSyncConfig, error) { if strings.TrimSpace(raw) == "" { return reyouSyncConfig{}, nil @@ -366,6 +392,17 @@ func decodeReyouSyncConfig(raw string) (reyouSyncConfig, error) { return config, nil } +func decodeYomiSyncConfig(raw string) (yomiSyncConfig, error) { + if strings.TrimSpace(raw) == "" { + return yomiSyncConfig{}, nil + } + var config yomiSyncConfig + if err := json.Unmarshal([]byte(raw), &config); err != nil { + return yomiSyncConfig{}, fmt.Errorf("小游适配器配置 JSON 不合法") + } + return config, nil +} + func reyouCatalogItems(platformCode string, config reyouSyncConfig, req syncGamesRequest) ([]catalogRequest, map[string]string) { keys := make([]string, 0, len(config.GameURLs)) for providerGameID, launchURL := range config.GameURLs { @@ -401,6 +438,59 @@ func reyouCatalogItems(platformCode string, config reyouSyncConfig, req syncGame return items, gameURLs } +func yomiCatalogItems(platformCode string, config yomiSyncConfig, req syncGamesRequest) ([]catalogRequest, map[string]string) { + keys := make([]string, 0, len(config.GameNames)+len(config.GameURLs)) + seen := make(map[string]struct{}, len(config.GameNames)+len(config.GameURLs)) + // 小游没有列表 API,game_names 是本地候选游戏的事实来源;game_urls 只用于后台预览,不参与启动拼参。 + for providerGameID := range config.GameNames { + key := strings.TrimSpace(providerGameID) + if key == "" { + continue + } + seen[key] = struct{}{} + keys = append(keys, key) + } + for providerGameID, launchURL := range config.GameURLs { + key := strings.TrimSpace(providerGameID) + if key == "" || strings.TrimSpace(launchURL) == "" { + continue + } + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + keys = append(keys, key) + } + sort.Strings(keys) + items := make([]catalogRequest, 0, len(keys)) + gameURLs := make(map[string]string, len(keys)) + for index, providerGameID := range keys { + launchURL := strings.TrimSpace(config.GameURLs[providerGameID]) + if launchURL != "" { + gameURLs[providerGameID] = launchURL + } + name := firstNonEmpty(config.GameNames[providerGameID], zeeoneGameNameFromURL(launchURL), providerGameID) + iconURL := strings.TrimSpace(config.GameIcons[providerGameID]) + coverURL := firstNonEmpty(config.GameCovers[providerGameID], iconURL) + items = append(items, catalogRequest{ + GameID: stableGameID(platformCode, providerGameID), + PlatformCode: strings.TrimSpace(platformCode), + ProviderGameID: providerGameID, + GameName: name, + Category: defaulted(req.Category, defaultGameCategory), + IconURL: iconURL, + CoverURL: coverURL, + LaunchMode: defaulted(req.LaunchMode, defaultLaunchMode), + Orientation: defaultOrientation, + MinCoin: req.MinCoin, + Status: defaulted(req.Status, defaultGameStatus), + SortOrder: int32((index + 1) * 10), + Tags: compactTags(append([]string{strings.TrimSpace(platformCode), adapterYomiV4}, req.Tags...)), + }) + } + return items, gameURLs +} + func titleGameName(value string) string { tokens := splitGameNameTokens(value) if len(tokens) == 0 { diff --git a/server/admin/internal/modules/gamemanagement/sync_games_test.go b/server/admin/internal/modules/gamemanagement/sync_games_test.go index b61bbd01..5b65645b 100644 --- a/server/admin/internal/modules/gamemanagement/sync_games_test.go +++ b/server/admin/internal/modules/gamemanagement/sync_games_test.go @@ -167,6 +167,42 @@ func TestFetchReyouGameSyncPlanReadsConfiguredGameURLs(t *testing.T) { } } +func TestFetchYomiGameSyncPlanReadsConfiguredGameNames(t *testing.T) { + plan, err := fetchProviderGameSyncPlan(t.Context(), &gamev1.GamePlatform{ + PlatformCode: "yomi", + AdapterType: adapterYomiV4, + AdapterConfigJson: `{ + "game_names": { + "481": "MahjongWays", + "480": "ChickenPlinko" + }, + "game_urls": { + "481": "https://api-testv2.yomigame.games/gate/gateway/auth" + } + }`, + }, syncGamesRequest{Status: "active"}) + if err != nil { + t.Fatalf("fetchProviderGameSyncPlan failed: %v", err) + } + if len(plan.Games) != 2 { + t.Fatalf("games len = %d, want 2", len(plan.Games)) + } + first := plan.Games[0] + if first.GameID != "yomi_480" || first.ProviderGameID != "480" || first.GameName != "ChickenPlinko" { + t.Fatalf("first yomi game mismatch: %+v", first) + } + second := plan.Games[1] + if second.GameID != "yomi_481" || second.ProviderGameID != "481" || second.GameName != "MahjongWays" { + t.Fatalf("second yomi game mismatch: %+v", second) + } + if plan.GameURLs["481"] != "https://api-testv2.yomigame.games/gate/gateway/auth" { + t.Fatalf("game url mismatch: %+v", plan.GameURLs) + } + if first.Status != "active" || first.LaunchMode != "full_screen" || first.Orientation != "portrait" { + t.Fatalf("default fields mismatch: %+v", first) + } +} + func TestSelectProviderGamesKeepsOnlySelectedIDs(t *testing.T) { games := []catalogRequest{ {ProviderGameID: "1001", GameName: "one"}, From 6b16f8c96ea4ab5d20e96827d1c4e79647a0c6ee Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 19:16:05 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=81=AB=E7=AE=AD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/flutter对接/语音房火箭Flutter对接.md | 2 +- docs/语音房火箭功能架构.md | 13 +-- .../internal/room/service/room_rocket.go | 53 ++++++------ .../internal/room/service/room_rocket_test.go | 81 +++++++++++++++++++ 4 files changed, 114 insertions(+), 35 deletions(-) diff --git a/docs/flutter对接/语音房火箭Flutter对接.md b/docs/flutter对接/语音房火箭Flutter对接.md index 629c00d1..4d687a6b 100644 --- a/docs/flutter对接/语音房火箭Flutter对接.md +++ b/docs/flutter对接/语音房火箭Flutter对接.md @@ -274,7 +274,7 @@ | `rewards[].grant_id` | 发奖回执 | | `rewards[].status` | 发奖状态 | -奖励 IM 用于通知本次哪些人中奖。需要渲染金币数量、奖励天数、聊天气泡或麦位特效素材时,收到 IM 后调用 `GET /api/v1/rooms/{room_id}/rocket`,读取 `state.last_rewards[].resource_group`。 +奖励 IM 用于通知本次哪些人中奖。top1 和点火人命中后会发完整配置池,同一用户可能同时收到多条 `top1`、`igniter` 和 1 条在房随机奖励。需要渲染金币数量、奖励天数、聊天气泡或麦位特效素材时,收到 IM 后调用 `GET /api/v1/rooms/{room_id}/rocket`,读取 `state.last_rewards[].resource_group`。 ## 客户端处理 diff --git a/docs/语音房火箭功能架构.md b/docs/语音房火箭功能架构.md index 31d55d52..dcd06124 100644 --- a/docs/语音房火箭功能架构.md +++ b/docs/语音房火箭功能架构.md @@ -74,12 +74,15 @@ | 贡献第一奖励 | `pending_launches[].top1_user_id` | | 在房奖励 | 发射命令执行时 Room Cell `OnlineUsers` | -`reward_stack_policy`: +发放规则: -| 值 | 说明 | +| 奖励 | 规则 | | --- | --- | -| `allow_stack` | 同一用户可同时拿在房、top1、点火人奖励 | -| `priority_only` | 先点火人,再 top1,再在房;同一用户只拿一份 | +| 在房奖励 | 每个在线用户从 `inRoomRewards` 按权重随机抽 1 个 | +| 贡献第一奖励 | top1 用户获得 `top1Rewards` 配置池内全部奖励 | +| 点火人奖励 | 点火用户获得 `igniterRewards` 配置池内全部奖励 | + +同一用户同时是 top1 和点火人时,top1 池和点火人池继续叠加;如果该用户发射瞬间仍在房间,也会再拿 1 个在房随机奖励。 ## IM @@ -119,7 +122,7 @@ | `launch_delay_ms` | 点火到发射延迟 | | `broadcast_enabled` | 是否发飘屏 | | `broadcast_scope` | `region`、`global`、`none` | -| `reward_stack_policy` | 奖励叠加策略 | +| `reward_stack_policy` | 历史兼容字段;当前特殊身份奖励按上方规则叠加 | | `levels_json` | 5 级阈值、物料和奖励池 | | `gift_fuel_rules_json` | 礼物燃料规则 | diff --git a/services/room-service/internal/room/service/room_rocket.go b/services/room-service/internal/room/service/room_rocket.go index c7373ca1..f120986f 100644 --- a/services/room-service/internal/room/service/room_rocket.go +++ b/services/room-service/internal/room/service/room_rocket.go @@ -913,21 +913,16 @@ func sortedRocketOnlineUserIDs(users map[int64]*state.RoomUserState) []int64 { return userIDs } -func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, current *state.RoomState, launch state.RocketPendingLaunch, cfg RoomRocketConfig, levelCfg RoomRocketLevelConfig, inRoomUserIDs []int64) ([]state.RocketRewardGrant, error) { +func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, current *state.RoomState, launch state.RocketPendingLaunch, _ RoomRocketConfig, levelCfg RoomRocketLevelConfig, inRoomUserIDs []int64) ([]state.RocketRewardGrant, error) { if current == nil { return nil, nil } - rewards := make([]state.RocketRewardGrant, 0, len(inRoomUserIDs)+2) - claimed := make(map[int64]bool, len(inRoomUserIDs)+2) - addReward := func(role string, userID int64, pool []RoomRocketRewardItem) error { + rewards := make([]state.RocketRewardGrant, 0, len(inRoomUserIDs)+len(levelCfg.Top1Rewards)+len(levelCfg.IgniterRewards)) + addRewardItem := func(role string, userID int64, item RoomRocketRewardItem) error { if userID <= 0 { return nil } - if cfg.RewardStackPolicy == roomRocketRewardStackPriority && claimed[userID] { - return nil - } - item, ok := selectRoomRocketReward(launch.RocketID, role, userID, pool) - if !ok { + if item.ResourceGroupID <= 0 || item.Weight <= 0 { return nil } grantID, err := s.grantRoomRocketReward(ctx, now, current, launch.RocketID, role, userID, item) @@ -944,35 +939,35 @@ func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, cu GrantID: grantID, Status: roomRocketGrantStatus, }) - claimed[userID] = true + return nil + } + addRandomReward := func(role string, userID int64, pool []RoomRocketRewardItem) error { + item, ok := selectRoomRocketReward(launch.RocketID, role, userID, pool) + if !ok { + return nil + } + return addRewardItem(role, userID, item) + } + addAllRewards := func(role string, userID int64, pool []RoomRocketRewardItem) error { + for _, item := range pool { + if err := addRewardItem(role, userID, item); err != nil { + return err + } + } return nil } - if cfg.RewardStackPolicy == roomRocketRewardStackPriority { - // 去重策略下先发特殊身份奖励,再发普通在房奖励,避免 top1/点火人被普通池占位。 - if err := addReward(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil { - return nil, err - } - if err := addReward(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil { - return nil, err - } - for _, userID := range inRoomUserIDs { - if err := addReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil { - return nil, err - } - } - return rewards, nil - } - for _, userID := range inRoomUserIDs { - if err := addReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil { + // 在房奖励是大面积发放池,每个在线用户只按权重抽一个奖项,避免房间人数放大单次火箭成本。 + if err := addRandomReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil { return nil, err } } - if err := addReward(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil { + // top1 和点火人是确定身份权益:命中后发完整配置池;同一用户同时命中两个身份时继续叠加。 + if err := addAllRewards(roomRocketRewardTop1, launch.Top1UserID, levelCfg.Top1Rewards); err != nil { return nil, err } - if err := addReward(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil { + if err := addAllRewards(roomRocketRewardIgniter, launch.IgniterUserID, levelCfg.IgniterRewards); err != nil { return nil, err } return rewards, nil diff --git a/services/room-service/internal/room/service/room_rocket_test.go b/services/room-service/internal/room/service/room_rocket_test.go index a2212938..64aae985 100644 --- a/services/room-service/internal/room/service/room_rocket_test.go +++ b/services/room-service/internal/room/service/room_rocket_test.go @@ -433,6 +433,77 @@ func TestRoomRocketLaunchGrantsLockedTop1AndIgniterAfterLeave(t *testing.T) { } } +func TestRoomRocketLaunchStacksAllSpecialPoolsAndRandomInRoomReward(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + now := &fixedRoomRocketClock{now: time.Date(2026, 5, 20, 13, 0, 0, 0, time.UTC)} + wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{ + {BillingReceiptId: "receipt-stack", GiftPointAdded: 100, HeatValue: 100, GiftTypeCode: "normal"}, + }} + svc := newRocketTestService(t, repository, wallet, now) + config := rocketConfigForTest(100, 1_000) + config.Levels[0].InRoomRewards = []roomservice.RoomRocketRewardItem{ + {RewardItemID: "level-1-in-room-a", ResourceGroupID: 1001, Weight: 1, DisplayName: "In Room A"}, + {RewardItemID: "level-1-in-room-b", ResourceGroupID: 1101, Weight: 1, DisplayName: "In Room B"}, + } + config.Levels[0].Top1Rewards = []roomservice.RoomRocketRewardItem{ + {RewardItemID: "level-1-top1-a", ResourceGroupID: 2001, Weight: 1, DisplayName: "Top 1 A"}, + {RewardItemID: "level-1-top1-b", ResourceGroupID: 2101, Weight: 1, DisplayName: "Top 1 B"}, + } + config.Levels[0].IgniterRewards = []roomservice.RoomRocketRewardItem{ + {RewardItemID: "level-1-igniter-a", ResourceGroupID: 3001, Weight: 1, DisplayName: "Igniter A"}, + {RewardItemID: "level-1-igniter-b", ResourceGroupID: 3101, Weight: 1, DisplayName: "Igniter B"}, + } + writeRocketConfig(t, ctx, repository, config) + + roomID := "room-rocket-stack" + ownerID := int64(401) + igniterID := int64(402) + createRocketRoom(t, ctx, svc, roomID, ownerID, 9003) + joinRocketRoom(t, ctx, svc, roomID, igniterID) + + if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{ + Meta: rocketMeta(roomID, igniterID, "stack"), + TargetType: "user", + TargetUserId: ownerID, + GiftId: "gift-stack", + GiftCount: 1, + }); err != nil { + t.Fatalf("ignite stack rocket failed: %v", err) + } + + now.now = now.now.Add(1100 * time.Millisecond) + if err := svc.SweepRoomRocketLaunchings(ctx); err != nil { + t.Fatalf("sweep stacked room rocket launches failed: %v", err) + } + + info, err := svc.GetRoomRocket(ctx, &roomv1.GetRoomRocketRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID}, + RoomId: roomID, + ViewerUserId: ownerID, + }) + if err != nil { + t.Fatalf("get stacked rocket after launch failed: %v", err) + } + rewards := info.GetRocket().GetState().GetLastRewards() + if len(rewards) != 6 { + t.Fatalf("launch must keep two in-room random grants plus all top1/igniter grants: %+v", rewards) + } + if countRocketRewards(rewards, ownerID, "in_room") != 1 { + t.Fatalf("owner must receive exactly one random in-room reward: %+v", rewards) + } + if countRocketRewards(rewards, igniterID, "in_room") != 1 { + t.Fatalf("top1 igniter must still receive exactly one random in-room reward: %+v", rewards) + } + if countRocketRewards(rewards, igniterID, "top1") != 2 || countRocketRewards(rewards, igniterID, "igniter") != 2 { + t.Fatalf("top1 igniter must receive every configured special reward: %+v", rewards) + } + if !hasRocketGrant(wallet.grants, igniterID, 2001) || !hasRocketGrant(wallet.grants, igniterID, 2101) || + !hasRocketGrant(wallet.grants, igniterID, 3001) || !hasRocketGrant(wallet.grants, igniterID, 3101) { + t.Fatalf("wallet must grant every top1 and igniter resource group: %+v", wallet.grants) + } +} + func TestRoomRocketUTCResetCancelsCrossDayIgnited(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) @@ -676,6 +747,16 @@ func hasRocketGrant(grants []*walletv1.GrantResourceGroupRequest, userID int64, return false } +func countRocketRewards(rewards []*roomv1.RoomRocketRewardGrant, userID int64, role string) int { + count := 0 + for _, reward := range rewards { + if reward.GetUserId() == userID && reward.GetRewardRole() == role { + count++ + } + } + return count +} + func rocketRewardUserIDs(rewards []*roomv1.RoomRocketRewardGrant) []int64 { userIDs := make([]int64, 0, len(rewards)) for _, reward := range rewards { From 1e180b844c997219c5acb7b10df8f7a0efc2e260 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 9 Jun 2026 22:08:52 +0800 Subject: [PATCH 09/28] =?UTF-8?q?=E5=BC=80=E5=B1=8F=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/flutter对接/开屏配置Flutter对接.md | 69 +++++++ docs/openapi/gateway.swagger.yaml | 95 ++++++++++ docs/接口清单.md | 5 + server/admin/internal/model/models.go | 22 +++ .../internal/modules/appconfig/handler.go | 72 ++++++++ .../internal/modules/appconfig/request.go | 14 ++ .../internal/modules/appconfig/routes.go | 5 + .../internal/modules/appconfig/service.go | 171 ++++++++++++++++++ .../modules/appconfig/service_test.go | 76 ++++++++ .../app_splash_screen_repository.go | 75 ++++++++ .../admin/internal/repository/repository.go | 1 + server/admin/internal/repository/seed.go | 7 +- .../042_admin_app_splash_screens.sql | 50 +++++ .../internal/appconfig/mysql.go | 94 ++++++++++ .../http/appapi/app_config_handler.go | 21 +++ .../internal/transport/http/appapi/handler.go | 5 + .../transport/http/httproutes/router.go | 2 + .../internal/transport/http/response_test.go | 88 +++++++++ .../internal/transport/http/router.go | 1 + 19 files changed, 870 insertions(+), 3 deletions(-) create mode 100644 docs/flutter对接/开屏配置Flutter对接.md create mode 100644 server/admin/internal/repository/app_splash_screen_repository.go create mode 100644 server/admin/migrations/042_admin_app_splash_screens.sql diff --git a/docs/flutter对接/开屏配置Flutter对接.md b/docs/flutter对接/开屏配置Flutter对接.md new file mode 100644 index 00000000..10e920fe --- /dev/null +++ b/docs/flutter对接/开屏配置Flutter对接.md @@ -0,0 +1,69 @@ +# 开屏配置 Flutter 对接 + +## 地址 + +`GET /api/v1/app/splash-screens` + +公开接口,不需要登录。返回后台 `APP配置 -> 开屏配置` 中当前有效的开屏列表。 + +## 参数 + +| 参数 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `X-App-Code` | Header | 否 | App 编码,默认 `lalu`。 | +| `X-App-Package` | Header | 否 | 包名或 Bundle ID,用于服务端解析 App。 | +| `platform` | Query | 否 | `android` 或 `ios`。不传时读取 `X-App-Platform` / `X-Platform`。 | +| `region_id` | Query | 否 | 当前用户区域 ID。 | +| `country` | Query | 否 | 当前用户国家码。不传时读取 `X-Country-Code` / `X-App-Country`。 | + +示例: + +```http +GET /api/v1/app/splash-screens?platform=android®ion_id=1&country=CN +X-App-Code: lalu +X-App-Platform: android +``` + +## 返回值 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_abc", + "data": { + "items": [ + { + "id": 12, + "cover_url": "https://cdn.example.com/splash.png", + "type": "h5", + "param": "https://h5.example.com/splash", + "platform": "android", + "sort_order": 1, + "region_id": 1, + "country_code": "CN", + "description": "活动开屏", + "starts_at_ms": 1700000000000, + "ends_at_ms": 1800000000000, + "updated_at_ms": 1700000003000 + } + ], + "total": 1 + } +} +``` + +| 字段 | 说明 | +| --- | --- | +| `items` | 当前可展示的开屏配置列表,已按 `sort_order ASC, id DESC` 排序。 | +| `cover_url` | 开屏图片地址。 | +| `type` | `h5` 或 `app`。 | +| `param` | `type=h5` 时是 H5 链接;`type=app` 时由客户端解释。 | +| `platform` | `android` 或 `ios`。 | +| `region_id` | 配置限制的区域 ID;为空或 0 表示全部区域。 | +| `country_code` | 配置限制的国家码;为空表示全部国家。 | +| `starts_at_ms` / `ends_at_ms` | 投放时间。0 表示不限。 | + +## 相关 IM + +无。 diff --git a/docs/openapi/gateway.swagger.yaml b/docs/openapi/gateway.swagger.yaml index ce54b195..e8359b26 100644 --- a/docs/openapi/gateway.swagger.yaml +++ b/docs/openapi/gateway.swagger.yaml @@ -393,6 +393,47 @@ paths: $ref: "#/definitions/H5LinkListEnvelope" "502": $ref: "#/responses/UpstreamError" + /api/v1/app/splash-screens: + get: + tags: + - app-config + summary: 获取 App 开屏配置 + operationId: listSplashScreens + description: 公开读接口,返回后台 APP配置/开屏配置 中当前有效的开屏列表,客户端按列表顺序取最高优先级配置展示。 + parameters: + - name: platform + in: query + required: false + type: string + enum: [android, ios] + description: 可选平台覆盖值;不传时读取 `X-App-Platform` / `X-Platform`。 + - name: region_id + in: query + required: false + type: integer + format: int64 + description: 当前用户区域 ID;0 或不传只命中全区域配置。 + - name: country + in: query + required: false + type: string + description: 当前用户国家码;不传时读取 `X-Country-Code` / `X-App-Country`。 + - name: X-App-Platform + in: header + required: false + type: string + enum: [android, ios] + - name: X-Country-Code + in: header + required: false + type: string + responses: + "200": + description: 查询成功,`data.items` 返回当前可展示的开屏配置。 + schema: + $ref: "#/definitions/SplashScreenListEnvelope" + "502": + $ref: "#/responses/UpstreamError" /api/v1/im/usersig: get: tags: @@ -3278,6 +3319,53 @@ definitions: total: type: integer format: int32 + SplashScreenData: + type: object + properties: + id: + type: integer + format: uint64 + cover_url: + type: string + description: 开屏图片 URL。 + type: + type: string + enum: [h5, app] + description: h5 表示 param 是 H5 链接;app 表示 param 由客户端解释。 + param: + type: string + platform: + type: string + enum: [android, ios] + sort_order: + type: integer + format: int32 + region_id: + type: integer + format: int64 + country_code: + type: string + description: + type: string + starts_at_ms: + type: integer + format: int64 + ends_at_ms: + type: integer + format: int64 + updated_at_ms: + type: integer + format: int64 + SplashScreenListData: + type: object + properties: + items: + type: array + items: + $ref: "#/definitions/SplashScreenData" + total: + type: integer + format: int32 AppBootstrapTab: type: object properties: @@ -5121,6 +5209,13 @@ definitions: properties: data: $ref: "#/definitions/H5LinkListData" + SplashScreenListEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/SplashScreenListData" ResourceListEnvelope: allOf: - $ref: "#/definitions/GatewayOKEnvelopeBase" diff --git a/docs/接口清单.md b/docs/接口清单.md index 9a00a3cf..f637567a 100644 --- a/docs/接口清单.md +++ b/docs/接口清单.md @@ -18,6 +18,7 @@ | GET | `/api/v1/countries` | countries | `listRegistrationCountries` | 获取 App 注册页可选国家 | | GET | `/api/v1/app/bootstrap` | app-config | `getAppBootstrap` | 获取 App 启动配置摘要 | | GET | `/api/v1/app/h5-links` | app-config | `listAppH5Links` | 获取 App H5 入口地址 | +| GET | `/api/v1/app/splash-screens` | app-config | `listSplashScreens` | 获取 App 开屏配置 | | POST | `/api/v1/app/heartbeat` | app | `appHeartbeat` | 刷新当前 App 登录会话心跳 | | GET | `/api/v1/im/usersig` | tencent-im | `issueTencentIMUserSig` | 获取腾讯云 IM UserSig | | POST | `/api/v1/rtc/token` | tencent-rtc | `issueTencentRTCToken` | 获取腾讯 RTC 进房 UserSig | @@ -122,6 +123,10 @@ | POST | `/api/v1/admin/app-config/banners` | admin | `createBanner` | createBanner | | DELETE | `/api/v1/admin/app-config/banners/{banner_id}` | admin | `deleteBanner` | deleteBanner | | PUT | `/api/v1/admin/app-config/banners/{banner_id}` | admin | `updateBanner` | updateBanner | +| GET | `/api/v1/admin/app-config/splash-screens` | admin | `listSplashScreens` | listSplashScreens | +| POST | `/api/v1/admin/app-config/splash-screens` | admin | `createSplashScreen` | createSplashScreen | +| DELETE | `/api/v1/admin/app-config/splash-screens/{splash_id}` | admin | `deleteSplashScreen` | deleteSplashScreen | +| PUT | `/api/v1/admin/app-config/splash-screens/{splash_id}` | admin | `updateSplashScreen` | updateSplashScreen | | GET | `/api/v1/admin/app-config/h5-links` | admin | `listH5Links` | listH5Links | | PUT | `/api/v1/admin/app-config/h5-links` | admin | `updateH5Links` | updateH5Links | | GET | `/api/v1/admin/apps` | admin | `listApps` | listApps | diff --git a/server/admin/internal/model/models.go b/server/admin/internal/model/models.go index b654bcec..7a158904 100644 --- a/server/admin/internal/model/models.go +++ b/server/admin/internal/model/models.go @@ -115,6 +115,28 @@ func (AppBanner) TableName() string { return "admin_app_banners" } +type AppSplashScreen struct { + ID uint `gorm:"primaryKey" json:"id"` + AppCode string `gorm:"size:32;index:idx_admin_app_splash_screens_app_sort,not null;default:lalu" json:"appCode"` + CoverURL string `gorm:"size:1024;not null" json:"coverUrl"` + SplashType string `gorm:"size:16;not null" json:"splashType"` + Param string `gorm:"size:2048" json:"param"` + Status string `gorm:"size:24;index:idx_admin_app_splash_screens_app_sort,not null;default:active" json:"status"` + Platform string `gorm:"size:24;index:idx_admin_app_splash_screens_scope,not null" json:"platform"` + SortOrder int `gorm:"index:idx_admin_app_splash_screens_app_sort,not null;default:0" json:"sortOrder"` + RegionID int64 `gorm:"index:idx_admin_app_splash_screens_scope,not null;default:0" json:"regionId"` + CountryCode string `gorm:"size:8;index:idx_admin_app_splash_screens_scope" json:"countryCode"` + Description string `gorm:"size:255" json:"description"` + StartsAtMS int64 `gorm:"column:starts_at_ms;not null;default:0;comment:投放开始时间,UTC epoch ms" json:"startsAtMs"` + EndsAtMS int64 `gorm:"column:ends_at_ms;not null;default:0;comment:投放结束时间,UTC epoch ms" json:"endsAtMs"` + CreatedAtMS int64 `gorm:"column:created_at_ms;autoCreateTime:milli" json:"createdAtMs"` + UpdatedAtMS int64 `gorm:"column:updated_at_ms;autoUpdateTime:milli" json:"updatedAtMs"` +} + +func (AppSplashScreen) TableName() string { + return "admin_app_splash_screens" +} + type AppVersion struct { ID uint `gorm:"primaryKey" json:"id"` AppCode string `gorm:"size:32;uniqueIndex:uk_admin_app_versions_build,not null;default:lalu" json:"appCode"` diff --git a/server/admin/internal/modules/appconfig/handler.go b/server/admin/internal/modules/appconfig/handler.go index 2a5551d2..f748a7b8 100644 --- a/server/admin/internal/modules/appconfig/handler.go +++ b/server/admin/internal/modules/appconfig/handler.go @@ -160,6 +160,69 @@ func (h *Handler) DeleteBanner(c *gin.Context) { response.OK(c, gin.H{"deleted": true}) } +func (h *Handler) ListSplashScreens(c *gin.Context) { + options := shared.ListOptions(c) + items, err := h.service.ListSplashScreens(appctx.FromContext(c.Request.Context()), repository.AppSplashScreenListOptions{ + Keyword: options.Keyword, + Status: options.Status, + Platform: strings.TrimSpace(c.Query("platform")), + RegionID: parseOptionalInt64(c.Query("regionId"), c.Query("region_id")), + Country: firstQuery(c, "countryCode", "country_code", "country"), + }) + if err != nil { + response.ServerError(c, "获取开屏配置失败") + return + } + response.OK(c, gin.H{"items": items, "total": len(items)}) +} + +func (h *Handler) CreateSplashScreen(c *gin.Context) { + var request splashScreenRequest + if err := c.ShouldBindJSON(&request); err != nil { + response.BadRequest(c, "参数不正确") + return + } + item, err := h.service.CreateSplashScreen(appctx.FromContext(c.Request.Context()), request) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLog(c, h.audit, "create-app-splash-screen", "admin_app_splash_screens", "success", fmt.Sprintf("splash_id=%d", item.ID)) + response.Created(c, item) +} + +func (h *Handler) UpdateSplashScreen(c *gin.Context) { + id, ok := splashScreenID(c) + if !ok { + return + } + var request splashScreenRequest + if err := c.ShouldBindJSON(&request); err != nil { + response.BadRequest(c, "参数不正确") + return + } + item, err := h.service.UpdateSplashScreen(appctx.FromContext(c.Request.Context()), id, request) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLog(c, h.audit, "update-app-splash-screen", "admin_app_splash_screens", "success", fmt.Sprintf("splash_id=%d", item.ID)) + response.OK(c, item) +} + +func (h *Handler) DeleteSplashScreen(c *gin.Context) { + id, ok := splashScreenID(c) + if !ok { + return + } + if err := h.service.DeleteSplashScreen(appctx.FromContext(c.Request.Context()), id); err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLog(c, h.audit, "delete-app-splash-screen", "admin_app_splash_screens", "success", fmt.Sprintf("splash_id=%d", id)) + response.OK(c, gin.H{"deleted": true}) +} + func (h *Handler) ListAppVersions(c *gin.Context) { items, err := h.service.ListAppVersions(appctx.FromContext(c.Request.Context()), repository.AppVersionListOptions{ Keyword: strings.TrimSpace(c.Query("keyword")), @@ -297,6 +360,15 @@ func bannerID(c *gin.Context) (uint, bool) { return uint(value), true } +func splashScreenID(c *gin.Context) (uint, bool) { + value, err := strconv.ParseUint(c.Param("splash_id"), 10, 64) + if err != nil || value == 0 { + response.BadRequest(c, "splash screen id is invalid") + return 0, false + } + return uint(value), true +} + func appVersionID(c *gin.Context) (uint, bool) { value, err := strconv.ParseUint(c.Param("version_id"), 10, 64) if err != nil || value == 0 { diff --git a/server/admin/internal/modules/appconfig/request.go b/server/admin/internal/modules/appconfig/request.go index bff655ab..63dfb1a3 100644 --- a/server/admin/internal/modules/appconfig/request.go +++ b/server/admin/internal/modules/appconfig/request.go @@ -27,6 +27,20 @@ type bannerRequest struct { EndsAtMs int64 `json:"endsAtMs"` } +type splashScreenRequest struct { + CoverURL string `json:"coverUrl" binding:"required"` + SplashType string `json:"splashType" binding:"required"` + Param string `json:"param"` + Status string `json:"status" binding:"required"` + Platform string `json:"platform" binding:"required"` + SortOrder int `json:"sortOrder"` + RegionID int64 `json:"regionId"` + CountryCode string `json:"countryCode"` + Description string `json:"description"` + StartsAtMs int64 `json:"startsAtMs"` + EndsAtMs int64 `json:"endsAtMs"` +} + type appVersionRequest struct { Platform string `json:"platform" binding:"required"` Version string `json:"version" binding:"required"` diff --git a/server/admin/internal/modules/appconfig/routes.go b/server/admin/internal/modules/appconfig/routes.go index b36595cb..c112af9f 100644 --- a/server/admin/internal/modules/appconfig/routes.go +++ b/server/admin/internal/modules/appconfig/routes.go @@ -27,6 +27,11 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.PUT("/admin/app-config/banners/:banner_id", middleware.RequirePermission("app-config:update"), h.UpdateBanner) protected.DELETE("/admin/app-config/banners/:banner_id", middleware.RequirePermission("app-config:update"), h.DeleteBanner) + protected.GET("/admin/app-config/splash-screens", middleware.RequirePermission("app-config:view"), h.ListSplashScreens) + protected.POST("/admin/app-config/splash-screens", middleware.RequirePermission("app-config:update"), h.CreateSplashScreen) + protected.PUT("/admin/app-config/splash-screens/:splash_id", middleware.RequirePermission("app-config:update"), h.UpdateSplashScreen) + protected.DELETE("/admin/app-config/splash-screens/:splash_id", middleware.RequirePermission("app-config:update"), h.DeleteSplashScreen) + protected.GET("/admin/app-config/versions", middleware.RequirePermission("app-version:view"), h.ListAppVersions) protected.POST("/admin/app-config/versions", middleware.RequirePermission("app-version:create"), h.CreateAppVersion) protected.PUT("/admin/app-config/versions/:version_id", middleware.RequirePermission("app-version:update"), h.UpdateAppVersion) diff --git a/server/admin/internal/modules/appconfig/service.go b/server/admin/internal/modules/appconfig/service.go index b9e35fff..1ae84b97 100644 --- a/server/admin/internal/modules/appconfig/service.go +++ b/server/admin/internal/modules/appconfig/service.go @@ -59,6 +59,24 @@ type AppBanner struct { UpdatedAtMs int64 `json:"updatedAtMs"` } +type AppSplashScreen struct { + ID uint `json:"id"` + AppCode string `json:"appCode"` + CoverURL string `json:"coverUrl"` + SplashType string `json:"splashType"` + Param string `json:"param"` + Status string `json:"status"` + Platform string `json:"platform"` + SortOrder int `json:"sortOrder"` + RegionID int64 `json:"regionId"` + CountryCode string `json:"countryCode"` + Description string `json:"description"` + StartsAtMs int64 `json:"startsAtMs"` + EndsAtMs int64 `json:"endsAtMs"` + CreatedAtMs int64 `json:"createdAtMs"` + UpdatedAtMs int64 `json:"updatedAtMs"` +} + type AppVersion struct { ID uint `json:"id"` AppCode string `json:"appCode"` @@ -225,6 +243,70 @@ func (s *AppConfigService) DeleteBanner(appCode string, id uint) error { return s.store.DeleteAppBanner(appctx.Normalize(appCode), id) } +func (s *AppConfigService) ListSplashScreens(appCode string, options repository.AppSplashScreenListOptions) ([]AppSplashScreen, error) { + // 列表入口先把管理后台传入的筛选条件统一成数据库稳定值,再执行过期回收,避免已过期 active 数据继续展示。 + options.AppCode = appctx.Normalize(appCode) + options.Status = normalizeBannerStatus(options.Status) + options.Platform = normalizeBannerPlatform(options.Platform) + options.Country = normalizeCountryCode(options.Country) + options.Keyword = strings.TrimSpace(options.Keyword) + if err := s.store.ExpireAppSplashScreens(options.AppCode, time.Now().UTC().UnixMilli()); err != nil { + return nil, err + } + items, err := s.store.ListAppSplashScreens(options) + if err != nil { + return nil, err + } + out := make([]AppSplashScreen, 0, len(items)) + for _, item := range items { + out = append(out, appSplashScreenFromModel(item)) + } + return out, nil +} + +func (s *AppConfigService) CreateSplashScreen(appCode string, req splashScreenRequest) (AppSplashScreen, error) { + // 创建时只接受 service 归一化后的模型,保证 status、平台、国家码和时间窗口在入库前已经被校验。 + item, err := splashScreenModelFromRequest(appctx.Normalize(appCode), req) + if err != nil { + return AppSplashScreen{}, err + } + if err := s.store.CreateAppSplashScreen(&item); err != nil { + return AppSplashScreen{}, err + } + return appSplashScreenFromModel(item), nil +} + +func (s *AppConfigService) UpdateSplashScreen(appCode string, id uint, req splashScreenRequest) (AppSplashScreen, error) { + // 更新先按 app_code 和 id 锁定原记录,再只覆盖业务字段,避免请求体伪造 app_code 或创建时间。 + item, err := s.store.GetAppSplashScreen(appctx.Normalize(appCode), id) + if err != nil { + return AppSplashScreen{}, err + } + updated, err := splashScreenModelFromRequest(item.AppCode, req) + if err != nil { + return AppSplashScreen{}, err + } + item.CoverURL = updated.CoverURL + item.SplashType = updated.SplashType + item.Param = updated.Param + item.Status = updated.Status + item.Platform = updated.Platform + item.SortOrder = updated.SortOrder + item.RegionID = updated.RegionID + item.CountryCode = updated.CountryCode + item.Description = updated.Description + item.StartsAtMS = updated.StartsAtMS + item.EndsAtMS = updated.EndsAtMS + if err := s.store.UpdateAppSplashScreen(&item); err != nil { + return AppSplashScreen{}, err + } + return appSplashScreenFromModel(item), nil +} + +func (s *AppConfigService) DeleteSplashScreen(appCode string, id uint) error { + return s.store.DeleteAppSplashScreen(appctx.Normalize(appCode), id) +} + func (s *AppConfigService) ListAppVersions(appCode string, options repository.AppVersionListOptions) ([]AppVersion, error) { options.AppCode = appctx.Normalize(appCode) options.Platform = normalizeAppPlatform(options.Platform) @@ -495,6 +577,91 @@ func appBannerFromModel(item model.AppBanner) AppBanner { } } +func splashScreenModelFromRequest(appCode string, req splashScreenRequest) (model.AppSplashScreen, error) { + item := model.AppSplashScreen{ + AppCode: appctx.Normalize(appCode), + CoverURL: strings.TrimSpace(req.CoverURL), + SplashType: normalizeSplashType(req.SplashType), + Param: strings.TrimSpace(req.Param), + Status: normalizeBannerStatus(req.Status), + Platform: normalizeBannerPlatform(req.Platform), + SortOrder: req.SortOrder, + RegionID: req.RegionID, + CountryCode: normalizeCountryCode(req.CountryCode), + Description: strings.TrimSpace(req.Description), + StartsAtMS: req.StartsAtMs, + EndsAtMS: req.EndsAtMs, + } + if item.CoverURL == "" || len(item.CoverURL) > 1024 { + return model.AppSplashScreen{}, errors.New("splash cover is invalid") + } + if containsWhitespace(item.CoverURL) { + return model.AppSplashScreen{}, errors.New("splash cover cannot contain whitespace") + } + if item.SplashType != "h5" && item.SplashType != "app" { + return model.AppSplashScreen{}, errors.New("splash type is invalid") + } + if item.SplashType == "h5" { + // H5 开屏必须带可直接打开的链接;APP 类型参数由客户端解释,所以只做长度限制。 + if item.Param == "" { + return model.AppSplashScreen{}, errors.New("h5 link is required") + } + if err := validateH5URL(item.Param); err != nil { + return model.AppSplashScreen{}, err + } + } + if len(item.Param) > 2048 { + return model.AppSplashScreen{}, errors.New("splash param is too long") + } + if item.Status != bannerStatusActive && item.Status != bannerStatusDisabled && item.Status != bannerStatusExpired { + return model.AppSplashScreen{}, errors.New("splash status is invalid") + } + if item.StartsAtMS < 0 || item.EndsAtMS < 0 { + return model.AppSplashScreen{}, errors.New("splash delivery time is invalid") + } + if item.StartsAtMS > 0 && item.EndsAtMS > 0 && item.StartsAtMS >= item.EndsAtMS { + return model.AppSplashScreen{}, errors.New("splash delivery time range is invalid") + } + if item.Status == bannerStatusActive && item.EndsAtMS > 0 && item.EndsAtMS <= time.Now().UTC().UnixMilli() { + // 管理员保存已经结束的 active 配置时直接落为 expired,列表和 App 侧都不会误判为可投放。 + item.Status = bannerStatusExpired + } + if item.Platform != "android" && item.Platform != "ios" { + return model.AppSplashScreen{}, errors.New("splash platform is invalid") + } + if item.RegionID < 0 { + return model.AppSplashScreen{}, errors.New("splash region is invalid") + } + if item.CountryCode != "" && !validCountryCode(item.CountryCode) { + return model.AppSplashScreen{}, errors.New("splash country is invalid") + } + if utf8.RuneCountInString(item.Description) > 255 { + return model.AppSplashScreen{}, errors.New("splash description is too long") + } + return item, nil +} + +func appSplashScreenFromModel(item model.AppSplashScreen) AppSplashScreen { + // DTO 只返回后台页面需要的字段,不暴露数据库列名;gateway 另有 snake_case 的 App 输出结构。 + return AppSplashScreen{ + ID: item.ID, + AppCode: item.AppCode, + CoverURL: item.CoverURL, + SplashType: item.SplashType, + Param: item.Param, + Status: item.Status, + Platform: item.Platform, + SortOrder: item.SortOrder, + RegionID: item.RegionID, + CountryCode: item.CountryCode, + Description: item.Description, + StartsAtMs: item.StartsAtMS, + EndsAtMs: item.EndsAtMS, + CreatedAtMs: item.CreatedAtMS, + UpdatedAtMs: item.UpdatedAtMS, + } +} + func appVersionModelFromRequest(appCode string, req appVersionRequest) (model.AppVersion, error) { item := model.AppVersion{ AppCode: appctx.Normalize(appCode), @@ -580,6 +747,10 @@ func normalizeBannerType(value string) string { return strings.ToLower(strings.TrimSpace(value)) } +func normalizeSplashType(value string) string { + return strings.ToLower(strings.TrimSpace(value)) +} + func normalizeBannerStatus(value string) string { value = strings.ToLower(strings.TrimSpace(value)) if value == "" { diff --git a/server/admin/internal/modules/appconfig/service_test.go b/server/admin/internal/modules/appconfig/service_test.go index 39d8d118..e33a63ef 100644 --- a/server/admin/internal/modules/appconfig/service_test.go +++ b/server/admin/internal/modules/appconfig/service_test.go @@ -104,6 +104,82 @@ func TestAppBannerFromModelReturnsDisplayScopes(t *testing.T) { } } +func TestSplashScreenModelFromRequestValidatesH5Config(t *testing.T) { + startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli() + endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond) + item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "h5", + Param: "https://h5.example.com/splash", + Status: bannerStatusActive, + Platform: "android", + SortOrder: 7, + RegionID: 1001, + CountryCode: "us", + Description: "launch campaign", + StartsAtMs: startsAtMs, + EndsAtMs: endsAtMs, + }) + if err != nil { + t.Fatalf("splash screen model should be valid: %v", err) + } + if item.AppCode != "lalu" || item.SplashType != "h5" || item.CountryCode != "US" || item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs { + t.Fatalf("splash screen model mismatch: %+v", item) + } +} + +func TestSplashScreenModelFromRequestRequiresH5Link(t *testing.T) { + _, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "h5", + Status: bannerStatusActive, + Platform: "android", + }) + if err == nil { + t.Fatal("expected h5 splash screen without link to fail") + } +} + +func TestSplashScreenModelFromRequestExpiresEndedActiveConfig(t *testing.T) { + item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "app", + Status: bannerStatusActive, + Platform: "ios", + EndsAtMs: 1, + }) + if err != nil { + t.Fatalf("ended app splash screen should be accepted: %v", err) + } + if item.Status != bannerStatusExpired { + t.Fatalf("ended active splash screen must become expired: %+v", item) + } +} + +func TestAppSplashScreenFromModelMapsDTOFields(t *testing.T) { + item := appSplashScreenFromModel(model.AppSplashScreen{ + ID: 9, + AppCode: "lalu", + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "app", + Param: "profile", + Status: bannerStatusDisabled, + Platform: "ios", + SortOrder: 3, + RegionID: 2002, + CountryCode: "BR", + Description: "disabled campaign", + StartsAtMS: 1700000000000, + EndsAtMS: 1800000000000, + CreatedAtMS: 1690000000000, + UpdatedAtMS: 1700000002000, + }) + + if item.ID != 9 || item.SplashType != "app" || item.Param != "profile" || item.RegionID != 2002 || item.UpdatedAtMs != 1700000002000 { + t.Fatalf("splash screen dto mismatch: %+v", item) + } +} + func bannerModel(displayScope string) model.AppBanner { return model.AppBanner{ ID: 1, diff --git a/server/admin/internal/repository/app_splash_screen_repository.go b/server/admin/internal/repository/app_splash_screen_repository.go new file mode 100644 index 00000000..cd37a690 --- /dev/null +++ b/server/admin/internal/repository/app_splash_screen_repository.go @@ -0,0 +1,75 @@ +package repository + +import ( + "strings" + + "hyapp-admin-server/internal/model" +) + +type AppSplashScreenListOptions struct { + AppCode string + Keyword string + Status string + Platform string + RegionID int64 + Country string +} + +func (s *Store) ListAppSplashScreens(options AppSplashScreenListOptions) ([]model.AppSplashScreen, error) { + var items []model.AppSplashScreen + // 后台列表始终先按 app_code 隔离,再叠加可选筛选;区域 0 和国家空值在管理端表示全量配置,不需要额外匹配。 + query := s.db.Where("app_code = ?", strings.TrimSpace(options.AppCode)) + if options.Status != "" { + query = query.Where("status = ?", strings.TrimSpace(options.Status)) + } + if options.Platform != "" { + query = query.Where("platform = ?", strings.TrimSpace(options.Platform)) + } + if options.RegionID > 0 { + query = query.Where("region_id = ?", options.RegionID) + } + if options.Country != "" { + query = query.Where("country_code = ?", strings.TrimSpace(options.Country)) + } + if options.Keyword != "" { + like := "%" + strings.TrimSpace(options.Keyword) + "%" + // 关键字只查人工可识别的字段,避免把封面 URL 这类长字段带进模糊查询拖慢列表。 + query = query.Where("(description LIKE ? OR param LIKE ? OR country_code LIKE ?)", like, like, like) + } + err := query.Order("sort_order ASC, id DESC").Find(&items).Error + return items, err +} + +func (s *Store) ExpireAppSplashScreens(appCode string, nowMs int64) error { + appCode = strings.TrimSpace(appCode) + if appCode == "" || nowMs <= 0 { + return nil + } + // 过期回收只处理 active 且已到结束时间的记录,手动关闭和历史 expired 状态保持原样,方便后台审计。 + return s.db.Model(&model.AppSplashScreen{}). + Where("app_code = ? AND status = ? AND ends_at_ms > 0 AND ends_at_ms <= ?", appCode, "active", nowMs). + Updates(map[string]any{ + "status": "expired", + "updated_at_ms": nowMs, + }).Error +} + +func (s *Store) GetAppSplashScreen(appCode string, id uint) (model.AppSplashScreen, error) { + var item model.AppSplashScreen + // app_code 和 id 共同限定记录,避免多 App 后台误编辑其他租户配置。 + err := s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).First(&item).Error + return item, err +} + +func (s *Store) CreateAppSplashScreen(item *model.AppSplashScreen) error { + return s.db.Create(item).Error +} + +func (s *Store) UpdateAppSplashScreen(item *model.AppSplashScreen) error { + return s.db.Save(item).Error +} + +func (s *Store) DeleteAppSplashScreen(appCode string, id uint) error { + // 删除沿用硬删除语义,和 Banner 配置保持一致;操作日志在 handler 层记录。 + return s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).Delete(&model.AppSplashScreen{}).Error +} diff --git a/server/admin/internal/repository/repository.go b/server/admin/internal/repository/repository.go index 8b630098..ed4a0f08 100644 --- a/server/admin/internal/repository/repository.go +++ b/server/admin/internal/repository/repository.go @@ -64,6 +64,7 @@ func (s *Store) AutoMigrate() error { &model.Menu{}, &model.AppConfig{}, &model.AppBanner{}, + &model.AppSplashScreen{}, &model.AppVersion{}, &model.AppExploreTab{}, &model.HostAgencySalaryPolicy{}, diff --git a/server/admin/internal/repository/seed.go b/server/admin/internal/repository/seed.go index 0db4b6e0..fddeb14d 100644 --- a/server/admin/internal/repository/seed.go +++ b/server/admin/internal/repository/seed.go @@ -249,9 +249,10 @@ func (s *Store) seedMenus() error { {ParentID: &roomsID, Title: "房间配置", Code: "room-config", Path: "/rooms/config", Icon: "settings", PermissionCode: "room-config:view", Sort: 67, 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: "Explore配置", Code: "app-config-explore", Path: "/app-config/explore", Icon: "explore", PermissionCode: "app-config:view", Sort: 68, Visible: true}, - {ParentID: &appConfigID, Title: "内购配置", Code: "payment-recharge-products", Path: "/app-config/recharge-products", Icon: "wallet", PermissionCode: "payment-product:view", Sort: 69, Visible: true}, - {ParentID: &appConfigID, Title: "版本管理", Code: "app-config-versions", Path: "/app-config/versions", Icon: "settings", PermissionCode: "app-version:view", Sort: 70, 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}, + {ParentID: &appConfigID, Title: "Explore配置", Code: "app-config-explore", Path: "/app-config/explore", Icon: "explore", PermissionCode: "app-config:view", Sort: 69, Visible: true}, + {ParentID: &appConfigID, Title: "内购配置", Code: "payment-recharge-products", Path: "/app-config/recharge-products", Icon: "wallet", PermissionCode: "payment-product:view", Sort: 70, Visible: true}, + {ParentID: &appConfigID, Title: "版本管理", Code: "app-config-versions", Path: "/app-config/versions", Icon: "settings", PermissionCode: "app-version:view", Sort: 71, Visible: true}, {ParentID: &resourceID, Title: "资源列表", Code: "resource-list", Path: "/resources", Icon: "inventory", PermissionCode: "resource:view", Sort: 67, Visible: true}, {ParentID: &resourceID, Title: "道具商店", Code: "resource-shop-list", Path: "/resource-shop", Icon: "storefront", PermissionCode: "resource-shop:view", Sort: 68, Visible: true}, {ParentID: &resourceID, Title: "资源组列表", Code: "resource-group-list", Path: "/resource-groups", Icon: "category", PermissionCode: "resource-group:view", Sort: 69, Visible: true}, diff --git a/server/admin/migrations/042_admin_app_splash_screens.sql b/server/admin/migrations/042_admin_app_splash_screens.sql new file mode 100644 index 00000000..47b3e356 --- /dev/null +++ b/server/admin/migrations/042_admin_app_splash_screens.sql @@ -0,0 +1,50 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 开屏配置和 Banner 共用投放维度,但没有显示范围和房间小图;gateway 只读取当前有效窗口。 +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +CREATE TABLE IF NOT EXISTS admin_app_splash_screens ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '主键 ID', + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + cover_url VARCHAR(1024) NOT NULL COMMENT '开屏图片 URL', + splash_type VARCHAR(16) NOT NULL COMMENT '开屏跳转类型', + param VARCHAR(2048) NOT NULL DEFAULT '' COMMENT '跳转参数', + status VARCHAR(24) NOT NULL DEFAULT 'active' COMMENT '业务状态', + platform VARCHAR(24) NOT NULL COMMENT '平台', + sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重,数值越小越靠前', + region_id BIGINT NOT NULL DEFAULT 0 COMMENT '区域 ID,0 表示全部区域', + country_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '国家或地区编码,空表示全部国家', + description VARCHAR(255) NOT NULL DEFAULT '' COMMENT '描述信息', + starts_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '投放开始时间,UTC epoch ms', + ends_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '投放结束时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + INDEX idx_admin_app_splash_screens_app_sort (app_code, status, sort_order, id), + INDEX idx_admin_app_splash_screens_scope (app_code, platform, region_id, country_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台 App 开屏配置表'; + +INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) +SELECT parent.id, '开屏配置', 'app-config-splash-screens', '/app-config/splash-screens', 'image', 'app-config:view', 68, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'app-config' +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; + +UPDATE admin_menus +SET sort = 69, updated_at_ms = @now_ms +WHERE code = 'app-config-explore' AND sort < 69; + +UPDATE admin_menus +SET sort = 70, updated_at_ms = @now_ms +WHERE code = 'payment-recharge-products' AND sort < 70; + +UPDATE admin_menus +SET sort = 71, updated_at_ms = @now_ms +WHERE code = 'app-config-versions' AND sort < 71; diff --git a/services/gateway-service/internal/appconfig/mysql.go b/services/gateway-service/internal/appconfig/mysql.go index 153a7b5a..eaded1b0 100644 --- a/services/gateway-service/internal/appconfig/mysql.go +++ b/services/gateway-service/internal/appconfig/mysql.go @@ -35,6 +35,16 @@ const listAppBannersSQL = ` AND (starts_at_ms = 0 OR starts_at_ms <= ?) AND (ends_at_ms = 0 OR ends_at_ms > ?) ORDER BY sort_order ASC, id DESC` +const listSplashScreensSQL = ` + SELECT id, app_code, cover_url, splash_type, param, platform, sort_order, region_id, country_code, description, starts_at_ms, ends_at_ms, updated_at_ms + FROM admin_app_splash_screens + WHERE app_code = ? AND status = 'active' + AND (? = '' OR platform = ?) + AND (region_id = 0 OR region_id = ?) + AND (country_code = '' OR country_code = ?) + AND (starts_at_ms = 0 OR starts_at_ms <= ?) + AND (ends_at_ms = 0 OR ends_at_ms > ?) + ORDER BY sort_order ASC, id DESC` const latestAppVersionSQL = ` SELECT id, app_code, platform, version, build_number, force_update, download_url, COALESCE(description, ''), updated_at_ms FROM admin_app_versions @@ -95,6 +105,30 @@ type Banner struct { UpdatedAtMs int64 `json:"updated_at_ms"` } +// SplashScreenQuery 是 App 开屏配置的公开筛选条件。 +type SplashScreenQuery struct { + AppCode string + Platform string + RegionID int64 + Country string +} + +// SplashScreen 是 gateway 下发给 App 的开屏配置;type 语义和 banner 保持一致,h5 参数是链接,app 参数由客户端解释。 +type SplashScreen struct { + ID uint `json:"id"` + CoverURL string `json:"cover_url"` + SplashType string `json:"type"` + Param string `json:"param"` + Platform string `json:"platform"` + SortOrder int `json:"sort_order"` + RegionID int64 `json:"region_id"` + CountryCode string `json:"country_code"` + Description string `json:"description"` + StartsAtMs int64 `json:"starts_at_ms"` + EndsAtMs int64 `json:"ends_at_ms"` + UpdatedAtMs int64 `json:"updated_at_ms"` +} + // VersionQuery 是 App 检查更新的公开筛选条件。 type VersionQuery struct { AppCode string @@ -120,6 +154,7 @@ type Reader interface { ListH5Links(ctx context.Context, query H5LinkQuery) ([]H5Link, error) ListExploreTabs(ctx context.Context, appCode string) ([]ExploreTab, error) ListBanners(ctx context.Context, query BannerQuery) ([]Banner, error) + ListSplashScreens(ctx context.Context, query SplashScreenQuery) ([]SplashScreen, error) LatestVersion(ctx context.Context, query VersionQuery) (Version, error) } @@ -283,6 +318,53 @@ func (r *MySQLReader) ListBanners(ctx context.Context, query BannerQuery) ([]Ban return items, nil } +// ListSplashScreens 返回当前 App、平台和地域可见的开屏配置;未限定平台时返回所有平台配置,客户端可按需自筛。 +func (r *MySQLReader) ListSplashScreens(ctx context.Context, query SplashScreenQuery) ([]SplashScreen, error) { + if r == nil || r.db == nil { + return nil, errors.New("app config reader is not configured") + } + + appCode := normalizeAppCode(query.AppCode) + platform := normalizePlatform(query.Platform) + country := normalizeCountry(query.Country) + nowMs := time.Now().UTC().UnixMilli() + rows, err := r.db.QueryContext(ctx, listSplashScreensSQL, appCode, platform, platform, query.RegionID, country, nowMs, nowMs) + if err != nil { + return nil, err + } + defer rows.Close() + + items := make([]SplashScreen, 0) + for rows.Next() { + var item SplashScreen + var appCode string + var updatedAtMS int64 + if err := rows.Scan( + &item.ID, + &appCode, + &item.CoverURL, + &item.SplashType, + &item.Param, + &item.Platform, + &item.SortOrder, + &item.RegionID, + &item.CountryCode, + &item.Description, + &item.StartsAtMs, + &item.EndsAtMs, + &updatedAtMS, + ); err != nil { + return nil, err + } + item.UpdatedAtMs = updatedAtMS + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + // LatestVersion 返回指定 App 和平台的最高 build_number 版本;未配置时返回空版本,方便 App 端按无更新处理。 func (r *MySQLReader) LatestVersion(ctx context.Context, query VersionQuery) (Version, error) { if r == nil || r.db == nil { @@ -315,6 +397,7 @@ type StaticReader struct { Links []H5Link ExploreTabs []ExploreTab Banners []Banner + SplashScreens []SplashScreen Version Version Err error PositionAliases map[string]string @@ -361,6 +444,17 @@ func (r StaticReader) ListBanners(context.Context, BannerQuery) ([]Banner, error return out, nil } +// ListSplashScreens 返回预置开屏配置,供 HTTP 单测和无 MySQL 场景复用同一 Reader 接口。 +func (r StaticReader) ListSplashScreens(context.Context, SplashScreenQuery) ([]SplashScreen, error) { + if r.Err != nil { + return nil, r.Err + } + + out := make([]SplashScreen, len(r.SplashScreens)) + copy(out, r.SplashScreens) + return out, nil +} + // LatestVersion 返回预置版本配置。 func (r StaticReader) LatestVersion(_ context.Context, query VersionQuery) (Version, error) { if r.Err != nil { diff --git a/services/gateway-service/internal/transport/http/appapi/app_config_handler.go b/services/gateway-service/internal/transport/http/appapi/app_config_handler.go index aeba5dda..5dcc899d 100644 --- a/services/gateway-service/internal/transport/http/appapi/app_config_handler.go +++ b/services/gateway-service/internal/transport/http/appapi/app_config_handler.go @@ -142,6 +142,27 @@ func (h *Handler) listAppBanners(writer http.ResponseWriter, request *http.Reque httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)}) } +// listSplashScreens 返回后台 APP配置/开屏配置 中当前可见的开屏列表;客户端按列表顺序取最高优先级配置展示。 +func (h *Handler) listSplashScreens(writer http.ResponseWriter, request *http.Request) { + if h.appConfigReader == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + + items, err := h.appConfigReader.ListSplashScreens(request.Context(), appconfig.SplashScreenQuery{ + AppCode: appcode.FromContext(request.Context()), + Platform: httpkit.FirstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform"), + RegionID: optionalInt64Query(request, "region_id"), + Country: httpkit.FirstQueryOrHeader(request, "country", "X-Country-Code", "X-App-Country"), + }) + if err != nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + + httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)}) +} + // getAppVersion 返回当前平台最新版本;App 用 build_number 判定是否需要提示升级。 func (h *Handler) getAppVersion(writer http.ResponseWriter, request *http.Request) { if h.appConfigReader == nil { diff --git a/services/gateway-service/internal/transport/http/appapi/handler.go b/services/gateway-service/internal/transport/http/appapi/handler.go index 06fa397f..11a7f9ee 100644 --- a/services/gateway-service/internal/transport/http/appapi/handler.go +++ b/services/gateway-service/internal/transport/http/appapi/handler.go @@ -14,6 +14,7 @@ type ConfigReader interface { ListH5Links(ctx context.Context, query appconfig.H5LinkQuery) ([]appconfig.H5Link, error) ListExploreTabs(ctx context.Context, appCode string) ([]appconfig.ExploreTab, error) ListBanners(ctx context.Context, query appconfig.BannerQuery) ([]appconfig.Banner, error) + ListSplashScreens(ctx context.Context, query appconfig.SplashScreenQuery) ([]appconfig.SplashScreen, error) LatestVersion(ctx context.Context, query appconfig.VersionQuery) (appconfig.Version, error) } @@ -63,6 +64,10 @@ func (h *Handler) ListAppBanners(writer http.ResponseWriter, request *http.Reque h.listAppBanners(writer, request) } +func (h *Handler) ListSplashScreens(writer http.ResponseWriter, request *http.Request) { + h.listSplashScreens(writer, request) +} + func (h *Handler) GetAppVersion(writer http.ResponseWriter, request *http.Request) { h.getAppVersion(writer, request) } diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index 1b3f9382..0f444c5a 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -44,6 +44,7 @@ type AppHandlers struct { ListH5Links http.HandlerFunc ListExploreTabs http.HandlerFunc ListAppBanners http.HandlerFunc + ListSplashScreens http.HandlerFunc GetAppVersion http.HandlerFunc GetResourceGroup http.HandlerFunc ListResourceShopItems http.HandlerFunc @@ -308,6 +309,7 @@ func (r routes) registerAppRoutes() { r.public("/app/h5-links", "", h.ListH5Links) r.public("/app/explore-tabs", http.MethodGet, h.ListExploreTabs) r.public("/app/banners", "", h.ListAppBanners) + r.public("/app/splash-screens", http.MethodGet, h.ListSplashScreens) r.public("/app/version", http.MethodGet, h.GetAppVersion) r.public("/resource-groups/{group_id}", "", h.GetResourceGroup) r.public("/resource-shop/items", http.MethodGet, h.ListResourceShopItems) diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index 9b62eaef..de0d1471 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -3473,6 +3473,7 @@ func TestAppBootstrapIsPublicAndLightweight(t *testing.T) { type captureAppConfigReader struct { appconfig.StaticReader bannerQuery appconfig.BannerQuery + splashQuery appconfig.SplashScreenQuery } func (r *captureAppConfigReader) ListBanners(ctx context.Context, query appconfig.BannerQuery) ([]appconfig.Banner, error) { @@ -3480,6 +3481,11 @@ func (r *captureAppConfigReader) ListBanners(ctx context.Context, query appconfi return r.StaticReader.ListBanners(ctx, query) } +func (r *captureAppConfigReader) ListSplashScreens(ctx context.Context, query appconfig.SplashScreenQuery) ([]appconfig.SplashScreen, error) { + r.splashQuery = query + return r.StaticReader.ListSplashScreens(ctx, query) +} + func TestListAppBannersReturnsAdminAppConfig(t *testing.T) { handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) reader := &captureAppConfigReader{StaticReader: appconfig.StaticReader{Banners: []appconfig.Banner{ @@ -3547,6 +3553,88 @@ func TestListAppBannersReturnsAdminAppConfig(t *testing.T) { } } +func TestListSplashScreensReturnsAdminAppConfig(t *testing.T) { + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + reader := &captureAppConfigReader{StaticReader: appconfig.StaticReader{SplashScreens: []appconfig.SplashScreen{ + { + ID: 12, + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "h5", + Param: "https://h5.example.com/splash", + Platform: "ios", + SortOrder: 2, + RegionID: 7, + CountryCode: "BR", + Description: "launch splash", + StartsAtMs: 1700000000000, + EndsAtMs: 1800000000000, + UpdatedAtMs: 1700000003000, + }, + }}} + handler.SetAppConfigReader(reader) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/app/splash-screens?platform=ios&country=BR®ion_id=7", nil) + request.Header.Set("X-Request-ID", "req-app-splash") + request.Header.Set("X-App-Code", "lalu") + 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()) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data, ok := response.Data.(map[string]any) + if response.Code != httpkit.CodeOK || !ok { + t.Fatalf("unexpected envelope: %+v", response) + } + if reader.splashQuery.AppCode != "lalu" || reader.splashQuery.Platform != "ios" || reader.splashQuery.RegionID != 7 || reader.splashQuery.Country != "BR" { + t.Fatalf("splash query mismatch: %+v", reader.splashQuery) + } + if data["total"].(float64) != 1 { + t.Fatalf("splash total mismatch: %+v", data) + } + items, ok := data["items"].([]any) + if !ok || len(items) != 1 { + t.Fatalf("splash items shape mismatch: %+v", data) + } + first, ok := items[0].(map[string]any) + if !ok || first["cover_url"] != "https://cdn.example.com/splash.png" || first["type"] != "h5" || first["param"] != "https://h5.example.com/splash" { + t.Fatalf("splash item mismatch: %+v", first) + } + if first["platform"] != "ios" || first["sort_order"].(float64) != 2 || first["region_id"].(float64) != 7 || first["country_code"] != "BR" { + t.Fatalf("splash scope mismatch: %+v", first) + } + if first["starts_at_ms"].(float64) != 1700000000000 || first["ends_at_ms"].(float64) != 1800000000000 || first["updated_at_ms"].(float64) != 1700000003000 { + t.Fatalf("splash metadata mismatch: %+v", first) + } +} + +func TestSplashScreenJSONIncludesEmptyScopeFields(t *testing.T) { + payload, err := json.Marshal(appconfig.SplashScreen{ + ID: 99, + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "app", + Platform: "android", + UpdatedAtMs: 1700000003000, + }) + if err != nil { + t.Fatalf("marshal splash screen failed: %v", err) + } + var item map[string]any + if err := json.Unmarshal(payload, &item); err != nil { + t.Fatalf("unmarshal splash screen failed: %v", err) + } + for _, key := range []string{"region_id", "country_code", "description", "starts_at_ms", "ends_at_ms"} { + if _, ok := item[key]; !ok { + t.Fatalf("splash json missing %s: %+v", key, item) + } + } +} + func TestGetAppVersionReturnsLatestVersionAndUpdateDecision(t *testing.T) { handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) handler.SetAppConfigReader(appconfig.StaticReader{Version: appconfig.Version{ diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index 4156ea8d..cc088df6 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -151,6 +151,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { ListH5Links: appAPI.ListH5Links, ListExploreTabs: appAPI.ListExploreTabs, ListAppBanners: appAPI.ListAppBanners, + ListSplashScreens: appAPI.ListSplashScreens, GetAppVersion: appAPI.GetAppVersion, GetResourceGroup: resourceAPI.GetResourceGroup, ListResourceShopItems: resourceAPI.ListResourceShopItems, From 709fd00ab2cbe7f76d40c8c49d47f91392736fa1 Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 10 Jun 2026 11:14:49 +0800 Subject: [PATCH 10/28] =?UTF-8?q?bd=20=E5=92=8C=20bd=20leader=E5=8C=BA?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/proto/user/v1/host.pb.go | 26 ++- api/proto/user/v1/host.proto | 2 + .../mysql/038_split_bd_leader_profiles.sql | 26 +++ .../internal/integration/userclient/client.go | 2 + .../admin/internal/modules/hostorg/handler.go | 16 +- .../admin/internal/modules/hostorg/reader.go | 116 +++++++++++- .../admin/internal/modules/hostorg/service.go | 3 +- .../modules/teamsalarysettlement/service.go | 18 +- .../service_integration_test.go | 12 +- .../internal/transport/http/response_test.go | 45 ++++- .../http/userapi/bd_leader_center_handler.go | 4 +- .../http/walletapi/salary_wallet_handler.go | 5 + .../deploy/mysql/initdb/001_user_service.sql | 11 ++ .../user-service/internal/domain/host/host.go | 4 +- .../internal/service/host/commands.go | 2 + .../internal/service/host/service.go | 32 +++- .../internal/service/host/service_test.go | 133 ++++++++++++- .../internal/storage/mysql/host/admin.go | 48 +++-- .../internal/storage/mysql/host/common.go | 3 + .../storage/mysql/host/invitations.go | 57 +++--- .../internal/storage/mysql/host/queries.go | 91 +++++++-- .../internal/storage/mysql/host/writes.go | 23 ++- .../internal/testutil/mysqltest/mysqltest.go | 18 +- .../transport/grpc/bd_identity_flow_test.go | 174 ++++++++++++++++++ .../internal/transport/grpc/host.go | 6 +- .../internal/transport/grpc/host_admin.go | 1 + 26 files changed, 773 insertions(+), 105 deletions(-) create mode 100644 scripts/mysql/038_split_bd_leader_profiles.sql create mode 100644 services/user-service/internal/transport/grpc/bd_identity_flow_test.go diff --git a/api/proto/user/v1/host.pb.go b/api/proto/user/v1/host.pb.go index 57d6d79e..80e68078 100644 --- a/api/proto/user/v1/host.pb.go +++ b/api/proto/user/v1/host.pb.go @@ -2513,6 +2513,7 @@ type GetBDProfileRequest 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"` + Role string `protobuf:"bytes,3,opt,name=role,proto3" json:"role,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2561,6 +2562,13 @@ func (x *GetBDProfileRequest) GetUserId() int64 { return 0 } +func (x *GetBDProfileRequest) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + type GetBDProfileResponse struct { state protoimpl.MessageState `protogen:"open.v1"` BdProfile *BDProfile `protobuf:"bytes,1,opt,name=bd_profile,json=bdProfile,proto3" json:"bd_profile,omitempty"` @@ -3565,6 +3573,7 @@ type SetBDStatusRequest struct { TargetUserId int64 `protobuf:"varint,4,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"` + Role string `protobuf:"bytes,7,opt,name=role,proto3" json:"role,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3641,6 +3650,13 @@ func (x *SetBDStatusRequest) GetReason() string { return "" } +func (x *SetBDStatusRequest) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + type SetBDStatusResponse struct { state protoimpl.MessageState `protogen:"open.v1"` BdProfile *BDProfile `protobuf:"bytes,1,opt,name=bd_profile,json=bdProfile,proto3" json:"bd_profile,omitempty"` @@ -4717,10 +4733,11 @@ const file_proto_user_v1_host_proto_rawDesc = "" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\"W\n" + "\x16GetHostProfileResponse\x12=\n" + - "\fhost_profile\x18\x01 \x01(\v2\x1a.hyapp.user.v1.HostProfileR\vhostProfile\"^\n" + + "\fhost_profile\x18\x01 \x01(\v2\x1a.hyapp.user.v1.HostProfileR\vhostProfile\"r\n" + "\x13GetBDProfileRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + - "\auser_id\x18\x02 \x01(\x03R\x06userId\"O\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x12\n" + + "\x04role\x18\x03 \x01(\tR\x04role\"O\n" + "\x14GetBDProfileResponse\x127\n" + "\n" + "bd_profile\x18\x01 \x01(\v2\x18.hyapp.user.v1.BDProfileR\tbdProfile\"f\n" + @@ -4785,7 +4802,7 @@ const file_proto_user_v1_host_proto_rawDesc = "" + "\x06reason\x18\x06 \x01(\tR\x06reason\"K\n" + "\x10CreateBDResponse\x127\n" + "\n" + - "bd_profile\x18\x01 \x01(\v2\x18.hyapp.user.v1.BDProfileR\tbdProfile\"\xdd\x01\n" + + "bd_profile\x18\x01 \x01(\v2\x18.hyapp.user.v1.BDProfileR\tbdProfile\"\xf1\x01\n" + "\x12SetBDStatusRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1d\n" + "\n" + @@ -4793,7 +4810,8 @@ const file_proto_user_v1_host_proto_rawDesc = "" + "\radmin_user_id\x18\x03 \x01(\x03R\vadminUserId\x12$\n" + "\x0etarget_user_id\x18\x04 \x01(\x03R\ftargetUserId\x12\x16\n" + "\x06status\x18\x05 \x01(\tR\x06status\x12\x16\n" + - "\x06reason\x18\x06 \x01(\tR\x06reason\"N\n" + + "\x06reason\x18\x06 \x01(\tR\x06reason\x12\x12\n" + + "\x04role\x18\a \x01(\tR\x04role\"N\n" + "\x13SetBDStatusResponse\x127\n" + "\n" + "bd_profile\x18\x01 \x01(\v2\x18.hyapp.user.v1.BDProfileR\tbdProfile\"\xca\x01\n" + diff --git a/api/proto/user/v1/host.proto b/api/proto/user/v1/host.proto index 586ef814..bf22a859 100644 --- a/api/proto/user/v1/host.proto +++ b/api/proto/user/v1/host.proto @@ -284,6 +284,7 @@ message GetHostProfileResponse { message GetBDProfileRequest { RequestMeta meta = 1; int64 user_id = 2; + string role = 3; } message GetBDProfileResponse { @@ -389,6 +390,7 @@ message SetBDStatusRequest { int64 target_user_id = 4; string status = 5; string reason = 6; + string role = 7; } message SetBDStatusResponse { diff --git a/scripts/mysql/038_split_bd_leader_profiles.sql b/scripts/mysql/038_split_bd_leader_profiles.sql new file mode 100644 index 00000000..f6d3771f --- /dev/null +++ b/scripts/mysql/038_split_bd_leader_profiles.sql @@ -0,0 +1,26 @@ +CREATE TABLE IF NOT EXISTS bd_leader_profiles ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', + region_id BIGINT NOT NULL COMMENT '区域 ID', + status VARCHAR(32) NOT NULL COMMENT '业务状态', + created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + KEY idx_bd_leader_profiles_region_status (app_code, region_id, status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BD Leader资料表'; + +INSERT INTO bd_leader_profiles ( + app_code, user_id, region_id, status, created_by_user_id, created_at_ms, updated_at_ms +) +SELECT app_code, user_id, region_id, status, created_by_user_id, created_at_ms, updated_at_ms +FROM bd_profiles +WHERE role = 'bd_leader' +ON DUPLICATE KEY UPDATE + region_id = VALUES(region_id), + status = VALUES(status), + created_by_user_id = VALUES(created_by_user_id), + created_at_ms = VALUES(created_at_ms), + updated_at_ms = VALUES(updated_at_ms); + +DELETE FROM bd_profiles +WHERE role = 'bd_leader'; diff --git a/server/admin/internal/integration/userclient/client.go b/server/admin/internal/integration/userclient/client.go index aef89d21..3d2b291c 100644 --- a/server/admin/internal/integration/userclient/client.go +++ b/server/admin/internal/integration/userclient/client.go @@ -317,6 +317,7 @@ type SetBDStatusRequest struct { CommandID string AdminUserID int64 TargetUserID int64 + Role string Status string Reason string } @@ -517,6 +518,7 @@ func (c *GRPCClient) SetBDStatus(ctx context.Context, req SetBDStatusRequest) (* CommandId: req.CommandID, AdminUserId: req.AdminUserID, TargetUserId: req.TargetUserID, + Role: req.Role, Status: req.Status, Reason: req.Reason, }) diff --git a/server/admin/internal/modules/hostorg/handler.go b/server/admin/internal/modules/hostorg/handler.go index e0026ae4..38f3fc8f 100644 --- a/server/admin/internal/modules/hostorg/handler.go +++ b/server/admin/internal/modules/hostorg/handler.go @@ -100,7 +100,7 @@ func (h *Handler) CreateBDLeader(c *gin.Context) { response.BadRequest(c, err.Error()) return } - writeHostOrgAuditLog(c, h.audit, "create-bd-leader", "bd_profiles", profile.UserID, + writeHostOrgAuditLog(c, h.audit, "create-bd-leader", "bd_leader_profiles", profile.UserID, fmt.Sprintf("command_id=%s user_id=%d region_id=%d position_alias=%q reason=%q", strings.TrimSpace(req.CommandID), profile.UserID, profile.RegionID, strings.TrimSpace(req.PositionAlias), strings.TrimSpace(req.Reason))) response.Created(c, profile) } @@ -131,16 +131,24 @@ func (h *Handler) SetBDStatus(c *gin.Context) { response.BadRequest(c, "BD 状态参数不正确") return } - profile, err := h.service.SetBDStatus(c.Request.Context(), adminActorID(c), targetUserID, middleware.CurrentRequestID(c), req) + role, auditTarget := bdRoleFromStatusRoute(c) + profile, err := h.service.SetBDStatus(c.Request.Context(), adminActorID(c), targetUserID, middleware.CurrentRequestID(c), role, req) if err != nil { response.BadRequest(c, err.Error()) return } - writeHostOrgAuditLog(c, h.audit, "set-bd-status", "bd_profiles", profile.UserID, + writeHostOrgAuditLog(c, h.audit, "set-bd-status", auditTarget, profile.UserID, fmt.Sprintf("command_id=%s user_id=%d status=%s reason=%q", strings.TrimSpace(req.CommandID), profile.UserID, profile.Status, strings.TrimSpace(req.Reason))) response.OK(c, profile) } +func bdRoleFromStatusRoute(c *gin.Context) (string, string) { + if strings.HasPrefix(c.FullPath(), "/admin/bd-leaders/") { + return "bd_leader", "bd_leader_profiles" + } + return "bd", "bd_profiles" +} + func (h *Handler) UpdateBDLeaderPositionAlias(c *gin.Context) { targetUserID, ok := parseInt64ID(c, "user_id") if !ok { @@ -171,7 +179,7 @@ func (h *Handler) DeleteBDLeader(c *gin.Context) { response.BadRequest(c, err.Error()) return } - writeHostOrgAuditLog(c, h.audit, "delete-bd-leader", "bd_profiles", profile.UserID, + writeHostOrgAuditLog(c, h.audit, "delete-bd-leader", "bd_leader_profiles", profile.UserID, fmt.Sprintf("user_id=%d", profile.UserID)) response.OK(c, profile) } diff --git a/server/admin/internal/modules/hostorg/reader.go b/server/admin/internal/modules/hostorg/reader.go index c3585640..ee119d99 100644 --- a/server/admin/internal/modules/hostorg/reader.go +++ b/server/admin/internal/modules/hostorg/reader.go @@ -53,11 +53,14 @@ type CoinSellerSalaryRateTier struct { UpdatedAtMs int64 `json:"updatedAtMs"` } -// ListBDProfiles 只读 user-service 的 BD 事实表,避免后台列表需求改动 user-service 发布物。 +// ListBDProfiles 只读 user-service 的 BD/BD Leader 事实表,后台列表按角色选择物理表,避免把两种身份重新混在一起。 func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role string) ([]*userclient.BDProfile, int64, error) { if r == nil || r.db == nil { return nil, 0, errUserDBNotConfigured() } + if role == "bd_leader" { + return r.listBDLeaderProfiles(ctx, query) + } appCode := appctx.FromContext(ctx) whereSQL := ` FROM bd_profiles bp @@ -156,6 +159,101 @@ func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role strin return items, total, nil } +func (r *Reader) listBDLeaderProfiles(ctx context.Context, query listQuery) ([]*userclient.BDProfile, int64, error) { + appCode := appctx.FromContext(ctx) + if query.ParentLeaderUserID > 0 { + // Leader 身份没有上级 Leader 字段;带父级筛选时直接返回空结果,避免误用普通 BD 的父级语义。 + return []*userclient.BDProfile{}, 0, nil + } + whereSQL := ` + FROM bd_leader_profiles bp + LEFT JOIN users u ON u.app_code = bp.app_code AND u.user_id = bp.user_id + LEFT JOIN users creator ON creator.app_code = bp.app_code AND creator.user_id = bp.created_by_user_id + LEFT JOIN regions r ON r.app_code = bp.app_code AND r.region_id = bp.region_id + WHERE bp.app_code = ?` + args := []any{appCode} + if query.Status != "" { + whereSQL += " AND bp.status = ?" + args = append(args, query.Status) + } + if query.RegionID > 0 { + whereSQL += " AND bp.region_id = ?" + args = append(args, query.RegionID) + } + if keyword := strings.TrimSpace(query.Keyword); keyword != "" { + like := "%" + keyword + "%" + whereSQL += " AND (CAST(bp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR r.name LIKE ?)" + args = append(args, like, like, like, like) + } + + total, err := countRows(ctx, r.db, whereSQL, args...) + if err != nil { + return nil, 0, err + } + rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` + SELECT bp.user_id, 'bd_leader', bp.region_id, 0, + bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms, + COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), + COALESCE(u.avatar, ''), COALESCE(r.name, ''), + '', '', '', + COALESCE(creator.current_display_user_id, ''), COALESCE(creator.username, ''), + COALESCE(creator.avatar, ''), + ( + SELECT COUNT(1) + FROM bd_profiles child + WHERE child.app_code = bp.app_code + AND child.role = 'bd' + AND child.parent_leader_user_id = bp.user_id + ) + %s + ORDER BY bp.created_at_ms DESC, bp.user_id DESC + LIMIT ? OFFSET ? + `, whereSQL), append(args, query.PageSize, offset(query.Page, query.PageSize))...) + if err != nil { + return nil, 0, err + } + defer rows.Close() + + items := make([]*userclient.BDProfile, 0, query.PageSize) + for rows.Next() { + item := &userclient.BDProfile{} + if err := rows.Scan( + &item.UserID, + &item.Role, + &item.RegionID, + &item.ParentLeaderUserID, + &item.Status, + &item.CreatedByUserID, + &item.CreatedAtMs, + &item.UpdatedAtMs, + &item.DisplayUserID, + &item.Username, + &item.Avatar, + &item.RegionName, + &item.ParentLeaderDisplayID, + &item.ParentLeaderUsername, + &item.ParentLeaderAvatar, + &item.CreatedByDisplayUserID, + &item.CreatedByUsername, + &item.CreatedByAvatar, + &item.SubBDCount, + ); err != nil { + return nil, 0, err + } + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, 0, err + } + if err := r.fillBDProfileCreators(ctx, items); err != nil { + return nil, 0, err + } + if err := r.fillBDLeaderPositionAliases(ctx, appCode, items); err != nil { + return nil, 0, err + } + return items, total, nil +} + func (r *Reader) GetBDLeader(ctx context.Context, userID int64) (*userclient.BDProfile, error) { if r == nil || r.db == nil { return nil, errUserDBNotConfigured() @@ -163,7 +261,7 @@ func (r *Reader) GetBDLeader(ctx context.Context, userID int64) (*userclient.BDP appCode := appctx.FromContext(ctx) item := &userclient.BDProfile{} err := r.db.QueryRowContext(ctx, ` - SELECT bp.user_id, bp.role, bp.region_id, COALESCE(bp.parent_leader_user_id, 0), + SELECT bp.user_id, 'bd_leader', bp.region_id, 0, bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms, COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), COALESCE(u.avatar, ''), COALESCE(r.name, ''), @@ -176,11 +274,11 @@ func (r *Reader) GetBDLeader(ctx context.Context, userID int64) (*userclient.BDP AND child.role = 'bd' AND child.parent_leader_user_id = bp.user_id ) - FROM bd_profiles bp + FROM bd_leader_profiles bp LEFT JOIN users u ON u.app_code = bp.app_code AND u.user_id = bp.user_id LEFT JOIN users creator ON creator.app_code = bp.app_code AND creator.user_id = bp.created_by_user_id LEFT JOIN regions r ON r.app_code = bp.app_code AND r.region_id = bp.region_id - WHERE bp.app_code = ? AND bp.user_id = ? AND bp.role = 'bd_leader' + WHERE bp.app_code = ? AND bp.user_id = ? `, appCode, userID).Scan( &item.UserID, &item.Role, @@ -351,10 +449,10 @@ func (r *Reader) DeleteBDLeader(ctx context.Context, userID int64) (*userclient. appCode := appctx.FromContext(ctx) item := &userclient.BDProfile{} err := r.db.QueryRowContext(ctx, ` - SELECT bp.user_id, bp.role, bp.region_id, COALESCE(bp.parent_leader_user_id, 0), + SELECT bp.user_id, 'bd_leader', bp.region_id, 0, bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms - FROM bd_profiles bp - WHERE bp.app_code = ? AND bp.user_id = ? AND bp.role = 'bd_leader' + FROM bd_leader_profiles bp + WHERE bp.app_code = ? AND bp.user_id = ? `, appCode, userID).Scan( &item.UserID, &item.Role, @@ -372,8 +470,8 @@ func (r *Reader) DeleteBDLeader(ctx context.Context, userID int64) (*userclient. return nil, err } result, err := r.db.ExecContext(ctx, ` - DELETE FROM bd_profiles - WHERE app_code = ? AND user_id = ? AND role = 'bd_leader' + DELETE FROM bd_leader_profiles + WHERE app_code = ? AND user_id = ? `, appCode, userID) if err != nil { return nil, err diff --git a/server/admin/internal/modules/hostorg/service.go b/server/admin/internal/modules/hostorg/service.go index 60e9f418..5722637a 100644 --- a/server/admin/internal/modules/hostorg/service.go +++ b/server/admin/internal/modules/hostorg/service.go @@ -193,13 +193,14 @@ func (s *Service) CreateBD(ctx context.Context, actorID int64, requestID string, }) } -func (s *Service) SetBDStatus(ctx context.Context, actorID int64, targetUserID int64, requestID string, req bdStatusRequest) (*userclient.BDProfile, error) { +func (s *Service) SetBDStatus(ctx context.Context, actorID int64, targetUserID int64, requestID string, role string, req bdStatusRequest) (*userclient.BDProfile, error) { return s.userClient.SetBDStatus(ctx, userclient.SetBDStatusRequest{ RequestID: requestID, Caller: "hyapp-admin-server", CommandID: strings.TrimSpace(req.CommandID), AdminUserID: actorID, TargetUserID: targetUserID, + Role: role, Status: strings.TrimSpace(req.Status), Reason: strings.TrimSpace(req.Reason), }) diff --git a/server/admin/internal/modules/teamsalarysettlement/service.go b/server/admin/internal/modules/teamsalarysettlement/service.go index 07af8ce9..b910a269 100644 --- a/server/admin/internal/modules/teamsalarysettlement/service.go +++ b/server/admin/internal/modules/teamsalarysettlement/service.go @@ -653,22 +653,30 @@ type bdProfile struct { ParentLeaderUserID int64 } -// bdProfilesByUser 按角色取 active profile,BD 和 BD Leader 共用一张表但语义不同,调用方必须显式传 role。 +// bdProfilesByUser 按角色取 active profile,普通 BD 和 BD Leader 已拆成两张物理表,调用方必须显式传 role。 func (s *Service) bdProfilesByUser(ctx context.Context, appCode string, userIDs []int64, role string) (map[int64]bdProfile, error) { userIDs = uniquePositiveUserIDs(userIDs) if len(userIDs) == 0 { return map[int64]bdProfile{}, nil } placeholders := placeholders(len(userIDs)) - args := make([]any, 0, len(userIDs)+2) - args = append(args, appCode, role) + args := make([]any, 0, len(userIDs)+1) + args = append(args, appCode) for _, id := range userIDs { args = append(args, id) } - rows, err := s.userDB.QueryContext(ctx, ` + query := ` SELECT user_id, role, region_id, COALESCE(parent_leader_user_id, 0) FROM bd_profiles - WHERE app_code = ? AND role = ? AND status = 'active' AND user_id IN (`+placeholders+`)`, args...) + WHERE app_code = ? AND role = 'bd' AND status = 'active' AND user_id IN (` + placeholders + `)` + if role == "bd_leader" { + query = ` + SELECT user_id, 'bd_leader', region_id, 0 + FROM bd_leader_profiles + WHERE app_code = ? AND status = 'active' AND user_id IN (` + placeholders + `)` + } + rows, err := s.userDB.QueryContext(ctx, ` + `+query, args...) if err != nil { return nil, err } diff --git a/server/admin/internal/modules/teamsalarysettlement/service_integration_test.go b/server/admin/internal/modules/teamsalarysettlement/service_integration_test.go index 98488f07..564dfe1e 100644 --- a/server/admin/internal/modules/teamsalarysettlement/service_integration_test.go +++ b/server/admin/internal/modules/teamsalarysettlement/service_integration_test.go @@ -157,8 +157,9 @@ func seedTeamSalaryFlow(t *testing.T, adminDB *sql.DB, userDB *sql.DB, walletDB ('lalu', 3001, 4001, 686, 'active'), ('lalu', 3002, 4001, 686, 'active')`, `INSERT INTO bd_profiles (app_code, user_id, role, region_id, parent_leader_user_id, status) VALUES - ('lalu', 4001, 'bd', 686, 5001, 'active'), - ('lalu', 5001, 'bd_leader', 686, 0, 'active')`, + ('lalu', 4001, 'bd', 686, 5001, 'active')`, + `INSERT INTO bd_leader_profiles (app_code, user_id, region_id, status) VALUES + ('lalu', 5001, 686, 'active')`, ) execAll(t, walletDB, fmt.Sprintf(`INSERT INTO host_salary_settlement_records (app_code, settlement_id, command_id, transaction_id, settlement_type, user_id, agency_owner_user_id, @@ -255,6 +256,13 @@ func teamSalaryUserDDL() []string { status VARCHAR(24) NOT NULL, PRIMARY KEY (app_code, user_id, role) )`, + `CREATE TABLE bd_leader_profiles ( + app_code VARCHAR(32) NOT NULL, + user_id BIGINT NOT NULL, + region_id BIGINT NOT NULL, + status VARCHAR(24) NOT NULL, + PRIMARY KEY (app_code, user_id) + )`, } } diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index de0d1471..67911736 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -4206,7 +4206,7 @@ func TestBDCenterOverviewUsesBDSalaryAndDirectAgencies(t *testing.T) { if recorder.Code != http.StatusOK { t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) } - if hostClient.lastBD == nil || hostClient.lastBD.GetUserId() != 42 || hostClient.lastBDAgencies == nil || hostClient.lastBDAgencies.GetBdUserId() != 42 { + if hostClient.lastBD == nil || hostClient.lastBD.GetUserId() != 42 || hostClient.lastBD.GetRole() != "bd" || hostClient.lastBDAgencies == nil || hostClient.lastBDAgencies.GetBdUserId() != 42 { t.Fatalf("bd center requests mismatch: profile=%+v agencies=%+v", hostClient.lastBD, hostClient.lastBDAgencies) } if walletClient.last == nil || walletClient.last.GetUserId() != 42 || len(walletClient.last.GetAssetTypes()) != 1 || walletClient.last.GetAssetTypes()[0] != "BD_SALARY_USD" { @@ -4253,7 +4253,7 @@ func TestBDCenterInviteAgencyDirectlyForwardsInviteAgency(t *testing.T) { if recorder.Code != http.StatusOK { t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) } - if hostClient.lastInviteAgency == nil || hostClient.lastInviteAgency.GetInviterUserId() != 42 || hostClient.lastInviteAgency.GetTargetUserId() != 99 || hostClient.lastInviteAgency.GetAgencyName() != "Target Agency" { + if hostClient.lastBD == nil || hostClient.lastBD.GetRole() != "bd" || hostClient.lastInviteAgency == nil || hostClient.lastInviteAgency.GetInviterUserId() != 42 || hostClient.lastInviteAgency.GetTargetUserId() != 99 || hostClient.lastInviteAgency.GetAgencyName() != "Target Agency" { t.Fatalf("bd invite agency request mismatch: %+v", hostClient.lastInviteAgency) } var response httpkit.ResponseEnvelope @@ -4267,6 +4267,47 @@ func TestBDCenterInviteAgencyDirectlyForwardsInviteAgency(t *testing.T) { } } +func TestBDLeaderInviteBDForwardsSelfInvite(t *testing.T) { + hostClient := &fakeUserHostClient{ + bdProfile: &userv1.BDProfile{UserId: 42, Role: "bd_leader", Status: "active", RegionId: 1001}, + inviteBDResp: &userv1.InviteBDResponse{Invitation: &userv1.RoleInvitation{ + InvitationId: 9002, + CommandId: "cmd-bd-self", + InvitationType: "bd", + Status: "accepted", + InviterUserId: 42, + InviterBdUserId: 42, + TargetUserId: 42, + ParentLeaderUserId: 42, + }}, + } + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetUserHostClient(hostClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/bd-leader/invitations/bd", bytes.NewReader([]byte(`{"command_id":"cmd-bd-self","target_user_id":"42"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-bd-self") + 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 hostClient.lastBD == nil || hostClient.lastBD.GetRole() != "bd_leader" || hostClient.lastInviteBD == nil || hostClient.lastInviteBD.GetInviterUserId() != 42 || hostClient.lastInviteBD.GetTargetUserId() != 42 { + t.Fatalf("self invite must be forwarded to user-service: %+v", hostClient.lastInviteBD) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data := response.Data.(map[string]any) + invitation := data["invitation"].(map[string]any) + if invitation["status"] != "accepted" || invitation["target_user_id"] != "42" || invitation["parent_leader_user_id"] != "42" { + t.Fatalf("bd self invite response mismatch: %+v", data) + } +} + func TestAgencyCenterOverviewRejectsNonOwner(t *testing.T) { hostClient := &fakeUserHostClient{roleSummary: &userv1.UserRoleSummary{UserId: 42, IsManager: false, AgencyId: 7001}} handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) diff --git a/services/gateway-service/internal/transport/http/userapi/bd_leader_center_handler.go b/services/gateway-service/internal/transport/http/userapi/bd_leader_center_handler.go index 6904e2ea..62202b2f 100644 --- a/services/gateway-service/internal/transport/http/userapi/bd_leader_center_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/bd_leader_center_handler.go @@ -395,6 +395,7 @@ func (h *Handler) resolveActiveBDLeader(writer http.ResponseWriter, request *htt resp, err := h.userHostClient.GetBDProfile(request.Context(), &userv1.GetBDProfileRequest{ Meta: httpkit.UserMeta(request, ""), UserId: userID, + Role: "bd_leader", }) if err != nil { httpkit.WriteRPCError(writer, request, err) @@ -416,13 +417,14 @@ func (h *Handler) resolveActiveBD(writer http.ResponseWriter, request *http.Requ resp, err := h.userHostClient.GetBDProfile(request.Context(), &userv1.GetBDProfileRequest{ Meta: httpkit.UserMeta(request, ""), UserId: userID, + Role: "bd", }) if err != nil { httpkit.WriteRPCError(writer, request, err) return nil, false } profile := resp.GetBdProfile() - if profile == nil || profile.GetStatus() != bdStatusActive || (profile.GetRole() != "bd" && profile.GetRole() != "bd_leader") { + if profile == nil || profile.GetStatus() != bdStatusActive || profile.GetRole() != "bd" { httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied") return nil, false } diff --git a/services/gateway-service/internal/transport/http/walletapi/salary_wallet_handler.go b/services/gateway-service/internal/transport/http/walletapi/salary_wallet_handler.go index 289d5705..1bc1d2ce 100644 --- a/services/gateway-service/internal/transport/http/walletapi/salary_wallet_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/salary_wallet_handler.go @@ -367,9 +367,14 @@ func (h *Handler) resolveAgencySalaryIdentity(writer http.ResponseWriter, reques } func (h *Handler) resolveBDSalaryIdentity(writer http.ResponseWriter, request *http.Request, userID int64, identity string) (salaryWalletIdentityContext, bool) { + role := "bd" + if identity == salaryIdentityBDLeader { + role = "bd_leader" + } resp, err := h.userHostClient.GetBDProfile(request.Context(), &userv1.GetBDProfileRequest{ Meta: httpkit.UserMeta(request, ""), UserId: userID, + Role: role, }) if err != nil { httpkit.WriteRPCError(writer, request, err) diff --git a/services/user-service/deploy/mysql/initdb/001_user_service.sql b/services/user-service/deploy/mysql/initdb/001_user_service.sql index 72c936c6..02cd9290 100644 --- a/services/user-service/deploy/mysql/initdb/001_user_service.sql +++ b/services/user-service/deploy/mysql/initdb/001_user_service.sql @@ -750,6 +750,17 @@ CREATE TABLE IF NOT EXISTS bd_profiles ( KEY idx_bd_profiles_parent_leader (app_code, parent_leader_user_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BD资料表'; +CREATE TABLE IF NOT EXISTS bd_leader_profiles ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', + region_id BIGINT NOT NULL COMMENT '区域 ID', + status VARCHAR(32) NOT NULL COMMENT '业务状态', + created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + KEY idx_bd_leader_profiles_region_status (app_code, region_id, status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BD Leader资料表'; + CREATE TABLE IF NOT EXISTS coin_seller_profiles ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', diff --git a/services/user-service/internal/domain/host/host.go b/services/user-service/internal/domain/host/host.go index 874dbedb..36170afd 100644 --- a/services/user-service/internal/domain/host/host.go +++ b/services/user-service/internal/domain/host/host.go @@ -28,7 +28,7 @@ const ( // BDRoleBD 是普通拓展角色。 BDRoleBD = "bd" - // BDRoleLeader 是 BD Leader,具备邀请 BD 的能力,同时也是 BD。 + // BDRoleLeader 是 BD Leader,具备邀请 BD 的能力;它与普通 BD 身份分表保存。 BDRoleLeader = "bd_leader" // CoinSellerStatusActive 表示币商身份有效,可以使用币商金币账户转账能力。 @@ -124,7 +124,7 @@ const ( ResultTypeMembership = "agency_membership" // ResultTypeInvitation 表示命令结果是 role_invitations 行。 ResultTypeInvitation = "role_invitation" - // ResultTypeBDProfile 表示命令结果是 bd_profiles 行。 + // ResultTypeBDProfile 表示命令结果是普通 BD 或 BD Leader 身份行。 ResultTypeBDProfile = "bd_profile" // ResultTypeAgency 表示命令结果是 agencies 行。 ResultTypeAgency = "agency" diff --git a/services/user-service/internal/service/host/commands.go b/services/user-service/internal/service/host/commands.go index d656d60d..e0d8c7e3 100644 --- a/services/user-service/internal/service/host/commands.go +++ b/services/user-service/internal/service/host/commands.go @@ -192,6 +192,7 @@ type SetBDStatusInput struct { CommandID string AdminUserID int64 TargetUserID int64 + Role string Status string Reason string RequestID string @@ -202,6 +203,7 @@ type SetBDStatusCommand struct { CommandID string AdminUserID int64 TargetUserID int64 + Role string Status string Reason string RequestID string diff --git a/services/user-service/internal/service/host/service.go b/services/user-service/internal/service/host/service.go index 7100787d..e1051ee2 100644 --- a/services/user-service/internal/service/host/service.go +++ b/services/user-service/internal/service/host/service.go @@ -34,7 +34,7 @@ type Repository interface { DeleteAgency(ctx context.Context, command DeleteAgencyCommand) (hostdomain.Agency, error) SetAgencyJoinEnabled(ctx context.Context, command SetAgencyJoinEnabledCommand) (hostdomain.Agency, error) GetHostProfile(ctx context.Context, userID int64) (hostdomain.HostProfile, error) - GetBDProfile(ctx context.Context, userID int64) (hostdomain.BDProfile, error) + GetBDProfile(ctx context.Context, userID int64, role string) (hostdomain.BDProfile, error) GetCoinSellerProfile(ctx context.Context, userID int64) (hostdomain.CoinSellerProfile, error) ListActiveCoinSellersInMyRegion(ctx context.Context, userID int64) ([]hostdomain.CoinSellerListItem, error) GetUserRoleSummary(ctx context.Context, userID int64) (hostdomain.UserRoleSummary, error) @@ -368,14 +368,15 @@ func (s *Service) CreateBD(ctx context.Context, command CreateBDInput) (hostdoma }) } -// SetBDStatus 调整 BD 或 BD Leader 状态;Phase 2 只允许 active/disabled 两个稳定状态。 +// SetBDStatus 调整普通 BD 或 BD Leader 状态;调用方必须传 role,避免同一用户两种身份时误改。 func (s *Service) SetBDStatus(ctx context.Context, command SetBDStatusInput) (hostdomain.BDProfile, error) { if err := s.requireWriteDependencies(); err != nil { return hostdomain.BDProfile{}, err } status := normalizeStatus(command.Status) - if strings.TrimSpace(command.CommandID) == "" || command.AdminUserID <= 0 || command.TargetUserID <= 0 { - return hostdomain.BDProfile{}, xerr.New(xerr.InvalidArgument, "command_id, admin_user_id and target_user_id are required") + role := normalizeBDRole(command.Role) + if strings.TrimSpace(command.CommandID) == "" || command.AdminUserID <= 0 || command.TargetUserID <= 0 || role == "" { + return hostdomain.BDProfile{}, xerr.New(xerr.InvalidArgument, "command_id, admin_user_id, target_user_id and role are required") } if status != hostdomain.BDStatusActive && status != hostdomain.BDStatusDisabled { return hostdomain.BDProfile{}, xerr.New(xerr.InvalidArgument, "bd status is invalid") @@ -386,6 +387,7 @@ func (s *Service) SetBDStatus(ctx context.Context, command SetBDStatusInput) (ho CommandID: strings.TrimSpace(command.CommandID), AdminUserID: command.AdminUserID, TargetUserID: command.TargetUserID, + Role: role, Status: status, Reason: strings.TrimSpace(command.Reason), RequestID: strings.TrimSpace(command.RequestID), @@ -548,16 +550,28 @@ func (s *Service) GetHostProfile(ctx context.Context, userID int64) (hostdomain. return s.repository.GetHostProfile(ctx, userID) } -// GetBDProfile 读取 BD/BD 负责人身份事实。 -func (s *Service) GetBDProfile(ctx context.Context, userID int64) (hostdomain.BDProfile, error) { +// GetBDProfile 按指定 role 读取普通 BD 或 BD Leader 身份事实。 +func (s *Service) GetBDProfile(ctx context.Context, userID int64, role string) (hostdomain.BDProfile, error) { if s.repository == nil { return hostdomain.BDProfile{}, xerr.New(xerr.Unavailable, "host repository is not configured") } - if userID <= 0 { - return hostdomain.BDProfile{}, xerr.New(xerr.InvalidArgument, "user_id is required") + role = normalizeBDRole(role) + if userID <= 0 || role == "" { + return hostdomain.BDProfile{}, xerr.New(xerr.InvalidArgument, "user_id and role are required") } - return s.repository.GetBDProfile(ctx, userID) + return s.repository.GetBDProfile(ctx, userID, role) +} + +func normalizeBDRole(role string) string { + switch strings.ToLower(strings.TrimSpace(role)) { + case hostdomain.BDRoleBD: + return hostdomain.BDRoleBD + case hostdomain.BDRoleLeader: + return hostdomain.BDRoleLeader + default: + return "" + } } // GetCoinSellerProfile 读取币商身份事实,供 gateway 在发起币商转账前校验。 diff --git a/services/user-service/internal/service/host/service_test.go b/services/user-service/internal/service/host/service_test.go index d0f91a90..9b6d1ffb 100644 --- a/services/user-service/internal/service/host/service_test.go +++ b/services/user-service/internal/service/host/service_test.go @@ -274,7 +274,7 @@ func TestAcceptBDInvitationDoesNotCreateHostProfile(t *testing.T) { if invitation.Status != hostdomain.InvitationStatusAccepted { t.Fatalf("bd invitation should be accepted immediately: %+v", invitation) } - bdProfile, err := svc.GetBDProfile(ctx, 702) + bdProfile, err := svc.GetBDProfile(ctx, 702, hostdomain.BDRoleBD) if err != nil { t.Fatalf("GetBDProfile failed: %v", err) } @@ -286,6 +286,53 @@ func TestAcceptBDInvitationDoesNotCreateHostProfile(t *testing.T) { } } +func TestInviteBDAcceptsSelfInviteForLeader(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedActiveUser(t, repository, 703, 10) + repository.PutBDProfile(hostdomain.BDProfile{ + UserID: 703, + Role: hostdomain.BDRoleLeader, + RegionID: 10, + Status: hostdomain.BDStatusActive, + }) + svc := newHostService(repository, 3100, 4100) + + invitation, err := svc.InviteBD(ctx, hostservice.InviteBDInput{ + CommandID: "invite-bd-self-703", + InviterUserID: 703, + TargetUserID: 703, + }) + if err != nil { + t.Fatalf("bd leader self invite must be accepted: %v", err) + } + if invitation.Status != hostdomain.InvitationStatusAccepted || invitation.InviterUserID != 703 || invitation.TargetUserID != 703 || invitation.ParentLeaderUserID != 703 { + t.Fatalf("self invite invitation mismatch: %+v", invitation) + } + ordinaryBD, err := svc.GetBDProfile(ctx, 703, hostdomain.BDRoleBD) + if err != nil { + t.Fatalf("self invite must create ordinary bd profile: %v", err) + } + if ordinaryBD.Role != hostdomain.BDRoleBD || ordinaryBD.ParentLeaderUserID != 703 { + t.Fatalf("self invite ordinary bd mismatch: %+v", ordinaryBD) + } + leaderProfile, err := svc.GetBDProfile(ctx, 703, hostdomain.BDRoleLeader) + if err != nil { + t.Fatalf("self invite must keep existing leader profile unchanged: %v", err) + } + if leaderProfile.Role != hostdomain.BDRoleLeader || leaderProfile.Status != hostdomain.BDStatusActive { + t.Fatalf("self invite leader profile mismatch: %+v", leaderProfile) + } + retried, err := svc.InviteBD(ctx, hostservice.InviteBDInput{ + CommandID: "invite-bd-self-703", + InviterUserID: 703, + TargetUserID: 703, + }) + if err != nil || retried.InvitationID != invitation.InvitationID { + t.Fatalf("self invite retry must replay existing invitation: retried=%+v err=%v", retried, err) + } +} + func TestAdminCreateBDLeaderAndBDIdempotently(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) @@ -327,6 +374,25 @@ func TestAdminCreateBDLeaderAndBDIdempotently(t *testing.T) { t.Fatalf("leader retry must replay existing result: %+v", retriedLeader) } + sameUserBD, err := svc.CreateBD(ctx, hostservice.CreateBDInput{ + CommandID: "admin-create-bd-801-same-user", + AdminUserID: 1, + TargetUserID: 801, + ParentLeaderUserID: 801, + Reason: "same user can also be bd", + RequestID: "req-bd-same-user-1", + }) + if err != nil { + t.Fatalf("CreateBD for existing leader user failed: %v", err) + } + if sameUserBD.Role != hostdomain.BDRoleBD || sameUserBD.ParentLeaderUserID != 801 { + t.Fatalf("same user bd mismatch: %+v", sameUserBD) + } + leaderAfterBD, err := svc.GetBDProfile(ctx, 801, hostdomain.BDRoleLeader) + if err != nil || leaderAfterBD.Role != hostdomain.BDRoleLeader || leaderAfterBD.Status != hostdomain.BDStatusActive { + t.Fatalf("ordinary bd creation must not rewrite leader row: leader=%+v err=%v", leaderAfterBD, err) + } + bd, err := svc.CreateBD(ctx, hostservice.CreateBDInput{ CommandID: "admin-create-bd-802", AdminUserID: 1, @@ -360,6 +426,7 @@ func TestAdminCreateBDLeaderAndBDIdempotently(t *testing.T) { CommandID: "admin-disable-bd-802", AdminUserID: 1, TargetUserID: 802, + Role: hostdomain.BDRoleBD, Status: hostdomain.BDStatusDisabled, Reason: "stop bd", RequestID: "req-bd-stop-1", @@ -370,6 +437,52 @@ func TestAdminCreateBDLeaderAndBDIdempotently(t *testing.T) { if disabled.Status != hostdomain.BDStatusDisabled { t.Fatalf("disabled bd mismatch: %+v", disabled) } + _, err = svc.InviteBD(ctx, hostservice.InviteBDInput{ + CommandID: "invite-disabled-bd-802-again", + InviterUserID: 801, + TargetUserID: 802, + }) + if got := xerr.CodeOf(err); got != xerr.Conflict { + t.Fatalf("disabled ordinary bd must not be invited again: got %s err=%v", got, err) + } + disabledSameUserBD, err := svc.SetBDStatus(ctx, hostservice.SetBDStatusInput{ + CommandID: "admin-disable-bd-801", + AdminUserID: 1, + TargetUserID: 801, + Role: hostdomain.BDRoleBD, + Status: hostdomain.BDStatusDisabled, + Reason: "stop ordinary bd only", + RequestID: "req-bd-stop-801", + }) + if err != nil { + t.Fatalf("SetBDStatus ordinary bd for same user failed: %v", err) + } + if disabledSameUserBD.Role != hostdomain.BDRoleBD || disabledSameUserBD.Status != hostdomain.BDStatusDisabled { + t.Fatalf("disabled same user bd mismatch: %+v", disabledSameUserBD) + } + stillLeader, err := svc.GetBDProfile(ctx, 801, hostdomain.BDRoleLeader) + if err != nil || stillLeader.Status != hostdomain.BDStatusActive { + t.Fatalf("disabling ordinary bd must not affect leader: leader=%+v err=%v", stillLeader, err) + } + disabledLeader, err := svc.SetBDStatus(ctx, hostservice.SetBDStatusInput{ + CommandID: "admin-disable-leader-801", + AdminUserID: 1, + TargetUserID: 801, + Role: hostdomain.BDRoleLeader, + Status: hostdomain.BDStatusDisabled, + Reason: "stop leader only", + RequestID: "req-leader-stop-801", + }) + if err != nil { + t.Fatalf("SetBDStatus leader failed: %v", err) + } + if disabledLeader.Role != hostdomain.BDRoleLeader || disabledLeader.Status != hostdomain.BDStatusDisabled { + t.Fatalf("disabled leader mismatch: %+v", disabledLeader) + } + ordinaryStillDisabled, err := svc.GetBDProfile(ctx, 801, hostdomain.BDRoleBD) + if err != nil || ordinaryStillDisabled.Status != hostdomain.BDStatusDisabled { + t.Fatalf("disabling leader must not rewrite ordinary bd: bd=%+v err=%v", ordinaryStillDisabled, err) + } } func TestAdminCreateBDLeaderRejectsDisabledRegion(t *testing.T) { @@ -808,8 +921,22 @@ func TestGetUserRoleSummaryAggregatesRolesAndPendingInvitations(t *testing.T) { if err != nil { t.Fatalf("GetUserRoleSummary failed: %v", err) } - if !summary.IsHost || !summary.IsAgency || !summary.IsManager || !summary.IsBD || !summary.IsBDLeader || !summary.IsCoinSeller || summary.AgencyID != 933 || summary.BDID != 931 { - t.Fatalf("role summary mismatch: %+v", summary) + if !summary.IsHost || !summary.IsAgency || !summary.IsManager || summary.IsBD || !summary.IsBDLeader || !summary.IsCoinSeller || summary.AgencyID != 933 || summary.BDID != 931 { + t.Fatalf("leader-only role summary mismatch: %+v", summary) + } + if _, err := svc.InviteBD(ctx, hostservice.InviteBDInput{ + CommandID: "invite-bd-self-931", + InviterUserID: 931, + TargetUserID: 931, + }); err != nil { + t.Fatalf("self InviteBD for summary failed: %v", err) + } + combinedSummary, err := svc.GetUserRoleSummary(ctx, 931) + if err != nil { + t.Fatalf("GetUserRoleSummary combined failed: %v", err) + } + if !combinedSummary.IsBD || !combinedSummary.IsBDLeader || combinedSummary.BDID != 931 { + t.Fatalf("bd plus leader role summary mismatch: %+v", combinedSummary) } directBD, err := svc.GetUserRoleSummary(ctx, 932) if err != nil { diff --git a/services/user-service/internal/storage/mysql/host/admin.go b/services/user-service/internal/storage/mysql/host/admin.go index 3c1e5003..21ceaf9f 100644 --- a/services/user-service/internal/storage/mysql/host/admin.go +++ b/services/user-service/internal/storage/mysql/host/admin.go @@ -24,7 +24,7 @@ func (r *Repository) CreateBDLeader(ctx context.Context, command hostservice.Cre if err := requireCommandResult(existing, hostdomain.CommandTypeCreateBDLeader, hostdomain.ResultTypeBDProfile); err != nil { return hostdomain.BDProfile{}, err } - return queryBDProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID) + return queryBDLeaderProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID) } regionID, err := r.userRegion(ctx, tx, command.TargetUserID, "FOR UPDATE") @@ -34,10 +34,10 @@ func (r *Repository) CreateBDLeader(ctx context.Context, command hostservice.Cre if err := requireActiveRegion(ctx, tx, regionID); err != nil { return hostdomain.BDProfile{}, err } - if _, ok, err := queryBDProfileByUserMaybe(ctx, tx, command.TargetUserID, true); err != nil { + if _, ok, err := queryBDLeaderProfileByUserMaybe(ctx, tx, command.TargetUserID, true); err != nil { return hostdomain.BDProfile{}, err } else if ok { - return hostdomain.BDProfile{}, xerr.New(xerr.Conflict, "target already has bd profile") + return hostdomain.BDProfile{}, xerr.New(xerr.Conflict, "target already has bd leader profile") } profile := hostdomain.BDProfile{ @@ -49,7 +49,7 @@ func (r *Repository) CreateBDLeader(ctx context.Context, command hostservice.Cre CreatedAtMs: command.NowMs, UpdatedAtMs: command.NowMs, } - if err := insertBDProfile(ctx, tx, profile); err != nil { + if err := insertBDLeaderProfile(ctx, tx, profile); err != nil { return hostdomain.BDProfile{}, mapHostDuplicateError(err) } if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeCreateBDLeader, ResultType: hostdomain.ResultTypeBDProfile, ResultID: profile.UserID, CreatedAtMs: command.NowMs}); err != nil { @@ -86,12 +86,12 @@ func (r *Repository) CreateBD(ctx context.Context, command hostservice.CreateBDC return hostdomain.BDProfile{}, err } if command.ParentLeaderUserID > 0 { - // 独立 BD 不需要父级行锁;只有传入 Leader 时才校验 Leader 身份、启用状态和区域归属。 - leader, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.ParentLeaderUserID) + // 独立 BD 不需要父级行锁;只有传入 Leader 时才锁定独立 Leader 表并校验启用状态和区域归属。 + leader, err := queryBDLeaderProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.ParentLeaderUserID) if err != nil { return hostdomain.BDProfile{}, err } - if leader.Status != hostdomain.BDStatusActive || leader.Role != hostdomain.BDRoleLeader { + if leader.Status != hostdomain.BDStatusActive { return hostdomain.BDProfile{}, xerr.New(xerr.PermissionDenied, "parent leader is not active") } if regionID != leader.RegionID { @@ -129,7 +129,7 @@ func (r *Repository) CreateBD(ctx context.Context, command hostservice.CreateBDC return profile, nil } -// SetBDStatus 只改 BD 状态,不迁移角色、区域和上级负责人。 +// SetBDStatus 按调用方明确传入的角色只改对应身份状态,不迁移角色、区域和上级负责人。 func (r *Repository) SetBDStatus(ctx context.Context, command hostservice.SetBDStatusCommand) (hostdomain.BDProfile, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { @@ -143,18 +143,37 @@ func (r *Repository) SetBDStatus(ctx context.Context, command hostservice.SetBDS if err := requireCommandResult(existing, hostdomain.CommandTypeSetBDStatus, hostdomain.ResultTypeBDProfile); err != nil { return hostdomain.BDProfile{}, err } + if command.Role == hostdomain.BDRoleLeader { + return queryBDLeaderProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID) + } return queryBDProfile(ctx, tx, "WHERE user_id = ?", existing.ResultID) } - before, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.TargetUserID) + var before hostdomain.BDProfile + switch command.Role { + case hostdomain.BDRoleBD: + // 普通 BD 和 Leader 已经拆表;状态路由传 bd 时只锁普通 BD 行。 + before, err = queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.TargetUserID) + case hostdomain.BDRoleLeader: + // 状态路由传 bd_leader 时只锁 Leader 行,避免同用户的普通 BD 钱包权限被误停。 + before, err = queryBDLeaderProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.TargetUserID) + default: + err = xerr.New(xerr.InvalidArgument, "bd role is invalid") + } if err != nil { return hostdomain.BDProfile{}, err } after := before after.Status = command.Status after.UpdatedAtMs = command.NowMs - if err := updateBDProfileStatus(ctx, tx, after); err != nil { - return hostdomain.BDProfile{}, err + if command.Role == hostdomain.BDRoleLeader { + if err := updateBDLeaderProfileStatus(ctx, tx, after); err != nil { + return hostdomain.BDProfile{}, err + } + } else { + if err := updateBDProfileStatus(ctx, tx, after); err != nil { + return hostdomain.BDProfile{}, err + } } if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{CommandID: command.CommandID, CommandType: hostdomain.CommandTypeSetBDStatus, ResultType: hostdomain.ResultTypeBDProfile, ResultID: after.UserID, CreatedAtMs: command.NowMs}); err != nil { return hostdomain.BDProfile{}, mapHostDuplicateError(err) @@ -278,14 +297,11 @@ func (r *Repository) CreateAgency(ctx context.Context, command hostservice.Creat return hostdomain.CreateAgencyResult{}, err } if command.ParentBDUserID > 0 { - // 独立 Agency 不需要父级行锁;只有传入 BD 时才校验 BD 启用状态和同区域归属。 - parentBD, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.ParentBDUserID) + // 独立 Agency 不需要父级行锁;传入父级时可挂普通 BD 或 Leader 自身,但必须是有效身份且同区域。 + parentBD, err := queryActiveAgencyInviterProfile(ctx, tx, command.ParentBDUserID) if err != nil { return hostdomain.CreateAgencyResult{}, err } - if parentBD.Status != hostdomain.BDStatusActive { - return hostdomain.CreateAgencyResult{}, xerr.New(xerr.PermissionDenied, "parent bd is not active") - } if regionID != parentBD.RegionID { return hostdomain.CreateAgencyResult{}, xerr.New(xerr.PermissionDenied, "owner region does not match parent bd region") } diff --git a/services/user-service/internal/storage/mysql/host/common.go b/services/user-service/internal/storage/mysql/host/common.go index 69dd9e86..6b162793 100644 --- a/services/user-service/internal/storage/mysql/host/common.go +++ b/services/user-service/internal/storage/mysql/host/common.go @@ -47,6 +47,9 @@ const ( bdProfileColumns = ` user_id, role, region_id, COALESCE(parent_leader_user_id, 0), status, created_by_user_id, created_at_ms, updated_at_ms` + bdLeaderProfileColumns = ` + user_id, 'bd_leader', region_id, 0, status, + created_by_user_id, created_at_ms, updated_at_ms` coinSellerProfileColumns = ` user_id, status, merchant_asset_type, created_by_user_id, created_at_ms, updated_at_ms` diff --git a/services/user-service/internal/storage/mysql/host/invitations.go b/services/user-service/internal/storage/mysql/host/invitations.go index b614d3a4..ae466c76 100644 --- a/services/user-service/internal/storage/mysql/host/invitations.go +++ b/services/user-service/internal/storage/mysql/host/invitations.go @@ -28,15 +28,12 @@ func (r *Repository) InviteAgency(ctx context.Context, command hostservice.Invit return queryRoleInvitation(ctx, tx, "WHERE invitation_id = ?", existing.ResultID) } - // 邀请人的 BD 身份行同时是授权凭证和邀请归属来源; - // 锁住它可以避免 BD 被停用/换区时仍继续发出旧区域邀请。 - inviter, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.InviterUserID) + // InviteAgency 没有显式角色字段;普通 BD 行优先承载 BD Center 邀请,普通 BD 不可用时再使用 Leader 行承载负责人直属邀请。 + // 两种身份都用 FOR UPDATE 串行化停用/换区和邀请创建,避免旧权限在同一事务窗口内继续写入。 + inviter, err := queryActiveAgencyInviterProfile(ctx, tx, command.InviterUserID) if err != nil { return hostdomain.RoleInvitation{}, err } - if inviter.Status != hostdomain.BDStatusActive { - return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "inviter bd is not active") - } // 目标用户还不是 Host/Agency 时没有领域表行,所以锁 users 行来串行化目标身份变更。 targetRegionID, err := r.userRegion(ctx, tx, command.TargetUserID, "FOR UPDATE") if err != nil { @@ -59,7 +56,7 @@ func (r *Repository) InviteAgency(ctx context.Context, command hostservice.Invit } // BD 发出的 Agency 邀请要记录直接上级 BD 和负责人链路; - // 如果邀请人自己就是负责人,则 parent_leader_user_id 指向自己。 + // 如果本次以负责人身份邀请,则直接 BD 和负责人都指向 Leader 自己。 parentLeaderUserID := inviter.ParentLeaderUserID if inviter.Role == hostdomain.BDRoleLeader { parentLeaderUserID = inviter.UserID @@ -146,12 +143,12 @@ func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDC return queryRoleInvitation(ctx, tx, "WHERE invitation_id = ?", existing.ResultID) } - // 只有有效 BD 负责人能发展 BD;锁住负责人身份行让停用/降级和邀请创建串行。 - inviter, err := queryBDProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.InviterUserID) + // 只有有效 BD 负责人能发展 BD;锁住独立 Leader 表行让停用和邀请创建串行。 + inviter, err := queryBDLeaderProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.InviterUserID) if err != nil { return hostdomain.RoleInvitation{}, err } - if inviter.Status != hostdomain.BDStatusActive || inviter.Role != hostdomain.BDRoleLeader { + if inviter.Status != hostdomain.BDStatusActive { return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "inviter is not active bd leader") } // BD 从属于负责人的区域,邀请创建时先锁目标用户当前区域。 @@ -162,11 +159,12 @@ func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDC if targetRegionID != inviter.RegionID { return hostdomain.RoleInvitation{}, xerr.New(xerr.PermissionDenied, "target region does not match leader region") } - // 目标已有有效 BD 身份行时不再创建新邀请,避免多条 BD 身份事实竞争。 - if _, ok, err := queryBDProfileMaybe(ctx, tx, command.TargetUserID, true); err != nil { + // 目标已有普通 BD 身份行时不再创建新邀请,即使已停用也不能再插入第二条普通 BD 事实。 + // BD Leader 自邀也必须新增普通 BD 行,后续才能获得 BD_SALARY_USD 钱包鉴权。 + if _, ok, err := queryBDProfileByUserMaybe(ctx, tx, command.TargetUserID, true); err != nil { return hostdomain.RoleInvitation{}, err } else if ok { - return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already has active bd profile") + return hostdomain.RoleInvitation{}, xerr.New(xerr.Conflict, "target already has bd profile") } invitation := hostdomain.RoleInvitation{ @@ -189,6 +187,15 @@ func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDC if err := insertRoleInvitation(ctx, tx, invitation); err != nil { return hostdomain.RoleInvitation{}, mapHostDuplicateError(err) } + recordCommandResult := func() error { + return insertCommandResult(ctx, tx, hostdomain.CommandResult{ + CommandID: command.CommandID, + CommandType: hostdomain.CommandTypeInviteBD, + ResultType: hostdomain.ResultTypeInvitation, + ResultID: invitation.InvitationID, + CreatedAtMs: command.NowMs, + }) + } result := hostdomain.ProcessRoleInvitationResult{Invitation: invitation} if err := r.acceptBDInvitation(ctx, tx, hostservice.ProcessRoleInvitationCommand{ CommandID: command.CommandID, @@ -204,13 +211,7 @@ func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDC if err := insertHostOutbox(ctx, tx, command.EventID, "BDCreated", "bd_profile", invitation.TargetUserID, command.NowMs); err != nil { return hostdomain.RoleInvitation{}, err } - if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{ - CommandID: command.CommandID, - CommandType: hostdomain.CommandTypeInviteBD, - ResultType: hostdomain.ResultTypeInvitation, - ResultID: invitation.InvitationID, - CreatedAtMs: command.NowMs, - }); err != nil { + if err := recordCommandResult(); err != nil { return hostdomain.RoleInvitation{}, mapHostDuplicateError(err) } if err := tx.Commit(); err != nil { @@ -220,6 +221,16 @@ func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDC return invitation, nil } +func queryActiveAgencyInviterProfile(ctx context.Context, tx *sql.Tx, userID int64) (hostdomain.BDProfile, error) { + if profile, ok, err := queryBDProfileMaybe(ctx, tx, userID, true); err != nil || ok { + return profile, err + } + if profile, ok, err := queryBDLeaderProfileMaybe(ctx, tx, userID, true); err != nil || ok { + return profile, err + } + return hostdomain.BDProfile{}, xerr.New(xerr.PermissionDenied, "inviter bd is not active") +} + // ProcessRoleInvitation 处理角色邀请,接受路径在同一事务内创建 Agency/BD 角色事实。 func (r *Repository) ProcessRoleInvitation(ctx context.Context, command hostservice.ProcessRoleInvitationCommand) (hostdomain.ProcessRoleInvitationResult, error) { tx, err := r.db.BeginTx(ctx, nil) @@ -397,11 +408,11 @@ func (r *Repository) acceptBDInvitation(ctx context.Context, tx *sql.Tx, command if regionID != invitation.RegionID { return xerr.New(xerr.PermissionDenied, "target region no longer matches invitation") } - // BD 身份是独立角色事实,不依赖 host_profile;只需要阻止重复有效 BD。 - if _, ok, err := queryBDProfileMaybe(ctx, tx, invitation.TargetUserID, true); err != nil { + // BD 身份是独立角色事实,不依赖 host_profile;任何已有普通 BD 行都不能重复插入。 + if _, ok, err := queryBDProfileByUserMaybe(ctx, tx, invitation.TargetUserID, true); err != nil { return err } else if ok { - return xerr.New(xerr.Conflict, "target already has active bd profile") + return xerr.New(xerr.Conflict, "target already has bd profile") } bd := hostdomain.BDProfile{ UserID: invitation.TargetUserID, diff --git a/services/user-service/internal/storage/mysql/host/queries.go b/services/user-service/internal/storage/mysql/host/queries.go index 8fb8dda4..eada8c48 100644 --- a/services/user-service/internal/storage/mysql/host/queries.go +++ b/services/user-service/internal/storage/mysql/host/queries.go @@ -100,8 +100,11 @@ func (r *Repository) GetHostProfile(ctx context.Context, userID int64) (hostdoma return queryHostProfile(ctx, r.db, "WHERE user_id = ?", userID) } -// GetBDProfile 读取 BD 身份事实。 -func (r *Repository) GetBDProfile(ctx context.Context, userID int64) (hostdomain.BDProfile, error) { +// GetBDProfile 按 role 读取普通 BD 或 BD Leader 身份事实。 +func (r *Repository) GetBDProfile(ctx context.Context, userID int64, role string) (hostdomain.BDProfile, error) { + if role == hostdomain.BDRoleLeader { + return queryBDLeaderProfile(ctx, r.db, "WHERE user_id = ?", userID) + } return queryBDProfile(ctx, r.db, "WHERE user_id = ?", userID) } @@ -201,8 +204,9 @@ func (r *Repository) GetUserRoleSummary(ctx context.Context, userID int64) (host var hostAgencyID int64 var ownerAgencyID int64 var bdUserID int64 - var bdRole string var bdStatus string + var leaderUserID int64 + var leaderStatus string var coinSellerStatus string var merchantAssetType string @@ -213,8 +217,9 @@ func (r *Repository) GetUserRoleSummary(ctx context.Context, userID int64) (host COALESCE(h.current_agency_id, 0), COALESCE(a.agency_id, 0), COALESCE(b.user_id, 0), - COALESCE(b.role, ''), COALESCE(b.status, ''), + COALESCE(bl.user_id, 0), + COALESCE(bl.status, ''), COALESCE(c.status, ''), COALESCE(c.merchant_asset_type, '') FROM (SELECT ? AS user_id) u @@ -224,6 +229,8 @@ func (r *Repository) GetUserRoleSummary(ctx context.Context, userID int64) (host ON a.app_code = ? AND a.owner_user_id = u.user_id AND a.status = ? LEFT JOIN bd_profiles b ON b.app_code = ? AND b.user_id = u.user_id + LEFT JOIN bd_leader_profiles bl + ON bl.app_code = ? AND bl.user_id = u.user_id LEFT JOIN coin_seller_profiles c ON c.app_code = ? AND c.user_id = u.user_id`, userID, @@ -231,7 +238,8 @@ func (r *Repository) GetUserRoleSummary(ctx context.Context, userID int64) (host app, hostdomain.AgencyStatusActive, app, app, - ).Scan(&hostStatus, &hostSource, &hostAgencyID, &ownerAgencyID, &bdUserID, &bdRole, &bdStatus, &coinSellerStatus, &merchantAssetType) + app, + ).Scan(&hostStatus, &hostSource, &hostAgencyID, &ownerAgencyID, &bdUserID, &bdStatus, &leaderUserID, &leaderStatus, &coinSellerStatus, &merchantAssetType) if err != nil { return hostdomain.UserRoleSummary{}, err } @@ -249,13 +257,16 @@ func (r *Repository) GetUserRoleSummary(ctx context.Context, userID int64) (host summary.BDStatus = bdStatus if bdUserID > 0 && bdStatus == hostdomain.BDStatusActive { summary.BDID = bdUserID - switch bdRole { - case hostdomain.BDRoleLeader: - summary.IsBD = true - summary.IsBDLeader = true - case hostdomain.BDRoleBD: - summary.IsBD = true + summary.IsBD = true + } + if leaderUserID > 0 && leaderStatus == hostdomain.BDStatusActive { + if summary.BDID == 0 { + summary.BDID = leaderUserID } + if summary.BDStatus == "" { + summary.BDStatus = leaderStatus + } + summary.IsBDLeader = true } summary.CoinSellerStatus = coinSellerStatus summary.IsCoinSeller = coinSellerStatus == hostdomain.CoinSellerStatusActive && merchantAssetType == hostdomain.CoinSellerMerchantAssetType @@ -281,13 +292,13 @@ func (r *Repository) HasActiveAgencyOwner(ctx context.Context, userID int64) (in return agency.AgencyID, true, nil } -// ListBDLeaderBDs 读取 BD Leader 直属 BD;调用前先校验 leader 当前仍有效,避免停用后继续读取团队数据。 +// ListBDLeaderBDs 读取 BD Leader 直属普通 BD;调用前先校验独立 Leader 行当前仍有效,避免停用后继续读取团队数据。 func (r *Repository) ListBDLeaderBDs(ctx context.Context, command hostservice.ListBDLeaderBDsCommand) ([]hostdomain.BDProfile, error) { - leader, err := queryBDProfile(ctx, r.db, "WHERE user_id = ?", command.LeaderUserID) + leader, err := queryBDLeaderProfile(ctx, r.db, "WHERE user_id = ?", command.LeaderUserID) if err != nil { return nil, err } - if leader.Status != hostdomain.BDStatusActive || leader.Role != hostdomain.BDRoleLeader { + if leader.Status != hostdomain.BDStatusActive { return nil, xerr.New(xerr.PermissionDenied, "leader is not active") } status := command.Status @@ -298,13 +309,13 @@ func (r *Repository) ListBDLeaderBDs(ctx context.Context, command hostservice.Li return queryBDProfiles(ctx, r.db, clause, args...) } -// ListBDLeaderAgencies 读取 BD Leader 团队 Agency;包含直属 leader 自己创建的 Agency 和下属 BD 创建的 Agency。 +// ListBDLeaderAgencies 读取 BD Leader 团队 Agency;包含直属 leader 自己创建的 Agency 和下属普通 BD 创建的 Agency。 func (r *Repository) ListBDLeaderAgencies(ctx context.Context, command hostservice.ListBDLeaderAgenciesCommand) ([]hostdomain.Agency, error) { - leader, err := queryBDProfile(ctx, r.db, "WHERE user_id = ?", command.LeaderUserID) + leader, err := queryBDLeaderProfile(ctx, r.db, "WHERE user_id = ?", command.LeaderUserID) if err != nil { return nil, err } - if leader.Status != hostdomain.BDStatusActive || leader.Role != hostdomain.BDRoleLeader { + if leader.Status != hostdomain.BDStatusActive { return nil, xerr.New(xerr.PermissionDenied, "leader is not active") } status := command.Status @@ -325,13 +336,13 @@ func (r *Repository) ListBDLeaderAgencies(ctx context.Context, command hostservi return queryAgencies(ctx, r.db, clause, args...) } -// ListBDAgencies 读取一个有效 BD 直接拓展的 Agency,不包含同负责人下其他 BD 的数据。 +// ListBDAgencies 只读取一个有效普通 BD 直接拓展的 Agency,不包含同负责人下其他 BD 的数据。 func (r *Repository) ListBDAgencies(ctx context.Context, command hostservice.ListBDAgenciesCommand) ([]hostdomain.Agency, error) { bd, err := queryBDProfile(ctx, r.db, "WHERE user_id = ?", command.BDUserID) if err != nil { return nil, err } - if bd.Status != hostdomain.BDStatusActive { + if bd.Status != hostdomain.BDStatusActive || bd.Role != hostdomain.BDRoleBD { return nil, xerr.New(xerr.PermissionDenied, "bd is not active") } status := command.Status @@ -558,6 +569,15 @@ func queryBDProfiles(ctx context.Context, q sqlQueryer, clause string, args ...a return profiles, rows.Err() } +func queryBDLeaderProfile(ctx context.Context, q sqlQueryer, clause string, args ...any) (hostdomain.BDProfile, error) { + clause, args = appScopedClause(ctx, clause, args...) + profile, err := scanBDProfile(q.QueryRowContext(ctx, fmt.Sprintf(`SELECT %s FROM bd_leader_profiles %s`, bdLeaderProfileColumns, clause), args...)) + if err == sql.ErrNoRows { + return hostdomain.BDProfile{}, xerr.New(xerr.NotFound, "bd leader profile not found") + } + return profile, err +} + func queryBDProfileMaybe(ctx context.Context, q sqlQueryer, userID int64, lock bool) (hostdomain.BDProfile, bool, error) { clause := "WHERE user_id = ? AND status = ?" if lock { @@ -574,6 +594,22 @@ func queryBDProfileMaybe(ctx context.Context, q sqlQueryer, userID int64, lock b return profile, true, nil } +func queryBDLeaderProfileMaybe(ctx context.Context, q sqlQueryer, userID int64, lock bool) (hostdomain.BDProfile, bool, error) { + clause := "WHERE user_id = ? AND status = ?" + if lock { + clause += " FOR UPDATE" + } + clause, args := appScopedClause(ctx, clause, userID, hostdomain.BDStatusActive) + profile, err := scanBDProfile(q.QueryRowContext(ctx, fmt.Sprintf(`SELECT %s FROM bd_leader_profiles %s`, bdLeaderProfileColumns, clause), args...)) + if err == sql.ErrNoRows { + return hostdomain.BDProfile{}, false, nil + } + if err != nil { + return hostdomain.BDProfile{}, false, err + } + return profile, true, nil +} + func queryBDProfileByUserMaybe(ctx context.Context, q sqlQueryer, userID int64, lock bool) (hostdomain.BDProfile, bool, error) { clause := "WHERE user_id = ?" if lock { @@ -591,6 +627,23 @@ func queryBDProfileByUserMaybe(ctx context.Context, q sqlQueryer, userID int64, return profile, true, nil } +func queryBDLeaderProfileByUserMaybe(ctx context.Context, q sqlQueryer, userID int64, lock bool) (hostdomain.BDProfile, bool, error) { + clause := "WHERE user_id = ?" + if lock { + // 后台创建/停用 Leader 使用主键锁,覆盖 active 和 disabled 两种状态。 + clause += " FOR UPDATE" + } + clause, args := appScopedClause(ctx, clause, userID) + profile, err := scanBDProfile(q.QueryRowContext(ctx, fmt.Sprintf(`SELECT %s FROM bd_leader_profiles %s`, bdLeaderProfileColumns, clause), args...)) + if err == sql.ErrNoRows { + return hostdomain.BDProfile{}, false, nil + } + if err != nil { + return hostdomain.BDProfile{}, false, err + } + return profile, true, nil +} + func scanBDProfile(scanner rowScanner) (hostdomain.BDProfile, error) { var profile hostdomain.BDProfile err := scanner.Scan( diff --git a/services/user-service/internal/storage/mysql/host/writes.go b/services/user-service/internal/storage/mysql/host/writes.go index d022fefa..b055da46 100644 --- a/services/user-service/internal/storage/mysql/host/writes.go +++ b/services/user-service/internal/storage/mysql/host/writes.go @@ -148,7 +148,7 @@ func updateAgencyAdminState(ctx context.Context, tx *sql.Tx, agency hostdomain.A } func insertBDProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error { - // BD 身份行和 host_profile 相互独立;BD 用户不会自动成为 Host。 + // 普通 BD 身份行和 host_profile 相互独立;BD 用户不会自动成为 Host,也不会隐式拥有 BD Leader 权限。 _, err := tx.ExecContext(ctx, ` INSERT INTO bd_profiles ( app_code, user_id, role, region_id, parent_leader_user_id, status, @@ -158,11 +158,32 @@ func insertBDProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfi return err } +func insertBDLeaderProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error { + // BD Leader 使用独立表承载负责人身份,避免和普通 BD 工资、状态、团队归属混成同一条事实。 + _, err := tx.ExecContext(ctx, ` + INSERT INTO bd_leader_profiles ( + app_code, user_id, region_id, status, + created_by_user_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?) + `, appcode.FromContext(ctx), profile.UserID, profile.RegionID, profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) + return err +} + func updateBDProfileStatus(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error { // 状态变更只更新 status/updated_at_ms,不改角色、区域和上级 Leader 这些身份历史字段。 _, err := tx.ExecContext(ctx, ` UPDATE bd_profiles SET status = ?, updated_at_ms = ? + WHERE app_code = ? AND user_id = ? AND role = 'bd' + `, profile.Status, profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID) + return err +} + +func updateBDLeaderProfileStatus(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfile) error { + // Leader 状态只影响负责人权限和 ADMIN_SALARY_USD 钱包入口,不影响同用户可能存在的普通 BD 身份。 + _, err := tx.ExecContext(ctx, ` + UPDATE bd_leader_profiles + SET status = ?, updated_at_ms = ? WHERE app_code = ? AND user_id = ? `, profile.Status, profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID) return err diff --git a/services/user-service/internal/testutil/mysqltest/mysqltest.go b/services/user-service/internal/testutil/mysqltest/mysqltest.go index 17289413..ee33605d 100644 --- a/services/user-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/user-service/internal/testutil/mysqltest/mysqltest.go @@ -634,7 +634,7 @@ func (r *Repository) PutHostProfile(profile hostdomain.HostProfile) { } } -// PutBDProfile seeds a BD or BD Leader profile fact for host-domain tests. +// PutBDProfile seeds ordinary BD rows into bd_profiles and BD Leader rows into bd_leader_profiles. func (r *Repository) PutBDProfile(profile hostdomain.BDProfile) { r.t.Helper() @@ -653,6 +653,22 @@ func (r *Repository) PutBDProfile(profile hostdomain.BDProfile) { } profile.UpdatedAtMs = nowMs + if profile.Role == hostdomain.BDRoleLeader { + _, err := r.schema.DB.ExecContext(context.Background(), ` + INSERT INTO bd_leader_profiles ( + user_id, region_id, status, created_by_user_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + region_id = VALUES(region_id), + status = VALUES(status), + updated_at_ms = VALUES(updated_at_ms) + `, profile.UserID, profile.RegionID, profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) + if err != nil { + r.t.Fatalf("seed bd leader profile %d failed: %v", profile.UserID, err) + } + return + } + _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO bd_profiles ( user_id, role, region_id, parent_leader_user_id, status, diff --git a/services/user-service/internal/transport/grpc/bd_identity_flow_test.go b/services/user-service/internal/transport/grpc/bd_identity_flow_test.go new file mode 100644 index 00000000..d90063d2 --- /dev/null +++ b/services/user-service/internal/transport/grpc/bd_identity_flow_test.go @@ -0,0 +1,174 @@ +package grpc_test + +import ( + "context" + "testing" + "time" + + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/xerr" + hostdomain "hyapp/services/user-service/internal/domain/host" + userdomain "hyapp/services/user-service/internal/domain/user" + authservice "hyapp/services/user-service/internal/service/auth" + hostservice "hyapp/services/user-service/internal/service/host" + "hyapp/services/user-service/internal/testutil/mysqltest" + grpcserver "hyapp/services/user-service/internal/transport/grpc" +) + +type grpcHostSequenceIDGenerator struct { + next int64 +} + +func (g *grpcHostSequenceIDGenerator) NewInt64() int64 { + id := g.next + g.next++ + return id +} + +func TestBDLeaderSelfInviteFlowThroughGRPCUsesSplitIdentityTables(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + repository.PutRegion(userdomain.Region{RegionID: 686, RegionCode: "BD-RPC", Name: "BD RPC Region"}) + seedActiveRPCUser(t, repository, 10001, 686) + + hostSvc := hostservice.New(repository.HostRepository(), + hostservice.WithIDGenerator(&grpcHostSequenceIDGenerator{next: 700000}), + hostservice.WithClock(func() time.Time { return time.UnixMilli(1700000000000) }), + ) + server := grpcserver.NewServer(authservice.New(authservice.Config{}), newUserService(repository), hostSvc) + meta := &userv1.RequestMeta{AppCode: "lalu", RequestId: "req-bd-grpc-flow"} + + leaderResp, err := server.CreateBDLeader(ctx, &userv1.CreateBDLeaderRequest{ + Meta: meta, + CommandId: "rpc-create-leader-10001", + AdminUserId: 90001, + TargetUserId: 10001, + Reason: "grpc split identity flow", + }) + if err != nil { + t.Fatalf("CreateBDLeader RPC failed: %v", err) + } + if leaderResp.GetBdProfile().GetRole() != hostdomain.BDRoleLeader || leaderResp.GetBdProfile().GetStatus() != hostdomain.BDStatusActive { + t.Fatalf("leader response mismatch: %+v", leaderResp.GetBdProfile()) + } + + if _, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleBD}); xerr.ReasonFromGRPC(err) != xerr.NotFound { + t.Fatalf("leader-only user must not have ordinary bd before self invite: err=%v", err) + } + + inviteResp, err := server.InviteBD(ctx, &userv1.InviteBDRequest{ + Meta: meta, + CommandId: "rpc-self-invite-bd-10001", + InviterUserId: 10001, + TargetUserId: 10001, + }) + if err != nil { + t.Fatalf("InviteBD self RPC failed: %v", err) + } + invitation := inviteResp.GetInvitation() + if invitation.GetStatus() != hostdomain.InvitationStatusAccepted || invitation.GetTargetUserId() != 10001 || invitation.GetParentLeaderUserId() != 10001 { + t.Fatalf("self invite response mismatch: %+v", invitation) + } + retryInviteResp, err := server.InviteBD(ctx, &userv1.InviteBDRequest{ + Meta: meta, + CommandId: "rpc-self-invite-bd-10001", + InviterUserId: 10001, + TargetUserId: 10001, + }) + if err != nil { + t.Fatalf("InviteBD retry RPC failed: %v", err) + } + if retryInviteResp.GetInvitation().GetInvitationId() != invitation.GetInvitationId() { + t.Fatalf("self invite retry must replay same invitation: first=%+v retry=%+v", invitation, retryInviteResp.GetInvitation()) + } + + bdResp, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleBD}) + if err != nil { + t.Fatalf("GetBDProfile role=bd RPC failed after self invite: %v", err) + } + if bdResp.GetBdProfile().GetRole() != hostdomain.BDRoleBD || bdResp.GetBdProfile().GetParentLeaderUserId() != 10001 { + t.Fatalf("ordinary bd profile mismatch: %+v", bdResp.GetBdProfile()) + } + leaderCheckResp, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleLeader}) + if err != nil { + t.Fatalf("GetBDProfile role=bd_leader RPC failed: %v", err) + } + if leaderCheckResp.GetBdProfile().GetRole() != hostdomain.BDRoleLeader || leaderCheckResp.GetBdProfile().GetStatus() != hostdomain.BDStatusActive { + t.Fatalf("leader profile changed unexpectedly: %+v", leaderCheckResp.GetBdProfile()) + } + + summaryResp, err := server.GetUserRoleSummary(ctx, &userv1.GetUserRoleSummaryRequest{Meta: meta, UserId: 10001}) + if err != nil { + t.Fatalf("GetUserRoleSummary RPC failed: %v", err) + } + if !summaryResp.GetSummary().GetIsBd() || !summaryResp.GetSummary().GetIsBdLeader() { + t.Fatalf("summary must expose both independent identities: %+v", summaryResp.GetSummary()) + } + + disabledBDResp, err := server.SetBDStatus(ctx, &userv1.SetBDStatusRequest{ + Meta: meta, + CommandId: "rpc-disable-bd-10001", + AdminUserId: 90001, + TargetUserId: 10001, + Role: hostdomain.BDRoleBD, + Status: hostdomain.BDStatusDisabled, + Reason: "disable ordinary bd only", + }) + if err != nil { + t.Fatalf("SetBDStatus role=bd RPC failed: %v", err) + } + if disabledBDResp.GetBdProfile().GetRole() != hostdomain.BDRoleBD || disabledBDResp.GetBdProfile().GetStatus() != hostdomain.BDStatusDisabled { + t.Fatalf("disabled ordinary bd response mismatch: %+v", disabledBDResp.GetBdProfile()) + } + leaderStillActiveResp, err := server.GetBDProfile(ctx, &userv1.GetBDProfileRequest{Meta: meta, UserId: 10001, Role: hostdomain.BDRoleLeader}) + if err != nil { + t.Fatalf("GetBDProfile role=bd_leader after bd disable failed: %v", err) + } + if leaderStillActiveResp.GetBdProfile().GetStatus() != hostdomain.BDStatusActive { + t.Fatalf("disabling ordinary bd must not affect leader: %+v", leaderStillActiveResp.GetBdProfile()) + } + + disabledLeaderResp, err := server.SetBDStatus(ctx, &userv1.SetBDStatusRequest{ + Meta: meta, + CommandId: "rpc-disable-leader-10001", + AdminUserId: 90001, + TargetUserId: 10001, + Role: hostdomain.BDRoleLeader, + Status: hostdomain.BDStatusDisabled, + Reason: "disable leader only", + }) + if err != nil { + t.Fatalf("SetBDStatus role=bd_leader RPC failed: %v", err) + } + if disabledLeaderResp.GetBdProfile().GetRole() != hostdomain.BDRoleLeader || disabledLeaderResp.GetBdProfile().GetStatus() != hostdomain.BDStatusDisabled { + t.Fatalf("disabled leader response mismatch: %+v", disabledLeaderResp.GetBdProfile()) + } + + var bdRows int + var leaderRows int + if err := repository.RawDB().QueryRowContext(ctx, `SELECT COUNT(1) FROM bd_profiles WHERE app_code = 'lalu' AND user_id = 10001 AND role = 'bd'`).Scan(&bdRows); err != nil { + t.Fatalf("count ordinary bd rows failed: %v", err) + } + if err := repository.RawDB().QueryRowContext(ctx, `SELECT COUNT(1) FROM bd_leader_profiles WHERE app_code = 'lalu' AND user_id = 10001`).Scan(&leaderRows); err != nil { + t.Fatalf("count leader rows failed: %v", err) + } + if bdRows != 1 || leaderRows != 1 { + t.Fatalf("split identity rows mismatch: bd_rows=%d leader_rows=%d", bdRows, leaderRows) + } +} + +func seedActiveRPCUser(t *testing.T, repository *mysqltest.Repository, userID int64, regionID int64) { + t.Helper() + repository.PutUser(userdomain.User{ + UserID: userID, + DefaultDisplayUserID: "910001", + CurrentDisplayUserID: "910001", + CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault, + Country: "CN", + RegionID: regionID, + ProfileCompleted: true, + ProfileCompletedAtMs: 1, + OnboardingStatus: userdomain.OnboardingStatusCompleted, + Status: userdomain.StatusActive, + }) +} diff --git a/services/user-service/internal/transport/grpc/host.go b/services/user-service/internal/transport/grpc/host.go index 9c49870b..c6877162 100644 --- a/services/user-service/internal/transport/grpc/host.go +++ b/services/user-service/internal/transport/grpc/host.go @@ -124,7 +124,7 @@ func (s *Server) InviteBD(ctx context.Context, req *userv1.InviteBDRequest) (*us return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured")) } ctx = contextWithApp(ctx, req.GetMeta()) - // BD 负责人授权在持久化层内基于 bd_profiles 当前事实判断。 + // BD 负责人授权在持久化层内基于 bd_leader_profiles 当前事实判断。 invitation, err := s.hostSvc.InviteBD(ctx, hostservice.InviteBDInput{ CommandID: req.GetCommandId(), InviterUserID: req.GetInviterUserId(), @@ -244,13 +244,13 @@ func (s *Server) GetHostProfile(ctx context.Context, req *userv1.GetHostProfileR return &userv1.GetHostProfileResponse{HostProfile: toProtoHostProfile(profile)}, nil } -// GetBDProfile 返回当前用户 BD/BD 负责人身份事实。 +// GetBDProfile 按调用方声明的角色返回普通 BD 或 BD Leader 身份事实。 func (s *Server) GetBDProfile(ctx context.Context, req *userv1.GetBDProfileRequest) (*userv1.GetBDProfileResponse, error) { if s.hostSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured")) } ctx = contextWithApp(ctx, req.GetMeta()) - profile, err := s.hostSvc.GetBDProfile(ctx, req.GetUserId()) + profile, err := s.hostSvc.GetBDProfile(ctx, req.GetUserId(), req.GetRole()) if err != nil { return nil, xerr.ToGRPCError(err) } diff --git a/services/user-service/internal/transport/grpc/host_admin.go b/services/user-service/internal/transport/grpc/host_admin.go index 7f950bbf..0856a00e 100644 --- a/services/user-service/internal/transport/grpc/host_admin.go +++ b/services/user-service/internal/transport/grpc/host_admin.go @@ -57,6 +57,7 @@ func (s *Server) SetBDStatus(ctx context.Context, req *userv1.SetBDStatusRequest CommandID: req.GetCommandId(), AdminUserID: req.GetAdminUserId(), TargetUserID: req.GetTargetUserId(), + Role: req.GetRole(), Status: req.GetStatus(), Reason: req.GetReason(), RequestID: req.GetMeta().GetRequestId(), From 59a609a45ded15aa2df3df1474263ffeebae1f12 Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 10 Jun 2026 11:25:28 +0800 Subject: [PATCH 11/28] fix(game): scope yomi launch session lookup --- .../internal/service/game/service.go | 1 + .../internal/service/game/service_test.go | 99 +++++++++++++++++++ .../internal/service/game/yomi_adapter.go | 21 +++- .../internal/storage/mysql/repository.go | 25 +++++ 4 files changed, 143 insertions(+), 3 deletions(-) diff --git a/services/game-service/internal/service/game/service.go b/services/game-service/internal/service/game/service.go index 10852b04..e0947207 100644 --- a/services/game-service/internal/service/game/service.go +++ b/services/game-service/internal/service/game/service.go @@ -35,6 +35,7 @@ type Repository interface { GetPlatform(ctx context.Context, appCode string, platformCode string) (gamedomain.Platform, error) CreateLaunchSession(ctx context.Context, session gamedomain.LaunchSession) error GetLaunchSessionByToken(ctx context.Context, appCode string, token string) (gamedomain.LaunchSession, error) + GetLaunchSessionByTokenScope(ctx context.Context, appCode string, token string, platformCode string, providerGameID string) (gamedomain.LaunchSession, error) InsertCallbackLog(ctx context.Context, log gamedomain.CallbackLog) error CreateGameOrder(ctx context.Context, order gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) MarkOrderSucceeded(ctx context.Context, appCode string, orderID string, walletTransactionID string, balanceAfter int64, nowMs int64) error diff --git a/services/game-service/internal/service/game/service_test.go b/services/game-service/internal/service/game/service_test.go index f3e45d87..da07a565 100644 --- a/services/game-service/internal/service/game/service_test.go +++ b/services/game-service/internal/service/game/service_test.go @@ -876,6 +876,79 @@ func TestHandleYomiGetTokenAndUserInfo(t *testing.T) { } } +func TestHandleYomiGetTokenScopesSharedAccessTokenByPlatformAndGame(t *testing.T) { + secret := "12345678901234567890123456789012" + token := "shared_app_access_token" + repo := &fakeRepository{ + platform: gamedomain.Platform{ + PlatformCode: "yomi", + AdapterType: gamedomain.AdapterYomiV4, + CallbackSecretCiphertext: secret, + AdapterConfigJSON: `{"uid_mode":"display_user_id"}`, + }, + sessions: []gamedomain.LaunchSession{ + { + AppCode: "lalu", + SessionID: "game_sess_yomi_463", + UserID: 42, + DisplayUserID: "420001", + RoomID: "room_463", + PlatformCode: "yomi", + GameID: "yomi_463", + ProviderGameID: "463", + LaunchTokenHash: stableHash(token), + Status: gamedomain.SessionActive, + ExpiresAtMS: 1700086400000, + CreatedAtMS: 1700000000000, + }, + { + AppCode: "lalu", + SessionID: "game_sess_yomi_464", + UserID: 42, + DisplayUserID: "420001", + RoomID: "room_464", + PlatformCode: "yomi", + GameID: "yomi_464", + ProviderGameID: "464", + LaunchTokenHash: stableHash(token), + Status: gamedomain.SessionActive, + ExpiresAtMS: 1700086400000, + CreatedAtMS: 1700000001000, + }, + { + AppCode: "lalu", + SessionID: "game_sess_reyou_1", + UserID: 42, + DisplayUserID: "420001", + RoomID: "room_reyou", + PlatformCode: "reyou", + GameID: "reyou_1", + ProviderGameID: "1", + LaunchTokenHash: stableHash(token), + Status: gamedomain.SessionActive, + ExpiresAtMS: 1700086400000, + CreatedAtMS: 1700000002000, + }, + }, + } + svc := New(Config{}, repo, &fakeWallet{balanceAfter: 1880}, &fakeUser{}) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + + raw, _, err := svc.HandleCallback(context.Background(), &gamev1.CallbackRequest{ + Meta: &gamev1.RequestMeta{RequestId: "req-yomi-scoped-gettoken", AppCode: "lalu"}, + PlatformCode: "yomi", + Operation: "gettoken", + RawBody: encryptYomiTestBody(t, secret, `{"gameUid":463,"lang":"en","platAuthCode":"shared_app_access_token","platUserId":"420001","platPayload":"","platRoomId":"room_463"}`), + RemoteAddr: "3.1.174.194:443", + }) + if err != nil { + t.Fatalf("HandleCallback failed: %v", err) + } + if !strings.Contains(string(raw), `"code":200`) || !strings.Contains(string(raw), `"platToken":"shared_app_access_token"`) { + t.Fatalf("yomi scoped gettoken response mismatch: %s", raw) + } +} + func TestHandleYomiChangeBalanceDecryptsAndAppliesWallet(t *testing.T) { secret := "12345678901234567890123456789012" token := "gt_test_token" @@ -1581,6 +1654,7 @@ type fakeRepository struct { launchable gamedomain.LaunchableGame platform gamedomain.Platform session gamedomain.LaunchSession + sessions []gamedomain.LaunchSession order gamedomain.GameOrder orders []gamedomain.GameOrder repair gamedomain.RepairOrder @@ -1613,14 +1687,39 @@ func (f *fakeRepository) GetPlatform(context.Context, string, string) (gamedomai } func (f *fakeRepository) CreateLaunchSession(_ context.Context, session gamedomain.LaunchSession) error { f.session = session + f.sessions = append(f.sessions, session) return nil } func (f *fakeRepository) GetLaunchSessionByToken(_ context.Context, _ string, token string) (gamedomain.LaunchSession, error) { + for index := len(f.sessions) - 1; index >= 0; index-- { + if f.sessions[index].LaunchTokenHash == stableHash(token) { + return f.sessions[index], nil + } + } if f.session.LaunchTokenHash == stableHash(token) { return f.session, nil } return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "session token invalid") } +func (f *fakeRepository) GetLaunchSessionByTokenScope(_ context.Context, _ string, token string, platformCode string, providerGameID string) (gamedomain.LaunchSession, error) { + tokenHash := stableHash(token) + for index := len(f.sessions) - 1; index >= 0; index-- { + session := f.sessions[index] + if session.LaunchTokenHash != tokenHash || session.PlatformCode != strings.TrimSpace(platformCode) { + continue + } + if strings.TrimSpace(providerGameID) != "" && session.ProviderGameID != strings.TrimSpace(providerGameID) { + continue + } + return session, nil + } + if f.session.LaunchTokenHash == tokenHash && f.session.PlatformCode == strings.TrimSpace(platformCode) { + if strings.TrimSpace(providerGameID) == "" || f.session.ProviderGameID == strings.TrimSpace(providerGameID) { + return f.session, nil + } + } + return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "session token invalid") +} func (f *fakeRepository) InsertCallbackLog(context.Context, gamedomain.CallbackLog) error { return nil } func (f *fakeRepository) CreateGameOrder(_ context.Context, order gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) { for _, existing := range f.orders { diff --git a/services/game-service/internal/service/game/yomi_adapter.go b/services/game-service/internal/service/game/yomi_adapter.go index 37edfbb8..d5fa2326 100644 --- a/services/game-service/internal/service/game/yomi_adapter.go +++ b/services/game-service/internal/service/game/yomi_adapter.go @@ -153,7 +153,7 @@ func (s *Service) handleYomiGetToken(ctx context.Context, app string, platform g return yomiAdapterError(yomiCodeSystemError, "invalid payload", true, "") } config := yomiConfigFromPlatform(platform) - session, err := s.validYomiSession(ctx, app, body.PlatAuthCode) + session, err := s.validScopedYomiSession(ctx, app, platform.PlatformCode, body.PlatAuthCode, body.GameUID.String()) // 同时校验 token、用户和游戏,避免 A 游戏拿到的 token 被挪到 B 游戏或别的用户。 if err != nil || !yomiSessionMatches(session, config, body.PlatUserID, body.GameUID.String()) { return yomiAdapterError(yomiCodeTokenInvalid, "token invalid", true, "") @@ -188,7 +188,7 @@ func (s *Service) handleYomiUserInfo(ctx context.Context, app string, platform g return yomiAdapterError(yomiCodeSystemError, "invalid payload", true, "") } config := yomiConfigFromPlatform(platform) - session, err := s.validYomiSession(ctx, app, body.PlatToken) + session, err := s.validScopedYomiSession(ctx, app, platform.PlatformCode, body.PlatToken, "") // userinfo 没有 gameUid 时只校验用户;change_balance 会额外校验游戏 ID。 if err != nil || !yomiSessionMatches(session, config, body.PlatUserID, "") { return yomiAdapterError(yomiCodeTokenInvalid, "token invalid", true, "") @@ -229,7 +229,7 @@ func (s *Service) handleYomiChangeBalance(ctx context.Context, app string, platf } providerOrderID := strings.TrimSpace(body.RequestID.String()) config := yomiConfigFromPlatform(platform) - session, err := s.validYomiSession(ctx, app, body.PlatToken) + session, err := s.validScopedYomiSession(ctx, app, platform.PlatformCode, body.PlatToken, body.GameUID.String()) // 改账必须绑定 token、uid、gameUid 三个维度,防止跨用户、跨游戏复用合法 token。 if err != nil || !yomiSessionMatches(session, config, body.PlatUserID, body.GameUID.String()) { return yomiAdapterError(yomiCodeTokenInvalid, "token invalid", true, providerOrderID) @@ -432,6 +432,21 @@ func (s *Service) validYomiSession(ctx context.Context, app string, token string return session, nil } +func (s *Service) validScopedYomiSession(ctx context.Context, app string, platformCode string, token string, providerGameID string) (gamedomain.LaunchSession, error) { + // 同一个 App token 可能同时启动多个厂商游戏,所以 Yomi 回调必须带平台和可选 gameUid 查询,不能只按 token 命中任意旧 session。 + if strings.TrimSpace(token) == "" { + return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "token invalid") + } + session, err := s.repository.GetLaunchSessionByTokenScope(ctx, app, token, platformCode, providerGameID) + if err != nil { + return gamedomain.LaunchSession{}, err + } + if session.Status != gamedomain.SessionActive || session.ExpiresAtMS <= s.now().UnixMilli() { + return gamedomain.LaunchSession{}, xerr.New(xerr.SessionExpired, "token expired") + } + return session, nil +} + func yomiSessionMatches(session gamedomain.LaunchSession, config yomiAdapterConfig, platUserID string, gameUID string) bool { // gameUID 允许为空是为了兼容 userinfo;只要厂商传了就必须和启动会话里的 provider_game_id 一致。 if strings.TrimSpace(gameUID) != "" && strings.TrimSpace(gameUID) != strings.TrimSpace(session.ProviderGameID) { diff --git a/services/game-service/internal/storage/mysql/repository.go b/services/game-service/internal/storage/mysql/repository.go index fbd7f73e..961cadc2 100644 --- a/services/game-service/internal/storage/mysql/repository.go +++ b/services/game-service/internal/storage/mysql/repository.go @@ -544,6 +544,31 @@ func (r *Repository) GetLaunchSessionByToken(ctx context.Context, appCode string return item, nil } +func (r *Repository) GetLaunchSessionByTokenScope(ctx context.Context, appCode string, token string, platformCode string, providerGameID string) (gamedomain.LaunchSession, error) { + // 同一个 App access token 会被多个厂商和多个游戏复用,回调校验必须把 token、平台和可选游戏 ID 一起收窄。 + // 这里按 created_at_ms 倒序取最新一条,避免同用户重复打开同一游戏时拿到旧房间或旧 session。 + row := r.db.QueryRowContext(ctx, + `SELECT app_code, session_id, user_id, display_user_id, room_id, region_id, scene, platform_code, game_id, + provider_game_id, launch_token_hash, status, expires_at_ms, created_at_ms, updated_at_ms + FROM game_launch_sessions + WHERE app_code = ? + AND launch_token_hash = ? + AND platform_code = ? + AND (? = '' OR provider_game_id = ?) + ORDER BY created_at_ms DESC + LIMIT 1`, + appcode.Normalize(appCode), stableHash(strings.TrimSpace(token)), strings.TrimSpace(platformCode), strings.TrimSpace(providerGameID), strings.TrimSpace(providerGameID), + ) + var item gamedomain.LaunchSession + if err := row.Scan(&item.AppCode, &item.SessionID, &item.UserID, &item.DisplayUserID, &item.RoomID, &item.RegionID, &item.Scene, &item.PlatformCode, &item.GameID, &item.ProviderGameID, &item.LaunchTokenHash, &item.Status, &item.ExpiresAtMS, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "game session token invalid") + } + return gamedomain.LaunchSession{}, err + } + return item, nil +} + func (r *Repository) InsertCallbackLog(ctx context.Context, log gamedomain.CallbackLog) error { _, err := r.db.ExecContext(ctx, `INSERT INTO game_callback_logs ( From 49381e12c005eaaafbb09b03cea85cdb41d6c6e0 Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 10 Jun 2026 11:28:25 +0800 Subject: [PATCH 12/28] fix(game): complete scoped session repository interface --- services/game-service/internal/transport/grpc/server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/game-service/internal/transport/grpc/server.go b/services/game-service/internal/transport/grpc/server.go index dc384b4a..42235d34 100644 --- a/services/game-service/internal/transport/grpc/server.go +++ b/services/game-service/internal/transport/grpc/server.go @@ -248,6 +248,9 @@ func (unavailableRepository) CreateLaunchSession(context.Context, gamedomain.Lau func (unavailableRepository) GetLaunchSessionByToken(context.Context, string, string) (gamedomain.LaunchSession, error) { return gamedomain.LaunchSession{}, xerr.New(xerr.Unavailable, "game repository is not configured") } +func (unavailableRepository) GetLaunchSessionByTokenScope(context.Context, string, string, string, string) (gamedomain.LaunchSession, error) { + return gamedomain.LaunchSession{}, xerr.New(xerr.Unavailable, "game repository is not configured") +} func (unavailableRepository) InsertCallbackLog(context.Context, gamedomain.CallbackLog) error { return xerr.New(xerr.Unavailable, "game repository is not configured") } From f5842f8702bc3198facaa5c1e54f99d73432efff Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 10 Jun 2026 11:58:49 +0800 Subject: [PATCH 13/28] =?UTF-8?q?=E5=BE=BD=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mysql/initdb/001_wallet_service.sql | 2 +- .../internal/service/wallet/service_test.go | 173 ++++++++++++++++-- .../internal/storage/mysql/repository.go | 59 ++++++ .../storage/mysql/resource_repository.go | 91 +++++++-- 4 files changed, 297 insertions(+), 28 deletions(-) diff --git a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql index 95e27fc0..505895d1 100644 --- a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql +++ b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql @@ -921,7 +921,7 @@ CREATE TABLE IF NOT EXISTS user_resource_equipment ( entitlement_id VARCHAR(96) NOT NULL COMMENT '权益 ID', resource_id BIGINT NOT NULL COMMENT '资源 ID', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', - PRIMARY KEY (app_code, user_id, resource_type), + PRIMARY KEY (app_code, user_id, resource_type, entitlement_id), KEY idx_user_resource_equipment_user (app_code, user_id, updated_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户资源装备表'; diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 4fb56510..e7daa3e3 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -2719,6 +2719,16 @@ func TestGrantResourceGroupExpandsWalletAssetAndEntitlement(t *testing.T) { if got := repository.CountRows("user_resource_entitlements", "user_id = ?", int64(42001)); got != 1 { t.Fatalf("group grant should write one non-coin entitlement, got %d", got) } + equippedResources, err := svc.BatchGetUserEquippedResources(ctx, resourcedomain.BatchGetUserEquippedResourcesQuery{ + UserIDs: []int64{42001}, + ResourceTypes: []string{resourcedomain.TypeBadge}, + }) + if err != nil { + t.Fatalf("batch get group-granted badge equipment failed: %v", err) + } + if len(equippedResources) != 1 || len(equippedResources[0].Resources) != 1 || equippedResources[0].Resources[0].ResourceID != badgeResource.ResourceID { + t.Fatalf("group-granted badge should be auto equipped: %+v", equippedResources) + } if got := repository.CountRows("wallet_entries", "user_id = ? AND asset_type = ?", int64(42001), ledger.AssetCoin); got != 1 { t.Fatalf("group grant should write one coin wallet entry, got %d", got) } @@ -2732,9 +2742,9 @@ func TestPurchaseResourceShopItemDebitsCoinAndGrantsEntitlement(t *testing.T) { ctx := context.Background() resource, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{ - ResourceCode: "shop_frame_gold", - ResourceType: resourcedomain.TypeAvatarFrame, - Name: "Gold Frame", + ResourceCode: "shop_badge_gold", + ResourceType: resourcedomain.TypeBadge, + Name: "Gold Badge", Status: resourcedomain.StatusActive, Grantable: true, GrantStrategy: resourcedomain.GrantStrategyExtendExpiry, @@ -2760,7 +2770,7 @@ func TestPurchaseResourceShopItemDebitsCoinAndGrantsEntitlement(t *testing.T) { } receipt, err := svc.PurchaseResourceShopItem(ctx, resourcedomain.ResourceShopPurchaseCommand{ - CommandID: "cmd-shop-frame", + CommandID: "cmd-shop-badge", UserID: 53001, ShopItemID: shopItems[0].ShopItemID, }) @@ -2773,9 +2783,12 @@ func TestPurchaseResourceShopItemDebitsCoinAndGrantsEntitlement(t *testing.T) { if receipt.Resource.EntitlementID == "" || receipt.Resource.ResourceID != resource.ResourceID || receipt.Resource.ExpiresAtMS <= time.Now().UnixMilli() { t.Fatalf("resource shop entitlement mismatch: %+v", receipt.Resource) } + if !receipt.Resource.Equipped { + t.Fatalf("resource shop badge purchase should auto equip receipt resource: %+v", receipt.Resource) + } again, err := svc.PurchaseResourceShopItem(ctx, resourcedomain.ResourceShopPurchaseCommand{ - CommandID: "cmd-shop-frame", + CommandID: "cmd-shop-badge", UserID: 53001, ShopItemID: shopItems[0].ShopItemID, }) @@ -2788,7 +2801,7 @@ func TestPurchaseResourceShopItemDebitsCoinAndGrantsEntitlement(t *testing.T) { if got := repository.CountRows("resource_shop_purchase_orders", "user_id = ?", int64(53001)); got != 1 { t.Fatalf("resource shop order should be persisted once, got %d", got) } - if got := repository.CountRows("wallet_transactions", "command_id = ? AND biz_type = ?", "cmd-shop-frame", "resource_shop_purchase"); got != 1 { + if got := repository.CountRows("wallet_transactions", "command_id = ? AND biz_type = ?", "cmd-shop-badge", "resource_shop_purchase"); got != 1 { t.Fatalf("resource shop purchase should write one wallet transaction, got %d", got) } if got := repository.CountRows("wallet_entries", "transaction_id = ? AND available_delta = ?", receipt.TransactionID, int64(-300)); got != 1 { @@ -2800,6 +2813,9 @@ func TestPurchaseResourceShopItemDebitsCoinAndGrantsEntitlement(t *testing.T) { if got := repository.CountRows("user_resource_entitlements", "user_id = ? AND resource_id = ?", int64(53001), resource.ResourceID); got != 1 { t.Fatalf("resource shop purchase should grant one entitlement, got %d", got) } + if got := repository.CountRows("user_resource_equipment", "user_id = ? AND resource_type = ? AND resource_id = ?", int64(53001), resourcedomain.TypeBadge, resource.ResourceID); got != 1 { + t.Fatalf("resource shop badge purchase should write one equipment row, got %d", got) + } } func TestProjectPendingBadgeGrantEventsRelaysWalletOutbox(t *testing.T) { @@ -3141,6 +3157,17 @@ func TestEquipUserResourceRequiresActiveEquipableEntitlement(t *testing.T) { if err != nil { t.Fatalf("grant badge resource failed: %v", err) } + resources, err := svc.ListUserResources(ctx, resourcedomain.ListUserResourcesQuery{ + UserID: 43001, + ResourceType: resourcedomain.TypeBadge, + ActiveOnly: true, + }) + if err != nil { + t.Fatalf("list auto-equipped badge resources failed: %v", err) + } + if len(resources) != 1 || !resources[0].Equipped || resources[0].EntitlementID != grant.Items[0].EntitlementID { + t.Fatalf("badge grant should auto equip entitlement: resources=%+v grant=%+v", resources, grant) + } equipped, err := svc.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ UserID: 43001, ResourceID: badgeResource.ResourceID, @@ -3154,7 +3181,32 @@ func TestEquipUserResourceRequiresActiveEquipableEntitlement(t *testing.T) { if !equipped.Equipped { t.Fatalf("equip response should mark entitlement as equipped: %+v", equipped) } - resources, err := svc.ListUserResources(ctx, resourcedomain.ListUserResourcesQuery{ + secondBadgeResource, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{ + ResourceCode: "badge_equippable_short", + ResourceType: resourcedomain.TypeBadge, + Name: "Second Equippable Badge", + Status: resourcedomain.StatusActive, + Grantable: true, + GrantStrategy: resourcedomain.GrantStrategySetActiveFlag, + UsageScopes: []string{"profile"}, + OperatorUserID: 90001, + }) + if err != nil { + t.Fatalf("create second badge resource failed: %v", err) + } + secondGrant, err := svc.GrantResource(ctx, resourcedomain.GrantResourceCommand{ + CommandID: "cmd-grant-equip-badge-second", + TargetUserID: 43001, + ResourceID: secondBadgeResource.ResourceID, + Quantity: 1, + Reason: "test second badge equip", + OperatorUserID: 90001, + GrantSource: resourcedomain.GrantSourceAdmin, + }) + if err != nil { + t.Fatalf("grant second badge resource failed: %v", err) + } + resources, err = svc.ListUserResources(ctx, resourcedomain.ListUserResourcesQuery{ UserID: 43001, ResourceType: resourcedomain.TypeBadge, ActiveOnly: true, @@ -3162,8 +3214,17 @@ func TestEquipUserResourceRequiresActiveEquipableEntitlement(t *testing.T) { if err != nil { t.Fatalf("list user resources failed: %v", err) } - if len(resources) != 1 || !resources[0].Equipped || resources[0].EntitlementID != equipped.EntitlementID { - t.Fatalf("resource list should expose equipped entitlement: resources=%+v equipped=%+v", resources, equipped) + if len(resources) != 2 { + t.Fatalf("badge grants should keep multiple equipped resources: resources=%+v", resources) + } + equippedBadgeIDs := map[string]bool{} + for _, item := range resources { + if item.Equipped { + equippedBadgeIDs[item.EntitlementID] = true + } + } + if !equippedBadgeIDs[equipped.EntitlementID] || !equippedBadgeIDs[secondGrant.Items[0].EntitlementID] { + t.Fatalf("resource list should expose both equipped badges: resources=%+v first=%+v second=%+v", resources, equipped, secondGrant) } repeated, err := svc.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ UserID: 43001, @@ -3176,8 +3237,8 @@ func TestEquipUserResourceRequiresActiveEquipableEntitlement(t *testing.T) { if repeated.EntitlementID != equipped.EntitlementID { t.Fatalf("repeat equip should keep entitlement: repeated=%+v equipped=%+v", repeated, equipped) } - if got := repository.CountRows("user_resource_equipment", "user_id = ? AND resource_type = ?", int64(43001), resourcedomain.TypeBadge); got != 1 { - t.Fatalf("equipment should keep one active badge, got %d", got) + if got := repository.CountRows("user_resource_equipment", "user_id = ? AND resource_type = ?", int64(43001), resourcedomain.TypeBadge); got != 2 { + t.Fatalf("repeat equip should not duplicate and should keep both active badges, got %d", got) } equippedResources, err := svc.BatchGetUserEquippedResources(ctx, resourcedomain.BatchGetUserEquippedResourcesQuery{ UserIDs: []int64{43001}, @@ -3186,7 +3247,7 @@ func TestEquipUserResourceRequiresActiveEquipableEntitlement(t *testing.T) { if err != nil { t.Fatalf("batch get equipped resources failed: %v", err) } - if len(equippedResources) != 1 || len(equippedResources[0].Resources) != 1 || equippedResources[0].Resources[0].EntitlementID != equipped.EntitlementID { + if len(equippedResources) != 1 || len(equippedResources[0].Resources) != 2 { t.Fatalf("batch equipped resources mismatch: %+v", equippedResources) } unequipped, err := svc.UnequipUserResource(ctx, resourcedomain.UnequipUserResourceCommand{ @@ -3196,11 +3257,11 @@ func TestEquipUserResourceRequiresActiveEquipableEntitlement(t *testing.T) { if err != nil { t.Fatalf("unequip badge resource failed: %v", err) } - if !unequipped.Unequipped || unequipped.ResourceType != resourcedomain.TypeBadge { + if unequipped.Unequipped || unequipped.ResourceType != resourcedomain.TypeBadge { t.Fatalf("unequip result mismatch: %+v", unequipped) } - if got := repository.CountRows("user_resource_equipment", "user_id = ? AND resource_type = ?", int64(43001), resourcedomain.TypeBadge); got != 0 { - t.Fatalf("equipment should be removed after unequip, got %d", got) + if got := repository.CountRows("user_resource_equipment", "user_id = ? AND resource_type = ?", int64(43001), resourcedomain.TypeBadge); got != 2 { + t.Fatalf("badge equipment should remain after no-op unequip, got %d", got) } emptyUnequip, err := svc.UnequipUserResource(ctx, resourcedomain.UnequipUserResourceCommand{ UserID: 43001, @@ -3293,6 +3354,88 @@ func TestEquipUserResourceRequiresActiveEquipableEntitlement(t *testing.T) { } } +// TestEquipUserResourceKeepsSingleEquipmentForNonBadgeTypes 验证多徽章 schema 不会改变其它装扮类型的单选语义。 +func TestEquipUserResourceKeepsSingleEquipmentForNonBadgeTypes(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + ctx := context.Background() + + firstFrame, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{ + ResourceCode: "frame_single_first", + ResourceType: resourcedomain.TypeAvatarFrame, + Name: "First Frame", + Status: resourcedomain.StatusActive, + Grantable: true, + GrantStrategy: resourcedomain.GrantStrategySetActiveFlag, + UsageScopes: []string{"profile"}, + OperatorUserID: 90001, + }) + if err != nil { + t.Fatalf("create first frame failed: %v", err) + } + secondFrame, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{ + ResourceCode: "frame_single_second", + ResourceType: resourcedomain.TypeAvatarFrame, + Name: "Second Frame", + Status: resourcedomain.StatusActive, + Grantable: true, + GrantStrategy: resourcedomain.GrantStrategySetActiveFlag, + UsageScopes: []string{"profile"}, + OperatorUserID: 90001, + }) + if err != nil { + t.Fatalf("create second frame failed: %v", err) + } + if _, err := svc.GrantResource(ctx, resourcedomain.GrantResourceCommand{ + CommandID: "cmd-grant-first-frame", + TargetUserID: 43002, + ResourceID: firstFrame.ResourceID, + Quantity: 1, + Reason: "test first frame", + OperatorUserID: 90001, + GrantSource: resourcedomain.GrantSourceAdmin, + }); err != nil { + t.Fatalf("grant first frame failed: %v", err) + } + if _, err := svc.GrantResource(ctx, resourcedomain.GrantResourceCommand{ + CommandID: "cmd-grant-second-frame", + TargetUserID: 43002, + ResourceID: secondFrame.ResourceID, + Quantity: 1, + Reason: "test second frame", + OperatorUserID: 90001, + GrantSource: resourcedomain.GrantSourceAdmin, + }); err != nil { + t.Fatalf("grant second frame failed: %v", err) + } + if _, err := svc.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ + UserID: 43002, + ResourceID: firstFrame.ResourceID, + }); err != nil { + t.Fatalf("equip first frame failed: %v", err) + } + if _, err := svc.EquipUserResource(ctx, resourcedomain.EquipUserResourceCommand{ + UserID: 43002, + ResourceID: secondFrame.ResourceID, + }); err != nil { + t.Fatalf("equip second frame failed: %v", err) + } + + if got := repository.CountRows("user_resource_equipment", "user_id = ? AND resource_type = ?", int64(43002), resourcedomain.TypeAvatarFrame); got != 1 { + t.Fatalf("avatar_frame equipment should keep one active row, got %d", got) + } + equippedResources, err := svc.BatchGetUserEquippedResources(ctx, resourcedomain.BatchGetUserEquippedResourcesQuery{ + UserIDs: []int64{43002}, + ResourceTypes: []string{resourcedomain.TypeAvatarFrame}, + }) + if err != nil { + t.Fatalf("batch get equipped frames failed: %v", err) + } + if len(equippedResources) != 1 || len(equippedResources[0].Resources) != 1 || equippedResources[0].Resources[0].ResourceID != secondFrame.ResourceID { + t.Fatalf("avatar_frame should expose only the last equipped resource: %+v", equippedResources) + } +} + // TestPurchaseVipDebitsCoinAndGrantsReward 验证 VIP 购买在同一账务事务中扣金币、更新会员和发权益。 func TestPurchaseVipDebitsCoinAndGrantsReward(t *testing.T) { repository := mysqltest.NewRepository(t) diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index 3f3cefba..6edf95ca 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -104,6 +104,10 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { _ = db.Close() return nil, err } + if err := ensureUserResourceEquipmentSchema(ctx, db); err != nil { + _ = db.Close() + return nil, err + } return &Repository{db: db}, nil } @@ -158,6 +162,61 @@ func ensureCoinSellerSalaryExchangeRateSchema(ctx context.Context, db *sql.DB) e return err } +func ensureUserResourceEquipmentSchema(ctx context.Context, db *sql.DB) error { + // 徽章现在是“拥有即佩戴”的多选资源,装备表必须允许同一用户同一类型下存在多条 entitlement。 + // 非徽章仍由写入逻辑先删同类型旧装备再插入新装备,避免把 schema 的多行能力泄漏成其它装扮多穿。 + if _, err := db.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS user_resource_equipment ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + user_id BIGINT NOT NULL COMMENT '用户 ID', + resource_type VARCHAR(32) NOT NULL COMMENT '资源类型', + entitlement_id VARCHAR(96) NOT NULL COMMENT '权益 ID', + resource_id BIGINT NOT NULL COMMENT '资源 ID', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, user_id, resource_type, entitlement_id), + KEY idx_user_resource_equipment_user (app_code, user_id, updated_at_ms) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户资源装备表'`); err != nil { + return err + } + + primaryColumns, err := tablePrimaryKeyColumns(ctx, db, "user_resource_equipment") + if err != nil { + return err + } + if strings.Join(primaryColumns, ",") == "app_code,user_id,resource_type" { + _, err = db.ExecContext(ctx, ` + ALTER TABLE user_resource_equipment + DROP PRIMARY KEY, + ADD PRIMARY KEY (app_code, user_id, resource_type, entitlement_id)`) + return err + } + return nil +} + +func tablePrimaryKeyColumns(ctx context.Context, db *sql.DB, tableName string) ([]string, error) { + rows, err := db.QueryContext(ctx, ` + SELECT COLUMN_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = ? + AND CONSTRAINT_NAME = 'PRIMARY' + ORDER BY ORDINAL_POSITION ASC`, tableName) + if err != nil { + return nil, err + } + defer rows.Close() + + columns := make([]string, 0, 4) + for rows.Next() { + var column string + if err := rows.Scan(&column); err != nil { + return nil, err + } + columns = append(columns, column) + } + return columns, rows.Err() +} + // Close 释放 MySQL 连接池。 func (r *Repository) Close() error { if r == nil || r.db == nil { diff --git a/services/wallet-service/internal/storage/mysql/resource_repository.go b/services/wallet-service/internal/storage/mysql/resource_repository.go index 038f5b56..ee22de3f 100644 --- a/services/wallet-service/internal/storage/mysql/resource_repository.go +++ b/services/wallet-service/internal/storage/mysql/resource_repository.go @@ -958,18 +958,10 @@ func (r *Repository) EquipUserResource(ctx context.Context, command resourcedoma if !isEquipableResourceType(entitlement.Resource.ResourceType) { return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.InvalidArgument, "resource_type cannot be equipped") } - if _, err := tx.ExecContext(ctx, ` - INSERT INTO user_resource_equipment ( - app_code, user_id, resource_type, entitlement_id, resource_id, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - entitlement_id = VALUES(entitlement_id), - resource_id = VALUES(resource_id), - updated_at_ms = VALUES(updated_at_ms)`, - command.AppCode, command.UserID, entitlement.Resource.ResourceType, entitlement.EntitlementID, entitlement.ResourceID, nowMs, - ); err != nil { + if err := r.equipUserResourceEntitlementTx(ctx, tx, command.UserID, entitlement, nowMs); err != nil { return resourcedomain.UserResourceEntitlement{}, err } + entitlement.Equipped = true eventCommandID := fmt.Sprintf("equip:%s:%d", entitlement.EntitlementID, time.Now().UnixNano()) if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ resourceOutboxEvent("UserResourceChanged", eventCommandID, command.UserID, command.ResourceID, map[string]any{ @@ -988,7 +980,6 @@ func (r *Repository) EquipUserResource(ctx context.Context, command resourcedoma if err := tx.Commit(); err != nil { return resourcedomain.UserResourceEntitlement{}, err } - entitlement.Equipped = true return entitlement, nil } @@ -1005,6 +996,15 @@ func (r *Repository) UnequipUserResource(ctx context.Context, command resourcedo if !resourcedomain.ValidResourceType(command.ResourceType) || !isEquipableResourceType(command.ResourceType) { return resourcedomain.UnequipUserResourceResult{}, xerr.New(xerr.InvalidArgument, "resource_type cannot be equipped") } + if isAutoEquippedBadgeResourceType(command.ResourceType) { + // 徽章是“拥有即佩戴”的展示资源,不再支持按类型整类卸下。 + // 旧客户端如果仍调用 badge unequip,按幂等无变更返回,避免把用户全部徽章一次删掉。 + return resourcedomain.UnequipUserResourceResult{ + ResourceType: command.ResourceType, + Unequipped: false, + UpdatedAtMS: time.Now().UnixMilli(), + }, nil + } // 取消佩戴要和 outbox 事件在同一事务内提交: // UI 投影可以监听 UserResourceChanged,但不能在 equipment 删除失败时收到“已卸下”的假事件。 @@ -1083,7 +1083,7 @@ func (r *Repository) BatchGetUserEquippedResources(ctx context.Context, query re args = append(args, stringAnyArgs(resourceTypes)...) } // 从 equipment 反查 entitlement/resource,而不是从 entitlement 扫描 equipped 标记: - // 这样每个 user_id + resource_type 只会返回当前佩戴的那一个有效权益。 + // 这样非徽章类型仍由写入侧保证每类一个,徽章则可以按同一个 resource_type 返回多个已佩戴权益。 rows, err := r.db.QueryContext(ctx, ` SELECT e.app_code, e.entitlement_id, e.user_id, e.resource_id, e.status, e.quantity, e.remaining_quantity, e.effective_at_ms, e.expires_at_ms, @@ -1647,6 +1647,9 @@ func (r *Repository) applyGrantItem(ctx context.Context, tx *sql.Tx, grantID str } item.ResultType = resourcedomain.ResultEntitlement item.EntitlementID = entitlementID + if err := r.autoEquipGrantedBadgeEntitlementTx(ctx, tx, targetUserID, resource, entitlementID, nowMs); err != nil { + return resourcedomain.ResourceGrantItem{}, err + } } inserted, err := r.insertResourceGrantItem(ctx, tx, item) if err != nil { @@ -1793,6 +1796,62 @@ func (r *Repository) applyEntitlement(ctx context.Context, tx *sql.Tx, userID in return entitlementID, nil } +func (r *Repository) autoEquipGrantedBadgeEntitlementTx(ctx context.Context, tx *sql.Tx, userID int64, resource resourcedomain.Resource, entitlementID string, nowMs int64) error { + if !isAutoEquippedBadgeResourceType(resource.ResourceType) || strings.TrimSpace(entitlementID) == "" { + return nil + } + entitlement, err := r.getUserResourceEntitlementTx(ctx, tx, entitlementID) + if err != nil { + return err + } + if err := r.equipUserResourceEntitlementTx(ctx, tx, userID, entitlement, nowMs); err != nil { + return err + } + entitlement.Equipped = true + // 徽章发放来自商店购买、VIP/活动资源组和后台资源发放,统一在 wallet 授权事务内自动佩戴。 + // 单独发 UserResourceChanged 事件,让只监听外观变化的投影不用重新理解 ResourceGranted 的来源差异。 + eventCommandID := fmt.Sprintf("auto_equip:%s:%d", entitlement.EntitlementID, time.Now().UnixNano()) + return r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + resourceOutboxEvent("UserResourceChanged", eventCommandID, userID, resource.ResourceID, map[string]any{ + "action": "auto_equip", + "app_code": appcode.FromContext(ctx), + "user_id": userID, + "resource_id": resource.ResourceID, + "resource_type": resource.ResourceType, + "entitlement": entitlement, + "updated_at_ms": nowMs, + "event_command": eventCommandID, + }, nowMs), + }) +} + +func (r *Repository) equipUserResourceEntitlementTx(ctx context.Context, tx *sql.Tx, userID int64, entitlement resourcedomain.UserResourceEntitlement, nowMs int64) error { + resourceType := resourcedomain.NormalizeResourceType(entitlement.Resource.ResourceType) + if !isEquipableResourceType(resourceType) { + return xerr.New(xerr.InvalidArgument, "resource_type cannot be equipped") + } + if !allowsMultipleEquippedResources(resourceType) { + // schema 已经允许同类型多条 equipment;非徽章类型仍要在写入侧清掉旧指针,保持头像框/资料卡/座驾等单选语义。 + if _, err := tx.ExecContext(ctx, ` + DELETE FROM user_resource_equipment + WHERE app_code = ? AND user_id = ? AND resource_type = ? AND entitlement_id <> ?`, + appcode.FromContext(ctx), userID, resourceType, entitlement.EntitlementID, + ); err != nil { + return err + } + } + _, err := tx.ExecContext(ctx, ` + INSERT INTO user_resource_equipment ( + app_code, user_id, resource_type, entitlement_id, resource_id, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + resource_id = VALUES(resource_id), + updated_at_ms = VALUES(updated_at_ms)`, + appcode.FromContext(ctx), userID, resourceType, entitlement.EntitlementID, entitlement.ResourceID, nowMs, + ) + return err +} + func (r *Repository) alignEntitlementExpiresAt(ctx context.Context, tx *sql.Tx, entitlementID string, expiresAtMS int64, nowMs int64) error { if strings.TrimSpace(entitlementID) == "" { return nil @@ -3031,6 +3090,14 @@ func normalizeEquipableResourceTypes(values []string) ([]string, error) { return out, nil } +func allowsMultipleEquippedResources(resourceType string) bool { + return resourcedomain.NormalizeResourceType(resourceType) == resourcedomain.TypeBadge +} + +func isAutoEquippedBadgeResourceType(resourceType string) bool { + return resourcedomain.NormalizeResourceType(resourceType) == resourcedomain.TypeBadge +} + func (r *Repository) pruneExpiredUserResourceEquipment(ctx context.Context, userIDs []int64, resourceTypes []string, nowMs int64) error { userIDs = compactPositiveInt64s(userIDs) if r == nil || r.db == nil || len(userIDs) == 0 { From 7b2b3a3e2fe3d20242b02b0e5975930187f2c100 Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 10 Jun 2026 16:41:13 +0800 Subject: [PATCH 14/28] =?UTF-8?q?=E6=88=96=E5=B0=B1=E7=88=B1=E4=BD=A0?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/admin/AGENTS.md | 2 +- .../admin/internal/modules/hostorg/handler.go | 7 ++++- .../internal/modules/hostorg/service_test.go | 13 +++++++++ .../internal/room/service/room_rocket.go | 27 +++++++++++++++++++ .../internal/room/service/room_rocket_test.go | 3 +++ .../room-service/internal/room/state/state.go | 4 +-- 6 files changed, 52 insertions(+), 4 deletions(-) diff --git a/server/admin/AGENTS.md b/server/admin/AGENTS.md index d2e89492..e0c89ecd 100644 --- a/server/admin/AGENTS.md +++ b/server/admin/AGENTS.md @@ -85,7 +85,7 @@ go run ./cmd/server -config configs/config.yaml -bootstrap 5. 新增任务类型时,需要补齐创建接口、runner handler、重试/失败结果和产物策略。 6. 区域创建和区域启用/禁用是后台管理能力,实现在 `hyapp-admin-server`,不要为此新增 user-service RPC 或把后台开关逻辑下沉到 app 后端服务。 7. 国家创建和国家启用/禁用是后台管理后端能力,实现在 `hyapp-admin-server`,不要为这些后台动作新增 user-service RPC 或 user-service 业务逻辑。 -8. 后台创建 BD Leader 必须传后台选择的 `regionId`;admin-server 负责权限和审计,user-service 在关系创建事务里更新目标用户 `users.region_id`,不要在 admin-server 里用 GetUser 做“用户原区域必须一致”的拒绝。 +8. 后台创建 BD Leader 按目标用户当前 `users.region_id` 创建;admin-server 只负责权限和审计,不传 `regionId`,user-service 在关系创建事务里读取目标用户区域并写入 `bd_leader_profiles.region_id`,不要在创建 BD Leader 时顺手修改用户区域。 9. `hyapp.local/api` 是唯一允许直接依赖的 app 后端契约 module;不要 import `hyapp/services/*`、`hyapp/internal/*` 或 `hyapp/pkg/*`。 ## 验证 diff --git a/server/admin/internal/modules/hostorg/handler.go b/server/admin/internal/modules/hostorg/handler.go index 38f3fc8f..e23146c1 100644 --- a/server/admin/internal/modules/hostorg/handler.go +++ b/server/admin/internal/modules/hostorg/handler.go @@ -143,7 +143,12 @@ func (h *Handler) SetBDStatus(c *gin.Context) { } func bdRoleFromStatusRoute(c *gin.Context) (string, string) { - if strings.HasPrefix(c.FullPath(), "/admin/bd-leaders/") { + return bdRoleFromStatusPath(c.FullPath()) +} + +func bdRoleFromStatusPath(fullPath string) (string, string) { + // FullPath 带有外层 /api/v1 分组;只要命中 BD Leader 状态路由片段,就必须走独立的 bd_leader_profiles,避免误查普通 bd_profiles。 + if strings.Contains(fullPath, "/admin/bd-leaders/") { return "bd_leader", "bd_leader_profiles" } return "bd", "bd_profiles" diff --git a/server/admin/internal/modules/hostorg/service_test.go b/server/admin/internal/modules/hostorg/service_test.go index 7bd84b4a..a5935319 100644 --- a/server/admin/internal/modules/hostorg/service_test.go +++ b/server/admin/internal/modules/hostorg/service_test.go @@ -12,6 +12,19 @@ func TestNormalizeListQueryAllowsCoinSellerComputedSorts(t *testing.T) { } } +// TestBDRoleFromStatusPathUsesLeaderTableWithAPIPrefix 锁定线上真实 FullPath 带 /api/v1 前缀时,BD Leader 状态仍然写独立负责人表。 +func TestBDRoleFromStatusPathUsesLeaderTableWithAPIPrefix(t *testing.T) { + role, target := bdRoleFromStatusPath("/api/v1/admin/bd-leaders/:user_id/status") + if role != "bd_leader" || target != "bd_leader_profiles" { + t.Fatalf("leader status route must use bd_leader_profiles, got role=%q target=%q", role, target) + } + + role, target = bdRoleFromStatusPath("/api/v1/admin/bds/:user_id/status") + if role != "bd" || target != "bd_profiles" { + t.Fatalf("bd status route must use bd_profiles, got role=%q target=%q", role, target) + } +} + // TestSortCoinSellerListItemsComputedFields 验证 wallet 聚合字段排序只改变展示顺序,不改动币商身份数据。 func TestSortCoinSellerListItemsComputedFields(t *testing.T) { items := []*CoinSellerListItem{ diff --git a/services/room-service/internal/room/service/room_rocket.go b/services/room-service/internal/room/service/room_rocket.go index f120986f..32000158 100644 --- a/services/room-service/internal/room/service/room_rocket.go +++ b/services/room-service/internal/room/service/room_rocket.go @@ -496,6 +496,7 @@ func rocketStateForNow(input *state.RocketState, cfg RoomRocketConfig, now time. rocket.CurrentFuel = rocket.FuelThreshold } rocket.PendingLaunches = validRocketPendingLaunches(rocket.PendingLaunches, rocket.ResetAtMS) + syncRocketLaunchSnapshotFromPending(rocket) if rocket.Status == state.RocketStatusCompleted { rocket.CurrentLevel = roomRocketLevelCount rocket.FuelThreshold = roomRocketLevelByNumber(cfg, roomRocketLevelCount).FuelThreshold @@ -510,6 +511,31 @@ func rocketStateForNow(input *state.RocketState, cfg RoomRocketConfig, now time. return rocket } +func syncRocketLaunchSnapshotFromPending(rocket *state.RocketState) { + if rocket == nil { + return + } + if len(rocket.PendingLaunches) == 0 { + // 当前充能档没有待发射火箭时,不能继续暴露上一枚火箭的发射时间,否则客户端会把过期 launch_at_ms 渲染成 00:00:00。 + rocket.IgnitedAtMS = 0 + rocket.LaunchAtMS = 0 + rocket.Top1UserID = 0 + rocket.IgniterUserID = 0 + return + } + next := rocket.PendingLaunches[0] + for _, launch := range rocket.PendingLaunches[1:] { + if launch.LaunchAtMS > 0 && (next.LaunchAtMS <= 0 || launch.LaunchAtMS < next.LaunchAtMS) { + next = launch + } + } + // RoomRocketState 上的倒计时字段兼容旧客户端;真实多枚队列仍以 PendingLaunches 为准。 + rocket.IgnitedAtMS = next.IgnitedAtMS + rocket.LaunchAtMS = next.LaunchAtMS + rocket.Top1UserID = next.Top1UserID + rocket.IgniterUserID = next.IgniterUserID +} + func validRocketPendingLaunches(input []state.RocketPendingLaunch, resetAtMS int64) []state.RocketPendingLaunch { out := make([]state.RocketPendingLaunch, 0, len(input)) seen := make(map[string]bool, len(input)) @@ -821,6 +847,7 @@ func (s *Service) launchRoomRocket(ctx context.Context, cmd command.LaunchRoomRo pendingAfter := removeRocketPendingLaunch(current.Rocket.PendingLaunches, pendingIndex) current.Rocket.PendingLaunches = pendingAfter + syncRocketLaunchSnapshotFromPending(current.Rocket) current.Rocket.LaunchedAtMS = nowMS current.Rocket.LastRewards = rewards diff --git a/services/room-service/internal/room/service/room_rocket_test.go b/services/room-service/internal/room/service/room_rocket_test.go index 64aae985..92aace1f 100644 --- a/services/room-service/internal/room/service/room_rocket_test.go +++ b/services/room-service/internal/room/service/room_rocket_test.go @@ -425,6 +425,9 @@ func TestRoomRocketLaunchGrantsLockedTop1AndIgniterAfterLeave(t *testing.T) { if stateView.GetCurrentLevel() != 2 || stateView.GetStatus() != state.RocketStatusCharging || len(stateView.GetPendingLaunches()) != 0 { t.Fatalf("launch rocket must keep current next-level progress and clear pending launch: %+v", stateView) } + if stateView.GetLaunchAtMs() != 0 || stateView.GetIgnitedAtMs() != 0 || stateView.GetTop1UserId() != 0 || stateView.GetIgniterUserId() != 0 { + t.Fatalf("launched rocket must not leak expired launch countdown into next charging level: %+v", stateView) + } if len(stateView.GetLastRewards()) != 3 { t.Fatalf("launch rocket must retain in-room, top1 and igniter grants for client display: %+v", stateView.GetLastRewards()) } diff --git a/services/room-service/internal/room/state/state.go b/services/room-service/internal/room/state/state.go index 0c9eec7b..5bde4274 100644 --- a/services/room-service/internal/room/state/state.go +++ b/services/room-service/internal/room/state/state.go @@ -181,13 +181,13 @@ type RocketState struct { FuelThreshold int64 // Status 区分 charging/completed;已点火火箭在 PendingLaunches 中独立排队,不阻塞当前进度。 Status string - // IgnitedAtMS/LaunchAtMS 保留最近一次点火快照,客户端可用 PendingLaunches 获得完整队列。 + // IgnitedAtMS/LaunchAtMS 投影下一枚待发射火箭,完整多枚队列仍以 PendingLaunches 为准。 IgnitedAtMS int64 LaunchAtMS int64 LaunchedAtMS int64 // ResetAtMS 是下一次 UTC 自然日重置毫秒时间。 ResetAtMS int64 - // Top1UserID 和 IgniterUserID 在燃料满瞬间锁定,用户离房或下线仍按这里发奖。 + // Top1UserID 和 IgniterUserID 投影下一枚待发射火箭的锁定用户;发奖以 PendingLaunches 内快照为准。 Top1UserID int64 IgniterUserID int64 // RocketID 是单次火箭从积攒到发射的幂等标识,奖励抽取和 wallet command_id 都依赖它。 From ebd14c7f8529faddb856031f9bbae8e61bcf3d5d Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 01:02:16 +0800 Subject: [PATCH 15/28] =?UTF-8?q?=E6=B8=B8=E6=88=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 3 +- api/proto/activity/v1/activity.pb.go | 737 +++-- api/proto/activity/v1/activity.proto | 15 +- api/proto/events/room/v1/events.pb.go | 13 +- api/proto/events/room/v1/events.proto | 1 + api/proto/game/v1/game.pb.go | 2641 ++++++++++++++++- api/proto/game/v1/game.proto | 235 ++ api/proto/game/v1/game_grpc.pb.go | 544 +++- api/proto/user/v1/auth.pb.go | 58 +- api/proto/user/v1/auth.proto | 3 +- api/proto/user/v1/user.pb.go | 2152 +++++++++++++- api/proto/user/v1/user.proto | 195 ++ api/proto/user/v1/user_grpc.pb.go | 421 ++- api/proto/wallet/v1/wallet.pb.go | 2506 +++++++++++++--- api/proto/wallet/v1/wallet.proto | 173 ++ api/proto/wallet/v1/wallet_grpc.pb.go | 304 ++ docs/game-service游戏结构设计.md | 474 +++ .../039_pretty_display_id_management.sql | 92 + .../mysql/040_external_recharge_payments.sql | 129 + ...41_first_recharge_google_product_tiers.sql | 93 + scripts/mysql/042_mifapay_payment_methods.sql | 107 + server/admin/cmd/server/main.go | 37 +- server/admin/docs/靓号管理服务端技术文档.md | 631 ++++ .../internal/integration/gameclient/client.go | 36 + .../internal/integration/userclient/client.go | 100 + .../internal/integration/userclient/pretty.go | 328 ++ .../integration/walletclient/client.go | 15 + server/admin/internal/model/models.go | 31 +- .../internal/modules/appconfig/request.go | 23 +- .../internal/modules/appconfig/service.go | 101 +- .../modules/appconfig/service_test.go | 86 +- .../modules/firstrechargereward/handler.go | 21 +- .../internal/modules/gamemanagement/dto.go | 112 +- .../modules/gamemanagement/handler.go | 20 +- .../modules/gamemanagement/request.go | 58 + .../modules/gamemanagement/robot_avatar.go | 167 ++ .../gamemanagement/robot_avatar_test.go | 133 + .../modules/gamemanagement/robot_presets.go | 513 ++++ .../modules/gamemanagement/robot_profiles.go | 42 + .../internal/modules/gamemanagement/routes.go | 7 + .../gamemanagement/self_game_handler.go | 262 ++ server/admin/internal/modules/payment/dto.go | 72 + .../admin/internal/modules/payment/handler.go | 110 +- .../admin/internal/modules/payment/routes.go | 3 + .../internal/modules/prettyid/handler.go | 289 ++ .../admin/internal/modules/prettyid/routes.go | 21 + server/admin/internal/repository/seed.go | 26 +- server/admin/internal/router/router.go | 3 + .../migrations/009_admin_navigation_seed.sql | 48 +- .../043_admin_app_splash_display_duration.sql | 5 + .../migrations/044_pretty_id_navigation.sql | 47 + .../045_payment_third_party_navigation.sql | 50 + .../046_first_recharge_gift_copy.sql | 16 + .../mysql/initdb/001_activity_service.sql | 13 +- .../internal/app/wallet_events.go | 53 +- .../internal/client/user_profile_source.go | 11 +- .../domain/firstrecharge/first_recharge.go | 14 +- .../internal/service/broadcast/service.go | 113 +- .../service/broadcast/service_test.go | 59 +- .../internal/service/firstrecharge/service.go | 85 +- .../service/firstrecharge/service_test.go | 47 +- .../mysql/first_recharge_repository.go | 95 +- .../transport/grpc/first_recharge_server.go | 15 + .../deploy/mysql/initdb/001_game_service.sql | 217 ++ services/game-service/internal/app/app.go | 8 +- .../game-service/internal/domain/dice/dice.go | 393 +++ .../internal/service/dice/service.go | 898 ++++++ .../internal/service/dice/service_test.go | 512 ++++ .../mysql/dice_matchmaking_repository.go | 571 ++++ .../internal/storage/mysql/dice_repository.go | 510 ++++ .../internal/storage/mysql/repository.go | 59 + .../internal/transport/grpc/server.go | 400 ++- .../configs/config.docker.yaml | 16 +- services/gateway-service/configs/config.yaml | 18 +- .../internal/appconfig/mysql.go | 28 +- services/gateway-service/internal/auth/jwt.go | 57 +- .../internal/client/game_client.go | 35 + .../internal/client/user_client.go | 10 + .../internal/client/wallet_client.go | 25 + .../internal/config/config_test.go | 2 +- .../first_recharge_reward_handler.go | 149 +- .../http/appapi/heartbeat_handler.go | 26 +- .../internal/transport/http/cors.go | 9 +- .../internal/transport/http/cors_test.go | 38 + .../transport/http/game_handler_test.go | 141 + .../http/gameapi/dice_profile_view.go | 109 + .../transport/http/gameapi/game_handler.go | 244 +- .../transport/http/gameapi/game_view.go | 195 +- .../transport/http/gameapi/handler.go | 68 +- .../http/gameapi/hotgame_compat_handler.go | 243 ++ .../transport/http/httproutes/router.go | 55 +- .../internal/transport/http/response_test.go | 139 +- .../transport/http/roomapi/room_handler.go | 38 +- .../transport/http/roomapi/room_view.go | 38 +- .../internal/transport/http/router.go | 2 + .../transport/http/userapi/handler.go | 2 + .../transport/http/userapi/user_handler.go | 84 + .../http/walletapi/app_wallet_handler.go | 13 +- .../http/walletapi/h5_recharge_handler.go | 509 ++++ .../transport/http/walletapi/handler.go | 6 + .../internal/integration/tencent_im.go | 11 + .../internal/room/service/gift.go | 2 + .../internal/storage/mysql/repository.go | 3 + .../deploy/mysql/initdb/001_user_service.sql | 79 +- services/user-service/internal/app/app.go | 4 + .../user-service/internal/domain/user/user.go | 369 +++ .../internal/domain/user/user_test.go | 45 + .../integration/activityclient/client.go | 31 + .../internal/service/user/pretty_id.go | 239 ++ .../internal/service/user/service.go | 35 + .../internal/storage/mysql/identity/checks.go | 28 +- .../internal/storage/mysql/identity/pretty.go | 967 +++++- .../storage/mysql/identity/pretty_rules.go | 365 +++ .../storage/mysql/identity/pretty_test.go | 98 + .../internal/storage/mysql/user/common.go | 24 + .../internal/testutil/mysqltest/mysqltest.go | 45 + .../internal/transport/grpc/convert.go | 66 + .../internal/transport/grpc/server.go | 168 ++ .../wallet-service/configs/config.docker.yaml | 20 + .../configs/config.tencent.example.yaml | 20 + services/wallet-service/configs/config.yaml | 20 + .../mysql/initdb/001_wallet_service.sql | 130 +- services/wallet-service/internal/app/app.go | 34 + .../internal/client/mifapay/client.go | 268 ++ .../internal/client/trongrid/client.go | 123 + .../wallet-service/internal/config/config.go | 76 + .../internal/domain/ledger/ledger.go | 226 +- .../service/wallet/external_recharge.go | 410 +++ .../internal/service/wallet/service.go | 56 +- .../internal/service/wallet/service_test.go | 6 +- .../mysql/recharge_product_repository.go | 39 +- .../internal/storage/mysql/repository.go | 166 +- .../mysql/third_party_payment_repository.go | 675 +++++ .../internal/transport/grpc/server.go | 233 +- 134 files changed, 24130 insertions(+), 1632 deletions(-) create mode 100644 docs/game-service游戏结构设计.md create mode 100644 scripts/mysql/039_pretty_display_id_management.sql create mode 100644 scripts/mysql/040_external_recharge_payments.sql create mode 100644 scripts/mysql/041_first_recharge_google_product_tiers.sql create mode 100644 scripts/mysql/042_mifapay_payment_methods.sql create mode 100644 server/admin/docs/靓号管理服务端技术文档.md create mode 100644 server/admin/internal/integration/userclient/pretty.go create mode 100644 server/admin/internal/modules/gamemanagement/robot_avatar.go create mode 100644 server/admin/internal/modules/gamemanagement/robot_avatar_test.go create mode 100644 server/admin/internal/modules/gamemanagement/robot_presets.go create mode 100644 server/admin/internal/modules/gamemanagement/robot_profiles.go create mode 100644 server/admin/internal/modules/gamemanagement/self_game_handler.go create mode 100644 server/admin/internal/modules/prettyid/handler.go create mode 100644 server/admin/internal/modules/prettyid/routes.go create mode 100644 server/admin/migrations/043_admin_app_splash_display_duration.sql create mode 100644 server/admin/migrations/044_pretty_id_navigation.sql create mode 100644 server/admin/migrations/045_payment_third_party_navigation.sql create mode 100644 server/admin/migrations/046_first_recharge_gift_copy.sql create mode 100644 services/game-service/internal/domain/dice/dice.go create mode 100644 services/game-service/internal/service/dice/service.go create mode 100644 services/game-service/internal/service/dice/service_test.go create mode 100644 services/game-service/internal/storage/mysql/dice_matchmaking_repository.go create mode 100644 services/game-service/internal/storage/mysql/dice_repository.go create mode 100644 services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go create mode 100644 services/gateway-service/internal/transport/http/gameapi/hotgame_compat_handler.go create mode 100644 services/gateway-service/internal/transport/http/walletapi/h5_recharge_handler.go create mode 100644 services/user-service/internal/storage/mysql/identity/pretty_rules.go create mode 100644 services/user-service/internal/storage/mysql/identity/pretty_test.go create mode 100644 services/wallet-service/internal/client/mifapay/client.go create mode 100644 services/wallet-service/internal/client/trongrid/client.go create mode 100644 services/wallet-service/internal/service/wallet/external_recharge.go create mode 100644 services/wallet-service/internal/storage/mysql/third_party_payment_repository.go diff --git a/AGENTS.md b/AGENTS.md index 447b7193..e8301945 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,8 +92,9 @@ ## Coding Rules - 默认使用 Go 标准库和仓库已有小包;不要为了轻量逻辑引入重框架。 -- 手写代码保持高密度有效注释,注释解释工程语义和失败分支,不复述语法。 +- 手写代码保持高密度有效注释,所有业务逻辑、状态流转、权限判断、支付链路、异步流程都要解释工程语义和失败分支,不复述语法。 - 新增配置必须同时考虑本地 `config.yaml`、Docker `config.docker.yaml`、以及线上配置示例。 +- 所有逻辑功能配置默认开启;密钥、地址、三方账号等必须由运行环境提供的敏感配置只能使用占位符或环境配置,不能写入真实值,也不能伪造可用凭证。 - 新增服务依赖必须在 `docker-compose.yml` 中体现本地可运行形态。 - 数据库表结构变更必须同步更新对应 owner service 的 `deploy/mysql/initdb`。 - 不要把线上密钥、云数据库密码、Redis 密码写进真实配置。只能写占位示例。 diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index b47c4461..cf8d5012 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -4675,7 +4675,7 @@ func (x *ListRegistrationRewardClaimsResponse) GetTodayClaimedCount() int64 { return 0 } -// FirstRechargeRewardTier 是首冲奖励的一个金币充值档位;max_coin_amount=0 表示无上限。 +// FirstRechargeRewardTier 是首冲奖励的一个 Google 商品档位;旧金币区间字段仅保留兼容。 type FirstRechargeRewardTier struct { state protoimpl.MessageState `protogen:"open.v1"` TierId int64 `protobuf:"varint,1,opt,name=tier_id,json=tierId,proto3" json:"tier_id,omitempty"` @@ -4688,6 +4688,9 @@ type FirstRechargeRewardTier struct { SortOrder int32 `protobuf:"varint,8,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` CreatedAtMs int64 `protobuf:"varint,9,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` UpdatedAtMs int64 `protobuf:"varint,10,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + UsdMinorAmount int64 `protobuf:"varint,11,opt,name=usd_minor_amount,json=usdMinorAmount,proto3" json:"usd_minor_amount,omitempty"` + GoogleProductId string `protobuf:"bytes,12,opt,name=google_product_id,json=googleProductId,proto3" json:"google_product_id,omitempty"` + DiscountPercent int32 `protobuf:"varint,13,opt,name=discount_percent,json=discountPercent,proto3" json:"discount_percent,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4792,6 +4795,27 @@ func (x *FirstRechargeRewardTier) GetUpdatedAtMs() int64 { return 0 } +func (x *FirstRechargeRewardTier) GetUsdMinorAmount() int64 { + if x != nil { + return x.UsdMinorAmount + } + return 0 +} + +func (x *FirstRechargeRewardTier) GetGoogleProductId() string { + if x != nil { + return x.GoogleProductId + } + return "" +} + +func (x *FirstRechargeRewardTier) GetDiscountPercent() int32 { + if x != nil { + return x.DiscountPercent + } + return 0 +} + // FirstRechargeRewardConfig 是当前 App 的首冲奖励配置;档位内容在 activity-service 内保持版本一致。 type FirstRechargeRewardConfig struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -4877,7 +4901,7 @@ func (x *FirstRechargeRewardConfig) GetUpdatedAtMs() int64 { return 0 } -// FirstRechargeRewardClaim 是某个用户首笔充值触发后的发奖事实;user_id 唯一保证一生只处理一次。 +// FirstRechargeRewardClaim 是某个用户购买某个首充档位后的发奖事实;同一用户同一档位只处理一次。 type FirstRechargeRewardClaim struct { state protoimpl.MessageState `protogen:"open.v1"` ClaimId string `protobuf:"bytes,1,opt,name=claim_id,json=claimId,proto3" json:"claim_id,omitempty"` @@ -4899,6 +4923,9 @@ type FirstRechargeRewardClaim struct { GrantedAtMs int64 `protobuf:"varint,17,opt,name=granted_at_ms,json=grantedAtMs,proto3" json:"granted_at_ms,omitempty"` CreatedAtMs int64 `protobuf:"varint,18,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` UpdatedAtMs int64 `protobuf:"varint,19,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + TierUsdMinorAmount int64 `protobuf:"varint,20,opt,name=tier_usd_minor_amount,json=tierUsdMinorAmount,proto3" json:"tier_usd_minor_amount,omitempty"` + GoogleProductId string `protobuf:"bytes,21,opt,name=google_product_id,json=googleProductId,proto3" json:"google_product_id,omitempty"` + RechargeUsdMinor int64 `protobuf:"varint,22,opt,name=recharge_usd_minor,json=rechargeUsdMinor,proto3" json:"recharge_usd_minor,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -5066,14 +5093,36 @@ func (x *FirstRechargeRewardClaim) GetUpdatedAtMs() int64 { return 0 } +func (x *FirstRechargeRewardClaim) GetTierUsdMinorAmount() int64 { + if x != nil { + return x.TierUsdMinorAmount + } + return 0 +} + +func (x *FirstRechargeRewardClaim) GetGoogleProductId() string { + if x != nil { + return x.GoogleProductId + } + return "" +} + +func (x *FirstRechargeRewardClaim) GetRechargeUsdMinor() int64 { + if x != nil { + return x.RechargeUsdMinor + } + return 0 +} + // FirstRechargeRewardStatus 是 App 首冲奖励面板可直接展示的当前状态。 type FirstRechargeRewardStatus struct { - state protoimpl.MessageState `protogen:"open.v1"` - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` - Tiers []*FirstRechargeRewardTier `protobuf:"bytes,2,rep,name=tiers,proto3" json:"tiers,omitempty"` - Rewarded bool `protobuf:"varint,3,opt,name=rewarded,proto3" json:"rewarded,omitempty"` - Claim *FirstRechargeRewardClaim `protobuf:"bytes,4,opt,name=claim,proto3" json:"claim,omitempty"` - ServerTimeMs int64 `protobuf:"varint,5,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Tiers []*FirstRechargeRewardTier `protobuf:"bytes,2,rep,name=tiers,proto3" json:"tiers,omitempty"` + Rewarded bool `protobuf:"varint,3,opt,name=rewarded,proto3" json:"rewarded,omitempty"` + Claim *FirstRechargeRewardClaim `protobuf:"bytes,4,opt,name=claim,proto3" json:"claim,omitempty"` + ServerTimeMs int64 `protobuf:"varint,5,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + Claims []*FirstRechargeRewardClaim `protobuf:"bytes,6,rep,name=claims,proto3" json:"claims,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -5143,6 +5192,13 @@ func (x *FirstRechargeRewardStatus) GetServerTimeMs() int64 { return 0 } +func (x *FirstRechargeRewardStatus) GetClaims() []*FirstRechargeRewardClaim { + if x != nil { + return x.Claims + } + return nil +} + type GetFirstRechargeRewardStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -5239,7 +5295,7 @@ func (x *GetFirstRechargeRewardStatusResponse) GetStatus() *FirstRechargeRewardS return nil } -// ConsumeFirstRechargeRewardRequest 是 wallet 充值事实消费入口;只处理 recharge_sequence=1 的成功充值。 +// ConsumeFirstRechargeRewardRequest 是 wallet 充值事实消费入口;Google 商品 ID 命中档位后发奖。 type ConsumeFirstRechargeRewardRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -5251,6 +5307,8 @@ type ConsumeFirstRechargeRewardRequest struct { RechargeSequence int64 `protobuf:"varint,7,opt,name=recharge_sequence,json=rechargeSequence,proto3" json:"recharge_sequence,omitempty"` RechargeType string `protobuf:"bytes,8,opt,name=recharge_type,json=rechargeType,proto3" json:"recharge_type,omitempty"` OccurredAtMs int64 `protobuf:"varint,9,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"` + RechargeUsdMinor int64 `protobuf:"varint,10,opt,name=recharge_usd_minor,json=rechargeUsdMinor,proto3" json:"recharge_usd_minor,omitempty"` + GoogleProductId string `protobuf:"bytes,11,opt,name=google_product_id,json=googleProductId,proto3" json:"google_product_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -5348,6 +5406,20 @@ func (x *ConsumeFirstRechargeRewardRequest) GetOccurredAtMs() int64 { return 0 } +func (x *ConsumeFirstRechargeRewardRequest) GetRechargeUsdMinor() int64 { + if x != nil { + return x.RechargeUsdMinor + } + return 0 +} + +func (x *ConsumeFirstRechargeRewardRequest) GetGoogleProductId() string { + if x != nil { + return x.GoogleProductId + } + return "" +} + type ConsumeFirstRechargeRewardResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Claim *FirstRechargeRewardClaim `protobuf:"bytes,1,opt,name=claim,proto3" json:"claim,omitempty"` @@ -16927,7 +16999,7 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "$ListRegistrationRewardClaimsResponse\x12B\n" + "\x06claims\x18\x01 \x03(\v2*.hyapp.activity.v1.RegistrationRewardClaimR\x06claims\x12\x14\n" + "\x05total\x18\x02 \x01(\x03R\x05total\x12.\n" + - "\x13today_claimed_count\x18\x03 \x01(\x03R\x11todayClaimedCount\"\xe7\x02\n" + + "\x13today_claimed_count\x18\x03 \x01(\x03R\x11todayClaimedCount\"\xe8\x03\n" + "\x17FirstRechargeRewardTier\x12\x17\n" + "\atier_id\x18\x01 \x01(\x03R\x06tierId\x12\x1b\n" + "\ttier_code\x18\x02 \x01(\tR\btierCode\x12\x1b\n" + @@ -16940,14 +17012,17 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "sort_order\x18\b \x01(\x05R\tsortOrder\x12\"\n" + "\rcreated_at_ms\x18\t \x01(\x03R\vcreatedAtMs\x12\"\n" + "\rupdated_at_ms\x18\n" + - " \x01(\x03R\vupdatedAtMs\"\x89\x02\n" + + " \x01(\x03R\vupdatedAtMs\x12(\n" + + "\x10usd_minor_amount\x18\v \x01(\x03R\x0eusdMinorAmount\x12*\n" + + "\x11google_product_id\x18\f \x01(\tR\x0fgoogleProductId\x12)\n" + + "\x10discount_percent\x18\r \x01(\x05R\x0fdiscountPercent\"\x89\x02\n" + "\x19FirstRechargeRewardConfig\x12\x19\n" + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x18\n" + "\aenabled\x18\x02 \x01(\bR\aenabled\x12@\n" + "\x05tiers\x18\x03 \x03(\v2*.hyapp.activity.v1.FirstRechargeRewardTierR\x05tiers\x12-\n" + "\x13updated_by_admin_id\x18\x04 \x01(\x03R\x10updatedByAdminId\x12\"\n" + "\rcreated_at_ms\x18\x05 \x01(\x03R\vcreatedAtMs\x12\"\n" + - "\rupdated_at_ms\x18\x06 \x01(\x03R\vupdatedAtMs\"\xbc\x05\n" + + "\rupdated_at_ms\x18\x06 \x01(\x03R\vupdatedAtMs\"\xc9\x06\n" + "\x18FirstRechargeRewardClaim\x12\x19\n" + "\bclaim_id\x18\x01 \x01(\tR\aclaimId\x12\x19\n" + "\bevent_id\x18\x02 \x01(\tR\aeventId\x12%\n" + @@ -16969,18 +17044,22 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\x0frecharged_at_ms\x18\x10 \x01(\x03R\rrechargedAtMs\x12\"\n" + "\rgranted_at_ms\x18\x11 \x01(\x03R\vgrantedAtMs\x12\"\n" + "\rcreated_at_ms\x18\x12 \x01(\x03R\vcreatedAtMs\x12\"\n" + - "\rupdated_at_ms\x18\x13 \x01(\x03R\vupdatedAtMs\"\xfc\x01\n" + + "\rupdated_at_ms\x18\x13 \x01(\x03R\vupdatedAtMs\x121\n" + + "\x15tier_usd_minor_amount\x18\x14 \x01(\x03R\x12tierUsdMinorAmount\x12*\n" + + "\x11google_product_id\x18\x15 \x01(\tR\x0fgoogleProductId\x12,\n" + + "\x12recharge_usd_minor\x18\x16 \x01(\x03R\x10rechargeUsdMinor\"\xc1\x02\n" + "\x19FirstRechargeRewardStatus\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x12@\n" + "\x05tiers\x18\x02 \x03(\v2*.hyapp.activity.v1.FirstRechargeRewardTierR\x05tiers\x12\x1a\n" + "\brewarded\x18\x03 \x01(\bR\brewarded\x12A\n" + "\x05claim\x18\x04 \x01(\v2+.hyapp.activity.v1.FirstRechargeRewardClaimR\x05claim\x12$\n" + - "\x0eserver_time_ms\x18\x05 \x01(\x03R\fserverTimeMs\"r\n" + + "\x0eserver_time_ms\x18\x05 \x01(\x03R\fserverTimeMs\x12C\n" + + "\x06claims\x18\x06 \x03(\v2+.hyapp.activity.v1.FirstRechargeRewardClaimR\x06claims\"r\n" + "#GetFirstRechargeRewardStatusRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\"l\n" + "$GetFirstRechargeRewardStatusResponse\x12D\n" + - "\x06status\x18\x01 \x01(\v2,.hyapp.activity.v1.FirstRechargeRewardStatusR\x06status\"\xfb\x02\n" + + "\x06status\x18\x01 \x01(\v2,.hyapp.activity.v1.FirstRechargeRewardStatusR\x06status\"\xd5\x03\n" + "!ConsumeFirstRechargeRewardRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x19\n" + "\bevent_id\x18\x02 \x01(\tR\aeventId\x12%\n" + @@ -16991,7 +17070,10 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\x14recharge_coin_amount\x18\x06 \x01(\x03R\x12rechargeCoinAmount\x12+\n" + "\x11recharge_sequence\x18\a \x01(\x03R\x10rechargeSequence\x12#\n" + "\rrecharge_type\x18\b \x01(\tR\frechargeType\x12$\n" + - "\x0eoccurred_at_ms\x18\t \x01(\x03R\foccurredAtMs\"\x9b\x01\n" + + "\x0eoccurred_at_ms\x18\t \x01(\x03R\foccurredAtMs\x12,\n" + + "\x12recharge_usd_minor\x18\n" + + " \x01(\x03R\x10rechargeUsdMinor\x12*\n" + + "\x11google_product_id\x18\v \x01(\tR\x0fgoogleProductId\"\x9b\x01\n" + "\"ConsumeFirstRechargeRewardResponse\x12A\n" + "\x05claim\x18\x01 \x01(\v2+.hyapp.activity.v1.FirstRechargeRewardClaimR\x05claim\x12\x1a\n" + "\bconsumed\x18\x02 \x01(\bR\bconsumed\x12\x16\n" + @@ -18358,317 +18440,318 @@ var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 62, // 40: hyapp.activity.v1.FirstRechargeRewardConfig.tiers:type_name -> hyapp.activity.v1.FirstRechargeRewardTier 62, // 41: hyapp.activity.v1.FirstRechargeRewardStatus.tiers:type_name -> hyapp.activity.v1.FirstRechargeRewardTier 64, // 42: hyapp.activity.v1.FirstRechargeRewardStatus.claim:type_name -> hyapp.activity.v1.FirstRechargeRewardClaim - 0, // 43: hyapp.activity.v1.GetFirstRechargeRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 65, // 44: hyapp.activity.v1.GetFirstRechargeRewardStatusResponse.status:type_name -> hyapp.activity.v1.FirstRechargeRewardStatus - 0, // 45: hyapp.activity.v1.ConsumeFirstRechargeRewardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 64, // 46: hyapp.activity.v1.ConsumeFirstRechargeRewardResponse.claim:type_name -> hyapp.activity.v1.FirstRechargeRewardClaim - 0, // 47: hyapp.activity.v1.GetFirstRechargeRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 63, // 48: hyapp.activity.v1.GetFirstRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.FirstRechargeRewardConfig - 0, // 49: hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 62, // 50: hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.FirstRechargeRewardTier - 63, // 51: hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.FirstRechargeRewardConfig - 0, // 52: hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 64, // 53: hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse.claims:type_name -> hyapp.activity.v1.FirstRechargeRewardClaim - 76, // 54: hyapp.activity.v1.CumulativeRechargeRewardConfig.tiers:type_name -> hyapp.activity.v1.CumulativeRechargeRewardTier - 77, // 55: hyapp.activity.v1.CumulativeRechargeRewardStatus.config:type_name -> hyapp.activity.v1.CumulativeRechargeRewardConfig - 79, // 56: hyapp.activity.v1.CumulativeRechargeRewardStatus.progress:type_name -> hyapp.activity.v1.CumulativeRechargeRewardProgress - 78, // 57: hyapp.activity.v1.CumulativeRechargeRewardStatus.grants:type_name -> hyapp.activity.v1.CumulativeRechargeRewardGrant - 0, // 58: hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 80, // 59: hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse.status:type_name -> hyapp.activity.v1.CumulativeRechargeRewardStatus - 0, // 60: hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 78, // 61: hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse.grants:type_name -> hyapp.activity.v1.CumulativeRechargeRewardGrant - 0, // 62: hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 77, // 63: hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.CumulativeRechargeRewardConfig - 0, // 64: hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 76, // 65: hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.CumulativeRechargeRewardTier - 77, // 66: hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.CumulativeRechargeRewardConfig - 0, // 67: hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 78, // 68: hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse.grants:type_name -> hyapp.activity.v1.CumulativeRechargeRewardGrant - 91, // 69: hyapp.activity.v1.RoomTurnoverRewardConfig.tiers:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier - 92, // 70: hyapp.activity.v1.RoomTurnoverRewardStatus.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig - 91, // 71: hyapp.activity.v1.RoomTurnoverRewardStatus.matched_tier:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier - 93, // 72: hyapp.activity.v1.RoomTurnoverRewardStatus.latest_settlement:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement - 0, // 73: hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 94, // 74: hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse.status:type_name -> hyapp.activity.v1.RoomTurnoverRewardStatus - 0, // 75: hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 92, // 76: hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig - 0, // 77: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 91, // 78: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier - 92, // 79: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig - 0, // 80: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 93, // 81: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse.settlements:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement - 0, // 82: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 93, // 83: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse.settlement:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement - 105, // 84: hyapp.activity.v1.SevenDayCheckInConfig.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInReward - 0, // 85: hyapp.activity.v1.GetSevenDayCheckInStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 107, // 86: hyapp.activity.v1.GetSevenDayCheckInStatusResponse.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInRewardStatus - 0, // 87: hyapp.activity.v1.SignSevenDayCheckInRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 88: hyapp.activity.v1.GetSevenDayCheckInConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 106, // 89: hyapp.activity.v1.GetSevenDayCheckInConfigResponse.config:type_name -> hyapp.activity.v1.SevenDayCheckInConfig - 0, // 90: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 105, // 91: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInReward - 106, // 92: hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse.config:type_name -> hyapp.activity.v1.SevenDayCheckInConfig - 0, // 93: hyapp.activity.v1.ListSevenDayCheckInClaimsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 116, // 94: hyapp.activity.v1.ListSevenDayCheckInClaimsResponse.claims:type_name -> hyapp.activity.v1.SevenDayCheckInClaim - 0, // 95: hyapp.activity.v1.GetMyLevelOverviewRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 122, // 96: hyapp.activity.v1.GetMyLevelOverviewResponse.tracks:type_name -> hyapp.activity.v1.LevelTrackOverview - 0, // 97: hyapp.activity.v1.GetLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 122, // 98: hyapp.activity.v1.GetLevelTrackResponse.overview:type_name -> hyapp.activity.v1.LevelTrackOverview - 120, // 99: hyapp.activity.v1.GetLevelTrackResponse.rules:type_name -> hyapp.activity.v1.LevelRule - 121, // 100: hyapp.activity.v1.GetLevelTrackResponse.tiers:type_name -> hyapp.activity.v1.LevelTier - 127, // 101: hyapp.activity.v1.UserLevelDisplayProfile.wealth:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile - 127, // 102: hyapp.activity.v1.UserLevelDisplayProfile.game:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile - 127, // 103: hyapp.activity.v1.UserLevelDisplayProfile.charm:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile - 0, // 104: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 128, // 105: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse.profiles:type_name -> hyapp.activity.v1.UserLevelDisplayProfile - 0, // 106: hyapp.activity.v1.ListLevelRewardsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 131, // 107: hyapp.activity.v1.ListLevelRewardsResponse.rewards:type_name -> hyapp.activity.v1.LevelRewardJob - 0, // 108: hyapp.activity.v1.ConsumeLevelEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 109: hyapp.activity.v1.SetUserLevelRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 110: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 138, // 111: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse.grants:type_name -> hyapp.activity.v1.RegistrationLevelBadgeGrant - 0, // 112: hyapp.activity.v1.UpsertLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 119, // 113: hyapp.activity.v1.UpsertLevelTrackResponse.track:type_name -> hyapp.activity.v1.LevelTrack - 0, // 114: hyapp.activity.v1.UpsertLevelRuleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 120, // 115: hyapp.activity.v1.UpsertLevelRuleResponse.rule:type_name -> hyapp.activity.v1.LevelRule - 0, // 116: hyapp.activity.v1.UpsertLevelTierRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 121, // 117: hyapp.activity.v1.UpsertLevelTierResponse.tier:type_name -> hyapp.activity.v1.LevelTier - 0, // 118: hyapp.activity.v1.ListLevelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 119, // 119: hyapp.activity.v1.ListLevelConfigResponse.tracks:type_name -> hyapp.activity.v1.LevelTrack - 120, // 120: hyapp.activity.v1.ListLevelConfigResponse.rules:type_name -> hyapp.activity.v1.LevelRule - 121, // 121: hyapp.activity.v1.ListLevelConfigResponse.tiers:type_name -> hyapp.activity.v1.LevelTier - 149, // 122: hyapp.activity.v1.AchievementDefinition.conditions:type_name -> hyapp.activity.v1.AchievementCondition - 150, // 123: hyapp.activity.v1.UserAchievement.definition:type_name -> hyapp.activity.v1.AchievementDefinition - 0, // 124: hyapp.activity.v1.ListAchievementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 151, // 125: hyapp.activity.v1.ListAchievementsResponse.achievements:type_name -> hyapp.activity.v1.UserAchievement - 0, // 126: hyapp.activity.v1.ConsumeAchievementEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 127: hyapp.activity.v1.ListMyBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 156, // 128: hyapp.activity.v1.ListMyBadgesResponse.strip_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 156, // 129: hyapp.activity.v1.ListMyBadgesResponse.profile_tile_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 156, // 130: hyapp.activity.v1.ListMyBadgesResponse.honor_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 0, // 131: hyapp.activity.v1.SetBadgeDisplayRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 156, // 132: hyapp.activity.v1.SetBadgeDisplayRequest.items:type_name -> hyapp.activity.v1.BadgeDisplayItem - 158, // 133: hyapp.activity.v1.SetBadgeDisplayResponse.profile:type_name -> hyapp.activity.v1.ListMyBadgesResponse - 0, // 134: hyapp.activity.v1.UpsertAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 149, // 135: hyapp.activity.v1.UpsertAchievementDefinitionRequest.conditions:type_name -> hyapp.activity.v1.AchievementCondition - 150, // 136: hyapp.activity.v1.UpsertAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition - 0, // 137: hyapp.activity.v1.LuckyGiftMeta.meta:type_name -> hyapp.activity.v1.RequestMeta - 164, // 138: hyapp.activity.v1.LuckyGiftConfig.tiers:type_name -> hyapp.activity.v1.LuckyGiftTier - 166, // 139: hyapp.activity.v1.LuckyGiftRuleStage.tiers:type_name -> hyapp.activity.v1.LuckyGiftRuleTier - 167, // 140: hyapp.activity.v1.LuckyGiftRuleConfig.stages:type_name -> hyapp.activity.v1.LuckyGiftRuleStage - 0, // 141: hyapp.activity.v1.CheckLuckyGiftRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 163, // 142: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest.lucky_gift:type_name -> hyapp.activity.v1.LuckyGiftMeta - 171, // 143: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse.result:type_name -> hyapp.activity.v1.LuckyGiftDrawResult - 0, // 144: hyapp.activity.v1.GetLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 168, // 145: hyapp.activity.v1.GetLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 146: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 168, // 147: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 168, // 148: hyapp.activity.v1.UpsertLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 149: hyapp.activity.v1.ListLuckyGiftConfigsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 168, // 150: hyapp.activity.v1.ListLuckyGiftConfigsResponse.configs:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 151: hyapp.activity.v1.ListLuckyGiftDrawsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 171, // 152: hyapp.activity.v1.ListLuckyGiftDrawsResponse.draws:type_name -> hyapp.activity.v1.LuckyGiftDrawResult - 0, // 153: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 182, // 154: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.activity.v1.LuckyGiftDrawSummary - 185, // 155: hyapp.activity.v1.WeeklyStarCycle.gifts:type_name -> hyapp.activity.v1.WeeklyStarGift - 186, // 156: hyapp.activity.v1.WeeklyStarCycle.rewards:type_name -> hyapp.activity.v1.WeeklyStarReward - 0, // 157: hyapp.activity.v1.ListWeeklyStarCyclesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 158: hyapp.activity.v1.ListWeeklyStarCyclesResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 159: hyapp.activity.v1.GetWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 160: hyapp.activity.v1.GetWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 161: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 162: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 187, // 163: hyapp.activity.v1.UpsertWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 164: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 165: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 166: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 167: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 188, // 168: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 0, // 169: hyapp.activity.v1.ListWeeklyStarSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 189, // 170: hyapp.activity.v1.ListWeeklyStarSettlementsResponse.settlements:type_name -> hyapp.activity.v1.WeeklyStarSettlement - 0, // 171: hyapp.activity.v1.GetWeeklyStarCurrentRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 172: hyapp.activity.v1.GetWeeklyStarCurrentResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 188, // 173: hyapp.activity.v1.GetWeeklyStarCurrentResponse.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 188, // 174: hyapp.activity.v1.GetWeeklyStarCurrentResponse.my_entry:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 0, // 175: hyapp.activity.v1.ListWeeklyStarHistoryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 176: hyapp.activity.v1.WeeklyStarHistoryCycle.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 188, // 177: hyapp.activity.v1.WeeklyStarHistoryCycle.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 205, // 178: hyapp.activity.v1.ListWeeklyStarHistoryResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarHistoryCycle - 1, // 179: hyapp.activity.v1.ActivityService.PingActivity:input_type -> hyapp.activity.v1.PingActivityRequest - 3, // 180: hyapp.activity.v1.ActivityService.GetActivityStatus:input_type -> hyapp.activity.v1.GetActivityStatusRequest - 6, // 181: hyapp.activity.v1.MessageInboxService.ListMessageTabs:input_type -> hyapp.activity.v1.ListMessageTabsRequest - 9, // 182: hyapp.activity.v1.MessageInboxService.ListInboxMessages:input_type -> hyapp.activity.v1.ListInboxMessagesRequest - 11, // 183: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:input_type -> hyapp.activity.v1.MarkInboxMessageReadRequest - 13, // 184: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:input_type -> hyapp.activity.v1.MarkInboxSectionReadRequest - 15, // 185: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:input_type -> hyapp.activity.v1.DeleteInboxMessageRequest - 17, // 186: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:input_type -> hyapp.activity.v1.CreateInboxMessageRequest - 19, // 187: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:input_type -> hyapp.activity.v1.CreateFanoutJobRequest - 21, // 188: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 189: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 190: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 191: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 192: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 25, // 193: hyapp.activity.v1.TaskService.ListUserTasks:input_type -> hyapp.activity.v1.ListUserTasksRequest - 27, // 194: hyapp.activity.v1.TaskService.ClaimTaskReward:input_type -> hyapp.activity.v1.ClaimTaskRewardRequest - 29, // 195: hyapp.activity.v1.TaskService.ConsumeTaskEvent:input_type -> hyapp.activity.v1.ConsumeTaskEventRequest - 123, // 196: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:input_type -> hyapp.activity.v1.GetMyLevelOverviewRequest - 125, // 197: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:input_type -> hyapp.activity.v1.GetLevelTrackRequest - 129, // 198: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:input_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest - 132, // 199: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:input_type -> hyapp.activity.v1.ListLevelRewardsRequest - 134, // 200: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:input_type -> hyapp.activity.v1.ConsumeLevelEventRequest - 136, // 201: hyapp.activity.v1.GrowthLevelService.SetUserLevel:input_type -> hyapp.activity.v1.SetUserLevelRequest - 139, // 202: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:input_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesRequest - 152, // 203: hyapp.activity.v1.AchievementService.ListAchievements:input_type -> hyapp.activity.v1.ListAchievementsRequest - 154, // 204: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:input_type -> hyapp.activity.v1.ConsumeAchievementEventRequest - 157, // 205: hyapp.activity.v1.AchievementService.ListMyBadges:input_type -> hyapp.activity.v1.ListMyBadgesRequest - 159, // 206: hyapp.activity.v1.AchievementService.SetBadgeDisplay:input_type -> hyapp.activity.v1.SetBadgeDisplayRequest - 169, // 207: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.activity.v1.CheckLuckyGiftRequest - 172, // 208: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawRequest - 95, // 209: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest - 202, // 210: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:input_type -> hyapp.activity.v1.GetWeeklyStarCurrentRequest - 198, // 211: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest - 204, // 212: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:input_type -> hyapp.activity.v1.ListWeeklyStarHistoryRequest - 32, // 213: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:input_type -> hyapp.activity.v1.EnsureBroadcastGroupsRequest - 34, // 214: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:input_type -> hyapp.activity.v1.PublishRegionBroadcastRequest - 35, // 215: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:input_type -> hyapp.activity.v1.PublishGlobalBroadcastRequest - 37, // 216: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:input_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberRequest - 21, // 217: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 40, // 218: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:input_type -> hyapp.activity.v1.ConsumeRoomEventRequest - 43, // 219: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:input_type -> hyapp.activity.v1.ListTaskDefinitionsRequest - 45, // 220: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:input_type -> hyapp.activity.v1.UpsertTaskDefinitionRequest - 47, // 221: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:input_type -> hyapp.activity.v1.SetTaskDefinitionStatusRequest - 52, // 222: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:input_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityRequest - 54, // 223: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:input_type -> hyapp.activity.v1.IssueRegistrationRewardRequest - 56, // 224: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:input_type -> hyapp.activity.v1.GetRegistrationRewardConfigRequest - 58, // 225: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:input_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigRequest - 60, // 226: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:input_type -> hyapp.activity.v1.ListRegistrationRewardClaimsRequest - 66, // 227: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:input_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusRequest - 68, // 228: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:input_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardRequest - 70, // 229: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigRequest - 72, // 230: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest - 74, // 231: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:input_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest - 81, // 232: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest - 83, // 233: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:input_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest - 85, // 234: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest - 87, // 235: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest - 89, // 236: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:input_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest - 97, // 237: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest - 99, // 238: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest - 101, // 239: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:input_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest - 103, // 240: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:input_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest - 190, // 241: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:input_type -> hyapp.activity.v1.ListWeeklyStarCyclesRequest - 194, // 242: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest - 192, // 243: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:input_type -> hyapp.activity.v1.GetWeeklyStarCycleRequest - 194, // 244: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest - 196, // 245: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:input_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusRequest - 198, // 246: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest - 200, // 247: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:input_type -> hyapp.activity.v1.ListWeeklyStarSettlementsRequest - 108, // 248: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:input_type -> hyapp.activity.v1.GetSevenDayCheckInStatusRequest - 110, // 249: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:input_type -> hyapp.activity.v1.SignSevenDayCheckInRequest - 112, // 250: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:input_type -> hyapp.activity.v1.GetSevenDayCheckInConfigRequest - 114, // 251: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:input_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest - 117, // 252: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:input_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsRequest - 147, // 253: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:input_type -> hyapp.activity.v1.ListLevelConfigRequest - 141, // 254: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:input_type -> hyapp.activity.v1.UpsertLevelTrackRequest - 143, // 255: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:input_type -> hyapp.activity.v1.UpsertLevelRuleRequest - 145, // 256: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:input_type -> hyapp.activity.v1.UpsertLevelTierRequest - 152, // 257: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:input_type -> hyapp.activity.v1.ListAchievementsRequest - 161, // 258: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:input_type -> hyapp.activity.v1.UpsertAchievementDefinitionRequest - 174, // 259: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.activity.v1.GetLuckyGiftConfigRequest - 176, // 260: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.activity.v1.UpsertLuckyGiftConfigRequest - 178, // 261: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.activity.v1.ListLuckyGiftConfigsRequest - 180, // 262: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.activity.v1.ListLuckyGiftDrawsRequest - 183, // 263: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest - 2, // 264: hyapp.activity.v1.ActivityService.PingActivity:output_type -> hyapp.activity.v1.PingActivityResponse - 4, // 265: hyapp.activity.v1.ActivityService.GetActivityStatus:output_type -> hyapp.activity.v1.GetActivityStatusResponse - 7, // 266: hyapp.activity.v1.MessageInboxService.ListMessageTabs:output_type -> hyapp.activity.v1.ListMessageTabsResponse - 10, // 267: hyapp.activity.v1.MessageInboxService.ListInboxMessages:output_type -> hyapp.activity.v1.ListInboxMessagesResponse - 12, // 268: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:output_type -> hyapp.activity.v1.MarkInboxMessageReadResponse - 14, // 269: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:output_type -> hyapp.activity.v1.MarkInboxSectionReadResponse - 16, // 270: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:output_type -> hyapp.activity.v1.DeleteInboxMessageResponse - 18, // 271: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:output_type -> hyapp.activity.v1.CreateInboxMessageResponse - 20, // 272: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:output_type -> hyapp.activity.v1.CreateFanoutJobResponse - 22, // 273: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 274: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 275: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 276: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 277: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 26, // 278: hyapp.activity.v1.TaskService.ListUserTasks:output_type -> hyapp.activity.v1.ListUserTasksResponse - 28, // 279: hyapp.activity.v1.TaskService.ClaimTaskReward:output_type -> hyapp.activity.v1.ClaimTaskRewardResponse - 30, // 280: hyapp.activity.v1.TaskService.ConsumeTaskEvent:output_type -> hyapp.activity.v1.ConsumeTaskEventResponse - 124, // 281: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:output_type -> hyapp.activity.v1.GetMyLevelOverviewResponse - 126, // 282: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:output_type -> hyapp.activity.v1.GetLevelTrackResponse - 130, // 283: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:output_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse - 133, // 284: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:output_type -> hyapp.activity.v1.ListLevelRewardsResponse - 135, // 285: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:output_type -> hyapp.activity.v1.ConsumeLevelEventResponse - 137, // 286: hyapp.activity.v1.GrowthLevelService.SetUserLevel:output_type -> hyapp.activity.v1.SetUserLevelResponse - 140, // 287: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:output_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesResponse - 153, // 288: hyapp.activity.v1.AchievementService.ListAchievements:output_type -> hyapp.activity.v1.ListAchievementsResponse - 155, // 289: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:output_type -> hyapp.activity.v1.ConsumeAchievementEventResponse - 158, // 290: hyapp.activity.v1.AchievementService.ListMyBadges:output_type -> hyapp.activity.v1.ListMyBadgesResponse - 160, // 291: hyapp.activity.v1.AchievementService.SetBadgeDisplay:output_type -> hyapp.activity.v1.SetBadgeDisplayResponse - 170, // 292: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.activity.v1.CheckLuckyGiftResponse - 173, // 293: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawResponse - 96, // 294: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse - 203, // 295: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:output_type -> hyapp.activity.v1.GetWeeklyStarCurrentResponse - 199, // 296: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse - 206, // 297: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:output_type -> hyapp.activity.v1.ListWeeklyStarHistoryResponse - 33, // 298: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:output_type -> hyapp.activity.v1.EnsureBroadcastGroupsResponse - 36, // 299: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse - 36, // 300: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse - 38, // 301: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:output_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberResponse - 39, // 302: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:output_type -> hyapp.activity.v1.ProcessBroadcastOutboxBatchResponse - 41, // 303: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:output_type -> hyapp.activity.v1.ConsumeRoomEventResponse - 44, // 304: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:output_type -> hyapp.activity.v1.ListTaskDefinitionsResponse - 46, // 305: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:output_type -> hyapp.activity.v1.UpsertTaskDefinitionResponse - 48, // 306: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:output_type -> hyapp.activity.v1.SetTaskDefinitionStatusResponse - 53, // 307: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:output_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityResponse - 55, // 308: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:output_type -> hyapp.activity.v1.IssueRegistrationRewardResponse - 57, // 309: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:output_type -> hyapp.activity.v1.GetRegistrationRewardConfigResponse - 59, // 310: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:output_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigResponse - 61, // 311: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:output_type -> hyapp.activity.v1.ListRegistrationRewardClaimsResponse - 67, // 312: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:output_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusResponse - 69, // 313: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:output_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardResponse - 71, // 314: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigResponse - 73, // 315: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse - 75, // 316: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:output_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse - 82, // 317: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse - 84, // 318: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:output_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse - 86, // 319: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse - 88, // 320: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse - 90, // 321: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:output_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse - 98, // 322: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse - 100, // 323: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse - 102, // 324: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:output_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse - 104, // 325: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:output_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse - 191, // 326: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:output_type -> hyapp.activity.v1.ListWeeklyStarCyclesResponse - 195, // 327: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse - 193, // 328: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:output_type -> hyapp.activity.v1.GetWeeklyStarCycleResponse - 195, // 329: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse - 197, // 330: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:output_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusResponse - 199, // 331: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse - 201, // 332: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:output_type -> hyapp.activity.v1.ListWeeklyStarSettlementsResponse - 109, // 333: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:output_type -> hyapp.activity.v1.GetSevenDayCheckInStatusResponse - 111, // 334: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:output_type -> hyapp.activity.v1.SignSevenDayCheckInResponse - 113, // 335: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:output_type -> hyapp.activity.v1.GetSevenDayCheckInConfigResponse - 115, // 336: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:output_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse - 118, // 337: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:output_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsResponse - 148, // 338: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:output_type -> hyapp.activity.v1.ListLevelConfigResponse - 142, // 339: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:output_type -> hyapp.activity.v1.UpsertLevelTrackResponse - 144, // 340: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:output_type -> hyapp.activity.v1.UpsertLevelRuleResponse - 146, // 341: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:output_type -> hyapp.activity.v1.UpsertLevelTierResponse - 153, // 342: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:output_type -> hyapp.activity.v1.ListAchievementsResponse - 162, // 343: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:output_type -> hyapp.activity.v1.UpsertAchievementDefinitionResponse - 175, // 344: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.activity.v1.GetLuckyGiftConfigResponse - 177, // 345: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.activity.v1.UpsertLuckyGiftConfigResponse - 179, // 346: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.activity.v1.ListLuckyGiftConfigsResponse - 181, // 347: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.activity.v1.ListLuckyGiftDrawsResponse - 184, // 348: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse - 264, // [264:349] is the sub-list for method output_type - 179, // [179:264] is the sub-list for method input_type - 179, // [179:179] is the sub-list for extension type_name - 179, // [179:179] is the sub-list for extension extendee - 0, // [0:179] is the sub-list for field type_name + 64, // 43: hyapp.activity.v1.FirstRechargeRewardStatus.claims:type_name -> hyapp.activity.v1.FirstRechargeRewardClaim + 0, // 44: hyapp.activity.v1.GetFirstRechargeRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 65, // 45: hyapp.activity.v1.GetFirstRechargeRewardStatusResponse.status:type_name -> hyapp.activity.v1.FirstRechargeRewardStatus + 0, // 46: hyapp.activity.v1.ConsumeFirstRechargeRewardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 64, // 47: hyapp.activity.v1.ConsumeFirstRechargeRewardResponse.claim:type_name -> hyapp.activity.v1.FirstRechargeRewardClaim + 0, // 48: hyapp.activity.v1.GetFirstRechargeRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 63, // 49: hyapp.activity.v1.GetFirstRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.FirstRechargeRewardConfig + 0, // 50: hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 62, // 51: hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.FirstRechargeRewardTier + 63, // 52: hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.FirstRechargeRewardConfig + 0, // 53: hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 64, // 54: hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse.claims:type_name -> hyapp.activity.v1.FirstRechargeRewardClaim + 76, // 55: hyapp.activity.v1.CumulativeRechargeRewardConfig.tiers:type_name -> hyapp.activity.v1.CumulativeRechargeRewardTier + 77, // 56: hyapp.activity.v1.CumulativeRechargeRewardStatus.config:type_name -> hyapp.activity.v1.CumulativeRechargeRewardConfig + 79, // 57: hyapp.activity.v1.CumulativeRechargeRewardStatus.progress:type_name -> hyapp.activity.v1.CumulativeRechargeRewardProgress + 78, // 58: hyapp.activity.v1.CumulativeRechargeRewardStatus.grants:type_name -> hyapp.activity.v1.CumulativeRechargeRewardGrant + 0, // 59: hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 80, // 60: hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse.status:type_name -> hyapp.activity.v1.CumulativeRechargeRewardStatus + 0, // 61: hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 78, // 62: hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse.grants:type_name -> hyapp.activity.v1.CumulativeRechargeRewardGrant + 0, // 63: hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 77, // 64: hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.CumulativeRechargeRewardConfig + 0, // 65: hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 76, // 66: hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.CumulativeRechargeRewardTier + 77, // 67: hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.CumulativeRechargeRewardConfig + 0, // 68: hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 78, // 69: hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse.grants:type_name -> hyapp.activity.v1.CumulativeRechargeRewardGrant + 91, // 70: hyapp.activity.v1.RoomTurnoverRewardConfig.tiers:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier + 92, // 71: hyapp.activity.v1.RoomTurnoverRewardStatus.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig + 91, // 72: hyapp.activity.v1.RoomTurnoverRewardStatus.matched_tier:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier + 93, // 73: hyapp.activity.v1.RoomTurnoverRewardStatus.latest_settlement:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement + 0, // 74: hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 94, // 75: hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse.status:type_name -> hyapp.activity.v1.RoomTurnoverRewardStatus + 0, // 76: hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 92, // 77: hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig + 0, // 78: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 91, // 79: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier + 92, // 80: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig + 0, // 81: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 93, // 82: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse.settlements:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement + 0, // 83: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 93, // 84: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse.settlement:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement + 105, // 85: hyapp.activity.v1.SevenDayCheckInConfig.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInReward + 0, // 86: hyapp.activity.v1.GetSevenDayCheckInStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 107, // 87: hyapp.activity.v1.GetSevenDayCheckInStatusResponse.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInRewardStatus + 0, // 88: hyapp.activity.v1.SignSevenDayCheckInRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 89: hyapp.activity.v1.GetSevenDayCheckInConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 106, // 90: hyapp.activity.v1.GetSevenDayCheckInConfigResponse.config:type_name -> hyapp.activity.v1.SevenDayCheckInConfig + 0, // 91: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 105, // 92: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInReward + 106, // 93: hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse.config:type_name -> hyapp.activity.v1.SevenDayCheckInConfig + 0, // 94: hyapp.activity.v1.ListSevenDayCheckInClaimsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 116, // 95: hyapp.activity.v1.ListSevenDayCheckInClaimsResponse.claims:type_name -> hyapp.activity.v1.SevenDayCheckInClaim + 0, // 96: hyapp.activity.v1.GetMyLevelOverviewRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 122, // 97: hyapp.activity.v1.GetMyLevelOverviewResponse.tracks:type_name -> hyapp.activity.v1.LevelTrackOverview + 0, // 98: hyapp.activity.v1.GetLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 122, // 99: hyapp.activity.v1.GetLevelTrackResponse.overview:type_name -> hyapp.activity.v1.LevelTrackOverview + 120, // 100: hyapp.activity.v1.GetLevelTrackResponse.rules:type_name -> hyapp.activity.v1.LevelRule + 121, // 101: hyapp.activity.v1.GetLevelTrackResponse.tiers:type_name -> hyapp.activity.v1.LevelTier + 127, // 102: hyapp.activity.v1.UserLevelDisplayProfile.wealth:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 127, // 103: hyapp.activity.v1.UserLevelDisplayProfile.game:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 127, // 104: hyapp.activity.v1.UserLevelDisplayProfile.charm:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 0, // 105: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 128, // 106: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse.profiles:type_name -> hyapp.activity.v1.UserLevelDisplayProfile + 0, // 107: hyapp.activity.v1.ListLevelRewardsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 131, // 108: hyapp.activity.v1.ListLevelRewardsResponse.rewards:type_name -> hyapp.activity.v1.LevelRewardJob + 0, // 109: hyapp.activity.v1.ConsumeLevelEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 110: hyapp.activity.v1.SetUserLevelRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 111: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 138, // 112: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse.grants:type_name -> hyapp.activity.v1.RegistrationLevelBadgeGrant + 0, // 113: hyapp.activity.v1.UpsertLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 119, // 114: hyapp.activity.v1.UpsertLevelTrackResponse.track:type_name -> hyapp.activity.v1.LevelTrack + 0, // 115: hyapp.activity.v1.UpsertLevelRuleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 120, // 116: hyapp.activity.v1.UpsertLevelRuleResponse.rule:type_name -> hyapp.activity.v1.LevelRule + 0, // 117: hyapp.activity.v1.UpsertLevelTierRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 121, // 118: hyapp.activity.v1.UpsertLevelTierResponse.tier:type_name -> hyapp.activity.v1.LevelTier + 0, // 119: hyapp.activity.v1.ListLevelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 119, // 120: hyapp.activity.v1.ListLevelConfigResponse.tracks:type_name -> hyapp.activity.v1.LevelTrack + 120, // 121: hyapp.activity.v1.ListLevelConfigResponse.rules:type_name -> hyapp.activity.v1.LevelRule + 121, // 122: hyapp.activity.v1.ListLevelConfigResponse.tiers:type_name -> hyapp.activity.v1.LevelTier + 149, // 123: hyapp.activity.v1.AchievementDefinition.conditions:type_name -> hyapp.activity.v1.AchievementCondition + 150, // 124: hyapp.activity.v1.UserAchievement.definition:type_name -> hyapp.activity.v1.AchievementDefinition + 0, // 125: hyapp.activity.v1.ListAchievementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 151, // 126: hyapp.activity.v1.ListAchievementsResponse.achievements:type_name -> hyapp.activity.v1.UserAchievement + 0, // 127: hyapp.activity.v1.ConsumeAchievementEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 128: hyapp.activity.v1.ListMyBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 156, // 129: hyapp.activity.v1.ListMyBadgesResponse.strip_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 156, // 130: hyapp.activity.v1.ListMyBadgesResponse.profile_tile_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 156, // 131: hyapp.activity.v1.ListMyBadgesResponse.honor_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 0, // 132: hyapp.activity.v1.SetBadgeDisplayRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 156, // 133: hyapp.activity.v1.SetBadgeDisplayRequest.items:type_name -> hyapp.activity.v1.BadgeDisplayItem + 158, // 134: hyapp.activity.v1.SetBadgeDisplayResponse.profile:type_name -> hyapp.activity.v1.ListMyBadgesResponse + 0, // 135: hyapp.activity.v1.UpsertAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 149, // 136: hyapp.activity.v1.UpsertAchievementDefinitionRequest.conditions:type_name -> hyapp.activity.v1.AchievementCondition + 150, // 137: hyapp.activity.v1.UpsertAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition + 0, // 138: hyapp.activity.v1.LuckyGiftMeta.meta:type_name -> hyapp.activity.v1.RequestMeta + 164, // 139: hyapp.activity.v1.LuckyGiftConfig.tiers:type_name -> hyapp.activity.v1.LuckyGiftTier + 166, // 140: hyapp.activity.v1.LuckyGiftRuleStage.tiers:type_name -> hyapp.activity.v1.LuckyGiftRuleTier + 167, // 141: hyapp.activity.v1.LuckyGiftRuleConfig.stages:type_name -> hyapp.activity.v1.LuckyGiftRuleStage + 0, // 142: hyapp.activity.v1.CheckLuckyGiftRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 163, // 143: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest.lucky_gift:type_name -> hyapp.activity.v1.LuckyGiftMeta + 171, // 144: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse.result:type_name -> hyapp.activity.v1.LuckyGiftDrawResult + 0, // 145: hyapp.activity.v1.GetLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 168, // 146: hyapp.activity.v1.GetLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 147: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 168, // 148: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 168, // 149: hyapp.activity.v1.UpsertLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 150: hyapp.activity.v1.ListLuckyGiftConfigsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 168, // 151: hyapp.activity.v1.ListLuckyGiftConfigsResponse.configs:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 152: hyapp.activity.v1.ListLuckyGiftDrawsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 171, // 153: hyapp.activity.v1.ListLuckyGiftDrawsResponse.draws:type_name -> hyapp.activity.v1.LuckyGiftDrawResult + 0, // 154: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 182, // 155: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.activity.v1.LuckyGiftDrawSummary + 185, // 156: hyapp.activity.v1.WeeklyStarCycle.gifts:type_name -> hyapp.activity.v1.WeeklyStarGift + 186, // 157: hyapp.activity.v1.WeeklyStarCycle.rewards:type_name -> hyapp.activity.v1.WeeklyStarReward + 0, // 158: hyapp.activity.v1.ListWeeklyStarCyclesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 187, // 159: hyapp.activity.v1.ListWeeklyStarCyclesResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 160: hyapp.activity.v1.GetWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 187, // 161: hyapp.activity.v1.GetWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 162: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 187, // 163: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 187, // 164: hyapp.activity.v1.UpsertWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 165: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 187, // 166: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 167: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 187, // 168: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 188, // 169: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 0, // 170: hyapp.activity.v1.ListWeeklyStarSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 189, // 171: hyapp.activity.v1.ListWeeklyStarSettlementsResponse.settlements:type_name -> hyapp.activity.v1.WeeklyStarSettlement + 0, // 172: hyapp.activity.v1.GetWeeklyStarCurrentRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 187, // 173: hyapp.activity.v1.GetWeeklyStarCurrentResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 188, // 174: hyapp.activity.v1.GetWeeklyStarCurrentResponse.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 188, // 175: hyapp.activity.v1.GetWeeklyStarCurrentResponse.my_entry:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 0, // 176: hyapp.activity.v1.ListWeeklyStarHistoryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 187, // 177: hyapp.activity.v1.WeeklyStarHistoryCycle.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 188, // 178: hyapp.activity.v1.WeeklyStarHistoryCycle.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 205, // 179: hyapp.activity.v1.ListWeeklyStarHistoryResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarHistoryCycle + 1, // 180: hyapp.activity.v1.ActivityService.PingActivity:input_type -> hyapp.activity.v1.PingActivityRequest + 3, // 181: hyapp.activity.v1.ActivityService.GetActivityStatus:input_type -> hyapp.activity.v1.GetActivityStatusRequest + 6, // 182: hyapp.activity.v1.MessageInboxService.ListMessageTabs:input_type -> hyapp.activity.v1.ListMessageTabsRequest + 9, // 183: hyapp.activity.v1.MessageInboxService.ListInboxMessages:input_type -> hyapp.activity.v1.ListInboxMessagesRequest + 11, // 184: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:input_type -> hyapp.activity.v1.MarkInboxMessageReadRequest + 13, // 185: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:input_type -> hyapp.activity.v1.MarkInboxSectionReadRequest + 15, // 186: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:input_type -> hyapp.activity.v1.DeleteInboxMessageRequest + 17, // 187: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:input_type -> hyapp.activity.v1.CreateInboxMessageRequest + 19, // 188: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:input_type -> hyapp.activity.v1.CreateFanoutJobRequest + 21, // 189: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 190: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 191: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 192: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 193: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 25, // 194: hyapp.activity.v1.TaskService.ListUserTasks:input_type -> hyapp.activity.v1.ListUserTasksRequest + 27, // 195: hyapp.activity.v1.TaskService.ClaimTaskReward:input_type -> hyapp.activity.v1.ClaimTaskRewardRequest + 29, // 196: hyapp.activity.v1.TaskService.ConsumeTaskEvent:input_type -> hyapp.activity.v1.ConsumeTaskEventRequest + 123, // 197: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:input_type -> hyapp.activity.v1.GetMyLevelOverviewRequest + 125, // 198: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:input_type -> hyapp.activity.v1.GetLevelTrackRequest + 129, // 199: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:input_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest + 132, // 200: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:input_type -> hyapp.activity.v1.ListLevelRewardsRequest + 134, // 201: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:input_type -> hyapp.activity.v1.ConsumeLevelEventRequest + 136, // 202: hyapp.activity.v1.GrowthLevelService.SetUserLevel:input_type -> hyapp.activity.v1.SetUserLevelRequest + 139, // 203: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:input_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesRequest + 152, // 204: hyapp.activity.v1.AchievementService.ListAchievements:input_type -> hyapp.activity.v1.ListAchievementsRequest + 154, // 205: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:input_type -> hyapp.activity.v1.ConsumeAchievementEventRequest + 157, // 206: hyapp.activity.v1.AchievementService.ListMyBadges:input_type -> hyapp.activity.v1.ListMyBadgesRequest + 159, // 207: hyapp.activity.v1.AchievementService.SetBadgeDisplay:input_type -> hyapp.activity.v1.SetBadgeDisplayRequest + 169, // 208: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.activity.v1.CheckLuckyGiftRequest + 172, // 209: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawRequest + 95, // 210: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest + 202, // 211: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:input_type -> hyapp.activity.v1.GetWeeklyStarCurrentRequest + 198, // 212: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest + 204, // 213: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:input_type -> hyapp.activity.v1.ListWeeklyStarHistoryRequest + 32, // 214: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:input_type -> hyapp.activity.v1.EnsureBroadcastGroupsRequest + 34, // 215: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:input_type -> hyapp.activity.v1.PublishRegionBroadcastRequest + 35, // 216: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:input_type -> hyapp.activity.v1.PublishGlobalBroadcastRequest + 37, // 217: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:input_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberRequest + 21, // 218: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 40, // 219: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:input_type -> hyapp.activity.v1.ConsumeRoomEventRequest + 43, // 220: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:input_type -> hyapp.activity.v1.ListTaskDefinitionsRequest + 45, // 221: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:input_type -> hyapp.activity.v1.UpsertTaskDefinitionRequest + 47, // 222: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:input_type -> hyapp.activity.v1.SetTaskDefinitionStatusRequest + 52, // 223: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:input_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityRequest + 54, // 224: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:input_type -> hyapp.activity.v1.IssueRegistrationRewardRequest + 56, // 225: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:input_type -> hyapp.activity.v1.GetRegistrationRewardConfigRequest + 58, // 226: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:input_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigRequest + 60, // 227: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:input_type -> hyapp.activity.v1.ListRegistrationRewardClaimsRequest + 66, // 228: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:input_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusRequest + 68, // 229: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:input_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardRequest + 70, // 230: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigRequest + 72, // 231: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest + 74, // 232: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:input_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest + 81, // 233: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest + 83, // 234: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:input_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest + 85, // 235: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest + 87, // 236: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest + 89, // 237: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:input_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest + 97, // 238: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest + 99, // 239: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest + 101, // 240: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:input_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest + 103, // 241: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:input_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest + 190, // 242: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:input_type -> hyapp.activity.v1.ListWeeklyStarCyclesRequest + 194, // 243: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest + 192, // 244: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:input_type -> hyapp.activity.v1.GetWeeklyStarCycleRequest + 194, // 245: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest + 196, // 246: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:input_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusRequest + 198, // 247: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest + 200, // 248: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:input_type -> hyapp.activity.v1.ListWeeklyStarSettlementsRequest + 108, // 249: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:input_type -> hyapp.activity.v1.GetSevenDayCheckInStatusRequest + 110, // 250: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:input_type -> hyapp.activity.v1.SignSevenDayCheckInRequest + 112, // 251: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:input_type -> hyapp.activity.v1.GetSevenDayCheckInConfigRequest + 114, // 252: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:input_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest + 117, // 253: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:input_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsRequest + 147, // 254: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:input_type -> hyapp.activity.v1.ListLevelConfigRequest + 141, // 255: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:input_type -> hyapp.activity.v1.UpsertLevelTrackRequest + 143, // 256: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:input_type -> hyapp.activity.v1.UpsertLevelRuleRequest + 145, // 257: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:input_type -> hyapp.activity.v1.UpsertLevelTierRequest + 152, // 258: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:input_type -> hyapp.activity.v1.ListAchievementsRequest + 161, // 259: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:input_type -> hyapp.activity.v1.UpsertAchievementDefinitionRequest + 174, // 260: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.activity.v1.GetLuckyGiftConfigRequest + 176, // 261: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.activity.v1.UpsertLuckyGiftConfigRequest + 178, // 262: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.activity.v1.ListLuckyGiftConfigsRequest + 180, // 263: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.activity.v1.ListLuckyGiftDrawsRequest + 183, // 264: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest + 2, // 265: hyapp.activity.v1.ActivityService.PingActivity:output_type -> hyapp.activity.v1.PingActivityResponse + 4, // 266: hyapp.activity.v1.ActivityService.GetActivityStatus:output_type -> hyapp.activity.v1.GetActivityStatusResponse + 7, // 267: hyapp.activity.v1.MessageInboxService.ListMessageTabs:output_type -> hyapp.activity.v1.ListMessageTabsResponse + 10, // 268: hyapp.activity.v1.MessageInboxService.ListInboxMessages:output_type -> hyapp.activity.v1.ListInboxMessagesResponse + 12, // 269: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:output_type -> hyapp.activity.v1.MarkInboxMessageReadResponse + 14, // 270: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:output_type -> hyapp.activity.v1.MarkInboxSectionReadResponse + 16, // 271: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:output_type -> hyapp.activity.v1.DeleteInboxMessageResponse + 18, // 272: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:output_type -> hyapp.activity.v1.CreateInboxMessageResponse + 20, // 273: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:output_type -> hyapp.activity.v1.CreateFanoutJobResponse + 22, // 274: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 275: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 276: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 277: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 278: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 26, // 279: hyapp.activity.v1.TaskService.ListUserTasks:output_type -> hyapp.activity.v1.ListUserTasksResponse + 28, // 280: hyapp.activity.v1.TaskService.ClaimTaskReward:output_type -> hyapp.activity.v1.ClaimTaskRewardResponse + 30, // 281: hyapp.activity.v1.TaskService.ConsumeTaskEvent:output_type -> hyapp.activity.v1.ConsumeTaskEventResponse + 124, // 282: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:output_type -> hyapp.activity.v1.GetMyLevelOverviewResponse + 126, // 283: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:output_type -> hyapp.activity.v1.GetLevelTrackResponse + 130, // 284: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:output_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse + 133, // 285: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:output_type -> hyapp.activity.v1.ListLevelRewardsResponse + 135, // 286: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:output_type -> hyapp.activity.v1.ConsumeLevelEventResponse + 137, // 287: hyapp.activity.v1.GrowthLevelService.SetUserLevel:output_type -> hyapp.activity.v1.SetUserLevelResponse + 140, // 288: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:output_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesResponse + 153, // 289: hyapp.activity.v1.AchievementService.ListAchievements:output_type -> hyapp.activity.v1.ListAchievementsResponse + 155, // 290: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:output_type -> hyapp.activity.v1.ConsumeAchievementEventResponse + 158, // 291: hyapp.activity.v1.AchievementService.ListMyBadges:output_type -> hyapp.activity.v1.ListMyBadgesResponse + 160, // 292: hyapp.activity.v1.AchievementService.SetBadgeDisplay:output_type -> hyapp.activity.v1.SetBadgeDisplayResponse + 170, // 293: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.activity.v1.CheckLuckyGiftResponse + 173, // 294: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawResponse + 96, // 295: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse + 203, // 296: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:output_type -> hyapp.activity.v1.GetWeeklyStarCurrentResponse + 199, // 297: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse + 206, // 298: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:output_type -> hyapp.activity.v1.ListWeeklyStarHistoryResponse + 33, // 299: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:output_type -> hyapp.activity.v1.EnsureBroadcastGroupsResponse + 36, // 300: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse + 36, // 301: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse + 38, // 302: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:output_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberResponse + 39, // 303: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:output_type -> hyapp.activity.v1.ProcessBroadcastOutboxBatchResponse + 41, // 304: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:output_type -> hyapp.activity.v1.ConsumeRoomEventResponse + 44, // 305: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:output_type -> hyapp.activity.v1.ListTaskDefinitionsResponse + 46, // 306: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:output_type -> hyapp.activity.v1.UpsertTaskDefinitionResponse + 48, // 307: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:output_type -> hyapp.activity.v1.SetTaskDefinitionStatusResponse + 53, // 308: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:output_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityResponse + 55, // 309: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:output_type -> hyapp.activity.v1.IssueRegistrationRewardResponse + 57, // 310: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:output_type -> hyapp.activity.v1.GetRegistrationRewardConfigResponse + 59, // 311: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:output_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigResponse + 61, // 312: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:output_type -> hyapp.activity.v1.ListRegistrationRewardClaimsResponse + 67, // 313: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:output_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusResponse + 69, // 314: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:output_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardResponse + 71, // 315: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigResponse + 73, // 316: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse + 75, // 317: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:output_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse + 82, // 318: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse + 84, // 319: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:output_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse + 86, // 320: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse + 88, // 321: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse + 90, // 322: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:output_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse + 98, // 323: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse + 100, // 324: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse + 102, // 325: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:output_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse + 104, // 326: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:output_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse + 191, // 327: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:output_type -> hyapp.activity.v1.ListWeeklyStarCyclesResponse + 195, // 328: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse + 193, // 329: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:output_type -> hyapp.activity.v1.GetWeeklyStarCycleResponse + 195, // 330: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse + 197, // 331: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:output_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusResponse + 199, // 332: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse + 201, // 333: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:output_type -> hyapp.activity.v1.ListWeeklyStarSettlementsResponse + 109, // 334: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:output_type -> hyapp.activity.v1.GetSevenDayCheckInStatusResponse + 111, // 335: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:output_type -> hyapp.activity.v1.SignSevenDayCheckInResponse + 113, // 336: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:output_type -> hyapp.activity.v1.GetSevenDayCheckInConfigResponse + 115, // 337: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:output_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse + 118, // 338: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:output_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsResponse + 148, // 339: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:output_type -> hyapp.activity.v1.ListLevelConfigResponse + 142, // 340: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:output_type -> hyapp.activity.v1.UpsertLevelTrackResponse + 144, // 341: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:output_type -> hyapp.activity.v1.UpsertLevelRuleResponse + 146, // 342: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:output_type -> hyapp.activity.v1.UpsertLevelTierResponse + 153, // 343: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:output_type -> hyapp.activity.v1.ListAchievementsResponse + 162, // 344: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:output_type -> hyapp.activity.v1.UpsertAchievementDefinitionResponse + 175, // 345: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.activity.v1.GetLuckyGiftConfigResponse + 177, // 346: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.activity.v1.UpsertLuckyGiftConfigResponse + 179, // 347: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.activity.v1.ListLuckyGiftConfigsResponse + 181, // 348: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.activity.v1.ListLuckyGiftDrawsResponse + 184, // 349: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse + 265, // [265:350] is the sub-list for method output_type + 180, // [180:265] is the sub-list for method input_type + 180, // [180:180] is the sub-list for extension type_name + 180, // [180:180] is the sub-list for extension extendee + 0, // [0:180] is the sub-list for field type_name } func init() { file_proto_activity_v1_activity_proto_init() } diff --git a/api/proto/activity/v1/activity.proto b/api/proto/activity/v1/activity.proto index b5bc10f6..820b2d61 100644 --- a/api/proto/activity/v1/activity.proto +++ b/api/proto/activity/v1/activity.proto @@ -534,7 +534,7 @@ message ListRegistrationRewardClaimsResponse { int64 today_claimed_count = 3; } -// FirstRechargeRewardTier 是首冲奖励的一个金币充值档位;max_coin_amount=0 表示无上限。 +// FirstRechargeRewardTier 是首冲奖励的一个 Google 商品档位;旧金币区间字段仅保留兼容。 message FirstRechargeRewardTier { int64 tier_id = 1; string tier_code = 2; @@ -546,6 +546,9 @@ message FirstRechargeRewardTier { int32 sort_order = 8; int64 created_at_ms = 9; int64 updated_at_ms = 10; + int64 usd_minor_amount = 11; + string google_product_id = 12; + int32 discount_percent = 13; } // FirstRechargeRewardConfig 是当前 App 的首冲奖励配置;档位内容在 activity-service 内保持版本一致。 @@ -558,7 +561,7 @@ message FirstRechargeRewardConfig { int64 updated_at_ms = 6; } -// FirstRechargeRewardClaim 是某个用户首笔充值触发后的发奖事实;user_id 唯一保证一生只处理一次。 +// FirstRechargeRewardClaim 是某个用户购买某个首充档位后的发奖事实;同一用户同一档位只处理一次。 message FirstRechargeRewardClaim { string claim_id = 1; string event_id = 2; @@ -579,6 +582,9 @@ message FirstRechargeRewardClaim { int64 granted_at_ms = 17; int64 created_at_ms = 18; int64 updated_at_ms = 19; + int64 tier_usd_minor_amount = 20; + string google_product_id = 21; + int64 recharge_usd_minor = 22; } // FirstRechargeRewardStatus 是 App 首冲奖励面板可直接展示的当前状态。 @@ -588,6 +594,7 @@ message FirstRechargeRewardStatus { bool rewarded = 3; FirstRechargeRewardClaim claim = 4; int64 server_time_ms = 5; + repeated FirstRechargeRewardClaim claims = 6; } message GetFirstRechargeRewardStatusRequest { @@ -599,7 +606,7 @@ message GetFirstRechargeRewardStatusResponse { FirstRechargeRewardStatus status = 1; } -// ConsumeFirstRechargeRewardRequest 是 wallet 充值事实消费入口;只处理 recharge_sequence=1 的成功充值。 +// ConsumeFirstRechargeRewardRequest 是 wallet 充值事实消费入口;Google 商品 ID 命中档位后发奖。 message ConsumeFirstRechargeRewardRequest { RequestMeta meta = 1; string event_id = 2; @@ -610,6 +617,8 @@ message ConsumeFirstRechargeRewardRequest { int64 recharge_sequence = 7; string recharge_type = 8; int64 occurred_at_ms = 9; + int64 recharge_usd_minor = 10; + string google_product_id = 11; } message ConsumeFirstRechargeRewardResponse { diff --git a/api/proto/events/room/v1/events.pb.go b/api/proto/events/room/v1/events.pb.go index b901a3c3..7c062d7e 100644 --- a/api/proto/events/room/v1/events.pb.go +++ b/api/proto/events/room/v1/events.pb.go @@ -1208,6 +1208,7 @@ type RoomGiftSent struct { GiftIconUrl string `protobuf:"bytes,16,opt,name=gift_icon_url,json=giftIconUrl,proto3" json:"gift_icon_url,omitempty"` GiftAnimationUrl string `protobuf:"bytes,17,opt,name=gift_animation_url,json=giftAnimationUrl,proto3" json:"gift_animation_url,omitempty"` TargetGiftValue int64 `protobuf:"varint,18,opt,name=target_gift_value,json=targetGiftValue,proto3" json:"target_gift_value,omitempty"` + GiftEffectTypes []string `protobuf:"bytes,19,rep,name=gift_effect_types,json=giftEffectTypes,proto3" json:"gift_effect_types,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1368,6 +1369,13 @@ func (x *RoomGiftSent) GetTargetGiftValue() int64 { return 0 } +func (x *RoomGiftSent) GetGiftEffectTypes() []string { + if x != nil { + return x.GiftEffectTypes + } + return nil +} + // RoomHeatChanged 表达热度变化结果。 type RoomHeatChanged struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2152,7 +2160,7 @@ const file_proto_events_room_v1_events_proto_rawDesc = "" + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\\\n" + "\x10RoomUserUnbanned\x12\"\n" + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12$\n" + - "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\x89\x05\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\xb5\x05\n" + "\fRoomGiftSent\x12$\n" + "\x0esender_user_id\x18\x01 \x01(\x03R\fsenderUserId\x12$\n" + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x17\n" + @@ -2177,7 +2185,8 @@ const file_proto_events_room_v1_events_proto_rawDesc = "" + "\tgift_name\x18\x0f \x01(\tR\bgiftName\x12\"\n" + "\rgift_icon_url\x18\x10 \x01(\tR\vgiftIconUrl\x12,\n" + "\x12gift_animation_url\x18\x11 \x01(\tR\x10giftAnimationUrl\x12*\n" + - "\x11target_gift_value\x18\x12 \x01(\x03R\x0ftargetGiftValue\"J\n" + + "\x11target_gift_value\x18\x12 \x01(\x03R\x0ftargetGiftValue\x12*\n" + + "\x11gift_effect_types\x18\x13 \x03(\tR\x0fgiftEffectTypes\"J\n" + "\x0fRoomHeatChanged\x12\x14\n" + "\x05delta\x18\x01 \x01(\x03R\x05delta\x12!\n" + "\fcurrent_heat\x18\x02 \x01(\x03R\vcurrentHeat\"_\n" + diff --git a/api/proto/events/room/v1/events.proto b/api/proto/events/room/v1/events.proto index ae64da5f..f0519de2 100644 --- a/api/proto/events/room/v1/events.proto +++ b/api/proto/events/room/v1/events.proto @@ -165,6 +165,7 @@ message RoomGiftSent { string gift_icon_url = 16; string gift_animation_url = 17; int64 target_gift_value = 18; + repeated string gift_effect_types = 19; } // RoomHeatChanged 表达热度变化结果。 diff --git a/api/proto/game/v1/game.pb.go b/api/proto/game/v1/game.pb.go index 7ca62445..bdf7d029 100644 --- a/api/proto/game/v1/game.pb.go +++ b/api/proto/game/v1/game.pb.go @@ -1145,6 +1145,1162 @@ func (x *LaunchGameResponse) GetSafeHeight() int32 { return 0 } +type DiceParticipant struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + StakeCoin int64 `protobuf:"varint,4,opt,name=stake_coin,json=stakeCoin,proto3" json:"stake_coin,omitempty"` + DicePoints []int32 `protobuf:"varint,5,rep,packed,name=dice_points,json=dicePoints,proto3" json:"dice_points,omitempty"` + Result string `protobuf:"bytes,6,opt,name=result,proto3" json:"result,omitempty"` + PayoutCoin int64 `protobuf:"varint,7,opt,name=payout_coin,json=payoutCoin,proto3" json:"payout_coin,omitempty"` + BalanceAfter int64 `protobuf:"varint,8,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` + JoinedAtMs int64 `protobuf:"varint,9,opt,name=joined_at_ms,json=joinedAtMs,proto3" json:"joined_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,10,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + ParticipantType string `protobuf:"bytes,11,opt,name=participant_type,json=participantType,proto3" json:"participant_type,omitempty"` + IsRobot bool `protobuf:"varint,12,opt,name=is_robot,json=isRobot,proto3" json:"is_robot,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiceParticipant) Reset() { + *x = DiceParticipant{} + mi := &file_proto_game_v1_game_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiceParticipant) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiceParticipant) ProtoMessage() {} + +func (x *DiceParticipant) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiceParticipant.ProtoReflect.Descriptor instead. +func (*DiceParticipant) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{12} +} + +func (x *DiceParticipant) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *DiceParticipant) GetSeatNo() int32 { + if x != nil { + return x.SeatNo + } + return 0 +} + +func (x *DiceParticipant) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *DiceParticipant) GetStakeCoin() int64 { + if x != nil { + return x.StakeCoin + } + return 0 +} + +func (x *DiceParticipant) GetDicePoints() []int32 { + if x != nil { + return x.DicePoints + } + return nil +} + +func (x *DiceParticipant) GetResult() string { + if x != nil { + return x.Result + } + return "" +} + +func (x *DiceParticipant) GetPayoutCoin() int64 { + if x != nil { + return x.PayoutCoin + } + return 0 +} + +func (x *DiceParticipant) GetBalanceAfter() int64 { + if x != nil { + return x.BalanceAfter + } + return 0 +} + +func (x *DiceParticipant) GetJoinedAtMs() int64 { + if x != nil { + return x.JoinedAtMs + } + return 0 +} + +func (x *DiceParticipant) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +func (x *DiceParticipant) GetParticipantType() string { + if x != nil { + return x.ParticipantType + } + return "" +} + +func (x *DiceParticipant) GetIsRobot() bool { + if x != nil { + return x.IsRobot + } + return false +} + +type DiceMatch struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + MatchId string `protobuf:"bytes,2,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + RoomId string `protobuf:"bytes,4,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + RegionId int64 `protobuf:"varint,5,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + MinPlayers int32 `protobuf:"varint,6,opt,name=min_players,json=minPlayers,proto3" json:"min_players,omitempty"` + MaxPlayers int32 `protobuf:"varint,7,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` + CurrentPlayers int32 `protobuf:"varint,8,opt,name=current_players,json=currentPlayers,proto3" json:"current_players,omitempty"` + StakeCoin int64 `protobuf:"varint,9,opt,name=stake_coin,json=stakeCoin,proto3" json:"stake_coin,omitempty"` + RoundNo int32 `protobuf:"varint,10,opt,name=round_no,json=roundNo,proto3" json:"round_no,omitempty"` + Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` + Result string `protobuf:"bytes,12,opt,name=result,proto3" json:"result,omitempty"` + Participants []*DiceParticipant `protobuf:"bytes,13,rep,name=participants,proto3" json:"participants,omitempty"` + JoinDeadlineMs int64 `protobuf:"varint,14,opt,name=join_deadline_ms,json=joinDeadlineMs,proto3" json:"join_deadline_ms,omitempty"` + CreatedAtMs int64 `protobuf:"varint,15,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,16,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + SettledAtMs int64 `protobuf:"varint,17,opt,name=settled_at_ms,json=settledAtMs,proto3" json:"settled_at_ms,omitempty"` + Phase string `protobuf:"bytes,18,opt,name=phase,proto3" json:"phase,omitempty"` + PhaseDeadlineMs int64 `protobuf:"varint,19,opt,name=phase_deadline_ms,json=phaseDeadlineMs,proto3" json:"phase_deadline_ms,omitempty"` + FeeBps int32 `protobuf:"varint,20,opt,name=fee_bps,json=feeBps,proto3" json:"fee_bps,omitempty"` + PoolBps int32 `protobuf:"varint,21,opt,name=pool_bps,json=poolBps,proto3" json:"pool_bps,omitempty"` + MatchMode string `protobuf:"bytes,22,opt,name=match_mode,json=matchMode,proto3" json:"match_mode,omitempty"` + ForcedResult string `protobuf:"bytes,23,opt,name=forced_result,json=forcedResult,proto3" json:"forced_result,omitempty"` + ReadyAtMs int64 `protobuf:"varint,24,opt,name=ready_at_ms,json=readyAtMs,proto3" json:"ready_at_ms,omitempty"` + CanceledAtMs int64 `protobuf:"varint,25,opt,name=canceled_at_ms,json=canceledAtMs,proto3" json:"canceled_at_ms,omitempty"` + PoolDeltaCoin int64 `protobuf:"varint,26,opt,name=pool_delta_coin,json=poolDeltaCoin,proto3" json:"pool_delta_coin,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiceMatch) Reset() { + *x = DiceMatch{} + mi := &file_proto_game_v1_game_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiceMatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiceMatch) ProtoMessage() {} + +func (x *DiceMatch) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiceMatch.ProtoReflect.Descriptor instead. +func (*DiceMatch) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{13} +} + +func (x *DiceMatch) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *DiceMatch) GetMatchId() string { + if x != nil { + return x.MatchId + } + return "" +} + +func (x *DiceMatch) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *DiceMatch) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *DiceMatch) GetRegionId() int64 { + if x != nil { + return x.RegionId + } + return 0 +} + +func (x *DiceMatch) GetMinPlayers() int32 { + if x != nil { + return x.MinPlayers + } + return 0 +} + +func (x *DiceMatch) GetMaxPlayers() int32 { + if x != nil { + return x.MaxPlayers + } + return 0 +} + +func (x *DiceMatch) GetCurrentPlayers() int32 { + if x != nil { + return x.CurrentPlayers + } + return 0 +} + +func (x *DiceMatch) GetStakeCoin() int64 { + if x != nil { + return x.StakeCoin + } + return 0 +} + +func (x *DiceMatch) GetRoundNo() int32 { + if x != nil { + return x.RoundNo + } + return 0 +} + +func (x *DiceMatch) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *DiceMatch) GetResult() string { + if x != nil { + return x.Result + } + return "" +} + +func (x *DiceMatch) GetParticipants() []*DiceParticipant { + if x != nil { + return x.Participants + } + return nil +} + +func (x *DiceMatch) GetJoinDeadlineMs() int64 { + if x != nil { + return x.JoinDeadlineMs + } + return 0 +} + +func (x *DiceMatch) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *DiceMatch) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +func (x *DiceMatch) GetSettledAtMs() int64 { + if x != nil { + return x.SettledAtMs + } + return 0 +} + +func (x *DiceMatch) GetPhase() string { + if x != nil { + return x.Phase + } + return "" +} + +func (x *DiceMatch) GetPhaseDeadlineMs() int64 { + if x != nil { + return x.PhaseDeadlineMs + } + return 0 +} + +func (x *DiceMatch) GetFeeBps() int32 { + if x != nil { + return x.FeeBps + } + return 0 +} + +func (x *DiceMatch) GetPoolBps() int32 { + if x != nil { + return x.PoolBps + } + return 0 +} + +func (x *DiceMatch) GetMatchMode() string { + if x != nil { + return x.MatchMode + } + return "" +} + +func (x *DiceMatch) GetForcedResult() string { + if x != nil { + return x.ForcedResult + } + return "" +} + +func (x *DiceMatch) GetReadyAtMs() int64 { + if x != nil { + return x.ReadyAtMs + } + return 0 +} + +func (x *DiceMatch) GetCanceledAtMs() int64 { + if x != nil { + return x.CanceledAtMs + } + return 0 +} + +func (x *DiceMatch) GetPoolDeltaCoin() int64 { + if x != nil { + return x.PoolDeltaCoin + } + return 0 +} + +type DiceStakeOption struct { + state protoimpl.MessageState `protogen:"open.v1"` + StakeCoin int64 `protobuf:"varint,1,opt,name=stake_coin,json=stakeCoin,proto3" json:"stake_coin,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + SortOrder int32 `protobuf:"varint,3,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiceStakeOption) Reset() { + *x = DiceStakeOption{} + mi := &file_proto_game_v1_game_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiceStakeOption) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiceStakeOption) ProtoMessage() {} + +func (x *DiceStakeOption) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiceStakeOption.ProtoReflect.Descriptor instead. +func (*DiceStakeOption) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{14} +} + +func (x *DiceStakeOption) GetStakeCoin() int64 { + if x != nil { + return x.StakeCoin + } + return 0 +} + +func (x *DiceStakeOption) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *DiceStakeOption) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +type DiceConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,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"` + StakeOptions []*DiceStakeOption `protobuf:"bytes,4,rep,name=stake_options,json=stakeOptions,proto3" json:"stake_options,omitempty"` + FeeBps int32 `protobuf:"varint,5,opt,name=fee_bps,json=feeBps,proto3" json:"fee_bps,omitempty"` + PoolBps int32 `protobuf:"varint,6,opt,name=pool_bps,json=poolBps,proto3" json:"pool_bps,omitempty"` + MinPlayers int32 `protobuf:"varint,7,opt,name=min_players,json=minPlayers,proto3" json:"min_players,omitempty"` + MaxPlayers int32 `protobuf:"varint,8,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` + RobotEnabled bool `protobuf:"varint,9,opt,name=robot_enabled,json=robotEnabled,proto3" json:"robot_enabled,omitempty"` + RobotMatchWaitMs int64 `protobuf:"varint,10,opt,name=robot_match_wait_ms,json=robotMatchWaitMs,proto3" json:"robot_match_wait_ms,omitempty"` + PoolBalanceCoin int64 `protobuf:"varint,11,opt,name=pool_balance_coin,json=poolBalanceCoin,proto3" json:"pool_balance_coin,omitempty"` + CreatedAtMs int64 `protobuf:"varint,12,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,13,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiceConfig) Reset() { + *x = DiceConfig{} + mi := &file_proto_game_v1_game_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiceConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiceConfig) ProtoMessage() {} + +func (x *DiceConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiceConfig.ProtoReflect.Descriptor instead. +func (*DiceConfig) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{15} +} + +func (x *DiceConfig) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *DiceConfig) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *DiceConfig) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *DiceConfig) GetStakeOptions() []*DiceStakeOption { + if x != nil { + return x.StakeOptions + } + return nil +} + +func (x *DiceConfig) GetFeeBps() int32 { + if x != nil { + return x.FeeBps + } + return 0 +} + +func (x *DiceConfig) GetPoolBps() int32 { + if x != nil { + return x.PoolBps + } + return 0 +} + +func (x *DiceConfig) GetMinPlayers() int32 { + if x != nil { + return x.MinPlayers + } + return 0 +} + +func (x *DiceConfig) GetMaxPlayers() int32 { + if x != nil { + return x.MaxPlayers + } + return 0 +} + +func (x *DiceConfig) GetRobotEnabled() bool { + if x != nil { + return x.RobotEnabled + } + return false +} + +func (x *DiceConfig) GetRobotMatchWaitMs() int64 { + if x != nil { + return x.RobotMatchWaitMs + } + return 0 +} + +func (x *DiceConfig) GetPoolBalanceCoin() int64 { + if x != nil { + return x.PoolBalanceCoin + } + return 0 +} + +func (x *DiceConfig) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *DiceConfig) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type GetDiceConfigRequest 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"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDiceConfigRequest) Reset() { + *x = GetDiceConfigRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDiceConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDiceConfigRequest) ProtoMessage() {} + +func (x *GetDiceConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDiceConfigRequest.ProtoReflect.Descriptor instead. +func (*GetDiceConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{16} +} + +func (x *GetDiceConfigRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *GetDiceConfigRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +type DiceConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Config *DiceConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiceConfigResponse) Reset() { + *x = DiceConfigResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiceConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiceConfigResponse) ProtoMessage() {} + +func (x *DiceConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiceConfigResponse.ProtoReflect.Descriptor instead. +func (*DiceConfigResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{17} +} + +func (x *DiceConfigResponse) GetConfig() *DiceConfig { + if x != nil { + return x.Config + } + return nil +} + +func (x *DiceConfigResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type CreateDiceMatchRequest 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"` + GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + RoomId string `protobuf:"bytes,4,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + RegionId int64 `protobuf:"varint,5,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + StakeCoin int64 `protobuf:"varint,6,opt,name=stake_coin,json=stakeCoin,proto3" json:"stake_coin,omitempty"` + MinPlayers int32 `protobuf:"varint,7,opt,name=min_players,json=minPlayers,proto3" json:"min_players,omitempty"` + MaxPlayers int32 `protobuf:"varint,8,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateDiceMatchRequest) Reset() { + *x = CreateDiceMatchRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateDiceMatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateDiceMatchRequest) ProtoMessage() {} + +func (x *CreateDiceMatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateDiceMatchRequest.ProtoReflect.Descriptor instead. +func (*CreateDiceMatchRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{18} +} + +func (x *CreateDiceMatchRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *CreateDiceMatchRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *CreateDiceMatchRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *CreateDiceMatchRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *CreateDiceMatchRequest) GetRegionId() int64 { + if x != nil { + return x.RegionId + } + return 0 +} + +func (x *CreateDiceMatchRequest) GetStakeCoin() int64 { + if x != nil { + return x.StakeCoin + } + return 0 +} + +func (x *CreateDiceMatchRequest) GetMinPlayers() int32 { + if x != nil { + return x.MinPlayers + } + return 0 +} + +func (x *CreateDiceMatchRequest) GetMaxPlayers() int32 { + if x != nil { + return x.MaxPlayers + } + return 0 +} + +type JoinDiceMatchRequest 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"` + MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *JoinDiceMatchRequest) Reset() { + *x = JoinDiceMatchRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JoinDiceMatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JoinDiceMatchRequest) ProtoMessage() {} + +func (x *JoinDiceMatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JoinDiceMatchRequest.ProtoReflect.Descriptor instead. +func (*JoinDiceMatchRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{19} +} + +func (x *JoinDiceMatchRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *JoinDiceMatchRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *JoinDiceMatchRequest) GetMatchId() string { + if x != nil { + return x.MatchId + } + return "" +} + +type GetDiceMatchRequest 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"` + MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDiceMatchRequest) Reset() { + *x = GetDiceMatchRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDiceMatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDiceMatchRequest) ProtoMessage() {} + +func (x *GetDiceMatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDiceMatchRequest.ProtoReflect.Descriptor instead. +func (*GetDiceMatchRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{20} +} + +func (x *GetDiceMatchRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *GetDiceMatchRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *GetDiceMatchRequest) GetMatchId() string { + if x != nil { + return x.MatchId + } + return "" +} + +type RollDiceMatchRequest 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"` + MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RollDiceMatchRequest) Reset() { + *x = RollDiceMatchRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RollDiceMatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RollDiceMatchRequest) ProtoMessage() {} + +func (x *RollDiceMatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RollDiceMatchRequest.ProtoReflect.Descriptor instead. +func (*RollDiceMatchRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{21} +} + +func (x *RollDiceMatchRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *RollDiceMatchRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *RollDiceMatchRequest) GetMatchId() string { + if x != nil { + return x.MatchId + } + return "" +} + +type MatchDiceRequest 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"` + GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + RoomId string `protobuf:"bytes,4,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + RegionId int64 `protobuf:"varint,5,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + StakeCoin int64 `protobuf:"varint,6,opt,name=stake_coin,json=stakeCoin,proto3" json:"stake_coin,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MatchDiceRequest) Reset() { + *x = MatchDiceRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MatchDiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MatchDiceRequest) ProtoMessage() {} + +func (x *MatchDiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MatchDiceRequest.ProtoReflect.Descriptor instead. +func (*MatchDiceRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{22} +} + +func (x *MatchDiceRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *MatchDiceRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *MatchDiceRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *MatchDiceRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *MatchDiceRequest) GetRegionId() int64 { + if x != nil { + return x.RegionId + } + return 0 +} + +func (x *MatchDiceRequest) GetStakeCoin() int64 { + if x != nil { + return x.StakeCoin + } + return 0 +} + +type CancelDiceMatchRequest 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"` + MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CancelDiceMatchRequest) Reset() { + *x = CancelDiceMatchRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CancelDiceMatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelDiceMatchRequest) ProtoMessage() {} + +func (x *CancelDiceMatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelDiceMatchRequest.ProtoReflect.Descriptor instead. +func (*CancelDiceMatchRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{23} +} + +func (x *CancelDiceMatchRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *CancelDiceMatchRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *CancelDiceMatchRequest) GetMatchId() string { + if x != nil { + return x.MatchId + } + return "" +} + +type DiceMatchResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Match *DiceMatch `protobuf:"bytes,1,opt,name=match,proto3" json:"match,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiceMatchResponse) Reset() { + *x = DiceMatchResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiceMatchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiceMatchResponse) ProtoMessage() {} + +func (x *DiceMatchResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiceMatchResponse.ProtoReflect.Descriptor instead. +func (*DiceMatchResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{24} +} + +func (x *DiceMatchResponse) GetMatch() *DiceMatch { + if x != nil { + return x.Match + } + return nil +} + +func (x *DiceMatchResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + type CallbackRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -1160,7 +2316,7 @@ type CallbackRequest struct { func (x *CallbackRequest) Reset() { *x = CallbackRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[12] + mi := &file_proto_game_v1_game_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1172,7 +2328,7 @@ func (x *CallbackRequest) String() string { func (*CallbackRequest) ProtoMessage() {} func (x *CallbackRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[12] + mi := &file_proto_game_v1_game_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1185,7 +2341,7 @@ func (x *CallbackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackRequest.ProtoReflect.Descriptor instead. func (*CallbackRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{12} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{25} } func (x *CallbackRequest) GetMeta() *RequestMeta { @@ -1247,7 +2403,7 @@ type CallbackResponse struct { func (x *CallbackResponse) Reset() { *x = CallbackResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[13] + mi := &file_proto_game_v1_game_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1259,7 +2415,7 @@ func (x *CallbackResponse) String() string { func (*CallbackResponse) ProtoMessage() {} func (x *CallbackResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[13] + mi := &file_proto_game_v1_game_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1272,7 +2428,7 @@ func (x *CallbackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackResponse.ProtoReflect.Descriptor instead. func (*CallbackResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{13} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{26} } func (x *CallbackResponse) GetRawBody() []byte { @@ -1302,7 +2458,7 @@ type CronBatchRequest struct { func (x *CronBatchRequest) Reset() { *x = CronBatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[14] + mi := &file_proto_game_v1_game_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1314,7 +2470,7 @@ func (x *CronBatchRequest) String() string { func (*CronBatchRequest) ProtoMessage() {} func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[14] + mi := &file_proto_game_v1_game_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1327,7 +2483,7 @@ func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchRequest.ProtoReflect.Descriptor instead. func (*CronBatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{14} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{27} } func (x *CronBatchRequest) GetMeta() *RequestMeta { @@ -1378,7 +2534,7 @@ type CronBatchResponse struct { func (x *CronBatchResponse) Reset() { *x = CronBatchResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[15] + mi := &file_proto_game_v1_game_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1390,7 +2546,7 @@ func (x *CronBatchResponse) String() string { func (*CronBatchResponse) ProtoMessage() {} func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[15] + mi := &file_proto_game_v1_game_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1403,7 +2559,7 @@ func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchResponse.ProtoReflect.Descriptor instead. func (*CronBatchResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{15} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{28} } func (x *CronBatchResponse) GetClaimedCount() int32 { @@ -1451,7 +2607,7 @@ type ListPlatformsRequest struct { func (x *ListPlatformsRequest) Reset() { *x = ListPlatformsRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[16] + mi := &file_proto_game_v1_game_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1463,7 +2619,7 @@ func (x *ListPlatformsRequest) String() string { func (*ListPlatformsRequest) ProtoMessage() {} func (x *ListPlatformsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[16] + mi := &file_proto_game_v1_game_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1476,7 +2632,7 @@ func (x *ListPlatformsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPlatformsRequest.ProtoReflect.Descriptor instead. func (*ListPlatformsRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{16} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{29} } func (x *ListPlatformsRequest) GetMeta() *RequestMeta { @@ -1503,7 +2659,7 @@ type ListPlatformsResponse struct { func (x *ListPlatformsResponse) Reset() { *x = ListPlatformsResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[17] + mi := &file_proto_game_v1_game_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1515,7 +2671,7 @@ func (x *ListPlatformsResponse) String() string { func (*ListPlatformsResponse) ProtoMessage() {} func (x *ListPlatformsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[17] + mi := &file_proto_game_v1_game_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1528,7 +2684,7 @@ func (x *ListPlatformsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPlatformsResponse.ProtoReflect.Descriptor instead. func (*ListPlatformsResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{17} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{30} } func (x *ListPlatformsResponse) GetPlatforms() []*GamePlatform { @@ -1555,7 +2711,7 @@ type UpsertPlatformRequest struct { func (x *UpsertPlatformRequest) Reset() { *x = UpsertPlatformRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[18] + mi := &file_proto_game_v1_game_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1567,7 +2723,7 @@ func (x *UpsertPlatformRequest) String() string { func (*UpsertPlatformRequest) ProtoMessage() {} func (x *UpsertPlatformRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[18] + mi := &file_proto_game_v1_game_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1580,7 +2736,7 @@ func (x *UpsertPlatformRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertPlatformRequest.ProtoReflect.Descriptor instead. func (*UpsertPlatformRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{18} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{31} } func (x *UpsertPlatformRequest) GetMeta() *RequestMeta { @@ -1607,7 +2763,7 @@ type PlatformResponse struct { func (x *PlatformResponse) Reset() { *x = PlatformResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[19] + mi := &file_proto_game_v1_game_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1619,7 +2775,7 @@ func (x *PlatformResponse) String() string { func (*PlatformResponse) ProtoMessage() {} func (x *PlatformResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[19] + mi := &file_proto_game_v1_game_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1632,7 +2788,7 @@ func (x *PlatformResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PlatformResponse.ProtoReflect.Descriptor instead. func (*PlatformResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{19} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{32} } func (x *PlatformResponse) GetPlatform() *GamePlatform { @@ -1662,7 +2818,7 @@ type ListCatalogRequest struct { func (x *ListCatalogRequest) Reset() { *x = ListCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[20] + mi := &file_proto_game_v1_game_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1674,7 +2830,7 @@ func (x *ListCatalogRequest) String() string { func (*ListCatalogRequest) ProtoMessage() {} func (x *ListCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[20] + mi := &file_proto_game_v1_game_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1687,7 +2843,7 @@ func (x *ListCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCatalogRequest.ProtoReflect.Descriptor instead. func (*ListCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{20} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{33} } func (x *ListCatalogRequest) GetMeta() *RequestMeta { @@ -1736,7 +2892,7 @@ type ListCatalogResponse struct { func (x *ListCatalogResponse) Reset() { *x = ListCatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[21] + mi := &file_proto_game_v1_game_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1748,7 +2904,7 @@ func (x *ListCatalogResponse) String() string { func (*ListCatalogResponse) ProtoMessage() {} func (x *ListCatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[21] + mi := &file_proto_game_v1_game_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1761,7 +2917,7 @@ func (x *ListCatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCatalogResponse.ProtoReflect.Descriptor instead. func (*ListCatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{21} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{34} } func (x *ListCatalogResponse) GetGames() []*GameCatalogItem { @@ -1795,7 +2951,7 @@ type UpsertCatalogRequest struct { func (x *UpsertCatalogRequest) Reset() { *x = UpsertCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[22] + mi := &file_proto_game_v1_game_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1807,7 +2963,7 @@ func (x *UpsertCatalogRequest) String() string { func (*UpsertCatalogRequest) ProtoMessage() {} func (x *UpsertCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[22] + mi := &file_proto_game_v1_game_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1820,7 +2976,7 @@ func (x *UpsertCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertCatalogRequest.ProtoReflect.Descriptor instead. func (*UpsertCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{22} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{35} } func (x *UpsertCatalogRequest) GetMeta() *RequestMeta { @@ -1847,7 +3003,7 @@ type CatalogResponse struct { func (x *CatalogResponse) Reset() { *x = CatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[23] + mi := &file_proto_game_v1_game_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1859,7 +3015,7 @@ func (x *CatalogResponse) String() string { func (*CatalogResponse) ProtoMessage() {} func (x *CatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[23] + mi := &file_proto_game_v1_game_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1872,7 +3028,7 @@ func (x *CatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CatalogResponse.ProtoReflect.Descriptor instead. func (*CatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{23} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{36} } func (x *CatalogResponse) GetGame() *GameCatalogItem { @@ -1900,7 +3056,7 @@ type SetGameStatusRequest struct { func (x *SetGameStatusRequest) Reset() { *x = SetGameStatusRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[24] + mi := &file_proto_game_v1_game_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1912,7 +3068,7 @@ func (x *SetGameStatusRequest) String() string { func (*SetGameStatusRequest) ProtoMessage() {} func (x *SetGameStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[24] + mi := &file_proto_game_v1_game_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1925,7 +3081,7 @@ func (x *SetGameStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetGameStatusRequest.ProtoReflect.Descriptor instead. func (*SetGameStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{24} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{37} } func (x *SetGameStatusRequest) GetMeta() *RequestMeta { @@ -1959,7 +3115,7 @@ type DeleteCatalogRequest struct { func (x *DeleteCatalogRequest) Reset() { *x = DeleteCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[25] + mi := &file_proto_game_v1_game_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1971,7 +3127,7 @@ func (x *DeleteCatalogRequest) String() string { func (*DeleteCatalogRequest) ProtoMessage() {} func (x *DeleteCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[25] + mi := &file_proto_game_v1_game_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1984,7 +3140,7 @@ func (x *DeleteCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteCatalogRequest.ProtoReflect.Descriptor instead. func (*DeleteCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{25} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{38} } func (x *DeleteCatalogRequest) GetMeta() *RequestMeta { @@ -2010,7 +3166,7 @@ type DeleteCatalogResponse struct { func (x *DeleteCatalogResponse) Reset() { *x = DeleteCatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[26] + mi := &file_proto_game_v1_game_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2022,7 +3178,7 @@ func (x *DeleteCatalogResponse) String() string { func (*DeleteCatalogResponse) ProtoMessage() {} func (x *DeleteCatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[26] + mi := &file_proto_game_v1_game_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2035,7 +3191,7 @@ func (x *DeleteCatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteCatalogResponse.ProtoReflect.Descriptor instead. func (*DeleteCatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{26} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{39} } func (x *DeleteCatalogResponse) GetServerTimeMs() int64 { @@ -2045,6 +3201,954 @@ func (x *DeleteCatalogResponse) GetServerTimeMs() int64 { return 0 } +type ListSelfGamesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSelfGamesRequest) Reset() { + *x = ListSelfGamesRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSelfGamesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSelfGamesRequest) ProtoMessage() {} + +func (x *ListSelfGamesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSelfGamesRequest.ProtoReflect.Descriptor instead. +func (*ListSelfGamesRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{40} +} + +func (x *ListSelfGamesRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +type ListSelfGamesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Games []*DiceConfig `protobuf:"bytes,1,rep,name=games,proto3" json:"games,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSelfGamesResponse) Reset() { + *x = ListSelfGamesResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSelfGamesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSelfGamesResponse) ProtoMessage() {} + +func (x *ListSelfGamesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSelfGamesResponse.ProtoReflect.Descriptor instead. +func (*ListSelfGamesResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{41} +} + +func (x *ListSelfGamesResponse) GetGames() []*DiceConfig { + if x != nil { + return x.Games + } + return nil +} + +func (x *ListSelfGamesResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type UpdateDiceConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Config *DiceConfig `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateDiceConfigRequest) Reset() { + *x = UpdateDiceConfigRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateDiceConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateDiceConfigRequest) ProtoMessage() {} + +func (x *UpdateDiceConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateDiceConfigRequest.ProtoReflect.Descriptor instead. +func (*UpdateDiceConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{42} +} + +func (x *UpdateDiceConfigRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *UpdateDiceConfigRequest) GetConfig() *DiceConfig { + if x != nil { + return x.Config + } + return nil +} + +type DicePoolAdjustment struct { + state protoimpl.MessageState `protogen:"open.v1"` + AdjustmentId string `protobuf:"bytes,1,opt,name=adjustment_id,json=adjustmentId,proto3" json:"adjustment_id,omitempty"` + GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + AmountCoin int64 `protobuf:"varint,3,opt,name=amount_coin,json=amountCoin,proto3" json:"amount_coin,omitempty"` + Direction string `protobuf:"bytes,4,opt,name=direction,proto3" json:"direction,omitempty"` + Reason string `protobuf:"bytes,5,opt,name=reason,proto3" json:"reason,omitempty"` + BalanceAfter int64 `protobuf:"varint,6,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` + CreatedAtMs int64 `protobuf:"varint,7,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DicePoolAdjustment) Reset() { + *x = DicePoolAdjustment{} + mi := &file_proto_game_v1_game_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DicePoolAdjustment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DicePoolAdjustment) ProtoMessage() {} + +func (x *DicePoolAdjustment) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[43] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DicePoolAdjustment.ProtoReflect.Descriptor instead. +func (*DicePoolAdjustment) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{43} +} + +func (x *DicePoolAdjustment) GetAdjustmentId() string { + if x != nil { + return x.AdjustmentId + } + return "" +} + +func (x *DicePoolAdjustment) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *DicePoolAdjustment) GetAmountCoin() int64 { + if x != nil { + return x.AmountCoin + } + return 0 +} + +func (x *DicePoolAdjustment) GetDirection() string { + if x != nil { + return x.Direction + } + return "" +} + +func (x *DicePoolAdjustment) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *DicePoolAdjustment) GetBalanceAfter() int64 { + if x != nil { + return x.BalanceAfter + } + return 0 +} + +func (x *DicePoolAdjustment) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +type AdjustDicePoolRequest 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"` + AmountCoin int64 `protobuf:"varint,3,opt,name=amount_coin,json=amountCoin,proto3" json:"amount_coin,omitempty"` + Direction string `protobuf:"bytes,4,opt,name=direction,proto3" json:"direction,omitempty"` + Reason string `protobuf:"bytes,5,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdjustDicePoolRequest) Reset() { + *x = AdjustDicePoolRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdjustDicePoolRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdjustDicePoolRequest) ProtoMessage() {} + +func (x *AdjustDicePoolRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[44] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdjustDicePoolRequest.ProtoReflect.Descriptor instead. +func (*AdjustDicePoolRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{44} +} + +func (x *AdjustDicePoolRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *AdjustDicePoolRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *AdjustDicePoolRequest) GetAmountCoin() int64 { + if x != nil { + return x.AmountCoin + } + return 0 +} + +func (x *AdjustDicePoolRequest) GetDirection() string { + if x != nil { + return x.Direction + } + return "" +} + +func (x *AdjustDicePoolRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type AdjustDicePoolResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Adjustment *DicePoolAdjustment `protobuf:"bytes,1,opt,name=adjustment,proto3" json:"adjustment,omitempty"` + Config *DiceConfig `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdjustDicePoolResponse) Reset() { + *x = AdjustDicePoolResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdjustDicePoolResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdjustDicePoolResponse) ProtoMessage() {} + +func (x *AdjustDicePoolResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[45] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdjustDicePoolResponse.ProtoReflect.Descriptor instead. +func (*AdjustDicePoolResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{45} +} + +func (x *AdjustDicePoolResponse) GetAdjustment() *DicePoolAdjustment { + if x != nil { + return x.Adjustment + } + return nil +} + +func (x *AdjustDicePoolResponse) GetConfig() *DiceConfig { + if x != nil { + return x.Config + } + return nil +} + +func (x *AdjustDicePoolResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type DiceRobot struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,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"` + CreatedByAdminId int64 `protobuf:"varint,5,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,6,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,7,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiceRobot) Reset() { + *x = DiceRobot{} + mi := &file_proto_game_v1_game_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiceRobot) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiceRobot) ProtoMessage() {} + +func (x *DiceRobot) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[46] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiceRobot.ProtoReflect.Descriptor instead. +func (*DiceRobot) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{46} +} + +func (x *DiceRobot) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *DiceRobot) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *DiceRobot) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *DiceRobot) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *DiceRobot) GetCreatedByAdminId() int64 { + if x != nil { + return x.CreatedByAdminId + } + return 0 +} + +func (x *DiceRobot) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *DiceRobot) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type ListDiceRobotsRequest 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 + sizeCache protoimpl.SizeCache +} + +func (x *ListDiceRobotsRequest) Reset() { + *x = ListDiceRobotsRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListDiceRobotsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListDiceRobotsRequest) ProtoMessage() {} + +func (x *ListDiceRobotsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListDiceRobotsRequest.ProtoReflect.Descriptor instead. +func (*ListDiceRobotsRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{47} +} + +func (x *ListDiceRobotsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListDiceRobotsRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *ListDiceRobotsRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ListDiceRobotsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListDiceRobotsRequest) GetCursor() string { + if x != nil { + return x.Cursor + } + return "" +} + +type ListDiceRobotsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Robots []*DiceRobot `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 + sizeCache protoimpl.SizeCache +} + +func (x *ListDiceRobotsResponse) Reset() { + *x = ListDiceRobotsResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListDiceRobotsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListDiceRobotsResponse) ProtoMessage() {} + +func (x *ListDiceRobotsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[48] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListDiceRobotsResponse.ProtoReflect.Descriptor instead. +func (*ListDiceRobotsResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{48} +} + +func (x *ListDiceRobotsResponse) GetRobots() []*DiceRobot { + if x != nil { + return x.Robots + } + return nil +} + +func (x *ListDiceRobotsResponse) GetNextCursor() string { + if x != nil { + return x.NextCursor + } + return "" +} + +func (x *ListDiceRobotsResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type RegisterDiceRobotsRequest 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 + sizeCache protoimpl.SizeCache +} + +func (x *RegisterDiceRobotsRequest) Reset() { + *x = RegisterDiceRobotsRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterDiceRobotsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterDiceRobotsRequest) ProtoMessage() {} + +func (x *RegisterDiceRobotsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[49] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterDiceRobotsRequest.ProtoReflect.Descriptor instead. +func (*RegisterDiceRobotsRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{49} +} + +func (x *RegisterDiceRobotsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *RegisterDiceRobotsRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *RegisterDiceRobotsRequest) GetUserIds() []int64 { + if x != nil { + return x.UserIds + } + return nil +} + +type RegisterDiceRobotsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Robots []*DiceRobot `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 + sizeCache protoimpl.SizeCache +} + +func (x *RegisterDiceRobotsResponse) Reset() { + *x = RegisterDiceRobotsResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterDiceRobotsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterDiceRobotsResponse) ProtoMessage() {} + +func (x *RegisterDiceRobotsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterDiceRobotsResponse.ProtoReflect.Descriptor instead. +func (*RegisterDiceRobotsResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{50} +} + +func (x *RegisterDiceRobotsResponse) GetRobots() []*DiceRobot { + if x != nil { + return x.Robots + } + return nil +} + +func (x *RegisterDiceRobotsResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type SetDiceRobotStatusRequest 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 + sizeCache protoimpl.SizeCache +} + +func (x *SetDiceRobotStatusRequest) Reset() { + *x = SetDiceRobotStatusRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetDiceRobotStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetDiceRobotStatusRequest) ProtoMessage() {} + +func (x *SetDiceRobotStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetDiceRobotStatusRequest.ProtoReflect.Descriptor instead. +func (*SetDiceRobotStatusRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{51} +} + +func (x *SetDiceRobotStatusRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *SetDiceRobotStatusRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *SetDiceRobotStatusRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *SetDiceRobotStatusRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +type DeleteDiceRobotRequest 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 + sizeCache protoimpl.SizeCache +} + +func (x *DeleteDiceRobotRequest) Reset() { + *x = DeleteDiceRobotRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteDiceRobotRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteDiceRobotRequest) ProtoMessage() {} + +func (x *DeleteDiceRobotRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[52] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteDiceRobotRequest.ProtoReflect.Descriptor instead. +func (*DeleteDiceRobotRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{52} +} + +func (x *DeleteDiceRobotRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *DeleteDiceRobotRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *DeleteDiceRobotRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +type DeleteDiceRobotResponse 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 + sizeCache protoimpl.SizeCache +} + +func (x *DeleteDiceRobotResponse) Reset() { + *x = DeleteDiceRobotResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteDiceRobotResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteDiceRobotResponse) ProtoMessage() {} + +func (x *DeleteDiceRobotResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteDiceRobotResponse.ProtoReflect.Descriptor instead. +func (*DeleteDiceRobotResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{53} +} + +func (x *DeleteDiceRobotResponse) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +func (x *DeleteDiceRobotResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type DiceRobotResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Robot *DiceRobot `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 + sizeCache protoimpl.SizeCache +} + +func (x *DiceRobotResponse) Reset() { + *x = DiceRobotResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiceRobotResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiceRobotResponse) ProtoMessage() {} + +func (x *DiceRobotResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[54] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiceRobotResponse.ProtoReflect.Descriptor instead. +func (*DiceRobotResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{54} +} + +func (x *DiceRobotResponse) GetRobot() *DiceRobot { + if x != nil { + return x.Robot + } + return nil +} + +func (x *DiceRobotResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + var File_proto_game_v1_game_proto protoreflect.FileDescriptor const file_proto_game_v1_game_proto_rawDesc = "" + @@ -2163,7 +4267,126 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\vorientation\x18\x04 \x01(\tR\vorientation\x12$\n" + "\x0eserver_time_ms\x18\x05 \x01(\x03R\fserverTimeMs\x12\x1f\n" + "\vsafe_height\x18\x06 \x01(\x05R\n" + - "safeHeight\"\xbe\x03\n" + + "safeHeight\"\x85\x03\n" + + "\x0fDiceParticipant\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x17\n" + + "\aseat_no\x18\x02 \x01(\x05R\x06seatNo\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "stake_coin\x18\x04 \x01(\x03R\tstakeCoin\x12\x1f\n" + + "\vdice_points\x18\x05 \x03(\x05R\n" + + "dicePoints\x12\x16\n" + + "\x06result\x18\x06 \x01(\tR\x06result\x12\x1f\n" + + "\vpayout_coin\x18\a \x01(\x03R\n" + + "payoutCoin\x12#\n" + + "\rbalance_after\x18\b \x01(\x03R\fbalanceAfter\x12 \n" + + "\fjoined_at_ms\x18\t \x01(\x03R\n" + + "joinedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\n" + + " \x01(\x03R\vupdatedAtMs\x12)\n" + + "\x10participant_type\x18\v \x01(\tR\x0fparticipantType\x12\x19\n" + + "\bis_robot\x18\f \x01(\bR\aisRobot\"\xe7\x06\n" + + "\tDiceMatch\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x19\n" + + "\bmatch_id\x18\x02 \x01(\tR\amatchId\x12\x17\n" + + "\agame_id\x18\x03 \x01(\tR\x06gameId\x12\x17\n" + + "\aroom_id\x18\x04 \x01(\tR\x06roomId\x12\x1b\n" + + "\tregion_id\x18\x05 \x01(\x03R\bregionId\x12\x1f\n" + + "\vmin_players\x18\x06 \x01(\x05R\n" + + "minPlayers\x12\x1f\n" + + "\vmax_players\x18\a \x01(\x05R\n" + + "maxPlayers\x12'\n" + + "\x0fcurrent_players\x18\b \x01(\x05R\x0ecurrentPlayers\x12\x1d\n" + + "\n" + + "stake_coin\x18\t \x01(\x03R\tstakeCoin\x12\x19\n" + + "\bround_no\x18\n" + + " \x01(\x05R\aroundNo\x12\x16\n" + + "\x06status\x18\v \x01(\tR\x06status\x12\x16\n" + + "\x06result\x18\f \x01(\tR\x06result\x12B\n" + + "\fparticipants\x18\r \x03(\v2\x1e.hyapp.game.v1.DiceParticipantR\fparticipants\x12(\n" + + "\x10join_deadline_ms\x18\x0e \x01(\x03R\x0ejoinDeadlineMs\x12\"\n" + + "\rcreated_at_ms\x18\x0f \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x10 \x01(\x03R\vupdatedAtMs\x12\"\n" + + "\rsettled_at_ms\x18\x11 \x01(\x03R\vsettledAtMs\x12\x14\n" + + "\x05phase\x18\x12 \x01(\tR\x05phase\x12*\n" + + "\x11phase_deadline_ms\x18\x13 \x01(\x03R\x0fphaseDeadlineMs\x12\x17\n" + + "\afee_bps\x18\x14 \x01(\x05R\x06feeBps\x12\x19\n" + + "\bpool_bps\x18\x15 \x01(\x05R\apoolBps\x12\x1d\n" + + "\n" + + "match_mode\x18\x16 \x01(\tR\tmatchMode\x12#\n" + + "\rforced_result\x18\x17 \x01(\tR\fforcedResult\x12\x1e\n" + + "\vready_at_ms\x18\x18 \x01(\x03R\treadyAtMs\x12$\n" + + "\x0ecanceled_at_ms\x18\x19 \x01(\x03R\fcanceledAtMs\x12&\n" + + "\x0fpool_delta_coin\x18\x1a \x01(\x03R\rpoolDeltaCoin\"i\n" + + "\x0fDiceStakeOption\x12\x1d\n" + + "\n" + + "stake_coin\x18\x01 \x01(\x03R\tstakeCoin\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\x12\x1d\n" + + "\n" + + "sort_order\x18\x03 \x01(\x05R\tsortOrder\"\xdb\x03\n" + + "\n" + + "DiceConfig\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x17\n" + + "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x12C\n" + + "\rstake_options\x18\x04 \x03(\v2\x1e.hyapp.game.v1.DiceStakeOptionR\fstakeOptions\x12\x17\n" + + "\afee_bps\x18\x05 \x01(\x05R\x06feeBps\x12\x19\n" + + "\bpool_bps\x18\x06 \x01(\x05R\apoolBps\x12\x1f\n" + + "\vmin_players\x18\a \x01(\x05R\n" + + "minPlayers\x12\x1f\n" + + "\vmax_players\x18\b \x01(\x05R\n" + + "maxPlayers\x12#\n" + + "\rrobot_enabled\x18\t \x01(\bR\frobotEnabled\x12-\n" + + "\x13robot_match_wait_ms\x18\n" + + " \x01(\x03R\x10robotMatchWaitMs\x12*\n" + + "\x11pool_balance_coin\x18\v \x01(\x03R\x0fpoolBalanceCoin\x12\"\n" + + "\rcreated_at_ms\x18\f \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\r \x01(\x03R\vupdatedAtMs\"_\n" + + "\x14GetDiceConfigRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\agame_id\x18\x02 \x01(\tR\x06gameId\"m\n" + + "\x12DiceConfigResponse\x121\n" + + "\x06config\x18\x01 \x01(\v2\x19.hyapp.game.v1.DiceConfigR\x06config\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\x91\x02\n" + + "\x16CreateDiceMatchRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x17\n" + + "\agame_id\x18\x03 \x01(\tR\x06gameId\x12\x17\n" + + "\aroom_id\x18\x04 \x01(\tR\x06roomId\x12\x1b\n" + + "\tregion_id\x18\x05 \x01(\x03R\bregionId\x12\x1d\n" + + "\n" + + "stake_coin\x18\x06 \x01(\x03R\tstakeCoin\x12\x1f\n" + + "\vmin_players\x18\a \x01(\x05R\n" + + "minPlayers\x12\x1f\n" + + "\vmax_players\x18\b \x01(\x05R\n" + + "maxPlayers\"z\n" + + "\x14JoinDiceMatchRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + + "\bmatch_id\x18\x03 \x01(\tR\amatchId\"y\n" + + "\x13GetDiceMatchRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + + "\bmatch_id\x18\x03 \x01(\tR\amatchId\"z\n" + + "\x14RollDiceMatchRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + + "\bmatch_id\x18\x03 \x01(\tR\amatchId\"\xc9\x01\n" + + "\x10MatchDiceRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x17\n" + + "\agame_id\x18\x03 \x01(\tR\x06gameId\x12\x17\n" + + "\aroom_id\x18\x04 \x01(\tR\x06roomId\x12\x1b\n" + + "\tregion_id\x18\x05 \x01(\x03R\bregionId\x12\x1d\n" + + "\n" + + "stake_coin\x18\x06 \x01(\x03R\tstakeCoin\"|\n" + + "\x16CancelDiceMatchRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + + "\bmatch_id\x18\x03 \x01(\tR\amatchId\"i\n" + + "\x11DiceMatchResponse\x12.\n" + + "\x05match\x18\x01 \x01(\v2\x18.hyapp.game.v1.DiceMatchR\x05match\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xbe\x03\n" + "\x0fCallbackRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12#\n" + "\rplatform_code\x18\x02 \x01(\tR\fplatformCode\x12\x1c\n" + @@ -2233,24 +4456,109 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + "\agame_id\x18\x02 \x01(\tR\x06gameId\"=\n" + "\x15DeleteCatalogResponse\x12$\n" + - "\x0eserver_time_ms\x18\x01 \x01(\x03R\fserverTimeMs2\xf1\x02\n" + + "\x0eserver_time_ms\x18\x01 \x01(\x03R\fserverTimeMs\"F\n" + + "\x14ListSelfGamesRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\"n\n" + + "\x15ListSelfGamesResponse\x12/\n" + + "\x05games\x18\x01 \x03(\v2\x19.hyapp.game.v1.DiceConfigR\x05games\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"|\n" + + "\x17UpdateDiceConfigRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x121\n" + + "\x06config\x18\x02 \x01(\v2\x19.hyapp.game.v1.DiceConfigR\x06config\"\xf2\x01\n" + + "\x12DicePoolAdjustment\x12#\n" + + "\radjustment_id\x18\x01 \x01(\tR\fadjustmentId\x12\x17\n" + + "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x1f\n" + + "\vamount_coin\x18\x03 \x01(\x03R\n" + + "amountCoin\x12\x1c\n" + + "\tdirection\x18\x04 \x01(\tR\tdirection\x12\x16\n" + + "\x06reason\x18\x05 \x01(\tR\x06reason\x12#\n" + + "\rbalance_after\x18\x06 \x01(\x03R\fbalanceAfter\x12\"\n" + + "\rcreated_at_ms\x18\a \x01(\x03R\vcreatedAtMs\"\xb7\x01\n" + + "\x15AdjustDicePoolRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x1f\n" + + "\vamount_coin\x18\x03 \x01(\x03R\n" + + "amountCoin\x12\x1c\n" + + "\tdirection\x18\x04 \x01(\tR\tdirection\x12\x16\n" + + "\x06reason\x18\x05 \x01(\tR\x06reason\"\xb4\x01\n" + + "\x16AdjustDicePoolResponse\x12A\n" + + "\n" + + "adjustment\x18\x01 \x01(\v2!.hyapp.game.v1.DicePoolAdjustmentR\n" + + "adjustment\x121\n" + + "\x06config\x18\x02 \x01(\v2\x19.hyapp.game.v1.DiceConfigR\x06config\x12$\n" + + "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\xe7\x01\n" + + "\tDiceRobot\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\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\x12-\n" + + "\x13created_by_admin_id\x18\x05 \x01(\x03R\x10createdByAdminId\x12\"\n" + + "\rcreated_at_ms\x18\x06 \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\a \x01(\x03R\vupdatedAtMs\"\xad\x01\n" + + "\x15ListDiceRobotsRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.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\"\x91\x01\n" + + "\x16ListDiceRobotsResponse\x120\n" + + "\x06robots\x18\x01 \x03(\v2\x18.hyapp.game.v1.DiceRobotR\x06robots\x12\x1f\n" + + "\vnext_cursor\x18\x02 \x01(\tR\n" + + "nextCursor\x12$\n" + + "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x7f\n" + + "\x19RegisterDiceRobotsRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x19\n" + + "\buser_ids\x18\x03 \x03(\x03R\auserIds\"t\n" + + "\x1aRegisterDiceRobotsResponse\x120\n" + + "\x06robots\x18\x01 \x03(\v2\x18.hyapp.game.v1.DiceRobotR\x06robots\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\x95\x01\n" + + "\x19SetDiceRobotStatusRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.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\"z\n" + + "\x16DeleteDiceRobotRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.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" + + "\x17DeleteDiceRobotResponse\x12\x18\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"i\n" + + "\x11DiceRobotResponse\x12.\n" + + "\x05robot\x18\x01 \x01(\v2\x18.hyapp.game.v1.DiceRobotR\x05robot\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs2\xd8\a\n" + "\x0eGameAppService\x12N\n" + "\tListGames\x12\x1f.hyapp.game.v1.ListGamesRequest\x1a .hyapp.game.v1.ListGamesResponse\x12Z\n" + "\x0fListRecentGames\x12%.hyapp.game.v1.ListRecentGamesRequest\x1a .hyapp.game.v1.ListGamesResponse\x12`\n" + "\x0fGetBridgeScript\x12%.hyapp.game.v1.GetBridgeScriptRequest\x1a&.hyapp.game.v1.GetBridgeScriptResponse\x12Q\n" + "\n" + - "LaunchGame\x12 .hyapp.game.v1.LaunchGameRequest\x1a!.hyapp.game.v1.LaunchGameResponse2h\n" + + "LaunchGame\x12 .hyapp.game.v1.LaunchGameRequest\x1a!.hyapp.game.v1.LaunchGameResponse\x12W\n" + + "\rGetDiceConfig\x12#.hyapp.game.v1.GetDiceConfigRequest\x1a!.hyapp.game.v1.DiceConfigResponse\x12N\n" + + "\tMatchDice\x12\x1f.hyapp.game.v1.MatchDiceRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12Z\n" + + "\x0fCreateDiceMatch\x12%.hyapp.game.v1.CreateDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12V\n" + + "\rJoinDiceMatch\x12#.hyapp.game.v1.JoinDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12T\n" + + "\fGetDiceMatch\x12\".hyapp.game.v1.GetDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12V\n" + + "\rRollDiceMatch\x12#.hyapp.game.v1.RollDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12Z\n" + + "\x0fCancelDiceMatch\x12%.hyapp.game.v1.CancelDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse2h\n" + "\x13GameCallbackService\x12Q\n" + "\x0eHandleCallback\x12\x1e.hyapp.game.v1.CallbackRequest\x1a\x1f.hyapp.game.v1.CallbackResponse2t\n" + "\x0fGameCronService\x12a\n" + - "\x1cProcessLevelEventOutboxBatch\x12\x1f.hyapp.game.v1.CronBatchRequest\x1a .hyapp.game.v1.CronBatchResponse2\xa5\x04\n" + + "\x1cProcessLevelEventOutboxBatch\x12\x1f.hyapp.game.v1.CronBatchRequest\x1a .hyapp.game.v1.CronBatchResponse2\xcd\t\n" + "\x10GameAdminService\x12Z\n" + "\rListPlatforms\x12#.hyapp.game.v1.ListPlatformsRequest\x1a$.hyapp.game.v1.ListPlatformsResponse\x12W\n" + "\x0eUpsertPlatform\x12$.hyapp.game.v1.UpsertPlatformRequest\x1a\x1f.hyapp.game.v1.PlatformResponse\x12T\n" + "\vListCatalog\x12!.hyapp.game.v1.ListCatalogRequest\x1a\".hyapp.game.v1.ListCatalogResponse\x12T\n" + "\rUpsertCatalog\x12#.hyapp.game.v1.UpsertCatalogRequest\x1a\x1e.hyapp.game.v1.CatalogResponse\x12T\n" + "\rSetGameStatus\x12#.hyapp.game.v1.SetGameStatusRequest\x1a\x1e.hyapp.game.v1.CatalogResponse\x12Z\n" + - "\rDeleteCatalog\x12#.hyapp.game.v1.DeleteCatalogRequest\x1a$.hyapp.game.v1.DeleteCatalogResponseB&Z$hyapp.local/api/proto/game/v1;gamev1b\x06proto3" + "\rDeleteCatalog\x12#.hyapp.game.v1.DeleteCatalogRequest\x1a$.hyapp.game.v1.DeleteCatalogResponse\x12Z\n" + + "\rListSelfGames\x12#.hyapp.game.v1.ListSelfGamesRequest\x1a$.hyapp.game.v1.ListSelfGamesResponse\x12]\n" + + "\x10UpdateDiceConfig\x12&.hyapp.game.v1.UpdateDiceConfigRequest\x1a!.hyapp.game.v1.DiceConfigResponse\x12]\n" + + "\x0eAdjustDicePool\x12$.hyapp.game.v1.AdjustDicePoolRequest\x1a%.hyapp.game.v1.AdjustDicePoolResponse\x12]\n" + + "\x0eListDiceRobots\x12$.hyapp.game.v1.ListDiceRobotsRequest\x1a%.hyapp.game.v1.ListDiceRobotsResponse\x12i\n" + + "\x12RegisterDiceRobots\x12(.hyapp.game.v1.RegisterDiceRobotsRequest\x1a).hyapp.game.v1.RegisterDiceRobotsResponse\x12`\n" + + "\x12SetDiceRobotStatus\x12(.hyapp.game.v1.SetDiceRobotStatusRequest\x1a .hyapp.game.v1.DiceRobotResponse\x12`\n" + + "\x0fDeleteDiceRobot\x12%.hyapp.game.v1.DeleteDiceRobotRequest\x1a&.hyapp.game.v1.DeleteDiceRobotResponseB&Z$hyapp.local/api/proto/game/v1;gamev1b\x06proto3" var ( file_proto_game_v1_game_proto_rawDescOnce sync.Once @@ -2264,37 +4572,65 @@ func file_proto_game_v1_game_proto_rawDescGZIP() []byte { return file_proto_game_v1_game_proto_rawDescData } -var file_proto_game_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_proto_game_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 57) var file_proto_game_v1_game_proto_goTypes = []any{ - (*RequestMeta)(nil), // 0: hyapp.game.v1.RequestMeta - (*GamePlatform)(nil), // 1: hyapp.game.v1.GamePlatform - (*GameCatalogItem)(nil), // 2: hyapp.game.v1.GameCatalogItem - (*AppGame)(nil), // 3: hyapp.game.v1.AppGame - (*ListGamesRequest)(nil), // 4: hyapp.game.v1.ListGamesRequest - (*ListGamesResponse)(nil), // 5: hyapp.game.v1.ListGamesResponse - (*ListRecentGamesRequest)(nil), // 6: hyapp.game.v1.ListRecentGamesRequest - (*GetBridgeScriptRequest)(nil), // 7: hyapp.game.v1.GetBridgeScriptRequest - (*GameBridgeScriptConfig)(nil), // 8: hyapp.game.v1.GameBridgeScriptConfig - (*GetBridgeScriptResponse)(nil), // 9: hyapp.game.v1.GetBridgeScriptResponse - (*LaunchGameRequest)(nil), // 10: hyapp.game.v1.LaunchGameRequest - (*LaunchGameResponse)(nil), // 11: hyapp.game.v1.LaunchGameResponse - (*CallbackRequest)(nil), // 12: hyapp.game.v1.CallbackRequest - (*CallbackResponse)(nil), // 13: hyapp.game.v1.CallbackResponse - (*CronBatchRequest)(nil), // 14: hyapp.game.v1.CronBatchRequest - (*CronBatchResponse)(nil), // 15: hyapp.game.v1.CronBatchResponse - (*ListPlatformsRequest)(nil), // 16: hyapp.game.v1.ListPlatformsRequest - (*ListPlatformsResponse)(nil), // 17: hyapp.game.v1.ListPlatformsResponse - (*UpsertPlatformRequest)(nil), // 18: hyapp.game.v1.UpsertPlatformRequest - (*PlatformResponse)(nil), // 19: hyapp.game.v1.PlatformResponse - (*ListCatalogRequest)(nil), // 20: hyapp.game.v1.ListCatalogRequest - (*ListCatalogResponse)(nil), // 21: hyapp.game.v1.ListCatalogResponse - (*UpsertCatalogRequest)(nil), // 22: hyapp.game.v1.UpsertCatalogRequest - (*CatalogResponse)(nil), // 23: hyapp.game.v1.CatalogResponse - (*SetGameStatusRequest)(nil), // 24: hyapp.game.v1.SetGameStatusRequest - (*DeleteCatalogRequest)(nil), // 25: hyapp.game.v1.DeleteCatalogRequest - (*DeleteCatalogResponse)(nil), // 26: hyapp.game.v1.DeleteCatalogResponse - nil, // 27: hyapp.game.v1.CallbackRequest.HeadersEntry - nil, // 28: hyapp.game.v1.CallbackRequest.QueryEntry + (*RequestMeta)(nil), // 0: hyapp.game.v1.RequestMeta + (*GamePlatform)(nil), // 1: hyapp.game.v1.GamePlatform + (*GameCatalogItem)(nil), // 2: hyapp.game.v1.GameCatalogItem + (*AppGame)(nil), // 3: hyapp.game.v1.AppGame + (*ListGamesRequest)(nil), // 4: hyapp.game.v1.ListGamesRequest + (*ListGamesResponse)(nil), // 5: hyapp.game.v1.ListGamesResponse + (*ListRecentGamesRequest)(nil), // 6: hyapp.game.v1.ListRecentGamesRequest + (*GetBridgeScriptRequest)(nil), // 7: hyapp.game.v1.GetBridgeScriptRequest + (*GameBridgeScriptConfig)(nil), // 8: hyapp.game.v1.GameBridgeScriptConfig + (*GetBridgeScriptResponse)(nil), // 9: hyapp.game.v1.GetBridgeScriptResponse + (*LaunchGameRequest)(nil), // 10: hyapp.game.v1.LaunchGameRequest + (*LaunchGameResponse)(nil), // 11: hyapp.game.v1.LaunchGameResponse + (*DiceParticipant)(nil), // 12: hyapp.game.v1.DiceParticipant + (*DiceMatch)(nil), // 13: hyapp.game.v1.DiceMatch + (*DiceStakeOption)(nil), // 14: hyapp.game.v1.DiceStakeOption + (*DiceConfig)(nil), // 15: hyapp.game.v1.DiceConfig + (*GetDiceConfigRequest)(nil), // 16: hyapp.game.v1.GetDiceConfigRequest + (*DiceConfigResponse)(nil), // 17: hyapp.game.v1.DiceConfigResponse + (*CreateDiceMatchRequest)(nil), // 18: hyapp.game.v1.CreateDiceMatchRequest + (*JoinDiceMatchRequest)(nil), // 19: hyapp.game.v1.JoinDiceMatchRequest + (*GetDiceMatchRequest)(nil), // 20: hyapp.game.v1.GetDiceMatchRequest + (*RollDiceMatchRequest)(nil), // 21: hyapp.game.v1.RollDiceMatchRequest + (*MatchDiceRequest)(nil), // 22: hyapp.game.v1.MatchDiceRequest + (*CancelDiceMatchRequest)(nil), // 23: hyapp.game.v1.CancelDiceMatchRequest + (*DiceMatchResponse)(nil), // 24: hyapp.game.v1.DiceMatchResponse + (*CallbackRequest)(nil), // 25: hyapp.game.v1.CallbackRequest + (*CallbackResponse)(nil), // 26: hyapp.game.v1.CallbackResponse + (*CronBatchRequest)(nil), // 27: hyapp.game.v1.CronBatchRequest + (*CronBatchResponse)(nil), // 28: hyapp.game.v1.CronBatchResponse + (*ListPlatformsRequest)(nil), // 29: hyapp.game.v1.ListPlatformsRequest + (*ListPlatformsResponse)(nil), // 30: hyapp.game.v1.ListPlatformsResponse + (*UpsertPlatformRequest)(nil), // 31: hyapp.game.v1.UpsertPlatformRequest + (*PlatformResponse)(nil), // 32: hyapp.game.v1.PlatformResponse + (*ListCatalogRequest)(nil), // 33: hyapp.game.v1.ListCatalogRequest + (*ListCatalogResponse)(nil), // 34: hyapp.game.v1.ListCatalogResponse + (*UpsertCatalogRequest)(nil), // 35: hyapp.game.v1.UpsertCatalogRequest + (*CatalogResponse)(nil), // 36: hyapp.game.v1.CatalogResponse + (*SetGameStatusRequest)(nil), // 37: hyapp.game.v1.SetGameStatusRequest + (*DeleteCatalogRequest)(nil), // 38: hyapp.game.v1.DeleteCatalogRequest + (*DeleteCatalogResponse)(nil), // 39: hyapp.game.v1.DeleteCatalogResponse + (*ListSelfGamesRequest)(nil), // 40: hyapp.game.v1.ListSelfGamesRequest + (*ListSelfGamesResponse)(nil), // 41: hyapp.game.v1.ListSelfGamesResponse + (*UpdateDiceConfigRequest)(nil), // 42: hyapp.game.v1.UpdateDiceConfigRequest + (*DicePoolAdjustment)(nil), // 43: hyapp.game.v1.DicePoolAdjustment + (*AdjustDicePoolRequest)(nil), // 44: hyapp.game.v1.AdjustDicePoolRequest + (*AdjustDicePoolResponse)(nil), // 45: hyapp.game.v1.AdjustDicePoolResponse + (*DiceRobot)(nil), // 46: hyapp.game.v1.DiceRobot + (*ListDiceRobotsRequest)(nil), // 47: hyapp.game.v1.ListDiceRobotsRequest + (*ListDiceRobotsResponse)(nil), // 48: hyapp.game.v1.ListDiceRobotsResponse + (*RegisterDiceRobotsRequest)(nil), // 49: hyapp.game.v1.RegisterDiceRobotsRequest + (*RegisterDiceRobotsResponse)(nil), // 50: hyapp.game.v1.RegisterDiceRobotsResponse + (*SetDiceRobotStatusRequest)(nil), // 51: hyapp.game.v1.SetDiceRobotStatusRequest + (*DeleteDiceRobotRequest)(nil), // 52: hyapp.game.v1.DeleteDiceRobotRequest + (*DeleteDiceRobotResponse)(nil), // 53: hyapp.game.v1.DeleteDiceRobotResponse + (*DiceRobotResponse)(nil), // 54: hyapp.game.v1.DiceRobotResponse + nil, // 55: hyapp.game.v1.CallbackRequest.HeadersEntry + nil, // 56: hyapp.game.v1.CallbackRequest.QueryEntry } var file_proto_game_v1_game_proto_depIdxs = []int32{ 0, // 0: hyapp.game.v1.ListGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta @@ -2303,51 +4639,104 @@ var file_proto_game_v1_game_proto_depIdxs = []int32{ 0, // 3: hyapp.game.v1.GetBridgeScriptRequest.meta:type_name -> hyapp.game.v1.RequestMeta 8, // 4: hyapp.game.v1.GetBridgeScriptResponse.config:type_name -> hyapp.game.v1.GameBridgeScriptConfig 0, // 5: hyapp.game.v1.LaunchGameRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 6: hyapp.game.v1.CallbackRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 27, // 7: hyapp.game.v1.CallbackRequest.headers:type_name -> hyapp.game.v1.CallbackRequest.HeadersEntry - 28, // 8: hyapp.game.v1.CallbackRequest.query:type_name -> hyapp.game.v1.CallbackRequest.QueryEntry - 0, // 9: hyapp.game.v1.CronBatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 10: hyapp.game.v1.ListPlatformsRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 1, // 11: hyapp.game.v1.ListPlatformsResponse.platforms:type_name -> hyapp.game.v1.GamePlatform - 0, // 12: hyapp.game.v1.UpsertPlatformRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 1, // 13: hyapp.game.v1.UpsertPlatformRequest.platform:type_name -> hyapp.game.v1.GamePlatform - 1, // 14: hyapp.game.v1.PlatformResponse.platform:type_name -> hyapp.game.v1.GamePlatform - 0, // 15: hyapp.game.v1.ListCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 2, // 16: hyapp.game.v1.ListCatalogResponse.games:type_name -> hyapp.game.v1.GameCatalogItem - 0, // 17: hyapp.game.v1.UpsertCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 2, // 18: hyapp.game.v1.UpsertCatalogRequest.game:type_name -> hyapp.game.v1.GameCatalogItem - 2, // 19: hyapp.game.v1.CatalogResponse.game:type_name -> hyapp.game.v1.GameCatalogItem - 0, // 20: hyapp.game.v1.SetGameStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 21: hyapp.game.v1.DeleteCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 4, // 22: hyapp.game.v1.GameAppService.ListGames:input_type -> hyapp.game.v1.ListGamesRequest - 6, // 23: hyapp.game.v1.GameAppService.ListRecentGames:input_type -> hyapp.game.v1.ListRecentGamesRequest - 7, // 24: hyapp.game.v1.GameAppService.GetBridgeScript:input_type -> hyapp.game.v1.GetBridgeScriptRequest - 10, // 25: hyapp.game.v1.GameAppService.LaunchGame:input_type -> hyapp.game.v1.LaunchGameRequest - 12, // 26: hyapp.game.v1.GameCallbackService.HandleCallback:input_type -> hyapp.game.v1.CallbackRequest - 14, // 27: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:input_type -> hyapp.game.v1.CronBatchRequest - 16, // 28: hyapp.game.v1.GameAdminService.ListPlatforms:input_type -> hyapp.game.v1.ListPlatformsRequest - 18, // 29: hyapp.game.v1.GameAdminService.UpsertPlatform:input_type -> hyapp.game.v1.UpsertPlatformRequest - 20, // 30: hyapp.game.v1.GameAdminService.ListCatalog:input_type -> hyapp.game.v1.ListCatalogRequest - 22, // 31: hyapp.game.v1.GameAdminService.UpsertCatalog:input_type -> hyapp.game.v1.UpsertCatalogRequest - 24, // 32: hyapp.game.v1.GameAdminService.SetGameStatus:input_type -> hyapp.game.v1.SetGameStatusRequest - 25, // 33: hyapp.game.v1.GameAdminService.DeleteCatalog:input_type -> hyapp.game.v1.DeleteCatalogRequest - 5, // 34: hyapp.game.v1.GameAppService.ListGames:output_type -> hyapp.game.v1.ListGamesResponse - 5, // 35: hyapp.game.v1.GameAppService.ListRecentGames:output_type -> hyapp.game.v1.ListGamesResponse - 9, // 36: hyapp.game.v1.GameAppService.GetBridgeScript:output_type -> hyapp.game.v1.GetBridgeScriptResponse - 11, // 37: hyapp.game.v1.GameAppService.LaunchGame:output_type -> hyapp.game.v1.LaunchGameResponse - 13, // 38: hyapp.game.v1.GameCallbackService.HandleCallback:output_type -> hyapp.game.v1.CallbackResponse - 15, // 39: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:output_type -> hyapp.game.v1.CronBatchResponse - 17, // 40: hyapp.game.v1.GameAdminService.ListPlatforms:output_type -> hyapp.game.v1.ListPlatformsResponse - 19, // 41: hyapp.game.v1.GameAdminService.UpsertPlatform:output_type -> hyapp.game.v1.PlatformResponse - 21, // 42: hyapp.game.v1.GameAdminService.ListCatalog:output_type -> hyapp.game.v1.ListCatalogResponse - 23, // 43: hyapp.game.v1.GameAdminService.UpsertCatalog:output_type -> hyapp.game.v1.CatalogResponse - 23, // 44: hyapp.game.v1.GameAdminService.SetGameStatus:output_type -> hyapp.game.v1.CatalogResponse - 26, // 45: hyapp.game.v1.GameAdminService.DeleteCatalog:output_type -> hyapp.game.v1.DeleteCatalogResponse - 34, // [34:46] is the sub-list for method output_type - 22, // [22:34] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 12, // 6: hyapp.game.v1.DiceMatch.participants:type_name -> hyapp.game.v1.DiceParticipant + 14, // 7: hyapp.game.v1.DiceConfig.stake_options:type_name -> hyapp.game.v1.DiceStakeOption + 0, // 8: hyapp.game.v1.GetDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 15, // 9: hyapp.game.v1.DiceConfigResponse.config:type_name -> hyapp.game.v1.DiceConfig + 0, // 10: hyapp.game.v1.CreateDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 11: hyapp.game.v1.JoinDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 12: hyapp.game.v1.GetDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 13: hyapp.game.v1.RollDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 14: hyapp.game.v1.MatchDiceRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 15: hyapp.game.v1.CancelDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 13, // 16: hyapp.game.v1.DiceMatchResponse.match:type_name -> hyapp.game.v1.DiceMatch + 0, // 17: hyapp.game.v1.CallbackRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 55, // 18: hyapp.game.v1.CallbackRequest.headers:type_name -> hyapp.game.v1.CallbackRequest.HeadersEntry + 56, // 19: hyapp.game.v1.CallbackRequest.query:type_name -> hyapp.game.v1.CallbackRequest.QueryEntry + 0, // 20: hyapp.game.v1.CronBatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 21: hyapp.game.v1.ListPlatformsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 1, // 22: hyapp.game.v1.ListPlatformsResponse.platforms:type_name -> hyapp.game.v1.GamePlatform + 0, // 23: hyapp.game.v1.UpsertPlatformRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 1, // 24: hyapp.game.v1.UpsertPlatformRequest.platform:type_name -> hyapp.game.v1.GamePlatform + 1, // 25: hyapp.game.v1.PlatformResponse.platform:type_name -> hyapp.game.v1.GamePlatform + 0, // 26: hyapp.game.v1.ListCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 2, // 27: hyapp.game.v1.ListCatalogResponse.games:type_name -> hyapp.game.v1.GameCatalogItem + 0, // 28: hyapp.game.v1.UpsertCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 2, // 29: hyapp.game.v1.UpsertCatalogRequest.game:type_name -> hyapp.game.v1.GameCatalogItem + 2, // 30: hyapp.game.v1.CatalogResponse.game:type_name -> hyapp.game.v1.GameCatalogItem + 0, // 31: hyapp.game.v1.SetGameStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 32: hyapp.game.v1.DeleteCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 33: hyapp.game.v1.ListSelfGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 15, // 34: hyapp.game.v1.ListSelfGamesResponse.games:type_name -> hyapp.game.v1.DiceConfig + 0, // 35: hyapp.game.v1.UpdateDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 15, // 36: hyapp.game.v1.UpdateDiceConfigRequest.config:type_name -> hyapp.game.v1.DiceConfig + 0, // 37: hyapp.game.v1.AdjustDicePoolRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 43, // 38: hyapp.game.v1.AdjustDicePoolResponse.adjustment:type_name -> hyapp.game.v1.DicePoolAdjustment + 15, // 39: hyapp.game.v1.AdjustDicePoolResponse.config:type_name -> hyapp.game.v1.DiceConfig + 0, // 40: hyapp.game.v1.ListDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 46, // 41: hyapp.game.v1.ListDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot + 0, // 42: hyapp.game.v1.RegisterDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 46, // 43: hyapp.game.v1.RegisterDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot + 0, // 44: hyapp.game.v1.SetDiceRobotStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 45: hyapp.game.v1.DeleteDiceRobotRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 46, // 46: hyapp.game.v1.DiceRobotResponse.robot:type_name -> hyapp.game.v1.DiceRobot + 4, // 47: hyapp.game.v1.GameAppService.ListGames:input_type -> hyapp.game.v1.ListGamesRequest + 6, // 48: hyapp.game.v1.GameAppService.ListRecentGames:input_type -> hyapp.game.v1.ListRecentGamesRequest + 7, // 49: hyapp.game.v1.GameAppService.GetBridgeScript:input_type -> hyapp.game.v1.GetBridgeScriptRequest + 10, // 50: hyapp.game.v1.GameAppService.LaunchGame:input_type -> hyapp.game.v1.LaunchGameRequest + 16, // 51: hyapp.game.v1.GameAppService.GetDiceConfig:input_type -> hyapp.game.v1.GetDiceConfigRequest + 22, // 52: hyapp.game.v1.GameAppService.MatchDice:input_type -> hyapp.game.v1.MatchDiceRequest + 18, // 53: hyapp.game.v1.GameAppService.CreateDiceMatch:input_type -> hyapp.game.v1.CreateDiceMatchRequest + 19, // 54: hyapp.game.v1.GameAppService.JoinDiceMatch:input_type -> hyapp.game.v1.JoinDiceMatchRequest + 20, // 55: hyapp.game.v1.GameAppService.GetDiceMatch:input_type -> hyapp.game.v1.GetDiceMatchRequest + 21, // 56: hyapp.game.v1.GameAppService.RollDiceMatch:input_type -> hyapp.game.v1.RollDiceMatchRequest + 23, // 57: hyapp.game.v1.GameAppService.CancelDiceMatch:input_type -> hyapp.game.v1.CancelDiceMatchRequest + 25, // 58: hyapp.game.v1.GameCallbackService.HandleCallback:input_type -> hyapp.game.v1.CallbackRequest + 27, // 59: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:input_type -> hyapp.game.v1.CronBatchRequest + 29, // 60: hyapp.game.v1.GameAdminService.ListPlatforms:input_type -> hyapp.game.v1.ListPlatformsRequest + 31, // 61: hyapp.game.v1.GameAdminService.UpsertPlatform:input_type -> hyapp.game.v1.UpsertPlatformRequest + 33, // 62: hyapp.game.v1.GameAdminService.ListCatalog:input_type -> hyapp.game.v1.ListCatalogRequest + 35, // 63: hyapp.game.v1.GameAdminService.UpsertCatalog:input_type -> hyapp.game.v1.UpsertCatalogRequest + 37, // 64: hyapp.game.v1.GameAdminService.SetGameStatus:input_type -> hyapp.game.v1.SetGameStatusRequest + 38, // 65: hyapp.game.v1.GameAdminService.DeleteCatalog:input_type -> hyapp.game.v1.DeleteCatalogRequest + 40, // 66: hyapp.game.v1.GameAdminService.ListSelfGames:input_type -> hyapp.game.v1.ListSelfGamesRequest + 42, // 67: hyapp.game.v1.GameAdminService.UpdateDiceConfig:input_type -> hyapp.game.v1.UpdateDiceConfigRequest + 44, // 68: hyapp.game.v1.GameAdminService.AdjustDicePool:input_type -> hyapp.game.v1.AdjustDicePoolRequest + 47, // 69: hyapp.game.v1.GameAdminService.ListDiceRobots:input_type -> hyapp.game.v1.ListDiceRobotsRequest + 49, // 70: hyapp.game.v1.GameAdminService.RegisterDiceRobots:input_type -> hyapp.game.v1.RegisterDiceRobotsRequest + 51, // 71: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:input_type -> hyapp.game.v1.SetDiceRobotStatusRequest + 52, // 72: hyapp.game.v1.GameAdminService.DeleteDiceRobot:input_type -> hyapp.game.v1.DeleteDiceRobotRequest + 5, // 73: hyapp.game.v1.GameAppService.ListGames:output_type -> hyapp.game.v1.ListGamesResponse + 5, // 74: hyapp.game.v1.GameAppService.ListRecentGames:output_type -> hyapp.game.v1.ListGamesResponse + 9, // 75: hyapp.game.v1.GameAppService.GetBridgeScript:output_type -> hyapp.game.v1.GetBridgeScriptResponse + 11, // 76: hyapp.game.v1.GameAppService.LaunchGame:output_type -> hyapp.game.v1.LaunchGameResponse + 17, // 77: hyapp.game.v1.GameAppService.GetDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse + 24, // 78: hyapp.game.v1.GameAppService.MatchDice:output_type -> hyapp.game.v1.DiceMatchResponse + 24, // 79: hyapp.game.v1.GameAppService.CreateDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 24, // 80: hyapp.game.v1.GameAppService.JoinDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 24, // 81: hyapp.game.v1.GameAppService.GetDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 24, // 82: hyapp.game.v1.GameAppService.RollDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 24, // 83: hyapp.game.v1.GameAppService.CancelDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 26, // 84: hyapp.game.v1.GameCallbackService.HandleCallback:output_type -> hyapp.game.v1.CallbackResponse + 28, // 85: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:output_type -> hyapp.game.v1.CronBatchResponse + 30, // 86: hyapp.game.v1.GameAdminService.ListPlatforms:output_type -> hyapp.game.v1.ListPlatformsResponse + 32, // 87: hyapp.game.v1.GameAdminService.UpsertPlatform:output_type -> hyapp.game.v1.PlatformResponse + 34, // 88: hyapp.game.v1.GameAdminService.ListCatalog:output_type -> hyapp.game.v1.ListCatalogResponse + 36, // 89: hyapp.game.v1.GameAdminService.UpsertCatalog:output_type -> hyapp.game.v1.CatalogResponse + 36, // 90: hyapp.game.v1.GameAdminService.SetGameStatus:output_type -> hyapp.game.v1.CatalogResponse + 39, // 91: hyapp.game.v1.GameAdminService.DeleteCatalog:output_type -> hyapp.game.v1.DeleteCatalogResponse + 41, // 92: hyapp.game.v1.GameAdminService.ListSelfGames:output_type -> hyapp.game.v1.ListSelfGamesResponse + 17, // 93: hyapp.game.v1.GameAdminService.UpdateDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse + 45, // 94: hyapp.game.v1.GameAdminService.AdjustDicePool:output_type -> hyapp.game.v1.AdjustDicePoolResponse + 48, // 95: hyapp.game.v1.GameAdminService.ListDiceRobots:output_type -> hyapp.game.v1.ListDiceRobotsResponse + 50, // 96: hyapp.game.v1.GameAdminService.RegisterDiceRobots:output_type -> hyapp.game.v1.RegisterDiceRobotsResponse + 54, // 97: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:output_type -> hyapp.game.v1.DiceRobotResponse + 53, // 98: hyapp.game.v1.GameAdminService.DeleteDiceRobot:output_type -> hyapp.game.v1.DeleteDiceRobotResponse + 73, // [73:99] is the sub-list for method output_type + 47, // [47:73] is the sub-list for method input_type + 47, // [47:47] is the sub-list for extension type_name + 47, // [47:47] is the sub-list for extension extendee + 0, // [0:47] is the sub-list for field type_name } func init() { file_proto_game_v1_game_proto_init() } @@ -2361,7 +4750,7 @@ func file_proto_game_v1_game_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_game_v1_game_proto_rawDesc), len(file_proto_game_v1_game_proto_rawDesc)), NumEnums: 0, - NumMessages: 29, + NumMessages: 57, NumExtensions: 0, NumServices: 4, }, diff --git a/api/proto/game/v1/game.proto b/api/proto/game/v1/game.proto index 1f17e87e..fe2a68e8 100644 --- a/api/proto/game/v1/game.proto +++ b/api/proto/game/v1/game.proto @@ -130,6 +130,131 @@ message LaunchGameResponse { int32 safe_height = 6; } +message DiceParticipant { + int64 user_id = 1; + int32 seat_no = 2; + string status = 3; + int64 stake_coin = 4; + repeated int32 dice_points = 5; + string result = 6; + int64 payout_coin = 7; + int64 balance_after = 8; + int64 joined_at_ms = 9; + int64 updated_at_ms = 10; + string participant_type = 11; + bool is_robot = 12; +} + +message DiceMatch { + string app_code = 1; + string match_id = 2; + string game_id = 3; + string room_id = 4; + int64 region_id = 5; + int32 min_players = 6; + int32 max_players = 7; + int32 current_players = 8; + int64 stake_coin = 9; + int32 round_no = 10; + string status = 11; + string result = 12; + repeated DiceParticipant participants = 13; + int64 join_deadline_ms = 14; + int64 created_at_ms = 15; + int64 updated_at_ms = 16; + int64 settled_at_ms = 17; + string phase = 18; + int64 phase_deadline_ms = 19; + int32 fee_bps = 20; + int32 pool_bps = 21; + string match_mode = 22; + string forced_result = 23; + int64 ready_at_ms = 24; + int64 canceled_at_ms = 25; + int64 pool_delta_coin = 26; +} + +message DiceStakeOption { + int64 stake_coin = 1; + bool enabled = 2; + int32 sort_order = 3; +} + +message DiceConfig { + string app_code = 1; + string game_id = 2; + string status = 3; + repeated DiceStakeOption stake_options = 4; + int32 fee_bps = 5; + int32 pool_bps = 6; + int32 min_players = 7; + int32 max_players = 8; + bool robot_enabled = 9; + int64 robot_match_wait_ms = 10; + int64 pool_balance_coin = 11; + int64 created_at_ms = 12; + int64 updated_at_ms = 13; +} + +message GetDiceConfigRequest { + RequestMeta meta = 1; + string game_id = 2; +} + +message DiceConfigResponse { + DiceConfig config = 1; + int64 server_time_ms = 2; +} + +message CreateDiceMatchRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string game_id = 3; + string room_id = 4; + int64 region_id = 5; + int64 stake_coin = 6; + int32 min_players = 7; + int32 max_players = 8; +} + +message JoinDiceMatchRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string match_id = 3; +} + +message GetDiceMatchRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string match_id = 3; +} + +message RollDiceMatchRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string match_id = 3; +} + +message MatchDiceRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string game_id = 3; + string room_id = 4; + int64 region_id = 5; + int64 stake_coin = 6; +} + +message CancelDiceMatchRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string match_id = 3; +} + +message DiceMatchResponse { + DiceMatch match = 1; + int64 server_time_ms = 2; +} + message CallbackRequest { RequestMeta meta = 1; string platform_code = 2; @@ -220,11 +345,114 @@ message DeleteCatalogResponse { int64 server_time_ms = 1; } +message ListSelfGamesRequest { + RequestMeta meta = 1; +} + +message ListSelfGamesResponse { + repeated DiceConfig games = 1; + int64 server_time_ms = 2; +} + +message UpdateDiceConfigRequest { + RequestMeta meta = 1; + DiceConfig config = 2; +} + +message DicePoolAdjustment { + string adjustment_id = 1; + string game_id = 2; + int64 amount_coin = 3; + string direction = 4; + string reason = 5; + int64 balance_after = 6; + int64 created_at_ms = 7; +} + +message AdjustDicePoolRequest { + RequestMeta meta = 1; + string game_id = 2; + int64 amount_coin = 3; + string direction = 4; + string reason = 5; +} + +message AdjustDicePoolResponse { + DicePoolAdjustment adjustment = 1; + DiceConfig config = 2; + int64 server_time_ms = 3; +} + +message DiceRobot { + string app_code = 1; + string game_id = 2; + int64 user_id = 3; + string status = 4; + int64 created_by_admin_id = 5; + int64 created_at_ms = 6; + int64 updated_at_ms = 7; +} + +message ListDiceRobotsRequest { + RequestMeta meta = 1; + string game_id = 2; + string status = 3; + int32 page_size = 4; + string cursor = 5; +} + +message ListDiceRobotsResponse { + repeated DiceRobot robots = 1; + string next_cursor = 2; + int64 server_time_ms = 3; +} + +message RegisterDiceRobotsRequest { + RequestMeta meta = 1; + string game_id = 2; + repeated int64 user_ids = 3; +} + +message RegisterDiceRobotsResponse { + repeated DiceRobot robots = 1; + int64 server_time_ms = 2; +} + +message SetDiceRobotStatusRequest { + RequestMeta meta = 1; + string game_id = 2; + int64 user_id = 3; + string status = 4; +} + +message DeleteDiceRobotRequest { + RequestMeta meta = 1; + string game_id = 2; + int64 user_id = 3; +} + +message DeleteDiceRobotResponse { + bool deleted = 1; + int64 server_time_ms = 2; +} + +message DiceRobotResponse { + DiceRobot robot = 1; + int64 server_time_ms = 2; +} + service GameAppService { rpc ListGames(ListGamesRequest) returns (ListGamesResponse); rpc ListRecentGames(ListRecentGamesRequest) returns (ListGamesResponse); rpc GetBridgeScript(GetBridgeScriptRequest) returns (GetBridgeScriptResponse); rpc LaunchGame(LaunchGameRequest) returns (LaunchGameResponse); + rpc GetDiceConfig(GetDiceConfigRequest) returns (DiceConfigResponse); + rpc MatchDice(MatchDiceRequest) returns (DiceMatchResponse); + rpc CreateDiceMatch(CreateDiceMatchRequest) returns (DiceMatchResponse); + rpc JoinDiceMatch(JoinDiceMatchRequest) returns (DiceMatchResponse); + rpc GetDiceMatch(GetDiceMatchRequest) returns (DiceMatchResponse); + rpc RollDiceMatch(RollDiceMatchRequest) returns (DiceMatchResponse); + rpc CancelDiceMatch(CancelDiceMatchRequest) returns (DiceMatchResponse); } service GameCallbackService { @@ -243,4 +471,11 @@ service GameAdminService { rpc UpsertCatalog(UpsertCatalogRequest) returns (CatalogResponse); rpc SetGameStatus(SetGameStatusRequest) returns (CatalogResponse); rpc DeleteCatalog(DeleteCatalogRequest) returns (DeleteCatalogResponse); + rpc ListSelfGames(ListSelfGamesRequest) returns (ListSelfGamesResponse); + rpc UpdateDiceConfig(UpdateDiceConfigRequest) returns (DiceConfigResponse); + rpc AdjustDicePool(AdjustDicePoolRequest) returns (AdjustDicePoolResponse); + rpc ListDiceRobots(ListDiceRobotsRequest) returns (ListDiceRobotsResponse); + rpc RegisterDiceRobots(RegisterDiceRobotsRequest) returns (RegisterDiceRobotsResponse); + rpc SetDiceRobotStatus(SetDiceRobotStatusRequest) returns (DiceRobotResponse); + rpc DeleteDiceRobot(DeleteDiceRobotRequest) returns (DeleteDiceRobotResponse); } diff --git a/api/proto/game/v1/game_grpc.pb.go b/api/proto/game/v1/game_grpc.pb.go index 4dbd5ff5..9b10a0f8 100644 --- a/api/proto/game/v1/game_grpc.pb.go +++ b/api/proto/game/v1/game_grpc.pb.go @@ -23,6 +23,13 @@ const ( GameAppService_ListRecentGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListRecentGames" GameAppService_GetBridgeScript_FullMethodName = "/hyapp.game.v1.GameAppService/GetBridgeScript" GameAppService_LaunchGame_FullMethodName = "/hyapp.game.v1.GameAppService/LaunchGame" + GameAppService_GetDiceConfig_FullMethodName = "/hyapp.game.v1.GameAppService/GetDiceConfig" + GameAppService_MatchDice_FullMethodName = "/hyapp.game.v1.GameAppService/MatchDice" + GameAppService_CreateDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/CreateDiceMatch" + GameAppService_JoinDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/JoinDiceMatch" + GameAppService_GetDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/GetDiceMatch" + GameAppService_RollDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/RollDiceMatch" + GameAppService_CancelDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/CancelDiceMatch" ) // GameAppServiceClient is the client API for GameAppService service. @@ -33,6 +40,13 @@ type GameAppServiceClient interface { ListRecentGames(ctx context.Context, in *ListRecentGamesRequest, opts ...grpc.CallOption) (*ListGamesResponse, error) GetBridgeScript(ctx context.Context, in *GetBridgeScriptRequest, opts ...grpc.CallOption) (*GetBridgeScriptResponse, error) LaunchGame(ctx context.Context, in *LaunchGameRequest, opts ...grpc.CallOption) (*LaunchGameResponse, error) + GetDiceConfig(ctx context.Context, in *GetDiceConfigRequest, opts ...grpc.CallOption) (*DiceConfigResponse, error) + MatchDice(ctx context.Context, in *MatchDiceRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) + CreateDiceMatch(ctx context.Context, in *CreateDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) + JoinDiceMatch(ctx context.Context, in *JoinDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) + GetDiceMatch(ctx context.Context, in *GetDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) + RollDiceMatch(ctx context.Context, in *RollDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) + CancelDiceMatch(ctx context.Context, in *CancelDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) } type gameAppServiceClient struct { @@ -83,6 +97,76 @@ func (c *gameAppServiceClient) LaunchGame(ctx context.Context, in *LaunchGameReq return out, nil } +func (c *gameAppServiceClient) GetDiceConfig(ctx context.Context, in *GetDiceConfigRequest, opts ...grpc.CallOption) (*DiceConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceConfigResponse) + err := c.cc.Invoke(ctx, GameAppService_GetDiceConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) MatchDice(ctx context.Context, in *MatchDiceRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceMatchResponse) + err := c.cc.Invoke(ctx, GameAppService_MatchDice_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) CreateDiceMatch(ctx context.Context, in *CreateDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceMatchResponse) + err := c.cc.Invoke(ctx, GameAppService_CreateDiceMatch_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) JoinDiceMatch(ctx context.Context, in *JoinDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceMatchResponse) + err := c.cc.Invoke(ctx, GameAppService_JoinDiceMatch_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) GetDiceMatch(ctx context.Context, in *GetDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceMatchResponse) + err := c.cc.Invoke(ctx, GameAppService_GetDiceMatch_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) RollDiceMatch(ctx context.Context, in *RollDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceMatchResponse) + err := c.cc.Invoke(ctx, GameAppService_RollDiceMatch_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) CancelDiceMatch(ctx context.Context, in *CancelDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceMatchResponse) + err := c.cc.Invoke(ctx, GameAppService_CancelDiceMatch_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // GameAppServiceServer is the server API for GameAppService service. // All implementations must embed UnimplementedGameAppServiceServer // for forward compatibility. @@ -91,6 +175,13 @@ type GameAppServiceServer interface { ListRecentGames(context.Context, *ListRecentGamesRequest) (*ListGamesResponse, error) GetBridgeScript(context.Context, *GetBridgeScriptRequest) (*GetBridgeScriptResponse, error) LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) + GetDiceConfig(context.Context, *GetDiceConfigRequest) (*DiceConfigResponse, error) + MatchDice(context.Context, *MatchDiceRequest) (*DiceMatchResponse, error) + CreateDiceMatch(context.Context, *CreateDiceMatchRequest) (*DiceMatchResponse, error) + JoinDiceMatch(context.Context, *JoinDiceMatchRequest) (*DiceMatchResponse, error) + GetDiceMatch(context.Context, *GetDiceMatchRequest) (*DiceMatchResponse, error) + RollDiceMatch(context.Context, *RollDiceMatchRequest) (*DiceMatchResponse, error) + CancelDiceMatch(context.Context, *CancelDiceMatchRequest) (*DiceMatchResponse, error) mustEmbedUnimplementedGameAppServiceServer() } @@ -113,6 +204,27 @@ func (UnimplementedGameAppServiceServer) GetBridgeScript(context.Context, *GetBr func (UnimplementedGameAppServiceServer) LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) { return nil, status.Error(codes.Unimplemented, "method LaunchGame not implemented") } +func (UnimplementedGameAppServiceServer) GetDiceConfig(context.Context, *GetDiceConfigRequest) (*DiceConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDiceConfig not implemented") +} +func (UnimplementedGameAppServiceServer) MatchDice(context.Context, *MatchDiceRequest) (*DiceMatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method MatchDice not implemented") +} +func (UnimplementedGameAppServiceServer) CreateDiceMatch(context.Context, *CreateDiceMatchRequest) (*DiceMatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateDiceMatch not implemented") +} +func (UnimplementedGameAppServiceServer) JoinDiceMatch(context.Context, *JoinDiceMatchRequest) (*DiceMatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method JoinDiceMatch not implemented") +} +func (UnimplementedGameAppServiceServer) GetDiceMatch(context.Context, *GetDiceMatchRequest) (*DiceMatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDiceMatch not implemented") +} +func (UnimplementedGameAppServiceServer) RollDiceMatch(context.Context, *RollDiceMatchRequest) (*DiceMatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RollDiceMatch not implemented") +} +func (UnimplementedGameAppServiceServer) CancelDiceMatch(context.Context, *CancelDiceMatchRequest) (*DiceMatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CancelDiceMatch not implemented") +} func (UnimplementedGameAppServiceServer) mustEmbedUnimplementedGameAppServiceServer() {} func (UnimplementedGameAppServiceServer) testEmbeddedByValue() {} @@ -206,6 +318,132 @@ func _GameAppService_LaunchGame_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _GameAppService_GetDiceConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDiceConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).GetDiceConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_GetDiceConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).GetDiceConfig(ctx, req.(*GetDiceConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_MatchDice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MatchDiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).MatchDice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_MatchDice_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).MatchDice(ctx, req.(*MatchDiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_CreateDiceMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateDiceMatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).CreateDiceMatch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_CreateDiceMatch_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).CreateDiceMatch(ctx, req.(*CreateDiceMatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_JoinDiceMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(JoinDiceMatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).JoinDiceMatch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_JoinDiceMatch_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).JoinDiceMatch(ctx, req.(*JoinDiceMatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_GetDiceMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDiceMatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).GetDiceMatch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_GetDiceMatch_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).GetDiceMatch(ctx, req.(*GetDiceMatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_RollDiceMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RollDiceMatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).RollDiceMatch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_RollDiceMatch_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).RollDiceMatch(ctx, req.(*RollDiceMatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_CancelDiceMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CancelDiceMatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).CancelDiceMatch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_CancelDiceMatch_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).CancelDiceMatch(ctx, req.(*CancelDiceMatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + // GameAppService_ServiceDesc is the grpc.ServiceDesc for GameAppService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -229,6 +467,34 @@ var GameAppService_ServiceDesc = grpc.ServiceDesc{ MethodName: "LaunchGame", Handler: _GameAppService_LaunchGame_Handler, }, + { + MethodName: "GetDiceConfig", + Handler: _GameAppService_GetDiceConfig_Handler, + }, + { + MethodName: "MatchDice", + Handler: _GameAppService_MatchDice_Handler, + }, + { + MethodName: "CreateDiceMatch", + Handler: _GameAppService_CreateDiceMatch_Handler, + }, + { + MethodName: "JoinDiceMatch", + Handler: _GameAppService_JoinDiceMatch_Handler, + }, + { + MethodName: "GetDiceMatch", + Handler: _GameAppService_GetDiceMatch_Handler, + }, + { + MethodName: "RollDiceMatch", + Handler: _GameAppService_RollDiceMatch_Handler, + }, + { + MethodName: "CancelDiceMatch", + Handler: _GameAppService_CancelDiceMatch_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/game/v1/game.proto", @@ -443,12 +709,19 @@ var GameCronService_ServiceDesc = grpc.ServiceDesc{ } const ( - GameAdminService_ListPlatforms_FullMethodName = "/hyapp.game.v1.GameAdminService/ListPlatforms" - GameAdminService_UpsertPlatform_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertPlatform" - GameAdminService_ListCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/ListCatalog" - GameAdminService_UpsertCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertCatalog" - GameAdminService_SetGameStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetGameStatus" - GameAdminService_DeleteCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteCatalog" + GameAdminService_ListPlatforms_FullMethodName = "/hyapp.game.v1.GameAdminService/ListPlatforms" + GameAdminService_UpsertPlatform_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertPlatform" + GameAdminService_ListCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/ListCatalog" + GameAdminService_UpsertCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertCatalog" + GameAdminService_SetGameStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetGameStatus" + GameAdminService_DeleteCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteCatalog" + GameAdminService_ListSelfGames_FullMethodName = "/hyapp.game.v1.GameAdminService/ListSelfGames" + GameAdminService_UpdateDiceConfig_FullMethodName = "/hyapp.game.v1.GameAdminService/UpdateDiceConfig" + GameAdminService_AdjustDicePool_FullMethodName = "/hyapp.game.v1.GameAdminService/AdjustDicePool" + GameAdminService_ListDiceRobots_FullMethodName = "/hyapp.game.v1.GameAdminService/ListDiceRobots" + GameAdminService_RegisterDiceRobots_FullMethodName = "/hyapp.game.v1.GameAdminService/RegisterDiceRobots" + GameAdminService_SetDiceRobotStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetDiceRobotStatus" + GameAdminService_DeleteDiceRobot_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteDiceRobot" ) // GameAdminServiceClient is the client API for GameAdminService service. @@ -461,6 +734,13 @@ type GameAdminServiceClient interface { UpsertCatalog(ctx context.Context, in *UpsertCatalogRequest, opts ...grpc.CallOption) (*CatalogResponse, error) SetGameStatus(ctx context.Context, in *SetGameStatusRequest, opts ...grpc.CallOption) (*CatalogResponse, error) DeleteCatalog(ctx context.Context, in *DeleteCatalogRequest, opts ...grpc.CallOption) (*DeleteCatalogResponse, error) + ListSelfGames(ctx context.Context, in *ListSelfGamesRequest, opts ...grpc.CallOption) (*ListSelfGamesResponse, error) + UpdateDiceConfig(ctx context.Context, in *UpdateDiceConfigRequest, opts ...grpc.CallOption) (*DiceConfigResponse, error) + AdjustDicePool(ctx context.Context, in *AdjustDicePoolRequest, opts ...grpc.CallOption) (*AdjustDicePoolResponse, error) + ListDiceRobots(ctx context.Context, in *ListDiceRobotsRequest, opts ...grpc.CallOption) (*ListDiceRobotsResponse, error) + RegisterDiceRobots(ctx context.Context, in *RegisterDiceRobotsRequest, opts ...grpc.CallOption) (*RegisterDiceRobotsResponse, error) + SetDiceRobotStatus(ctx context.Context, in *SetDiceRobotStatusRequest, opts ...grpc.CallOption) (*DiceRobotResponse, error) + DeleteDiceRobot(ctx context.Context, in *DeleteDiceRobotRequest, opts ...grpc.CallOption) (*DeleteDiceRobotResponse, error) } type gameAdminServiceClient struct { @@ -531,6 +811,76 @@ func (c *gameAdminServiceClient) DeleteCatalog(ctx context.Context, in *DeleteCa return out, nil } +func (c *gameAdminServiceClient) ListSelfGames(ctx context.Context, in *ListSelfGamesRequest, opts ...grpc.CallOption) (*ListSelfGamesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSelfGamesResponse) + err := c.cc.Invoke(ctx, GameAdminService_ListSelfGames_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) UpdateDiceConfig(ctx context.Context, in *UpdateDiceConfigRequest, opts ...grpc.CallOption) (*DiceConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceConfigResponse) + err := c.cc.Invoke(ctx, GameAdminService_UpdateDiceConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) AdjustDicePool(ctx context.Context, in *AdjustDicePoolRequest, opts ...grpc.CallOption) (*AdjustDicePoolResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdjustDicePoolResponse) + err := c.cc.Invoke(ctx, GameAdminService_AdjustDicePool_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) ListDiceRobots(ctx context.Context, in *ListDiceRobotsRequest, opts ...grpc.CallOption) (*ListDiceRobotsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListDiceRobotsResponse) + err := c.cc.Invoke(ctx, GameAdminService_ListDiceRobots_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) RegisterDiceRobots(ctx context.Context, in *RegisterDiceRobotsRequest, opts ...grpc.CallOption) (*RegisterDiceRobotsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RegisterDiceRobotsResponse) + err := c.cc.Invoke(ctx, GameAdminService_RegisterDiceRobots_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) SetDiceRobotStatus(ctx context.Context, in *SetDiceRobotStatusRequest, opts ...grpc.CallOption) (*DiceRobotResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DiceRobotResponse) + err := c.cc.Invoke(ctx, GameAdminService_SetDiceRobotStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) DeleteDiceRobot(ctx context.Context, in *DeleteDiceRobotRequest, opts ...grpc.CallOption) (*DeleteDiceRobotResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteDiceRobotResponse) + err := c.cc.Invoke(ctx, GameAdminService_DeleteDiceRobot_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // GameAdminServiceServer is the server API for GameAdminService service. // All implementations must embed UnimplementedGameAdminServiceServer // for forward compatibility. @@ -541,6 +891,13 @@ type GameAdminServiceServer interface { UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error) SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error) DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error) + ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error) + UpdateDiceConfig(context.Context, *UpdateDiceConfigRequest) (*DiceConfigResponse, error) + AdjustDicePool(context.Context, *AdjustDicePoolRequest) (*AdjustDicePoolResponse, error) + ListDiceRobots(context.Context, *ListDiceRobotsRequest) (*ListDiceRobotsResponse, error) + RegisterDiceRobots(context.Context, *RegisterDiceRobotsRequest) (*RegisterDiceRobotsResponse, error) + SetDiceRobotStatus(context.Context, *SetDiceRobotStatusRequest) (*DiceRobotResponse, error) + DeleteDiceRobot(context.Context, *DeleteDiceRobotRequest) (*DeleteDiceRobotResponse, error) mustEmbedUnimplementedGameAdminServiceServer() } @@ -569,6 +926,27 @@ func (UnimplementedGameAdminServiceServer) SetGameStatus(context.Context, *SetGa func (UnimplementedGameAdminServiceServer) DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeleteCatalog not implemented") } +func (UnimplementedGameAdminServiceServer) ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSelfGames not implemented") +} +func (UnimplementedGameAdminServiceServer) UpdateDiceConfig(context.Context, *UpdateDiceConfigRequest) (*DiceConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateDiceConfig not implemented") +} +func (UnimplementedGameAdminServiceServer) AdjustDicePool(context.Context, *AdjustDicePoolRequest) (*AdjustDicePoolResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AdjustDicePool not implemented") +} +func (UnimplementedGameAdminServiceServer) ListDiceRobots(context.Context, *ListDiceRobotsRequest) (*ListDiceRobotsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListDiceRobots not implemented") +} +func (UnimplementedGameAdminServiceServer) RegisterDiceRobots(context.Context, *RegisterDiceRobotsRequest) (*RegisterDiceRobotsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RegisterDiceRobots not implemented") +} +func (UnimplementedGameAdminServiceServer) SetDiceRobotStatus(context.Context, *SetDiceRobotStatusRequest) (*DiceRobotResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetDiceRobotStatus not implemented") +} +func (UnimplementedGameAdminServiceServer) DeleteDiceRobot(context.Context, *DeleteDiceRobotRequest) (*DeleteDiceRobotResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteDiceRobot not implemented") +} func (UnimplementedGameAdminServiceServer) mustEmbedUnimplementedGameAdminServiceServer() {} func (UnimplementedGameAdminServiceServer) testEmbeddedByValue() {} @@ -698,6 +1076,132 @@ func _GameAdminService_DeleteCatalog_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _GameAdminService_ListSelfGames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSelfGamesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).ListSelfGames(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_ListSelfGames_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).ListSelfGames(ctx, req.(*ListSelfGamesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_UpdateDiceConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateDiceConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).UpdateDiceConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_UpdateDiceConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).UpdateDiceConfig(ctx, req.(*UpdateDiceConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_AdjustDicePool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdjustDicePoolRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).AdjustDicePool(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_AdjustDicePool_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).AdjustDicePool(ctx, req.(*AdjustDicePoolRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_ListDiceRobots_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListDiceRobotsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).ListDiceRobots(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_ListDiceRobots_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).ListDiceRobots(ctx, req.(*ListDiceRobotsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_RegisterDiceRobots_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterDiceRobotsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).RegisterDiceRobots(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_RegisterDiceRobots_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).RegisterDiceRobots(ctx, req.(*RegisterDiceRobotsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_SetDiceRobotStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetDiceRobotStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).SetDiceRobotStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_SetDiceRobotStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).SetDiceRobotStatus(ctx, req.(*SetDiceRobotStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_DeleteDiceRobot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteDiceRobotRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).DeleteDiceRobot(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_DeleteDiceRobot_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).DeleteDiceRobot(ctx, req.(*DeleteDiceRobotRequest)) + } + return interceptor(ctx, in, info, handler) +} + // GameAdminService_ServiceDesc is the grpc.ServiceDesc for GameAdminService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -729,6 +1233,34 @@ var GameAdminService_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeleteCatalog", Handler: _GameAdminService_DeleteCatalog_Handler, }, + { + MethodName: "ListSelfGames", + Handler: _GameAdminService_ListSelfGames_Handler, + }, + { + MethodName: "UpdateDiceConfig", + Handler: _GameAdminService_UpdateDiceConfig_Handler, + }, + { + MethodName: "AdjustDicePool", + Handler: _GameAdminService_AdjustDicePool_Handler, + }, + { + MethodName: "ListDiceRobots", + Handler: _GameAdminService_ListDiceRobots_Handler, + }, + { + MethodName: "RegisterDiceRobots", + Handler: _GameAdminService_RegisterDiceRobots_Handler, + }, + { + MethodName: "SetDiceRobotStatus", + Handler: _GameAdminService_SetDiceRobotStatus_Handler, + }, + { + MethodName: "DeleteDiceRobot", + Handler: _GameAdminService_DeleteDiceRobot_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/game/v1/game.proto", diff --git a/api/proto/user/v1/auth.pb.go b/api/proto/user/v1/auth.pb.go index 021b5529..5f78c911 100644 --- a/api/proto/user/v1/auth.pb.go +++ b/api/proto/user/v1/auth.pb.go @@ -1091,12 +1091,13 @@ func (x *AppHeartbeatRequest) GetSessionId() string { return "" } -// AppHeartbeatResponse 返回本次会话心跳写入时间。 +// 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 sizeCache protoimpl.SizeCache } @@ -1152,6 +1153,13 @@ func (x *AppHeartbeatResponse) GetServerTimeMs() int64 { return 0 } +func (x *AppHeartbeatResponse) GetToken() *AuthToken { + if x != nil { + return x.Token + } + return nil +} + var File_proto_user_v1_auth_proto protoreflect.FileDescriptor const file_proto_user_v1_auth_proto_rawDesc = "" + @@ -1251,11 +1259,12 @@ const file_proto_user_v1_auth_proto_rawDesc = "" + "\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\"\x80\x01\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\fserverTimeMs2\xdc\x05\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" + @@ -1310,27 +1319,28 @@ var file_proto_user_v1_auth_proto_depIdxs = []int32{ 15, // 8: hyapp.user.v1.LogoutRequest.meta:type_name -> hyapp.user.v1.RequestMeta 15, // 9: hyapp.user.v1.RecordLoginBlockedRequest.meta:type_name -> hyapp.user.v1.RequestMeta 15, // 10: hyapp.user.v1.AppHeartbeatRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 0, // 11: hyapp.user.v1.AuthService.LoginPassword:input_type -> hyapp.user.v1.LoginPasswordRequest - 1, // 12: hyapp.user.v1.AuthService.LoginThirdParty:input_type -> hyapp.user.v1.LoginThirdPartyRequest - 3, // 13: hyapp.user.v1.AuthService.SetPassword:input_type -> hyapp.user.v1.SetPasswordRequest - 5, // 14: hyapp.user.v1.AuthService.QuickCreateAccount:input_type -> hyapp.user.v1.QuickCreateAccountRequest - 7, // 15: hyapp.user.v1.AuthService.RefreshToken:input_type -> hyapp.user.v1.RefreshTokenRequest - 9, // 16: hyapp.user.v1.AuthService.Logout:input_type -> hyapp.user.v1.LogoutRequest - 11, // 17: hyapp.user.v1.AuthService.RecordLoginBlocked:input_type -> hyapp.user.v1.RecordLoginBlockedRequest - 13, // 18: hyapp.user.v1.AuthService.AppHeartbeat:input_type -> hyapp.user.v1.AppHeartbeatRequest - 2, // 19: hyapp.user.v1.AuthService.LoginPassword:output_type -> hyapp.user.v1.AuthResponse - 2, // 20: hyapp.user.v1.AuthService.LoginThirdParty:output_type -> hyapp.user.v1.AuthResponse - 4, // 21: hyapp.user.v1.AuthService.SetPassword:output_type -> hyapp.user.v1.SetPasswordResponse - 6, // 22: hyapp.user.v1.AuthService.QuickCreateAccount:output_type -> hyapp.user.v1.QuickCreateAccountResponse - 8, // 23: hyapp.user.v1.AuthService.RefreshToken:output_type -> hyapp.user.v1.RefreshTokenResponse - 10, // 24: hyapp.user.v1.AuthService.Logout:output_type -> hyapp.user.v1.LogoutResponse - 12, // 25: hyapp.user.v1.AuthService.RecordLoginBlocked:output_type -> hyapp.user.v1.RecordLoginBlockedResponse - 14, // 26: hyapp.user.v1.AuthService.AppHeartbeat:output_type -> hyapp.user.v1.AppHeartbeatResponse - 19, // [19:27] is the sub-list for method output_type - 11, // [11:19] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 16, // 11: hyapp.user.v1.AppHeartbeatResponse.token:type_name -> hyapp.user.v1.AuthToken + 0, // 12: hyapp.user.v1.AuthService.LoginPassword:input_type -> hyapp.user.v1.LoginPasswordRequest + 1, // 13: hyapp.user.v1.AuthService.LoginThirdParty:input_type -> hyapp.user.v1.LoginThirdPartyRequest + 3, // 14: hyapp.user.v1.AuthService.SetPassword:input_type -> hyapp.user.v1.SetPasswordRequest + 5, // 15: hyapp.user.v1.AuthService.QuickCreateAccount:input_type -> hyapp.user.v1.QuickCreateAccountRequest + 7, // 16: hyapp.user.v1.AuthService.RefreshToken:input_type -> hyapp.user.v1.RefreshTokenRequest + 9, // 17: hyapp.user.v1.AuthService.Logout:input_type -> hyapp.user.v1.LogoutRequest + 11, // 18: hyapp.user.v1.AuthService.RecordLoginBlocked:input_type -> hyapp.user.v1.RecordLoginBlockedRequest + 13, // 19: hyapp.user.v1.AuthService.AppHeartbeat:input_type -> hyapp.user.v1.AppHeartbeatRequest + 2, // 20: hyapp.user.v1.AuthService.LoginPassword:output_type -> hyapp.user.v1.AuthResponse + 2, // 21: hyapp.user.v1.AuthService.LoginThirdParty:output_type -> hyapp.user.v1.AuthResponse + 4, // 22: hyapp.user.v1.AuthService.SetPassword:output_type -> hyapp.user.v1.SetPasswordResponse + 6, // 23: hyapp.user.v1.AuthService.QuickCreateAccount:output_type -> hyapp.user.v1.QuickCreateAccountResponse + 8, // 24: hyapp.user.v1.AuthService.RefreshToken:output_type -> hyapp.user.v1.RefreshTokenResponse + 10, // 25: hyapp.user.v1.AuthService.Logout:output_type -> hyapp.user.v1.LogoutResponse + 12, // 26: hyapp.user.v1.AuthService.RecordLoginBlocked:output_type -> hyapp.user.v1.RecordLoginBlockedResponse + 14, // 27: hyapp.user.v1.AuthService.AppHeartbeat:output_type -> hyapp.user.v1.AppHeartbeatResponse + 20, // [20:28] is the sub-list for method output_type + 12, // [12:20] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_proto_user_v1_auth_proto_init() } diff --git a/api/proto/user/v1/auth.proto b/api/proto/user/v1/auth.proto index dd5ab812..62d5b071 100644 --- a/api/proto/user/v1/auth.proto +++ b/api/proto/user/v1/auth.proto @@ -131,11 +131,12 @@ message AppHeartbeatRequest { string session_id = 3; } -// AppHeartbeatResponse 返回本次会话心跳写入时间。 +// AppHeartbeatResponse 返回本次会话心跳写入时间,并在当前会话仍有效时顺手重签 access token。 message AppHeartbeatResponse { bool accepted = 1; int64 heartbeat_at_ms = 2; int64 server_time_ms = 3; + AuthToken token = 4; } // AuthService 承载登录注册和 token 生命周期能力。 diff --git a/api/proto/user/v1/user.pb.go b/api/proto/user/v1/user.pb.go index d5e5097d..d15b7e79 100644 --- a/api/proto/user/v1/user.pb.go +++ b/api/proto/user/v1/user.pb.go @@ -460,6 +460,8 @@ type User struct { CountryEnabled bool `protobuf:"varint,26,opt,name=country_enabled,json=countryEnabled,proto3" json:"country_enabled,omitempty"` AppCode string `protobuf:"bytes,27,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` Invite *InviteOverview `protobuf:"bytes,28,opt,name=invite,proto3" json:"invite,omitempty"` + PrettyId string `protobuf:"bytes,29,opt,name=pretty_id,json=prettyId,proto3" json:"pretty_id,omitempty"` + PrettyDisplayUserId string `protobuf:"bytes,30,opt,name=pretty_display_user_id,json=prettyDisplayUserId,proto3" json:"pretty_display_user_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -690,6 +692,20 @@ func (x *User) GetInvite() *InviteOverview { return nil } +func (x *User) GetPrettyId() string { + if x != nil { + return x.PrettyId + } + return "" +} + +func (x *User) GetPrettyDisplayUserId() string { + if x != nil { + return x.PrettyDisplayUserId + } + return "" +} + // InviteOverview 是我的页可直接展示的邀请码和邀请计数 read model。 type InviteOverview struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -8092,6 +8108,8 @@ type UserIdentity struct { DisplayUserIdKind string `protobuf:"bytes,5,opt,name=display_user_id_kind,json=displayUserIdKind,proto3" json:"display_user_id_kind,omitempty"` DisplayUserIdExpiresAtMs int64 `protobuf:"varint,6,opt,name=display_user_id_expires_at_ms,json=displayUserIdExpiresAtMs,proto3" json:"display_user_id_expires_at_ms,omitempty"` AppCode string `protobuf:"bytes,7,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + PrettyId string `protobuf:"bytes,8,opt,name=pretty_id,json=prettyId,proto3" json:"pretty_id,omitempty"` + PrettyDisplayUserId string `protobuf:"bytes,9,opt,name=pretty_display_user_id,json=prettyDisplayUserId,proto3" json:"pretty_display_user_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -8175,6 +8193,20 @@ func (x *UserIdentity) GetAppCode() string { return "" } +func (x *UserIdentity) GetPrettyId() string { + if x != nil { + return x.PrettyId + } + return "" +} + +func (x *UserIdentity) GetPrettyDisplayUserId() string { + if x != nil { + return x.PrettyDisplayUserId + } + return "" +} + // GetUserIdentityRequest 按 user_id 查询当前短号。 type GetUserIdentityRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -8729,6 +8761,1656 @@ func (x *ExpirePrettyDisplayUserIDResponse) GetIdentity() *UserIdentity { return nil } +// PrettyDisplayIDPool 是靓号池等级区间和生成规则配置。 +type PrettyDisplayIDPool struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + LevelTrack string `protobuf:"bytes,4,opt,name=level_track,json=levelTrack,proto3" json:"level_track,omitempty"` + MinLevel int32 `protobuf:"varint,5,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` + MaxLevel int32 `protobuf:"varint,6,opt,name=max_level,json=maxLevel,proto3" json:"max_level,omitempty"` + RuleType string `protobuf:"bytes,7,opt,name=rule_type,json=ruleType,proto3" json:"rule_type,omitempty"` + RuleConfigJson string `protobuf:"bytes,8,opt,name=rule_config_json,json=ruleConfigJson,proto3" json:"rule_config_json,omitempty"` + Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,10,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + CreatedByAdminId int64 `protobuf:"varint,11,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"` + UpdatedByAdminId int64 `protobuf:"varint,12,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,13,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,14,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PrettyDisplayIDPool) Reset() { + *x = PrettyDisplayIDPool{} + mi := &file_proto_user_v1_user_proto_msgTypes[121] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PrettyDisplayIDPool) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrettyDisplayIDPool) ProtoMessage() {} + +func (x *PrettyDisplayIDPool) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[121] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrettyDisplayIDPool.ProtoReflect.Descriptor instead. +func (*PrettyDisplayIDPool) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{121} +} + +func (x *PrettyDisplayIDPool) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *PrettyDisplayIDPool) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *PrettyDisplayIDPool) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PrettyDisplayIDPool) GetLevelTrack() string { + if x != nil { + return x.LevelTrack + } + return "" +} + +func (x *PrettyDisplayIDPool) GetMinLevel() int32 { + if x != nil { + return x.MinLevel + } + return 0 +} + +func (x *PrettyDisplayIDPool) GetMaxLevel() int32 { + if x != nil { + return x.MaxLevel + } + return 0 +} + +func (x *PrettyDisplayIDPool) GetRuleType() string { + if x != nil { + return x.RuleType + } + return "" +} + +func (x *PrettyDisplayIDPool) GetRuleConfigJson() string { + if x != nil { + return x.RuleConfigJson + } + return "" +} + +func (x *PrettyDisplayIDPool) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *PrettyDisplayIDPool) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *PrettyDisplayIDPool) GetCreatedByAdminId() int64 { + if x != nil { + return x.CreatedByAdminId + } + return 0 +} + +func (x *PrettyDisplayIDPool) GetUpdatedByAdminId() int64 { + if x != nil { + return x.UpdatedByAdminId + } + return 0 +} + +func (x *PrettyDisplayIDPool) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *PrettyDisplayIDPool) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// PrettyDisplayID 是一个可申请或已发放的靓号记录。 +type PrettyDisplayID struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + PrettyId string `protobuf:"bytes,2,opt,name=pretty_id,json=prettyId,proto3" json:"pretty_id,omitempty"` + PoolId string `protobuf:"bytes,3,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` + DisplayUserId string `protobuf:"bytes,5,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` + Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` + AssignedUserId int64 `protobuf:"varint,7,opt,name=assigned_user_id,json=assignedUserId,proto3" json:"assigned_user_id,omitempty"` + AssignedLeaseId string `protobuf:"bytes,8,opt,name=assigned_lease_id,json=assignedLeaseId,proto3" json:"assigned_lease_id,omitempty"` + AssignedAtMs int64 `protobuf:"varint,9,opt,name=assigned_at_ms,json=assignedAtMs,proto3" json:"assigned_at_ms,omitempty"` + ReleasedAtMs int64 `protobuf:"varint,10,opt,name=released_at_ms,json=releasedAtMs,proto3" json:"released_at_ms,omitempty"` + ReleaseReason string `protobuf:"bytes,11,opt,name=release_reason,json=releaseReason,proto3" json:"release_reason,omitempty"` + GeneratedBatchId string `protobuf:"bytes,12,opt,name=generated_batch_id,json=generatedBatchId,proto3" json:"generated_batch_id,omitempty"` + CreatedByAdminId int64 `protobuf:"varint,13,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,14,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,15,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + Pool *PrettyDisplayIDPool `protobuf:"bytes,16,opt,name=pool,proto3" json:"pool,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PrettyDisplayID) Reset() { + *x = PrettyDisplayID{} + mi := &file_proto_user_v1_user_proto_msgTypes[122] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PrettyDisplayID) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrettyDisplayID) ProtoMessage() {} + +func (x *PrettyDisplayID) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[122] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrettyDisplayID.ProtoReflect.Descriptor instead. +func (*PrettyDisplayID) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{122} +} + +func (x *PrettyDisplayID) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *PrettyDisplayID) GetPrettyId() string { + if x != nil { + return x.PrettyId + } + return "" +} + +func (x *PrettyDisplayID) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *PrettyDisplayID) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *PrettyDisplayID) GetDisplayUserId() string { + if x != nil { + return x.DisplayUserId + } + return "" +} + +func (x *PrettyDisplayID) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *PrettyDisplayID) GetAssignedUserId() int64 { + if x != nil { + return x.AssignedUserId + } + return 0 +} + +func (x *PrettyDisplayID) GetAssignedLeaseId() string { + if x != nil { + return x.AssignedLeaseId + } + return "" +} + +func (x *PrettyDisplayID) GetAssignedAtMs() int64 { + if x != nil { + return x.AssignedAtMs + } + return 0 +} + +func (x *PrettyDisplayID) GetReleasedAtMs() int64 { + if x != nil { + return x.ReleasedAtMs + } + return 0 +} + +func (x *PrettyDisplayID) GetReleaseReason() string { + if x != nil { + return x.ReleaseReason + } + return "" +} + +func (x *PrettyDisplayID) GetGeneratedBatchId() string { + if x != nil { + return x.GeneratedBatchId + } + return "" +} + +func (x *PrettyDisplayID) GetCreatedByAdminId() int64 { + if x != nil { + return x.CreatedByAdminId + } + return 0 +} + +func (x *PrettyDisplayID) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *PrettyDisplayID) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +func (x *PrettyDisplayID) GetPool() *PrettyDisplayIDPool { + if x != nil { + return x.Pool + } + return nil +} + +type PrettyDisplayIDGenerationBatch struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + BatchId string `protobuf:"bytes,2,opt,name=batch_id,json=batchId,proto3" json:"batch_id,omitempty"` + PoolId string `protobuf:"bytes,3,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + RuleType string `protobuf:"bytes,4,opt,name=rule_type,json=ruleType,proto3" json:"rule_type,omitempty"` + RuleConfigJson string `protobuf:"bytes,5,opt,name=rule_config_json,json=ruleConfigJson,proto3" json:"rule_config_json,omitempty"` + RequestedCount int32 `protobuf:"varint,6,opt,name=requested_count,json=requestedCount,proto3" json:"requested_count,omitempty"` + GeneratedCount int32 `protobuf:"varint,7,opt,name=generated_count,json=generatedCount,proto3" json:"generated_count,omitempty"` + SkippedConflictCount int32 `protobuf:"varint,8,opt,name=skipped_conflict_count,json=skippedConflictCount,proto3" json:"skipped_conflict_count,omitempty"` + Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"` + OperatorAdminId int64 `protobuf:"varint,10,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + RequestId string `protobuf:"bytes,11,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,12,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,13,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PrettyDisplayIDGenerationBatch) Reset() { + *x = PrettyDisplayIDGenerationBatch{} + mi := &file_proto_user_v1_user_proto_msgTypes[123] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PrettyDisplayIDGenerationBatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrettyDisplayIDGenerationBatch) ProtoMessage() {} + +func (x *PrettyDisplayIDGenerationBatch) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[123] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrettyDisplayIDGenerationBatch.ProtoReflect.Descriptor instead. +func (*PrettyDisplayIDGenerationBatch) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{123} +} + +func (x *PrettyDisplayIDGenerationBatch) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *PrettyDisplayIDGenerationBatch) GetBatchId() string { + if x != nil { + return x.BatchId + } + return "" +} + +func (x *PrettyDisplayIDGenerationBatch) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *PrettyDisplayIDGenerationBatch) GetRuleType() string { + if x != nil { + return x.RuleType + } + return "" +} + +func (x *PrettyDisplayIDGenerationBatch) GetRuleConfigJson() string { + if x != nil { + return x.RuleConfigJson + } + return "" +} + +func (x *PrettyDisplayIDGenerationBatch) GetRequestedCount() int32 { + if x != nil { + return x.RequestedCount + } + return 0 +} + +func (x *PrettyDisplayIDGenerationBatch) GetGeneratedCount() int32 { + if x != nil { + return x.GeneratedCount + } + return 0 +} + +func (x *PrettyDisplayIDGenerationBatch) GetSkippedConflictCount() int32 { + if x != nil { + return x.SkippedConflictCount + } + return 0 +} + +func (x *PrettyDisplayIDGenerationBatch) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *PrettyDisplayIDGenerationBatch) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +func (x *PrettyDisplayIDGenerationBatch) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *PrettyDisplayIDGenerationBatch) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *PrettyDisplayIDGenerationBatch) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type ListAvailablePrettyDisplayIDsRequest 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"` + Page int32 `protobuf:"varint,3,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAvailablePrettyDisplayIDsRequest) Reset() { + *x = ListAvailablePrettyDisplayIDsRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[124] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAvailablePrettyDisplayIDsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAvailablePrettyDisplayIDsRequest) ProtoMessage() {} + +func (x *ListAvailablePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[124] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAvailablePrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. +func (*ListAvailablePrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{124} +} + +func (x *ListAvailablePrettyDisplayIDsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListAvailablePrettyDisplayIDsRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ListAvailablePrettyDisplayIDsRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListAvailablePrettyDisplayIDsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ListAvailablePrettyDisplayIDsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*PrettyDisplayID `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAvailablePrettyDisplayIDsResponse) Reset() { + *x = ListAvailablePrettyDisplayIDsResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[125] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAvailablePrettyDisplayIDsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAvailablePrettyDisplayIDsResponse) ProtoMessage() {} + +func (x *ListAvailablePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[125] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAvailablePrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. +func (*ListAvailablePrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{125} +} + +func (x *ListAvailablePrettyDisplayIDsResponse) GetItems() []*PrettyDisplayID { + if x != nil { + return x.Items + } + return nil +} + +func (x *ListAvailablePrettyDisplayIDsResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +type ApplyPrettyDisplayIDFromPoolRequest 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"` + PrettyId string `protobuf:"bytes,3,opt,name=pretty_id,json=prettyId,proto3" json:"pretty_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApplyPrettyDisplayIDFromPoolRequest) Reset() { + *x = ApplyPrettyDisplayIDFromPoolRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[126] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplyPrettyDisplayIDFromPoolRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyPrettyDisplayIDFromPoolRequest) ProtoMessage() {} + +func (x *ApplyPrettyDisplayIDFromPoolRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[126] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplyPrettyDisplayIDFromPoolRequest.ProtoReflect.Descriptor instead. +func (*ApplyPrettyDisplayIDFromPoolRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{126} +} + +func (x *ApplyPrettyDisplayIDFromPoolRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ApplyPrettyDisplayIDFromPoolRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ApplyPrettyDisplayIDFromPoolRequest) GetPrettyId() string { + if x != nil { + return x.PrettyId + } + return "" +} + +type ApplyPrettyDisplayIDFromPoolResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` + PrettyId string `protobuf:"bytes,2,opt,name=pretty_id,json=prettyId,proto3" json:"pretty_id,omitempty"` + LeaseId string `protobuf:"bytes,3,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApplyPrettyDisplayIDFromPoolResponse) Reset() { + *x = ApplyPrettyDisplayIDFromPoolResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[127] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplyPrettyDisplayIDFromPoolResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyPrettyDisplayIDFromPoolResponse) ProtoMessage() {} + +func (x *ApplyPrettyDisplayIDFromPoolResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[127] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplyPrettyDisplayIDFromPoolResponse.ProtoReflect.Descriptor instead. +func (*ApplyPrettyDisplayIDFromPoolResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{127} +} + +func (x *ApplyPrettyDisplayIDFromPoolResponse) GetIdentity() *UserIdentity { + if x != nil { + return x.Identity + } + return nil +} + +func (x *ApplyPrettyDisplayIDFromPoolResponse) GetPrettyId() string { + if x != nil { + return x.PrettyId + } + return "" +} + +func (x *ApplyPrettyDisplayIDFromPoolResponse) GetLeaseId() string { + if x != nil { + return x.LeaseId + } + return "" +} + +type ListPrettyDisplayIDPoolsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + LevelTrack string `protobuf:"bytes,3,opt,name=level_track,json=levelTrack,proto3" json:"level_track,omitempty"` + Page int32 `protobuf:"varint,4,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,5,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPrettyDisplayIDPoolsRequest) Reset() { + *x = ListPrettyDisplayIDPoolsRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[128] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPrettyDisplayIDPoolsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPrettyDisplayIDPoolsRequest) ProtoMessage() {} + +func (x *ListPrettyDisplayIDPoolsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[128] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPrettyDisplayIDPoolsRequest.ProtoReflect.Descriptor instead. +func (*ListPrettyDisplayIDPoolsRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{128} +} + +func (x *ListPrettyDisplayIDPoolsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListPrettyDisplayIDPoolsRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ListPrettyDisplayIDPoolsRequest) GetLevelTrack() string { + if x != nil { + return x.LevelTrack + } + return "" +} + +func (x *ListPrettyDisplayIDPoolsRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListPrettyDisplayIDPoolsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ListPrettyDisplayIDPoolsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*PrettyDisplayIDPool `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPrettyDisplayIDPoolsResponse) Reset() { + *x = ListPrettyDisplayIDPoolsResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[129] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPrettyDisplayIDPoolsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPrettyDisplayIDPoolsResponse) ProtoMessage() {} + +func (x *ListPrettyDisplayIDPoolsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[129] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPrettyDisplayIDPoolsResponse.ProtoReflect.Descriptor instead. +func (*ListPrettyDisplayIDPoolsResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{129} +} + +func (x *ListPrettyDisplayIDPoolsResponse) GetItems() []*PrettyDisplayIDPool { + if x != nil { + return x.Items + } + return nil +} + +func (x *ListPrettyDisplayIDPoolsResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +type CreatePrettyDisplayIDPoolRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + LevelTrack string `protobuf:"bytes,3,opt,name=level_track,json=levelTrack,proto3" json:"level_track,omitempty"` + MinLevel int32 `protobuf:"varint,4,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` + MaxLevel int32 `protobuf:"varint,5,opt,name=max_level,json=maxLevel,proto3" json:"max_level,omitempty"` + RuleType string `protobuf:"bytes,6,opt,name=rule_type,json=ruleType,proto3" json:"rule_type,omitempty"` + RuleConfigJson string `protobuf:"bytes,7,opt,name=rule_config_json,json=ruleConfigJson,proto3" json:"rule_config_json,omitempty"` + Status string `protobuf:"bytes,8,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,9,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + OperatorAdminId int64 `protobuf:"varint,10,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreatePrettyDisplayIDPoolRequest) Reset() { + *x = CreatePrettyDisplayIDPoolRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[130] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreatePrettyDisplayIDPoolRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreatePrettyDisplayIDPoolRequest) ProtoMessage() {} + +func (x *CreatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[130] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreatePrettyDisplayIDPoolRequest.ProtoReflect.Descriptor instead. +func (*CreatePrettyDisplayIDPoolRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{130} +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetLevelTrack() string { + if x != nil { + return x.LevelTrack + } + return "" +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetMinLevel() int32 { + if x != nil { + return x.MinLevel + } + return 0 +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetMaxLevel() int32 { + if x != nil { + return x.MaxLevel + } + return 0 +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetRuleType() string { + if x != nil { + return x.RuleType + } + return "" +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetRuleConfigJson() string { + if x != nil { + return x.RuleConfigJson + } + return "" +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *CreatePrettyDisplayIDPoolRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +type UpdatePrettyDisplayIDPoolRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + LevelTrack string `protobuf:"bytes,4,opt,name=level_track,json=levelTrack,proto3" json:"level_track,omitempty"` + MinLevel int32 `protobuf:"varint,5,opt,name=min_level,json=minLevel,proto3" json:"min_level,omitempty"` + MaxLevel int32 `protobuf:"varint,6,opt,name=max_level,json=maxLevel,proto3" json:"max_level,omitempty"` + RuleType string `protobuf:"bytes,7,opt,name=rule_type,json=ruleType,proto3" json:"rule_type,omitempty"` + RuleConfigJson string `protobuf:"bytes,8,opt,name=rule_config_json,json=ruleConfigJson,proto3" json:"rule_config_json,omitempty"` + Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,10,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + OperatorAdminId int64 `protobuf:"varint,11,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdatePrettyDisplayIDPoolRequest) Reset() { + *x = UpdatePrettyDisplayIDPoolRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[131] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdatePrettyDisplayIDPoolRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdatePrettyDisplayIDPoolRequest) ProtoMessage() {} + +func (x *UpdatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[131] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdatePrettyDisplayIDPoolRequest.ProtoReflect.Descriptor instead. +func (*UpdatePrettyDisplayIDPoolRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{131} +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetLevelTrack() string { + if x != nil { + return x.LevelTrack + } + return "" +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetMinLevel() int32 { + if x != nil { + return x.MinLevel + } + return 0 +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetMaxLevel() int32 { + if x != nil { + return x.MaxLevel + } + return 0 +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetRuleType() string { + if x != nil { + return x.RuleType + } + return "" +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetRuleConfigJson() string { + if x != nil { + return x.RuleConfigJson + } + return "" +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *UpdatePrettyDisplayIDPoolRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +type PrettyDisplayIDPoolResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Pool *PrettyDisplayIDPool `protobuf:"bytes,1,opt,name=pool,proto3" json:"pool,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PrettyDisplayIDPoolResponse) Reset() { + *x = PrettyDisplayIDPoolResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[132] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PrettyDisplayIDPoolResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrettyDisplayIDPoolResponse) ProtoMessage() {} + +func (x *PrettyDisplayIDPoolResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[132] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrettyDisplayIDPoolResponse.ProtoReflect.Descriptor instead. +func (*PrettyDisplayIDPoolResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{132} +} + +func (x *PrettyDisplayIDPoolResponse) GetPool() *PrettyDisplayIDPool { + if x != nil { + return x.Pool + } + return nil +} + +type GeneratePrettyDisplayIDsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + RuleType string `protobuf:"bytes,3,opt,name=rule_type,json=ruleType,proto3" json:"rule_type,omitempty"` + RuleConfigJson string `protobuf:"bytes,4,opt,name=rule_config_json,json=ruleConfigJson,proto3" json:"rule_config_json,omitempty"` + Count int32 `protobuf:"varint,5,opt,name=count,proto3" json:"count,omitempty"` + OperatorAdminId int64 `protobuf:"varint,6,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GeneratePrettyDisplayIDsRequest) Reset() { + *x = GeneratePrettyDisplayIDsRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[133] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GeneratePrettyDisplayIDsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeneratePrettyDisplayIDsRequest) ProtoMessage() {} + +func (x *GeneratePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[133] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeneratePrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. +func (*GeneratePrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{133} +} + +func (x *GeneratePrettyDisplayIDsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *GeneratePrettyDisplayIDsRequest) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *GeneratePrettyDisplayIDsRequest) GetRuleType() string { + if x != nil { + return x.RuleType + } + return "" +} + +func (x *GeneratePrettyDisplayIDsRequest) GetRuleConfigJson() string { + if x != nil { + return x.RuleConfigJson + } + return "" +} + +func (x *GeneratePrettyDisplayIDsRequest) GetCount() int32 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *GeneratePrettyDisplayIDsRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +type GeneratePrettyDisplayIDsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Batch *PrettyDisplayIDGenerationBatch `protobuf:"bytes,1,opt,name=batch,proto3" json:"batch,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GeneratePrettyDisplayIDsResponse) Reset() { + *x = GeneratePrettyDisplayIDsResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[134] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GeneratePrettyDisplayIDsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeneratePrettyDisplayIDsResponse) ProtoMessage() {} + +func (x *GeneratePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[134] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeneratePrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. +func (*GeneratePrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{134} +} + +func (x *GeneratePrettyDisplayIDsResponse) GetBatch() *PrettyDisplayIDGenerationBatch { + if x != nil { + return x.Batch + } + return nil +} + +type ListPrettyDisplayIDsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + Source string `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + Keyword string `protobuf:"bytes,5,opt,name=keyword,proto3" json:"keyword,omitempty"` + AssignedUserId int64 `protobuf:"varint,6,opt,name=assigned_user_id,json=assignedUserId,proto3" json:"assigned_user_id,omitempty"` + Page int32 `protobuf:"varint,7,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,8,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPrettyDisplayIDsRequest) Reset() { + *x = ListPrettyDisplayIDsRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[135] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPrettyDisplayIDsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPrettyDisplayIDsRequest) ProtoMessage() {} + +func (x *ListPrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[135] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. +func (*ListPrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{135} +} + +func (x *ListPrettyDisplayIDsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListPrettyDisplayIDsRequest) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *ListPrettyDisplayIDsRequest) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ListPrettyDisplayIDsRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ListPrettyDisplayIDsRequest) GetKeyword() string { + if x != nil { + return x.Keyword + } + return "" +} + +func (x *ListPrettyDisplayIDsRequest) GetAssignedUserId() int64 { + if x != nil { + return x.AssignedUserId + } + return 0 +} + +func (x *ListPrettyDisplayIDsRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListPrettyDisplayIDsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ListPrettyDisplayIDsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*PrettyDisplayID `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPrettyDisplayIDsResponse) Reset() { + *x = ListPrettyDisplayIDsResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[136] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPrettyDisplayIDsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPrettyDisplayIDsResponse) ProtoMessage() {} + +func (x *ListPrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[136] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. +func (*ListPrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{136} +} + +func (x *ListPrettyDisplayIDsResponse) GetItems() []*PrettyDisplayID { + if x != nil { + return x.Items + } + return nil +} + +func (x *ListPrettyDisplayIDsResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +type SetPrettyDisplayIDStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + PrettyId string `protobuf:"bytes,2,opt,name=pretty_id,json=prettyId,proto3" json:"pretty_id,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + OperatorAdminId int64 `protobuf:"varint,4,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + Reason string `protobuf:"bytes,5,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetPrettyDisplayIDStatusRequest) Reset() { + *x = SetPrettyDisplayIDStatusRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[137] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetPrettyDisplayIDStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetPrettyDisplayIDStatusRequest) ProtoMessage() {} + +func (x *SetPrettyDisplayIDStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[137] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetPrettyDisplayIDStatusRequest.ProtoReflect.Descriptor instead. +func (*SetPrettyDisplayIDStatusRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{137} +} + +func (x *SetPrettyDisplayIDStatusRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *SetPrettyDisplayIDStatusRequest) GetPrettyId() string { + if x != nil { + return x.PrettyId + } + return "" +} + +func (x *SetPrettyDisplayIDStatusRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *SetPrettyDisplayIDStatusRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +func (x *SetPrettyDisplayIDStatusRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type PrettyDisplayIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Item *PrettyDisplayID `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PrettyDisplayIDResponse) Reset() { + *x = PrettyDisplayIDResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[138] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PrettyDisplayIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrettyDisplayIDResponse) ProtoMessage() {} + +func (x *PrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[138] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrettyDisplayIDResponse.ProtoReflect.Descriptor instead. +func (*PrettyDisplayIDResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{138} +} + +func (x *PrettyDisplayIDResponse) GetItem() *PrettyDisplayID { + if x != nil { + return x.Item + } + return nil +} + +type AdminGrantPrettyDisplayIDRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + DisplayUserId string `protobuf:"bytes,3,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` + DurationMs int64 `protobuf:"varint,4,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"` + Reason string `protobuf:"bytes,5,opt,name=reason,proto3" json:"reason,omitempty"` + OperatorAdminId int64 `protobuf:"varint,6,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdminGrantPrettyDisplayIDRequest) Reset() { + *x = AdminGrantPrettyDisplayIDRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[139] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminGrantPrettyDisplayIDRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminGrantPrettyDisplayIDRequest) ProtoMessage() {} + +func (x *AdminGrantPrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[139] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdminGrantPrettyDisplayIDRequest.ProtoReflect.Descriptor instead. +func (*AdminGrantPrettyDisplayIDRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{139} +} + +func (x *AdminGrantPrettyDisplayIDRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *AdminGrantPrettyDisplayIDRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *AdminGrantPrettyDisplayIDRequest) GetDisplayUserId() string { + if x != nil { + return x.DisplayUserId + } + return "" +} + +func (x *AdminGrantPrettyDisplayIDRequest) GetDurationMs() int64 { + if x != nil { + return x.DurationMs + } + return 0 +} + +func (x *AdminGrantPrettyDisplayIDRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *AdminGrantPrettyDisplayIDRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +type AdminGrantPrettyDisplayIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` + PrettyId string `protobuf:"bytes,2,opt,name=pretty_id,json=prettyId,proto3" json:"pretty_id,omitempty"` + LeaseId string `protobuf:"bytes,3,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdminGrantPrettyDisplayIDResponse) Reset() { + *x = AdminGrantPrettyDisplayIDResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[140] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminGrantPrettyDisplayIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminGrantPrettyDisplayIDResponse) ProtoMessage() {} + +func (x *AdminGrantPrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[140] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdminGrantPrettyDisplayIDResponse.ProtoReflect.Descriptor instead. +func (*AdminGrantPrettyDisplayIDResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{140} +} + +func (x *AdminGrantPrettyDisplayIDResponse) GetIdentity() *UserIdentity { + if x != nil { + return x.Identity + } + return nil +} + +func (x *AdminGrantPrettyDisplayIDResponse) GetPrettyId() string { + if x != nil { + return x.PrettyId + } + return "" +} + +func (x *AdminGrantPrettyDisplayIDResponse) GetLeaseId() string { + if x != nil { + return x.LeaseId + } + return "" +} + var File_proto_user_v1_user_proto protoreflect.FileDescriptor const file_proto_user_v1_user_proto_rawDesc = "" + @@ -8768,7 +10450,7 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\bplatform\x18\x03 \x01(\tR\bplatform\x12\x19\n" + "\bapp_code\x18\x04 \x01(\tR\aappCode\":\n" + "\x12ResolveAppResponse\x12$\n" + - "\x03app\x18\x01 \x01(\v2\x12.hyapp.user.v1.AppR\x03app\"\xb1\b\n" + + "\x03app\x18\x01 \x01(\v2\x12.hyapp.user.v1.AppR\x03app\"\x83\t\n" + "\x04User\x12\x17\n" + "\auser_id\x18\x01 \x01(\x03R\x06userId\x121\n" + "\x06status\x18\x02 \x01(\x0e2\x19.hyapp.user.v1.UserStatusR\x06status\x12\"\n" + @@ -8802,7 +10484,9 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\x12phone_country_code\x18\x19 \x01(\tR\x10phoneCountryCode\x12'\n" + "\x0fcountry_enabled\x18\x1a \x01(\bR\x0ecountryEnabled\x12\x19\n" + "\bapp_code\x18\x1b \x01(\tR\aappCode\x125\n" + - "\x06invite\x18\x1c \x01(\v2\x1d.hyapp.user.v1.InviteOverviewR\x06invite\"\xed\x01\n" + + "\x06invite\x18\x1c \x01(\v2\x1d.hyapp.user.v1.InviteOverviewR\x06invite\x12\x1b\n" + + "\tpretty_id\x18\x1d \x01(\tR\bprettyId\x123\n" + + "\x16pretty_display_user_id\x18\x1e \x01(\tR\x13prettyDisplayUserId\"\xed\x01\n" + "\x0eInviteOverview\x12$\n" + "\x0emy_invite_code\x18\x01 \x01(\tR\fmyInviteCode\x12%\n" + "\x0einvite_enabled\x18\x02 \x01(\bR\rinviteEnabled\x12!\n" + @@ -9414,7 +11098,7 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\tcountries\x18\x03 \x03(\tR\tcountries\x12(\n" + "\x10operator_user_id\x18\x04 \x01(\x03R\x0eoperatorUserId\"?\n" + "\x0eRegionResponse\x12-\n" + - "\x06region\x18\x01 \x01(\v2\x15.hyapp.user.v1.RegionR\x06region\"\xab\x02\n" + + "\x06region\x18\x01 \x01(\v2\x15.hyapp.user.v1.RegionR\x06region\"\xfd\x02\n" + "\fUserIdentity\x12\x17\n" + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12&\n" + "\x0fdisplay_user_id\x18\x02 \x01(\tR\rdisplayUserId\x12\x16\n" + @@ -9422,7 +11106,9 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\x17default_display_user_id\x18\x04 \x01(\tR\x14defaultDisplayUserId\x12/\n" + "\x14display_user_id_kind\x18\x05 \x01(\tR\x11displayUserIdKind\x12?\n" + "\x1ddisplay_user_id_expires_at_ms\x18\x06 \x01(\x03R\x18displayUserIdExpiresAtMs\x12\x19\n" + - "\bapp_code\x18\a \x01(\tR\aappCode\"a\n" + + "\bapp_code\x18\a \x01(\tR\aappCode\x12\x1b\n" + + "\tpretty_id\x18\b \x01(\tR\bprettyId\x123\n" + + "\x16pretty_display_user_id\x18\t \x01(\tR\x13prettyDisplayUserId\"a\n" + "\x16GetUserIdentityRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\"R\n" + @@ -9455,7 +11141,157 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + "\blease_id\x18\x03 \x01(\tR\aleaseId\"\\\n" + "!ExpirePrettyDisplayUserIDResponse\x127\n" + - "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity*s\n" + + "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity\"\xdc\x03\n" + + "\x13PrettyDisplayIDPool\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x17\n" + + "\apool_id\x18\x02 \x01(\tR\x06poolId\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x1f\n" + + "\vlevel_track\x18\x04 \x01(\tR\n" + + "levelTrack\x12\x1b\n" + + "\tmin_level\x18\x05 \x01(\x05R\bminLevel\x12\x1b\n" + + "\tmax_level\x18\x06 \x01(\x05R\bmaxLevel\x12\x1b\n" + + "\trule_type\x18\a \x01(\tR\bruleType\x12(\n" + + "\x10rule_config_json\x18\b \x01(\tR\x0eruleConfigJson\x12\x16\n" + + "\x06status\x18\t \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "sort_order\x18\n" + + " \x01(\x05R\tsortOrder\x12-\n" + + "\x13created_by_admin_id\x18\v \x01(\x03R\x10createdByAdminId\x12-\n" + + "\x13updated_by_admin_id\x18\f \x01(\x03R\x10updatedByAdminId\x12\"\n" + + "\rcreated_at_ms\x18\r \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x0e \x01(\x03R\vupdatedAtMs\"\xe0\x04\n" + + "\x0fPrettyDisplayID\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x1b\n" + + "\tpretty_id\x18\x02 \x01(\tR\bprettyId\x12\x17\n" + + "\apool_id\x18\x03 \x01(\tR\x06poolId\x12\x16\n" + + "\x06source\x18\x04 \x01(\tR\x06source\x12&\n" + + "\x0fdisplay_user_id\x18\x05 \x01(\tR\rdisplayUserId\x12\x16\n" + + "\x06status\x18\x06 \x01(\tR\x06status\x12(\n" + + "\x10assigned_user_id\x18\a \x01(\x03R\x0eassignedUserId\x12*\n" + + "\x11assigned_lease_id\x18\b \x01(\tR\x0fassignedLeaseId\x12$\n" + + "\x0eassigned_at_ms\x18\t \x01(\x03R\fassignedAtMs\x12$\n" + + "\x0ereleased_at_ms\x18\n" + + " \x01(\x03R\freleasedAtMs\x12%\n" + + "\x0erelease_reason\x18\v \x01(\tR\rreleaseReason\x12,\n" + + "\x12generated_batch_id\x18\f \x01(\tR\x10generatedBatchId\x12-\n" + + "\x13created_by_admin_id\x18\r \x01(\x03R\x10createdByAdminId\x12\"\n" + + "\rcreated_at_ms\x18\x0e \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x0f \x01(\x03R\vupdatedAtMs\x126\n" + + "\x04pool\x18\x10 \x01(\v2\".hyapp.user.v1.PrettyDisplayIDPoolR\x04pool\"\xe9\x03\n" + + "\x1ePrettyDisplayIDGenerationBatch\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x19\n" + + "\bbatch_id\x18\x02 \x01(\tR\abatchId\x12\x17\n" + + "\apool_id\x18\x03 \x01(\tR\x06poolId\x12\x1b\n" + + "\trule_type\x18\x04 \x01(\tR\bruleType\x12(\n" + + "\x10rule_config_json\x18\x05 \x01(\tR\x0eruleConfigJson\x12'\n" + + "\x0frequested_count\x18\x06 \x01(\x05R\x0erequestedCount\x12'\n" + + "\x0fgenerated_count\x18\a \x01(\x05R\x0egeneratedCount\x124\n" + + "\x16skipped_conflict_count\x18\b \x01(\x05R\x14skippedConflictCount\x12\x16\n" + + "\x06status\x18\t \x01(\tR\x06status\x12*\n" + + "\x11operator_admin_id\x18\n" + + " \x01(\x03R\x0foperatorAdminId\x12\x1d\n" + + "\n" + + "request_id\x18\v \x01(\tR\trequestId\x12\"\n" + + "\rcreated_at_ms\x18\f \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\r \x01(\x03R\vupdatedAtMs\"\xa0\x01\n" + + "$ListAvailablePrettyDisplayIDsRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x12\n" + + "\x04page\x18\x03 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\"s\n" + + "%ListAvailablePrettyDisplayIDsResponse\x124\n" + + "\x05items\x18\x01 \x03(\v2\x1e.hyapp.user.v1.PrettyDisplayIDR\x05items\x12\x14\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\"\x8b\x01\n" + + "#ApplyPrettyDisplayIDFromPoolRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1b\n" + + "\tpretty_id\x18\x03 \x01(\tR\bprettyId\"\x97\x01\n" + + "$ApplyPrettyDisplayIDFromPoolResponse\x127\n" + + "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity\x12\x1b\n" + + "\tpretty_id\x18\x02 \x01(\tR\bprettyId\x12\x19\n" + + "\blease_id\x18\x03 \x01(\tR\aleaseId\"\xbb\x01\n" + + "\x1fListPrettyDisplayIDPoolsRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x1f\n" + + "\vlevel_track\x18\x03 \x01(\tR\n" + + "levelTrack\x12\x12\n" + + "\x04page\x18\x04 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x05 \x01(\x05R\bpageSize\"r\n" + + " ListPrettyDisplayIDPoolsResponse\x128\n" + + "\x05items\x18\x01 \x03(\v2\".hyapp.user.v1.PrettyDisplayIDPoolR\x05items\x12\x14\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\"\xeb\x02\n" + + " CreatePrettyDisplayIDPoolRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1f\n" + + "\vlevel_track\x18\x03 \x01(\tR\n" + + "levelTrack\x12\x1b\n" + + "\tmin_level\x18\x04 \x01(\x05R\bminLevel\x12\x1b\n" + + "\tmax_level\x18\x05 \x01(\x05R\bmaxLevel\x12\x1b\n" + + "\trule_type\x18\x06 \x01(\tR\bruleType\x12(\n" + + "\x10rule_config_json\x18\a \x01(\tR\x0eruleConfigJson\x12\x16\n" + + "\x06status\x18\b \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "sort_order\x18\t \x01(\x05R\tsortOrder\x12*\n" + + "\x11operator_admin_id\x18\n" + + " \x01(\x03R\x0foperatorAdminId\"\x84\x03\n" + + " UpdatePrettyDisplayIDPoolRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\apool_id\x18\x02 \x01(\tR\x06poolId\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x1f\n" + + "\vlevel_track\x18\x04 \x01(\tR\n" + + "levelTrack\x12\x1b\n" + + "\tmin_level\x18\x05 \x01(\x05R\bminLevel\x12\x1b\n" + + "\tmax_level\x18\x06 \x01(\x05R\bmaxLevel\x12\x1b\n" + + "\trule_type\x18\a \x01(\tR\bruleType\x12(\n" + + "\x10rule_config_json\x18\b \x01(\tR\x0eruleConfigJson\x12\x16\n" + + "\x06status\x18\t \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "sort_order\x18\n" + + " \x01(\x05R\tsortOrder\x12*\n" + + "\x11operator_admin_id\x18\v \x01(\x03R\x0foperatorAdminId\"U\n" + + "\x1bPrettyDisplayIDPoolResponse\x126\n" + + "\x04pool\x18\x01 \x01(\v2\".hyapp.user.v1.PrettyDisplayIDPoolR\x04pool\"\xf3\x01\n" + + "\x1fGeneratePrettyDisplayIDsRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\apool_id\x18\x02 \x01(\tR\x06poolId\x12\x1b\n" + + "\trule_type\x18\x03 \x01(\tR\bruleType\x12(\n" + + "\x10rule_config_json\x18\x04 \x01(\tR\x0eruleConfigJson\x12\x14\n" + + "\x05count\x18\x05 \x01(\x05R\x05count\x12*\n" + + "\x11operator_admin_id\x18\x06 \x01(\x03R\x0foperatorAdminId\"g\n" + + " GeneratePrettyDisplayIDsResponse\x12C\n" + + "\x05batch\x18\x01 \x01(\v2-.hyapp.user.v1.PrettyDisplayIDGenerationBatchR\x05batch\"\x8b\x02\n" + + "\x1bListPrettyDisplayIDsRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\apool_id\x18\x02 \x01(\tR\x06poolId\x12\x16\n" + + "\x06source\x18\x03 \x01(\tR\x06source\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x12\x18\n" + + "\akeyword\x18\x05 \x01(\tR\akeyword\x12(\n" + + "\x10assigned_user_id\x18\x06 \x01(\x03R\x0eassignedUserId\x12\x12\n" + + "\x04page\x18\a \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\b \x01(\x05R\bpageSize\"j\n" + + "\x1cListPrettyDisplayIDsResponse\x124\n" + + "\x05items\x18\x01 \x03(\v2\x1e.hyapp.user.v1.PrettyDisplayIDR\x05items\x12\x14\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\"\xca\x01\n" + + "\x1fSetPrettyDisplayIDStatusRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1b\n" + + "\tpretty_id\x18\x02 \x01(\tR\bprettyId\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x12*\n" + + "\x11operator_admin_id\x18\x04 \x01(\x03R\x0foperatorAdminId\x12\x16\n" + + "\x06reason\x18\x05 \x01(\tR\x06reason\"M\n" + + "\x17PrettyDisplayIDResponse\x122\n" + + "\x04item\x18\x01 \x01(\v2\x1e.hyapp.user.v1.PrettyDisplayIDR\x04item\"\x85\x02\n" + + " AdminGrantPrettyDisplayIDRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12&\n" + + "\x0fdisplay_user_id\x18\x03 \x01(\tR\rdisplayUserId\x12\x1f\n" + + "\vduration_ms\x18\x04 \x01(\x03R\n" + + "durationMs\x12\x16\n" + + "\x06reason\x18\x05 \x01(\tR\x06reason\x12*\n" + + "\x11operator_admin_id\x18\x06 \x01(\x03R\x0foperatorAdminId\"\x94\x01\n" + + "!AdminGrantPrettyDisplayIDResponse\x127\n" + + "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity\x12\x1b\n" + + "\tpretty_id\x18\x02 \x01(\tR\bprettyId\x12\x19\n" + + "\blease_id\x18\x03 \x01(\tR\aleaseId*s\n" + "\n" + "UserStatus\x12\x1b\n" + "\x17USER_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" + @@ -9521,13 +11357,23 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\vListRegions\x12!.hyapp.user.v1.ListRegionsRequest\x1a\".hyapp.user.v1.ListRegionsResponse\x12K\n" + "\tGetRegion\x12\x1f.hyapp.user.v1.GetRegionRequest\x1a\x1d.hyapp.user.v1.RegionResponse\x12Q\n" + "\fUpdateRegion\x12\".hyapp.user.v1.UpdateRegionRequest\x1a\x1d.hyapp.user.v1.RegionResponse\x12e\n" + - "\x16ReplaceRegionCountries\x12,.hyapp.user.v1.ReplaceRegionCountriesRequest\x1a\x1d.hyapp.user.v1.RegionResponse2\xd3\x04\n" + + "\x16ReplaceRegionCountries\x12,.hyapp.user.v1.ReplaceRegionCountriesRequest\x1a\x1d.hyapp.user.v1.RegionResponse2\xea\x06\n" + "\x13UserIdentityService\x12`\n" + "\x0fGetUserIdentity\x12%.hyapp.user.v1.GetUserIdentityRequest\x1a&.hyapp.user.v1.GetUserIdentityResponse\x12o\n" + "\x14ResolveDisplayUserID\x12*.hyapp.user.v1.ResolveDisplayUserIDRequest\x1a+.hyapp.user.v1.ResolveDisplayUserIDResponse\x12l\n" + "\x13ChangeDisplayUserID\x12).hyapp.user.v1.ChangeDisplayUserIDRequest\x1a*.hyapp.user.v1.ChangeDisplayUserIDResponse\x12{\n" + - "\x18ApplyPrettyDisplayUserID\x12..hyapp.user.v1.ApplyPrettyDisplayUserIDRequest\x1a/.hyapp.user.v1.ApplyPrettyDisplayUserIDResponse\x12~\n" + - "\x19ExpirePrettyDisplayUserID\x12/.hyapp.user.v1.ExpirePrettyDisplayUserIDRequest\x1a0.hyapp.user.v1.ExpirePrettyDisplayUserIDResponseB&Z$hyapp.local/api/proto/user/v1;userv1b\x06proto3" + "\x18ApplyPrettyDisplayUserID\x12..hyapp.user.v1.ApplyPrettyDisplayUserIDRequest\x1a/.hyapp.user.v1.ApplyPrettyDisplayUserIDResponse\x12\x8a\x01\n" + + "\x1dListAvailablePrettyDisplayIDs\x123.hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest\x1a4.hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse\x12\x87\x01\n" + + "\x1cApplyPrettyDisplayIDFromPool\x122.hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest\x1a3.hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse\x12~\n" + + "\x19ExpirePrettyDisplayUserID\x12/.hyapp.user.v1.ExpirePrettyDisplayUserIDRequest\x1a0.hyapp.user.v1.ExpirePrettyDisplayUserIDResponse2\xf4\x06\n" + + "\x1fUserPrettyDisplayIDAdminService\x12{\n" + + "\x18ListPrettyDisplayIDPools\x12..hyapp.user.v1.ListPrettyDisplayIDPoolsRequest\x1a/.hyapp.user.v1.ListPrettyDisplayIDPoolsResponse\x12x\n" + + "\x19CreatePrettyDisplayIDPool\x12/.hyapp.user.v1.CreatePrettyDisplayIDPoolRequest\x1a*.hyapp.user.v1.PrettyDisplayIDPoolResponse\x12x\n" + + "\x19UpdatePrettyDisplayIDPool\x12/.hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest\x1a*.hyapp.user.v1.PrettyDisplayIDPoolResponse\x12{\n" + + "\x18GeneratePrettyDisplayIDs\x12..hyapp.user.v1.GeneratePrettyDisplayIDsRequest\x1a/.hyapp.user.v1.GeneratePrettyDisplayIDsResponse\x12o\n" + + "\x14ListPrettyDisplayIDs\x12*.hyapp.user.v1.ListPrettyDisplayIDsRequest\x1a+.hyapp.user.v1.ListPrettyDisplayIDsResponse\x12r\n" + + "\x18SetPrettyDisplayIDStatus\x12..hyapp.user.v1.SetPrettyDisplayIDStatusRequest\x1a&.hyapp.user.v1.PrettyDisplayIDResponse\x12~\n" + + "\x19AdminGrantPrettyDisplayID\x12/.hyapp.user.v1.AdminGrantPrettyDisplayIDRequest\x1a0.hyapp.user.v1.AdminGrantPrettyDisplayIDResponseB&Z$hyapp.local/api/proto/user/v1;userv1b\x06proto3" var ( file_proto_user_v1_user_proto_rawDescOnce sync.Once @@ -9542,7 +11388,7 @@ func file_proto_user_v1_user_proto_rawDescGZIP() []byte { } var file_proto_user_v1_user_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 122) +var file_proto_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 142) var file_proto_user_v1_user_proto_goTypes = []any{ (UserStatus)(0), // 0: hyapp.user.v1.UserStatus (*RequestMeta)(nil), // 1: hyapp.user.v1.RequestMeta @@ -9666,7 +11512,27 @@ var file_proto_user_v1_user_proto_goTypes = []any{ (*ApplyPrettyDisplayUserIDResponse)(nil), // 119: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse (*ExpirePrettyDisplayUserIDRequest)(nil), // 120: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest (*ExpirePrettyDisplayUserIDResponse)(nil), // 121: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse - nil, // 122: hyapp.user.v1.BatchGetUsersResponse.UsersEntry + (*PrettyDisplayIDPool)(nil), // 122: hyapp.user.v1.PrettyDisplayIDPool + (*PrettyDisplayID)(nil), // 123: hyapp.user.v1.PrettyDisplayID + (*PrettyDisplayIDGenerationBatch)(nil), // 124: hyapp.user.v1.PrettyDisplayIDGenerationBatch + (*ListAvailablePrettyDisplayIDsRequest)(nil), // 125: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest + (*ListAvailablePrettyDisplayIDsResponse)(nil), // 126: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse + (*ApplyPrettyDisplayIDFromPoolRequest)(nil), // 127: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest + (*ApplyPrettyDisplayIDFromPoolResponse)(nil), // 128: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse + (*ListPrettyDisplayIDPoolsRequest)(nil), // 129: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest + (*ListPrettyDisplayIDPoolsResponse)(nil), // 130: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse + (*CreatePrettyDisplayIDPoolRequest)(nil), // 131: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest + (*UpdatePrettyDisplayIDPoolRequest)(nil), // 132: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest + (*PrettyDisplayIDPoolResponse)(nil), // 133: hyapp.user.v1.PrettyDisplayIDPoolResponse + (*GeneratePrettyDisplayIDsRequest)(nil), // 134: hyapp.user.v1.GeneratePrettyDisplayIDsRequest + (*GeneratePrettyDisplayIDsResponse)(nil), // 135: hyapp.user.v1.GeneratePrettyDisplayIDsResponse + (*ListPrettyDisplayIDsRequest)(nil), // 136: hyapp.user.v1.ListPrettyDisplayIDsRequest + (*ListPrettyDisplayIDsResponse)(nil), // 137: hyapp.user.v1.ListPrettyDisplayIDsResponse + (*SetPrettyDisplayIDStatusRequest)(nil), // 138: hyapp.user.v1.SetPrettyDisplayIDStatusRequest + (*PrettyDisplayIDResponse)(nil), // 139: hyapp.user.v1.PrettyDisplayIDResponse + (*AdminGrantPrettyDisplayIDRequest)(nil), // 140: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest + (*AdminGrantPrettyDisplayIDResponse)(nil), // 141: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse + nil, // 142: hyapp.user.v1.BatchGetUsersResponse.UsersEntry } var file_proto_user_v1_user_proto_depIdxs = []int32{ 1, // 0: hyapp.user.v1.ResolveAppRequest.meta:type_name -> hyapp.user.v1.RequestMeta @@ -9729,7 +11595,7 @@ var file_proto_user_v1_user_proto_depIdxs = []int32{ 1, // 57: hyapp.user.v1.SubmitReportRequest.meta:type_name -> hyapp.user.v1.RequestMeta 67, // 58: hyapp.user.v1.SubmitReportResponse.report:type_name -> hyapp.user.v1.UserReport 1, // 59: hyapp.user.v1.BatchGetUsersRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 122, // 60: hyapp.user.v1.BatchGetUsersResponse.users:type_name -> hyapp.user.v1.BatchGetUsersResponse.UsersEntry + 142, // 60: hyapp.user.v1.BatchGetUsersResponse.users:type_name -> hyapp.user.v1.BatchGetUsersResponse.UsersEntry 1, // 61: hyapp.user.v1.ListUserIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta 1, // 62: hyapp.user.v1.UpdateUserProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta 5, // 63: hyapp.user.v1.UpdateUserProfileResponse.user:type_name -> hyapp.user.v1.User @@ -9777,118 +11643,154 @@ var file_proto_user_v1_user_proto_depIdxs = []int32{ 111, // 105: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity 1, // 106: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta 111, // 107: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 5, // 108: hyapp.user.v1.BatchGetUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.User - 14, // 109: hyapp.user.v1.UserService.GetUser:input_type -> hyapp.user.v1.GetUserRequest - 16, // 110: hyapp.user.v1.UserService.BusinessUserLookup:input_type -> hyapp.user.v1.BusinessUserLookupRequest - 20, // 111: hyapp.user.v1.UserService.GetMyProfileStats:input_type -> hyapp.user.v1.GetMyProfileStatsRequest - 70, // 112: hyapp.user.v1.UserService.BatchGetUsers:input_type -> hyapp.user.v1.BatchGetUsersRequest - 72, // 113: hyapp.user.v1.UserService.ListUserIDs:input_type -> hyapp.user.v1.ListUserIDsRequest - 9, // 114: hyapp.user.v1.UserService.GetUserMicLifetimeStats:input_type -> hyapp.user.v1.GetUserMicLifetimeStatsRequest - 74, // 115: hyapp.user.v1.UserService.UpdateUserProfile:input_type -> hyapp.user.v1.UpdateUserProfileRequest - 76, // 116: hyapp.user.v1.UserService.ChangeUserCountry:input_type -> hyapp.user.v1.ChangeUserCountryRequest - 77, // 117: hyapp.user.v1.UserService.AdminChangeUserCountry:input_type -> hyapp.user.v1.AdminChangeUserCountryRequest - 79, // 118: hyapp.user.v1.UserService.SetUserStatus:input_type -> hyapp.user.v1.SetUserStatusRequest - 82, // 119: hyapp.user.v1.UserService.CreateManagerUserBlock:input_type -> hyapp.user.v1.CreateManagerUserBlockRequest - 84, // 120: hyapp.user.v1.UserService.ListManagerUserBlocks:input_type -> hyapp.user.v1.ListManagerUserBlocksRequest - 86, // 121: hyapp.user.v1.UserService.UnblockManagerUser:input_type -> hyapp.user.v1.UnblockManagerUserRequest - 88, // 122: hyapp.user.v1.UserService.CompleteOnboarding:input_type -> hyapp.user.v1.CompleteOnboardingRequest - 22, // 123: hyapp.user.v1.UserSocialService.RecordProfileVisit:input_type -> hyapp.user.v1.RecordProfileVisitRequest - 25, // 124: hyapp.user.v1.UserSocialService.ListProfileVisitors:input_type -> hyapp.user.v1.ListProfileVisitorsRequest - 27, // 125: hyapp.user.v1.UserSocialService.FollowUser:input_type -> hyapp.user.v1.FollowUserRequest - 29, // 126: hyapp.user.v1.UserSocialService.UnfollowUser:input_type -> hyapp.user.v1.UnfollowUserRequest - 32, // 127: hyapp.user.v1.UserSocialService.ListFollowing:input_type -> hyapp.user.v1.ListFollowingRequest - 34, // 128: hyapp.user.v1.UserSocialService.ApplyFriend:input_type -> hyapp.user.v1.ApplyFriendRequest - 36, // 129: hyapp.user.v1.UserSocialService.AcceptFriendApplication:input_type -> hyapp.user.v1.AcceptFriendApplicationRequest - 38, // 130: hyapp.user.v1.UserSocialService.DeleteFriend:input_type -> hyapp.user.v1.DeleteFriendRequest - 41, // 131: hyapp.user.v1.UserSocialService.ListFriends:input_type -> hyapp.user.v1.ListFriendsRequest - 44, // 132: hyapp.user.v1.UserSocialService.ListFriendApplications:input_type -> hyapp.user.v1.ListFriendApplicationsRequest - 68, // 133: hyapp.user.v1.UserSocialService.SubmitReport:input_type -> hyapp.user.v1.SubmitReportRequest - 50, // 134: hyapp.user.v1.UserCPService.ListCPApplications:input_type -> hyapp.user.v1.ListCPApplicationsRequest - 52, // 135: hyapp.user.v1.UserCPService.AcceptCPApplication:input_type -> hyapp.user.v1.AcceptCPApplicationRequest - 54, // 136: hyapp.user.v1.UserCPService.RejectCPApplication:input_type -> hyapp.user.v1.RejectCPApplicationRequest - 56, // 137: hyapp.user.v1.UserCPService.ListCPRelationships:input_type -> hyapp.user.v1.ListCPRelationshipsRequest - 58, // 138: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:input_type -> hyapp.user.v1.PrepareBreakCPRelationshipRequest - 60, // 139: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:input_type -> hyapp.user.v1.ConfirmBreakCPRelationshipRequest - 62, // 140: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:input_type -> hyapp.user.v1.CancelBreakCPRelationshipRequest - 65, // 141: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:input_type -> hyapp.user.v1.ConsumeRoomGiftCPEventRequest - 11, // 142: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 143: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 144: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 145: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest - 90, // 146: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest - 92, // 147: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest - 3, // 148: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest - 96, // 149: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest - 98, // 150: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest - 100, // 151: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest - 103, // 152: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest - 105, // 153: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest - 107, // 154: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest - 108, // 155: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest - 109, // 156: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest - 112, // 157: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest - 114, // 158: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest - 116, // 159: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest - 118, // 160: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest - 120, // 161: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest - 15, // 162: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse - 18, // 163: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse - 21, // 164: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse - 71, // 165: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse - 73, // 166: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse - 10, // 167: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse - 75, // 168: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse - 78, // 169: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse - 78, // 170: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse - 80, // 171: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse - 83, // 172: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse - 85, // 173: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse - 87, // 174: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse - 89, // 175: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse - 23, // 176: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse - 26, // 177: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse - 28, // 178: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse - 30, // 179: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse - 33, // 180: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse - 35, // 181: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse - 37, // 182: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse - 39, // 183: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse - 42, // 184: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse - 45, // 185: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse - 69, // 186: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse - 51, // 187: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse - 53, // 188: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse - 55, // 189: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse - 57, // 190: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse - 59, // 191: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse - 61, // 192: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse - 63, // 193: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse - 66, // 194: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse - 12, // 195: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 196: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 197: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 198: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse - 91, // 199: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse - 93, // 200: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse - 4, // 201: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse - 97, // 202: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse - 99, // 203: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse - 101, // 204: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse - 104, // 205: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse - 106, // 206: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse - 110, // 207: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse - 110, // 208: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse - 110, // 209: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse - 113, // 210: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse - 115, // 211: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse - 117, // 212: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse - 119, // 213: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse - 121, // 214: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse - 162, // [162:215] is the sub-list for method output_type - 109, // [109:162] is the sub-list for method input_type - 109, // [109:109] is the sub-list for extension type_name - 109, // [109:109] is the sub-list for extension extendee - 0, // [0:109] is the sub-list for field type_name + 122, // 108: hyapp.user.v1.PrettyDisplayID.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 109: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 123, // 110: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 111: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 111, // 112: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 113: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 122, // 114: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 115: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 116: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 122, // 117: hyapp.user.v1.PrettyDisplayIDPoolResponse.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 118: hyapp.user.v1.GeneratePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 124, // 119: hyapp.user.v1.GeneratePrettyDisplayIDsResponse.batch:type_name -> hyapp.user.v1.PrettyDisplayIDGenerationBatch + 1, // 120: hyapp.user.v1.ListPrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 123, // 121: hyapp.user.v1.ListPrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 122: hyapp.user.v1.SetPrettyDisplayIDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 123, // 123: hyapp.user.v1.PrettyDisplayIDResponse.item:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 124: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 111, // 125: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 5, // 126: hyapp.user.v1.BatchGetUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.User + 14, // 127: hyapp.user.v1.UserService.GetUser:input_type -> hyapp.user.v1.GetUserRequest + 16, // 128: hyapp.user.v1.UserService.BusinessUserLookup:input_type -> hyapp.user.v1.BusinessUserLookupRequest + 20, // 129: hyapp.user.v1.UserService.GetMyProfileStats:input_type -> hyapp.user.v1.GetMyProfileStatsRequest + 70, // 130: hyapp.user.v1.UserService.BatchGetUsers:input_type -> hyapp.user.v1.BatchGetUsersRequest + 72, // 131: hyapp.user.v1.UserService.ListUserIDs:input_type -> hyapp.user.v1.ListUserIDsRequest + 9, // 132: hyapp.user.v1.UserService.GetUserMicLifetimeStats:input_type -> hyapp.user.v1.GetUserMicLifetimeStatsRequest + 74, // 133: hyapp.user.v1.UserService.UpdateUserProfile:input_type -> hyapp.user.v1.UpdateUserProfileRequest + 76, // 134: hyapp.user.v1.UserService.ChangeUserCountry:input_type -> hyapp.user.v1.ChangeUserCountryRequest + 77, // 135: hyapp.user.v1.UserService.AdminChangeUserCountry:input_type -> hyapp.user.v1.AdminChangeUserCountryRequest + 79, // 136: hyapp.user.v1.UserService.SetUserStatus:input_type -> hyapp.user.v1.SetUserStatusRequest + 82, // 137: hyapp.user.v1.UserService.CreateManagerUserBlock:input_type -> hyapp.user.v1.CreateManagerUserBlockRequest + 84, // 138: hyapp.user.v1.UserService.ListManagerUserBlocks:input_type -> hyapp.user.v1.ListManagerUserBlocksRequest + 86, // 139: hyapp.user.v1.UserService.UnblockManagerUser:input_type -> hyapp.user.v1.UnblockManagerUserRequest + 88, // 140: hyapp.user.v1.UserService.CompleteOnboarding:input_type -> hyapp.user.v1.CompleteOnboardingRequest + 22, // 141: hyapp.user.v1.UserSocialService.RecordProfileVisit:input_type -> hyapp.user.v1.RecordProfileVisitRequest + 25, // 142: hyapp.user.v1.UserSocialService.ListProfileVisitors:input_type -> hyapp.user.v1.ListProfileVisitorsRequest + 27, // 143: hyapp.user.v1.UserSocialService.FollowUser:input_type -> hyapp.user.v1.FollowUserRequest + 29, // 144: hyapp.user.v1.UserSocialService.UnfollowUser:input_type -> hyapp.user.v1.UnfollowUserRequest + 32, // 145: hyapp.user.v1.UserSocialService.ListFollowing:input_type -> hyapp.user.v1.ListFollowingRequest + 34, // 146: hyapp.user.v1.UserSocialService.ApplyFriend:input_type -> hyapp.user.v1.ApplyFriendRequest + 36, // 147: hyapp.user.v1.UserSocialService.AcceptFriendApplication:input_type -> hyapp.user.v1.AcceptFriendApplicationRequest + 38, // 148: hyapp.user.v1.UserSocialService.DeleteFriend:input_type -> hyapp.user.v1.DeleteFriendRequest + 41, // 149: hyapp.user.v1.UserSocialService.ListFriends:input_type -> hyapp.user.v1.ListFriendsRequest + 44, // 150: hyapp.user.v1.UserSocialService.ListFriendApplications:input_type -> hyapp.user.v1.ListFriendApplicationsRequest + 68, // 151: hyapp.user.v1.UserSocialService.SubmitReport:input_type -> hyapp.user.v1.SubmitReportRequest + 50, // 152: hyapp.user.v1.UserCPService.ListCPApplications:input_type -> hyapp.user.v1.ListCPApplicationsRequest + 52, // 153: hyapp.user.v1.UserCPService.AcceptCPApplication:input_type -> hyapp.user.v1.AcceptCPApplicationRequest + 54, // 154: hyapp.user.v1.UserCPService.RejectCPApplication:input_type -> hyapp.user.v1.RejectCPApplicationRequest + 56, // 155: hyapp.user.v1.UserCPService.ListCPRelationships:input_type -> hyapp.user.v1.ListCPRelationshipsRequest + 58, // 156: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:input_type -> hyapp.user.v1.PrepareBreakCPRelationshipRequest + 60, // 157: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:input_type -> hyapp.user.v1.ConfirmBreakCPRelationshipRequest + 62, // 158: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:input_type -> hyapp.user.v1.CancelBreakCPRelationshipRequest + 65, // 159: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:input_type -> hyapp.user.v1.ConsumeRoomGiftCPEventRequest + 11, // 160: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:input_type -> hyapp.user.v1.CronBatchRequest + 11, // 161: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:input_type -> hyapp.user.v1.CronBatchRequest + 11, // 162: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest + 11, // 163: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest + 90, // 164: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest + 92, // 165: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest + 3, // 166: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest + 96, // 167: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest + 98, // 168: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest + 100, // 169: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest + 103, // 170: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest + 105, // 171: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest + 107, // 172: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest + 108, // 173: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest + 109, // 174: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest + 112, // 175: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest + 114, // 176: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest + 116, // 177: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest + 118, // 178: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest + 125, // 179: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest + 127, // 180: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest + 120, // 181: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest + 129, // 182: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest + 131, // 183: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest + 132, // 184: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest + 134, // 185: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest + 136, // 186: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest + 138, // 187: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest + 140, // 188: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest + 15, // 189: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse + 18, // 190: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse + 21, // 191: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse + 71, // 192: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse + 73, // 193: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse + 10, // 194: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse + 75, // 195: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse + 78, // 196: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse + 78, // 197: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse + 80, // 198: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse + 83, // 199: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse + 85, // 200: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse + 87, // 201: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse + 89, // 202: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse + 23, // 203: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse + 26, // 204: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse + 28, // 205: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse + 30, // 206: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse + 33, // 207: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse + 35, // 208: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse + 37, // 209: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse + 39, // 210: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse + 42, // 211: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse + 45, // 212: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse + 69, // 213: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse + 51, // 214: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse + 53, // 215: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse + 55, // 216: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse + 57, // 217: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse + 59, // 218: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse + 61, // 219: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse + 63, // 220: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse + 66, // 221: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse + 12, // 222: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse + 12, // 223: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse + 12, // 224: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse + 12, // 225: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse + 91, // 226: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse + 93, // 227: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse + 4, // 228: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse + 97, // 229: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse + 99, // 230: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse + 101, // 231: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse + 104, // 232: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse + 106, // 233: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse + 110, // 234: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse + 110, // 235: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse + 110, // 236: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse + 113, // 237: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse + 115, // 238: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse + 117, // 239: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse + 119, // 240: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse + 126, // 241: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse + 128, // 242: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse + 121, // 243: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse + 130, // 244: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse + 133, // 245: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse + 133, // 246: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse + 135, // 247: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse + 137, // 248: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse + 139, // 249: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse + 141, // 250: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse + 189, // [189:251] is the sub-list for method output_type + 127, // [127:189] is the sub-list for method input_type + 127, // [127:127] is the sub-list for extension type_name + 127, // [127:127] is the sub-list for extension extendee + 0, // [0:127] is the sub-list for field type_name } func init() { file_proto_user_v1_user_proto_init() } @@ -9904,9 +11806,9 @@ func file_proto_user_v1_user_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_user_v1_user_proto_rawDesc), len(file_proto_user_v1_user_proto_rawDesc)), NumEnums: 1, - NumMessages: 122, + NumMessages: 142, NumExtensions: 0, - NumServices: 11, + NumServices: 12, }, GoTypes: file_proto_user_v1_user_proto_goTypes, DependencyIndexes: file_proto_user_v1_user_proto_depIdxs, diff --git a/api/proto/user/v1/user.proto b/api/proto/user/v1/user.proto index 705dd540..e5fbce1f 100644 --- a/api/proto/user/v1/user.proto +++ b/api/proto/user/v1/user.proto @@ -83,6 +83,8 @@ message User { bool country_enabled = 26; string app_code = 27; InviteOverview invite = 28; + string pretty_id = 29; + string pretty_display_user_id = 30; } // InviteOverview 是我的页可直接展示的邀请码和邀请计数 read model。 @@ -903,6 +905,8 @@ message UserIdentity { string display_user_id_kind = 5; int64 display_user_id_expires_at_ms = 6; string app_code = 7; + string pretty_id = 8; + string pretty_display_user_id = 9; } // GetUserIdentityRequest 按 user_id 查询当前短号。 @@ -968,6 +972,184 @@ message ExpirePrettyDisplayUserIDResponse { UserIdentity identity = 1; } +// PrettyDisplayIDPool 是靓号池等级区间和生成规则配置。 +message PrettyDisplayIDPool { + string app_code = 1; + string pool_id = 2; + string name = 3; + string level_track = 4; + int32 min_level = 5; + int32 max_level = 6; + string rule_type = 7; + string rule_config_json = 8; + string status = 9; + int32 sort_order = 10; + int64 created_by_admin_id = 11; + int64 updated_by_admin_id = 12; + int64 created_at_ms = 13; + int64 updated_at_ms = 14; +} + +// PrettyDisplayID 是一个可申请或已发放的靓号记录。 +message PrettyDisplayID { + string app_code = 1; + string pretty_id = 2; + string pool_id = 3; + string source = 4; + string display_user_id = 5; + string status = 6; + int64 assigned_user_id = 7; + string assigned_lease_id = 8; + int64 assigned_at_ms = 9; + int64 released_at_ms = 10; + string release_reason = 11; + string generated_batch_id = 12; + int64 created_by_admin_id = 13; + int64 created_at_ms = 14; + int64 updated_at_ms = 15; + PrettyDisplayIDPool pool = 16; +} + +message PrettyDisplayIDGenerationBatch { + string app_code = 1; + string batch_id = 2; + string pool_id = 3; + string rule_type = 4; + string rule_config_json = 5; + int32 requested_count = 6; + int32 generated_count = 7; + int32 skipped_conflict_count = 8; + string status = 9; + int64 operator_admin_id = 10; + string request_id = 11; + int64 created_at_ms = 12; + int64 updated_at_ms = 13; +} + +message ListAvailablePrettyDisplayIDsRequest { + RequestMeta meta = 1; + int64 user_id = 2; + int32 page = 3; + int32 page_size = 4; +} + +message ListAvailablePrettyDisplayIDsResponse { + repeated PrettyDisplayID items = 1; + int64 total = 2; +} + +message ApplyPrettyDisplayIDFromPoolRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string pretty_id = 3; +} + +message ApplyPrettyDisplayIDFromPoolResponse { + UserIdentity identity = 1; + string pretty_id = 2; + string lease_id = 3; +} + +message ListPrettyDisplayIDPoolsRequest { + RequestMeta meta = 1; + string status = 2; + string level_track = 3; + int32 page = 4; + int32 page_size = 5; +} + +message ListPrettyDisplayIDPoolsResponse { + repeated PrettyDisplayIDPool items = 1; + int64 total = 2; +} + +message CreatePrettyDisplayIDPoolRequest { + RequestMeta meta = 1; + string name = 2; + string level_track = 3; + int32 min_level = 4; + int32 max_level = 5; + string rule_type = 6; + string rule_config_json = 7; + string status = 8; + int32 sort_order = 9; + int64 operator_admin_id = 10; +} + +message UpdatePrettyDisplayIDPoolRequest { + RequestMeta meta = 1; + string pool_id = 2; + string name = 3; + string level_track = 4; + int32 min_level = 5; + int32 max_level = 6; + string rule_type = 7; + string rule_config_json = 8; + string status = 9; + int32 sort_order = 10; + int64 operator_admin_id = 11; +} + +message PrettyDisplayIDPoolResponse { + PrettyDisplayIDPool pool = 1; +} + +message GeneratePrettyDisplayIDsRequest { + RequestMeta meta = 1; + string pool_id = 2; + string rule_type = 3; + string rule_config_json = 4; + int32 count = 5; + int64 operator_admin_id = 6; +} + +message GeneratePrettyDisplayIDsResponse { + PrettyDisplayIDGenerationBatch batch = 1; +} + +message ListPrettyDisplayIDsRequest { + RequestMeta meta = 1; + string pool_id = 2; + string source = 3; + string status = 4; + string keyword = 5; + int64 assigned_user_id = 6; + int32 page = 7; + int32 page_size = 8; +} + +message ListPrettyDisplayIDsResponse { + repeated PrettyDisplayID items = 1; + int64 total = 2; +} + +message SetPrettyDisplayIDStatusRequest { + RequestMeta meta = 1; + string pretty_id = 2; + string status = 3; + int64 operator_admin_id = 4; + string reason = 5; +} + +message PrettyDisplayIDResponse { + PrettyDisplayID item = 1; +} + +message AdminGrantPrettyDisplayIDRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; + string display_user_id = 3; + int64 duration_ms = 4; + string reason = 5; + int64 operator_admin_id = 6; +} + +message AdminGrantPrettyDisplayIDResponse { + UserIdentity identity = 1; + string pretty_id = 2; + string lease_id = 3; +} + // UserService 提供用户主状态、基础资料和注册资料读写接口。 service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse); @@ -1064,5 +1246,18 @@ service UserIdentityService { rpc ResolveDisplayUserID(ResolveDisplayUserIDRequest) returns (ResolveDisplayUserIDResponse); rpc ChangeDisplayUserID(ChangeDisplayUserIDRequest) returns (ChangeDisplayUserIDResponse); rpc ApplyPrettyDisplayUserID(ApplyPrettyDisplayUserIDRequest) returns (ApplyPrettyDisplayUserIDResponse); + rpc ListAvailablePrettyDisplayIDs(ListAvailablePrettyDisplayIDsRequest) returns (ListAvailablePrettyDisplayIDsResponse); + rpc ApplyPrettyDisplayIDFromPool(ApplyPrettyDisplayIDFromPoolRequest) returns (ApplyPrettyDisplayIDFromPoolResponse); rpc ExpirePrettyDisplayUserID(ExpirePrettyDisplayUserIDRequest) returns (ExpirePrettyDisplayUserIDResponse); } + +// UserPrettyDisplayIDAdminService 承载后台靓号池和后台发放能力。 +service UserPrettyDisplayIDAdminService { + rpc ListPrettyDisplayIDPools(ListPrettyDisplayIDPoolsRequest) returns (ListPrettyDisplayIDPoolsResponse); + rpc CreatePrettyDisplayIDPool(CreatePrettyDisplayIDPoolRequest) returns (PrettyDisplayIDPoolResponse); + rpc UpdatePrettyDisplayIDPool(UpdatePrettyDisplayIDPoolRequest) returns (PrettyDisplayIDPoolResponse); + rpc GeneratePrettyDisplayIDs(GeneratePrettyDisplayIDsRequest) returns (GeneratePrettyDisplayIDsResponse); + rpc ListPrettyDisplayIDs(ListPrettyDisplayIDsRequest) returns (ListPrettyDisplayIDsResponse); + rpc SetPrettyDisplayIDStatus(SetPrettyDisplayIDStatusRequest) returns (PrettyDisplayIDResponse); + rpc AdminGrantPrettyDisplayID(AdminGrantPrettyDisplayIDRequest) returns (AdminGrantPrettyDisplayIDResponse); +} diff --git a/api/proto/user/v1/user_grpc.pb.go b/api/proto/user/v1/user_grpc.pb.go index 0e3d733d..d35f672a 100644 --- a/api/proto/user/v1/user_grpc.pb.go +++ b/api/proto/user/v1/user_grpc.pb.go @@ -2527,11 +2527,13 @@ var RegionAdminService_ServiceDesc = grpc.ServiceDesc{ } const ( - UserIdentityService_GetUserIdentity_FullMethodName = "/hyapp.user.v1.UserIdentityService/GetUserIdentity" - UserIdentityService_ResolveDisplayUserID_FullMethodName = "/hyapp.user.v1.UserIdentityService/ResolveDisplayUserID" - UserIdentityService_ChangeDisplayUserID_FullMethodName = "/hyapp.user.v1.UserIdentityService/ChangeDisplayUserID" - UserIdentityService_ApplyPrettyDisplayUserID_FullMethodName = "/hyapp.user.v1.UserIdentityService/ApplyPrettyDisplayUserID" - UserIdentityService_ExpirePrettyDisplayUserID_FullMethodName = "/hyapp.user.v1.UserIdentityService/ExpirePrettyDisplayUserID" + UserIdentityService_GetUserIdentity_FullMethodName = "/hyapp.user.v1.UserIdentityService/GetUserIdentity" + UserIdentityService_ResolveDisplayUserID_FullMethodName = "/hyapp.user.v1.UserIdentityService/ResolveDisplayUserID" + UserIdentityService_ChangeDisplayUserID_FullMethodName = "/hyapp.user.v1.UserIdentityService/ChangeDisplayUserID" + UserIdentityService_ApplyPrettyDisplayUserID_FullMethodName = "/hyapp.user.v1.UserIdentityService/ApplyPrettyDisplayUserID" + UserIdentityService_ListAvailablePrettyDisplayIDs_FullMethodName = "/hyapp.user.v1.UserIdentityService/ListAvailablePrettyDisplayIDs" + UserIdentityService_ApplyPrettyDisplayIDFromPool_FullMethodName = "/hyapp.user.v1.UserIdentityService/ApplyPrettyDisplayIDFromPool" + UserIdentityService_ExpirePrettyDisplayUserID_FullMethodName = "/hyapp.user.v1.UserIdentityService/ExpirePrettyDisplayUserID" ) // UserIdentityServiceClient is the client API for UserIdentityService service. @@ -2544,6 +2546,8 @@ type UserIdentityServiceClient interface { ResolveDisplayUserID(ctx context.Context, in *ResolveDisplayUserIDRequest, opts ...grpc.CallOption) (*ResolveDisplayUserIDResponse, error) ChangeDisplayUserID(ctx context.Context, in *ChangeDisplayUserIDRequest, opts ...grpc.CallOption) (*ChangeDisplayUserIDResponse, error) ApplyPrettyDisplayUserID(ctx context.Context, in *ApplyPrettyDisplayUserIDRequest, opts ...grpc.CallOption) (*ApplyPrettyDisplayUserIDResponse, error) + ListAvailablePrettyDisplayIDs(ctx context.Context, in *ListAvailablePrettyDisplayIDsRequest, opts ...grpc.CallOption) (*ListAvailablePrettyDisplayIDsResponse, error) + ApplyPrettyDisplayIDFromPool(ctx context.Context, in *ApplyPrettyDisplayIDFromPoolRequest, opts ...grpc.CallOption) (*ApplyPrettyDisplayIDFromPoolResponse, error) ExpirePrettyDisplayUserID(ctx context.Context, in *ExpirePrettyDisplayUserIDRequest, opts ...grpc.CallOption) (*ExpirePrettyDisplayUserIDResponse, error) } @@ -2595,6 +2599,26 @@ func (c *userIdentityServiceClient) ApplyPrettyDisplayUserID(ctx context.Context return out, nil } +func (c *userIdentityServiceClient) ListAvailablePrettyDisplayIDs(ctx context.Context, in *ListAvailablePrettyDisplayIDsRequest, opts ...grpc.CallOption) (*ListAvailablePrettyDisplayIDsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListAvailablePrettyDisplayIDsResponse) + err := c.cc.Invoke(ctx, UserIdentityService_ListAvailablePrettyDisplayIDs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userIdentityServiceClient) ApplyPrettyDisplayIDFromPool(ctx context.Context, in *ApplyPrettyDisplayIDFromPoolRequest, opts ...grpc.CallOption) (*ApplyPrettyDisplayIDFromPoolResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ApplyPrettyDisplayIDFromPoolResponse) + err := c.cc.Invoke(ctx, UserIdentityService_ApplyPrettyDisplayIDFromPool_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userIdentityServiceClient) ExpirePrettyDisplayUserID(ctx context.Context, in *ExpirePrettyDisplayUserIDRequest, opts ...grpc.CallOption) (*ExpirePrettyDisplayUserIDResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ExpirePrettyDisplayUserIDResponse) @@ -2615,6 +2639,8 @@ type UserIdentityServiceServer interface { ResolveDisplayUserID(context.Context, *ResolveDisplayUserIDRequest) (*ResolveDisplayUserIDResponse, error) ChangeDisplayUserID(context.Context, *ChangeDisplayUserIDRequest) (*ChangeDisplayUserIDResponse, error) ApplyPrettyDisplayUserID(context.Context, *ApplyPrettyDisplayUserIDRequest) (*ApplyPrettyDisplayUserIDResponse, error) + ListAvailablePrettyDisplayIDs(context.Context, *ListAvailablePrettyDisplayIDsRequest) (*ListAvailablePrettyDisplayIDsResponse, error) + ApplyPrettyDisplayIDFromPool(context.Context, *ApplyPrettyDisplayIDFromPoolRequest) (*ApplyPrettyDisplayIDFromPoolResponse, error) ExpirePrettyDisplayUserID(context.Context, *ExpirePrettyDisplayUserIDRequest) (*ExpirePrettyDisplayUserIDResponse, error) mustEmbedUnimplementedUserIdentityServiceServer() } @@ -2638,6 +2664,12 @@ func (UnimplementedUserIdentityServiceServer) ChangeDisplayUserID(context.Contex func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayUserID(context.Context, *ApplyPrettyDisplayUserIDRequest) (*ApplyPrettyDisplayUserIDResponse, error) { return nil, status.Error(codes.Unimplemented, "method ApplyPrettyDisplayUserID not implemented") } +func (UnimplementedUserIdentityServiceServer) ListAvailablePrettyDisplayIDs(context.Context, *ListAvailablePrettyDisplayIDsRequest) (*ListAvailablePrettyDisplayIDsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListAvailablePrettyDisplayIDs not implemented") +} +func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayIDFromPool(context.Context, *ApplyPrettyDisplayIDFromPoolRequest) (*ApplyPrettyDisplayIDFromPoolResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ApplyPrettyDisplayIDFromPool not implemented") +} func (UnimplementedUserIdentityServiceServer) ExpirePrettyDisplayUserID(context.Context, *ExpirePrettyDisplayUserIDRequest) (*ExpirePrettyDisplayUserIDResponse, error) { return nil, status.Error(codes.Unimplemented, "method ExpirePrettyDisplayUserID not implemented") } @@ -2734,6 +2766,42 @@ func _UserIdentityService_ApplyPrettyDisplayUserID_Handler(srv interface{}, ctx return interceptor(ctx, in, info, handler) } +func _UserIdentityService_ListAvailablePrettyDisplayIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAvailablePrettyDisplayIDsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserIdentityServiceServer).ListAvailablePrettyDisplayIDs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserIdentityService_ListAvailablePrettyDisplayIDs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserIdentityServiceServer).ListAvailablePrettyDisplayIDs(ctx, req.(*ListAvailablePrettyDisplayIDsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserIdentityService_ApplyPrettyDisplayIDFromPool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplyPrettyDisplayIDFromPoolRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserIdentityServiceServer).ApplyPrettyDisplayIDFromPool(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserIdentityService_ApplyPrettyDisplayIDFromPool_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserIdentityServiceServer).ApplyPrettyDisplayIDFromPool(ctx, req.(*ApplyPrettyDisplayIDFromPoolRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserIdentityService_ExpirePrettyDisplayUserID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ExpirePrettyDisplayUserIDRequest) if err := dec(in); err != nil { @@ -2775,6 +2843,14 @@ var UserIdentityService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ApplyPrettyDisplayUserID", Handler: _UserIdentityService_ApplyPrettyDisplayUserID_Handler, }, + { + MethodName: "ListAvailablePrettyDisplayIDs", + Handler: _UserIdentityService_ListAvailablePrettyDisplayIDs_Handler, + }, + { + MethodName: "ApplyPrettyDisplayIDFromPool", + Handler: _UserIdentityService_ApplyPrettyDisplayIDFromPool_Handler, + }, { MethodName: "ExpirePrettyDisplayUserID", Handler: _UserIdentityService_ExpirePrettyDisplayUserID_Handler, @@ -2783,3 +2859,338 @@ var UserIdentityService_ServiceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, Metadata: "proto/user/v1/user.proto", } + +const ( + UserPrettyDisplayIDAdminService_ListPrettyDisplayIDPools_FullMethodName = "/hyapp.user.v1.UserPrettyDisplayIDAdminService/ListPrettyDisplayIDPools" + UserPrettyDisplayIDAdminService_CreatePrettyDisplayIDPool_FullMethodName = "/hyapp.user.v1.UserPrettyDisplayIDAdminService/CreatePrettyDisplayIDPool" + UserPrettyDisplayIDAdminService_UpdatePrettyDisplayIDPool_FullMethodName = "/hyapp.user.v1.UserPrettyDisplayIDAdminService/UpdatePrettyDisplayIDPool" + UserPrettyDisplayIDAdminService_GeneratePrettyDisplayIDs_FullMethodName = "/hyapp.user.v1.UserPrettyDisplayIDAdminService/GeneratePrettyDisplayIDs" + UserPrettyDisplayIDAdminService_ListPrettyDisplayIDs_FullMethodName = "/hyapp.user.v1.UserPrettyDisplayIDAdminService/ListPrettyDisplayIDs" + UserPrettyDisplayIDAdminService_SetPrettyDisplayIDStatus_FullMethodName = "/hyapp.user.v1.UserPrettyDisplayIDAdminService/SetPrettyDisplayIDStatus" + UserPrettyDisplayIDAdminService_AdminGrantPrettyDisplayID_FullMethodName = "/hyapp.user.v1.UserPrettyDisplayIDAdminService/AdminGrantPrettyDisplayID" +) + +// UserPrettyDisplayIDAdminServiceClient is the client API for UserPrettyDisplayIDAdminService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// UserPrettyDisplayIDAdminService 承载后台靓号池和后台发放能力。 +type UserPrettyDisplayIDAdminServiceClient interface { + ListPrettyDisplayIDPools(ctx context.Context, in *ListPrettyDisplayIDPoolsRequest, opts ...grpc.CallOption) (*ListPrettyDisplayIDPoolsResponse, error) + CreatePrettyDisplayIDPool(ctx context.Context, in *CreatePrettyDisplayIDPoolRequest, opts ...grpc.CallOption) (*PrettyDisplayIDPoolResponse, error) + UpdatePrettyDisplayIDPool(ctx context.Context, in *UpdatePrettyDisplayIDPoolRequest, opts ...grpc.CallOption) (*PrettyDisplayIDPoolResponse, error) + GeneratePrettyDisplayIDs(ctx context.Context, in *GeneratePrettyDisplayIDsRequest, opts ...grpc.CallOption) (*GeneratePrettyDisplayIDsResponse, error) + ListPrettyDisplayIDs(ctx context.Context, in *ListPrettyDisplayIDsRequest, opts ...grpc.CallOption) (*ListPrettyDisplayIDsResponse, error) + SetPrettyDisplayIDStatus(ctx context.Context, in *SetPrettyDisplayIDStatusRequest, opts ...grpc.CallOption) (*PrettyDisplayIDResponse, error) + AdminGrantPrettyDisplayID(ctx context.Context, in *AdminGrantPrettyDisplayIDRequest, opts ...grpc.CallOption) (*AdminGrantPrettyDisplayIDResponse, error) +} + +type userPrettyDisplayIDAdminServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewUserPrettyDisplayIDAdminServiceClient(cc grpc.ClientConnInterface) UserPrettyDisplayIDAdminServiceClient { + return &userPrettyDisplayIDAdminServiceClient{cc} +} + +func (c *userPrettyDisplayIDAdminServiceClient) ListPrettyDisplayIDPools(ctx context.Context, in *ListPrettyDisplayIDPoolsRequest, opts ...grpc.CallOption) (*ListPrettyDisplayIDPoolsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListPrettyDisplayIDPoolsResponse) + err := c.cc.Invoke(ctx, UserPrettyDisplayIDAdminService_ListPrettyDisplayIDPools_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userPrettyDisplayIDAdminServiceClient) CreatePrettyDisplayIDPool(ctx context.Context, in *CreatePrettyDisplayIDPoolRequest, opts ...grpc.CallOption) (*PrettyDisplayIDPoolResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PrettyDisplayIDPoolResponse) + err := c.cc.Invoke(ctx, UserPrettyDisplayIDAdminService_CreatePrettyDisplayIDPool_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userPrettyDisplayIDAdminServiceClient) UpdatePrettyDisplayIDPool(ctx context.Context, in *UpdatePrettyDisplayIDPoolRequest, opts ...grpc.CallOption) (*PrettyDisplayIDPoolResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PrettyDisplayIDPoolResponse) + err := c.cc.Invoke(ctx, UserPrettyDisplayIDAdminService_UpdatePrettyDisplayIDPool_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userPrettyDisplayIDAdminServiceClient) GeneratePrettyDisplayIDs(ctx context.Context, in *GeneratePrettyDisplayIDsRequest, opts ...grpc.CallOption) (*GeneratePrettyDisplayIDsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GeneratePrettyDisplayIDsResponse) + err := c.cc.Invoke(ctx, UserPrettyDisplayIDAdminService_GeneratePrettyDisplayIDs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userPrettyDisplayIDAdminServiceClient) ListPrettyDisplayIDs(ctx context.Context, in *ListPrettyDisplayIDsRequest, opts ...grpc.CallOption) (*ListPrettyDisplayIDsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListPrettyDisplayIDsResponse) + err := c.cc.Invoke(ctx, UserPrettyDisplayIDAdminService_ListPrettyDisplayIDs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userPrettyDisplayIDAdminServiceClient) SetPrettyDisplayIDStatus(ctx context.Context, in *SetPrettyDisplayIDStatusRequest, opts ...grpc.CallOption) (*PrettyDisplayIDResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PrettyDisplayIDResponse) + err := c.cc.Invoke(ctx, UserPrettyDisplayIDAdminService_SetPrettyDisplayIDStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userPrettyDisplayIDAdminServiceClient) AdminGrantPrettyDisplayID(ctx context.Context, in *AdminGrantPrettyDisplayIDRequest, opts ...grpc.CallOption) (*AdminGrantPrettyDisplayIDResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdminGrantPrettyDisplayIDResponse) + err := c.cc.Invoke(ctx, UserPrettyDisplayIDAdminService_AdminGrantPrettyDisplayID_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// UserPrettyDisplayIDAdminServiceServer is the server API for UserPrettyDisplayIDAdminService service. +// All implementations must embed UnimplementedUserPrettyDisplayIDAdminServiceServer +// for forward compatibility. +// +// UserPrettyDisplayIDAdminService 承载后台靓号池和后台发放能力。 +type UserPrettyDisplayIDAdminServiceServer interface { + ListPrettyDisplayIDPools(context.Context, *ListPrettyDisplayIDPoolsRequest) (*ListPrettyDisplayIDPoolsResponse, error) + CreatePrettyDisplayIDPool(context.Context, *CreatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) + UpdatePrettyDisplayIDPool(context.Context, *UpdatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) + GeneratePrettyDisplayIDs(context.Context, *GeneratePrettyDisplayIDsRequest) (*GeneratePrettyDisplayIDsResponse, error) + ListPrettyDisplayIDs(context.Context, *ListPrettyDisplayIDsRequest) (*ListPrettyDisplayIDsResponse, error) + SetPrettyDisplayIDStatus(context.Context, *SetPrettyDisplayIDStatusRequest) (*PrettyDisplayIDResponse, error) + AdminGrantPrettyDisplayID(context.Context, *AdminGrantPrettyDisplayIDRequest) (*AdminGrantPrettyDisplayIDResponse, error) + mustEmbedUnimplementedUserPrettyDisplayIDAdminServiceServer() +} + +// UnimplementedUserPrettyDisplayIDAdminServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedUserPrettyDisplayIDAdminServiceServer struct{} + +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) ListPrettyDisplayIDPools(context.Context, *ListPrettyDisplayIDPoolsRequest) (*ListPrettyDisplayIDPoolsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListPrettyDisplayIDPools not implemented") +} +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) CreatePrettyDisplayIDPool(context.Context, *CreatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreatePrettyDisplayIDPool not implemented") +} +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) UpdatePrettyDisplayIDPool(context.Context, *UpdatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdatePrettyDisplayIDPool not implemented") +} +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) GeneratePrettyDisplayIDs(context.Context, *GeneratePrettyDisplayIDsRequest) (*GeneratePrettyDisplayIDsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GeneratePrettyDisplayIDs not implemented") +} +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) ListPrettyDisplayIDs(context.Context, *ListPrettyDisplayIDsRequest) (*ListPrettyDisplayIDsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListPrettyDisplayIDs not implemented") +} +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) SetPrettyDisplayIDStatus(context.Context, *SetPrettyDisplayIDStatusRequest) (*PrettyDisplayIDResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetPrettyDisplayIDStatus not implemented") +} +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) AdminGrantPrettyDisplayID(context.Context, *AdminGrantPrettyDisplayIDRequest) (*AdminGrantPrettyDisplayIDResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AdminGrantPrettyDisplayID not implemented") +} +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) mustEmbedUnimplementedUserPrettyDisplayIDAdminServiceServer() { +} +func (UnimplementedUserPrettyDisplayIDAdminServiceServer) testEmbeddedByValue() {} + +// UnsafeUserPrettyDisplayIDAdminServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to UserPrettyDisplayIDAdminServiceServer will +// result in compilation errors. +type UnsafeUserPrettyDisplayIDAdminServiceServer interface { + mustEmbedUnimplementedUserPrettyDisplayIDAdminServiceServer() +} + +func RegisterUserPrettyDisplayIDAdminServiceServer(s grpc.ServiceRegistrar, srv UserPrettyDisplayIDAdminServiceServer) { + // If the following call panics, 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. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&UserPrettyDisplayIDAdminService_ServiceDesc, srv) +} + +func _UserPrettyDisplayIDAdminService_ListPrettyDisplayIDPools_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPrettyDisplayIDPoolsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserPrettyDisplayIDAdminServiceServer).ListPrettyDisplayIDPools(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserPrettyDisplayIDAdminService_ListPrettyDisplayIDPools_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserPrettyDisplayIDAdminServiceServer).ListPrettyDisplayIDPools(ctx, req.(*ListPrettyDisplayIDPoolsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserPrettyDisplayIDAdminService_CreatePrettyDisplayIDPool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreatePrettyDisplayIDPoolRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserPrettyDisplayIDAdminServiceServer).CreatePrettyDisplayIDPool(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserPrettyDisplayIDAdminService_CreatePrettyDisplayIDPool_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserPrettyDisplayIDAdminServiceServer).CreatePrettyDisplayIDPool(ctx, req.(*CreatePrettyDisplayIDPoolRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserPrettyDisplayIDAdminService_UpdatePrettyDisplayIDPool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdatePrettyDisplayIDPoolRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserPrettyDisplayIDAdminServiceServer).UpdatePrettyDisplayIDPool(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserPrettyDisplayIDAdminService_UpdatePrettyDisplayIDPool_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserPrettyDisplayIDAdminServiceServer).UpdatePrettyDisplayIDPool(ctx, req.(*UpdatePrettyDisplayIDPoolRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserPrettyDisplayIDAdminService_GeneratePrettyDisplayIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GeneratePrettyDisplayIDsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserPrettyDisplayIDAdminServiceServer).GeneratePrettyDisplayIDs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserPrettyDisplayIDAdminService_GeneratePrettyDisplayIDs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserPrettyDisplayIDAdminServiceServer).GeneratePrettyDisplayIDs(ctx, req.(*GeneratePrettyDisplayIDsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserPrettyDisplayIDAdminService_ListPrettyDisplayIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPrettyDisplayIDsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserPrettyDisplayIDAdminServiceServer).ListPrettyDisplayIDs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserPrettyDisplayIDAdminService_ListPrettyDisplayIDs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserPrettyDisplayIDAdminServiceServer).ListPrettyDisplayIDs(ctx, req.(*ListPrettyDisplayIDsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserPrettyDisplayIDAdminService_SetPrettyDisplayIDStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetPrettyDisplayIDStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserPrettyDisplayIDAdminServiceServer).SetPrettyDisplayIDStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserPrettyDisplayIDAdminService_SetPrettyDisplayIDStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserPrettyDisplayIDAdminServiceServer).SetPrettyDisplayIDStatus(ctx, req.(*SetPrettyDisplayIDStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UserPrettyDisplayIDAdminService_AdminGrantPrettyDisplayID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdminGrantPrettyDisplayIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserPrettyDisplayIDAdminServiceServer).AdminGrantPrettyDisplayID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserPrettyDisplayIDAdminService_AdminGrantPrettyDisplayID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserPrettyDisplayIDAdminServiceServer).AdminGrantPrettyDisplayID(ctx, req.(*AdminGrantPrettyDisplayIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// UserPrettyDisplayIDAdminService_ServiceDesc is the grpc.ServiceDesc for UserPrettyDisplayIDAdminService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var UserPrettyDisplayIDAdminService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hyapp.user.v1.UserPrettyDisplayIDAdminService", + HandlerType: (*UserPrettyDisplayIDAdminServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListPrettyDisplayIDPools", + Handler: _UserPrettyDisplayIDAdminService_ListPrettyDisplayIDPools_Handler, + }, + { + MethodName: "CreatePrettyDisplayIDPool", + Handler: _UserPrettyDisplayIDAdminService_CreatePrettyDisplayIDPool_Handler, + }, + { + MethodName: "UpdatePrettyDisplayIDPool", + Handler: _UserPrettyDisplayIDAdminService_UpdatePrettyDisplayIDPool_Handler, + }, + { + MethodName: "GeneratePrettyDisplayIDs", + Handler: _UserPrettyDisplayIDAdminService_GeneratePrettyDisplayIDs_Handler, + }, + { + MethodName: "ListPrettyDisplayIDs", + Handler: _UserPrettyDisplayIDAdminService_ListPrettyDisplayIDs_Handler, + }, + { + MethodName: "SetPrettyDisplayIDStatus", + Handler: _UserPrettyDisplayIDAdminService_SetPrettyDisplayIDStatus_Handler, + }, + { + MethodName: "AdminGrantPrettyDisplayID", + Handler: _UserPrettyDisplayIDAdminService_AdminGrantPrettyDisplayID_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/user/v1/user.proto", +} diff --git a/api/proto/wallet/v1/wallet.pb.go b/api/proto/wallet/v1/wallet.pb.go index 3494dcad..cf8ffcbe 100644 --- a/api/proto/wallet/v1/wallet.pb.go +++ b/api/proto/wallet/v1/wallet.pb.go @@ -188,10 +188,11 @@ type DebitGiftResponse struct { // host_period_cycle_key 是 UTC 月周期,例如 2026-05;非主播为空。 HostPeriodCycleKey string `protobuf:"bytes,12,opt,name=host_period_cycle_key,json=hostPeriodCycleKey,proto3" json:"host_period_cycle_key,omitempty"` // cp_relation_type 只在 CP 礼物、兄弟礼物、姐妹礼物上有值,用于 room outbox 驱动关系申请。 - CpRelationType string `protobuf:"bytes,13,opt,name=cp_relation_type,json=cpRelationType,proto3" json:"cp_relation_type,omitempty"` - GiftName string `protobuf:"bytes,14,opt,name=gift_name,json=giftName,proto3" json:"gift_name,omitempty"` - GiftIconUrl string `protobuf:"bytes,15,opt,name=gift_icon_url,json=giftIconUrl,proto3" json:"gift_icon_url,omitempty"` - GiftAnimationUrl string `protobuf:"bytes,16,opt,name=gift_animation_url,json=giftAnimationUrl,proto3" json:"gift_animation_url,omitempty"` + CpRelationType string `protobuf:"bytes,13,opt,name=cp_relation_type,json=cpRelationType,proto3" json:"cp_relation_type,omitempty"` + GiftName string `protobuf:"bytes,14,opt,name=gift_name,json=giftName,proto3" json:"gift_name,omitempty"` + GiftIconUrl string `protobuf:"bytes,15,opt,name=gift_icon_url,json=giftIconUrl,proto3" json:"gift_icon_url,omitempty"` + GiftAnimationUrl string `protobuf:"bytes,16,opt,name=gift_animation_url,json=giftAnimationUrl,proto3" json:"gift_animation_url,omitempty"` + GiftEffectTypes []string `protobuf:"bytes,17,rep,name=gift_effect_types,json=giftEffectTypes,proto3" json:"gift_effect_types,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -338,6 +339,13 @@ func (x *DebitGiftResponse) GetGiftAnimationUrl() string { return "" } +func (x *DebitGiftResponse) GetGiftEffectTypes() []string { + if x != nil { + return x.GiftEffectTypes + } + return nil +} + // DebitGiftTarget 是一笔批量送礼中的单个接收方账务快照。 type DebitGiftTarget struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -9038,8 +9046,10 @@ type RechargeProduct struct { UpdatedAtMs int64 `protobuf:"varint,19,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` AppCode string `protobuf:"bytes,20,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` Status string `protobuf:"bytes,21,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // audience_type 区分 H5 普通用户档位和币商档位;旧 App 内购默认 normal。 + AudienceType string `protobuf:"bytes,22,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RechargeProduct) Reset() { @@ -9219,6 +9229,13 @@ func (x *RechargeProduct) GetStatus() string { return "" } +func (x *RechargeProduct) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + type ListRechargeProductsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` @@ -9226,7 +9243,9 @@ type ListRechargeProductsRequest struct { UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` RegionId int64 `protobuf:"varint,4,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` // platform 来自 gateway 解析的 App 平台请求头;为空时返回该区域全部已上架商品。 - Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` + Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` + // audience_type 只用于 H5 站外充值;App 旧入口为空时按 normal 兼容。 + AudienceType string `protobuf:"bytes,6,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -9296,6 +9315,13 @@ func (x *ListRechargeProductsRequest) GetPlatform() string { return "" } +func (x *ListRechargeProductsRequest) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + type ListRechargeProductsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Products []*RechargeProduct `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` @@ -9591,6 +9617,7 @@ type ListAdminRechargeProductsRequest struct { Keyword string `protobuf:"bytes,6,opt,name=keyword,proto3" json:"keyword,omitempty"` Page int32 `protobuf:"varint,7,opt,name=page,proto3" json:"page,omitempty"` PageSize int32 `protobuf:"varint,8,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + AudienceType string `protobuf:"bytes,9,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -9681,6 +9708,13 @@ func (x *ListAdminRechargeProductsRequest) GetPageSize() int32 { return 0 } +func (x *ListAdminRechargeProductsRequest) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + type ListAdminRechargeProductsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Products []*RechargeProduct `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` @@ -9746,6 +9780,7 @@ type CreateRechargeProductRequest struct { RegionIds []int64 `protobuf:"varint,8,rep,packed,name=region_ids,json=regionIds,proto3" json:"region_ids,omitempty"` Enabled bool `protobuf:"varint,9,opt,name=enabled,proto3" json:"enabled,omitempty"` OperatorUserId int64 `protobuf:"varint,10,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + AudienceType string `protobuf:"bytes,11,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -9850,6 +9885,13 @@ func (x *CreateRechargeProductRequest) GetOperatorUserId() int64 { return 0 } +func (x *CreateRechargeProductRequest) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + type UpdateRechargeProductRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` @@ -9863,6 +9905,7 @@ type UpdateRechargeProductRequest struct { RegionIds []int64 `protobuf:"varint,9,rep,packed,name=region_ids,json=regionIds,proto3" json:"region_ids,omitempty"` Enabled bool `protobuf:"varint,10,opt,name=enabled,proto3" json:"enabled,omitempty"` OperatorUserId int64 `protobuf:"varint,11,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + AudienceType string `protobuf:"bytes,12,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -9974,6 +10017,13 @@ func (x *UpdateRechargeProductRequest) GetOperatorUserId() int64 { return 0 } +func (x *UpdateRechargeProductRequest) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + type DeleteRechargeProductRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` @@ -10130,6 +10180,1447 @@ func (x *DeleteRechargeProductResponse) GetDeleted() bool { return false } +// ThirdPartyPaymentMethod 是后台和 H5 共用的三方支付方式配置快照。 +type ThirdPartyPaymentMethod struct { + state protoimpl.MessageState `protogen:"open.v1"` + MethodId int64 `protobuf:"varint,1,opt,name=method_id,json=methodId,proto3" json:"method_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + ProviderCode string `protobuf:"bytes,3,opt,name=provider_code,json=providerCode,proto3" json:"provider_code,omitempty"` + ProviderName string `protobuf:"bytes,4,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` + CountryCode string `protobuf:"bytes,5,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` + CountryName string `protobuf:"bytes,6,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` + CurrencyCode string `protobuf:"bytes,7,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` + PayWay string `protobuf:"bytes,8,opt,name=pay_way,json=payWay,proto3" json:"pay_way,omitempty"` + PayType string `protobuf:"bytes,9,opt,name=pay_type,json=payType,proto3" json:"pay_type,omitempty"` + MethodName string `protobuf:"bytes,10,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"` + LogoUrl string `protobuf:"bytes,11,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` + Status string `protobuf:"bytes,12,opt,name=status,proto3" json:"status,omitempty"` + UsdToCurrencyRate string `protobuf:"bytes,13,opt,name=usd_to_currency_rate,json=usdToCurrencyRate,proto3" json:"usd_to_currency_rate,omitempty"` + SortOrder int32 `protobuf:"varint,14,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + CreatedAtMs int64 `protobuf:"varint,15,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,16,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ThirdPartyPaymentMethod) Reset() { + *x = ThirdPartyPaymentMethod{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ThirdPartyPaymentMethod) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ThirdPartyPaymentMethod) ProtoMessage() {} + +func (x *ThirdPartyPaymentMethod) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ThirdPartyPaymentMethod.ProtoReflect.Descriptor instead. +func (*ThirdPartyPaymentMethod) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{112} +} + +func (x *ThirdPartyPaymentMethod) GetMethodId() int64 { + if x != nil { + return x.MethodId + } + return 0 +} + +func (x *ThirdPartyPaymentMethod) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetProviderCode() string { + if x != nil { + return x.ProviderCode + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetProviderName() string { + if x != nil { + return x.ProviderName + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetCountryCode() string { + if x != nil { + return x.CountryCode + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetCountryName() string { + if x != nil { + return x.CountryName + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetCurrencyCode() string { + if x != nil { + return x.CurrencyCode + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetPayWay() string { + if x != nil { + return x.PayWay + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetPayType() string { + if x != nil { + return x.PayType + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetMethodName() string { + if x != nil { + return x.MethodName + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetLogoUrl() string { + if x != nil { + return x.LogoUrl + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetUsdToCurrencyRate() string { + if x != nil { + return x.UsdToCurrencyRate + } + return "" +} + +func (x *ThirdPartyPaymentMethod) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *ThirdPartyPaymentMethod) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *ThirdPartyPaymentMethod) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type ThirdPartyPaymentChannel struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + ProviderCode string `protobuf:"bytes,2,opt,name=provider_code,json=providerCode,proto3" json:"provider_code,omitempty"` + ProviderName string `protobuf:"bytes,3,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + Methods []*ThirdPartyPaymentMethod `protobuf:"bytes,6,rep,name=methods,proto3" json:"methods,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ThirdPartyPaymentChannel) Reset() { + *x = ThirdPartyPaymentChannel{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ThirdPartyPaymentChannel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ThirdPartyPaymentChannel) ProtoMessage() {} + +func (x *ThirdPartyPaymentChannel) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ThirdPartyPaymentChannel.ProtoReflect.Descriptor instead. +func (*ThirdPartyPaymentChannel) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{113} +} + +func (x *ThirdPartyPaymentChannel) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ThirdPartyPaymentChannel) GetProviderCode() string { + if x != nil { + return x.ProviderCode + } + return "" +} + +func (x *ThirdPartyPaymentChannel) GetProviderName() string { + if x != nil { + return x.ProviderName + } + return "" +} + +func (x *ThirdPartyPaymentChannel) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ThirdPartyPaymentChannel) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *ThirdPartyPaymentChannel) GetMethods() []*ThirdPartyPaymentMethod { + if x != nil { + return x.Methods + } + return nil +} + +type ListThirdPartyPaymentChannelsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + ProviderCode string `protobuf:"bytes,3,opt,name=provider_code,json=providerCode,proto3" json:"provider_code,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + IncludeDisabledMethods bool `protobuf:"varint,5,opt,name=include_disabled_methods,json=includeDisabledMethods,proto3" json:"include_disabled_methods,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListThirdPartyPaymentChannelsRequest) Reset() { + *x = ListThirdPartyPaymentChannelsRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListThirdPartyPaymentChannelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListThirdPartyPaymentChannelsRequest) ProtoMessage() {} + +func (x *ListThirdPartyPaymentChannelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListThirdPartyPaymentChannelsRequest.ProtoReflect.Descriptor instead. +func (*ListThirdPartyPaymentChannelsRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{114} +} + +func (x *ListThirdPartyPaymentChannelsRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ListThirdPartyPaymentChannelsRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ListThirdPartyPaymentChannelsRequest) GetProviderCode() string { + if x != nil { + return x.ProviderCode + } + return "" +} + +func (x *ListThirdPartyPaymentChannelsRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ListThirdPartyPaymentChannelsRequest) GetIncludeDisabledMethods() bool { + if x != nil { + return x.IncludeDisabledMethods + } + return false +} + +type ListThirdPartyPaymentChannelsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Channels []*ThirdPartyPaymentChannel `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListThirdPartyPaymentChannelsResponse) Reset() { + *x = ListThirdPartyPaymentChannelsResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListThirdPartyPaymentChannelsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListThirdPartyPaymentChannelsResponse) ProtoMessage() {} + +func (x *ListThirdPartyPaymentChannelsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListThirdPartyPaymentChannelsResponse.ProtoReflect.Descriptor instead. +func (*ListThirdPartyPaymentChannelsResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{115} +} + +func (x *ListThirdPartyPaymentChannelsResponse) GetChannels() []*ThirdPartyPaymentChannel { + if x != nil { + return x.Channels + } + return nil +} + +type SetThirdPartyPaymentMethodStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + MethodId int64 `protobuf:"varint,3,opt,name=method_id,json=methodId,proto3" json:"method_id,omitempty"` + Enabled bool `protobuf:"varint,4,opt,name=enabled,proto3" json:"enabled,omitempty"` + OperatorUserId int64 `protobuf:"varint,5,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetThirdPartyPaymentMethodStatusRequest) Reset() { + *x = SetThirdPartyPaymentMethodStatusRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetThirdPartyPaymentMethodStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetThirdPartyPaymentMethodStatusRequest) ProtoMessage() {} + +func (x *SetThirdPartyPaymentMethodStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetThirdPartyPaymentMethodStatusRequest.ProtoReflect.Descriptor instead. +func (*SetThirdPartyPaymentMethodStatusRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{116} +} + +func (x *SetThirdPartyPaymentMethodStatusRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *SetThirdPartyPaymentMethodStatusRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *SetThirdPartyPaymentMethodStatusRequest) GetMethodId() int64 { + if x != nil { + return x.MethodId + } + return 0 +} + +func (x *SetThirdPartyPaymentMethodStatusRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *SetThirdPartyPaymentMethodStatusRequest) GetOperatorUserId() int64 { + if x != nil { + return x.OperatorUserId + } + return 0 +} + +type ThirdPartyPaymentMethodResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Method *ThirdPartyPaymentMethod `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ThirdPartyPaymentMethodResponse) Reset() { + *x = ThirdPartyPaymentMethodResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ThirdPartyPaymentMethodResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ThirdPartyPaymentMethodResponse) ProtoMessage() {} + +func (x *ThirdPartyPaymentMethodResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ThirdPartyPaymentMethodResponse.ProtoReflect.Descriptor instead. +func (*ThirdPartyPaymentMethodResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{117} +} + +func (x *ThirdPartyPaymentMethodResponse) GetMethod() *ThirdPartyPaymentMethod { + if x != nil { + return x.Method + } + return nil +} + +type UpdateThirdPartyPaymentRateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + MethodId int64 `protobuf:"varint,3,opt,name=method_id,json=methodId,proto3" json:"method_id,omitempty"` + UsdToCurrencyRate string `protobuf:"bytes,4,opt,name=usd_to_currency_rate,json=usdToCurrencyRate,proto3" json:"usd_to_currency_rate,omitempty"` + OperatorUserId int64 `protobuf:"varint,5,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateThirdPartyPaymentRateRequest) Reset() { + *x = UpdateThirdPartyPaymentRateRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateThirdPartyPaymentRateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateThirdPartyPaymentRateRequest) ProtoMessage() {} + +func (x *UpdateThirdPartyPaymentRateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateThirdPartyPaymentRateRequest.ProtoReflect.Descriptor instead. +func (*UpdateThirdPartyPaymentRateRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{118} +} + +func (x *UpdateThirdPartyPaymentRateRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *UpdateThirdPartyPaymentRateRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *UpdateThirdPartyPaymentRateRequest) GetMethodId() int64 { + if x != nil { + return x.MethodId + } + return 0 +} + +func (x *UpdateThirdPartyPaymentRateRequest) GetUsdToCurrencyRate() string { + if x != nil { + return x.UsdToCurrencyRate + } + return "" +} + +func (x *UpdateThirdPartyPaymentRateRequest) GetOperatorUserId() int64 { + if x != nil { + return x.OperatorUserId + } + return 0 +} + +type H5RechargeOptionsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + TargetRegionId int64 `protobuf:"varint,4,opt,name=target_region_id,json=targetRegionId,proto3" json:"target_region_id,omitempty"` + TargetCountryCode string `protobuf:"bytes,5,opt,name=target_country_code,json=targetCountryCode,proto3" json:"target_country_code,omitempty"` + AudienceType string `protobuf:"bytes,6,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *H5RechargeOptionsRequest) Reset() { + *x = H5RechargeOptionsRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *H5RechargeOptionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H5RechargeOptionsRequest) ProtoMessage() {} + +func (x *H5RechargeOptionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H5RechargeOptionsRequest.ProtoReflect.Descriptor instead. +func (*H5RechargeOptionsRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{119} +} + +func (x *H5RechargeOptionsRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *H5RechargeOptionsRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *H5RechargeOptionsRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *H5RechargeOptionsRequest) GetTargetRegionId() int64 { + if x != nil { + return x.TargetRegionId + } + return 0 +} + +func (x *H5RechargeOptionsRequest) GetTargetCountryCode() string { + if x != nil { + return x.TargetCountryCode + } + return "" +} + +func (x *H5RechargeOptionsRequest) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + +type H5RechargeOptionsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Products []*RechargeProduct `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` + PaymentMethods []*ThirdPartyPaymentMethod `protobuf:"bytes,2,rep,name=payment_methods,json=paymentMethods,proto3" json:"payment_methods,omitempty"` + UsdtTrc20Enabled bool `protobuf:"varint,3,opt,name=usdt_trc20_enabled,json=usdtTrc20Enabled,proto3" json:"usdt_trc20_enabled,omitempty"` + UsdtTrc20Address string `protobuf:"bytes,4,opt,name=usdt_trc20_address,json=usdtTrc20Address,proto3" json:"usdt_trc20_address,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *H5RechargeOptionsResponse) Reset() { + *x = H5RechargeOptionsResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *H5RechargeOptionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H5RechargeOptionsResponse) ProtoMessage() {} + +func (x *H5RechargeOptionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H5RechargeOptionsResponse.ProtoReflect.Descriptor instead. +func (*H5RechargeOptionsResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{120} +} + +func (x *H5RechargeOptionsResponse) GetProducts() []*RechargeProduct { + if x != nil { + return x.Products + } + return nil +} + +func (x *H5RechargeOptionsResponse) GetPaymentMethods() []*ThirdPartyPaymentMethod { + if x != nil { + return x.PaymentMethods + } + return nil +} + +func (x *H5RechargeOptionsResponse) GetUsdtTrc20Enabled() bool { + if x != nil { + return x.UsdtTrc20Enabled + } + return false +} + +func (x *H5RechargeOptionsResponse) GetUsdtTrc20Address() string { + if x != nil { + return x.UsdtTrc20Address + } + return "" +} + +type ExternalRechargeOrder struct { + state protoimpl.MessageState `protogen:"open.v1"` + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + TargetRegionId int64 `protobuf:"varint,4,opt,name=target_region_id,json=targetRegionId,proto3" json:"target_region_id,omitempty"` + TargetCountryCode string `protobuf:"bytes,5,opt,name=target_country_code,json=targetCountryCode,proto3" json:"target_country_code,omitempty"` + AudienceType string `protobuf:"bytes,6,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` + ProductId int64 `protobuf:"varint,7,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + ProductName string `protobuf:"bytes,8,opt,name=product_name,json=productName,proto3" json:"product_name,omitempty"` + CoinAmount int64 `protobuf:"varint,9,opt,name=coin_amount,json=coinAmount,proto3" json:"coin_amount,omitempty"` + UsdMinorAmount int64 `protobuf:"varint,10,opt,name=usd_minor_amount,json=usdMinorAmount,proto3" json:"usd_minor_amount,omitempty"` + ProviderCode string `protobuf:"bytes,11,opt,name=provider_code,json=providerCode,proto3" json:"provider_code,omitempty"` + PaymentMethodId int64 `protobuf:"varint,12,opt,name=payment_method_id,json=paymentMethodId,proto3" json:"payment_method_id,omitempty"` + CountryCode string `protobuf:"bytes,13,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` + CurrencyCode string `protobuf:"bytes,14,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"` + ProviderAmountMinor int64 `protobuf:"varint,15,opt,name=provider_amount_minor,json=providerAmountMinor,proto3" json:"provider_amount_minor,omitempty"` + PayWay string `protobuf:"bytes,16,opt,name=pay_way,json=payWay,proto3" json:"pay_way,omitempty"` + PayType string `protobuf:"bytes,17,opt,name=pay_type,json=payType,proto3" json:"pay_type,omitempty"` + PayUrl string `protobuf:"bytes,18,opt,name=pay_url,json=payUrl,proto3" json:"pay_url,omitempty"` + ProviderOrderId string `protobuf:"bytes,19,opt,name=provider_order_id,json=providerOrderId,proto3" json:"provider_order_id,omitempty"` + TxHash string `protobuf:"bytes,20,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` + ReceiveAddress string `protobuf:"bytes,21,opt,name=receive_address,json=receiveAddress,proto3" json:"receive_address,omitempty"` + Status string `protobuf:"bytes,22,opt,name=status,proto3" json:"status,omitempty"` + FailureReason string `protobuf:"bytes,23,opt,name=failure_reason,json=failureReason,proto3" json:"failure_reason,omitempty"` + TransactionId string `protobuf:"bytes,24,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,25,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,26,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + IdempotentReplay bool `protobuf:"varint,27,opt,name=idempotent_replay,json=idempotentReplay,proto3" json:"idempotent_replay,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExternalRechargeOrder) Reset() { + *x = ExternalRechargeOrder{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExternalRechargeOrder) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExternalRechargeOrder) ProtoMessage() {} + +func (x *ExternalRechargeOrder) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExternalRechargeOrder.ProtoReflect.Descriptor instead. +func (*ExternalRechargeOrder) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{121} +} + +func (x *ExternalRechargeOrder) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *ExternalRechargeOrder) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *ExternalRechargeOrder) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *ExternalRechargeOrder) GetTargetRegionId() int64 { + if x != nil { + return x.TargetRegionId + } + return 0 +} + +func (x *ExternalRechargeOrder) GetTargetCountryCode() string { + if x != nil { + return x.TargetCountryCode + } + return "" +} + +func (x *ExternalRechargeOrder) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + +func (x *ExternalRechargeOrder) GetProductId() int64 { + if x != nil { + return x.ProductId + } + return 0 +} + +func (x *ExternalRechargeOrder) GetProductName() string { + if x != nil { + return x.ProductName + } + return "" +} + +func (x *ExternalRechargeOrder) GetCoinAmount() int64 { + if x != nil { + return x.CoinAmount + } + return 0 +} + +func (x *ExternalRechargeOrder) GetUsdMinorAmount() int64 { + if x != nil { + return x.UsdMinorAmount + } + return 0 +} + +func (x *ExternalRechargeOrder) GetProviderCode() string { + if x != nil { + return x.ProviderCode + } + return "" +} + +func (x *ExternalRechargeOrder) GetPaymentMethodId() int64 { + if x != nil { + return x.PaymentMethodId + } + return 0 +} + +func (x *ExternalRechargeOrder) GetCountryCode() string { + if x != nil { + return x.CountryCode + } + return "" +} + +func (x *ExternalRechargeOrder) GetCurrencyCode() string { + if x != nil { + return x.CurrencyCode + } + return "" +} + +func (x *ExternalRechargeOrder) GetProviderAmountMinor() int64 { + if x != nil { + return x.ProviderAmountMinor + } + return 0 +} + +func (x *ExternalRechargeOrder) GetPayWay() string { + if x != nil { + return x.PayWay + } + return "" +} + +func (x *ExternalRechargeOrder) GetPayType() string { + if x != nil { + return x.PayType + } + return "" +} + +func (x *ExternalRechargeOrder) GetPayUrl() string { + if x != nil { + return x.PayUrl + } + return "" +} + +func (x *ExternalRechargeOrder) GetProviderOrderId() string { + if x != nil { + return x.ProviderOrderId + } + return "" +} + +func (x *ExternalRechargeOrder) GetTxHash() string { + if x != nil { + return x.TxHash + } + return "" +} + +func (x *ExternalRechargeOrder) GetReceiveAddress() string { + if x != nil { + return x.ReceiveAddress + } + return "" +} + +func (x *ExternalRechargeOrder) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ExternalRechargeOrder) GetFailureReason() string { + if x != nil { + return x.FailureReason + } + return "" +} + +func (x *ExternalRechargeOrder) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +func (x *ExternalRechargeOrder) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *ExternalRechargeOrder) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +func (x *ExternalRechargeOrder) GetIdempotentReplay() bool { + if x != nil { + return x.IdempotentReplay + } + return false +} + +type CreateH5RechargeOrderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + CommandId string `protobuf:"bytes,3,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + TargetUserId int64 `protobuf:"varint,4,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + TargetRegionId int64 `protobuf:"varint,5,opt,name=target_region_id,json=targetRegionId,proto3" json:"target_region_id,omitempty"` + TargetCountryCode string `protobuf:"bytes,6,opt,name=target_country_code,json=targetCountryCode,proto3" json:"target_country_code,omitempty"` + AudienceType string `protobuf:"bytes,7,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` + ProductId int64 `protobuf:"varint,8,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + ProviderCode string `protobuf:"bytes,9,opt,name=provider_code,json=providerCode,proto3" json:"provider_code,omitempty"` + PaymentMethodId int64 `protobuf:"varint,10,opt,name=payment_method_id,json=paymentMethodId,proto3" json:"payment_method_id,omitempty"` + ReturnUrl string `protobuf:"bytes,11,opt,name=return_url,json=returnUrl,proto3" json:"return_url,omitempty"` + NotifyUrl string `protobuf:"bytes,12,opt,name=notify_url,json=notifyUrl,proto3" json:"notify_url,omitempty"` + ClientIp string `protobuf:"bytes,13,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` + Language string `protobuf:"bytes,14,opt,name=language,proto3" json:"language,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateH5RechargeOrderRequest) Reset() { + *x = CreateH5RechargeOrderRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateH5RechargeOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateH5RechargeOrderRequest) ProtoMessage() {} + +func (x *CreateH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateH5RechargeOrderRequest.ProtoReflect.Descriptor instead. +func (*CreateH5RechargeOrderRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{122} +} + +func (x *CreateH5RechargeOrderRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *CreateH5RechargeOrderRequest) GetTargetRegionId() int64 { + if x != nil { + return x.TargetRegionId + } + return 0 +} + +func (x *CreateH5RechargeOrderRequest) GetTargetCountryCode() string { + if x != nil { + return x.TargetCountryCode + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetProductId() int64 { + if x != nil { + return x.ProductId + } + return 0 +} + +func (x *CreateH5RechargeOrderRequest) GetProviderCode() string { + if x != nil { + return x.ProviderCode + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetPaymentMethodId() int64 { + if x != nil { + return x.PaymentMethodId + } + return 0 +} + +func (x *CreateH5RechargeOrderRequest) GetReturnUrl() string { + if x != nil { + return x.ReturnUrl + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetNotifyUrl() string { + if x != nil { + return x.NotifyUrl + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetClientIp() string { + if x != nil { + return x.ClientIp + } + return "" +} + +func (x *CreateH5RechargeOrderRequest) GetLanguage() string { + if x != nil { + return x.Language + } + return "" +} + +type SubmitH5RechargeTxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + OrderId string `protobuf:"bytes,3,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + TargetUserId int64 `protobuf:"varint,4,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + TxHash string `protobuf:"bytes,5,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitH5RechargeTxRequest) Reset() { + *x = SubmitH5RechargeTxRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitH5RechargeTxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitH5RechargeTxRequest) ProtoMessage() {} + +func (x *SubmitH5RechargeTxRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitH5RechargeTxRequest.ProtoReflect.Descriptor instead. +func (*SubmitH5RechargeTxRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{123} +} + +func (x *SubmitH5RechargeTxRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *SubmitH5RechargeTxRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *SubmitH5RechargeTxRequest) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *SubmitH5RechargeTxRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *SubmitH5RechargeTxRequest) GetTxHash() string { + if x != nil { + return x.TxHash + } + return "" +} + +type GetH5RechargeOrderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + OrderId string `protobuf:"bytes,3,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + TargetUserId int64 `protobuf:"varint,4,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetH5RechargeOrderRequest) Reset() { + *x = GetH5RechargeOrderRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetH5RechargeOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetH5RechargeOrderRequest) ProtoMessage() {} + +func (x *GetH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetH5RechargeOrderRequest.ProtoReflect.Descriptor instead. +func (*GetH5RechargeOrderRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{124} +} + +func (x *GetH5RechargeOrderRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetH5RechargeOrderRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *GetH5RechargeOrderRequest) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *GetH5RechargeOrderRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +type H5RechargeOrderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Order *ExternalRechargeOrder `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *H5RechargeOrderResponse) Reset() { + *x = H5RechargeOrderResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *H5RechargeOrderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*H5RechargeOrderResponse) ProtoMessage() {} + +func (x *H5RechargeOrderResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use H5RechargeOrderResponse.ProtoReflect.Descriptor instead. +func (*H5RechargeOrderResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{125} +} + +func (x *H5RechargeOrderResponse) GetOrder() *ExternalRechargeOrder { + if x != nil { + return x.Order + } + return nil +} + +type HandleMifapayNotifyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + MerAccount string `protobuf:"bytes,3,opt,name=mer_account,json=merAccount,proto3" json:"mer_account,omitempty"` + Data string `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` + Sign string `protobuf:"bytes,5,opt,name=sign,proto3" json:"sign,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HandleMifapayNotifyRequest) Reset() { + *x = HandleMifapayNotifyRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HandleMifapayNotifyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HandleMifapayNotifyRequest) ProtoMessage() {} + +func (x *HandleMifapayNotifyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HandleMifapayNotifyRequest.ProtoReflect.Descriptor instead. +func (*HandleMifapayNotifyRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{126} +} + +func (x *HandleMifapayNotifyRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *HandleMifapayNotifyRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *HandleMifapayNotifyRequest) GetMerAccount() string { + if x != nil { + return x.MerAccount + } + return "" +} + +func (x *HandleMifapayNotifyRequest) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +func (x *HandleMifapayNotifyRequest) GetSign() string { + if x != nil { + return x.Sign + } + return "" +} + +type HandleMifapayNotifyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + ResponseText string `protobuf:"bytes,2,opt,name=response_text,json=responseText,proto3" json:"response_text,omitempty"` + OrderId string `protobuf:"bytes,3,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HandleMifapayNotifyResponse) Reset() { + *x = HandleMifapayNotifyResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HandleMifapayNotifyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HandleMifapayNotifyResponse) ProtoMessage() {} + +func (x *HandleMifapayNotifyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HandleMifapayNotifyResponse.ProtoReflect.Descriptor instead. +func (*HandleMifapayNotifyResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{127} +} + +func (x *HandleMifapayNotifyResponse) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *HandleMifapayNotifyResponse) GetResponseText() string { + if x != nil { + return x.ResponseText + } + return "" +} + +func (x *HandleMifapayNotifyResponse) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + type DiamondExchangeRule struct { state protoimpl.MessageState `protogen:"open.v1"` ExchangeType string `protobuf:"bytes,1,opt,name=exchange_type,json=exchangeType,proto3" json:"exchange_type,omitempty"` @@ -10144,7 +11635,7 @@ type DiamondExchangeRule struct { func (x *DiamondExchangeRule) Reset() { *x = DiamondExchangeRule{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10156,7 +11647,7 @@ func (x *DiamondExchangeRule) String() string { func (*DiamondExchangeRule) ProtoMessage() {} func (x *DiamondExchangeRule) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10169,7 +11660,7 @@ func (x *DiamondExchangeRule) ProtoReflect() protoreflect.Message { // Deprecated: Use DiamondExchangeRule.ProtoReflect.Descriptor instead. func (*DiamondExchangeRule) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{112} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{128} } func (x *DiamondExchangeRule) GetExchangeType() string { @@ -10225,7 +11716,7 @@ type GetDiamondExchangeConfigRequest struct { func (x *GetDiamondExchangeConfigRequest) Reset() { *x = GetDiamondExchangeConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10237,7 +11728,7 @@ func (x *GetDiamondExchangeConfigRequest) String() string { func (*GetDiamondExchangeConfigRequest) ProtoMessage() {} func (x *GetDiamondExchangeConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10250,7 +11741,7 @@ func (x *GetDiamondExchangeConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiamondExchangeConfigRequest.ProtoReflect.Descriptor instead. func (*GetDiamondExchangeConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{113} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{129} } func (x *GetDiamondExchangeConfigRequest) GetRequestId() string { @@ -10283,7 +11774,7 @@ type GetDiamondExchangeConfigResponse struct { func (x *GetDiamondExchangeConfigResponse) Reset() { *x = GetDiamondExchangeConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10295,7 +11786,7 @@ func (x *GetDiamondExchangeConfigResponse) String() string { func (*GetDiamondExchangeConfigResponse) ProtoMessage() {} func (x *GetDiamondExchangeConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10308,7 +11799,7 @@ func (x *GetDiamondExchangeConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiamondExchangeConfigResponse.ProtoReflect.Descriptor instead. func (*GetDiamondExchangeConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{114} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{130} } func (x *GetDiamondExchangeConfigResponse) GetRules() []*DiamondExchangeRule { @@ -10338,7 +11829,7 @@ type WalletTransaction struct { func (x *WalletTransaction) Reset() { *x = WalletTransaction{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10350,7 +11841,7 @@ func (x *WalletTransaction) String() string { func (*WalletTransaction) ProtoMessage() {} func (x *WalletTransaction) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10363,7 +11854,7 @@ func (x *WalletTransaction) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletTransaction.ProtoReflect.Descriptor instead. func (*WalletTransaction) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{115} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{131} } func (x *WalletTransaction) GetEntryId() int64 { @@ -10457,7 +11948,7 @@ type ListWalletTransactionsRequest struct { func (x *ListWalletTransactionsRequest) Reset() { *x = ListWalletTransactionsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10469,7 +11960,7 @@ func (x *ListWalletTransactionsRequest) String() string { func (*ListWalletTransactionsRequest) ProtoMessage() {} func (x *ListWalletTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10482,7 +11973,7 @@ func (x *ListWalletTransactionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWalletTransactionsRequest.ProtoReflect.Descriptor instead. func (*ListWalletTransactionsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{116} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{132} } func (x *ListWalletTransactionsRequest) GetRequestId() string { @@ -10537,7 +12028,7 @@ type ListWalletTransactionsResponse struct { func (x *ListWalletTransactionsResponse) Reset() { *x = ListWalletTransactionsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10549,7 +12040,7 @@ func (x *ListWalletTransactionsResponse) String() string { func (*ListWalletTransactionsResponse) ProtoMessage() {} func (x *ListWalletTransactionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10562,7 +12053,7 @@ func (x *ListWalletTransactionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWalletTransactionsResponse.ProtoReflect.Descriptor instead. func (*ListWalletTransactionsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{117} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{133} } func (x *ListWalletTransactionsResponse) GetTransactions() []*WalletTransaction { @@ -10596,7 +12087,7 @@ type VipRewardItem struct { func (x *VipRewardItem) Reset() { *x = VipRewardItem{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10608,7 +12099,7 @@ func (x *VipRewardItem) String() string { func (*VipRewardItem) ProtoMessage() {} func (x *VipRewardItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10621,7 +12112,7 @@ func (x *VipRewardItem) ProtoReflect() protoreflect.Message { // Deprecated: Use VipRewardItem.ProtoReflect.Descriptor instead. func (*VipRewardItem) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{118} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{134} } func (x *VipRewardItem) GetResourceId() int64 { @@ -10710,7 +12201,7 @@ type VipLevel struct { func (x *VipLevel) Reset() { *x = VipLevel{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10722,7 +12213,7 @@ func (x *VipLevel) String() string { func (*VipLevel) ProtoMessage() {} func (x *VipLevel) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10735,7 +12226,7 @@ func (x *VipLevel) ProtoReflect() protoreflect.Message { // Deprecated: Use VipLevel.ProtoReflect.Descriptor instead. func (*VipLevel) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{119} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{135} } func (x *VipLevel) GetLevel() int32 { @@ -10858,7 +12349,7 @@ type UserVip struct { func (x *UserVip) Reset() { *x = UserVip{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10870,7 +12361,7 @@ func (x *UserVip) String() string { func (*UserVip) ProtoMessage() {} func (x *UserVip) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10883,7 +12374,7 @@ func (x *UserVip) ProtoReflect() protoreflect.Message { // Deprecated: Use UserVip.ProtoReflect.Descriptor instead. func (*UserVip) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{120} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{136} } func (x *UserVip) GetUserId() int64 { @@ -10946,7 +12437,7 @@ type ListVipPackagesRequest struct { func (x *ListVipPackagesRequest) Reset() { *x = ListVipPackagesRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10958,7 +12449,7 @@ func (x *ListVipPackagesRequest) String() string { func (*ListVipPackagesRequest) ProtoMessage() {} func (x *ListVipPackagesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10971,7 +12462,7 @@ func (x *ListVipPackagesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVipPackagesRequest.ProtoReflect.Descriptor instead. func (*ListVipPackagesRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{121} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{137} } func (x *ListVipPackagesRequest) GetRequestId() string { @@ -11005,7 +12496,7 @@ type ListVipPackagesResponse struct { func (x *ListVipPackagesResponse) Reset() { *x = ListVipPackagesResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11017,7 +12508,7 @@ func (x *ListVipPackagesResponse) String() string { func (*ListVipPackagesResponse) ProtoMessage() {} func (x *ListVipPackagesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11030,7 +12521,7 @@ func (x *ListVipPackagesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVipPackagesResponse.ProtoReflect.Descriptor instead. func (*ListVipPackagesResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{122} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{138} } func (x *ListVipPackagesResponse) GetCurrentVip() *UserVip { @@ -11058,7 +12549,7 @@ type GetMyVipRequest struct { func (x *GetMyVipRequest) Reset() { *x = GetMyVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11070,7 +12561,7 @@ func (x *GetMyVipRequest) String() string { func (*GetMyVipRequest) ProtoMessage() {} func (x *GetMyVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11083,7 +12574,7 @@ func (x *GetMyVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyVipRequest.ProtoReflect.Descriptor instead. func (*GetMyVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{123} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{139} } func (x *GetMyVipRequest) GetRequestId() string { @@ -11116,7 +12607,7 @@ type GetMyVipResponse struct { func (x *GetMyVipResponse) Reset() { *x = GetMyVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11128,7 +12619,7 @@ func (x *GetMyVipResponse) String() string { func (*GetMyVipResponse) ProtoMessage() {} func (x *GetMyVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11141,7 +12632,7 @@ func (x *GetMyVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyVipResponse.ProtoReflect.Descriptor instead. func (*GetMyVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{124} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{140} } func (x *GetMyVipResponse) GetVip() *UserVip { @@ -11163,7 +12654,7 @@ type PurchaseVipRequest struct { func (x *PurchaseVipRequest) Reset() { *x = PurchaseVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11175,7 +12666,7 @@ func (x *PurchaseVipRequest) String() string { func (*PurchaseVipRequest) ProtoMessage() {} func (x *PurchaseVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11188,7 +12679,7 @@ func (x *PurchaseVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PurchaseVipRequest.ProtoReflect.Descriptor instead. func (*PurchaseVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{125} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{141} } func (x *PurchaseVipRequest) GetCommandId() string { @@ -11233,7 +12724,7 @@ type PurchaseVipResponse struct { func (x *PurchaseVipResponse) Reset() { *x = PurchaseVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11245,7 +12736,7 @@ func (x *PurchaseVipResponse) String() string { func (*PurchaseVipResponse) ProtoMessage() {} func (x *PurchaseVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11258,7 +12749,7 @@ func (x *PurchaseVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PurchaseVipResponse.ProtoReflect.Descriptor instead. func (*PurchaseVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{126} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{142} } func (x *PurchaseVipResponse) GetOrderId() string { @@ -11317,7 +12808,7 @@ type DebitCPBreakupFeeRequest struct { func (x *DebitCPBreakupFeeRequest) Reset() { *x = DebitCPBreakupFeeRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11329,7 +12820,7 @@ func (x *DebitCPBreakupFeeRequest) String() string { func (*DebitCPBreakupFeeRequest) ProtoMessage() {} func (x *DebitCPBreakupFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11342,7 +12833,7 @@ func (x *DebitCPBreakupFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DebitCPBreakupFeeRequest.ProtoReflect.Descriptor instead. func (*DebitCPBreakupFeeRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{127} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{143} } func (x *DebitCPBreakupFeeRequest) GetCommandId() string { @@ -11399,7 +12890,7 @@ type DebitCPBreakupFeeResponse struct { func (x *DebitCPBreakupFeeResponse) Reset() { *x = DebitCPBreakupFeeResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11411,7 +12902,7 @@ func (x *DebitCPBreakupFeeResponse) String() string { func (*DebitCPBreakupFeeResponse) ProtoMessage() {} func (x *DebitCPBreakupFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11424,7 +12915,7 @@ func (x *DebitCPBreakupFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DebitCPBreakupFeeResponse.ProtoReflect.Descriptor instead. func (*DebitCPBreakupFeeResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{128} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{144} } func (x *DebitCPBreakupFeeResponse) GetTransactionId() string { @@ -11470,7 +12961,7 @@ type GrantVipRequest struct { func (x *GrantVipRequest) Reset() { *x = GrantVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11482,7 +12973,7 @@ func (x *GrantVipRequest) String() string { func (*GrantVipRequest) ProtoMessage() {} func (x *GrantVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11495,7 +12986,7 @@ func (x *GrantVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantVipRequest.ProtoReflect.Descriptor instead. func (*GrantVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{129} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{145} } func (x *GrantVipRequest) GetCommandId() string { @@ -11559,7 +13050,7 @@ type GrantVipResponse struct { func (x *GrantVipResponse) Reset() { *x = GrantVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11571,7 +13062,7 @@ func (x *GrantVipResponse) String() string { func (*GrantVipResponse) ProtoMessage() {} func (x *GrantVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11584,7 +13075,7 @@ func (x *GrantVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantVipResponse.ProtoReflect.Descriptor instead. func (*GrantVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{130} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{146} } func (x *GrantVipResponse) GetTransactionId() string { @@ -11631,7 +13122,7 @@ type AdminVipLevelInput struct { func (x *AdminVipLevelInput) Reset() { *x = AdminVipLevelInput{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11643,7 +13134,7 @@ func (x *AdminVipLevelInput) String() string { func (*AdminVipLevelInput) ProtoMessage() {} func (x *AdminVipLevelInput) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11656,7 +13147,7 @@ func (x *AdminVipLevelInput) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminVipLevelInput.ProtoReflect.Descriptor instead. func (*AdminVipLevelInput) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{131} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{147} } func (x *AdminVipLevelInput) GetLevel() int32 { @@ -11725,7 +13216,7 @@ type ListAdminVipLevelsRequest struct { func (x *ListAdminVipLevelsRequest) Reset() { *x = ListAdminVipLevelsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11737,7 +13228,7 @@ func (x *ListAdminVipLevelsRequest) String() string { func (*ListAdminVipLevelsRequest) ProtoMessage() {} func (x *ListAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11750,7 +13241,7 @@ func (x *ListAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAdminVipLevelsRequest.ProtoReflect.Descriptor instead. func (*ListAdminVipLevelsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{132} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{148} } func (x *ListAdminVipLevelsRequest) GetRequestId() string { @@ -11777,7 +13268,7 @@ type ListAdminVipLevelsResponse struct { func (x *ListAdminVipLevelsResponse) Reset() { *x = ListAdminVipLevelsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11789,7 +13280,7 @@ func (x *ListAdminVipLevelsResponse) String() string { func (*ListAdminVipLevelsResponse) ProtoMessage() {} func (x *ListAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11802,7 +13293,7 @@ func (x *ListAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAdminVipLevelsResponse.ProtoReflect.Descriptor instead. func (*ListAdminVipLevelsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{133} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{149} } func (x *ListAdminVipLevelsResponse) GetLevels() []*VipLevel { @@ -11831,7 +13322,7 @@ type UpdateAdminVipLevelsRequest struct { func (x *UpdateAdminVipLevelsRequest) Reset() { *x = UpdateAdminVipLevelsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11843,7 +13334,7 @@ func (x *UpdateAdminVipLevelsRequest) String() string { func (*UpdateAdminVipLevelsRequest) ProtoMessage() {} func (x *UpdateAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11856,7 +13347,7 @@ func (x *UpdateAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateAdminVipLevelsRequest.ProtoReflect.Descriptor instead. func (*UpdateAdminVipLevelsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{134} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{150} } func (x *UpdateAdminVipLevelsRequest) GetRequestId() string { @@ -11897,7 +13388,7 @@ type UpdateAdminVipLevelsResponse struct { func (x *UpdateAdminVipLevelsResponse) Reset() { *x = UpdateAdminVipLevelsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11909,7 +13400,7 @@ func (x *UpdateAdminVipLevelsResponse) String() string { func (*UpdateAdminVipLevelsResponse) ProtoMessage() {} func (x *UpdateAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11922,7 +13413,7 @@ func (x *UpdateAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateAdminVipLevelsResponse.ProtoReflect.Descriptor instead. func (*UpdateAdminVipLevelsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{135} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{151} } func (x *UpdateAdminVipLevelsResponse) GetLevels() []*VipLevel { @@ -11956,7 +13447,7 @@ type CreditTaskRewardRequest struct { func (x *CreditTaskRewardRequest) Reset() { *x = CreditTaskRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11968,7 +13459,7 @@ func (x *CreditTaskRewardRequest) String() string { func (*CreditTaskRewardRequest) ProtoMessage() {} func (x *CreditTaskRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11981,7 +13472,7 @@ func (x *CreditTaskRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditTaskRewardRequest.ProtoReflect.Descriptor instead. func (*CreditTaskRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{136} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{152} } func (x *CreditTaskRewardRequest) GetCommandId() string { @@ -12053,7 +13544,7 @@ type CreditTaskRewardResponse struct { func (x *CreditTaskRewardResponse) Reset() { *x = CreditTaskRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12065,7 +13556,7 @@ func (x *CreditTaskRewardResponse) String() string { func (*CreditTaskRewardResponse) ProtoMessage() {} func (x *CreditTaskRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12078,7 +13569,7 @@ func (x *CreditTaskRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditTaskRewardResponse.ProtoReflect.Descriptor instead. func (*CreditTaskRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{137} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{153} } func (x *CreditTaskRewardResponse) GetTransactionId() string { @@ -12128,7 +13619,7 @@ type CreditLuckyGiftRewardRequest struct { func (x *CreditLuckyGiftRewardRequest) Reset() { *x = CreditLuckyGiftRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12140,7 +13631,7 @@ func (x *CreditLuckyGiftRewardRequest) String() string { func (*CreditLuckyGiftRewardRequest) ProtoMessage() {} func (x *CreditLuckyGiftRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12153,7 +13644,7 @@ func (x *CreditLuckyGiftRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditLuckyGiftRewardRequest.ProtoReflect.Descriptor instead. func (*CreditLuckyGiftRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{138} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{154} } func (x *CreditLuckyGiftRewardRequest) GetCommandId() string { @@ -12239,7 +13730,7 @@ type CreditLuckyGiftRewardResponse struct { func (x *CreditLuckyGiftRewardResponse) Reset() { *x = CreditLuckyGiftRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12251,7 +13742,7 @@ func (x *CreditLuckyGiftRewardResponse) String() string { func (*CreditLuckyGiftRewardResponse) ProtoMessage() {} func (x *CreditLuckyGiftRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12264,7 +13755,7 @@ func (x *CreditLuckyGiftRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditLuckyGiftRewardResponse.ProtoReflect.Descriptor instead. func (*CreditLuckyGiftRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{139} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{155} } func (x *CreditLuckyGiftRewardResponse) GetTransactionId() string { @@ -12316,7 +13807,7 @@ type CreditRoomTurnoverRewardRequest struct { func (x *CreditRoomTurnoverRewardRequest) Reset() { *x = CreditRoomTurnoverRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12328,7 +13819,7 @@ func (x *CreditRoomTurnoverRewardRequest) String() string { func (*CreditRoomTurnoverRewardRequest) ProtoMessage() {} func (x *CreditRoomTurnoverRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12341,7 +13832,7 @@ func (x *CreditRoomTurnoverRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditRoomTurnoverRewardRequest.ProtoReflect.Descriptor instead. func (*CreditRoomTurnoverRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{140} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{156} } func (x *CreditRoomTurnoverRewardRequest) GetCommandId() string { @@ -12441,7 +13932,7 @@ type CreditRoomTurnoverRewardResponse struct { func (x *CreditRoomTurnoverRewardResponse) Reset() { *x = CreditRoomTurnoverRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12453,7 +13944,7 @@ func (x *CreditRoomTurnoverRewardResponse) String() string { func (*CreditRoomTurnoverRewardResponse) ProtoMessage() {} func (x *CreditRoomTurnoverRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12466,7 +13957,7 @@ func (x *CreditRoomTurnoverRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditRoomTurnoverRewardResponse.ProtoReflect.Descriptor instead. func (*CreditRoomTurnoverRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{141} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{157} } func (x *CreditRoomTurnoverRewardResponse) GetTransactionId() string { @@ -12518,7 +14009,7 @@ type ApplyGameCoinChangeRequest struct { func (x *ApplyGameCoinChangeRequest) Reset() { *x = ApplyGameCoinChangeRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12530,7 +14021,7 @@ func (x *ApplyGameCoinChangeRequest) String() string { func (*ApplyGameCoinChangeRequest) ProtoMessage() {} func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12543,7 +14034,7 @@ func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyGameCoinChangeRequest.ProtoReflect.Descriptor instead. func (*ApplyGameCoinChangeRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{142} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{158} } func (x *ApplyGameCoinChangeRequest) GetRequestId() string { @@ -12642,7 +14133,7 @@ type ApplyGameCoinChangeResponse struct { func (x *ApplyGameCoinChangeResponse) Reset() { *x = ApplyGameCoinChangeResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12654,7 +14145,7 @@ func (x *ApplyGameCoinChangeResponse) String() string { func (*ApplyGameCoinChangeResponse) ProtoMessage() {} func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12667,7 +14158,7 @@ func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyGameCoinChangeResponse.ProtoReflect.Descriptor instead. func (*ApplyGameCoinChangeResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{143} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{159} } func (x *ApplyGameCoinChangeResponse) GetWalletTransactionId() string { @@ -12710,7 +14201,7 @@ type RedPacketConfig struct { func (x *RedPacketConfig) Reset() { *x = RedPacketConfig{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12722,7 +14213,7 @@ func (x *RedPacketConfig) String() string { func (*RedPacketConfig) ProtoMessage() {} func (x *RedPacketConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12735,7 +14226,7 @@ func (x *RedPacketConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacketConfig.ProtoReflect.Descriptor instead. func (*RedPacketConfig) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{144} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{160} } func (x *RedPacketConfig) GetAppCode() string { @@ -12834,7 +14325,7 @@ type RedPacketClaim struct { func (x *RedPacketClaim) Reset() { *x = RedPacketClaim{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12846,7 +14337,7 @@ func (x *RedPacketClaim) String() string { func (*RedPacketClaim) ProtoMessage() {} func (x *RedPacketClaim) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12859,7 +14350,7 @@ func (x *RedPacketClaim) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacketClaim.ProtoReflect.Descriptor instead. func (*RedPacketClaim) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{145} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{161} } func (x *RedPacketClaim) GetAppCode() string { @@ -12969,7 +14460,7 @@ type RedPacket struct { func (x *RedPacket) Reset() { *x = RedPacket{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12981,7 +14472,7 @@ func (x *RedPacket) String() string { func (*RedPacket) ProtoMessage() {} func (x *RedPacket) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12994,7 +14485,7 @@ func (x *RedPacket) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacket.ProtoReflect.Descriptor instead. func (*RedPacket) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{146} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{162} } func (x *RedPacket) GetAppCode() string { @@ -13161,7 +14652,7 @@ type GetRedPacketConfigRequest struct { func (x *GetRedPacketConfigRequest) Reset() { *x = GetRedPacketConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13173,7 +14664,7 @@ func (x *GetRedPacketConfigRequest) String() string { func (*GetRedPacketConfigRequest) ProtoMessage() {} func (x *GetRedPacketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13186,7 +14677,7 @@ func (x *GetRedPacketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketConfigRequest.ProtoReflect.Descriptor instead. func (*GetRedPacketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{147} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{163} } func (x *GetRedPacketConfigRequest) GetRequestId() string { @@ -13213,7 +14704,7 @@ type GetRedPacketConfigResponse struct { func (x *GetRedPacketConfigResponse) Reset() { *x = GetRedPacketConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13225,7 +14716,7 @@ func (x *GetRedPacketConfigResponse) String() string { func (*GetRedPacketConfigResponse) ProtoMessage() {} func (x *GetRedPacketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13238,7 +14729,7 @@ func (x *GetRedPacketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketConfigResponse.ProtoReflect.Descriptor instead. func (*GetRedPacketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{148} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{164} } func (x *GetRedPacketConfigResponse) GetConfig() *RedPacketConfig { @@ -13273,7 +14764,7 @@ type UpdateRedPacketConfigRequest struct { func (x *UpdateRedPacketConfigRequest) Reset() { *x = UpdateRedPacketConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13285,7 +14776,7 @@ func (x *UpdateRedPacketConfigRequest) String() string { func (*UpdateRedPacketConfigRequest) ProtoMessage() {} func (x *UpdateRedPacketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13298,7 +14789,7 @@ func (x *UpdateRedPacketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRedPacketConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateRedPacketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{149} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{165} } func (x *UpdateRedPacketConfigRequest) GetRequestId() string { @@ -13381,7 +14872,7 @@ type UpdateRedPacketConfigResponse struct { func (x *UpdateRedPacketConfigResponse) Reset() { *x = UpdateRedPacketConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13393,7 +14884,7 @@ func (x *UpdateRedPacketConfigResponse) String() string { func (*UpdateRedPacketConfigResponse) ProtoMessage() {} func (x *UpdateRedPacketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13406,7 +14897,7 @@ func (x *UpdateRedPacketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRedPacketConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateRedPacketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{150} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{166} } func (x *UpdateRedPacketConfigResponse) GetConfig() *RedPacketConfig { @@ -13440,7 +14931,7 @@ type CreateRedPacketRequest struct { func (x *CreateRedPacketRequest) Reset() { *x = CreateRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13452,7 +14943,7 @@ func (x *CreateRedPacketRequest) String() string { func (*CreateRedPacketRequest) ProtoMessage() {} func (x *CreateRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13465,7 +14956,7 @@ func (x *CreateRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRedPacketRequest.ProtoReflect.Descriptor instead. func (*CreateRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{151} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{167} } func (x *CreateRedPacketRequest) GetRequestId() string { @@ -13542,7 +15033,7 @@ type CreateRedPacketResponse struct { func (x *CreateRedPacketResponse) Reset() { *x = CreateRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13554,7 +15045,7 @@ func (x *CreateRedPacketResponse) String() string { func (*CreateRedPacketResponse) ProtoMessage() {} func (x *CreateRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13567,7 +15058,7 @@ func (x *CreateRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRedPacketResponse.ProtoReflect.Descriptor instead. func (*CreateRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{152} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{168} } func (x *CreateRedPacketResponse) GetPacket() *RedPacket { @@ -13604,7 +15095,7 @@ type ClaimRedPacketRequest struct { func (x *ClaimRedPacketRequest) Reset() { *x = ClaimRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13616,7 +15107,7 @@ func (x *ClaimRedPacketRequest) String() string { func (*ClaimRedPacketRequest) ProtoMessage() {} func (x *ClaimRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13629,7 +15120,7 @@ func (x *ClaimRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimRedPacketRequest.ProtoReflect.Descriptor instead. func (*ClaimRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{153} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{169} } func (x *ClaimRedPacketRequest) GetRequestId() string { @@ -13679,7 +15170,7 @@ type ClaimRedPacketResponse struct { func (x *ClaimRedPacketResponse) Reset() { *x = ClaimRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13691,7 +15182,7 @@ func (x *ClaimRedPacketResponse) String() string { func (*ClaimRedPacketResponse) ProtoMessage() {} func (x *ClaimRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13704,7 +15195,7 @@ func (x *ClaimRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimRedPacketResponse.ProtoReflect.Descriptor instead. func (*ClaimRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{154} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{170} } func (x *ClaimRedPacketResponse) GetClaim() *RedPacketClaim { @@ -13755,7 +15246,7 @@ type ListRedPacketsRequest struct { func (x *ListRedPacketsRequest) Reset() { *x = ListRedPacketsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13767,7 +15258,7 @@ func (x *ListRedPacketsRequest) String() string { func (*ListRedPacketsRequest) ProtoMessage() {} func (x *ListRedPacketsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13780,7 +15271,7 @@ func (x *ListRedPacketsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRedPacketsRequest.ProtoReflect.Descriptor instead. func (*ListRedPacketsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{155} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{171} } func (x *ListRedPacketsRequest) GetRequestId() string { @@ -13878,7 +15369,7 @@ type ListRedPacketsResponse struct { func (x *ListRedPacketsResponse) Reset() { *x = ListRedPacketsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13890,7 +15381,7 @@ func (x *ListRedPacketsResponse) String() string { func (*ListRedPacketsResponse) ProtoMessage() {} func (x *ListRedPacketsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13903,7 +15394,7 @@ func (x *ListRedPacketsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRedPacketsResponse.ProtoReflect.Descriptor instead. func (*ListRedPacketsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{156} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{172} } func (x *ListRedPacketsResponse) GetPackets() []*RedPacket { @@ -13940,7 +15431,7 @@ type GetRedPacketRequest struct { func (x *GetRedPacketRequest) Reset() { *x = GetRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13952,7 +15443,7 @@ func (x *GetRedPacketRequest) String() string { func (*GetRedPacketRequest) ProtoMessage() {} func (x *GetRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13965,7 +15456,7 @@ func (x *GetRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketRequest.ProtoReflect.Descriptor instead. func (*GetRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{157} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{173} } func (x *GetRedPacketRequest) GetRequestId() string { @@ -14013,7 +15504,7 @@ type GetRedPacketResponse struct { func (x *GetRedPacketResponse) Reset() { *x = GetRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14025,7 +15516,7 @@ func (x *GetRedPacketResponse) String() string { func (*GetRedPacketResponse) ProtoMessage() {} func (x *GetRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14038,7 +15529,7 @@ func (x *GetRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketResponse.ProtoReflect.Descriptor instead. func (*GetRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{158} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{174} } func (x *GetRedPacketResponse) GetPacket() *RedPacket { @@ -14066,7 +15557,7 @@ type ExpireRedPacketsRequest struct { func (x *ExpireRedPacketsRequest) Reset() { *x = ExpireRedPacketsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14078,7 +15569,7 @@ func (x *ExpireRedPacketsRequest) String() string { func (*ExpireRedPacketsRequest) ProtoMessage() {} func (x *ExpireRedPacketsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14091,7 +15582,7 @@ func (x *ExpireRedPacketsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRedPacketsRequest.ProtoReflect.Descriptor instead. func (*ExpireRedPacketsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{159} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{175} } func (x *ExpireRedPacketsRequest) GetRequestId() string { @@ -14126,7 +15617,7 @@ type ExpireRedPacketsResponse struct { func (x *ExpireRedPacketsResponse) Reset() { *x = ExpireRedPacketsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14138,7 +15629,7 @@ func (x *ExpireRedPacketsResponse) String() string { func (*ExpireRedPacketsResponse) ProtoMessage() {} func (x *ExpireRedPacketsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14151,7 +15642,7 @@ func (x *ExpireRedPacketsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRedPacketsResponse.ProtoReflect.Descriptor instead. func (*ExpireRedPacketsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{160} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{176} } func (x *ExpireRedPacketsResponse) GetExpiredCount() int32 { @@ -14187,7 +15678,7 @@ type RetryRedPacketRefundRequest struct { func (x *RetryRedPacketRefundRequest) Reset() { *x = RetryRedPacketRefundRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14199,7 +15690,7 @@ func (x *RetryRedPacketRefundRequest) String() string { func (*RetryRedPacketRefundRequest) ProtoMessage() {} func (x *RetryRedPacketRefundRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14212,7 +15703,7 @@ func (x *RetryRedPacketRefundRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryRedPacketRefundRequest.ProtoReflect.Descriptor instead. func (*RetryRedPacketRefundRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{161} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{177} } func (x *RetryRedPacketRefundRequest) GetRequestId() string { @@ -14254,7 +15745,7 @@ type RetryRedPacketRefundResponse struct { func (x *RetryRedPacketRefundResponse) Reset() { *x = RetryRedPacketRefundResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14266,7 +15757,7 @@ func (x *RetryRedPacketRefundResponse) String() string { func (*RetryRedPacketRefundResponse) ProtoMessage() {} func (x *RetryRedPacketRefundResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14279,7 +15770,7 @@ func (x *RetryRedPacketRefundResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryRedPacketRefundResponse.ProtoReflect.Descriptor instead. func (*RetryRedPacketRefundResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{162} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{178} } func (x *RetryRedPacketRefundResponse) GetPacket() *RedPacket { @@ -14318,7 +15809,7 @@ type CronBatchRequest struct { func (x *CronBatchRequest) Reset() { *x = CronBatchRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14330,7 +15821,7 @@ func (x *CronBatchRequest) String() string { func (*CronBatchRequest) ProtoMessage() {} func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14343,7 +15834,7 @@ func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchRequest.ProtoReflect.Descriptor instead. func (*CronBatchRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{163} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{179} } func (x *CronBatchRequest) GetRequestId() string { @@ -14402,7 +15893,7 @@ type CronBatchResponse struct { func (x *CronBatchResponse) Reset() { *x = CronBatchResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14414,7 +15905,7 @@ func (x *CronBatchResponse) String() string { func (*CronBatchResponse) ProtoMessage() {} func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14427,7 +15918,7 @@ func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchResponse.ProtoReflect.Descriptor instead. func (*CronBatchResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{164} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{180} } func (x *CronBatchResponse) GetClaimedCount() int32 { @@ -14486,7 +15977,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + " \x01(\bR\ftargetIsHost\x121\n" + "\x15target_host_region_id\x18\v \x01(\x03R\x12targetHostRegionId\x12<\n" + "\x1btarget_agency_owner_user_id\x18\f \x01(\x03R\x17targetAgencyOwnerUserId\x12(\n" + - "\x10sender_region_id\x18\r \x01(\x03R\x0esenderRegionId\"\x98\x05\n" + + "\x10sender_region_id\x18\r \x01(\x03R\x0esenderRegionId\"\xc4\x05\n" + "\x11DebitGiftResponse\x12,\n" + "\x12billing_receipt_id\x18\x01 \x01(\tR\x10billingReceiptId\x12%\n" + "\x0etransaction_id\x18\x02 \x01(\tR\rtransactionId\x12\x1d\n" + @@ -14506,7 +15997,8 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x10cp_relation_type\x18\r \x01(\tR\x0ecpRelationType\x12\x1b\n" + "\tgift_name\x18\x0e \x01(\tR\bgiftName\x12\"\n" + "\rgift_icon_url\x18\x0f \x01(\tR\vgiftIconUrl\x12,\n" + - "\x12gift_animation_url\x18\x10 \x01(\tR\x10giftAnimationUrl\"\xed\x01\n" + + "\x12gift_animation_url\x18\x10 \x01(\tR\x10giftAnimationUrl\x12*\n" + + "\x11gift_effect_types\x18\x11 \x03(\tR\x0fgiftEffectTypes\"\xed\x01\n" + "\x0fDebitGiftTarget\x12\x1d\n" + "\n" + "command_id\x18\x01 \x01(\tR\tcommandId\x12$\n" + @@ -15411,7 +16903,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x10total_coin_value\x18\x05 \x01(\x03R\x0etotalCoinValue\x12.\n" + "\x13total_diamond_value\x18\x06 \x01(\x03R\x11totalDiamondValue\x12(\n" + "\x10total_gift_point\x18\a \x01(\x03R\x0etotalGiftPoint\x12(\n" + - "\x10total_heat_value\x18\b \x01(\x03R\x0etotalHeatValue\"\xde\x05\n" + + "\x10total_heat_value\x18\b \x01(\x03R\x0etotalHeatValue\"\x83\x06\n" + "\x0fRechargeProduct\x12\x1d\n" + "\n" + "product_id\x18\x01 \x01(\x03R\tproductId\x12!\n" + @@ -15438,14 +16930,16 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\rcreated_at_ms\x18\x12 \x01(\x03R\vcreatedAtMs\x12\"\n" + "\rupdated_at_ms\x18\x13 \x01(\x03R\vupdatedAtMs\x12\x19\n" + "\bapp_code\x18\x14 \x01(\tR\aappCode\x12\x16\n" + - "\x06status\x18\x15 \x01(\tR\x06status\"\xa9\x01\n" + + "\x06status\x18\x15 \x01(\tR\x06status\x12#\n" + + "\raudience_type\x18\x16 \x01(\tR\faudienceType\"\xce\x01\n" + "\x1bListRechargeProductsRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x17\n" + "\auser_id\x18\x03 \x01(\x03R\x06userId\x12\x1b\n" + "\tregion_id\x18\x04 \x01(\x03R\bregionId\x12\x1a\n" + - "\bplatform\x18\x05 \x01(\tR\bplatform\"x\n" + + "\bplatform\x18\x05 \x01(\tR\bplatform\x12#\n" + + "\raudience_type\x18\x06 \x01(\tR\faudienceType\"x\n" + "\x1cListRechargeProductsResponse\x12<\n" + "\bproducts\x18\x01 \x03(\v2 .hyapp.wallet.v1.RechargeProductR\bproducts\x12\x1a\n" + "\bchannels\x18\x02 \x03(\tR\bchannels\"\xfd\x02\n" + @@ -15476,7 +16970,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "coinAmount\x127\n" + "\abalance\x18\a \x01(\v2\x1d.hyapp.wallet.v1.AssetBalanceR\abalance\x12+\n" + "\x11idempotent_replay\x18\b \x01(\bR\x10idempotentReplay\x12#\n" + - "\rconsume_state\x18\t \x01(\tR\fconsumeState\"\xf8\x01\n" + + "\rconsume_state\x18\t \x01(\tR\fconsumeState\"\x9d\x02\n" + " ListAdminRechargeProductsRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + @@ -15486,10 +16980,11 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\tregion_id\x18\x05 \x01(\x03R\bregionId\x12\x18\n" + "\akeyword\x18\x06 \x01(\tR\akeyword\x12\x12\n" + "\x04page\x18\a \x01(\x05R\x04page\x12\x1b\n" + - "\tpage_size\x18\b \x01(\x05R\bpageSize\"w\n" + + "\tpage_size\x18\b \x01(\x05R\bpageSize\x12#\n" + + "\raudience_type\x18\t \x01(\tR\faudienceType\"w\n" + "!ListAdminRechargeProductsResponse\x12<\n" + "\bproducts\x18\x01 \x03(\v2 .hyapp.wallet.v1.RechargeProductR\bproducts\x12\x14\n" + - "\x05total\x18\x02 \x01(\x03R\x05total\"\xe0\x02\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\"\x85\x03\n" + "\x1cCreateRechargeProductRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + @@ -15504,7 +16999,8 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "region_ids\x18\b \x03(\x03R\tregionIds\x12\x18\n" + "\aenabled\x18\t \x01(\bR\aenabled\x12(\n" + "\x10operator_user_id\x18\n" + - " \x01(\x03R\x0eoperatorUserId\"\xff\x02\n" + + " \x01(\x03R\x0eoperatorUserId\x12#\n" + + "\raudience_type\x18\v \x01(\tR\faudienceType\"\xa4\x03\n" + "\x1cUpdateRechargeProductRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + @@ -15521,7 +17017,8 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "region_ids\x18\t \x03(\x03R\tregionIds\x12\x18\n" + "\aenabled\x18\n" + " \x01(\bR\aenabled\x12(\n" + - "\x10operator_user_id\x18\v \x01(\x03R\x0eoperatorUserId\"\xa1\x01\n" + + "\x10operator_user_id\x18\v \x01(\x03R\x0eoperatorUserId\x12#\n" + + "\raudience_type\x18\f \x01(\tR\faudienceType\"\xa1\x01\n" + "\x1cDeleteRechargeProductRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + @@ -15532,7 +17029,152 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x17RechargeProductResponse\x12:\n" + "\aproduct\x18\x01 \x01(\v2 .hyapp.wallet.v1.RechargeProductR\aproduct\"9\n" + "\x1dDeleteRechargeProductResponse\x12\x18\n" + - "\adeleted\x18\x01 \x01(\bR\adeleted\"\xde\x01\n" + + "\adeleted\x18\x01 \x01(\bR\adeleted\"\xa6\x04\n" + + "\x17ThirdPartyPaymentMethod\x12\x1b\n" + + "\tmethod_id\x18\x01 \x01(\x03R\bmethodId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12#\n" + + "\rprovider_code\x18\x03 \x01(\tR\fproviderCode\x12#\n" + + "\rprovider_name\x18\x04 \x01(\tR\fproviderName\x12!\n" + + "\fcountry_code\x18\x05 \x01(\tR\vcountryCode\x12!\n" + + "\fcountry_name\x18\x06 \x01(\tR\vcountryName\x12#\n" + + "\rcurrency_code\x18\a \x01(\tR\fcurrencyCode\x12\x17\n" + + "\apay_way\x18\b \x01(\tR\x06payWay\x12\x19\n" + + "\bpay_type\x18\t \x01(\tR\apayType\x12\x1f\n" + + "\vmethod_name\x18\n" + + " \x01(\tR\n" + + "methodName\x12\x19\n" + + "\blogo_url\x18\v \x01(\tR\alogoUrl\x12\x16\n" + + "\x06status\x18\f \x01(\tR\x06status\x12/\n" + + "\x14usd_to_currency_rate\x18\r \x01(\tR\x11usdToCurrencyRate\x12\x1d\n" + + "\n" + + "sort_order\x18\x0e \x01(\x05R\tsortOrder\x12\"\n" + + "\rcreated_at_ms\x18\x0f \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x10 \x01(\x03R\vupdatedAtMs\"\xfa\x01\n" + + "\x18ThirdPartyPaymentChannel\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12#\n" + + "\rprovider_code\x18\x02 \x01(\tR\fproviderCode\x12#\n" + + "\rprovider_name\x18\x03 \x01(\tR\fproviderName\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "sort_order\x18\x05 \x01(\x05R\tsortOrder\x12B\n" + + "\amethods\x18\x06 \x03(\v2(.hyapp.wallet.v1.ThirdPartyPaymentMethodR\amethods\"\xd7\x01\n" + + "$ListThirdPartyPaymentChannelsRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12#\n" + + "\rprovider_code\x18\x03 \x01(\tR\fproviderCode\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x128\n" + + "\x18include_disabled_methods\x18\x05 \x01(\bR\x16includeDisabledMethods\"n\n" + + "%ListThirdPartyPaymentChannelsResponse\x12E\n" + + "\bchannels\x18\x01 \x03(\v2).hyapp.wallet.v1.ThirdPartyPaymentChannelR\bchannels\"\xc4\x01\n" + + "'SetThirdPartyPaymentMethodStatusRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x1b\n" + + "\tmethod_id\x18\x03 \x01(\x03R\bmethodId\x12\x18\n" + + "\aenabled\x18\x04 \x01(\bR\aenabled\x12(\n" + + "\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\"c\n" + + "\x1fThirdPartyPaymentMethodResponse\x12@\n" + + "\x06method\x18\x01 \x01(\v2(.hyapp.wallet.v1.ThirdPartyPaymentMethodR\x06method\"\xd6\x01\n" + + "\"UpdateThirdPartyPaymentRateRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x1b\n" + + "\tmethod_id\x18\x03 \x01(\x03R\bmethodId\x12/\n" + + "\x14usd_to_currency_rate\x18\x04 \x01(\tR\x11usdToCurrencyRate\x12(\n" + + "\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\"\xf9\x01\n" + + "\x18H5RechargeOptionsRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12$\n" + + "\x0etarget_user_id\x18\x03 \x01(\x03R\ftargetUserId\x12(\n" + + "\x10target_region_id\x18\x04 \x01(\x03R\x0etargetRegionId\x12.\n" + + "\x13target_country_code\x18\x05 \x01(\tR\x11targetCountryCode\x12#\n" + + "\raudience_type\x18\x06 \x01(\tR\faudienceType\"\x88\x02\n" + + "\x19H5RechargeOptionsResponse\x12<\n" + + "\bproducts\x18\x01 \x03(\v2 .hyapp.wallet.v1.RechargeProductR\bproducts\x12Q\n" + + "\x0fpayment_methods\x18\x02 \x03(\v2(.hyapp.wallet.v1.ThirdPartyPaymentMethodR\x0epaymentMethods\x12,\n" + + "\x12usdt_trc20_enabled\x18\x03 \x01(\bR\x10usdtTrc20Enabled\x12,\n" + + "\x12usdt_trc20_address\x18\x04 \x01(\tR\x10usdtTrc20Address\"\xe2\a\n" + + "\x15ExternalRechargeOrder\x12\x19\n" + + "\border_id\x18\x01 \x01(\tR\aorderId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12$\n" + + "\x0etarget_user_id\x18\x03 \x01(\x03R\ftargetUserId\x12(\n" + + "\x10target_region_id\x18\x04 \x01(\x03R\x0etargetRegionId\x12.\n" + + "\x13target_country_code\x18\x05 \x01(\tR\x11targetCountryCode\x12#\n" + + "\raudience_type\x18\x06 \x01(\tR\faudienceType\x12\x1d\n" + + "\n" + + "product_id\x18\a \x01(\x03R\tproductId\x12!\n" + + "\fproduct_name\x18\b \x01(\tR\vproductName\x12\x1f\n" + + "\vcoin_amount\x18\t \x01(\x03R\n" + + "coinAmount\x12(\n" + + "\x10usd_minor_amount\x18\n" + + " \x01(\x03R\x0eusdMinorAmount\x12#\n" + + "\rprovider_code\x18\v \x01(\tR\fproviderCode\x12*\n" + + "\x11payment_method_id\x18\f \x01(\x03R\x0fpaymentMethodId\x12!\n" + + "\fcountry_code\x18\r \x01(\tR\vcountryCode\x12#\n" + + "\rcurrency_code\x18\x0e \x01(\tR\fcurrencyCode\x122\n" + + "\x15provider_amount_minor\x18\x0f \x01(\x03R\x13providerAmountMinor\x12\x17\n" + + "\apay_way\x18\x10 \x01(\tR\x06payWay\x12\x19\n" + + "\bpay_type\x18\x11 \x01(\tR\apayType\x12\x17\n" + + "\apay_url\x18\x12 \x01(\tR\x06payUrl\x12*\n" + + "\x11provider_order_id\x18\x13 \x01(\tR\x0fproviderOrderId\x12\x17\n" + + "\atx_hash\x18\x14 \x01(\tR\x06txHash\x12'\n" + + "\x0freceive_address\x18\x15 \x01(\tR\x0ereceiveAddress\x12\x16\n" + + "\x06status\x18\x16 \x01(\tR\x06status\x12%\n" + + "\x0efailure_reason\x18\x17 \x01(\tR\rfailureReason\x12%\n" + + "\x0etransaction_id\x18\x18 \x01(\tR\rtransactionId\x12\"\n" + + "\rcreated_at_ms\x18\x19 \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x1a \x01(\x03R\vupdatedAtMs\x12+\n" + + "\x11idempotent_replay\x18\x1b \x01(\bR\x10idempotentReplay\"\x83\x04\n" + + "\x1cCreateH5RechargeOrderRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x1d\n" + + "\n" + + "command_id\x18\x03 \x01(\tR\tcommandId\x12$\n" + + "\x0etarget_user_id\x18\x04 \x01(\x03R\ftargetUserId\x12(\n" + + "\x10target_region_id\x18\x05 \x01(\x03R\x0etargetRegionId\x12.\n" + + "\x13target_country_code\x18\x06 \x01(\tR\x11targetCountryCode\x12#\n" + + "\raudience_type\x18\a \x01(\tR\faudienceType\x12\x1d\n" + + "\n" + + "product_id\x18\b \x01(\x03R\tproductId\x12#\n" + + "\rprovider_code\x18\t \x01(\tR\fproviderCode\x12*\n" + + "\x11payment_method_id\x18\n" + + " \x01(\x03R\x0fpaymentMethodId\x12\x1d\n" + + "\n" + + "return_url\x18\v \x01(\tR\treturnUrl\x12\x1d\n" + + "\n" + + "notify_url\x18\f \x01(\tR\tnotifyUrl\x12\x1b\n" + + "\tclient_ip\x18\r \x01(\tR\bclientIp\x12\x1a\n" + + "\blanguage\x18\x0e \x01(\tR\blanguage\"\xaf\x01\n" + + "\x19SubmitH5RechargeTxRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x19\n" + + "\border_id\x18\x03 \x01(\tR\aorderId\x12$\n" + + "\x0etarget_user_id\x18\x04 \x01(\x03R\ftargetUserId\x12\x17\n" + + "\atx_hash\x18\x05 \x01(\tR\x06txHash\"\x96\x01\n" + + "\x19GetH5RechargeOrderRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x19\n" + + "\border_id\x18\x03 \x01(\tR\aorderId\x12$\n" + + "\x0etarget_user_id\x18\x04 \x01(\x03R\ftargetUserId\"W\n" + + "\x17H5RechargeOrderResponse\x12<\n" + + "\x05order\x18\x01 \x01(\v2&.hyapp.wallet.v1.ExternalRechargeOrderR\x05order\"\x9f\x01\n" + + "\x1aHandleMifapayNotifyRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x1f\n" + + "\vmer_account\x18\x03 \x01(\tR\n" + + "merAccount\x12\x12\n" + + "\x04data\x18\x04 \x01(\tR\x04data\x12\x12\n" + + "\x04sign\x18\x05 \x01(\tR\x04sign\"y\n" + + "\x1bHandleMifapayNotifyResponse\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\bR\baccepted\x12#\n" + + "\rresponse_text\x18\x02 \x01(\tR\fresponseText\x12\x19\n" + + "\border_id\x18\x03 \x01(\tR\aorderId\"\xde\x01\n" + "\x13DiamondExchangeRule\x12#\n" + "\rexchange_type\x18\x01 \x01(\tR\fexchangeType\x12&\n" + "\x0ffrom_asset_type\x18\x02 \x01(\tR\rfromAssetType\x12\"\n" + @@ -15952,7 +17594,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x11WalletCronService\x12n\n" + "%ProcessHostSalaryDailySettlementBatch\x12!.hyapp.wallet.v1.CronBatchRequest\x1a\".hyapp.wallet.v1.CronBatchResponse\x12r\n" + ")ProcessHostSalaryHalfMonthSettlementBatch\x12!.hyapp.wallet.v1.CronBatchRequest\x1a\".hyapp.wallet.v1.CronBatchResponse\x12g\n" + - "\x1eProcessHostSalaryMonthEndBatch\x12!.hyapp.wallet.v1.CronBatchRequest\x1a\".hyapp.wallet.v1.CronBatchResponse2\xf5;\n" + + "\x1eProcessHostSalaryMonthEndBatch\x12!.hyapp.wallet.v1.CronBatchRequest\x1a\".hyapp.wallet.v1.CronBatchResponse2\xcaC\n" + "\rWalletService\x12R\n" + "\tDebitGift\x12!.hyapp.wallet.v1.DebitGiftRequest\x1a\".hyapp.wallet.v1.DebitGiftResponse\x12a\n" + "\x0eBatchDebitGift\x12&.hyapp.wallet.v1.BatchDebitGiftRequest\x1a'.hyapp.wallet.v1.BatchDebitGiftResponse\x12X\n" + @@ -16001,7 +17643,15 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x19ListAdminRechargeProducts\x121.hyapp.wallet.v1.ListAdminRechargeProductsRequest\x1a2.hyapp.wallet.v1.ListAdminRechargeProductsResponse\x12p\n" + "\x15CreateRechargeProduct\x12-.hyapp.wallet.v1.CreateRechargeProductRequest\x1a(.hyapp.wallet.v1.RechargeProductResponse\x12p\n" + "\x15UpdateRechargeProduct\x12-.hyapp.wallet.v1.UpdateRechargeProductRequest\x1a(.hyapp.wallet.v1.RechargeProductResponse\x12v\n" + - "\x15DeleteRechargeProduct\x12-.hyapp.wallet.v1.DeleteRechargeProductRequest\x1a..hyapp.wallet.v1.DeleteRechargeProductResponse\x12\x7f\n" + + "\x15DeleteRechargeProduct\x12-.hyapp.wallet.v1.DeleteRechargeProductRequest\x1a..hyapp.wallet.v1.DeleteRechargeProductResponse\x12\x8e\x01\n" + + "\x1dListThirdPartyPaymentChannels\x125.hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest\x1a6.hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse\x12\x8e\x01\n" + + " SetThirdPartyPaymentMethodStatus\x128.hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest\x1a0.hyapp.wallet.v1.ThirdPartyPaymentMethodResponse\x12\x84\x01\n" + + "\x1bUpdateThirdPartyPaymentRate\x123.hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest\x1a0.hyapp.wallet.v1.ThirdPartyPaymentMethodResponse\x12n\n" + + "\x15ListH5RechargeOptions\x12).hyapp.wallet.v1.H5RechargeOptionsRequest\x1a*.hyapp.wallet.v1.H5RechargeOptionsResponse\x12p\n" + + "\x15CreateH5RechargeOrder\x12-.hyapp.wallet.v1.CreateH5RechargeOrderRequest\x1a(.hyapp.wallet.v1.H5RechargeOrderResponse\x12j\n" + + "\x12SubmitH5RechargeTx\x12*.hyapp.wallet.v1.SubmitH5RechargeTxRequest\x1a(.hyapp.wallet.v1.H5RechargeOrderResponse\x12j\n" + + "\x12GetH5RechargeOrder\x12*.hyapp.wallet.v1.GetH5RechargeOrderRequest\x1a(.hyapp.wallet.v1.H5RechargeOrderResponse\x12p\n" + + "\x13HandleMifapayNotify\x12+.hyapp.wallet.v1.HandleMifapayNotifyRequest\x1a,.hyapp.wallet.v1.HandleMifapayNotifyResponse\x12\x7f\n" + "\x18GetDiamondExchangeConfig\x120.hyapp.wallet.v1.GetDiamondExchangeConfigRequest\x1a1.hyapp.wallet.v1.GetDiamondExchangeConfigResponse\x12y\n" + "\x16ListWalletTransactions\x12..hyapp.wallet.v1.ListWalletTransactionsRequest\x1a/.hyapp.wallet.v1.ListWalletTransactionsResponse\x12d\n" + "\x0fListVipPackages\x12'.hyapp.wallet.v1.ListVipPackagesRequest\x1a(.hyapp.wallet.v1.ListVipPackagesResponse\x12O\n" + @@ -16036,7 +17686,7 @@ func file_proto_wallet_v1_wallet_proto_rawDescGZIP() []byte { return file_proto_wallet_v1_wallet_proto_rawDescData } -var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 165) +var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 181) var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*DebitGiftRequest)(nil), // 0: hyapp.wallet.v1.DebitGiftRequest (*DebitGiftResponse)(nil), // 1: hyapp.wallet.v1.DebitGiftResponse @@ -16150,59 +17800,75 @@ var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*DeleteRechargeProductRequest)(nil), // 109: hyapp.wallet.v1.DeleteRechargeProductRequest (*RechargeProductResponse)(nil), // 110: hyapp.wallet.v1.RechargeProductResponse (*DeleteRechargeProductResponse)(nil), // 111: hyapp.wallet.v1.DeleteRechargeProductResponse - (*DiamondExchangeRule)(nil), // 112: hyapp.wallet.v1.DiamondExchangeRule - (*GetDiamondExchangeConfigRequest)(nil), // 113: hyapp.wallet.v1.GetDiamondExchangeConfigRequest - (*GetDiamondExchangeConfigResponse)(nil), // 114: hyapp.wallet.v1.GetDiamondExchangeConfigResponse - (*WalletTransaction)(nil), // 115: hyapp.wallet.v1.WalletTransaction - (*ListWalletTransactionsRequest)(nil), // 116: hyapp.wallet.v1.ListWalletTransactionsRequest - (*ListWalletTransactionsResponse)(nil), // 117: hyapp.wallet.v1.ListWalletTransactionsResponse - (*VipRewardItem)(nil), // 118: hyapp.wallet.v1.VipRewardItem - (*VipLevel)(nil), // 119: hyapp.wallet.v1.VipLevel - (*UserVip)(nil), // 120: hyapp.wallet.v1.UserVip - (*ListVipPackagesRequest)(nil), // 121: hyapp.wallet.v1.ListVipPackagesRequest - (*ListVipPackagesResponse)(nil), // 122: hyapp.wallet.v1.ListVipPackagesResponse - (*GetMyVipRequest)(nil), // 123: hyapp.wallet.v1.GetMyVipRequest - (*GetMyVipResponse)(nil), // 124: hyapp.wallet.v1.GetMyVipResponse - (*PurchaseVipRequest)(nil), // 125: hyapp.wallet.v1.PurchaseVipRequest - (*PurchaseVipResponse)(nil), // 126: hyapp.wallet.v1.PurchaseVipResponse - (*DebitCPBreakupFeeRequest)(nil), // 127: hyapp.wallet.v1.DebitCPBreakupFeeRequest - (*DebitCPBreakupFeeResponse)(nil), // 128: hyapp.wallet.v1.DebitCPBreakupFeeResponse - (*GrantVipRequest)(nil), // 129: hyapp.wallet.v1.GrantVipRequest - (*GrantVipResponse)(nil), // 130: hyapp.wallet.v1.GrantVipResponse - (*AdminVipLevelInput)(nil), // 131: hyapp.wallet.v1.AdminVipLevelInput - (*ListAdminVipLevelsRequest)(nil), // 132: hyapp.wallet.v1.ListAdminVipLevelsRequest - (*ListAdminVipLevelsResponse)(nil), // 133: hyapp.wallet.v1.ListAdminVipLevelsResponse - (*UpdateAdminVipLevelsRequest)(nil), // 134: hyapp.wallet.v1.UpdateAdminVipLevelsRequest - (*UpdateAdminVipLevelsResponse)(nil), // 135: hyapp.wallet.v1.UpdateAdminVipLevelsResponse - (*CreditTaskRewardRequest)(nil), // 136: hyapp.wallet.v1.CreditTaskRewardRequest - (*CreditTaskRewardResponse)(nil), // 137: hyapp.wallet.v1.CreditTaskRewardResponse - (*CreditLuckyGiftRewardRequest)(nil), // 138: hyapp.wallet.v1.CreditLuckyGiftRewardRequest - (*CreditLuckyGiftRewardResponse)(nil), // 139: hyapp.wallet.v1.CreditLuckyGiftRewardResponse - (*CreditRoomTurnoverRewardRequest)(nil), // 140: hyapp.wallet.v1.CreditRoomTurnoverRewardRequest - (*CreditRoomTurnoverRewardResponse)(nil), // 141: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse - (*ApplyGameCoinChangeRequest)(nil), // 142: hyapp.wallet.v1.ApplyGameCoinChangeRequest - (*ApplyGameCoinChangeResponse)(nil), // 143: hyapp.wallet.v1.ApplyGameCoinChangeResponse - (*RedPacketConfig)(nil), // 144: hyapp.wallet.v1.RedPacketConfig - (*RedPacketClaim)(nil), // 145: hyapp.wallet.v1.RedPacketClaim - (*RedPacket)(nil), // 146: hyapp.wallet.v1.RedPacket - (*GetRedPacketConfigRequest)(nil), // 147: hyapp.wallet.v1.GetRedPacketConfigRequest - (*GetRedPacketConfigResponse)(nil), // 148: hyapp.wallet.v1.GetRedPacketConfigResponse - (*UpdateRedPacketConfigRequest)(nil), // 149: hyapp.wallet.v1.UpdateRedPacketConfigRequest - (*UpdateRedPacketConfigResponse)(nil), // 150: hyapp.wallet.v1.UpdateRedPacketConfigResponse - (*CreateRedPacketRequest)(nil), // 151: hyapp.wallet.v1.CreateRedPacketRequest - (*CreateRedPacketResponse)(nil), // 152: hyapp.wallet.v1.CreateRedPacketResponse - (*ClaimRedPacketRequest)(nil), // 153: hyapp.wallet.v1.ClaimRedPacketRequest - (*ClaimRedPacketResponse)(nil), // 154: hyapp.wallet.v1.ClaimRedPacketResponse - (*ListRedPacketsRequest)(nil), // 155: hyapp.wallet.v1.ListRedPacketsRequest - (*ListRedPacketsResponse)(nil), // 156: hyapp.wallet.v1.ListRedPacketsResponse - (*GetRedPacketRequest)(nil), // 157: hyapp.wallet.v1.GetRedPacketRequest - (*GetRedPacketResponse)(nil), // 158: hyapp.wallet.v1.GetRedPacketResponse - (*ExpireRedPacketsRequest)(nil), // 159: hyapp.wallet.v1.ExpireRedPacketsRequest - (*ExpireRedPacketsResponse)(nil), // 160: hyapp.wallet.v1.ExpireRedPacketsResponse - (*RetryRedPacketRefundRequest)(nil), // 161: hyapp.wallet.v1.RetryRedPacketRefundRequest - (*RetryRedPacketRefundResponse)(nil), // 162: hyapp.wallet.v1.RetryRedPacketRefundResponse - (*CronBatchRequest)(nil), // 163: hyapp.wallet.v1.CronBatchRequest - (*CronBatchResponse)(nil), // 164: hyapp.wallet.v1.CronBatchResponse + (*ThirdPartyPaymentMethod)(nil), // 112: hyapp.wallet.v1.ThirdPartyPaymentMethod + (*ThirdPartyPaymentChannel)(nil), // 113: hyapp.wallet.v1.ThirdPartyPaymentChannel + (*ListThirdPartyPaymentChannelsRequest)(nil), // 114: hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest + (*ListThirdPartyPaymentChannelsResponse)(nil), // 115: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse + (*SetThirdPartyPaymentMethodStatusRequest)(nil), // 116: hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest + (*ThirdPartyPaymentMethodResponse)(nil), // 117: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + (*UpdateThirdPartyPaymentRateRequest)(nil), // 118: hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest + (*H5RechargeOptionsRequest)(nil), // 119: hyapp.wallet.v1.H5RechargeOptionsRequest + (*H5RechargeOptionsResponse)(nil), // 120: hyapp.wallet.v1.H5RechargeOptionsResponse + (*ExternalRechargeOrder)(nil), // 121: hyapp.wallet.v1.ExternalRechargeOrder + (*CreateH5RechargeOrderRequest)(nil), // 122: hyapp.wallet.v1.CreateH5RechargeOrderRequest + (*SubmitH5RechargeTxRequest)(nil), // 123: hyapp.wallet.v1.SubmitH5RechargeTxRequest + (*GetH5RechargeOrderRequest)(nil), // 124: hyapp.wallet.v1.GetH5RechargeOrderRequest + (*H5RechargeOrderResponse)(nil), // 125: hyapp.wallet.v1.H5RechargeOrderResponse + (*HandleMifapayNotifyRequest)(nil), // 126: hyapp.wallet.v1.HandleMifapayNotifyRequest + (*HandleMifapayNotifyResponse)(nil), // 127: hyapp.wallet.v1.HandleMifapayNotifyResponse + (*DiamondExchangeRule)(nil), // 128: hyapp.wallet.v1.DiamondExchangeRule + (*GetDiamondExchangeConfigRequest)(nil), // 129: hyapp.wallet.v1.GetDiamondExchangeConfigRequest + (*GetDiamondExchangeConfigResponse)(nil), // 130: hyapp.wallet.v1.GetDiamondExchangeConfigResponse + (*WalletTransaction)(nil), // 131: hyapp.wallet.v1.WalletTransaction + (*ListWalletTransactionsRequest)(nil), // 132: hyapp.wallet.v1.ListWalletTransactionsRequest + (*ListWalletTransactionsResponse)(nil), // 133: hyapp.wallet.v1.ListWalletTransactionsResponse + (*VipRewardItem)(nil), // 134: hyapp.wallet.v1.VipRewardItem + (*VipLevel)(nil), // 135: hyapp.wallet.v1.VipLevel + (*UserVip)(nil), // 136: hyapp.wallet.v1.UserVip + (*ListVipPackagesRequest)(nil), // 137: hyapp.wallet.v1.ListVipPackagesRequest + (*ListVipPackagesResponse)(nil), // 138: hyapp.wallet.v1.ListVipPackagesResponse + (*GetMyVipRequest)(nil), // 139: hyapp.wallet.v1.GetMyVipRequest + (*GetMyVipResponse)(nil), // 140: hyapp.wallet.v1.GetMyVipResponse + (*PurchaseVipRequest)(nil), // 141: hyapp.wallet.v1.PurchaseVipRequest + (*PurchaseVipResponse)(nil), // 142: hyapp.wallet.v1.PurchaseVipResponse + (*DebitCPBreakupFeeRequest)(nil), // 143: hyapp.wallet.v1.DebitCPBreakupFeeRequest + (*DebitCPBreakupFeeResponse)(nil), // 144: hyapp.wallet.v1.DebitCPBreakupFeeResponse + (*GrantVipRequest)(nil), // 145: hyapp.wallet.v1.GrantVipRequest + (*GrantVipResponse)(nil), // 146: hyapp.wallet.v1.GrantVipResponse + (*AdminVipLevelInput)(nil), // 147: hyapp.wallet.v1.AdminVipLevelInput + (*ListAdminVipLevelsRequest)(nil), // 148: hyapp.wallet.v1.ListAdminVipLevelsRequest + (*ListAdminVipLevelsResponse)(nil), // 149: hyapp.wallet.v1.ListAdminVipLevelsResponse + (*UpdateAdminVipLevelsRequest)(nil), // 150: hyapp.wallet.v1.UpdateAdminVipLevelsRequest + (*UpdateAdminVipLevelsResponse)(nil), // 151: hyapp.wallet.v1.UpdateAdminVipLevelsResponse + (*CreditTaskRewardRequest)(nil), // 152: hyapp.wallet.v1.CreditTaskRewardRequest + (*CreditTaskRewardResponse)(nil), // 153: hyapp.wallet.v1.CreditTaskRewardResponse + (*CreditLuckyGiftRewardRequest)(nil), // 154: hyapp.wallet.v1.CreditLuckyGiftRewardRequest + (*CreditLuckyGiftRewardResponse)(nil), // 155: hyapp.wallet.v1.CreditLuckyGiftRewardResponse + (*CreditRoomTurnoverRewardRequest)(nil), // 156: hyapp.wallet.v1.CreditRoomTurnoverRewardRequest + (*CreditRoomTurnoverRewardResponse)(nil), // 157: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse + (*ApplyGameCoinChangeRequest)(nil), // 158: hyapp.wallet.v1.ApplyGameCoinChangeRequest + (*ApplyGameCoinChangeResponse)(nil), // 159: hyapp.wallet.v1.ApplyGameCoinChangeResponse + (*RedPacketConfig)(nil), // 160: hyapp.wallet.v1.RedPacketConfig + (*RedPacketClaim)(nil), // 161: hyapp.wallet.v1.RedPacketClaim + (*RedPacket)(nil), // 162: hyapp.wallet.v1.RedPacket + (*GetRedPacketConfigRequest)(nil), // 163: hyapp.wallet.v1.GetRedPacketConfigRequest + (*GetRedPacketConfigResponse)(nil), // 164: hyapp.wallet.v1.GetRedPacketConfigResponse + (*UpdateRedPacketConfigRequest)(nil), // 165: hyapp.wallet.v1.UpdateRedPacketConfigRequest + (*UpdateRedPacketConfigResponse)(nil), // 166: hyapp.wallet.v1.UpdateRedPacketConfigResponse + (*CreateRedPacketRequest)(nil), // 167: hyapp.wallet.v1.CreateRedPacketRequest + (*CreateRedPacketResponse)(nil), // 168: hyapp.wallet.v1.CreateRedPacketResponse + (*ClaimRedPacketRequest)(nil), // 169: hyapp.wallet.v1.ClaimRedPacketRequest + (*ClaimRedPacketResponse)(nil), // 170: hyapp.wallet.v1.ClaimRedPacketResponse + (*ListRedPacketsRequest)(nil), // 171: hyapp.wallet.v1.ListRedPacketsRequest + (*ListRedPacketsResponse)(nil), // 172: hyapp.wallet.v1.ListRedPacketsResponse + (*GetRedPacketRequest)(nil), // 173: hyapp.wallet.v1.GetRedPacketRequest + (*GetRedPacketResponse)(nil), // 174: hyapp.wallet.v1.GetRedPacketResponse + (*ExpireRedPacketsRequest)(nil), // 175: hyapp.wallet.v1.ExpireRedPacketsRequest + (*ExpireRedPacketsResponse)(nil), // 176: hyapp.wallet.v1.ExpireRedPacketsResponse + (*RetryRedPacketRefundRequest)(nil), // 177: hyapp.wallet.v1.RetryRedPacketRefundRequest + (*RetryRedPacketRefundResponse)(nil), // 178: hyapp.wallet.v1.RetryRedPacketRefundResponse + (*CronBatchRequest)(nil), // 179: hyapp.wallet.v1.CronBatchRequest + (*CronBatchResponse)(nil), // 180: hyapp.wallet.v1.CronBatchResponse } var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 2, // 0: hyapp.wallet.v1.BatchDebitGiftRequest.targets:type_name -> hyapp.wallet.v1.DebitGiftTarget @@ -16256,181 +17922,203 @@ var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 6, // 48: hyapp.wallet.v1.ConfirmGooglePaymentResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance 100, // 49: hyapp.wallet.v1.ListAdminRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct 100, // 50: hyapp.wallet.v1.RechargeProductResponse.product:type_name -> hyapp.wallet.v1.RechargeProduct - 112, // 51: hyapp.wallet.v1.GetDiamondExchangeConfigResponse.rules:type_name -> hyapp.wallet.v1.DiamondExchangeRule - 115, // 52: hyapp.wallet.v1.ListWalletTransactionsResponse.transactions:type_name -> hyapp.wallet.v1.WalletTransaction - 118, // 53: hyapp.wallet.v1.VipLevel.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem - 120, // 54: hyapp.wallet.v1.ListVipPackagesResponse.current_vip:type_name -> hyapp.wallet.v1.UserVip - 119, // 55: hyapp.wallet.v1.ListVipPackagesResponse.packages:type_name -> hyapp.wallet.v1.VipLevel - 120, // 56: hyapp.wallet.v1.GetMyVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 120, // 57: hyapp.wallet.v1.PurchaseVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 118, // 58: hyapp.wallet.v1.PurchaseVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem - 6, // 59: hyapp.wallet.v1.DebitCPBreakupFeeResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 120, // 60: hyapp.wallet.v1.GrantVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 118, // 61: hyapp.wallet.v1.GrantVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem - 119, // 62: hyapp.wallet.v1.ListAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel - 131, // 63: hyapp.wallet.v1.UpdateAdminVipLevelsRequest.levels:type_name -> hyapp.wallet.v1.AdminVipLevelInput - 119, // 64: hyapp.wallet.v1.UpdateAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel - 6, // 65: hyapp.wallet.v1.CreditTaskRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 6, // 66: hyapp.wallet.v1.CreditLuckyGiftRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 6, // 67: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 145, // 68: hyapp.wallet.v1.RedPacket.claims:type_name -> hyapp.wallet.v1.RedPacketClaim - 144, // 69: hyapp.wallet.v1.GetRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig - 144, // 70: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig - 146, // 71: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 145, // 72: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim - 146, // 73: hyapp.wallet.v1.ClaimRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 146, // 74: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket - 146, // 75: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 146, // 76: hyapp.wallet.v1.RetryRedPacketRefundResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 163, // 77: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 163, // 78: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 163, // 79: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 0, // 80: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest - 3, // 81: hyapp.wallet.v1.WalletService.BatchDebitGift:input_type -> hyapp.wallet.v1.BatchDebitGiftRequest - 7, // 82: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest - 11, // 83: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:input_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyRequest - 14, // 84: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:input_type -> hyapp.wallet.v1.GetHostSalaryProgressRequest - 16, // 85: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest - 18, // 86: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest - 20, // 87: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest - 23, // 88: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:input_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersRequest - 25, // 89: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:input_type -> hyapp.wallet.v1.ExchangeSalaryToCoinRequest - 27, // 90: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:input_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerRequest - 39, // 91: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest - 41, // 92: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest - 43, // 93: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest - 44, // 94: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest - 45, // 95: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest - 47, // 96: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest - 49, // 97: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest - 51, // 98: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest - 52, // 99: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest - 53, // 100: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest - 55, // 101: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest - 57, // 102: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest - 61, // 103: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest - 62, // 104: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest - 63, // 105: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest - 59, // 106: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest - 65, // 107: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest - 66, // 108: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest - 68, // 109: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest - 70, // 110: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest - 72, // 111: hyapp.wallet.v1.WalletService.UnequipUserResource:input_type -> hyapp.wallet.v1.UnequipUserResourceRequest - 74, // 112: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:input_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesRequest - 77, // 113: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest - 80, // 114: hyapp.wallet.v1.WalletService.ListResourceShopItems:input_type -> hyapp.wallet.v1.ListResourceShopItemsRequest - 82, // 115: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:input_type -> hyapp.wallet.v1.UpsertResourceShopItemsRequest - 84, // 116: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:input_type -> hyapp.wallet.v1.SetResourceShopItemStatusRequest - 86, // 117: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:input_type -> hyapp.wallet.v1.PurchaseResourceShopItemRequest - 89, // 118: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest - 92, // 119: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest - 95, // 120: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest - 98, // 121: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest - 101, // 122: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest - 103, // 123: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest - 105, // 124: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest - 107, // 125: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest - 108, // 126: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest - 109, // 127: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest - 113, // 128: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest - 116, // 129: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest - 121, // 130: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest - 123, // 131: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest - 125, // 132: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest - 127, // 133: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:input_type -> hyapp.wallet.v1.DebitCPBreakupFeeRequest - 129, // 134: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest - 132, // 135: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest - 134, // 136: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest - 136, // 137: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest - 138, // 138: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest - 140, // 139: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:input_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardRequest - 142, // 140: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest - 147, // 141: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest - 149, // 142: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest - 151, // 143: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest - 153, // 144: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest - 155, // 145: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest - 157, // 146: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest - 159, // 147: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest - 161, // 148: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:input_type -> hyapp.wallet.v1.RetryRedPacketRefundRequest - 164, // 149: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 164, // 150: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 164, // 151: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 1, // 152: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 5, // 153: hyapp.wallet.v1.WalletService.BatchDebitGift:output_type -> hyapp.wallet.v1.BatchDebitGiftResponse - 8, // 154: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse - 12, // 155: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:output_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyResponse - 15, // 156: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:output_type -> hyapp.wallet.v1.GetHostSalaryProgressResponse - 17, // 157: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse - 19, // 158: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse - 21, // 159: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse - 24, // 160: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:output_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersResponse - 26, // 161: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:output_type -> hyapp.wallet.v1.ExchangeSalaryToCoinResponse - 28, // 162: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:output_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerResponse - 40, // 163: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse - 42, // 164: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse - 46, // 165: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 46, // 166: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 46, // 167: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse - 48, // 168: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse - 50, // 169: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse - 54, // 170: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 54, // 171: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 54, // 172: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 56, // 173: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse - 58, // 174: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse - 64, // 175: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 64, // 176: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 64, // 177: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse - 60, // 178: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse - 67, // 179: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 67, // 180: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 69, // 181: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse - 71, // 182: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse - 73, // 183: hyapp.wallet.v1.WalletService.UnequipUserResource:output_type -> hyapp.wallet.v1.UnequipUserResourceResponse - 76, // 184: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:output_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse - 78, // 185: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse - 81, // 186: hyapp.wallet.v1.WalletService.ListResourceShopItems:output_type -> hyapp.wallet.v1.ListResourceShopItemsResponse - 83, // 187: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:output_type -> hyapp.wallet.v1.UpsertResourceShopItemsResponse - 85, // 188: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:output_type -> hyapp.wallet.v1.ResourceShopItemResponse - 87, // 189: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:output_type -> hyapp.wallet.v1.PurchaseResourceShopItemResponse - 90, // 190: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse - 93, // 191: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse - 96, // 192: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse - 99, // 193: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse - 102, // 194: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse - 104, // 195: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse - 106, // 196: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse - 110, // 197: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 110, // 198: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 111, // 199: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse - 114, // 200: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse - 117, // 201: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse - 122, // 202: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse - 124, // 203: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse - 126, // 204: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse - 128, // 205: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:output_type -> hyapp.wallet.v1.DebitCPBreakupFeeResponse - 130, // 206: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse - 133, // 207: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse - 135, // 208: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse - 137, // 209: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse - 139, // 210: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse - 141, // 211: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:output_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardResponse - 143, // 212: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse - 148, // 213: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse - 150, // 214: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse - 152, // 215: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse - 154, // 216: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse - 156, // 217: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse - 158, // 218: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse - 160, // 219: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse - 162, // 220: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:output_type -> hyapp.wallet.v1.RetryRedPacketRefundResponse - 149, // [149:221] is the sub-list for method output_type - 77, // [77:149] is the sub-list for method input_type - 77, // [77:77] is the sub-list for extension type_name - 77, // [77:77] is the sub-list for extension extendee - 0, // [0:77] is the sub-list for field type_name + 112, // 51: hyapp.wallet.v1.ThirdPartyPaymentChannel.methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 113, // 52: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse.channels:type_name -> hyapp.wallet.v1.ThirdPartyPaymentChannel + 112, // 53: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse.method:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 100, // 54: hyapp.wallet.v1.H5RechargeOptionsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct + 112, // 55: hyapp.wallet.v1.H5RechargeOptionsResponse.payment_methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 121, // 56: hyapp.wallet.v1.H5RechargeOrderResponse.order:type_name -> hyapp.wallet.v1.ExternalRechargeOrder + 128, // 57: hyapp.wallet.v1.GetDiamondExchangeConfigResponse.rules:type_name -> hyapp.wallet.v1.DiamondExchangeRule + 131, // 58: hyapp.wallet.v1.ListWalletTransactionsResponse.transactions:type_name -> hyapp.wallet.v1.WalletTransaction + 134, // 59: hyapp.wallet.v1.VipLevel.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 136, // 60: hyapp.wallet.v1.ListVipPackagesResponse.current_vip:type_name -> hyapp.wallet.v1.UserVip + 135, // 61: hyapp.wallet.v1.ListVipPackagesResponse.packages:type_name -> hyapp.wallet.v1.VipLevel + 136, // 62: hyapp.wallet.v1.GetMyVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 136, // 63: hyapp.wallet.v1.PurchaseVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 134, // 64: hyapp.wallet.v1.PurchaseVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 6, // 65: hyapp.wallet.v1.DebitCPBreakupFeeResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 136, // 66: hyapp.wallet.v1.GrantVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 134, // 67: hyapp.wallet.v1.GrantVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 135, // 68: hyapp.wallet.v1.ListAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel + 147, // 69: hyapp.wallet.v1.UpdateAdminVipLevelsRequest.levels:type_name -> hyapp.wallet.v1.AdminVipLevelInput + 135, // 70: hyapp.wallet.v1.UpdateAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel + 6, // 71: hyapp.wallet.v1.CreditTaskRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 6, // 72: hyapp.wallet.v1.CreditLuckyGiftRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 6, // 73: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 161, // 74: hyapp.wallet.v1.RedPacket.claims:type_name -> hyapp.wallet.v1.RedPacketClaim + 160, // 75: hyapp.wallet.v1.GetRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig + 160, // 76: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig + 162, // 77: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 161, // 78: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim + 162, // 79: hyapp.wallet.v1.ClaimRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 162, // 80: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket + 162, // 81: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 162, // 82: hyapp.wallet.v1.RetryRedPacketRefundResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 179, // 83: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 179, // 84: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 179, // 85: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 0, // 86: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest + 3, // 87: hyapp.wallet.v1.WalletService.BatchDebitGift:input_type -> hyapp.wallet.v1.BatchDebitGiftRequest + 7, // 88: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest + 11, // 89: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:input_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyRequest + 14, // 90: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:input_type -> hyapp.wallet.v1.GetHostSalaryProgressRequest + 16, // 91: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest + 18, // 92: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest + 20, // 93: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest + 23, // 94: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:input_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersRequest + 25, // 95: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:input_type -> hyapp.wallet.v1.ExchangeSalaryToCoinRequest + 27, // 96: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:input_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerRequest + 39, // 97: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest + 41, // 98: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest + 43, // 99: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest + 44, // 100: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest + 45, // 101: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest + 47, // 102: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest + 49, // 103: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest + 51, // 104: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest + 52, // 105: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest + 53, // 106: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest + 55, // 107: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest + 57, // 108: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest + 61, // 109: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest + 62, // 110: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest + 63, // 111: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest + 59, // 112: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest + 65, // 113: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest + 66, // 114: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest + 68, // 115: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest + 70, // 116: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest + 72, // 117: hyapp.wallet.v1.WalletService.UnequipUserResource:input_type -> hyapp.wallet.v1.UnequipUserResourceRequest + 74, // 118: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:input_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesRequest + 77, // 119: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest + 80, // 120: hyapp.wallet.v1.WalletService.ListResourceShopItems:input_type -> hyapp.wallet.v1.ListResourceShopItemsRequest + 82, // 121: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:input_type -> hyapp.wallet.v1.UpsertResourceShopItemsRequest + 84, // 122: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:input_type -> hyapp.wallet.v1.SetResourceShopItemStatusRequest + 86, // 123: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:input_type -> hyapp.wallet.v1.PurchaseResourceShopItemRequest + 89, // 124: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest + 92, // 125: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest + 95, // 126: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest + 98, // 127: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest + 101, // 128: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest + 103, // 129: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest + 105, // 130: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest + 107, // 131: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest + 108, // 132: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest + 109, // 133: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest + 114, // 134: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:input_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest + 116, // 135: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:input_type -> hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest + 118, // 136: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:input_type -> hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest + 119, // 137: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:input_type -> hyapp.wallet.v1.H5RechargeOptionsRequest + 122, // 138: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:input_type -> hyapp.wallet.v1.CreateH5RechargeOrderRequest + 123, // 139: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:input_type -> hyapp.wallet.v1.SubmitH5RechargeTxRequest + 124, // 140: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:input_type -> hyapp.wallet.v1.GetH5RechargeOrderRequest + 126, // 141: hyapp.wallet.v1.WalletService.HandleMifapayNotify:input_type -> hyapp.wallet.v1.HandleMifapayNotifyRequest + 129, // 142: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest + 132, // 143: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest + 137, // 144: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest + 139, // 145: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest + 141, // 146: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest + 143, // 147: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:input_type -> hyapp.wallet.v1.DebitCPBreakupFeeRequest + 145, // 148: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest + 148, // 149: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest + 150, // 150: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest + 152, // 151: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest + 154, // 152: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest + 156, // 153: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:input_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardRequest + 158, // 154: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest + 163, // 155: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest + 165, // 156: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest + 167, // 157: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest + 169, // 158: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest + 171, // 159: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest + 173, // 160: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest + 175, // 161: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest + 177, // 162: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:input_type -> hyapp.wallet.v1.RetryRedPacketRefundRequest + 180, // 163: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 180, // 164: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 180, // 165: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 1, // 166: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 5, // 167: hyapp.wallet.v1.WalletService.BatchDebitGift:output_type -> hyapp.wallet.v1.BatchDebitGiftResponse + 8, // 168: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse + 12, // 169: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:output_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyResponse + 15, // 170: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:output_type -> hyapp.wallet.v1.GetHostSalaryProgressResponse + 17, // 171: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse + 19, // 172: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse + 21, // 173: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse + 24, // 174: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:output_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersResponse + 26, // 175: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:output_type -> hyapp.wallet.v1.ExchangeSalaryToCoinResponse + 28, // 176: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:output_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerResponse + 40, // 177: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse + 42, // 178: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse + 46, // 179: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 46, // 180: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 46, // 181: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse + 48, // 182: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse + 50, // 183: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse + 54, // 184: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 54, // 185: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 54, // 186: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 56, // 187: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse + 58, // 188: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse + 64, // 189: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 64, // 190: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 64, // 191: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse + 60, // 192: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse + 67, // 193: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 67, // 194: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 69, // 195: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse + 71, // 196: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse + 73, // 197: hyapp.wallet.v1.WalletService.UnequipUserResource:output_type -> hyapp.wallet.v1.UnequipUserResourceResponse + 76, // 198: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:output_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse + 78, // 199: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse + 81, // 200: hyapp.wallet.v1.WalletService.ListResourceShopItems:output_type -> hyapp.wallet.v1.ListResourceShopItemsResponse + 83, // 201: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:output_type -> hyapp.wallet.v1.UpsertResourceShopItemsResponse + 85, // 202: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:output_type -> hyapp.wallet.v1.ResourceShopItemResponse + 87, // 203: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:output_type -> hyapp.wallet.v1.PurchaseResourceShopItemResponse + 90, // 204: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse + 93, // 205: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse + 96, // 206: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse + 99, // 207: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse + 102, // 208: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse + 104, // 209: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse + 106, // 210: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse + 110, // 211: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 110, // 212: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 111, // 213: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse + 115, // 214: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:output_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse + 117, // 215: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + 117, // 216: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + 120, // 217: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:output_type -> hyapp.wallet.v1.H5RechargeOptionsResponse + 125, // 218: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 125, // 219: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 125, // 220: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 127, // 221: hyapp.wallet.v1.WalletService.HandleMifapayNotify:output_type -> hyapp.wallet.v1.HandleMifapayNotifyResponse + 130, // 222: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse + 133, // 223: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse + 138, // 224: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse + 140, // 225: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse + 142, // 226: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse + 144, // 227: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:output_type -> hyapp.wallet.v1.DebitCPBreakupFeeResponse + 146, // 228: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse + 149, // 229: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse + 151, // 230: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse + 153, // 231: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse + 155, // 232: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse + 157, // 233: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:output_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardResponse + 159, // 234: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse + 164, // 235: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse + 166, // 236: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse + 168, // 237: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse + 170, // 238: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse + 172, // 239: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse + 174, // 240: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse + 176, // 241: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse + 178, // 242: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:output_type -> hyapp.wallet.v1.RetryRedPacketRefundResponse + 163, // [163:243] is the sub-list for method output_type + 83, // [83:163] is the sub-list for method input_type + 83, // [83:83] is the sub-list for extension type_name + 83, // [83:83] is the sub-list for extension extendee + 0, // [0:83] is the sub-list for field type_name } func init() { file_proto_wallet_v1_wallet_proto_init() } @@ -16446,7 +18134,7 @@ func file_proto_wallet_v1_wallet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wallet_v1_wallet_proto_rawDesc), len(file_proto_wallet_v1_wallet_proto_rawDesc)), NumEnums: 0, - NumMessages: 165, + NumMessages: 181, NumExtensions: 0, NumServices: 2, }, diff --git a/api/proto/wallet/v1/wallet.proto b/api/proto/wallet/v1/wallet.proto index fee5779a..38a2c70d 100644 --- a/api/proto/wallet/v1/wallet.proto +++ b/api/proto/wallet/v1/wallet.proto @@ -50,6 +50,7 @@ message DebitGiftResponse { string gift_name = 14; string gift_icon_url = 15; string gift_animation_url = 16; + repeated string gift_effect_types = 17; } // DebitGiftTarget 是一笔批量送礼中的单个接收方账务快照。 @@ -1037,6 +1038,8 @@ message RechargeProduct { int64 updated_at_ms = 19; string app_code = 20; string status = 21; + // audience_type 区分 H5 普通用户档位和币商档位;旧 App 内购默认 normal。 + string audience_type = 22; } message ListRechargeProductsRequest { @@ -1046,6 +1049,8 @@ message ListRechargeProductsRequest { int64 region_id = 4; // platform 来自 gateway 解析的 App 平台请求头;为空时返回该区域全部已上架商品。 string platform = 5; + // audience_type 只用于 H5 站外充值;App 旧入口为空时按 normal 兼容。 + string audience_type = 6; } message ListRechargeProductsResponse { @@ -1089,6 +1094,7 @@ message ListAdminRechargeProductsRequest { string keyword = 6; int32 page = 7; int32 page_size = 8; + string audience_type = 9; } message ListAdminRechargeProductsResponse { @@ -1108,6 +1114,7 @@ message CreateRechargeProductRequest { repeated int64 region_ids = 8; bool enabled = 9; int64 operator_user_id = 10; + string audience_type = 11; } message UpdateRechargeProductRequest { @@ -1122,6 +1129,7 @@ message UpdateRechargeProductRequest { repeated int64 region_ids = 9; bool enabled = 10; int64 operator_user_id = 11; + string audience_type = 12; } message DeleteRechargeProductRequest { @@ -1139,6 +1147,163 @@ message DeleteRechargeProductResponse { bool deleted = 1; } +// ThirdPartyPaymentMethod 是后台和 H5 共用的三方支付方式配置快照。 +message ThirdPartyPaymentMethod { + int64 method_id = 1; + string app_code = 2; + string provider_code = 3; + string provider_name = 4; + string country_code = 5; + string country_name = 6; + string currency_code = 7; + string pay_way = 8; + string pay_type = 9; + string method_name = 10; + string logo_url = 11; + string status = 12; + string usd_to_currency_rate = 13; + int32 sort_order = 14; + int64 created_at_ms = 15; + int64 updated_at_ms = 16; +} + +message ThirdPartyPaymentChannel { + string app_code = 1; + string provider_code = 2; + string provider_name = 3; + string status = 4; + int32 sort_order = 5; + repeated ThirdPartyPaymentMethod methods = 6; +} + +message ListThirdPartyPaymentChannelsRequest { + string request_id = 1; + string app_code = 2; + string provider_code = 3; + string status = 4; + bool include_disabled_methods = 5; +} + +message ListThirdPartyPaymentChannelsResponse { + repeated ThirdPartyPaymentChannel channels = 1; +} + +message SetThirdPartyPaymentMethodStatusRequest { + string request_id = 1; + string app_code = 2; + int64 method_id = 3; + bool enabled = 4; + int64 operator_user_id = 5; +} + +message ThirdPartyPaymentMethodResponse { + ThirdPartyPaymentMethod method = 1; +} + +message UpdateThirdPartyPaymentRateRequest { + string request_id = 1; + string app_code = 2; + int64 method_id = 3; + string usd_to_currency_rate = 4; + int64 operator_user_id = 5; +} + +message H5RechargeOptionsRequest { + string request_id = 1; + string app_code = 2; + int64 target_user_id = 3; + int64 target_region_id = 4; + string target_country_code = 5; + string audience_type = 6; +} + +message H5RechargeOptionsResponse { + repeated RechargeProduct products = 1; + repeated ThirdPartyPaymentMethod payment_methods = 2; + bool usdt_trc20_enabled = 3; + string usdt_trc20_address = 4; +} + +message ExternalRechargeOrder { + string order_id = 1; + string app_code = 2; + int64 target_user_id = 3; + int64 target_region_id = 4; + string target_country_code = 5; + string audience_type = 6; + int64 product_id = 7; + string product_name = 8; + int64 coin_amount = 9; + int64 usd_minor_amount = 10; + string provider_code = 11; + int64 payment_method_id = 12; + string country_code = 13; + string currency_code = 14; + int64 provider_amount_minor = 15; + string pay_way = 16; + string pay_type = 17; + string pay_url = 18; + string provider_order_id = 19; + string tx_hash = 20; + string receive_address = 21; + string status = 22; + string failure_reason = 23; + string transaction_id = 24; + int64 created_at_ms = 25; + int64 updated_at_ms = 26; + bool idempotent_replay = 27; +} + +message CreateH5RechargeOrderRequest { + string request_id = 1; + string app_code = 2; + string command_id = 3; + int64 target_user_id = 4; + int64 target_region_id = 5; + string target_country_code = 6; + string audience_type = 7; + int64 product_id = 8; + string provider_code = 9; + int64 payment_method_id = 10; + string return_url = 11; + string notify_url = 12; + string client_ip = 13; + string language = 14; +} + +message SubmitH5RechargeTxRequest { + string request_id = 1; + string app_code = 2; + string order_id = 3; + int64 target_user_id = 4; + string tx_hash = 5; +} + +message GetH5RechargeOrderRequest { + string request_id = 1; + string app_code = 2; + string order_id = 3; + int64 target_user_id = 4; +} + +message H5RechargeOrderResponse { + ExternalRechargeOrder order = 1; +} + +message HandleMifapayNotifyRequest { + string request_id = 1; + string app_code = 2; + string mer_account = 3; + string data = 4; + string sign = 5; +} + +message HandleMifapayNotifyResponse { + bool accepted = 1; + string response_text = 2; + string order_id = 3; +} + message DiamondExchangeRule { string exchange_type = 1; string from_asset_type = 2; @@ -1668,6 +1833,14 @@ service WalletService { rpc CreateRechargeProduct(CreateRechargeProductRequest) returns (RechargeProductResponse); rpc UpdateRechargeProduct(UpdateRechargeProductRequest) returns (RechargeProductResponse); rpc DeleteRechargeProduct(DeleteRechargeProductRequest) returns (DeleteRechargeProductResponse); + rpc ListThirdPartyPaymentChannels(ListThirdPartyPaymentChannelsRequest) returns (ListThirdPartyPaymentChannelsResponse); + rpc SetThirdPartyPaymentMethodStatus(SetThirdPartyPaymentMethodStatusRequest) returns (ThirdPartyPaymentMethodResponse); + rpc UpdateThirdPartyPaymentRate(UpdateThirdPartyPaymentRateRequest) returns (ThirdPartyPaymentMethodResponse); + rpc ListH5RechargeOptions(H5RechargeOptionsRequest) returns (H5RechargeOptionsResponse); + rpc CreateH5RechargeOrder(CreateH5RechargeOrderRequest) returns (H5RechargeOrderResponse); + rpc SubmitH5RechargeTx(SubmitH5RechargeTxRequest) returns (H5RechargeOrderResponse); + rpc GetH5RechargeOrder(GetH5RechargeOrderRequest) returns (H5RechargeOrderResponse); + rpc HandleMifapayNotify(HandleMifapayNotifyRequest) returns (HandleMifapayNotifyResponse); rpc GetDiamondExchangeConfig(GetDiamondExchangeConfigRequest) returns (GetDiamondExchangeConfigResponse); rpc ListWalletTransactions(ListWalletTransactionsRequest) returns (ListWalletTransactionsResponse); rpc ListVipPackages(ListVipPackagesRequest) returns (ListVipPackagesResponse); diff --git a/api/proto/wallet/v1/wallet_grpc.pb.go b/api/proto/wallet/v1/wallet_grpc.pb.go index 4c67275f..8d1b7a14 100644 --- a/api/proto/wallet/v1/wallet_grpc.pb.go +++ b/api/proto/wallet/v1/wallet_grpc.pb.go @@ -249,6 +249,14 @@ const ( WalletService_CreateRechargeProduct_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateRechargeProduct" WalletService_UpdateRechargeProduct_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateRechargeProduct" WalletService_DeleteRechargeProduct_FullMethodName = "/hyapp.wallet.v1.WalletService/DeleteRechargeProduct" + WalletService_ListThirdPartyPaymentChannels_FullMethodName = "/hyapp.wallet.v1.WalletService/ListThirdPartyPaymentChannels" + WalletService_SetThirdPartyPaymentMethodStatus_FullMethodName = "/hyapp.wallet.v1.WalletService/SetThirdPartyPaymentMethodStatus" + WalletService_UpdateThirdPartyPaymentRate_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateThirdPartyPaymentRate" + WalletService_ListH5RechargeOptions_FullMethodName = "/hyapp.wallet.v1.WalletService/ListH5RechargeOptions" + WalletService_CreateH5RechargeOrder_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateH5RechargeOrder" + WalletService_SubmitH5RechargeTx_FullMethodName = "/hyapp.wallet.v1.WalletService/SubmitH5RechargeTx" + WalletService_GetH5RechargeOrder_FullMethodName = "/hyapp.wallet.v1.WalletService/GetH5RechargeOrder" + WalletService_HandleMifapayNotify_FullMethodName = "/hyapp.wallet.v1.WalletService/HandleMifapayNotify" WalletService_GetDiamondExchangeConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/GetDiamondExchangeConfig" WalletService_ListWalletTransactions_FullMethodName = "/hyapp.wallet.v1.WalletService/ListWalletTransactions" WalletService_ListVipPackages_FullMethodName = "/hyapp.wallet.v1.WalletService/ListVipPackages" @@ -326,6 +334,14 @@ type WalletServiceClient interface { CreateRechargeProduct(ctx context.Context, in *CreateRechargeProductRequest, opts ...grpc.CallOption) (*RechargeProductResponse, error) UpdateRechargeProduct(ctx context.Context, in *UpdateRechargeProductRequest, opts ...grpc.CallOption) (*RechargeProductResponse, error) DeleteRechargeProduct(ctx context.Context, in *DeleteRechargeProductRequest, opts ...grpc.CallOption) (*DeleteRechargeProductResponse, error) + ListThirdPartyPaymentChannels(ctx context.Context, in *ListThirdPartyPaymentChannelsRequest, opts ...grpc.CallOption) (*ListThirdPartyPaymentChannelsResponse, error) + SetThirdPartyPaymentMethodStatus(ctx context.Context, in *SetThirdPartyPaymentMethodStatusRequest, opts ...grpc.CallOption) (*ThirdPartyPaymentMethodResponse, error) + UpdateThirdPartyPaymentRate(ctx context.Context, in *UpdateThirdPartyPaymentRateRequest, opts ...grpc.CallOption) (*ThirdPartyPaymentMethodResponse, error) + ListH5RechargeOptions(ctx context.Context, in *H5RechargeOptionsRequest, opts ...grpc.CallOption) (*H5RechargeOptionsResponse, error) + CreateH5RechargeOrder(ctx context.Context, in *CreateH5RechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) + SubmitH5RechargeTx(ctx context.Context, in *SubmitH5RechargeTxRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) + GetH5RechargeOrder(ctx context.Context, in *GetH5RechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) + HandleMifapayNotify(ctx context.Context, in *HandleMifapayNotifyRequest, opts ...grpc.CallOption) (*HandleMifapayNotifyResponse, error) GetDiamondExchangeConfig(ctx context.Context, in *GetDiamondExchangeConfigRequest, opts ...grpc.CallOption) (*GetDiamondExchangeConfigResponse, error) ListWalletTransactions(ctx context.Context, in *ListWalletTransactionsRequest, opts ...grpc.CallOption) (*ListWalletTransactionsResponse, error) ListVipPackages(ctx context.Context, in *ListVipPackagesRequest, opts ...grpc.CallOption) (*ListVipPackagesResponse, error) @@ -837,6 +853,86 @@ func (c *walletServiceClient) DeleteRechargeProduct(ctx context.Context, in *Del return out, nil } +func (c *walletServiceClient) ListThirdPartyPaymentChannels(ctx context.Context, in *ListThirdPartyPaymentChannelsRequest, opts ...grpc.CallOption) (*ListThirdPartyPaymentChannelsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListThirdPartyPaymentChannelsResponse) + err := c.cc.Invoke(ctx, WalletService_ListThirdPartyPaymentChannels_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) SetThirdPartyPaymentMethodStatus(ctx context.Context, in *SetThirdPartyPaymentMethodStatusRequest, opts ...grpc.CallOption) (*ThirdPartyPaymentMethodResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ThirdPartyPaymentMethodResponse) + err := c.cc.Invoke(ctx, WalletService_SetThirdPartyPaymentMethodStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) UpdateThirdPartyPaymentRate(ctx context.Context, in *UpdateThirdPartyPaymentRateRequest, opts ...grpc.CallOption) (*ThirdPartyPaymentMethodResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ThirdPartyPaymentMethodResponse) + err := c.cc.Invoke(ctx, WalletService_UpdateThirdPartyPaymentRate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) ListH5RechargeOptions(ctx context.Context, in *H5RechargeOptionsRequest, opts ...grpc.CallOption) (*H5RechargeOptionsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(H5RechargeOptionsResponse) + err := c.cc.Invoke(ctx, WalletService_ListH5RechargeOptions_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) CreateH5RechargeOrder(ctx context.Context, in *CreateH5RechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(H5RechargeOrderResponse) + err := c.cc.Invoke(ctx, WalletService_CreateH5RechargeOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) SubmitH5RechargeTx(ctx context.Context, in *SubmitH5RechargeTxRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(H5RechargeOrderResponse) + err := c.cc.Invoke(ctx, WalletService_SubmitH5RechargeTx_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) GetH5RechargeOrder(ctx context.Context, in *GetH5RechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(H5RechargeOrderResponse) + err := c.cc.Invoke(ctx, WalletService_GetH5RechargeOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) HandleMifapayNotify(ctx context.Context, in *HandleMifapayNotifyRequest, opts ...grpc.CallOption) (*HandleMifapayNotifyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HandleMifapayNotifyResponse) + err := c.cc.Invoke(ctx, WalletService_HandleMifapayNotify_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) GetDiamondExchangeConfig(ctx context.Context, in *GetDiamondExchangeConfigRequest, opts ...grpc.CallOption) (*GetDiamondExchangeConfigResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetDiamondExchangeConfigResponse) @@ -1101,6 +1197,14 @@ type WalletServiceServer interface { CreateRechargeProduct(context.Context, *CreateRechargeProductRequest) (*RechargeProductResponse, error) UpdateRechargeProduct(context.Context, *UpdateRechargeProductRequest) (*RechargeProductResponse, error) DeleteRechargeProduct(context.Context, *DeleteRechargeProductRequest) (*DeleteRechargeProductResponse, error) + ListThirdPartyPaymentChannels(context.Context, *ListThirdPartyPaymentChannelsRequest) (*ListThirdPartyPaymentChannelsResponse, error) + SetThirdPartyPaymentMethodStatus(context.Context, *SetThirdPartyPaymentMethodStatusRequest) (*ThirdPartyPaymentMethodResponse, error) + UpdateThirdPartyPaymentRate(context.Context, *UpdateThirdPartyPaymentRateRequest) (*ThirdPartyPaymentMethodResponse, error) + ListH5RechargeOptions(context.Context, *H5RechargeOptionsRequest) (*H5RechargeOptionsResponse, error) + CreateH5RechargeOrder(context.Context, *CreateH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) + SubmitH5RechargeTx(context.Context, *SubmitH5RechargeTxRequest) (*H5RechargeOrderResponse, error) + GetH5RechargeOrder(context.Context, *GetH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) + HandleMifapayNotify(context.Context, *HandleMifapayNotifyRequest) (*HandleMifapayNotifyResponse, error) GetDiamondExchangeConfig(context.Context, *GetDiamondExchangeConfigRequest) (*GetDiamondExchangeConfigResponse, error) ListWalletTransactions(context.Context, *ListWalletTransactionsRequest) (*ListWalletTransactionsResponse, error) ListVipPackages(context.Context, *ListVipPackagesRequest) (*ListVipPackagesResponse, error) @@ -1276,6 +1380,30 @@ func (UnimplementedWalletServiceServer) UpdateRechargeProduct(context.Context, * func (UnimplementedWalletServiceServer) DeleteRechargeProduct(context.Context, *DeleteRechargeProductRequest) (*DeleteRechargeProductResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeleteRechargeProduct not implemented") } +func (UnimplementedWalletServiceServer) ListThirdPartyPaymentChannels(context.Context, *ListThirdPartyPaymentChannelsRequest) (*ListThirdPartyPaymentChannelsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListThirdPartyPaymentChannels not implemented") +} +func (UnimplementedWalletServiceServer) SetThirdPartyPaymentMethodStatus(context.Context, *SetThirdPartyPaymentMethodStatusRequest) (*ThirdPartyPaymentMethodResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetThirdPartyPaymentMethodStatus not implemented") +} +func (UnimplementedWalletServiceServer) UpdateThirdPartyPaymentRate(context.Context, *UpdateThirdPartyPaymentRateRequest) (*ThirdPartyPaymentMethodResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateThirdPartyPaymentRate not implemented") +} +func (UnimplementedWalletServiceServer) ListH5RechargeOptions(context.Context, *H5RechargeOptionsRequest) (*H5RechargeOptionsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListH5RechargeOptions not implemented") +} +func (UnimplementedWalletServiceServer) CreateH5RechargeOrder(context.Context, *CreateH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateH5RechargeOrder not implemented") +} +func (UnimplementedWalletServiceServer) SubmitH5RechargeTx(context.Context, *SubmitH5RechargeTxRequest) (*H5RechargeOrderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SubmitH5RechargeTx not implemented") +} +func (UnimplementedWalletServiceServer) GetH5RechargeOrder(context.Context, *GetH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetH5RechargeOrder not implemented") +} +func (UnimplementedWalletServiceServer) HandleMifapayNotify(context.Context, *HandleMifapayNotifyRequest) (*HandleMifapayNotifyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method HandleMifapayNotify not implemented") +} func (UnimplementedWalletServiceServer) GetDiamondExchangeConfig(context.Context, *GetDiamondExchangeConfigRequest) (*GetDiamondExchangeConfigResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetDiamondExchangeConfig not implemented") } @@ -2224,6 +2352,150 @@ func _WalletService_DeleteRechargeProduct_Handler(srv interface{}, ctx context.C return interceptor(ctx, in, info, handler) } +func _WalletService_ListThirdPartyPaymentChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListThirdPartyPaymentChannelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).ListThirdPartyPaymentChannels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_ListThirdPartyPaymentChannels_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).ListThirdPartyPaymentChannels(ctx, req.(*ListThirdPartyPaymentChannelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_SetThirdPartyPaymentMethodStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetThirdPartyPaymentMethodStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).SetThirdPartyPaymentMethodStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_SetThirdPartyPaymentMethodStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).SetThirdPartyPaymentMethodStatus(ctx, req.(*SetThirdPartyPaymentMethodStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_UpdateThirdPartyPaymentRate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateThirdPartyPaymentRateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).UpdateThirdPartyPaymentRate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_UpdateThirdPartyPaymentRate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).UpdateThirdPartyPaymentRate(ctx, req.(*UpdateThirdPartyPaymentRateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_ListH5RechargeOptions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(H5RechargeOptionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).ListH5RechargeOptions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_ListH5RechargeOptions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).ListH5RechargeOptions(ctx, req.(*H5RechargeOptionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_CreateH5RechargeOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateH5RechargeOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).CreateH5RechargeOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_CreateH5RechargeOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).CreateH5RechargeOrder(ctx, req.(*CreateH5RechargeOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_SubmitH5RechargeTx_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitH5RechargeTxRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).SubmitH5RechargeTx(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_SubmitH5RechargeTx_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).SubmitH5RechargeTx(ctx, req.(*SubmitH5RechargeTxRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_GetH5RechargeOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetH5RechargeOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GetH5RechargeOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GetH5RechargeOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GetH5RechargeOrder(ctx, req.(*GetH5RechargeOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_HandleMifapayNotify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HandleMifapayNotifyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).HandleMifapayNotify(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_HandleMifapayNotify_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).HandleMifapayNotify(ctx, req.(*HandleMifapayNotifyRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_GetDiamondExchangeConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetDiamondExchangeConfigRequest) if err := dec(in); err != nil { @@ -2801,6 +3073,38 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeleteRechargeProduct", Handler: _WalletService_DeleteRechargeProduct_Handler, }, + { + MethodName: "ListThirdPartyPaymentChannels", + Handler: _WalletService_ListThirdPartyPaymentChannels_Handler, + }, + { + MethodName: "SetThirdPartyPaymentMethodStatus", + Handler: _WalletService_SetThirdPartyPaymentMethodStatus_Handler, + }, + { + MethodName: "UpdateThirdPartyPaymentRate", + Handler: _WalletService_UpdateThirdPartyPaymentRate_Handler, + }, + { + MethodName: "ListH5RechargeOptions", + Handler: _WalletService_ListH5RechargeOptions_Handler, + }, + { + MethodName: "CreateH5RechargeOrder", + Handler: _WalletService_CreateH5RechargeOrder_Handler, + }, + { + MethodName: "SubmitH5RechargeTx", + Handler: _WalletService_SubmitH5RechargeTx_Handler, + }, + { + MethodName: "GetH5RechargeOrder", + Handler: _WalletService_GetH5RechargeOrder_Handler, + }, + { + MethodName: "HandleMifapayNotify", + Handler: _WalletService_HandleMifapayNotify_Handler, + }, { MethodName: "GetDiamondExchangeConfig", Handler: _WalletService_GetDiamondExchangeConfig_Handler, diff --git a/docs/game-service游戏结构设计.md b/docs/game-service游戏结构设计.md new file mode 100644 index 00000000..aba785e6 --- /dev/null +++ b/docs/game-service游戏结构设计.md @@ -0,0 +1,474 @@ +# game-service 游戏结构设计 + +本文定义 `game-service` 后续承载游戏的结构边界。当前仓库已经有第三方 H5 游戏接入能力:游戏列表、启动会话、厂商回调、钱包改账、统计 outbox 和等级事件 outbox 都在 `game-service` 内。本文补的是自研小游戏的结构,避免把骰子、猜拳这类玩法塞进第三方平台 adapter。 + +## 结论 + +- `game-service` 是游戏事实 owner。游戏列表、启动会话、匹配、局内状态、结算、补偿和游戏事件都归它管理。 +- `gateway-service` 只做 App HTTP 入口、鉴权、参数转换和 gRPC 转发,不保存游戏状态。 +- `wallet-service` 仍然是金币 owner。`game-service` 只能通过钱包 gRPC 扣款、派奖、退款,不能直接改余额。 +- `room-service` 不拥有游戏状态。房间只提供入口、房间上下文和必要的展示事件。 +- 第三方 H5 游戏继续走现有 `platform/catalog/session/callback/order` 结构。 +- 自研小游戏单独按 `game_code` 建领域模块、服务模块和表,不提前抽一个复杂通用引擎。 +- 只有当两个以上自研游戏出现相同匹配、计时、结算、IM 投递逻辑时,再把公共能力抽到 `internal/domain/minigame` 或 `internal/service/minigame`。 + +## 当前事实 + +现有入口: + +- App 游戏列表:`GET /api/v1/games` +- 最近游戏:`GET /api/v1/games/recent` +- 桥接脚本:`GET /api/v1/games/bridge-script` +- 启动游戏:`POST /api/v1/games/{game_id}/launch` +- 厂商回调:`POST /api/v1/game-callbacks/{platform_code}/{operation}` + +现有内部 RPC: + +- `GameAppService.ListGames` +- `GameAppService.ListRecentGames` +- `GameAppService.GetBridgeScript` +- `GameAppService.LaunchGame` +- `GameCallbackService.HandleCallback` +- `GameAdminService` 管理平台和目录 +- `GameCronService.ProcessLevelEventOutboxBatch` + +现有核心表: + +- `game_platforms`:第三方平台配置 +- `game_catalog`:App 看到的游戏目录 +- `game_display_rules`:按场景、区域、语言和客户端平台控制展示 +- `game_launch_sessions`:H5 启动会话 +- `game_orders`:第三方或游戏结算引起的钱包订单 +- `game_callback_logs`:第三方回调审计 +- `game_level_event_outbox`:游戏消耗投递 activity-service +- `game_outbox`:游戏订单事实投递 RocketMQ,给 statistics-service 等下游消费 + +## 分层结构 + +```text +gateway-service + internal/transport/http/gameapi + 只接 App HTTP,拿 user_id、request_id、app_code 后转 game-service gRPC + +api/proto/game/v1 + game.proto + 所有跨服务契约,新增 App 接口或后台接口先改这里 + +services/game-service + internal/domain/game + 已有通用游戏目录、平台、会话、订单、回调、outbox 领域对象 + + internal/service/game + 已有第三方 H5 平台接入和游戏列表、启动、回调主流程 + + internal/domain/ + 自研游戏纯规则,不能依赖数据库、gRPC、HTTP、钱包 client + + internal/service/ + 自研游戏用例层,负责匹配、状态推进、调用钱包、写 outbox + + internal/storage/mysql/_repository.go + 自研游戏持久化,行锁、幂等、状态转移都在这里收口 + + internal/transport/grpc + 把 game.proto 的 RPC 转成 service 调用,不写业务规则 +``` + +目录原则: + +- `internal/service/game` 保留给已有平台接入,不继续堆自研玩法。 +- 每个自研玩法用独立包,例如 `internal/domain/dice`、`internal/service/dice`。 +- 自研玩法可以复用 `game_catalog` 和 `game_display_rules` 做列表展示。 +- 自研玩法不走第三方 `PlatformAdapter`,也不走 `HandleCallback`。 +- 自研玩法的 HTTP 入口仍在 gateway,跨服务契约仍在 `api/proto/game/v1/game.proto`。 + +## 游戏类型 + +| 类型 | 例子 | 列表和启动 | 状态 owner | 结算方式 | +| ---------------- | ---------------------- | ------------------------------------------------- | --------------------------------------- | --------------------------------------------- | +| 第三方 H5 | 百顺、VivaGames、Reyou | 现有 `/games` 和 `/launch` | 第三方平台,game-service 保存会话和订单 | 厂商回调到 game-service,再调 wallet-service | +| 自研 H5 多人结算 | 骰子首版 | 复用 `/games` 和 `/launch`,局内操作走新增 HTTP | game-service | game-service 直接调 wallet-service | +| 自研实时对战 | 猜拳、后续双人骰子 | 复用 `/games` 和 `/launch`,匹配和出招走新增 HTTP | game-service | game-service 调 wallet-service,IM 只下发事件 | + +## 自研游戏统一链路 + +```mermaid +sequenceDiagram + participant App + participant Gateway as gateway-service + participant Game as game-service + participant Wallet as wallet-service + participant Notice as notice-service/Tencent IM + participant DB as hyapp_game + + App->>Gateway: POST /api/v1/games/{game_id}/launch + Gateway->>Game: LaunchGame + Game->>DB: create game_launch_sessions + Game-->>Gateway: launch_url/session_id + Gateway-->>App: H5 url + + App->>Gateway: POST /api/v1/games/dice/matches + Gateway->>Game: Create self-game match + Game->>DB: create match + first participant + App->>Gateway: POST /api/v1/games/dice/matches/{match_id}/join + Gateway->>Game: JoinDiceMatch + Game->>DB: lock match, insert participant + App->>Gateway: POST /api/v1/games/dice/matches/{match_id}/roll + Gateway->>Game: RollDiceMatch + Game->>DB: claim match for settling + Game->>Wallet: ApplyGameCoinChange debit/credit/refund + Wallet-->>Game: wallet_transaction_id/balance_after + Game->>DB: save dice points, orders, result and outbox + Game-->>Gateway: match/result state + Gateway-->>App: business response + Game-->>Notice: optional realtime outbox + Notice-->>App: optional IM custom event +``` + +关键约束: + +- 用户身份只信任 gateway 从 JWT 得到的 `user_id`。 +- `request_id` 只做链路追踪,不做业务幂等键。 +- 业务幂等键使用 `command_id`、`match_id`、`round_id`、`provider_order_id` 或唯一索引。 +- 所有时间字段使用 UTC epoch milliseconds,字段名统一 `*_ms`。 +- 金币金额必须是正数,扣款、派奖、退款用 `op_type` 表达方向。 +- 同一局的投注、派奖、退款必须能通过 `match_id/round_id/order_id` 追踪。 + +## 自研游戏表结构原则 + +不要用一个大表兼容所有玩法。每个玩法先建自己的事实表,公共 outbox 可以复用。 + +建议公共表: + +### game_realtime_outbox + +用于自研游戏的 IM 实时事件。不要复用 `game_outbox`,因为 `game_outbox` 是订单和统计事实,字段强依赖 `order_id/op_type/coin_amount`。 + +关键字段: + +- `app_code` +- `event_id` +- `event_type` +- `game_code` +- `match_id` +- `target_user_id` +- `payload_json` +- `status` +- `worker_id` +- `lock_until_ms` +- `retry_count` +- `next_retry_at_ms` +- `last_error` +- `created_at_ms` +- `updated_at_ms` + +推荐索引: + +- `PRIMARY KEY(app_code, event_id)` +- `idx_game_realtime_claim(app_code, status, next_retry_at_ms, created_at_ms, event_id)` +- `idx_game_realtime_target(app_code, target_user_id, created_at_ms)` + +## Redis 和 MySQL 边界 + +局信息可以同时出现在 MySQL 和 Redis,但不能是“双重事实”。 + +- MySQL 是唯一事实。参与人、扣款、派奖、结果、状态流转和补偿都必须能只靠 MySQL 恢复。 +- Redis 只保存热状态。可以存等待队列、房间当前局快照、倒计时、座位临时锁和防重复点击标记。 +- Redis 数据必须可丢。Redis 重启、Key 过期或缓存击穿时,服务必须从 MySQL 查回当前局。 +- 金币相关状态只以 MySQL 和 wallet-service 返回为准,不以 Redis 为准。 +- 关键状态推进用 MySQL 事务、唯一索引和行锁收口;Redis 锁只能减少并发压力,不能作为最终防线。 + +推荐 Key: + +- `game:dice:room:{app_code}:{room_id}:active_match`:房间当前局快照,TTL 按局时长加缓冲。 +- `game:dice:match:{app_code}:{match_id}`:局详情快照,TTL 按局时长加缓冲。 +- `game:dice:join_lock:{app_code}:{match_id}`:短 TTL 加入锁,用来减少并发抢座。 +- `game:dice:action:{app_code}:{match_id}:{user_id}:{action}`:短 TTL 动作防抖,最终幂等仍看 MySQL。 + +写入顺序: + +1. 先在 MySQL 事务里锁定 `game_dice_matches`。 +2. 创建/加入只写 `game_dice_participants` 和局状态。 +3. 开奖阶段用钱包幂等键写 `game_orders`,再保存点数、派奖和局结果。 +4. MySQL 事实提交后刷新 Redis 快照。 +5. Redis 刷新失败只记录日志和重试,不回滚已提交的 MySQL 事实。 + +## 骰子游戏首版结构 + +首版按多人可扩展结构设计。当前实现要求至少 2 人,默认最多 2 人,硬上限 6 人;3 人局只增加参与者行,不改表。H5 只负责展示和发起操作,随机、扣款、派奖和结果都由 `game-service` 完成。 + +当前骰子规则是单局胜场制。每个参与者每轮只扔 1 颗骰子,服务端把这个点数写入 `dice_points[0]`,点数高的一方获胜。同点不是终局,不退本金、不派奖,H5 展示点数 2 秒后继续调用 `roll`,同一个 `match_id` 会一直重投到分出胜负。 + +### 模块 + +```text +services/game-service/internal/domain/dice + rule.go 赔率、可选押注、结果计算 + match.go match 状态、参与者、局次、结果 + +services/game-service/internal/service/dice + service.go 创建局、加入局、扣款、开奖、派奖、查询 + +services/game-service/internal/storage/mysql/dice_repository.go + 创建局、参与人行锁读取、状态推进、订单关联、查询 +``` + +### 状态机 + +```mermaid +stateDiagram-v2 + [*] --> created + created --> joining + joining --> settling + created --> settling + settling --> payout_applying + payout_applying --> settled + settling --> failed + payout_applying --> failed +``` + +状态说明: + +- `created`:收到创建请求,已生成 `match_id`。 +- `joining`:等待参与者加入,人数达到 `min_players` 后可以开奖。 +- `settling`:已抢占结算权,正在扣押注、生成骰子结果。 +- `payout_applying`:骰子点数已经固定,正在派奖或退款;重试必须复用旧点数。 +- `settled`:局已结束,结果固定。 +- `failed`:扣款、派奖或退款失败,后续由补偿任务继续处理。 + +### 表 + +#### game_dice_matches + +关键字段: + +- `app_code` +- `match_id` +- `game_id` +- `platform_code` +- `provider_game_id` +- `room_id` +- `region_id` +- `min_players` +- `max_players` +- `current_players` +- `stake_coin` +- `round_no` +- `result` +- `status` +- `join_deadline_ms` +- `created_at_ms` +- `updated_at_ms` +- `settled_at_ms` + +索引: + +- `PRIMARY KEY(app_code, match_id)` +- `idx_game_dice_room_active(app_code, room_id, status, created_at_ms)` +- `idx_game_dice_status_time(app_code, status, updated_at_ms, match_id)` + +#### game_dice_participants + +一局一个参与者一行。2 人、3 人或 6 人只改变参与者数量,不改主表结构。 + +关键字段: + +- `app_code` +- `match_id` +- `user_id` +- `seat_no` +- `status` +- `stake_coin` +- `dice_points_json` +- `result` +- `payout_coin` +- `debit_order_id` +- `payout_order_id` +- `refund_order_id` +- `balance_after` +- `joined_at_ms` +- `updated_at_ms` + +索引: + +- `PRIMARY KEY(app_code, match_id, user_id)` +- `UNIQUE KEY uk_game_dice_seat(app_code, match_id, seat_no)` +- `idx_game_dice_participant_user(app_code, user_id, updated_at_ms)` +- `idx_game_dice_participant_status(app_code, match_id, status, seat_no)` + +#### game_dice_round_logs + +如果首版只有一局,可以先不建。若后续做多轮、连胜、多人奖池或房间内广播,再补小局日志表。 + +关键字段: + +- `app_code` +- `match_id` +- `round_no` +- `result_json` +- `created_at_ms` + +## 骰子接口草案 + +接口文档只保留地址、参数、返回值和相关 IM。 + +### 1. 创建骰子局 + +地址:`POST /api/v1/games/dice/matches` + +参数: + +- `game_id` +- `room_id` +- `stake_coin` +- `min_players` +- `max_players` + +返回值: + +- `match_id` +- `status` +- `min_players` +- `max_players` +- `current_players` +- `stake_coin` +- `participants` +- `server_time_ms` + +相关 IM: + +- 首版无 + +### 2. 加入骰子局 + +地址:`POST /api/v1/games/dice/matches/{match_id}/join` + +参数: + +- `match_id` + +返回值: + +- `match_id` +- `status` +- `min_players` +- `max_players` +- `current_players` +- `stake_coin` +- `participants` +- `server_time_ms` + +相关 IM: + +- 首版无 + +### 3. 查询骰子局 + +地址:`GET /api/v1/games/dice/matches/{match_id}` + +参数: + +- `match_id` + +返回值: + +- `match_id` +- `status` +- `min_players` +- `max_players` +- `current_players` +- `stake_coin` +- `result` +- `participants` +- `server_time_ms` + +相关 IM: + +- 首版无 + +### 4. 开奖 + +地址:`POST /api/v1/games/dice/matches/{match_id}/roll` + +参数: + +- `match_id` + +返回值: + +- `match_id` +- `status` +- `stake_coin` +- `result` +- `participants` +- `server_time_ms` + +相关 IM: + +- 首版无 +- 如果后续做房间公屏展示,再增加 `dice_match_settled` + +## 钱包和订单 + +自研游戏也要落 `game_orders`,不能直接只写玩法表。 + +骰子推荐订单: + +- 押注扣款:`op_type=debit`,`provider_order_id=dice:{match_id}:{user_id}:debit` +- 派奖加款:`op_type=credit`,`provider_order_id=dice:{match_id}:{user_id}:credit` +- 异常退款:`op_type=refund`,`provider_order_id=dice:{match_id}:{user_id}:refund` + +`wallet-service` 的 `CommandId`: + +- `game:dice:{match_id}:{user_id}:debit` +- `game:dice:{match_id}:{user_id}:credit` +- `game:dice:{match_id}:{user_id}:refund` + +扣款成功后: + +- 写 `game_orders` +- 写 `game_outbox`,供统计使用 +- 写 `game_level_event_outbox`,供 activity-service 游戏等级使用 + +派奖和退款是否进等级轨道由产品规则决定,默认只有 `debit` 进游戏等级。 + +## IM 事件边界 + +首版骰子不强依赖 IM。客户端发起 HTTP,HTTP 返回结果。 + +需要 IM 的场景: + +- 双人匹配成功 +- 倒计时开始 +- 双方状态变化 +- 结果揭晓 +- 对方离开或超时 + +IM 规则: + +- 客户端不能通过 IM 提交游戏动作。 +- 客户端动作必须走 HTTP 到 gateway,再转 game-service。 +- IM 只承载服务端事实事件。 +- IM payload 必须带 `event_id`、`game_code`、`match_id`、`server_time_ms`。 +- 客户端按 `event_id` 去重。 + +## 上线顺序 + +1. 先补 `api/proto/game/v1/game.proto` 的骰子 RPC。 +2. 生成 proto:`make proto`。 +3. 在 `gateway-service/internal/transport/http/gameapi` 加 HTTP handler。 +4. 在 `game-service` 加 `domain/dice`、`service/dice`、`storage/mysql/dice_repository.go`。 +5. 更新 `services/game-service/deploy/mysql/initdb/001_game_service.sql`。 +6. 更新 runtime `Migrate`,保证线上库自动补表和索引。 +7. 后台把骰子写入 `game_catalog` 和默认 `game_display_rules`。 +8. H5 `game/touzi` 通过启动页拿 `game_id/session_id`,局内操作走新增骰子接口。 +9. 跑测试:`make proto`、`go test ./services/game-service/... ./services/gateway-service/...`。 +10. 本地拉起:`docker compose config`,必要时 `docker compose up -d game-service gateway-service` 验证。 + +## 不做的事 + +- 不把骰子局状态放进 `room-service`。 +- 不让 H5 直接调 `wallet-service`。 +- 不把骰子动作放进第三方 `HandleCallback`。 +- 不把所有玩法硬塞进一个通用 `game_matches` 大表。 +- 不用 Redis 做唯一事实。Redis 后续只能做短期匹配队列、限流或缓存,MySQL 仍是恢复来源。 diff --git a/scripts/mysql/039_pretty_display_id_management.sql b/scripts/mysql/039_pretty_display_id_management.sql new file mode 100644 index 00000000..469bbdcc --- /dev/null +++ b/scripts/mysql/039_pretty_display_id_management.sql @@ -0,0 +1,92 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- 靓号管理把展示号从纯数字短号扩展为可发放的展示字符串,默认短号生成规则仍由业务代码保持纯数字。 +ALTER TABLE users + MODIFY COLUMN default_display_user_id VARCHAR(64) NOT NULL COMMENT '默认展示用户 ID', + MODIFY COLUMN current_display_user_id VARCHAR(64) NOT NULL COMMENT '当前展示用户 ID'; + +ALTER TABLE user_display_user_ids + MODIFY COLUMN display_user_id VARCHAR(64) NOT NULL COMMENT '展示用户 ID', + MODIFY COLUMN live_display_user_id VARCHAR(64) + GENERATED ALWAYS AS ( + CASE + WHEN status IN ('active', 'held', 'reserved', 'blocked') THEN display_user_id + ELSE NULL + END + ) STORED COMMENT '实时展示用户 ID'; + +ALTER TABLE pretty_display_user_id_leases + MODIFY COLUMN display_user_id VARCHAR(64) NOT NULL COMMENT '展示用户 ID', + MODIFY COLUMN active_display_user_id VARCHAR(64) + GENERATED ALWAYS AS ( + CASE WHEN status = 'active' THEN display_user_id ELSE NULL END + ) STORED COMMENT '启用展示用户 ID', + ADD COLUMN released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间,UTC epoch ms' AFTER expires_at_ms, + ADD COLUMN release_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '释放原因' AFTER released_at_ms; + +ALTER TABLE display_user_id_change_logs + MODIFY COLUMN old_display_user_id VARCHAR(64) NOT NULL COMMENT '原展示用户 ID', + MODIFY COLUMN new_display_user_id VARCHAR(64) NOT NULL COMMENT '新展示用户 ID'; + +CREATE TABLE IF NOT EXISTS pretty_display_id_pools ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + pool_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '靓号池 ID', + name VARCHAR(128) NOT NULL COMMENT '靓号池名称', + level_track VARCHAR(32) NOT NULL COMMENT '等级轨道:wealth/game/charm', + min_level INT NOT NULL COMMENT '最小等级,包含', + max_level INT NOT NULL COMMENT '最大等级,包含', + rule_type VARCHAR(32) NOT NULL COMMENT '生成规则', + rule_config_json JSON NULL COMMENT '生成规则配置', + status VARCHAR(32) NOT NULL COMMENT '业务状态:active/disabled', + sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重', + created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID', + updated_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '更新管理员 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + UNIQUE KEY uk_pretty_pool_range_rule (app_code, level_track, min_level, max_level, name), + KEY idx_pretty_pool_status (app_code, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池配置表'; + +CREATE TABLE IF NOT EXISTS pretty_display_ids ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + pretty_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '靓号记录 ID', + pool_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属靓号池 ID,后台发放为空', + source VARCHAR(32) NOT NULL DEFAULT 'pool' COMMENT '来源:pool/admin_grant', + display_user_id VARCHAR(64) NOT NULL COMMENT '靓号展示内容', + status VARCHAR(32) NOT NULL COMMENT '业务状态', + assigned_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '当前占用用户 ID', + assigned_lease_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '当前占用租约 ID', + assigned_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '占用时间,UTC epoch ms', + released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间,UTC epoch ms', + release_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '释放原因', + generated_batch_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '生成批次 ID', + created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + live_display_user_id VARCHAR(64) + GENERATED ALWAYS AS ( + CASE WHEN status IN ('available', 'assigned', 'disabled', 'reserved') THEN display_user_id ELSE NULL END + ) STORED COMMENT '未删除且未释放的靓号内容', + UNIQUE KEY uk_pretty_display_ids_live (app_code, live_display_user_id), + KEY idx_pretty_display_ids_pool_status (app_code, pool_id, status), + KEY idx_pretty_display_ids_assigned_user (app_code, assigned_user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池号码表'; + +CREATE TABLE IF NOT EXISTS pretty_display_id_generation_batches ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + batch_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '生成批次 ID', + pool_id VARCHAR(64) NOT NULL COMMENT '靓号池 ID', + rule_type VARCHAR(32) NOT NULL COMMENT '生成规则', + rule_config_json JSON NULL COMMENT '生成规则配置', + requested_count INT NOT NULL COMMENT '请求生成数量', + generated_count INT NOT NULL COMMENT '实际生成数量', + skipped_conflict_count INT NOT NULL COMMENT '冲突跳过数量', + status VARCHAR(32) NOT NULL COMMENT '批次状态', + operator_admin_id BIGINT NOT NULL COMMENT '操作管理员 ID', + request_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '链路请求 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + KEY idx_pretty_batch_pool (app_code, pool_id, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号批量生成记录表'; diff --git a/scripts/mysql/040_external_recharge_payments.sql b/scripts/mysql/040_external_recharge_payments.sql new file mode 100644 index 00000000..a26f4650 --- /dev/null +++ b/scripts/mysql/040_external_recharge_payments.sql @@ -0,0 +1,129 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- H5 外部充值:商品区分普通用户/币商,三方支付配置默认启用,外部订单独立于 Google payment_orders。 +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'wallet_recharge_products' AND COLUMN_NAME = 'audience_type') = 0, + 'ALTER TABLE wallet_recharge_products ADD COLUMN audience_type VARCHAR(32) NOT NULL DEFAULT ''normal'' COMMENT ''购买对象:normal 普通用户,coin_seller 币商'' AFTER description', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +CREATE TABLE IF NOT EXISTS third_party_payment_channels ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + provider_code VARCHAR(32) NOT NULL COMMENT '三方支付渠道编码', + provider_name VARCHAR(64) NOT NULL COMMENT '三方支付渠道名称', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '渠道状态', + sort_order INT 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, provider_code), + KEY idx_third_party_payment_channels_status (app_code, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='三方支付渠道表'; + +CREATE TABLE IF NOT EXISTS third_party_payment_methods ( + method_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '支付方式 ID', + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + provider_code VARCHAR(32) NOT NULL COMMENT '三方支付渠道编码', + country_code VARCHAR(8) NOT NULL COMMENT '国家码,GLOBAL 表示全球方式', + country_name VARCHAR(64) NOT NULL DEFAULT '' COMMENT '国家名称', + currency_code VARCHAR(8) NOT NULL COMMENT '下单币种', + pay_way VARCHAR(64) NOT NULL COMMENT 'MiFaPay payWay', + pay_type VARCHAR(64) NOT NULL COMMENT 'MiFaPay payType', + method_name VARCHAR(128) NOT NULL COMMENT '支付方式名称', + logo_url VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '支付方式图标', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '方式状态', + usd_to_currency_rate DECIMAL(20,8) NOT NULL DEFAULT 1 COMMENT 'USD 到支付币种汇率', + sort_order INT 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', + UNIQUE KEY uk_third_party_payment_methods_scope (app_code, provider_code, country_code, pay_way, pay_type), + KEY idx_third_party_payment_methods_h5 (app_code, provider_code, country_code, status, sort_order), + CONSTRAINT fk_third_party_payment_methods_channel FOREIGN KEY (app_code, provider_code) REFERENCES third_party_payment_channels(app_code, provider_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='三方支付国家方式配置表'; + +CREATE TABLE IF NOT EXISTS external_recharge_orders ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + order_id VARCHAR(96) NOT NULL COMMENT '外部充值订单号', + command_id VARCHAR(128) NOT NULL COMMENT 'H5 幂等命令 ID', + target_user_id BIGINT NOT NULL COMMENT '目标用户 ID', + target_region_id BIGINT NOT NULL COMMENT '目标区域 ID', + target_country_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '目标国家码', + audience_type VARCHAR(32) NOT NULL COMMENT '购买对象', + product_id BIGINT NOT NULL COMMENT '商品 ID', + product_code VARCHAR(128) NOT NULL COMMENT '商品编码快照', + product_name VARCHAR(128) NOT NULL COMMENT '商品名称快照', + coin_amount BIGINT NOT NULL COMMENT '到账金币数量', + usd_minor_amount BIGINT NOT NULL COMMENT 'USD cent 金额快照', + provider_code VARCHAR(32) NOT NULL COMMENT '支付渠道', + payment_method_id BIGINT NOT NULL DEFAULT 0 COMMENT '三方支付方式 ID', + country_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '支付方式国家码', + currency_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '支付币种', + provider_amount_minor BIGINT NOT NULL DEFAULT 0 COMMENT '三方支付币种最小单位金额', + pay_way VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'MiFaPay payWay', + pay_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'MiFaPay payType', + pay_url VARCHAR(2048) NOT NULL DEFAULT '' COMMENT '三方支付跳转 URL', + provider_order_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '三方支付订单号', + tx_hash VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'USDT tx hash', + receive_address VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'USDT 收款地址', + status VARCHAR(32) NOT NULL COMMENT '订单状态', + failure_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '失败原因', + wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '钱包交易 ID', + provider_payload JSON NULL 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, order_id), + UNIQUE KEY uk_external_recharge_orders_command (app_code, command_id), + KEY idx_external_recharge_orders_user_time (app_code, target_user_id, created_at_ms), + KEY idx_external_recharge_orders_provider_order (app_code, provider_code, provider_order_id), + KEY idx_external_recharge_orders_tx (app_code, provider_code, tx_hash) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='H5 外部充值订单表'; + +INSERT IGNORE INTO third_party_payment_channels (app_code, provider_code, provider_name, status, sort_order, created_at_ms, updated_at_ms) +VALUES ('lalu', 'mifapay', 'MiFaPay', 'active', 10, @now_ms, @now_ms); + +-- MiFaPay 是 H5 统一三方支付入口;真实商户号、商户标识、商户私钥、平台公钥只放运行环境配置。 +-- 国家和支付方式默认打开,运营可在后台逐项关闭;默认汇率为 1,避免新环境缺配置时 H5 完全不可见。 +INSERT IGNORE INTO third_party_payment_methods ( + app_code, provider_code, country_code, country_name, currency_code, pay_way, pay_type, + method_name, logo_url, status, usd_to_currency_rate, sort_order, created_at_ms, updated_at_ms +) VALUES + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BCA', 'BCA Bank', '', 'active', 1, 110, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BNI', 'BNI Bank', '', 'active', 1, 120, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BRI', 'BRI Bank', '', 'active', 1, 130, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'Mandiri', 'Mandiri Bank', '', 'active', 1, 140, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'Permata', 'Permata Bank', '', 'active', 1, 150, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'DANA', 'DANA', '', 'active', 1, 160, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'LinkAja', 'LinkAja', '', 'active', 1, 170, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'OVO', 'OVO', '', 'active', 1, 180, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'ShopeePay', 'ShopeePay', '', 'active', 1, 190, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'QR', 'QRIS', 'QRIS', '', 'active', 1, 200, @now_ms, @now_ms), + ('lalu', 'mifapay', 'VN', 'Vietnam', 'VND', 'BankTransfer', 'VN_BankTransfer', 'Vietnam Bank Transfer', '', 'active', 1, 210, @now_ms, @now_ms), + ('lalu', 'mifapay', 'VN', 'Vietnam', 'VND', 'QR', 'VietQR', 'VietQR', '', 'active', 1, 220, @now_ms, @now_ms), + ('lalu', 'mifapay', 'BH', 'Bahrain', 'BHD', 'Card', 'CreditCard', 'Bahrain Credit Card', '', 'active', 1, 310, @now_ms, @now_ms), + ('lalu', 'mifapay', 'QA', 'Qatar', 'QAR', 'Card', 'CreditCard', 'Qatar Credit Card', '', 'active', 1, 410, @now_ms, @now_ms), + ('lalu', 'mifapay', 'OM', 'Oman', 'OMR', 'Card', 'CreditCard', 'Oman Credit Card', '', 'active', 1, 510, @now_ms, @now_ms), + ('lalu', 'mifapay', 'AE', 'United Arab Emirates', 'AED', 'Card', 'CreditCard', 'UAE Credit Card', '', 'active', 1, 610, @now_ms, @now_ms), + ('lalu', 'mifapay', 'KW', 'Kuwait', 'KWD', 'Card', 'Knet', 'KNET', '', 'active', 1, 710, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Card', 'MADA', 'MADA', '', 'active', 1, 810, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Ewallet', 'ApplePay', 'ApplePay', '', 'active', 1, 820, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Ewallet', 'STCPay', 'STCPay', '', 'active', 1, 830, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'BHIM', 'BHIM', '', 'active', 1, 910, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'GooglePay', 'GooglePay', '', 'active', 1, 920, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'Mobikwik', 'Mobikwik', '', 'active', 1, 930, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'Paytm', 'Paytm', '', 'active', 1, 940, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'PhonePe', 'PhonePe', '', 'active', 1, 950, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'UPI', 'UPI', '', 'active', 1, 960, @now_ms, @now_ms), + ('lalu', 'mifapay', 'BD', 'Bangladesh', 'BDT', 'Ewallet', 'bKash', 'bKash', '', 'active', 1, 1010, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PK', 'Pakistan', 'PKR', 'Ewallet', 'Easypaisa', 'Easypaisa', '', 'active', 1, 1110, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PK', 'Pakistan', 'PKR', 'Ewallet', 'JazzCash', 'JazzCash', '', 'active', 1, 1120, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'FawryPay', 'FawryPay', '', 'active', 1, 1210, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Meeza', 'Meeza', '', 'active', 1, 1220, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Orange', 'Orange', '', 'active', 1, 1230, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Vodafone', 'Vodafone', '', 'active', 1, 1240, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'Ewallet', 'Gcash', 'GCash', '', 'active', 1, 1310, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'Ewallet', 'PayMaya', 'PayMaya', '', 'active', 1, 1320, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'QR', 'QRPH', 'QRPH', '', 'active', 1, 1330, @now_ms, @now_ms); diff --git a/scripts/mysql/041_first_recharge_google_product_tiers.sql b/scripts/mysql/041_first_recharge_google_product_tiers.sql new file mode 100644 index 00000000..28a130ff --- /dev/null +++ b/scripts/mysql/041_first_recharge_google_product_tiers.sql @@ -0,0 +1,93 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 首充奖励从“首笔金币区间”改为“Google 商品档位”:一个用户每个商品档位只能领取一次。 + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_tiers' AND COLUMN_NAME = 'usd_minor_amount') = 0, + 'ALTER TABLE first_recharge_reward_tiers ADD COLUMN usd_minor_amount BIGINT NOT NULL DEFAULT 0 COMMENT ''Google 商品美元美分档位'' AFTER max_coin_amount', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_tiers' AND COLUMN_NAME = 'google_product_id') = 0, + 'ALTER TABLE first_recharge_reward_tiers ADD COLUMN google_product_id VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''Google Play 商品 ID'' AFTER usd_minor_amount', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_tiers' AND COLUMN_NAME = 'discount_percent') = 0, + 'ALTER TABLE first_recharge_reward_tiers ADD COLUMN discount_percent INT NOT NULL DEFAULT 0 COMMENT ''展示优惠百分比,不参与结算'' AFTER google_product_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +UPDATE first_recharge_reward_tiers +SET google_product_id = CONCAT('legacy_', tier_id) +WHERE google_product_id = ''; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_tiers' AND INDEX_NAME = 'uk_first_recharge_reward_google_product') = 0, + 'ALTER TABLE first_recharge_reward_tiers ADD UNIQUE KEY uk_first_recharge_reward_google_product (app_code, google_product_id)', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_claims' AND COLUMN_NAME = 'tier_usd_minor_amount') = 0, + 'ALTER TABLE first_recharge_reward_claims ADD COLUMN tier_usd_minor_amount BIGINT NOT NULL DEFAULT 0 COMMENT ''命中档位美元美分'' AFTER resource_group_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_claims' AND COLUMN_NAME = 'google_product_id') = 0, + 'ALTER TABLE first_recharge_reward_claims ADD COLUMN google_product_id VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''命中 Google Play 商品 ID'' AFTER tier_usd_minor_amount', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_claims' AND COLUMN_NAME = 'recharge_usd_minor') = 0, + 'ALTER TABLE first_recharge_reward_claims ADD COLUMN recharge_usd_minor BIGINT NOT NULL DEFAULT 0 COMMENT ''本次充值美元美分'' AFTER recharge_coin_amount', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_claims' AND INDEX_NAME = 'uk_first_recharge_reward_user_once') > 0, + 'ALTER TABLE first_recharge_reward_claims DROP INDEX uk_first_recharge_reward_user_once', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'first_recharge_reward_claims' AND INDEX_NAME = 'uk_first_recharge_reward_user_tier') = 0, + 'ALTER TABLE first_recharge_reward_claims ADD UNIQUE KEY uk_first_recharge_reward_user_tier (app_code, user_id, tier_id)', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SELECT + COUNT(*) AS first_recharge_google_tier_count +FROM first_recharge_reward_tiers +WHERE google_product_id <> ''; diff --git a/scripts/mysql/042_mifapay_payment_methods.sql b/scripts/mysql/042_mifapay_payment_methods.sql new file mode 100644 index 00000000..4c366999 --- /dev/null +++ b/scripts/mysql/042_mifapay_payment_methods.sql @@ -0,0 +1,107 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- MiFaPay 是 H5 统一三方支付入口;迁移只写国家/支付方式清单,真实商户号、商户标识、商户私钥、平台公钥必须放运行环境配置。 +-- 所有截图内方式默认打开;已有正数汇率继续保留,避免迁移覆盖运营已经维护过的 USD->本币汇率。 +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +INSERT INTO third_party_payment_channels ( + app_code, provider_code, provider_name, status, sort_order, created_at_ms, updated_at_ms +) VALUES ('lalu', 'mifapay', 'MiFaPay', 'active', 10, @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE + provider_name = VALUES(provider_name), + status = VALUES(status), + sort_order = VALUES(sort_order), + updated_at_ms = VALUES(updated_at_ms); + +-- 存量库可能已经写入过早期测试方式;先关闭不在 MiFaPay 当前支持清单内的方式,H5 才不会展示未开通入口。 +UPDATE third_party_payment_methods +SET status = 'disabled', updated_at_ms = @now_ms +WHERE app_code = 'lalu' + AND provider_code = 'mifapay' + AND NOT ( + (country_code = 'ID' AND pay_way = 'BankTransfer' AND pay_type = 'BCA') OR + (country_code = 'ID' AND pay_way = 'BankTransfer' AND pay_type = 'BNI') OR + (country_code = 'ID' AND pay_way = 'BankTransfer' AND pay_type = 'BRI') OR + (country_code = 'ID' AND pay_way = 'BankTransfer' AND pay_type = 'Mandiri') OR + (country_code = 'ID' AND pay_way = 'BankTransfer' AND pay_type = 'Permata') OR + (country_code = 'ID' AND pay_way = 'Ewallet' AND pay_type = 'DANA') OR + (country_code = 'ID' AND pay_way = 'Ewallet' AND pay_type = 'LinkAja') OR + (country_code = 'ID' AND pay_way = 'Ewallet' AND pay_type = 'OVO') OR + (country_code = 'ID' AND pay_way = 'Ewallet' AND pay_type = 'ShopeePay') OR + (country_code = 'ID' AND pay_way = 'QR' AND pay_type = 'QRIS') OR + (country_code = 'VN' AND pay_way = 'BankTransfer' AND pay_type = 'VN_BankTransfer') OR + (country_code = 'VN' AND pay_way = 'QR' AND pay_type = 'VietQR') OR + (country_code = 'BH' AND pay_way = 'Card' AND pay_type = 'CreditCard') OR + (country_code = 'QA' AND pay_way = 'Card' AND pay_type = 'CreditCard') OR + (country_code = 'OM' AND pay_way = 'Card' AND pay_type = 'CreditCard') OR + (country_code = 'AE' AND pay_way = 'Card' AND pay_type = 'CreditCard') OR + (country_code = 'KW' AND pay_way = 'Card' AND pay_type = 'Knet') OR + (country_code = 'SA' AND pay_way = 'Card' AND pay_type = 'MADA') OR + (country_code = 'SA' AND pay_way = 'Ewallet' AND pay_type = 'ApplePay') OR + (country_code = 'SA' AND pay_way = 'Ewallet' AND pay_type = 'STCPay') OR + (country_code = 'IN' AND pay_way = 'Ewallet' AND pay_type = 'BHIM') OR + (country_code = 'IN' AND pay_way = 'Ewallet' AND pay_type = 'GooglePay') OR + (country_code = 'IN' AND pay_way = 'Ewallet' AND pay_type = 'Mobikwik') OR + (country_code = 'IN' AND pay_way = 'Ewallet' AND pay_type = 'Paytm') OR + (country_code = 'IN' AND pay_way = 'Ewallet' AND pay_type = 'PhonePe') OR + (country_code = 'IN' AND pay_way = 'Ewallet' AND pay_type = 'UPI') OR + (country_code = 'BD' AND pay_way = 'Ewallet' AND pay_type = 'bKash') OR + (country_code = 'PK' AND pay_way = 'Ewallet' AND pay_type = 'Easypaisa') OR + (country_code = 'PK' AND pay_way = 'Ewallet' AND pay_type = 'JazzCash') OR + (country_code = 'EG' AND pay_way = 'Ewallet' AND pay_type = 'FawryPay') OR + (country_code = 'EG' AND pay_way = 'Ewallet' AND pay_type = 'Meeza') OR + (country_code = 'EG' AND pay_way = 'Ewallet' AND pay_type = 'Orange') OR + (country_code = 'EG' AND pay_way = 'Ewallet' AND pay_type = 'Vodafone') OR + (country_code = 'PH' AND pay_way = 'Ewallet' AND pay_type = 'Gcash') OR + (country_code = 'PH' AND pay_way = 'Ewallet' AND pay_type = 'PayMaya') OR + (country_code = 'PH' AND pay_way = 'QR' AND pay_type = 'QRPH') + ); + +INSERT INTO third_party_payment_methods ( + app_code, provider_code, country_code, country_name, currency_code, pay_way, pay_type, + method_name, logo_url, status, usd_to_currency_rate, sort_order, created_at_ms, updated_at_ms +) VALUES + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BCA', 'BCA Bank', '', 'active', 1, 110, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BNI', 'BNI Bank', '', 'active', 1, 120, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BRI', 'BRI Bank', '', 'active', 1, 130, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'Mandiri', 'Mandiri Bank', '', 'active', 1, 140, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'Permata', 'Permata Bank', '', 'active', 1, 150, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'DANA', 'DANA', '', 'active', 1, 160, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'LinkAja', 'LinkAja', '', 'active', 1, 170, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'OVO', 'OVO', '', 'active', 1, 180, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'ShopeePay', 'ShopeePay', '', 'active', 1, 190, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'QR', 'QRIS', 'QRIS', '', 'active', 1, 200, @now_ms, @now_ms), + ('lalu', 'mifapay', 'VN', 'Vietnam', 'VND', 'BankTransfer', 'VN_BankTransfer', 'Vietnam Bank Transfer', '', 'active', 1, 210, @now_ms, @now_ms), + ('lalu', 'mifapay', 'VN', 'Vietnam', 'VND', 'QR', 'VietQR', 'VietQR', '', 'active', 1, 220, @now_ms, @now_ms), + ('lalu', 'mifapay', 'BH', 'Bahrain', 'BHD', 'Card', 'CreditCard', 'Bahrain Credit Card', '', 'active', 1, 310, @now_ms, @now_ms), + ('lalu', 'mifapay', 'QA', 'Qatar', 'QAR', 'Card', 'CreditCard', 'Qatar Credit Card', '', 'active', 1, 410, @now_ms, @now_ms), + ('lalu', 'mifapay', 'OM', 'Oman', 'OMR', 'Card', 'CreditCard', 'Oman Credit Card', '', 'active', 1, 510, @now_ms, @now_ms), + ('lalu', 'mifapay', 'AE', 'United Arab Emirates', 'AED', 'Card', 'CreditCard', 'UAE Credit Card', '', 'active', 1, 610, @now_ms, @now_ms), + ('lalu', 'mifapay', 'KW', 'Kuwait', 'KWD', 'Card', 'Knet', 'KNET', '', 'active', 1, 710, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Card', 'MADA', 'MADA', '', 'active', 1, 810, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Ewallet', 'ApplePay', 'ApplePay', '', 'active', 1, 820, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Ewallet', 'STCPay', 'STCPay', '', 'active', 1, 830, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'BHIM', 'BHIM', '', 'active', 1, 910, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'GooglePay', 'GooglePay', '', 'active', 1, 920, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'Mobikwik', 'Mobikwik', '', 'active', 1, 930, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'Paytm', 'Paytm', '', 'active', 1, 940, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'PhonePe', 'PhonePe', '', 'active', 1, 950, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'UPI', 'UPI', '', 'active', 1, 960, @now_ms, @now_ms), + ('lalu', 'mifapay', 'BD', 'Bangladesh', 'BDT', 'Ewallet', 'bKash', 'bKash', '', 'active', 1, 1010, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PK', 'Pakistan', 'PKR', 'Ewallet', 'Easypaisa', 'Easypaisa', '', 'active', 1, 1110, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PK', 'Pakistan', 'PKR', 'Ewallet', 'JazzCash', 'JazzCash', '', 'active', 1, 1120, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'FawryPay', 'FawryPay', '', 'active', 1, 1210, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Meeza', 'Meeza', '', 'active', 1, 1220, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Orange', 'Orange', '', 'active', 1, 1230, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Vodafone', 'Vodafone', '', 'active', 1, 1240, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'Ewallet', 'Gcash', 'GCash', '', 'active', 1, 1310, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'Ewallet', 'PayMaya', 'PayMaya', '', 'active', 1, 1320, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'QR', 'QRPH', 'QRPH', '', 'active', 1, 1330, @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE + country_name = VALUES(country_name), + currency_code = VALUES(currency_code), + method_name = VALUES(method_name), + status = VALUES(status), + usd_to_currency_rate = IF(usd_to_currency_rate > 0, usd_to_currency_rate, VALUES(usd_to_currency_rate)), + sort_order = VALUES(sort_order), + updated_at_ms = VALUES(updated_at_ms); diff --git a/server/admin/cmd/server/main.go b/server/admin/cmd/server/main.go index 25f3bbfd..20dba1c6 100644 --- a/server/admin/cmd/server/main.go +++ b/server/admin/cmd/server/main.go @@ -48,6 +48,7 @@ import ( luckygiftmodule "hyapp-admin-server/internal/modules/luckygift" menumodule "hyapp-admin-server/internal/modules/menu" paymentmodule "hyapp-admin-server/internal/modules/payment" + prettyidmodule "hyapp-admin-server/internal/modules/prettyid" rbacmodule "hyapp-admin-server/internal/modules/rbac" redpacketmodule "hyapp-admin-server/internal/modules/redpacket" regionblockmodule "hyapp-admin-server/internal/modules/regionblock" @@ -222,21 +223,26 @@ func main() { objectUploader = uploader } handlers := router.Handlers{ - Audit: auditHandler, - Auth: authmodule.New(store, auth, auditHandler, cfg), - AdminUser: adminusermodule.New(store, cfg, auditHandler), - AchievementConfig: achievementconfigmodule.New(activityclient.NewGRPC(activityConn), auditHandler), - AppConfig: appconfigmodule.New(store, auditHandler), - AppRegistry: appregistrymodule.New(userDB), - AppUser: appusermodule.New(userclient.NewGRPC(userConn), activityclient.NewGRPC(activityConn), sqlDB, userDB, walletDB, cfg, auditHandler), - CoinLedger: coinledgermodule.New(userDB, walletDB, sqlDB, walletclient.NewGRPC(walletConn), auditHandler), - CountryRegion: countryregionmodule.New(userclient.NewGRPC(userConn), userDB, cfg, auditHandler), - CPRelation: cprelationmodule.New(userDB, auditHandler), - CumulativeRecharge: cumulativerechargerewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), - DailyTask: dailytaskmodule.New(activityclient.NewGRPC(activityConn), auditHandler), - Dashboard: dashboardmodule.New(store, cfg), - FirstRechargeReward: firstrechargerewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), - Game: gamemanagementmodule.New(gameclient.NewGRPC(gameConn), auditHandler), + Audit: auditHandler, + Auth: authmodule.New(store, auth, auditHandler, cfg), + AdminUser: adminusermodule.New(store, cfg, auditHandler), + AchievementConfig: achievementconfigmodule.New(activityclient.NewGRPC(activityConn), auditHandler), + AppConfig: appconfigmodule.New(store, auditHandler), + AppRegistry: appregistrymodule.New(userDB), + AppUser: appusermodule.New(userclient.NewGRPC(userConn), activityclient.NewGRPC(activityConn), sqlDB, userDB, walletDB, cfg, auditHandler), + CoinLedger: coinledgermodule.New(userDB, walletDB, sqlDB, walletclient.NewGRPC(walletConn), auditHandler), + CountryRegion: countryregionmodule.New(userclient.NewGRPC(userConn), userDB, cfg, auditHandler), + CPRelation: cprelationmodule.New(userDB, auditHandler), + CumulativeRecharge: cumulativerechargerewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), + DailyTask: dailytaskmodule.New(activityclient.NewGRPC(activityConn), auditHandler), + Dashboard: dashboardmodule.New(store, cfg), + FirstRechargeReward: firstrechargerewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), + Game: gamemanagementmodule.New( + gameclient.NewGRPC(gameConn), + userclient.NewGRPC(userConn), + auditHandler, + gamemanagementmodule.WithRobotAvatarUploader(objectUploader, cfg.TencentCOS.ObjectPrefix), + ), GiftDiamond: giftdiamondmodule.New(walletDB, auditHandler), Health: healthmodule.New(store, redisClient, jobStatus), HostAgencyPolicy: hostagencypolicymodule.New(store, walletDB, auditHandler), @@ -247,6 +253,7 @@ func main() { LuckyGift: luckygiftmodule.New(activityclient.NewGRPC(activityConn), cfg.ActivityService.RequestTimeout, auditHandler), Menu: menumodule.New(store, auditHandler), Payment: paymentmodule.New(walletclient.NewGRPC(walletConn), userDB, auditHandler), + PrettyID: prettyidmodule.New(userclient.NewGRPC(userConn), auditHandler), RBAC: rbacmodule.New(store, auditHandler), RedPacket: redpacketmodule.New(walletclient.NewGRPC(walletConn), auditHandler), Report: reportmodule.New(userDB, roomClient), diff --git a/server/admin/docs/靓号管理服务端技术文档.md b/server/admin/docs/靓号管理服务端技术文档.md new file mode 100644 index 00000000..c532e9b8 --- /dev/null +++ b/server/admin/docs/靓号管理服务端技术文档.md @@ -0,0 +1,631 @@ +# 靓号管理服务端技术文档 + +## 当前结论 + +靓号管理不能只做成后台页面配置。当前仓库里 `user-service` 已经是 `user_id`、`display_user_id` 和临时靓号租约的 owner,`server/admin` 只能做后台 HTTP 入口、权限、审计和管理适配,App 入口继续由 `gateway-service` 暴露。 + +本需求新增的是“靓号池申请 + 后台发放靓号 + 资料返回靓号字段”,要在现有 `display_user_id` 体系上扩展,不重新引入一套用户 ID。 + +## 已有事实 + +- `users.user_id` 是内部不可变用户主键。 +- `users.current_display_user_id` 是当前对外展示短号。 +- `users.current_display_user_id_kind` 当前支持 `default` / `pretty`。 +- `users.current_display_user_id_expires_at_ms` 为 `0` 或空时表示当前展示号不自动过期。 +- `user_display_user_ids` 已保存默认短号和靓号的实时归属。 +- `pretty_display_user_id_leases` 已保存靓号覆盖关系。 +- 当前默认短号和旧自助靓号校验是 5 到 11 位、不能 0 开头的纯数字。 +- 本需求的靓号池批量生成默认仍可使用数字规则;后台发放靓号不能复用纯数字限制,需要支持阿拉伯语、英文和数字组合。 +- 当前 App 端已有 `/api/v1/users/me/display-id/pretty/apply`,但参数是 `pretty_display_user_id + lease_duration_ms + payment_receipt_id`,不是靓号池申请要的 `pretty_id`。 + +## 目标 + +- 后台在“用户管理”下新增二级菜单“靓号管理”。 +- 后台可以配置靓号池的等级轨道、等级区间、生成规则和生成数量。 +- 后台批量生成靓号时,不能和当前库里的 `users.user_id`、默认短号、当前短号、历史有效短号、已有靓号池号码冲突。 +- 每个靓号池号码都有一个稳定 `pretty_id`,App 靓号池申请时只传 `pretty_id`。 +- 靓号池申请默认长期持有;用户已有靓号时,新靓号直接覆盖旧靓号,旧靓号释放。 +- 后台发放靓号可以直接输入靓号内容,可以填写有效期;不填有效期时默认长期持有。 +- 后台发放靓号内容不限定纯数字,需要支持阿拉伯语、英文和数字组合。 +- App 用户申请靓号池号码时,服务端根据靓号池配置校验用户是否在对应等级区间内,在区间内才允许占用。 +- 用户拥有靓号后,个人信息、个人资料卡、房间个人资料卡、房间详情等资料返回都带当前靓号字段。 + +## 非目标 + +- 不把靓号作为内部主键使用,所有业务关系仍然使用 `user_id`。 +- 不让 `room-service` 保存靓号状态。房间只保存房间状态和用户 `user_id`,展示资料由 gateway 汇总用户资料。 +- 不让 `server/admin` 直接 import app 后端 `services/*` 或 `pkg/*`。 +- 首版不做等级下降后的自动回收。用户申请时校验等级区间;后续如果需要降级回收,再新增独立回收规则和定时任务。 +- 后台发放不需要等级校验,管理员只受后台权限和审计约束。 +- 首版不对外推送 IM。靓号申请成功后,客户端以接口返回和下次资料刷新为准。 + +## 服务边界 + +| 模块 | 责任 | +| --- | --- | +| `user-service` | 靓号池、靓号项、后台发放、替换释放事务、短号唯一性、当前展示号覆盖、资料字段 | +| `activity-service` | 提供用户等级展示投影,至少能按 `user_id` 返回 `wealth/game/charm` 当前等级 | +| `gateway-service` | App HTTP 入口,按 token 用户申请靓号,返回统一 envelope | +| `server/admin` | 后台 HTTP API、菜单、权限、审计,调用 `hyapp.local/api` protobuf client | +| `hyapp-admin-platform` | 用户管理下新增“靓号管理”页面,消费后台 API | + +## 等级区间规则 + +当前等级体系有三条轨道: + +| track | 含义 | +| --- | --- | +| `wealth` | 财富等级 | +| `game` | 游戏等级 | +| `charm` | 魅力等级 | + +靓号池配置必须保存 `level_track`,不能在服务端猜默认轨道。后台页面可以默认选中 `wealth`,但请求和数据库必须明确保存轨道。 + +靓号池申请时: + +1. 根据 `pretty_id` 锁定靓号项和所属靓号池。 +2. 读取用户当前等级,使用池子的 `level_track`。 +3. 判断 `min_level <= current_level <= max_level`。 +4. 通过后进入靓号占用事务。 + +## 数据库设计 + +新增表归属 `hyapp_user`,因为靓号和短号唯一性属于 `user-service`。 + +### 现有短号表扩展 + +后台发放的靓号不一定是数字,现有短号相关字段需要从数字短号能力扩展成“展示号字符串”能力: + +```sql +ALTER TABLE users + MODIFY COLUMN current_display_user_id VARCHAR(64) NOT NULL, + MODIFY COLUMN default_display_user_id VARCHAR(64) NOT NULL; + +ALTER TABLE user_display_user_ids + MODIFY COLUMN display_user_id VARCHAR(64) NOT NULL, + MODIFY COLUMN live_display_user_id VARCHAR(64) + GENERATED ALWAYS AS ( + CASE + WHEN status IN ('active', 'held', 'reserved', 'blocked') THEN display_user_id + ELSE NULL + END + ) STORED; + +ALTER TABLE pretty_display_user_id_leases + MODIFY COLUMN display_user_id VARCHAR(64) NOT NULL, + MODIFY COLUMN active_display_user_id VARCHAR(64) + GENERATED ALWAYS AS ( + CASE WHEN status = 'active' THEN display_user_id ELSE NULL END + ) STORED, + ADD COLUMN released_at_ms BIGINT NOT NULL DEFAULT 0 AFTER expires_at_ms, + ADD COLUMN release_reason VARCHAR(64) NOT NULL DEFAULT '' AFTER released_at_ms; + +ALTER TABLE display_user_id_change_logs + MODIFY COLUMN old_display_user_id VARCHAR(64) NOT NULL, + MODIFY COLUMN new_display_user_id VARCHAR(64) NOT NULL; +``` + +规则: + +- 默认短号仍然按现有数字规则生成。 +- 靓号池批量生成默认仍然按数字规则生成。 +- 后台发放靓号允许 1 到 64 个字符。 +- 后台发放靓号允许 Unicode 字母、Unicode 数字和组合标记,用于支持阿拉伯语、英文和数字。 +- 后台发放靓号不允许空白字符、控制字符、emoji 和标点符号。 +- 唯一性仍按 `app_code + display_user_id` 判断;如果产品要求大小写敏感,相关唯一列需要改成 `utf8mb4_bin` 或在写入前做明确大小写归一。 + +### `pretty_display_id_pools` + +保存后台配置的等级区间和默认生成规则。 + +```sql +CREATE TABLE pretty_display_id_pools ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', + pool_id VARCHAR(64) NOT NULL PRIMARY KEY, + name VARCHAR(128) NOT NULL, + level_track VARCHAR(32) NOT NULL, + min_level INT NOT NULL, + max_level INT NOT NULL, + rule_type VARCHAR(32) NOT NULL, + rule_config_json JSON NULL, + status VARCHAR(32) NOT NULL, + sort_order INT NOT NULL DEFAULT 0, + created_by_admin_id BIGINT NOT NULL DEFAULT 0, + updated_by_admin_id BIGINT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + UNIQUE KEY uk_pretty_pool_range_rule (app_code, level_track, min_level, max_level, name), + KEY idx_pretty_pool_status (app_code, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池配置表'; +``` + +字段说明: + +| 字段 | 说明 | +| --- | --- | +| `pool_id` | 靓号池 ID | +| `level_track` | `wealth` / `game` / `charm` | +| `min_level` | 可申请最小等级,包含 | +| `max_level` | 可申请最大等级,包含 | +| `rule_type` | 生成规则,例如 `aabbcc` | +| `rule_config_json` | 长度、号段、排除数字等扩展配置 | +| `status` | `active` / `disabled` | + +约束: + +- `min_level` 不能大于 `max_level`。 +- `level_track` 必须是已有等级轨道。 +- `active` 池才允许 App 用户申请。 + +### `pretty_display_ids` + +保存每一个可申请靓号。每个靓号都有自己的 `pretty_id`。 + +```sql +CREATE TABLE pretty_display_ids ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', + pretty_id VARCHAR(64) NOT NULL PRIMARY KEY, + pool_id VARCHAR(64) NOT NULL DEFAULT '', + source VARCHAR(32) NOT NULL DEFAULT 'pool', + display_user_id VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + assigned_user_id BIGINT NOT NULL DEFAULT 0, + assigned_lease_id VARCHAR(64) NOT NULL DEFAULT '', + assigned_at_ms BIGINT NOT NULL DEFAULT 0, + released_at_ms BIGINT NOT NULL DEFAULT 0, + release_reason VARCHAR(64) NOT NULL DEFAULT '', + generated_batch_id VARCHAR(64) NOT NULL DEFAULT '', + created_by_admin_id BIGINT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + live_display_user_id VARCHAR(64) + GENERATED ALWAYS AS ( + CASE WHEN status IN ('available', 'assigned', 'disabled', 'reserved') THEN display_user_id ELSE NULL END + ) STORED, + UNIQUE KEY uk_pretty_display_ids_live (app_code, live_display_user_id), + KEY idx_pretty_display_ids_pool_status (app_code, pool_id, status), + KEY idx_pretty_display_ids_assigned_user (app_code, assigned_user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池号码表'; +``` + +状态说明: + +| status | 说明 | +| --- | --- | +| `available` | 可申请 | +| `assigned` | 已被用户占用 | +| `disabled` | 后台下架,不允许申请 | +| `reserved` | 系统预留,不允许普通用户申请 | +| `released` | 曾经发放或占用,已释放,不参与可申请列表 | +| `deleted` | 软删除,只保留审计 | + +`source` 说明: + +| source | 说明 | +| --- | --- | +| `pool` | 靓号池生成,释放后可以回到 `available` | +| `admin_grant` | 后台直接发放,释放后默认进入 `released` | + +### `pretty_display_id_generation_batches` + +保存批量生成记录,方便后台展示和排查。 + +```sql +CREATE TABLE pretty_display_id_generation_batches ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu', + batch_id VARCHAR(64) NOT NULL PRIMARY KEY, + pool_id VARCHAR(64) NOT NULL, + rule_type VARCHAR(32) NOT NULL, + rule_config_json JSON NULL, + requested_count INT NOT NULL, + generated_count INT NOT NULL, + skipped_conflict_count INT NOT NULL, + status VARCHAR(32) NOT NULL, + operator_admin_id BIGINT NOT NULL, + request_id VARCHAR(64) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + KEY idx_pretty_batch_pool (app_code, pool_id, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号批量生成记录表'; +``` + +## 靓号池生成规则 + +靓号池批量生成首版支持 `aabbcc`: + +- 必须是 6 位数字。 +- 第 1、2 位相同。 +- 第 3、4 位相同。 +- 第 5、6 位相同。 +- 第 1 位不能是 `0`。 +- 生成后仍然复用数字短号格式校验,避免生成出 App 用户难以输入的池号码。 + +示例: + +```text +112233 +223344 +667788 +``` + +后续可以扩展: + +| rule_type | 说明 | +| --- | --- | +| `aabbcc` | 三组重复数字 | +| `aaaaaa` | 6 位相同数字 | +| `abcabc` | 前三位重复一次 | +| `manual` | 后台手动导入号码列表 | + +后台直接发放靓号不受本节数字生成规则限制,按“现有短号表扩展”里的后台发放字符规则校验。 + +## 批量生成事务 + +批量生成时由 `user-service` 完成冲突检查和入库。 + +冲突范围: + +- `CAST(users.user_id AS CHAR)`。 +- `users.default_display_user_id`。 +- `users.current_display_user_id`。 +- `user_display_user_ids` 中 `status IN ('active','held','reserved','blocked')` 的号码。 +- `pretty_display_ids` 中 `status IN ('available','assigned','disabled','reserved')` 的号码。 + +流程: + +1. 后台提交 `pool_id + rule_type + rule_config + count`。 +2. `user-service` 生成候选号码。 +3. 每个候选号码先做数字短号格式校验。 +4. 批量查询冲突号码,过滤已冲突项。 +5. 插入 `pretty_display_ids`,唯一键兜底处理并发冲突。 +6. 写 `pretty_display_id_generation_batches`。 +7. 返回本次生成数量和跳过冲突数量。 + +为了避免生成器死循环,服务端需要设置最大尝试次数,例如 `count * 20`,仍不足时返回实际生成数量和 `skipped_conflict_count`。 + +## 通用替换释放事务 + +靓号池申请和后台发放都复用同一个“替换当前靓号”事务。 + +如果用户没有 active pretty 展示号: + +1. 默认短号从 `active` 改为 `held`。 +2. 新靓号写入 `pretty_display_user_id_leases`。 +3. 新靓号写入 `user_display_user_ids`,`display_user_id_kind='pretty'`。 +4. 更新 `users.current_display_user_id`、`current_display_user_id_kind`、`current_display_user_id_expires_at_ms`。 +5. 写 `display_user_id_change_logs`。 + +如果用户已有 active pretty 展示号: + +1. 锁定当前用户行和旧 active lease。 +2. 旧 `pretty_display_user_id_leases.status` 改为 `cancelled`,`release_reason='replaced_by_new_pretty'`。 +3. 旧 `user_display_user_ids.status` 改为 `released`,记录 `released_at_ms`。 +4. 如果旧靓号来自靓号池,旧 `pretty_display_ids.status` 改回 `available`,清空 `assigned_user_id`、`assigned_lease_id`、`assigned_at_ms`。 +5. 如果旧靓号来自后台发放,旧 `pretty_display_ids.status` 改为 `released`,保留历史。 +6. 写旧靓号释放日志。 +7. 再按新靓号写入当前 active 展示号。 + +这个规则意味着:用户申请或被后台发放新靓号时,不返回“已有靓号”冲突,而是直接覆盖旧靓号。 + +## 靓号池申请事务 + +App 靓号池申请只接受 `pretty_id`,不接受客户端自报号码、等级、用户 ID 或租期。 + +事务流程: + +1. 从 access token 读取当前 `user_id`。 +2. `SELECT ... FOR UPDATE` 锁定 `pretty_display_ids`。 +3. 校验靓号项为 `available` 且 `source='pool'`。 +4. 锁定所属 `pretty_display_id_pools`,校验为 `active`。 +5. 调用 `activity-service` 读取当前用户在 `level_track` 下的等级。 +6. 校验用户等级在 `[min_level, max_level]`。 +7. 进入通用替换释放事务。 +8. 写新 `pretty_display_user_id_leases`,`source='level_pool'`,`expires_at_ms=0`。 +9. 更新新 `pretty_display_ids.status='assigned'`,写入 `assigned_user_id` 和 `assigned_lease_id`。 +10. 返回当前身份投影。 + +靓号池申请默认长期持有: + +- `pretty_display_user_id_leases.expires_at_ms = 0`。 +- `users.current_display_user_id_expires_at_ms = 0`。 +- 不由定时任务自动过期。 + +## 后台发放事务 + +后台发放不依赖靓号池,也不校验用户等级。 + +输入: + +- `target_user_id`:目标 App 用户。 +- `display_user_id`:管理员输入的靓号内容。 +- `duration_ms`:可选;大于 0 表示限时靓号,不传或传 0 表示长期。 +- `reason`:发放原因,写入审计和 change log。 + +事务流程: + +1. 校验管理员有 `pretty-id:grant` 权限。 +2. 校验 `display_user_id` 符合后台发放字符规则。 +3. 检查 `display_user_id` 不与当前有效 `users.user_id` 字符串、默认短号、当前短号、历史有效短号、已有靓号冲突。 +4. 锁定目标用户行。 +5. 进入通用替换释放事务。 +6. 生成新的 `pretty_id` 和 `lease_id`。 +7. 写 `pretty_display_ids`,`source='admin_grant'`,`status='assigned'`。 +8. 写 `pretty_display_user_id_leases`,`source='admin_grant'`。 +9. 如果 `duration_ms > 0`,`expires_at_ms = now_ms + duration_ms`;否则 `expires_at_ms = 0`。 +10. 更新 `users.current_display_user_id_expires_at_ms` 为同一个过期时间。 +11. 返回当前身份投影。 + +限时后台靓号: + +- `expires_at_ms > 0` 时继续复用现有靓号过期恢复逻辑。 +- 到期后恢复默认短号,不回到靓号池。 +- 如果到期前又发放或申请了新靓号,旧限时靓号按通用替换释放事务取消。 + +## Proto 变更 + +在 `api/proto/user/v1/user.proto` 中扩展或新增 identity 管理 RPC。 + +新增 App 申请 RPC: + +```proto +message ApplyPrettyDisplayIDFromPoolRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string pretty_id = 3; +} + +message ApplyPrettyDisplayIDFromPoolResponse { + UserIdentity identity = 1; + string pretty_id = 2; + string lease_id = 3; +} +``` + +新增后台管理 RPC: + +```proto +message AdminGrantPrettyDisplayIDRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; + string display_user_id = 3; + int64 duration_ms = 4; + string reason = 5; + int64 operator_admin_id = 6; +} + +message AdminGrantPrettyDisplayIDResponse { + UserIdentity identity = 1; + string pretty_id = 2; + string lease_id = 3; +} + +service UserPrettyDisplayIDAdminService { + rpc ListPrettyDisplayIDPools(ListPrettyDisplayIDPoolsRequest) returns (ListPrettyDisplayIDPoolsResponse); + rpc CreatePrettyDisplayIDPool(CreatePrettyDisplayIDPoolRequest) returns (PrettyDisplayIDPoolResponse); + rpc UpdatePrettyDisplayIDPool(UpdatePrettyDisplayIDPoolRequest) returns (PrettyDisplayIDPoolResponse); + rpc GeneratePrettyDisplayIDs(GeneratePrettyDisplayIDsRequest) returns (GeneratePrettyDisplayIDsResponse); + rpc ListPrettyDisplayIDs(ListPrettyDisplayIDsRequest) returns (ListPrettyDisplayIDsResponse); + rpc SetPrettyDisplayIDStatus(SetPrettyDisplayIDStatusRequest) returns (PrettyDisplayIDResponse); + rpc AdminGrantPrettyDisplayID(AdminGrantPrettyDisplayIDRequest) returns (AdminGrantPrettyDisplayIDResponse); +} +``` + +资料投影建议增加字段: + +```proto +message User { + // existing fields... + string pretty_id = 24; + string pretty_display_user_id = 25; +} + +message UserIdentity { + // existing fields... + string pretty_id = 8; + string pretty_display_user_id = 9; +} +``` + +字段规则: + +| 字段 | 说明 | +| --- | --- | +| `display_user_id` | 当前对外展示号;有靓号时等于靓号 | +| `display_user_id_kind` | `default` / `pretty` | +| `display_user_id_expires_at_ms` | 长期持有为 `0` | +| `pretty_id` | 当前占用的靓号池记录 ID,没有则为空 | +| `pretty_display_user_id` | 当前占用的靓号号码,没有则为空 | + +## 后台 HTTP 接口 + +后台接口归属 `server/admin/internal/modules/prettyid`,路径放在用户管理域。 + +### 靓号池列表 + +| 项 | 内容 | +| --- | --- | +| 地址 | `GET /api/v1/admin/users/pretty-id-pools` | +| 参数 | `status`、`level_track`、`page`、`page_size` | +| 返回值 | `items[]`、`total`,每项包含 `pool_id`、`name`、`level_track`、`min_level`、`max_level`、`rule_type`、`status` | +| 相关 IM | 无 | + +### 创建靓号池 + +| 项 | 内容 | +| --- | --- | +| 地址 | `POST /api/v1/admin/users/pretty-id-pools` | +| 参数 | `name`、`level_track`、`min_level`、`max_level`、`rule_type`、`rule_config_json`、`status` | +| 返回值 | 靓号池详情 | +| 相关 IM | 无 | + +### 更新靓号池 + +| 项 | 内容 | +| --- | --- | +| 地址 | `PUT /api/v1/admin/users/pretty-id-pools/{pool_id}` | +| 参数 | `name`、`level_track`、`min_level`、`max_level`、`rule_type`、`rule_config_json`、`status` | +| 返回值 | 靓号池详情 | +| 相关 IM | 无 | + +### 批量生成靓号 + +| 项 | 内容 | +| --- | --- | +| 地址 | `POST /api/v1/admin/users/pretty-id-pools/{pool_id}/generate` | +| 参数 | `rule_type`、`rule_config_json`、`count` | +| 返回值 | `batch_id`、`requested_count`、`generated_count`、`skipped_conflict_count` | +| 相关 IM | 无 | + +### 靓号列表 + +| 项 | 内容 | +| --- | --- | +| 地址 | `GET /api/v1/admin/users/pretty-ids` | +| 参数 | `pool_id`、`source`、`status`、`keyword`、`assigned_user_id`、`page`、`page_size` | +| 返回值 | `items[]`、`total`,每项包含 `pretty_id`、`source`、`display_user_id`、`pool`、`status`、`assigned_user_id`、`assigned_lease_id` | +| 相关 IM | 无 | + +### 后台发放靓号 + +| 项 | 内容 | +| --- | --- | +| 地址 | `POST /api/v1/admin/users/pretty-ids/grant` | +| 参数 | `target_user_id`、`display_user_id`、`duration_ms`、`reason`;`duration_ms` 不传或为 `0` 表示长期 | +| 返回值 | `user_id`、`display_user_id`、`display_user_id_kind`、`display_user_id_expires_at_ms`、`pretty_id`、`pretty_display_user_id`、`lease_id` | +| 相关 IM | 无 | + +### 靓号状态修改 + +| 项 | 内容 | +| --- | --- | +| 地址 | `POST /api/v1/admin/users/pretty-ids/{pretty_id}/status` | +| 参数 | `status`,只允许未占用的靓号在 `available`、`disabled`、`reserved` 之间切换 | +| 返回值 | 靓号详情 | +| 相关 IM | 无 | + +## App HTTP 接口 + +### 可申请靓号列表 + +| 项 | 内容 | +| --- | --- | +| 地址 | `GET /api/v1/users/pretty-ids/available` | +| 参数 | `page`、`page_size` | +| 返回值 | `items[]`、`total`,每项包含 `pretty_id`、`display_user_id`、`level_track`、`min_level`、`max_level` | +| 相关 IM | 无 | + +返回只包含当前用户等级满足条件的 `available` 靓号。 + +### 靓号池申请 + +| 项 | 内容 | +| --- | --- | +| 地址 | `POST /api/v1/users/me/display-id/pretty/apply` | +| 参数 | `pretty_id` | +| 返回值 | `user_id`、`display_user_id`、`display_user_id_kind`、`display_user_id_expires_at_ms`、`pretty_id`、`pretty_display_user_id`、`lease_id` | +| 相关 IM | 无 | + +说明: + +- 保留当前地址,避免前端路由重复;请求体改为优先读取 `pretty_id`。 +- 靓号池申请默认长期持有,`display_user_id_expires_at_ms=0`。 +- 用户已有靓号时,新靓号直接覆盖旧靓号,旧靓号释放。 +- 如果要兼容旧付费参数,服务端可以临时保留 `pretty_display_user_id + lease_duration_ms + payment_receipt_id` 分支,但新 App 只使用 `pretty_id`。 + +## 资料返回改造 + +所有资料返回统一使用同一套字段。 + +| 场景 | 当前链路 | 改造点 | +| --- | --- | --- | +| 个人信息 | `GET /api/v1/users/me` -> `user-service.GetUser` | `User` proto 增加 `pretty_id`、`pretty_display_user_id` | +| 个人资料卡 | 用户资料批量接口或资料详情 | 复用 `User` 投影字段 | +| 房间个人资料卡 | `GET /api/v1/users/room-display-profiles:batch` | `roomDisplayProfileData` 增加靓号字段 | +| 房间详情 | `room detail` 中的 `profiles[]` | 来自同一批 `roomDisplayProfileData` | +| 登录 / 刷新 token | `AuthToken` 已有 display 字段 | 如客户端需要启动后立即知道靓号,补 `pretty_id`、`pretty_display_user_id` | + +响应规则: + +- 用户没有靓号:`display_user_id` 返回默认短号,`display_user_id_kind='default'`,`pretty_id=''`,`pretty_display_user_id=''`。 +- 用户有靓号:`display_user_id` 返回靓号,`display_user_id_kind='pretty'`,`pretty_id` 返回池记录 ID,`pretty_display_user_id` 返回靓号号码。 +- 长期持有靓号:`display_user_id_expires_at_ms=0`。 +- 后台限时发放靓号:`display_user_id_expires_at_ms` 返回实际过期时间。 + +## 后台菜单和权限 + +新增权限: + +| 权限码 | kind | 说明 | +| --- | --- | --- | +| `pretty-id:view` | `menu` | 查看靓号管理 | +| `pretty-id:update` | `button` | 新增、编辑、上下架靓号池和靓号 | +| `pretty-id:generate` | `button` | 批量生成靓号 | +| `pretty-id:grant` | `button` | 后台直接发放靓号 | + +新增菜单: + +| 字段 | 值 | +| --- | --- | +| parent | `app-users` | +| title | `靓号管理` | +| code | `app-user-pretty-ids` | +| path | `/app/users/pretty-ids` | +| icon | `badge` | +| permission_code | `pretty-id:view` | +| sort | `65` | + +需要同步: + +- `server/admin/migrations/044_pretty_id_management.sql`。 +- `server/admin/internal/repository/seed.go` 默认权限和菜单。 +- `platform-admin` 默认绑定全部新增权限。 +- 只读角色只绑定 `pretty-id:view`。 + +## 代码落点 + +| 位置 | 改动 | +| --- | --- | +| `api/proto/user/v1/user.proto` | 增加靓号池 RPC、后台发放 RPC、资料字段 | +| `services/user-service/internal/domain/user` | 增加 pool、pretty item、申请命令、后台发放命令和非数字靓号校验 | +| `services/user-service/internal/storage/mysql/identity` | 增加靓号池、生成、申请、后台发放、替换释放事务 | +| `services/user-service/deploy/mysql/initdb/001_user_service.sql` | 增加新表并扩展短号字段长度 | +| `services/user-service/migrations` | 增加线上迁移 | +| `services/gateway-service/internal/transport/http/userapi` | 改造靓号池申请接口、可申请列表、资料字段 | +| `services/gateway-service/internal/transport/http/roomapi` | 房间资料结构补靓号字段 | +| `server/admin/internal/integration/userclient` | 增加后台靓号管理 client | +| `server/admin/internal/modules/prettyid` | 新增后台模块 | +| `server/admin/internal/router/router.go` | 注册后台模块 | +| `server/admin/migrations` | 菜单和权限迁移 | + +## 错误码建议 + +| code | 场景 | +| --- | --- | +| `PRETTY_ID_NOT_FOUND` | `pretty_id` 不存在 | +| `PRETTY_ID_NOT_AVAILABLE` | 靓号不可申请或已被占用 | +| `PRETTY_ID_LEVEL_NOT_MATCH` | 用户等级不在池区间内 | +| `PRETTY_DISPLAY_ID_INVALID` | 后台发放的靓号内容不符合字符规则 | +| `DISPLAY_USER_ID_CONFLICT` | 生成或申请时发现短号冲突 | + +## 验证项 + +必须验证: + +- `make proto` 成功,生成 `.pb.go` 和 `_grpc.pb.go`。 +- `go test ./...` 在 `hyapp-server` 根目录通过。 +- `go test ./...` 在 `hyapp-server/server/admin` 通过。 +- 批量生成 1000 个 `aabbcc` 靓号时,不插入与 `users.user_id`、默认短号、当前短号、已有靓号冲突的号码。 +- 用户等级低于池区间时,申请返回 `PRETTY_ID_LEVEL_NOT_MATCH`。 +- 用户等级在池区间内时,申请成功并更新: + - `pretty_display_ids.status='assigned'` + - `pretty_display_user_id_leases.source='level_pool'` + - `user_display_user_ids.display_user_id_kind='pretty'` + - `users.current_display_user_id_kind='pretty'` +- 用户已有靓号时,再申请另一个靓号会覆盖旧靓号,旧靓号释放;旧池靓号回到 `available`,旧后台发放靓号进入 `released`。 +- 后台发放 `VIP2026`、`ملك123` 这类英文数字或阿拉伯语数字组合可以成功。 +- 后台发放不传 `duration_ms` 时,返回 `display_user_id_expires_at_ms=0`。 +- 后台发放传 `duration_ms` 时,返回实际 `display_user_id_expires_at_ms`,到期后恢复默认短号。 +- `GET /api/v1/users/me` 返回靓号字段。 +- `GET /api/v1/users/room-display-profiles:batch` 返回靓号字段。 +- 房间详情 `profiles[]` 返回靓号字段。 +- 后台 `/api/v1/navigation/menus` 能看到“用户管理 > 靓号管理”。 diff --git a/server/admin/internal/integration/gameclient/client.go b/server/admin/internal/integration/gameclient/client.go index 9bdabcc4..1119d0a3 100644 --- a/server/admin/internal/integration/gameclient/client.go +++ b/server/admin/internal/integration/gameclient/client.go @@ -16,6 +16,13 @@ type Client interface { UpsertCatalog(ctx context.Context, req *gamev1.UpsertCatalogRequest) (*gamev1.CatalogResponse, error) SetGameStatus(ctx context.Context, req *gamev1.SetGameStatusRequest) (*gamev1.CatalogResponse, error) DeleteCatalog(ctx context.Context, req *gamev1.DeleteCatalogRequest) (*gamev1.DeleteCatalogResponse, error) + ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error) + UpdateDiceConfig(ctx context.Context, req *gamev1.UpdateDiceConfigRequest) (*gamev1.DiceConfigResponse, error) + AdjustDicePool(ctx context.Context, req *gamev1.AdjustDicePoolRequest) (*gamev1.AdjustDicePoolResponse, error) + ListDiceRobots(ctx context.Context, req *gamev1.ListDiceRobotsRequest) (*gamev1.ListDiceRobotsResponse, error) + RegisterDiceRobots(ctx context.Context, req *gamev1.RegisterDiceRobotsRequest) (*gamev1.RegisterDiceRobotsResponse, error) + SetDiceRobotStatus(ctx context.Context, req *gamev1.SetDiceRobotStatusRequest) (*gamev1.DiceRobotResponse, error) + DeleteDiceRobot(ctx context.Context, req *gamev1.DeleteDiceRobotRequest) (*gamev1.DeleteDiceRobotResponse, error) } type GRPCClient struct { @@ -49,3 +56,32 @@ func (c *GRPCClient) SetGameStatus(ctx context.Context, req *gamev1.SetGameStatu func (c *GRPCClient) DeleteCatalog(ctx context.Context, req *gamev1.DeleteCatalogRequest) (*gamev1.DeleteCatalogResponse, error) { return c.client.DeleteCatalog(ctx, req) } + +func (c *GRPCClient) ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error) { + return c.client.ListSelfGames(ctx, req) +} + +func (c *GRPCClient) UpdateDiceConfig(ctx context.Context, req *gamev1.UpdateDiceConfigRequest) (*gamev1.DiceConfigResponse, error) { + return c.client.UpdateDiceConfig(ctx, req) +} + +func (c *GRPCClient) AdjustDicePool(ctx context.Context, req *gamev1.AdjustDicePoolRequest) (*gamev1.AdjustDicePoolResponse, error) { + return c.client.AdjustDicePool(ctx, req) +} + +func (c *GRPCClient) ListDiceRobots(ctx context.Context, req *gamev1.ListDiceRobotsRequest) (*gamev1.ListDiceRobotsResponse, error) { + return c.client.ListDiceRobots(ctx, req) +} + +func (c *GRPCClient) RegisterDiceRobots(ctx context.Context, req *gamev1.RegisterDiceRobotsRequest) (*gamev1.RegisterDiceRobotsResponse, error) { + return c.client.RegisterDiceRobots(ctx, req) +} + +func (c *GRPCClient) SetDiceRobotStatus(ctx context.Context, req *gamev1.SetDiceRobotStatusRequest) (*gamev1.DiceRobotResponse, error) { + return c.client.SetDiceRobotStatus(ctx, req) +} + +// DeleteDiceRobot 透传后台删除机器人登记 RPC;admin-server 不直接操作 game-service 的机器人表。 +func (c *GRPCClient) DeleteDiceRobot(ctx context.Context, req *gamev1.DeleteDiceRobotRequest) (*gamev1.DeleteDiceRobotResponse, error) { + return c.client.DeleteDiceRobot(ctx, req) +} diff --git a/server/admin/internal/integration/userclient/client.go b/server/admin/internal/integration/userclient/client.go index 3d2b291c..5c708268 100644 --- a/server/admin/internal/integration/userclient/client.go +++ b/server/admin/internal/integration/userclient/client.go @@ -12,9 +12,17 @@ import ( type Client interface { GetUser(ctx context.Context, req GetUserRequest) (*User, error) + BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error) AdminChangeUserCountry(ctx context.Context, req AdminChangeUserCountryRequest) (*ChangeUserCountryResult, error) SetUserStatus(ctx context.Context, req SetUserStatusRequest) (*SetUserStatusResult, error) ResolveDisplayUserID(ctx context.Context, req ResolveDisplayUserIDRequest) (*UserIdentity, error) + ListPrettyDisplayIDPools(ctx context.Context, req ListPrettyDisplayIDPoolsRequest) ([]*PrettyDisplayIDPool, int64, error) + CreatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error) + UpdatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error) + GeneratePrettyDisplayIDs(ctx context.Context, req GeneratePrettyDisplayIDsRequest) (*PrettyDisplayIDGenerationBatch, error) + ListPrettyDisplayIDs(ctx context.Context, req ListPrettyDisplayIDsRequest) ([]*PrettyDisplayID, int64, error) + SetPrettyDisplayIDStatus(ctx context.Context, req SetPrettyDisplayIDStatusRequest) (*PrettyDisplayID, error) + AdminGrantPrettyDisplayID(ctx context.Context, req AdminGrantPrettyDisplayIDRequest) (*AdminGrantPrettyDisplayIDResult, error) ListCountries(ctx context.Context, req ListCountriesRequest) ([]*Country, error) UpdateCountry(ctx context.Context, req UpdateCountryRequest) (*Country, error) ListRegions(ctx context.Context, req ListRegionsRequest) ([]*Region, error) @@ -31,6 +39,7 @@ type Client interface { CloseAgency(ctx context.Context, req CloseAgencyRequest) (*Agency, error) DeleteAgency(ctx context.Context, req DeleteAgencyRequest) (*Agency, error) SetAgencyJoinEnabled(ctx context.Context, req SetAgencyJoinEnabledRequest) (*Agency, error) + QuickCreateAccount(ctx context.Context, req QuickCreateAccountRequest) (*QuickCreateAccountResult, error) } type GetUserRequest struct { @@ -39,6 +48,35 @@ type GetUserRequest struct { UserID int64 } +// BatchGetUsersRequest 只承载后台展示需要的一组真实 user_id,不在 admin-server 里回查用户库。 +type BatchGetUsersRequest struct { + RequestID string + Caller string + UserIDs []int64 +} + +type QuickCreateAccountRequest struct { + RequestID string + Caller string + Password string + Username string + Avatar string + Gender string + Country string + DeviceID string + Source string + InstallChannel string + Platform string + Language string + Timezone string +} + +type QuickCreateAccountResult struct { + UserID int64 + DisplayUserID string + AccessToken string +} + type SetUserStatusRequest struct { RequestID string Caller string @@ -92,6 +130,8 @@ type UserIdentity struct { DefaultDisplayUserID string `json:"defaultDisplayUserId"` DisplayUserIDKind string `json:"displayUserIdKind"` DisplayUserIDExpiresAtMs int64 `json:"displayUserIdExpiresAtMs"` + PrettyID string `json:"prettyId,omitempty"` + PrettyDisplayUserID string `json:"prettyDisplayUserId,omitempty"` AppCode string `json:"appCode"` } @@ -104,6 +144,8 @@ type User struct { DefaultDisplayUserID string `json:"defaultDisplayUserId"` DisplayUserIDKind string `json:"displayUserIdKind"` DisplayUserIDExpiresAtMs int64 `json:"displayUserIdExpiresAtMs"` + PrettyID string `json:"prettyId,omitempty"` + PrettyDisplayUserID string `json:"prettyDisplayUserId,omitempty"` Username string `json:"username"` Gender string `json:"gender"` Country string `json:"country"` @@ -126,9 +168,11 @@ type User struct { type GRPCClient struct { client userv1.UserServiceClient + authClient userv1.AuthServiceClient identityClient userv1.UserIdentityServiceClient countryClient userv1.CountryAdminServiceClient regionClient userv1.RegionAdminServiceClient + prettyClient userv1.UserPrettyDisplayIDAdminServiceClient hostClient userv1.UserHostServiceClient hostAdminClient userv1.UserHostAdminServiceClient } @@ -136,14 +180,50 @@ type GRPCClient struct { func NewGRPC(conn grpc.ClientConnInterface) *GRPCClient { return &GRPCClient{ client: userv1.NewUserServiceClient(conn), + authClient: userv1.NewAuthServiceClient(conn), identityClient: userv1.NewUserIdentityServiceClient(conn), countryClient: userv1.NewCountryAdminServiceClient(conn), regionClient: userv1.NewRegionAdminServiceClient(conn), + prettyClient: userv1.NewUserPrettyDisplayIDAdminServiceClient(conn), hostClient: userv1.NewUserHostServiceClient(conn), hostAdminClient: userv1.NewUserHostAdminServiceClient(conn), } } +func (c *GRPCClient) QuickCreateAccount(ctx context.Context, req QuickCreateAccountRequest) (*QuickCreateAccountResult, error) { + resp, err := c.authClient.QuickCreateAccount(ctx, &userv1.QuickCreateAccountRequest{ + Meta: &userv1.RequestMeta{ + RequestId: req.RequestID, + Caller: req.Caller, + SentAtMs: time.Now().UnixMilli(), + AppCode: appctx.FromContext(ctx), + Platform: req.Platform, + Language: req.Language, + Timezone: req.Timezone, + }, + Password: req.Password, + Username: req.Username, + Avatar: req.Avatar, + Gender: req.Gender, + Country: req.Country, + DeviceId: req.DeviceID, + Source: req.Source, + InstallChannel: req.InstallChannel, + Platform: req.Platform, + Language: req.Language, + Timezone: req.Timezone, + }) + if err != nil { + return nil, err + } + token := resp.GetToken() + return &QuickCreateAccountResult{ + UserID: token.GetUserId(), + DisplayUserID: token.GetDisplayUserId(), + AccessToken: token.GetAccessToken(), + }, nil +} + func (c *GRPCClient) GetUser(ctx context.Context, req GetUserRequest) (*User, error) { resp, err := c.client.GetUser(ctx, &userv1.GetUserRequest{ Meta: &userv1.RequestMeta{ @@ -160,6 +240,22 @@ func (c *GRPCClient) GetUser(ctx context.Context, req GetUserRequest) (*User, er return fromProtoUser(resp.GetUser()), nil } +// BatchGetUsers 复用 user-service 主数据 RPC,让后台列表只做展示聚合,不复制用户资料到游戏库。 +func (c *GRPCClient) BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error) { + resp, err := c.client.BatchGetUsers(ctx, &userv1.BatchGetUsersRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + UserIds: req.UserIDs, + }) + if err != nil { + return nil, err + } + users := make(map[int64]*User, len(resp.GetUsers())) + for userID, user := range resp.GetUsers() { + users[userID] = fromProtoUser(user) + } + return users, nil +} + func (c *GRPCClient) AdminChangeUserCountry(ctx context.Context, req AdminChangeUserCountryRequest) (*ChangeUserCountryResult, error) { resp, err := c.client.AdminChangeUserCountry(ctx, &userv1.AdminChangeUserCountryRequest{ Meta: requestMeta(ctx, req.RequestID, req.Caller), @@ -243,6 +339,8 @@ func fromProtoUser(user *userv1.User) *User { DefaultDisplayUserID: user.GetDefaultDisplayUserId(), DisplayUserIDKind: user.GetDisplayUserIdKind(), DisplayUserIDExpiresAtMs: user.GetDisplayUserIdExpiresAtMs(), + PrettyID: user.GetPrettyId(), + PrettyDisplayUserID: user.GetPrettyDisplayUserId(), Username: user.GetUsername(), Gender: user.GetGender(), Country: user.GetCountry(), @@ -288,6 +386,8 @@ func fromProtoUserIdentity(identity *userv1.UserIdentity) *UserIdentity { DefaultDisplayUserID: identity.GetDefaultDisplayUserId(), DisplayUserIDKind: identity.GetDisplayUserIdKind(), DisplayUserIDExpiresAtMs: identity.GetDisplayUserIdExpiresAtMs(), + PrettyID: identity.GetPrettyId(), + PrettyDisplayUserID: identity.GetPrettyDisplayUserId(), AppCode: identity.GetAppCode(), } } diff --git a/server/admin/internal/integration/userclient/pretty.go b/server/admin/internal/integration/userclient/pretty.go new file mode 100644 index 00000000..9ad35a7b --- /dev/null +++ b/server/admin/internal/integration/userclient/pretty.go @@ -0,0 +1,328 @@ +package userclient + +import ( + "context" + + userv1 "hyapp.local/api/proto/user/v1" +) + +type PrettyDisplayIDPool struct { + AppCode string `json:"app_code"` + PoolID string `json:"pool_id"` + Name string `json:"name"` + LevelTrack string `json:"level_track"` + MinLevel int32 `json:"min_level"` + MaxLevel int32 `json:"max_level"` + RuleType string `json:"rule_type"` + RuleConfigJSON string `json:"rule_config_json"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + CreatedByAdminID int64 `json:"created_by_admin_id,string"` + UpdatedByAdminID int64 `json:"updated_by_admin_id,string"` + CreatedAtMs int64 `json:"created_at_ms"` + UpdatedAtMs int64 `json:"updated_at_ms"` +} + +type PrettyDisplayID struct { + AppCode string `json:"app_code"` + PrettyID string `json:"pretty_id"` + PoolID string `json:"pool_id"` + Source string `json:"source"` + DisplayUserID string `json:"display_user_id"` + Status string `json:"status"` + AssignedUserID int64 `json:"assigned_user_id,string"` + AssignedLeaseID string `json:"assigned_lease_id"` + AssignedAtMs int64 `json:"assigned_at_ms"` + ReleasedAtMs int64 `json:"released_at_ms"` + ReleaseReason string `json:"release_reason"` + GeneratedBatchID string `json:"generated_batch_id"` + CreatedByAdminID int64 `json:"created_by_admin_id,string"` + CreatedAtMs int64 `json:"created_at_ms"` + UpdatedAtMs int64 `json:"updated_at_ms"` + Pool *PrettyDisplayIDPool `json:"pool,omitempty"` +} + +type PrettyDisplayIDGenerationBatch struct { + AppCode string `json:"app_code"` + BatchID string `json:"batch_id"` + PoolID string `json:"pool_id"` + RuleType string `json:"rule_type"` + RuleConfigJSON string `json:"rule_config_json"` + RequestedCount int32 `json:"requested_count"` + GeneratedCount int32 `json:"generated_count"` + SkippedConflictCount int32 `json:"skipped_conflict_count"` + Status string `json:"status"` + OperatorAdminID int64 `json:"operator_admin_id,string"` + RequestID string `json:"request_id"` + CreatedAtMs int64 `json:"created_at_ms"` + UpdatedAtMs int64 `json:"updated_at_ms"` +} + +type ListPrettyDisplayIDPoolsRequest struct { + RequestID string + Caller string + Status string + LevelTrack string + Page int32 + PageSize int32 +} + +type PrettyDisplayIDPoolRequest struct { + RequestID string + Caller string + PoolID string + Name string + LevelTrack string + MinLevel int32 + MaxLevel int32 + RuleType string + RuleConfigJSON string + Status string + SortOrder int32 + OperatorAdminID int64 +} + +type GeneratePrettyDisplayIDsRequest struct { + RequestID string + Caller string + PoolID string + RuleType string + RuleConfigJSON string + Count int32 + OperatorAdminID int64 +} + +type ListPrettyDisplayIDsRequest struct { + RequestID string + Caller string + PoolID string + Source string + Status string + Keyword string + AssignedUserID int64 + Page int32 + PageSize int32 +} + +type SetPrettyDisplayIDStatusRequest struct { + RequestID string + Caller string + PrettyID string + Status string + Reason string + OperatorAdminID int64 +} + +type AdminGrantPrettyDisplayIDRequest struct { + RequestID string + Caller string + TargetUserID int64 + DisplayUserID string + DurationMs int64 + Reason string + OperatorAdminID int64 +} + +type AdminGrantPrettyDisplayIDResult struct { + Identity *UserIdentity `json:"identity"` + PrettyID string `json:"pretty_id"` + LeaseID string `json:"lease_id"` +} + +// 以下方法只做 admin-server 到 user-service 的传输适配:HTTP 层 DTO 保持 snake_case,gRPC 层字段按 proto 命名传递。 +func (c *GRPCClient) ListPrettyDisplayIDPools(ctx context.Context, req ListPrettyDisplayIDPoolsRequest) ([]*PrettyDisplayIDPool, int64, error) { + resp, err := c.prettyClient.ListPrettyDisplayIDPools(ctx, &userv1.ListPrettyDisplayIDPoolsRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + Status: req.Status, + LevelTrack: req.LevelTrack, + Page: req.Page, + PageSize: req.PageSize, + }) + if err != nil { + return nil, 0, err + } + items := make([]*PrettyDisplayIDPool, 0, len(resp.GetItems())) + for _, item := range resp.GetItems() { + items = append(items, fromProtoPrettyDisplayIDPool(item)) + } + return items, resp.GetTotal(), nil +} + +func (c *GRPCClient) CreatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error) { + resp, err := c.prettyClient.CreatePrettyDisplayIDPool(ctx, &userv1.CreatePrettyDisplayIDPoolRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + Name: req.Name, + LevelTrack: req.LevelTrack, + MinLevel: req.MinLevel, + MaxLevel: req.MaxLevel, + RuleType: req.RuleType, + RuleConfigJson: req.RuleConfigJSON, + Status: req.Status, + SortOrder: req.SortOrder, + OperatorAdminId: req.OperatorAdminID, + }) + if err != nil { + return nil, err + } + return fromProtoPrettyDisplayIDPool(resp.GetPool()), nil +} + +func (c *GRPCClient) UpdatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error) { + resp, err := c.prettyClient.UpdatePrettyDisplayIDPool(ctx, &userv1.UpdatePrettyDisplayIDPoolRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + PoolId: req.PoolID, + Name: req.Name, + LevelTrack: req.LevelTrack, + MinLevel: req.MinLevel, + MaxLevel: req.MaxLevel, + RuleType: req.RuleType, + RuleConfigJson: req.RuleConfigJSON, + Status: req.Status, + SortOrder: req.SortOrder, + OperatorAdminId: req.OperatorAdminID, + }) + if err != nil { + return nil, err + } + return fromProtoPrettyDisplayIDPool(resp.GetPool()), nil +} + +func (c *GRPCClient) GeneratePrettyDisplayIDs(ctx context.Context, req GeneratePrettyDisplayIDsRequest) (*PrettyDisplayIDGenerationBatch, error) { + // 批量生成的冲突过滤、规则默认值和批次落库都在 user-service,这里不复制业务判断。 + resp, err := c.prettyClient.GeneratePrettyDisplayIDs(ctx, &userv1.GeneratePrettyDisplayIDsRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + PoolId: req.PoolID, + RuleType: req.RuleType, + RuleConfigJson: req.RuleConfigJSON, + Count: req.Count, + OperatorAdminId: req.OperatorAdminID, + }) + if err != nil { + return nil, err + } + return fromProtoPrettyDisplayIDGenerationBatch(resp.GetBatch()), nil +} + +func (c *GRPCClient) ListPrettyDisplayIDs(ctx context.Context, req ListPrettyDisplayIDsRequest) ([]*PrettyDisplayID, int64, error) { + resp, err := c.prettyClient.ListPrettyDisplayIDs(ctx, &userv1.ListPrettyDisplayIDsRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + PoolId: req.PoolID, + Source: req.Source, + Status: req.Status, + Keyword: req.Keyword, + AssignedUserId: req.AssignedUserID, + Page: req.Page, + PageSize: req.PageSize, + }) + if err != nil { + return nil, 0, err + } + items := make([]*PrettyDisplayID, 0, len(resp.GetItems())) + for _, item := range resp.GetItems() { + items = append(items, fromProtoPrettyDisplayID(item)) + } + return items, resp.GetTotal(), nil +} + +func (c *GRPCClient) SetPrettyDisplayIDStatus(ctx context.Context, req SetPrettyDisplayIDStatusRequest) (*PrettyDisplayID, error) { + resp, err := c.prettyClient.SetPrettyDisplayIDStatus(ctx, &userv1.SetPrettyDisplayIDStatusRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + PrettyId: req.PrettyID, + Status: req.Status, + OperatorAdminId: req.OperatorAdminID, + Reason: req.Reason, + }) + if err != nil { + return nil, err + } + return fromProtoPrettyDisplayID(resp.GetItem()), nil +} + +func (c *GRPCClient) AdminGrantPrettyDisplayID(ctx context.Context, req AdminGrantPrettyDisplayIDRequest) (*AdminGrantPrettyDisplayIDResult, error) { + // 后台发放返回当前用户身份快照,admin-server 原样转给前端,方便页面确认最终展示号。 + resp, err := c.prettyClient.AdminGrantPrettyDisplayID(ctx, &userv1.AdminGrantPrettyDisplayIDRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + TargetUserId: req.TargetUserID, + DisplayUserId: req.DisplayUserID, + DurationMs: req.DurationMs, + Reason: req.Reason, + OperatorAdminId: req.OperatorAdminID, + }) + if err != nil { + return nil, err + } + return &AdminGrantPrettyDisplayIDResult{ + Identity: fromProtoUserIdentity(resp.GetIdentity()), + PrettyID: resp.GetPrettyId(), + LeaseID: resp.GetLeaseId(), + }, nil +} + +func fromProtoPrettyDisplayIDPool(pool *userv1.PrettyDisplayIDPool) *PrettyDisplayIDPool { + if pool == nil { + return nil + } + // 转换层保留 int64 的 string JSON tag,避免后台前端在大 ID 上丢精度。 + return &PrettyDisplayIDPool{ + AppCode: pool.GetAppCode(), + PoolID: pool.GetPoolId(), + Name: pool.GetName(), + LevelTrack: pool.GetLevelTrack(), + MinLevel: pool.GetMinLevel(), + MaxLevel: pool.GetMaxLevel(), + RuleType: pool.GetRuleType(), + RuleConfigJSON: pool.GetRuleConfigJson(), + Status: pool.GetStatus(), + SortOrder: pool.GetSortOrder(), + CreatedByAdminID: pool.GetCreatedByAdminId(), + UpdatedByAdminID: pool.GetUpdatedByAdminId(), + CreatedAtMs: pool.GetCreatedAtMs(), + UpdatedAtMs: pool.GetUpdatedAtMs(), + } +} + +func fromProtoPrettyDisplayID(item *userv1.PrettyDisplayID) *PrettyDisplayID { + if item == nil { + return nil + } + // pool 可能为空:后台发放的自由靓号没有池归属,列表展示时仍要返回靓号本体。 + return &PrettyDisplayID{ + AppCode: item.GetAppCode(), + PrettyID: item.GetPrettyId(), + PoolID: item.GetPoolId(), + Source: item.GetSource(), + DisplayUserID: item.GetDisplayUserId(), + Status: item.GetStatus(), + AssignedUserID: item.GetAssignedUserId(), + AssignedLeaseID: item.GetAssignedLeaseId(), + AssignedAtMs: item.GetAssignedAtMs(), + ReleasedAtMs: item.GetReleasedAtMs(), + ReleaseReason: item.GetReleaseReason(), + GeneratedBatchID: item.GetGeneratedBatchId(), + CreatedByAdminID: item.GetCreatedByAdminId(), + CreatedAtMs: item.GetCreatedAtMs(), + UpdatedAtMs: item.GetUpdatedAtMs(), + Pool: fromProtoPrettyDisplayIDPool(item.GetPool()), + } +} + +func fromProtoPrettyDisplayIDGenerationBatch(batch *userv1.PrettyDisplayIDGenerationBatch) *PrettyDisplayIDGenerationBatch { + if batch == nil { + return nil + } + return &PrettyDisplayIDGenerationBatch{ + AppCode: batch.GetAppCode(), + BatchID: batch.GetBatchId(), + PoolID: batch.GetPoolId(), + RuleType: batch.GetRuleType(), + RuleConfigJSON: batch.GetRuleConfigJson(), + RequestedCount: batch.GetRequestedCount(), + GeneratedCount: batch.GetGeneratedCount(), + SkippedConflictCount: batch.GetSkippedConflictCount(), + Status: batch.GetStatus(), + OperatorAdminID: batch.GetOperatorAdminId(), + RequestID: batch.GetRequestId(), + CreatedAtMs: batch.GetCreatedAtMs(), + UpdatedAtMs: batch.GetUpdatedAtMs(), + } +} diff --git a/server/admin/internal/integration/walletclient/client.go b/server/admin/internal/integration/walletclient/client.go index c9ac62ed..22c3f8d5 100644 --- a/server/admin/internal/integration/walletclient/client.go +++ b/server/admin/internal/integration/walletclient/client.go @@ -35,6 +35,9 @@ type Client interface { AdminCreditAsset(ctx context.Context, req *walletv1.AdminCreditAssetRequest) (*walletv1.AdminCreditAssetResponse, error) AdminCreditCoinSellerStock(ctx context.Context, req *walletv1.AdminCreditCoinSellerStockRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error) ListRechargeBills(ctx context.Context, req *walletv1.ListRechargeBillsRequest) (*walletv1.ListRechargeBillsResponse, error) + ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) + SetThirdPartyPaymentMethodStatus(ctx context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) + UpdateThirdPartyPaymentRate(ctx context.Context, req *walletv1.UpdateThirdPartyPaymentRateRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) ListAdminRechargeProducts(ctx context.Context, req *walletv1.ListAdminRechargeProductsRequest) (*walletv1.ListAdminRechargeProductsResponse, error) CreateRechargeProduct(ctx context.Context, req *walletv1.CreateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) UpdateRechargeProduct(ctx context.Context, req *walletv1.UpdateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) @@ -157,6 +160,18 @@ func (c *GRPCClient) ListRechargeBills(ctx context.Context, req *walletv1.ListRe return c.client.ListRechargeBills(ctx, req) } +func (c *GRPCClient) ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) { + return c.client.ListThirdPartyPaymentChannels(ctx, req) +} + +func (c *GRPCClient) SetThirdPartyPaymentMethodStatus(ctx context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) { + return c.client.SetThirdPartyPaymentMethodStatus(ctx, req) +} + +func (c *GRPCClient) UpdateThirdPartyPaymentRate(ctx context.Context, req *walletv1.UpdateThirdPartyPaymentRateRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) { + return c.client.UpdateThirdPartyPaymentRate(ctx, req) +} + func (c *GRPCClient) ListAdminRechargeProducts(ctx context.Context, req *walletv1.ListAdminRechargeProductsRequest) (*walletv1.ListAdminRechargeProductsResponse, error) { return c.client.ListAdminRechargeProducts(ctx, req) } diff --git a/server/admin/internal/model/models.go b/server/admin/internal/model/models.go index 7a158904..5046d980 100644 --- a/server/admin/internal/model/models.go +++ b/server/admin/internal/model/models.go @@ -116,21 +116,22 @@ func (AppBanner) TableName() string { } type AppSplashScreen struct { - ID uint `gorm:"primaryKey" json:"id"` - AppCode string `gorm:"size:32;index:idx_admin_app_splash_screens_app_sort,not null;default:lalu" json:"appCode"` - CoverURL string `gorm:"size:1024;not null" json:"coverUrl"` - SplashType string `gorm:"size:16;not null" json:"splashType"` - Param string `gorm:"size:2048" json:"param"` - Status string `gorm:"size:24;index:idx_admin_app_splash_screens_app_sort,not null;default:active" json:"status"` - Platform string `gorm:"size:24;index:idx_admin_app_splash_screens_scope,not null" json:"platform"` - SortOrder int `gorm:"index:idx_admin_app_splash_screens_app_sort,not null;default:0" json:"sortOrder"` - RegionID int64 `gorm:"index:idx_admin_app_splash_screens_scope,not null;default:0" json:"regionId"` - CountryCode string `gorm:"size:8;index:idx_admin_app_splash_screens_scope" json:"countryCode"` - Description string `gorm:"size:255" json:"description"` - StartsAtMS int64 `gorm:"column:starts_at_ms;not null;default:0;comment:投放开始时间,UTC epoch ms" json:"startsAtMs"` - EndsAtMS int64 `gorm:"column:ends_at_ms;not null;default:0;comment:投放结束时间,UTC epoch ms" json:"endsAtMs"` - CreatedAtMS int64 `gorm:"column:created_at_ms;autoCreateTime:milli" json:"createdAtMs"` - UpdatedAtMS int64 `gorm:"column:updated_at_ms;autoUpdateTime:milli" json:"updatedAtMs"` + ID uint `gorm:"primaryKey" json:"id"` + AppCode string `gorm:"size:32;index:idx_admin_app_splash_screens_app_sort,not null;default:lalu" json:"appCode"` + CoverURL string `gorm:"size:1024;not null" json:"coverUrl"` + SplashType string `gorm:"size:16;not null" json:"splashType"` + Param string `gorm:"size:2048" json:"param"` + Status string `gorm:"size:24;index:idx_admin_app_splash_screens_app_sort,not null;default:active" json:"status"` + Platform string `gorm:"size:24;index:idx_admin_app_splash_screens_scope,not null" json:"platform"` + SortOrder int `gorm:"index:idx_admin_app_splash_screens_app_sort,not null;default:0" json:"sortOrder"` + RegionID int64 `gorm:"index:idx_admin_app_splash_screens_scope,not null;default:0" json:"regionId"` + CountryCode string `gorm:"size:8;index:idx_admin_app_splash_screens_scope" json:"countryCode"` + Description string `gorm:"size:255" json:"description"` + DisplayDurationMS int `gorm:"column:display_duration_ms;not null;default:3000;comment:开屏展示时长,毫秒;0 表示客户端默认值" json:"displayDurationMs"` + StartsAtMS int64 `gorm:"column:starts_at_ms;not null;default:0;comment:投放开始时间,UTC epoch ms" json:"startsAtMs"` + EndsAtMS int64 `gorm:"column:ends_at_ms;not null;default:0;comment:投放结束时间,UTC epoch ms" json:"endsAtMs"` + CreatedAtMS int64 `gorm:"column:created_at_ms;autoCreateTime:milli" json:"createdAtMs"` + UpdatedAtMS int64 `gorm:"column:updated_at_ms;autoUpdateTime:milli" json:"updatedAtMs"` } func (AppSplashScreen) TableName() string { diff --git a/server/admin/internal/modules/appconfig/request.go b/server/admin/internal/modules/appconfig/request.go index 63dfb1a3..699238ec 100644 --- a/server/admin/internal/modules/appconfig/request.go +++ b/server/admin/internal/modules/appconfig/request.go @@ -28,17 +28,18 @@ type bannerRequest struct { } type splashScreenRequest struct { - CoverURL string `json:"coverUrl" binding:"required"` - SplashType string `json:"splashType" binding:"required"` - Param string `json:"param"` - Status string `json:"status" binding:"required"` - Platform string `json:"platform" binding:"required"` - SortOrder int `json:"sortOrder"` - RegionID int64 `json:"regionId"` - CountryCode string `json:"countryCode"` - Description string `json:"description"` - StartsAtMs int64 `json:"startsAtMs"` - EndsAtMs int64 `json:"endsAtMs"` + CoverURL string `json:"coverUrl" binding:"required"` + SplashType string `json:"splashType" binding:"required"` + Param string `json:"param"` + Status string `json:"status" binding:"required"` + Platform string `json:"platform" binding:"required"` + SortOrder int `json:"sortOrder"` + RegionID int64 `json:"regionId"` + CountryCode string `json:"countryCode"` + Description string `json:"description"` + DisplayDurationMs int `json:"displayDurationMs"` + StartsAtMs int64 `json:"startsAtMs"` + EndsAtMs int64 `json:"endsAtMs"` } type appVersionRequest struct { diff --git a/server/admin/internal/modules/appconfig/service.go b/server/admin/internal/modules/appconfig/service.go index 1ae84b97..a24fc063 100644 --- a/server/admin/internal/modules/appconfig/service.go +++ b/server/admin/internal/modules/appconfig/service.go @@ -23,6 +23,8 @@ const ( bannerStatusActive = "active" bannerStatusDisabled = "disabled" bannerStatusExpired = "expired" + + splashDefaultDisplayDurationMS = 3000 ) var bannerDisplayScopeOrder = []string{bannerDisplayScopeHome, bannerDisplayScopeRoom, bannerDisplayScopeRecharge} @@ -60,21 +62,22 @@ type AppBanner struct { } type AppSplashScreen struct { - ID uint `json:"id"` - AppCode string `json:"appCode"` - CoverURL string `json:"coverUrl"` - SplashType string `json:"splashType"` - Param string `json:"param"` - Status string `json:"status"` - Platform string `json:"platform"` - SortOrder int `json:"sortOrder"` - RegionID int64 `json:"regionId"` - CountryCode string `json:"countryCode"` - Description string `json:"description"` - StartsAtMs int64 `json:"startsAtMs"` - EndsAtMs int64 `json:"endsAtMs"` - CreatedAtMs int64 `json:"createdAtMs"` - UpdatedAtMs int64 `json:"updatedAtMs"` + ID uint `json:"id"` + AppCode string `json:"appCode"` + CoverURL string `json:"coverUrl"` + SplashType string `json:"splashType"` + Param string `json:"param"` + Status string `json:"status"` + Platform string `json:"platform"` + SortOrder int `json:"sortOrder"` + RegionID int64 `json:"regionId"` + CountryCode string `json:"countryCode"` + Description string `json:"description"` + DisplayDurationMs int `json:"displayDurationMs"` + StartsAtMs int64 `json:"startsAtMs"` + EndsAtMs int64 `json:"endsAtMs"` + CreatedAtMs int64 `json:"createdAtMs"` + UpdatedAtMs int64 `json:"updatedAtMs"` } type AppVersion struct { @@ -295,6 +298,7 @@ func (s *AppConfigService) UpdateSplashScreen(appCode string, id uint, req splas item.RegionID = updated.RegionID item.CountryCode = updated.CountryCode item.Description = updated.Description + item.DisplayDurationMS = updated.DisplayDurationMS item.StartsAtMS = updated.StartsAtMS item.EndsAtMS = updated.EndsAtMS if err := s.store.UpdateAppSplashScreen(&item); err != nil { @@ -579,18 +583,19 @@ func appBannerFromModel(item model.AppBanner) AppBanner { func splashScreenModelFromRequest(appCode string, req splashScreenRequest) (model.AppSplashScreen, error) { item := model.AppSplashScreen{ - AppCode: appctx.Normalize(appCode), - CoverURL: strings.TrimSpace(req.CoverURL), - SplashType: normalizeSplashType(req.SplashType), - Param: strings.TrimSpace(req.Param), - Status: normalizeBannerStatus(req.Status), - Platform: normalizeBannerPlatform(req.Platform), - SortOrder: req.SortOrder, - RegionID: req.RegionID, - CountryCode: normalizeCountryCode(req.CountryCode), - Description: strings.TrimSpace(req.Description), - StartsAtMS: req.StartsAtMs, - EndsAtMS: req.EndsAtMs, + AppCode: appctx.Normalize(appCode), + CoverURL: strings.TrimSpace(req.CoverURL), + SplashType: normalizeSplashType(req.SplashType), + Param: strings.TrimSpace(req.Param), + Status: normalizeBannerStatus(req.Status), + Platform: normalizeBannerPlatform(req.Platform), + SortOrder: req.SortOrder, + RegionID: req.RegionID, + CountryCode: normalizeCountryCode(req.CountryCode), + Description: strings.TrimSpace(req.Description), + DisplayDurationMS: normalizeSplashDisplayDurationMS(req.DisplayDurationMs), + StartsAtMS: req.StartsAtMs, + EndsAtMS: req.EndsAtMs, } if item.CoverURL == "" || len(item.CoverURL) > 1024 { return model.AppSplashScreen{}, errors.New("splash cover is invalid") @@ -638,27 +643,31 @@ func splashScreenModelFromRequest(appCode string, req splashScreenRequest) (mode if utf8.RuneCountInString(item.Description) > 255 { return model.AppSplashScreen{}, errors.New("splash description is too long") } + if req.DisplayDurationMs < 0 { + return model.AppSplashScreen{}, errors.New("splash display duration is invalid") + } return item, nil } func appSplashScreenFromModel(item model.AppSplashScreen) AppSplashScreen { // DTO 只返回后台页面需要的字段,不暴露数据库列名;gateway 另有 snake_case 的 App 输出结构。 return AppSplashScreen{ - ID: item.ID, - AppCode: item.AppCode, - CoverURL: item.CoverURL, - SplashType: item.SplashType, - Param: item.Param, - Status: item.Status, - Platform: item.Platform, - SortOrder: item.SortOrder, - RegionID: item.RegionID, - CountryCode: item.CountryCode, - Description: item.Description, - StartsAtMs: item.StartsAtMS, - EndsAtMs: item.EndsAtMS, - CreatedAtMs: item.CreatedAtMS, - UpdatedAtMs: item.UpdatedAtMS, + ID: item.ID, + AppCode: item.AppCode, + CoverURL: item.CoverURL, + SplashType: item.SplashType, + Param: item.Param, + Status: item.Status, + Platform: item.Platform, + SortOrder: item.SortOrder, + RegionID: item.RegionID, + CountryCode: item.CountryCode, + Description: item.Description, + DisplayDurationMs: item.DisplayDurationMS, + StartsAtMs: item.StartsAtMS, + EndsAtMs: item.EndsAtMS, + CreatedAtMs: item.CreatedAtMS, + UpdatedAtMs: item.UpdatedAtMS, } } @@ -751,6 +760,14 @@ func normalizeSplashType(value string) string { return strings.ToLower(strings.TrimSpace(value)) } +func normalizeSplashDisplayDurationMS(value int) int { + // 管理端允许 0 表示“客户端默认 3 秒”,空请求和历史数据都统一补成 3000ms,避免 App 侧拿到缺省字段后出现 0 秒闪退式开屏。 + if value == 0 { + return splashDefaultDisplayDurationMS + } + return value +} + func normalizeBannerStatus(value string) string { value = strings.ToLower(strings.TrimSpace(value)) if value == "" { diff --git a/server/admin/internal/modules/appconfig/service_test.go b/server/admin/internal/modules/appconfig/service_test.go index e33a63ef..2c234eac 100644 --- a/server/admin/internal/modules/appconfig/service_test.go +++ b/server/admin/internal/modules/appconfig/service_test.go @@ -108,26 +108,55 @@ func TestSplashScreenModelFromRequestValidatesH5Config(t *testing.T) { startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli() endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond) item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ - CoverURL: "https://cdn.example.com/splash.png", - SplashType: "h5", - Param: "https://h5.example.com/splash", - Status: bannerStatusActive, - Platform: "android", - SortOrder: 7, - RegionID: 1001, - CountryCode: "us", - Description: "launch campaign", - StartsAtMs: startsAtMs, - EndsAtMs: endsAtMs, + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "h5", + Param: "https://h5.example.com/splash", + Status: bannerStatusActive, + Platform: "android", + SortOrder: 7, + RegionID: 1001, + CountryCode: "us", + Description: "launch campaign", + DisplayDurationMs: 4200, + StartsAtMs: startsAtMs, + EndsAtMs: endsAtMs, }) if err != nil { t.Fatalf("splash screen model should be valid: %v", err) } - if item.AppCode != "lalu" || item.SplashType != "h5" || item.CountryCode != "US" || item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs { + if item.AppCode != "lalu" || item.SplashType != "h5" || item.CountryCode != "US" || item.DisplayDurationMS != 4200 || item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs { t.Fatalf("splash screen model mismatch: %+v", item) } } +func TestSplashScreenModelFromRequestDefaultsDisplayDuration(t *testing.T) { + item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "app", + Status: bannerStatusActive, + Platform: "android", + }) + if err != nil { + t.Fatalf("splash screen model should accept missing display duration: %v", err) + } + if item.DisplayDurationMS != splashDefaultDisplayDurationMS { + t.Fatalf("display duration should default to %d, got %+v", splashDefaultDisplayDurationMS, item) + } +} + +func TestSplashScreenModelFromRequestRejectsNegativeDisplayDuration(t *testing.T) { + _, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "app", + Status: bannerStatusActive, + Platform: "android", + DisplayDurationMs: -1, + }) + if err == nil { + t.Fatal("expected negative display duration to fail") + } +} + func TestSplashScreenModelFromRequestRequiresH5Link(t *testing.T) { _, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ CoverURL: "https://cdn.example.com/splash.png", @@ -158,24 +187,25 @@ func TestSplashScreenModelFromRequestExpiresEndedActiveConfig(t *testing.T) { func TestAppSplashScreenFromModelMapsDTOFields(t *testing.T) { item := appSplashScreenFromModel(model.AppSplashScreen{ - ID: 9, - AppCode: "lalu", - CoverURL: "https://cdn.example.com/splash.png", - SplashType: "app", - Param: "profile", - Status: bannerStatusDisabled, - Platform: "ios", - SortOrder: 3, - RegionID: 2002, - CountryCode: "BR", - Description: "disabled campaign", - StartsAtMS: 1700000000000, - EndsAtMS: 1800000000000, - CreatedAtMS: 1690000000000, - UpdatedAtMS: 1700000002000, + ID: 9, + AppCode: "lalu", + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "app", + Param: "profile", + Status: bannerStatusDisabled, + Platform: "ios", + SortOrder: 3, + RegionID: 2002, + CountryCode: "BR", + Description: "disabled campaign", + DisplayDurationMS: 5000, + StartsAtMS: 1700000000000, + EndsAtMS: 1800000000000, + CreatedAtMS: 1690000000000, + UpdatedAtMS: 1700000002000, }) - if item.ID != 9 || item.SplashType != "app" || item.Param != "profile" || item.RegionID != 2002 || item.UpdatedAtMs != 1700000002000 { + if item.ID != 9 || item.SplashType != "app" || item.Param != "profile" || item.RegionID != 2002 || item.DisplayDurationMs != 5000 || item.UpdatedAtMs != 1700000002000 { t.Fatalf("splash screen dto mismatch: %+v", item) } } diff --git a/server/admin/internal/modules/firstrechargereward/handler.go b/server/admin/internal/modules/firstrechargereward/handler.go index 1436aa13..160495c3 100644 --- a/server/admin/internal/modules/firstrechargereward/handler.go +++ b/server/admin/internal/modules/firstrechargereward/handler.go @@ -51,6 +51,9 @@ type tierDTO struct { SortOrder int32 `json:"sort_order"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` + USDMinorAmount int64 `json:"usd_minor_amount"` + GoogleProductID string `json:"google_product_id"` + DiscountPercent int32 `json:"discount_percent"` } type claimDTO struct { @@ -66,6 +69,9 @@ type claimDTO struct { RechargeCoinAmount int64 `json:"recharge_coin_amount"` RechargeSequence int64 `json:"recharge_sequence"` RechargeType string `json:"recharge_type"` + TierUSDMinorAmount int64 `json:"tier_usd_minor_amount"` + GoogleProductID string `json:"google_product_id"` + RechargeUSDMinor int64 `json:"recharge_usd_minor"` Status string `json:"status"` WalletCommandID string `json:"wallet_command_id"` WalletGrantID string `json:"wallet_grant_id"` @@ -86,7 +92,7 @@ type userDTO struct { func (h *Handler) GetConfig(c *gin.Context) { resp, err := h.activity.GetFirstRechargeRewardConfig(c.Request.Context(), &activityv1.GetFirstRechargeRewardConfigRequest{Meta: h.meta(c)}) if err != nil { - response.ServerError(c, "获取首冲奖励配置失败") + response.ServerError(c, "获取首充礼包配置失败") return } response.OK(c, configFromProto(resp.GetConfig())) @@ -95,7 +101,7 @@ func (h *Handler) GetConfig(c *gin.Context) { func (h *Handler) UpdateConfig(c *gin.Context) { var req configRequest if err := c.ShouldBindJSON(&req); err != nil { - response.BadRequest(c, "首冲奖励配置参数不正确") + response.BadRequest(c, "首充礼包配置参数不正确") return } tiers := make([]*activityv1.FirstRechargeRewardTier, 0, len(req.Tiers)) @@ -109,6 +115,9 @@ func (h *Handler) UpdateConfig(c *gin.Context) { ResourceGroupId: tier.ResourceGroupID, Status: strings.TrimSpace(tier.Status), SortOrder: tier.SortOrder, + UsdMinorAmount: tier.USDMinorAmount, + GoogleProductId: strings.TrimSpace(tier.GoogleProductID), + DiscountPercent: tier.DiscountPercent, }) } resp, err := h.activity.UpdateFirstRechargeRewardConfig(c.Request.Context(), &activityv1.UpdateFirstRechargeRewardConfigRequest{ @@ -145,7 +154,7 @@ func (h *Handler) ListClaims(c *gin.Context) { PageSize: int32(options.PageSize), }) if err != nil { - response.ServerError(c, "获取首冲奖励发放记录失败") + response.ServerError(c, "获取首充礼包发放记录失败") return } items := make([]claimDTO, 0, len(resp.GetClaims())) @@ -282,6 +291,9 @@ func tierFromProto(tier *activityv1.FirstRechargeRewardTier) tierDTO { SortOrder: tier.GetSortOrder(), CreatedAtMS: tier.GetCreatedAtMs(), UpdatedAtMS: tier.GetUpdatedAtMs(), + USDMinorAmount: tier.GetUsdMinorAmount(), + GoogleProductID: tier.GetGoogleProductId(), + DiscountPercent: tier.GetDiscountPercent(), } } @@ -301,6 +313,9 @@ func claimFromProto(claim *activityv1.FirstRechargeRewardClaim) claimDTO { RechargeCoinAmount: claim.GetRechargeCoinAmount(), RechargeSequence: claim.GetRechargeSequence(), RechargeType: claim.GetRechargeType(), + TierUSDMinorAmount: claim.GetTierUsdMinorAmount(), + GoogleProductID: claim.GetGoogleProductId(), + RechargeUSDMinor: claim.GetRechargeUsdMinor(), Status: claim.GetStatus(), WalletCommandID: claim.GetWalletCommandId(), WalletGrantID: claim.GetWalletGrantId(), diff --git a/server/admin/internal/modules/gamemanagement/dto.go b/server/admin/internal/modules/gamemanagement/dto.go index 0579e4d3..e6348621 100644 --- a/server/admin/internal/modules/gamemanagement/dto.go +++ b/server/admin/internal/modules/gamemanagement/dto.go @@ -1,6 +1,10 @@ package gamemanagement -import gamev1 "hyapp.local/api/proto/game/v1" +import ( + "strconv" + + gamev1 "hyapp.local/api/proto/game/v1" +) type platformDTO struct { AppCode string `json:"appCode"` @@ -43,6 +47,52 @@ type catalogDTO struct { UpdatedAtMS int64 `json:"updatedAtMs"` } +type diceConfigDTO struct { + AppCode string `json:"appCode"` + GameID string `json:"gameId"` + Status string `json:"status"` + StakeOptions []diceStakeDTO `json:"stakeOptions"` + FeeBPS int32 `json:"feeBps"` + PoolBPS int32 `json:"poolBps"` + MinPlayers int32 `json:"minPlayers"` + MaxPlayers int32 `json:"maxPlayers"` + RobotEnabled bool `json:"robotEnabled"` + RobotMatchWaitMS int64 `json:"robotMatchWaitMs"` + PoolBalanceCoin int64 `json:"poolBalanceCoin"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type diceStakeDTO struct { + StakeCoin int64 `json:"stakeCoin"` + Enabled bool `json:"enabled"` + SortOrder int32 `json:"sortOrder"` +} + +type dicePoolAdjustmentDTO struct { + AdjustmentID string `json:"adjustmentId"` + GameID string `json:"gameId"` + AmountCoin int64 `json:"amountCoin"` + Direction string `json:"direction"` + Reason string `json:"reason"` + BalanceAfter int64 `json:"balanceAfter"` + CreatedAtMS int64 `json:"createdAtMs"` +} + +type diceRobotDTO struct { + AppCode string `json:"appCode"` + GameID string `json:"gameId"` + UserID string `json:"userId"` + UserIDNumber int64 `json:"userIdNumber"` + ShortID string `json:"shortId"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + Status string `json:"status"` + CreatedByAdminID int64 `json:"createdByAdminId"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + func platformFromProto(item *gamev1.GamePlatform) platformDTO { if item == nil { return platformDTO{} @@ -88,3 +138,63 @@ func catalogFromProto(item *gamev1.GameCatalogItem) catalogDTO { UpdatedAtMS: item.GetUpdatedAtMs(), } } + +func diceConfigFromProto(item *gamev1.DiceConfig) diceConfigDTO { + if item == nil { + return diceConfigDTO{} + } + options := make([]diceStakeDTO, 0, len(item.GetStakeOptions())) + for _, option := range item.GetStakeOptions() { + options = append(options, diceStakeDTO{ + StakeCoin: option.GetStakeCoin(), + Enabled: option.GetEnabled(), + SortOrder: option.GetSortOrder(), + }) + } + return diceConfigDTO{ + AppCode: item.GetAppCode(), + GameID: item.GetGameId(), + Status: item.GetStatus(), + StakeOptions: options, + FeeBPS: item.GetFeeBps(), + PoolBPS: item.GetPoolBps(), + MinPlayers: item.GetMinPlayers(), + MaxPlayers: item.GetMaxPlayers(), + RobotEnabled: item.GetRobotEnabled(), + RobotMatchWaitMS: item.GetRobotMatchWaitMs(), + PoolBalanceCoin: item.GetPoolBalanceCoin(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + +func dicePoolAdjustmentFromProto(item *gamev1.DicePoolAdjustment) dicePoolAdjustmentDTO { + if item == nil { + return dicePoolAdjustmentDTO{} + } + return dicePoolAdjustmentDTO{ + AdjustmentID: item.GetAdjustmentId(), + GameID: item.GetGameId(), + AmountCoin: item.GetAmountCoin(), + Direction: item.GetDirection(), + Reason: item.GetReason(), + BalanceAfter: item.GetBalanceAfter(), + CreatedAtMS: item.GetCreatedAtMs(), + } +} + +func diceRobotFromProto(item *gamev1.DiceRobot) diceRobotDTO { + if item == nil { + return diceRobotDTO{} + } + return diceRobotDTO{ + AppCode: item.GetAppCode(), + GameID: item.GetGameId(), + UserID: strconv.FormatInt(item.GetUserId(), 10), + UserIDNumber: item.GetUserId(), + Status: item.GetStatus(), + CreatedByAdminID: item.GetCreatedByAdminId(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} diff --git a/server/admin/internal/modules/gamemanagement/handler.go b/server/admin/internal/modules/gamemanagement/handler.go index d49658af..1a1be7a7 100644 --- a/server/admin/internal/modules/gamemanagement/handler.go +++ b/server/admin/internal/modules/gamemanagement/handler.go @@ -5,6 +5,7 @@ import ( "strings" "hyapp-admin-server/internal/integration/gameclient" + "hyapp-admin-server/internal/integration/userclient" "hyapp-admin-server/internal/modules/shared" "hyapp-admin-server/internal/response" gamev1 "hyapp.local/api/proto/game/v1" @@ -13,12 +14,23 @@ import ( ) type Handler struct { - game gameclient.Client - audit shared.OperationLogger + game gameclient.Client + user userclient.Client + audit shared.OperationLogger + robotAvatar robotAvatarConfig } -func New(game gameclient.Client, audit shared.OperationLogger) *Handler { - return &Handler{game: game, audit: audit} +// Option 只开放模块初始化时的基础设施注入点,避免 handler 方法里散落环境配置读取。 +type Option func(*Handler) + +func New(game gameclient.Client, user userclient.Client, audit shared.OperationLogger, options ...Option) *Handler { + h := &Handler{game: game, user: user, audit: audit, robotAvatar: defaultRobotAvatarConfig()} + for _, option := range options { + if option != nil { + option(h) + } + } + return h } // ListPlatforms 返回后台游戏平台筛选项和平台配置列表。 diff --git a/server/admin/internal/modules/gamemanagement/request.go b/server/admin/internal/modules/gamemanagement/request.go index 41e31b49..3608b561 100644 --- a/server/admin/internal/modules/gamemanagement/request.go +++ b/server/admin/internal/modules/gamemanagement/request.go @@ -47,6 +47,42 @@ type statusRequest struct { Status string `json:"status"` } +type diceConfigRequest struct { + Status string `json:"status"` + StakeOptions []diceStakeReq `json:"stakeOptions"` + FeeBPS int32 `json:"feeBps"` + PoolBPS int32 `json:"poolBps"` + MinPlayers int32 `json:"minPlayers"` + MaxPlayers int32 `json:"maxPlayers"` + RobotEnabled bool `json:"robotEnabled"` + RobotMatchWaitMS int64 `json:"robotMatchWaitMs"` +} + +type diceStakeReq struct { + StakeCoin int64 `json:"stakeCoin"` + Enabled bool `json:"enabled"` + SortOrder int32 `json:"sortOrder"` +} + +type dicePoolAdjustRequest struct { + AmountCoin int64 `json:"amountCoin"` + Direction string `json:"direction"` + Reason string `json:"reason"` +} + +type diceGenerateRobotsRequest struct { + Count int32 `json:"count"` + NicknamePrefix string `json:"nicknamePrefix"` + NicknameLanguage string `json:"nicknameLanguage"` + AvatarURLs []string `json:"avatarUrls"` + Country string `json:"country"` + Gender string `json:"gender"` +} + +type diceRobotStatusRequest struct { + Status string `json:"status"` +} + func (r platformRequest) toProto(platformCode string) *gamev1.GamePlatform { if platformCode == "" { platformCode = r.PlatformCode @@ -86,6 +122,28 @@ func (r catalogRequest) toProto(gameID string) *gamev1.GameCatalogItem { } } +func (r diceConfigRequest) toProto(gameID string) *gamev1.DiceConfig { + options := make([]*gamev1.DiceStakeOption, 0, len(r.StakeOptions)) + for _, option := range r.StakeOptions { + options = append(options, &gamev1.DiceStakeOption{ + StakeCoin: option.StakeCoin, + Enabled: option.Enabled, + SortOrder: option.SortOrder, + }) + } + return &gamev1.DiceConfig{ + GameId: strings.TrimSpace(gameID), + Status: strings.TrimSpace(r.Status), + StakeOptions: options, + FeeBps: r.FeeBPS, + PoolBps: r.PoolBPS, + MinPlayers: r.MinPlayers, + MaxPlayers: r.MaxPlayers, + RobotEnabled: r.RobotEnabled, + RobotMatchWaitMs: r.RobotMatchWaitMS, + } +} + func compactTags(tags []string) []string { out := make([]string, 0, len(tags)) seen := make(map[string]struct{}, len(tags)) diff --git a/server/admin/internal/modules/gamemanagement/robot_avatar.go b/server/admin/internal/modules/gamemanagement/robot_avatar.go new file mode 100644 index 00000000..68e1a8ad --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/robot_avatar.go @@ -0,0 +1,167 @@ +package gamemanagement + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "hyapp-admin-server/internal/platform/idgen" +) + +const ( + robotAvatarDownloadTimeout = 10 * time.Second + robotAvatarMaxBytes = 5 << 20 + robotAvatarSniffBytes = 512 +) + +// ObjectUploader 是游戏管理模块需要的对象存储最小能力,实际生产实现由后台 COS 平台层提供。 +type ObjectUploader interface { + PutObject(ctx context.Context, key string, reader io.Reader, sizeBytes int64, contentType string) (string, error) +} + +type robotAvatarConfig struct { + uploader ObjectUploader + objectPrefix string + httpClient *http.Client +} + +func defaultRobotAvatarConfig() robotAvatarConfig { + return robotAvatarConfig{ + objectPrefix: "admin", + httpClient: &http.Client{Timeout: robotAvatarDownloadTimeout}, + } +} + +// WithRobotAvatarUploader 注入后台已装配的 COS 上传器,机器人创建只拿这个薄接口,不直接依赖 COS SDK。 +func WithRobotAvatarUploader(uploader ObjectUploader, objectPrefix string) Option { + return func(h *Handler) { + h.robotAvatar.uploader = uploader + h.robotAvatar.objectPrefix = cleanRobotAvatarObjectPrefix(objectPrefix) + } +} + +// WithRobotAvatarHTTPClient 只给测试替换下载客户端;生产默认使用带超时的标准 HTTP client。 +func WithRobotAvatarHTTPClient(client *http.Client) Option { + return func(h *Handler) { + if client != nil { + h.robotAvatar.httpClient = client + } + } +} + +func (h *Handler) uploadRobotAvatarFromSource(ctx context.Context, actorID uint, sourceURL string) (string, error) { + sourceURL = strings.TrimSpace(sourceURL) + if sourceURL == "" { + return "", fmt.Errorf("robot avatar source url is empty") + } + if h.robotAvatar.uploader == nil { + return "", fmt.Errorf("robot avatar cos uploader is not configured") + } + data, contentType, err := h.downloadRobotAvatar(ctx, sourceURL) + if err != nil { + return "", err + } + + // 机器人头像先进入后台 COS 前缀,再写入用户资料;App 端只依赖自有 CDN URL,不直接依赖第三方头像站。 + key := buildRobotAvatarObjectKey(h.robotAvatar.objectPrefix, actorID, contentType, time.Now().UTC()) + return h.robotAvatar.uploader.PutObject(ctx, key, bytes.NewReader(data), int64(len(data)), contentType) +} + +func (h *Handler) downloadRobotAvatar(ctx context.Context, sourceURL string) ([]byte, string, error) { + if !validRobotAvatarSourceURL(sourceURL) { + return nil, "", fmt.Errorf("robot avatar source url is invalid") + } + client := h.robotAvatar.httpClient + if client == nil { + client = &http.Client{Timeout: robotAvatarDownloadTimeout} + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, sourceURL, nil) + if err != nil { + return nil, "", fmt.Errorf("build robot avatar request: %w", err) + } + req.Header.Set("User-Agent", "hyapp-admin-server/robot-avatar") + + resp, err := client.Do(req) + if err != nil { + return nil, "", fmt.Errorf("download robot avatar: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { + return nil, "", fmt.Errorf("download robot avatar returned status %d", resp.StatusCode) + } + if resp.ContentLength > robotAvatarMaxBytes { + return nil, "", fmt.Errorf("robot avatar is too large") + } + + data, err := io.ReadAll(io.LimitReader(resp.Body, robotAvatarMaxBytes+1)) + if err != nil { + return nil, "", fmt.Errorf("read robot avatar: %w", err) + } + if len(data) == 0 || len(data) > robotAvatarMaxBytes { + return nil, "", fmt.Errorf("robot avatar size is invalid") + } + + sniffSize := len(data) + if sniffSize > robotAvatarSniffBytes { + sniffSize = robotAvatarSniffBytes + } + contentType, ok := normalizeRobotAvatarContentType(http.DetectContentType(data[:sniffSize]), resp.Header.Get("Content-Type")) + if !ok { + return nil, "", fmt.Errorf("robot avatar content type is unsupported") + } + return data, contentType, nil +} + +func validRobotAvatarSourceURL(raw string) bool { + u, err := url.ParseRequestURI(strings.TrimSpace(raw)) + if err != nil { + return false + } + return (u.Scheme == "https" || u.Scheme == "http") && strings.TrimSpace(u.Host) != "" +} + +func normalizeRobotAvatarContentType(values ...string) (string, bool) { + for _, value := range values { + contentType := strings.ToLower(strings.TrimSpace(strings.Split(value, ";")[0])) + switch contentType { + case "image/jpeg", "image/jpg": + return "image/jpeg", true + case "image/png": + return "image/png", true + case "image/webp": + return "image/webp", true + } + } + return "", false +} + +func buildRobotAvatarObjectKey(objectPrefix string, actorID uint, contentType string, now time.Time) string { + extension := ".jpg" + switch contentType { + case "image/png": + extension = ".png" + case "image/webp": + extension = ".webp" + } + return fmt.Sprintf( + "%s/game-robots/avatars/%d/%s/%s%s", + cleanRobotAvatarObjectPrefix(objectPrefix), + actorID, + now.UTC().Format("20060102"), + idgen.New("avatar"), + extension, + ) +} + +func cleanRobotAvatarObjectPrefix(value string) string { + value = strings.Trim(strings.TrimSpace(value), "/") + if value == "" { + return "admin" + } + return value +} diff --git a/server/admin/internal/modules/gamemanagement/robot_avatar_test.go b/server/admin/internal/modules/gamemanagement/robot_avatar_test.go new file mode 100644 index 00000000..aafe6112 --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/robot_avatar_test.go @@ -0,0 +1,133 @@ +package gamemanagement + +import ( + "context" + "encoding/base64" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + "unicode" +) + +type fakeRobotAvatarUploader struct { + key string + contentType string + data []byte +} + +func (u *fakeRobotAvatarUploader) PutObject(_ context.Context, key string, reader io.Reader, _ int64, contentType string) (string, error) { + data, err := io.ReadAll(reader) + if err != nil { + return "", err + } + u.key = key + u.contentType = contentType + u.data = data + return "https://media.example.com/" + key, nil +} + +func TestUploadRobotAvatarFromSourceUploadsDownloadedImageToCOS(t *testing.T) { + imageBytes, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=") + if err != nil { + t.Fatalf("decode png: %v", err) + } + avatarServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "image/png") + _, _ = w.Write(imageBytes) + })) + defer avatarServer.Close() + + uploader := &fakeRobotAvatarUploader{} + handler := New(nil, nil, nil, WithRobotAvatarUploader(uploader, "admin"), WithRobotAvatarHTTPClient(avatarServer.Client())) + + got, err := handler.uploadRobotAvatarFromSource(t.Context(), 42, avatarServer.URL+"/avatar.png") + if err != nil { + t.Fatalf("uploadRobotAvatarFromSource failed: %v", err) + } + if !strings.HasPrefix(got, "https://media.example.com/admin/game-robots/avatars/42/") { + t.Fatalf("uploaded url = %q", got) + } + if uploader.contentType != "image/png" { + t.Fatalf("contentType = %q, want image/png", uploader.contentType) + } + if string(uploader.data) != string(imageBytes) { + t.Fatalf("uploaded bytes mismatch") + } +} + +func TestRobotNicknameUsesSingleLanguagePresetWithoutDigits(t *testing.T) { + // 预设池是批量创建机器人的基础数据,数量变化会直接影响前端一次创建 200 个机器人时的去重效果。 + if len(defaultArabicRobotNicknames) != 100 { + t.Fatalf("arabic nickname preset count = %d, want 100", len(defaultArabicRobotNicknames)) + } + if len(defaultEnglishRobotNicknames) != 100 { + t.Fatalf("english nickname preset count = %d, want 100", len(defaultEnglishRobotNicknames)) + } + if len(defaultRobotAvatarSources) != 200 { + t.Fatalf("avatar preset count = %d, want 200", len(defaultRobotAvatarSources)) + } + if uniqueCount(defaultRobotAvatarSources) != 200 { + t.Fatalf("avatar preset must keep 200 unique source urls") + } + + englishNames := make([]string, 0, 200) + arabicNames := make([]string, 0, 200) + for index := 0; index < 200; index++ { + english := robotNickname(index, "english") + if !isPureEnglishNickname(english) { + t.Fatalf("english nickname[%d] = %q, want letters only", index, english) + } + englishNames = append(englishNames, english) + arabic := robotNickname(index, "arabic") + if !isPureArabicNickname(arabic) { + t.Fatalf("arabic nickname[%d] = %q, want arabic letters only", index, arabic) + } + arabicNames = append(arabicNames, arabic) + } + if uniqueCount(englishNames) != len(englishNames) { + t.Fatalf("english nickname presets and overflow combinations must keep 200 unique names") + } + if uniqueCount(arabicNames) != len(arabicNames) { + t.Fatalf("arabic nickname presets and overflow combinations must keep 200 unique names") + } + if got := robotNickname(100, "english"); got != "AlexJordan" { + t.Fatalf("english overflow nickname = %q, want pure english combined name", got) + } + if got := robotNickname(100, "arabic"); got != "أميرسيف" { + t.Fatalf("arabic overflow nickname = %q, want pure arabic combined name", got) + } +} + +func isPureEnglishNickname(value string) bool { + if value == "" { + return false + } + for _, item := range value { + if (item < 'A' || item > 'Z') && (item < 'a' || item > 'z') { + return false + } + } + return true +} + +func isPureArabicNickname(value string) bool { + if value == "" { + return false + } + for _, item := range value { + if !unicode.Is(unicode.Arabic, item) { + return false + } + } + return true +} + +func uniqueCount(values []string) int { + seen := make(map[string]struct{}, len(values)) + for _, value := range values { + seen[value] = struct{}{} + } + return len(seen) +} diff --git a/server/admin/internal/modules/gamemanagement/robot_presets.go b/server/admin/internal/modules/gamemanagement/robot_presets.go new file mode 100644 index 00000000..aafcb85b --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/robot_presets.go @@ -0,0 +1,513 @@ +package gamemanagement + +import ( + "strings" + "unicode" +) + +const ( + robotNicknameLanguageArabic = "arabic" + robotNicknameLanguageEnglish = "english" +) + +var defaultRobotAvatarSources = []string{ + "https://randomuser.me/api/portraits/men/0.jpg", + "https://randomuser.me/api/portraits/men/1.jpg", + "https://randomuser.me/api/portraits/men/2.jpg", + "https://randomuser.me/api/portraits/men/3.jpg", + "https://randomuser.me/api/portraits/men/4.jpg", + "https://randomuser.me/api/portraits/men/5.jpg", + "https://randomuser.me/api/portraits/men/6.jpg", + "https://randomuser.me/api/portraits/men/7.jpg", + "https://randomuser.me/api/portraits/men/8.jpg", + "https://randomuser.me/api/portraits/men/9.jpg", + "https://randomuser.me/api/portraits/men/10.jpg", + "https://randomuser.me/api/portraits/men/11.jpg", + "https://randomuser.me/api/portraits/men/12.jpg", + "https://randomuser.me/api/portraits/men/13.jpg", + "https://randomuser.me/api/portraits/men/14.jpg", + "https://randomuser.me/api/portraits/men/15.jpg", + "https://randomuser.me/api/portraits/men/16.jpg", + "https://randomuser.me/api/portraits/men/17.jpg", + "https://randomuser.me/api/portraits/men/18.jpg", + "https://randomuser.me/api/portraits/men/19.jpg", + "https://randomuser.me/api/portraits/men/20.jpg", + "https://randomuser.me/api/portraits/men/21.jpg", + "https://randomuser.me/api/portraits/men/22.jpg", + "https://randomuser.me/api/portraits/men/23.jpg", + "https://randomuser.me/api/portraits/men/24.jpg", + "https://randomuser.me/api/portraits/men/25.jpg", + "https://randomuser.me/api/portraits/men/26.jpg", + "https://randomuser.me/api/portraits/men/27.jpg", + "https://randomuser.me/api/portraits/men/28.jpg", + "https://randomuser.me/api/portraits/men/29.jpg", + "https://randomuser.me/api/portraits/men/30.jpg", + "https://randomuser.me/api/portraits/men/31.jpg", + "https://randomuser.me/api/portraits/men/32.jpg", + "https://randomuser.me/api/portraits/men/33.jpg", + "https://randomuser.me/api/portraits/men/34.jpg", + "https://randomuser.me/api/portraits/men/35.jpg", + "https://randomuser.me/api/portraits/men/36.jpg", + "https://randomuser.me/api/portraits/men/37.jpg", + "https://randomuser.me/api/portraits/men/38.jpg", + "https://randomuser.me/api/portraits/men/39.jpg", + "https://randomuser.me/api/portraits/men/40.jpg", + "https://randomuser.me/api/portraits/men/41.jpg", + "https://randomuser.me/api/portraits/men/42.jpg", + "https://randomuser.me/api/portraits/men/43.jpg", + "https://randomuser.me/api/portraits/men/44.jpg", + "https://randomuser.me/api/portraits/men/45.jpg", + "https://randomuser.me/api/portraits/men/46.jpg", + "https://randomuser.me/api/portraits/men/47.jpg", + "https://randomuser.me/api/portraits/men/48.jpg", + "https://randomuser.me/api/portraits/men/49.jpg", + "https://randomuser.me/api/portraits/men/50.jpg", + "https://randomuser.me/api/portraits/men/51.jpg", + "https://randomuser.me/api/portraits/men/52.jpg", + "https://randomuser.me/api/portraits/men/53.jpg", + "https://randomuser.me/api/portraits/men/54.jpg", + "https://randomuser.me/api/portraits/men/55.jpg", + "https://randomuser.me/api/portraits/men/56.jpg", + "https://randomuser.me/api/portraits/men/57.jpg", + "https://randomuser.me/api/portraits/men/58.jpg", + "https://randomuser.me/api/portraits/men/59.jpg", + "https://randomuser.me/api/portraits/men/60.jpg", + "https://randomuser.me/api/portraits/men/61.jpg", + "https://randomuser.me/api/portraits/men/62.jpg", + "https://randomuser.me/api/portraits/men/63.jpg", + "https://randomuser.me/api/portraits/men/64.jpg", + "https://randomuser.me/api/portraits/men/65.jpg", + "https://randomuser.me/api/portraits/men/66.jpg", + "https://randomuser.me/api/portraits/men/67.jpg", + "https://randomuser.me/api/portraits/men/68.jpg", + "https://randomuser.me/api/portraits/men/69.jpg", + "https://randomuser.me/api/portraits/men/70.jpg", + "https://randomuser.me/api/portraits/men/71.jpg", + "https://randomuser.me/api/portraits/men/72.jpg", + "https://randomuser.me/api/portraits/men/73.jpg", + "https://randomuser.me/api/portraits/men/74.jpg", + "https://randomuser.me/api/portraits/men/75.jpg", + "https://randomuser.me/api/portraits/men/76.jpg", + "https://randomuser.me/api/portraits/men/77.jpg", + "https://randomuser.me/api/portraits/men/78.jpg", + "https://randomuser.me/api/portraits/men/79.jpg", + "https://randomuser.me/api/portraits/men/80.jpg", + "https://randomuser.me/api/portraits/men/81.jpg", + "https://randomuser.me/api/portraits/men/82.jpg", + "https://randomuser.me/api/portraits/men/83.jpg", + "https://randomuser.me/api/portraits/men/84.jpg", + "https://randomuser.me/api/portraits/men/85.jpg", + "https://randomuser.me/api/portraits/men/86.jpg", + "https://randomuser.me/api/portraits/men/87.jpg", + "https://randomuser.me/api/portraits/men/88.jpg", + "https://randomuser.me/api/portraits/men/89.jpg", + "https://randomuser.me/api/portraits/men/90.jpg", + "https://randomuser.me/api/portraits/men/91.jpg", + "https://randomuser.me/api/portraits/men/92.jpg", + "https://randomuser.me/api/portraits/men/93.jpg", + "https://randomuser.me/api/portraits/men/94.jpg", + "https://randomuser.me/api/portraits/men/95.jpg", + "https://randomuser.me/api/portraits/men/96.jpg", + "https://randomuser.me/api/portraits/men/97.jpg", + "https://randomuser.me/api/portraits/men/98.jpg", + "https://randomuser.me/api/portraits/men/99.jpg", + "https://randomuser.me/api/portraits/women/0.jpg", + "https://randomuser.me/api/portraits/women/1.jpg", + "https://randomuser.me/api/portraits/women/2.jpg", + "https://randomuser.me/api/portraits/women/3.jpg", + "https://randomuser.me/api/portraits/women/4.jpg", + "https://randomuser.me/api/portraits/women/5.jpg", + "https://randomuser.me/api/portraits/women/6.jpg", + "https://randomuser.me/api/portraits/women/7.jpg", + "https://randomuser.me/api/portraits/women/8.jpg", + "https://randomuser.me/api/portraits/women/9.jpg", + "https://randomuser.me/api/portraits/women/10.jpg", + "https://randomuser.me/api/portraits/women/11.jpg", + "https://randomuser.me/api/portraits/women/12.jpg", + "https://randomuser.me/api/portraits/women/13.jpg", + "https://randomuser.me/api/portraits/women/14.jpg", + "https://randomuser.me/api/portraits/women/15.jpg", + "https://randomuser.me/api/portraits/women/16.jpg", + "https://randomuser.me/api/portraits/women/17.jpg", + "https://randomuser.me/api/portraits/women/18.jpg", + "https://randomuser.me/api/portraits/women/19.jpg", + "https://randomuser.me/api/portraits/women/20.jpg", + "https://randomuser.me/api/portraits/women/21.jpg", + "https://randomuser.me/api/portraits/women/22.jpg", + "https://randomuser.me/api/portraits/women/23.jpg", + "https://randomuser.me/api/portraits/women/24.jpg", + "https://randomuser.me/api/portraits/women/25.jpg", + "https://randomuser.me/api/portraits/women/26.jpg", + "https://randomuser.me/api/portraits/women/27.jpg", + "https://randomuser.me/api/portraits/women/28.jpg", + "https://randomuser.me/api/portraits/women/29.jpg", + "https://randomuser.me/api/portraits/women/30.jpg", + "https://randomuser.me/api/portraits/women/31.jpg", + "https://randomuser.me/api/portraits/women/32.jpg", + "https://randomuser.me/api/portraits/women/33.jpg", + "https://randomuser.me/api/portraits/women/34.jpg", + "https://randomuser.me/api/portraits/women/35.jpg", + "https://randomuser.me/api/portraits/women/36.jpg", + "https://randomuser.me/api/portraits/women/37.jpg", + "https://randomuser.me/api/portraits/women/38.jpg", + "https://randomuser.me/api/portraits/women/39.jpg", + "https://randomuser.me/api/portraits/women/40.jpg", + "https://randomuser.me/api/portraits/women/41.jpg", + "https://randomuser.me/api/portraits/women/42.jpg", + "https://randomuser.me/api/portraits/women/43.jpg", + "https://randomuser.me/api/portraits/women/44.jpg", + "https://randomuser.me/api/portraits/women/45.jpg", + "https://randomuser.me/api/portraits/women/46.jpg", + "https://randomuser.me/api/portraits/women/47.jpg", + "https://randomuser.me/api/portraits/women/48.jpg", + "https://randomuser.me/api/portraits/women/49.jpg", + "https://randomuser.me/api/portraits/women/50.jpg", + "https://randomuser.me/api/portraits/women/51.jpg", + "https://randomuser.me/api/portraits/women/52.jpg", + "https://randomuser.me/api/portraits/women/53.jpg", + "https://randomuser.me/api/portraits/women/54.jpg", + "https://randomuser.me/api/portraits/women/55.jpg", + "https://randomuser.me/api/portraits/women/56.jpg", + "https://randomuser.me/api/portraits/women/57.jpg", + "https://randomuser.me/api/portraits/women/58.jpg", + "https://randomuser.me/api/portraits/women/59.jpg", + "https://randomuser.me/api/portraits/women/60.jpg", + "https://randomuser.me/api/portraits/women/61.jpg", + "https://randomuser.me/api/portraits/women/62.jpg", + "https://randomuser.me/api/portraits/women/63.jpg", + "https://randomuser.me/api/portraits/women/64.jpg", + "https://randomuser.me/api/portraits/women/65.jpg", + "https://randomuser.me/api/portraits/women/66.jpg", + "https://randomuser.me/api/portraits/women/67.jpg", + "https://randomuser.me/api/portraits/women/68.jpg", + "https://randomuser.me/api/portraits/women/69.jpg", + "https://randomuser.me/api/portraits/women/70.jpg", + "https://randomuser.me/api/portraits/women/71.jpg", + "https://randomuser.me/api/portraits/women/72.jpg", + "https://randomuser.me/api/portraits/women/73.jpg", + "https://randomuser.me/api/portraits/women/74.jpg", + "https://randomuser.me/api/portraits/women/75.jpg", + "https://randomuser.me/api/portraits/women/76.jpg", + "https://randomuser.me/api/portraits/women/77.jpg", + "https://randomuser.me/api/portraits/women/78.jpg", + "https://randomuser.me/api/portraits/women/79.jpg", + "https://randomuser.me/api/portraits/women/80.jpg", + "https://randomuser.me/api/portraits/women/81.jpg", + "https://randomuser.me/api/portraits/women/82.jpg", + "https://randomuser.me/api/portraits/women/83.jpg", + "https://randomuser.me/api/portraits/women/84.jpg", + "https://randomuser.me/api/portraits/women/85.jpg", + "https://randomuser.me/api/portraits/women/86.jpg", + "https://randomuser.me/api/portraits/women/87.jpg", + "https://randomuser.me/api/portraits/women/88.jpg", + "https://randomuser.me/api/portraits/women/89.jpg", + "https://randomuser.me/api/portraits/women/90.jpg", + "https://randomuser.me/api/portraits/women/91.jpg", + "https://randomuser.me/api/portraits/women/92.jpg", + "https://randomuser.me/api/portraits/women/93.jpg", + "https://randomuser.me/api/portraits/women/94.jpg", + "https://randomuser.me/api/portraits/women/95.jpg", + "https://randomuser.me/api/portraits/women/96.jpg", + "https://randomuser.me/api/portraits/women/97.jpg", + "https://randomuser.me/api/portraits/women/98.jpg", + "https://randomuser.me/api/portraits/women/99.jpg", +} + +var defaultArabicRobotNicknames = []string{ + "أمير", + "سيف", + "مالك", + "فارس", + "ليث", + "بدر", + "ماهر", + "رامي", + "كريم", + "سامي", + "عمر", + "يوسف", + "آدم", + "ريان", + "زيد", + "نور", + "ياسين", + "أنس", + "خالد", + "طارق", + "حازم", + "نادر", + "وسيم", + "جواد", + "مروان", + "إياد", + "نبيل", + "هلال", + "تميم", + "سلمان", + "راشد", + "جاسم", + "ماجد", + "سلطان", + "عادل", + "هاني", + "بلال", + "يزن", + "غيث", + "قصي", + "سليم", + "ريان_الليل", + "نجم", + "ملك_اللعبة", + "صقر", + "ذيب", + "أسد", + "الزعيم", + "القناص", + "الأسطورة", + "ظل", + "برق", + "رعد", + "موج", + "نمر", + "شهاب", + "فهد", + "نايف", + "نوف", + "ليان", + "ريم", + "سارة", + "مريم", + "لينا", + "هنا", + "جود", + "تالا", + "دانا", + "لارا", + "ياسمين", + "نوران", + "رؤى", + "سما", + "ملك", + "جنى", + "آية", + "ندى", + "فرح", + "أمل", + "هدى", + "سلمى", + "ليلى", + "رنا", + "نورا", + "ديما", + "مايا", + "ميس", + "رهف", + "شهد", + "غلا", + "لولو", + "وردة", + "قمر", + "نجمة", + "لؤلؤة", + "الملكة", + "أميرة", + "روح", + "حلم", + "بسمة", +} + +var defaultEnglishRobotNicknames = []string{ + "Alex", + "Jordan", + "Taylor", + "Morgan", + "Riley", + "Nova", + "Phoenix", + "Hunter", + "Luna", + "Mia", + "Noah", + "Liam", + "Ava", + "Emma", + "Oliver", + "Elijah", + "James", + "William", + "Benjamin", + "Lucas", + "Mason", + "Ethan", + "Logan", + "Jackson", + "Levi", + "Sebastian", + "Mateo", + "Jack", + "Owen", + "Theodore", + "Aiden", + "Samuel", + "Joseph", + "John", + "David", + "Wyatt", + "Matthew", + "Luke", + "Asher", + "Carter", + "Julian", + "Grayson", + "Leo", + "Jayden", + "Gabriel", + "Isaac", + "Lincoln", + "Anthony", + "Hudson", + "Dylan", + "Ezra", + "Thomas", + "Charles", + "Christopher", + "Jaxon", + "Maverick", + "Josiah", + "Isaiah", + "Andrew", + "Elias", + "Joshua", + "Nathan", + "Caleb", + "Ryan", + "Adrian", + "Miles", + "Eli", + "Nolan", + "Christian", + "Aaron", + "Cameron", + "Ezekiel", + "Colton", + "Luca", + "Landon", + "Hannah", + "Sofia", + "Isabella", + "Amelia", + "Olivia", + "Charlotte", + "Harper", + "Evelyn", + "Abigail", + "Ella", + "Scarlett", + "Grace", + "Chloe", + "Victoria", + "Aria", + "Penelope", + "Layla", + "Zoey", + "Nora", + "Lily", + "Ellie", + "Violet", + "Aurora", + "Stella", + "Hazel", +} + +func normalizeRobotNicknameLanguage(value string) string { + // 后台只开放英语和阿拉伯语两类昵称池;未知值回退英语,避免历史请求继续落到英阿混合池。 + switch strings.ToLower(strings.TrimSpace(value)) { + case "ar", "arabic", "arab": + return robotNicknameLanguageArabic + case "en", "english": + return robotNicknameLanguageEnglish + default: + return robotNicknameLanguageEnglish + } +} + +func robotAccountLanguage(nicknameLanguage string) string { + // 用户资料里的 language 跟昵称池保持一致,后续客户端或运营筛选机器人时不用再反推昵称字符集。 + if normalizeRobotNicknameLanguage(nicknameLanguage) == robotNicknameLanguageArabic { + return "ar" + } + return "en" +} + +func robotNickname(index int, nicknameLanguage string) string { + // 机器人昵称只从单一语言池取值,不能再拼随机 hex,否则会出现数字、下划线和英阿混排。 + language := normalizeRobotNicknameLanguage(nicknameLanguage) + if language == robotNicknameLanguageArabic { + return robotNicknameFromPool(index, defaultArabicRobotNicknames, "لاعب", language) + } + return robotNicknameFromPool(index, defaultEnglishRobotNicknames, "Player", language) +} + +func robotNicknameFromPool(index int, pool []string, fallback string, language string) string { + // 前 100 个直接使用预设名;超过预设数量时用同语言两个预设名拼接,保证批量 200 个仍然不带数字后缀。 + if index < 0 { + index = 0 + } + if len(pool) == 0 { + return fallback + } + base := sanitizeRobotNickname(pool[index%len(pool)], language) + if base == "" { + base = fallback + } + if index < len(pool) { + return base + } + next := sanitizeRobotNickname(pool[(index%len(pool)+1)%len(pool)], language) + if next == "" || next == base { + next = sanitizeRobotNickname(pool[(index+index/len(pool)+2)%len(pool)], language) + } + if next == "" || next == base { + return base + } + return base + next +} + +func sanitizeRobotNickname(value string, language string) string { + // 预设里可能有旧的分隔符或非目标语言字符;写库前统一剔除,保证“英语纯英语、阿拉伯语纯阿拉伯语”。 + var builder strings.Builder + for _, item := range strings.TrimSpace(value) { + switch language { + case robotNicknameLanguageArabic: + if unicode.Is(unicode.Arabic, item) { + builder.WriteRune(item) + } + default: + if (item >= 'A' && item <= 'Z') || (item >= 'a' && item <= 'z') { + builder.WriteRune(item) + } + } + } + return builder.String() +} + +func robotGender(index int, requested string) string { + requested = strings.TrimSpace(requested) + if requested != "" { + return requested + } + if index%2 == 0 { + return "male" + } + return "female" +} + +func robotAvatarSource(index int, requested []string) string { + if source := pickString(requested, index); source != "" { + return source + } + if len(defaultRobotAvatarSources) == 0 { + return "" + } + return defaultRobotAvatarSources[index%len(defaultRobotAvatarSources)] +} diff --git a/server/admin/internal/modules/gamemanagement/robot_profiles.go b/server/admin/internal/modules/gamemanagement/robot_profiles.go new file mode 100644 index 00000000..d9a2c3ac --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/robot_profiles.go @@ -0,0 +1,42 @@ +package gamemanagement + +import ( + "context" + + "hyapp-admin-server/internal/integration/userclient" +) + +func (h *Handler) enrichDiceRobotProfiles(ctx context.Context, requestID string, items []diceRobotDTO) error { + if h.user == nil || len(items) == 0 { + return nil + } + userIDs := make([]int64, 0, len(items)) + for _, item := range items { + if item.UserIDNumber > 0 { + userIDs = append(userIDs, item.UserIDNumber) + } + } + if len(userIDs) == 0 { + return nil + } + + // 机器人登记表只拥有游戏侧启停事实;头像、昵称和短 ID 仍以 user-service 用户主数据为准。 + users, err := h.user.BatchGetUsers(ctx, userclient.BatchGetUsersRequest{ + RequestID: requestID, + Caller: "admin-server", + UserIDs: userIDs, + }) + if err != nil { + return err + } + for index := range items { + user := users[items[index].UserIDNumber] + if user == nil { + continue + } + items[index].ShortID = user.DisplayUserID + items[index].Nickname = user.Username + items[index].Avatar = user.Avatar + } + return nil +} diff --git a/server/admin/internal/modules/gamemanagement/routes.go b/server/admin/internal/modules/gamemanagement/routes.go index d9ab9fe5..371935b8 100644 --- a/server/admin/internal/modules/gamemanagement/routes.go +++ b/server/admin/internal/modules/gamemanagement/routes.go @@ -20,4 +20,11 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.PATCH("/admin/game/games/:game_id", middleware.RequirePermission("game:update"), h.UpdateCatalog) protected.PATCH("/admin/game/games/:game_id/status", middleware.RequirePermission("game:status"), h.SetGameStatus) protected.DELETE("/admin/game/games/:game_id", middleware.RequireAnyPermission("game:delete", "game:update"), h.DeleteCatalog) + protected.GET("/admin/game/self-games", middleware.RequirePermission("game:view"), h.ListSelfGames) + protected.PATCH("/admin/game/self-games/:game_id/config", middleware.RequirePermission("game:update"), h.UpdateDiceConfig) + protected.POST("/admin/game/self-games/:game_id/pool/adjust", middleware.RequirePermission("game:update"), h.AdjustDicePool) + protected.GET("/admin/game/robots", middleware.RequirePermission("game:view"), h.ListDiceRobots) + protected.POST("/admin/game/robots/generate", middleware.RequirePermission("game:create"), h.GenerateDiceRobots) + protected.PATCH("/admin/game/robots/:user_id/status", middleware.RequirePermission("game:update"), h.SetDiceRobotStatus) + protected.DELETE("/admin/game/robots/:user_id", middleware.RequireAnyPermission("game:delete", "game:update"), h.DeleteDiceRobot) } diff --git a/server/admin/internal/modules/gamemanagement/self_game_handler.go b/server/admin/internal/modules/gamemanagement/self_game_handler.go new file mode 100644 index 00000000..ce89a5b3 --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/self_game_handler.go @@ -0,0 +1,262 @@ +package gamemanagement + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "strings" + + "hyapp-admin-server/internal/integration/userclient" + "hyapp-admin-server/internal/middleware" + "hyapp-admin-server/internal/response" + gamev1 "hyapp.local/api/proto/game/v1" + + "github.com/gin-gonic/gin" +) + +func (h *Handler) ListSelfGames(c *gin.Context) { + resp, err := h.game.ListSelfGames(c.Request.Context(), &gamev1.ListSelfGamesRequest{Meta: requestMeta(c)}) + if err != nil { + response.ServerError(c, "获取自研游戏失败") + return + } + items := make([]diceConfigDTO, 0, len(resp.GetGames())) + for _, item := range resp.GetGames() { + items = append(items, diceConfigFromProto(item)) + } + response.OK(c, gin.H{"items": items, "serverTimeMs": resp.GetServerTimeMs()}) +} + +func (h *Handler) UpdateDiceConfig(c *gin.Context) { + gameID := strings.TrimSpace(c.Param("game_id")) + if gameID == "" { + response.BadRequest(c, "游戏 ID 参数不正确") + return + } + var req diceConfigRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "骰子配置参数不正确") + return + } + resp, err := h.game.UpdateDiceConfig(c.Request.Context(), &gamev1.UpdateDiceConfigRequest{ + Meta: requestMeta(c), + Config: req.toProto(gameID), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := diceConfigFromProto(resp.GetConfig()) + h.auditLog(c, "update-dice-config", "game_self_game_configs", item.GameID, item.Status) + response.OK(c, item) +} + +func (h *Handler) AdjustDicePool(c *gin.Context) { + gameID := strings.TrimSpace(c.Param("game_id")) + if gameID == "" { + response.BadRequest(c, "游戏 ID 参数不正确") + return + } + var req dicePoolAdjustRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "奖池调整参数不正确") + return + } + resp, err := h.game.AdjustDicePool(c.Request.Context(), &gamev1.AdjustDicePoolRequest{ + Meta: requestMeta(c), + GameId: gameID, + AmountCoin: req.AmountCoin, + Direction: strings.TrimSpace(req.Direction), + Reason: strings.TrimSpace(req.Reason), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + h.auditLog(c, "adjust-dice-pool", "game_self_game_pools", gameID, fmt.Sprintf("%s:%d", req.Direction, req.AmountCoin)) + response.OK(c, gin.H{"adjustment": dicePoolAdjustmentFromProto(resp.GetAdjustment()), "config": diceConfigFromProto(resp.GetConfig())}) +} + +func (h *Handler) ListDiceRobots(c *gin.Context) { + pageSize := int32(parsePositiveInt(firstQuery(c, "pageSize", "page_size"), 50)) + requestID := middleware.CurrentRequestID(c) + resp, err := h.game.ListDiceRobots(c.Request.Context(), &gamev1.ListDiceRobotsRequest{ + Meta: requestMeta(c), + GameId: strings.TrimSpace(firstQuery(c, "gameId", "game_id")), + Status: strings.TrimSpace(c.Query("status")), + PageSize: pageSize, + Cursor: strings.TrimSpace(c.Query("cursor")), + }) + if err != nil { + response.ServerError(c, "获取游戏机器人失败") + return + } + items := make([]diceRobotDTO, 0, len(resp.GetRobots())) + for _, item := range resp.GetRobots() { + items = append(items, diceRobotFromProto(item)) + } + if err := h.enrichDiceRobotProfiles(c.Request.Context(), requestID, items); err != nil { + response.ServerError(c, "获取机器人用户资料失败") + return + } + response.OK(c, gin.H{"items": items, "nextCursor": resp.GetNextCursor(), "pageSize": pageSize, "serverTimeMs": resp.GetServerTimeMs()}) +} + +func (h *Handler) GenerateDiceRobots(c *gin.Context) { + if h.user == nil { + response.ServerError(c, "用户服务不可用") + return + } + var req diceGenerateRobotsRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "机器人生成参数不正确") + return + } + if req.Count <= 0 { + req.Count = 20 + } + if req.Count > 200 { + req.Count = 200 + } + nicknameLanguage := normalizeRobotNicknameLanguage(req.NicknameLanguage) + country := strings.TrimSpace(req.Country) + if country == "" { + country = "SA" + } + userIDs := make([]int64, 0, req.Count) + requestID := middleware.CurrentRequestID(c) + actorID := middleware.CurrentUserID(c) + avatarCache := make(map[string]string) + for index := int32(0); index < req.Count; index++ { + // 批量机器人必须一次性写齐完整资料;同一请求内相同头像源只转存一次,避免 200 个账号重复下载上传同一张图。 + avatarSource := robotAvatarSource(int(index), req.AvatarURLs) + avatarURL := avatarCache[avatarSource] + if avatarURL == "" { + var err error + avatarURL, err = h.uploadRobotAvatarFromSource(c.Request.Context(), actorID, avatarSource) + if err != nil { + response.BadRequest(c, "上传机器人头像失败: "+err.Error()) + return + } + avatarCache[avatarSource] = avatarURL + } + created, err := h.user.QuickCreateAccount(c.Request.Context(), userclient.QuickCreateAccountRequest{ + RequestID: requestID, + Caller: "admin-server", + Password: randomRobotPassword(), + Username: robotNickname(int(index), nicknameLanguage), + Avatar: avatarURL, + Gender: robotGender(int(index), req.Gender), + Country: country, + DeviceID: "game-robot-" + randomRobotHex(8), + Source: "game_robot", + InstallChannel: "admin", + Platform: "android", + Language: robotAccountLanguage(nicknameLanguage), + Timezone: "UTC", + }) + if 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), + GameId: strings.TrimSpace(firstQuery(c, "gameId", "game_id")), + UserIds: userIDs, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + items := make([]diceRobotDTO, 0, len(resp.GetRobots())) + for _, item := range resp.GetRobots() { + items = append(items, diceRobotFromProto(item)) + } + if err := h.enrichDiceRobotProfiles(c.Request.Context(), requestID, items); err != nil { + response.ServerError(c, "获取机器人用户资料失败") + return + } + h.auditLog(c, "generate-dice-robots", "game_self_game_robots", "dice", fmt.Sprintf("count=%d", len(userIDs))) + response.OK(c, gin.H{"created": len(userIDs), "items": items}) +} + +func (h *Handler) SetDiceRobotStatus(c *gin.Context) { + gameID := strings.TrimSpace(firstQuery(c, "gameId", "game_id")) + if gameID == "" { + gameID = "dice" + } + userID, err := parseInt64Param(c.Param("user_id")) + if err != nil || userID <= 0 { + response.BadRequest(c, "机器人用户 ID 参数不正确") + return + } + var req diceRobotStatusRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "机器人状态参数不正确") + return + } + resp, err := h.game.SetDiceRobotStatus(c.Request.Context(), &gamev1.SetDiceRobotStatusRequest{ + Meta: requestMeta(c), + GameId: gameID, + UserId: userID, + Status: strings.TrimSpace(req.Status), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := diceRobotFromProto(resp.GetRobot()) + h.auditLog(c, "set-dice-robot-status", "game_self_game_robots", item.UserID, item.Status) + response.OK(c, item) +} + +// DeleteDiceRobot 删除当前自研游戏的机器人登记,后台只移除可匹配机器人池,不触碰真实用户资料。 +func (h *Handler) DeleteDiceRobot(c *gin.Context) { + gameID := strings.TrimSpace(firstQuery(c, "gameId", "game_id")) + if gameID == "" { + gameID = "dice" + } + userID, err := parseInt64Param(c.Param("user_id")) + if err != nil || userID <= 0 { + response.BadRequest(c, "机器人用户 ID 参数不正确") + return + } + // 后台删除只撤销 game-service 的机器人登记;真实用户仍保留在 user-service,历史对局和钱包流水可以继续按 user_id 追溯。 + if _, err := h.game.DeleteDiceRobot(c.Request.Context(), &gamev1.DeleteDiceRobotRequest{ + Meta: requestMeta(c), + GameId: gameID, + UserId: userID, + }); err != nil { + response.BadRequest(c, err.Error()) + return + } + h.auditLog(c, "delete-dice-robot", "game_self_game_robots", fmt.Sprintf("%d", userID), gameID) + response.OK(c, gin.H{"deleted": true}) +} + +func randomRobotPassword() string { + return "Robot#" + randomRobotHex(12) +} + +func randomRobotHex(size int) string { + buf := make([]byte, size) + if _, err := rand.Read(buf); err != nil { + return "fallback" + } + return hex.EncodeToString(buf) +} + +func pickString(values []string, index int) string { + if len(values) == 0 { + return "" + } + return strings.TrimSpace(values[index%len(values)]) +} + +func parseInt64Param(raw string) (int64, error) { + var value int64 + _, err := fmt.Sscan(strings.TrimSpace(raw), &value) + return value, err +} diff --git a/server/admin/internal/modules/payment/dto.go b/server/admin/internal/modules/payment/dto.go index dd2a210f..11b69891 100644 --- a/server/admin/internal/modules/payment/dto.go +++ b/server/admin/internal/modules/payment/dto.go @@ -47,6 +47,7 @@ type rechargeProductDTO struct { ProductCode string `json:"productCode"` ProductName string `json:"productName"` Description string `json:"description"` + AudienceType string `json:"audienceType"` Platform string `json:"platform"` Channel string `json:"channel"` CurrencyCode string `json:"currencyCode"` @@ -67,6 +68,34 @@ type rechargeProductDTO struct { UpdatedAtMS int64 `json:"updatedAtMs"` } +type thirdPartyPaymentChannelDTO struct { + AppCode string `json:"appCode"` + ProviderCode string `json:"providerCode"` + ProviderName string `json:"providerName"` + Status string `json:"status"` + SortOrder int32 `json:"sortOrder"` + Methods []thirdPartyPaymentMethodDTO `json:"methods"` +} + +type thirdPartyPaymentMethodDTO struct { + MethodID int64 `json:"methodId"` + AppCode string `json:"appCode"` + ProviderCode string `json:"providerCode"` + ProviderName string `json:"providerName"` + CountryCode string `json:"countryCode"` + CountryName string `json:"countryName"` + CurrencyCode string `json:"currencyCode"` + PayWay string `json:"payWay"` + PayType string `json:"payType"` + MethodName string `json:"methodName"` + LogoURL string `json:"logoUrl"` + Status string `json:"status"` + USDToCurrencyRate string `json:"usdToCurrencyRate"` + SortOrder int32 `json:"sortOrder"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + func rechargeBillFromProto(item *walletv1.RechargeBill) rechargeBillDTO { if item == nil { return rechargeBillDTO{} @@ -154,6 +183,7 @@ func rechargeProductFromProto(item *walletv1.RechargeProduct) rechargeProductDTO ProductCode: item.GetProductCode(), ProductName: item.GetProductName(), Description: item.GetDescription(), + AudienceType: item.GetAudienceType(), Platform: item.GetPlatform(), Channel: item.GetChannel(), CurrencyCode: item.GetCurrencyCode(), @@ -175,6 +205,48 @@ func rechargeProductFromProto(item *walletv1.RechargeProduct) rechargeProductDTO } } +func thirdPartyPaymentChannelFromProto(item *walletv1.ThirdPartyPaymentChannel) thirdPartyPaymentChannelDTO { + if item == nil { + return thirdPartyPaymentChannelDTO{} + } + dto := thirdPartyPaymentChannelDTO{ + AppCode: item.GetAppCode(), + ProviderCode: item.GetProviderCode(), + ProviderName: item.GetProviderName(), + Status: item.GetStatus(), + SortOrder: item.GetSortOrder(), + Methods: make([]thirdPartyPaymentMethodDTO, 0, len(item.GetMethods())), + } + for _, method := range item.GetMethods() { + dto.Methods = append(dto.Methods, thirdPartyPaymentMethodFromProto(method)) + } + return dto +} + +func thirdPartyPaymentMethodFromProto(item *walletv1.ThirdPartyPaymentMethod) thirdPartyPaymentMethodDTO { + if item == nil { + return thirdPartyPaymentMethodDTO{} + } + return thirdPartyPaymentMethodDTO{ + MethodID: item.GetMethodId(), + AppCode: item.GetAppCode(), + ProviderCode: item.GetProviderCode(), + ProviderName: item.GetProviderName(), + CountryCode: item.GetCountryCode(), + CountryName: item.GetCountryName(), + CurrencyCode: item.GetCurrencyCode(), + PayWay: item.GetPayWay(), + PayType: item.GetPayType(), + MethodName: item.GetMethodName(), + LogoURL: item.GetLogoUrl(), + Status: item.GetStatus(), + USDToCurrencyRate: item.GetUsdToCurrencyRate(), + SortOrder: item.GetSortOrder(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + func formatUSDTMicro(amount int64) string { if amount <= 0 { return "0" diff --git a/server/admin/internal/modules/payment/handler.go b/server/admin/internal/modules/payment/handler.go index 40c61514..a138e506 100644 --- a/server/admin/internal/modules/payment/handler.go +++ b/server/admin/internal/modules/payment/handler.go @@ -130,14 +130,15 @@ func (h *Handler) loadRechargeBillUsers(ctx context.Context, appCode string, use func (h *Handler) ListRechargeProducts(c *gin.Context) { options := shared.ListOptions(c) resp, err := h.wallet.ListAdminRechargeProducts(c.Request.Context(), &walletv1.ListAdminRechargeProductsRequest{ - RequestId: middleware.CurrentRequestID(c), - AppCode: appctx.FromContext(c.Request.Context()), - Status: strings.TrimSpace(firstQuery(c, "status")), - Platform: strings.TrimSpace(firstQuery(c, "platform")), - RegionId: queryInt64(c, "region_id", "regionId"), - Keyword: options.Keyword, - Page: int32(options.Page), - PageSize: int32(options.PageSize), + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + Status: strings.TrimSpace(firstQuery(c, "status")), + Platform: strings.TrimSpace(firstQuery(c, "platform")), + RegionId: queryInt64(c, "region_id", "regionId"), + AudienceType: strings.TrimSpace(firstQuery(c, "audience_type", "audienceType")), + Keyword: options.Keyword, + Page: int32(options.Page), + PageSize: int32(options.PageSize), }) if err != nil { writeWalletError(c, err, "获取内购配置失败") @@ -169,6 +170,7 @@ func (h *Handler) CreateRechargeProduct(c *gin.Context) { ProductName: strings.TrimSpace(request.ProductName), Description: strings.TrimSpace(request.Description), Platform: strings.TrimSpace(request.Platform), + AudienceType: strings.TrimSpace(request.AudienceType), RegionIds: request.RegionIDs, Enabled: request.Enabled, OperatorUserId: actorID(c), @@ -206,6 +208,7 @@ func (h *Handler) UpdateRechargeProduct(c *gin.Context) { ProductName: strings.TrimSpace(request.ProductName), Description: strings.TrimSpace(request.Description), Platform: strings.TrimSpace(request.Platform), + AudienceType: strings.TrimSpace(request.AudienceType), RegionIds: request.RegionIDs, Enabled: request.Enabled, OperatorUserId: actorID(c), @@ -238,17 +241,99 @@ func (h *Handler) DeleteRechargeProduct(c *gin.Context) { response.OK(c, gin.H{"deleted": true}) } +func (h *Handler) ListThirdPartyPaymentChannels(c *gin.Context) { + resp, err := h.wallet.ListThirdPartyPaymentChannels(c.Request.Context(), &walletv1.ListThirdPartyPaymentChannelsRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + ProviderCode: strings.TrimSpace(firstQuery(c, "provider_code", "providerCode")), + Status: strings.TrimSpace(firstQuery(c, "status")), + IncludeDisabledMethods: true, + }) + if err != nil { + writeWalletError(c, err, "获取三方支付渠道失败") + return + } + items := make([]thirdPartyPaymentChannelDTO, 0, len(resp.GetChannels())) + for _, item := range resp.GetChannels() { + items = append(items, thirdPartyPaymentChannelFromProto(item)) + } + response.OK(c, gin.H{"items": items, "total": len(items)}) +} + +func (h *Handler) SetThirdPartyPaymentMethodStatus(c *gin.Context) { + methodID := queryPathInt64(c, "method_id") + if methodID <= 0 { + response.BadRequest(c, "支付方式ID不正确") + return + } + var request thirdPartyPaymentMethodStatusRequest + if err := c.ShouldBindJSON(&request); err != nil { + response.BadRequest(c, "支付方式状态参数不正确") + return + } + resp, err := h.wallet.SetThirdPartyPaymentMethodStatus(c.Request.Context(), &walletv1.SetThirdPartyPaymentMethodStatusRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + MethodId: methodID, + Enabled: request.Enabled, + OperatorUserId: actorID(c), + }) + if err != nil { + writeWalletError(c, err, "修改支付方式状态失败") + return + } + item := thirdPartyPaymentMethodFromProto(resp.GetMethod()) + shared.OperationLogWithResourceID(c, h.audit, "update-third-party-payment-method-status", "third_party_payment_methods", strconv.FormatInt(methodID, 10), "success", item.Status) + response.OK(c, item) +} + +func (h *Handler) UpdateThirdPartyPaymentRate(c *gin.Context) { + methodID := queryPathInt64(c, "method_id") + if methodID <= 0 { + response.BadRequest(c, "支付方式ID不正确") + return + } + var request thirdPartyPaymentRateRequest + if err := c.ShouldBindJSON(&request); err != nil || strings.TrimSpace(request.USDToCurrencyRate) == "" { + response.BadRequest(c, "支付汇率参数不正确") + return + } + resp, err := h.wallet.UpdateThirdPartyPaymentRate(c.Request.Context(), &walletv1.UpdateThirdPartyPaymentRateRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + MethodId: methodID, + UsdToCurrencyRate: strings.TrimSpace(request.USDToCurrencyRate), + OperatorUserId: actorID(c), + }) + if err != nil { + writeWalletError(c, err, "修改支付方式汇率失败") + return + } + item := thirdPartyPaymentMethodFromProto(resp.GetMethod()) + shared.OperationLogWithResourceID(c, h.audit, "update-third-party-payment-rate", "third_party_payment_methods", strconv.FormatInt(methodID, 10), "success", item.USDToCurrencyRate) + response.OK(c, item) +} + type rechargeProductRequest struct { AmountUSDT string `json:"amountUsdt"` AmountUSDTMicro int64 `json:"amountUsdtMicro"` CoinAmount int64 `json:"coinAmount"` ProductName string `json:"productName"` Description string `json:"description"` + AudienceType string `json:"audienceType"` Platform string `json:"platform"` RegionIDs []int64 `json:"regionIds"` Enabled bool `json:"enabled"` } +type thirdPartyPaymentMethodStatusRequest struct { + Enabled bool `json:"enabled"` +} + +type thirdPartyPaymentRateRequest struct { + USDToCurrencyRate string `json:"usdToCurrencyRate"` +} + func queryInt64(c *gin.Context, keys ...string) int64 { value := strings.TrimSpace(firstQuery(c, keys...)) if value == "" { @@ -270,6 +355,15 @@ func firstQuery(c *gin.Context, keys ...string) string { return "" } +func queryPathInt64(c *gin.Context, name string) int64 { + value := strings.TrimSpace(c.Param(name)) + parsed, err := strconv.ParseInt(value, 10, 64) + if err != nil || parsed <= 0 { + return 0 + } + return parsed +} + func productID(c *gin.Context) (int64, bool) { value := strings.TrimSpace(c.Param("product_id")) parsed, err := strconv.ParseInt(value, 10, 64) diff --git a/server/admin/internal/modules/payment/routes.go b/server/admin/internal/modules/payment/routes.go index 0a20785e..8392b04f 100644 --- a/server/admin/internal/modules/payment/routes.go +++ b/server/admin/internal/modules/payment/routes.go @@ -12,6 +12,9 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { } protected.GET("/admin/payment/recharge-bills", middleware.RequirePermission("payment-bill:view"), h.ListRechargeBills) + protected.GET("/admin/payment/third-party-channels", middleware.RequirePermission("payment-third-party:view"), h.ListThirdPartyPaymentChannels) + protected.PATCH("/admin/payment/third-party-methods/:method_id/status", middleware.RequirePermission("payment-third-party:update"), h.SetThirdPartyPaymentMethodStatus) + protected.PATCH("/admin/payment/third-party-rates/:method_id", middleware.RequirePermission("payment-third-party:update"), h.UpdateThirdPartyPaymentRate) protected.GET("/admin/payment/recharge-products", middleware.RequirePermission("payment-product:view"), h.ListRechargeProducts) protected.POST("/admin/payment/recharge-products", middleware.RequirePermission("payment-product:create"), h.CreateRechargeProduct) protected.PUT("/admin/payment/recharge-products/:product_id", middleware.RequirePermission("payment-product:update"), h.UpdateRechargeProduct) diff --git a/server/admin/internal/modules/prettyid/handler.go b/server/admin/internal/modules/prettyid/handler.go new file mode 100644 index 00000000..44fc65c4 --- /dev/null +++ b/server/admin/internal/modules/prettyid/handler.go @@ -0,0 +1,289 @@ +package prettyid + +import ( + "strconv" + "strings" + + "hyapp-admin-server/internal/integration/userclient" + "hyapp-admin-server/internal/middleware" + "hyapp-admin-server/internal/modules/shared" + "hyapp-admin-server/internal/response" + + "github.com/gin-gonic/gin" +) + +type Handler struct { + user userclient.Client + audit shared.OperationLogger +} + +func New(user userclient.Client, audit shared.OperationLogger) *Handler { + return &Handler{user: user, audit: audit} +} + +type poolRequest struct { + Name string `json:"name"` + LevelTrack string `json:"level_track"` + MinLevel int32 `json:"min_level"` + MaxLevel int32 `json:"max_level"` + RuleType string `json:"rule_type"` + RuleConfigJSON string `json:"rule_config_json"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` +} + +type generateRequest struct { + RuleType string `json:"rule_type"` + RuleConfigJSON string `json:"rule_config_json"` + Count int32 `json:"count"` +} + +type grantRequest struct { + TargetUserID any `json:"target_user_id"` + DisplayUserID string `json:"display_user_id"` + DurationMs int64 `json:"duration_ms"` + Reason string `json:"reason"` +} + +type statusRequest struct { + Status string `json:"status"` + Reason string `json:"reason"` +} + +func (h *Handler) ListPools(c *gin.Context) { + page, pageSize, ok := pageQuery(c) + if !ok { + response.BadRequest(c, "分页参数不正确") + return + } + // admin-server 只解析 HTTP 查询参数和当前管理员身份,池状态、等级轨道合法性由 user-service 统一校验。 + items, total, err := h.user.ListPrettyDisplayIDPools(c.Request.Context(), userclient.ListPrettyDisplayIDPoolsRequest{ + RequestID: middleware.CurrentRequestID(c), + Caller: "admin-server", + Status: strings.TrimSpace(c.Query("status")), + LevelTrack: strings.TrimSpace(c.Query("level_track")), + Page: page, + PageSize: pageSize, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + response.OK(c, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize}) +} + +func (h *Handler) CreatePool(c *gin.Context) { + var req poolRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "靓号池参数不正确") + return + } + // 创建池时操作者从后台登录态读取,不能信任请求体里的管理员 ID。 + pool, err := h.user.CreatePrettyDisplayIDPool(c.Request.Context(), userclient.PrettyDisplayIDPoolRequest{ + RequestID: middleware.CurrentRequestID(c), + Caller: "admin-server", + Name: req.Name, + LevelTrack: req.LevelTrack, + MinLevel: req.MinLevel, + MaxLevel: req.MaxLevel, + RuleType: req.RuleType, + RuleConfigJSON: req.RuleConfigJSON, + Status: req.Status, + SortOrder: req.SortOrder, + OperatorAdminID: int64(middleware.CurrentUserID(c)), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLogWithResourceID(c, h.audit, "create-pretty-id-pool", "pretty_display_id_pools", pool.PoolID, "success", "name="+pool.Name) + response.Created(c, pool) +} + +func (h *Handler) UpdatePool(c *gin.Context) { + var req poolRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "靓号池参数不正确") + return + } + // pool_id 来自路径,body 只承载可编辑字段,避免前端误传 ID 导致更新目标不一致。 + poolID := strings.TrimSpace(c.Param("pool_id")) + pool, err := h.user.UpdatePrettyDisplayIDPool(c.Request.Context(), userclient.PrettyDisplayIDPoolRequest{ + RequestID: middleware.CurrentRequestID(c), + Caller: "admin-server", + PoolID: poolID, + Name: req.Name, + LevelTrack: req.LevelTrack, + MinLevel: req.MinLevel, + MaxLevel: req.MaxLevel, + RuleType: req.RuleType, + RuleConfigJSON: req.RuleConfigJSON, + Status: req.Status, + SortOrder: req.SortOrder, + OperatorAdminID: int64(middleware.CurrentUserID(c)), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLogWithResourceID(c, h.audit, "update-pretty-id-pool", "pretty_display_id_pools", pool.PoolID, "success", "status="+pool.Status) + response.OK(c, pool) +} + +func (h *Handler) Generate(c *gin.Context) { + var req generateRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "生成参数不正确") + return + } + // 生成只从路径读取 pool_id,请求体里的规则和数量会继续交给 user-service 校验并写批次审计。 + poolID := strings.TrimSpace(c.Param("pool_id")) + batch, err := h.user.GeneratePrettyDisplayIDs(c.Request.Context(), userclient.GeneratePrettyDisplayIDsRequest{ + RequestID: middleware.CurrentRequestID(c), + Caller: "admin-server", + PoolID: poolID, + RuleType: req.RuleType, + RuleConfigJSON: req.RuleConfigJSON, + Count: req.Count, + OperatorAdminID: int64(middleware.CurrentUserID(c)), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLogWithResourceID(c, h.audit, "generate-pretty-ids", "pretty_display_id_generation_batches", batch.BatchID, "success", "pool_id="+poolID) + response.Created(c, batch) +} + +func (h *Handler) ListIDs(c *gin.Context) { + page, pageSize, ok := pageQuery(c) + if !ok { + response.BadRequest(c, "分页参数不正确") + return + } + assignedUserID, ok := optionalInt64Query(c, "assigned_user_id") + if !ok { + response.BadRequest(c, "assigned_user_id 参数不正确") + return + } + // 列表筛选保持轻量透传,空字符串表示不过滤,assigned_user_id 只在正整数时作为精确条件。 + items, total, err := h.user.ListPrettyDisplayIDs(c.Request.Context(), userclient.ListPrettyDisplayIDsRequest{ + RequestID: middleware.CurrentRequestID(c), + Caller: "admin-server", + PoolID: strings.TrimSpace(c.Query("pool_id")), + Source: strings.TrimSpace(c.Query("source")), + Status: strings.TrimSpace(c.Query("status")), + Keyword: strings.TrimSpace(c.Query("keyword")), + AssignedUserID: assignedUserID, + Page: page, + PageSize: pageSize, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + response.OK(c, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize}) +} + +func (h *Handler) Grant(c *gin.Context) { + var req grantRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "发放参数不正确") + return + } + targetUserID, ok := parseFlexibleInt64(req.TargetUserID) + if !ok { + response.BadRequest(c, "target_user_id 参数不正确") + return + } + // 后台发放允许 target_user_id 由 JS 以数字或字符串提交,这里转成 int64 后把业务校验交给 user-service。 + result, err := h.user.AdminGrantPrettyDisplayID(c.Request.Context(), userclient.AdminGrantPrettyDisplayIDRequest{ + RequestID: middleware.CurrentRequestID(c), + Caller: "admin-server", + TargetUserID: targetUserID, + DisplayUserID: req.DisplayUserID, + DurationMs: req.DurationMs, + Reason: req.Reason, + OperatorAdminID: int64(middleware.CurrentUserID(c)), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLogWithResourceID(c, h.audit, "grant-pretty-id", "pretty_display_ids", result.PrettyID, "success", "target_user_id="+strconv.FormatInt(targetUserID, 10)) + response.Created(c, result) +} + +func (h *Handler) SetStatus(c *gin.Context) { + var req statusRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "状态参数不正确") + return + } + // 状态修改以路径 pretty_id 为准,请求体只允许目标状态和原因,防止改错号码。 + prettyID := strings.TrimSpace(c.Param("pretty_id")) + item, err := h.user.SetPrettyDisplayIDStatus(c.Request.Context(), userclient.SetPrettyDisplayIDStatusRequest{ + RequestID: middleware.CurrentRequestID(c), + Caller: "admin-server", + PrettyID: prettyID, + Status: req.Status, + Reason: req.Reason, + OperatorAdminID: int64(middleware.CurrentUserID(c)), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLogWithResourceID(c, h.audit, "set-pretty-id-status", "pretty_display_ids", prettyID, "success", "status="+item.Status) + response.OK(c, item) +} + +func pageQuery(c *gin.Context) (int32, int32, bool) { + // 分页参数在 admin-server 先收敛到 1..100,避免后台页面传错值造成 user-service 大分页查询。 + page := int32(1) + pageSize := int32(20) + if raw := strings.TrimSpace(c.Query("page")); raw != "" { + value, err := strconv.ParseInt(raw, 10, 32) + if err != nil || value <= 0 { + return 0, 0, false + } + page = int32(value) + } + if raw := strings.TrimSpace(c.Query("page_size")); raw != "" { + value, err := strconv.ParseInt(raw, 10, 32) + if err != nil || value <= 0 { + return 0, 0, false + } + pageSize = int32(value) + } + if pageSize > 100 { + pageSize = 100 + } + return page, pageSize, true +} + +func optionalInt64Query(c *gin.Context, name string) (int64, bool) { + // 空查询表示不筛选,非空必须是正整数,避免把 0 当成真实用户 ID 传给列表接口。 + raw := strings.TrimSpace(c.Query(name)) + if raw == "" { + return 0, true + } + value, err := strconv.ParseInt(raw, 10, 64) + return value, err == nil && value > 0 +} + +func parseFlexibleInt64(raw any) (int64, bool) { + // JSON 大整数在前端经常以字符串提交,数字提交时也必须是精确整数,不能接受小数或负数。 + switch value := raw.(type) { + case float64: + if value <= 0 || value != float64(int64(value)) { + return 0, false + } + return int64(value), true + case string: + parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64) + return parsed, err == nil && parsed > 0 + default: + return 0, false + } +} diff --git a/server/admin/internal/modules/prettyid/routes.go b/server/admin/internal/modules/prettyid/routes.go new file mode 100644 index 00000000..d87decad --- /dev/null +++ b/server/admin/internal/modules/prettyid/routes.go @@ -0,0 +1,21 @@ +package prettyid + +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/users/pretty-id-pools", middleware.RequirePermission("pretty-id:view"), h.ListPools) + protected.POST("/admin/users/pretty-id-pools", middleware.RequirePermission("pretty-id:update"), h.CreatePool) + protected.PUT("/admin/users/pretty-id-pools/:pool_id", middleware.RequirePermission("pretty-id:update"), h.UpdatePool) + protected.POST("/admin/users/pretty-id-pools/:pool_id/generate", middleware.RequirePermission("pretty-id:generate"), h.Generate) + protected.GET("/admin/users/pretty-ids", middleware.RequirePermission("pretty-id:view"), h.ListIDs) + protected.POST("/admin/users/pretty-ids/grant", middleware.RequirePermission("pretty-id:grant"), h.Grant) + protected.POST("/admin/users/pretty-ids/:pretty_id/status", middleware.RequirePermission("pretty-id:update"), h.SetStatus) +} diff --git a/server/admin/internal/repository/seed.go b/server/admin/internal/repository/seed.go index fddeb14d..8082135c 100644 --- a/server/admin/internal/repository/seed.go +++ b/server/admin/internal/repository/seed.go @@ -22,6 +22,10 @@ var defaultPermissions = []model.Permission{ {Name: "App 用户设置密码", Code: "app-user:password", Kind: "button"}, {Name: "等级配置查看", Code: "level-config:view", Kind: "menu"}, {Name: "等级配置更新", Code: "level-config:update", Kind: "button"}, + {Name: "靓号管理查看", Code: "pretty-id:view", Kind: "menu"}, + {Name: "靓号池更新", Code: "pretty-id:update", Kind: "button"}, + {Name: "靓号批量生成", Code: "pretty-id:generate", Kind: "button"}, + {Name: "靓号后台发放", Code: "pretty-id:grant", Kind: "button"}, {Name: "地区屏蔽查看", Code: "region-block:view", Kind: "menu"}, {Name: "地区屏蔽更新", Code: "region-block:update", Kind: "button"}, {Name: "房间查看", Code: "room:view", Kind: "menu"}, @@ -94,6 +98,8 @@ var defaultPermissions = []model.Permission{ {Name: "礼物钻石查看", Code: "gift-diamond:view", Kind: "menu"}, {Name: "礼物钻石更新", Code: "gift-diamond:update", Kind: "button"}, {Name: "支付账单查看", Code: "payment-bill:view", Kind: "menu"}, + {Name: "三方支付查看", Code: "payment-third-party:view", Kind: "menu"}, + {Name: "三方支付更新", Code: "payment-third-party:update", Kind: "button"}, {Name: "内购配置查看", Code: "payment-product:view", Kind: "menu"}, {Name: "内购配置创建", Code: "payment-product:create", Kind: "button"}, {Name: "内购配置更新", Code: "payment-product:update", Kind: "button"}, @@ -243,7 +249,8 @@ func (s *Store) seedMenus() error { {ParentID: &appUsersID, Title: "封禁列表", Code: "app-user-bans", Path: "/app/users/bans", Icon: "block", PermissionCode: "app-user:view", Sort: 61, Visible: true}, {ParentID: &appUsersID, Title: "登录日志", Code: "app-user-login-logs", Path: "/app/users/login-logs", Icon: "login", PermissionCode: "app-user:view", Sort: 62, Visible: true}, {ParentID: &appUsersID, Title: "等级配置", Code: "app-user-level-config", Path: "/app/users/level-config", Icon: "military_tech", PermissionCode: "level-config:view", Sort: 63, Visible: true}, - {ParentID: &appUsersID, Title: "地区屏蔽", Code: "app-user-region-blocks", Path: "/app/users/region-blocks", Icon: "public", PermissionCode: "region-block:view", Sort: 64, Visible: true}, + {ParentID: &appUsersID, Title: "靓号管理", Code: "app-user-pretty-ids", Path: "/app/users/pretty-ids", Icon: "tag", PermissionCode: "pretty-id:view", Sort: 64, Visible: true}, + {ParentID: &appUsersID, Title: "地区屏蔽", Code: "app-user-region-blocks", Path: "/app/users/region-blocks", Icon: "public", PermissionCode: "region-block:view", Sort: 65, Visible: true}, {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}, @@ -251,7 +258,6 @@ func (s *Store) seedMenus() error { {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}, {ParentID: &appConfigID, Title: "Explore配置", Code: "app-config-explore", Path: "/app-config/explore", Icon: "explore", PermissionCode: "app-config:view", Sort: 69, Visible: true}, - {ParentID: &appConfigID, Title: "内购配置", Code: "payment-recharge-products", Path: "/app-config/recharge-products", Icon: "wallet", PermissionCode: "payment-product:view", Sort: 70, Visible: true}, {ParentID: &appConfigID, Title: "版本管理", Code: "app-config-versions", Path: "/app-config/versions", Icon: "settings", PermissionCode: "app-version:view", Sort: 71, Visible: true}, {ParentID: &resourceID, Title: "资源列表", Code: "resource-list", Path: "/resources", Icon: "inventory", PermissionCode: "resource:view", Sort: 67, Visible: true}, {ParentID: &resourceID, Title: "道具商店", Code: "resource-shop-list", Path: "/resource-shop", Icon: "storefront", PermissionCode: "resource-shop:view", Sort: 68, Visible: true}, @@ -266,6 +272,8 @@ func (s *Store) seedMenus() error { {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: &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}, {ParentID: &activityID, Title: "每日任务", Code: "daily-task-list", Path: "/activities/daily-tasks", Icon: "task", PermissionCode: "daily-task:view", Sort: 69, Visible: true}, {ParentID: &activityID, Title: "注册奖励", Code: "registration-reward", Path: "/activities/registration-reward", Icon: "gift", PermissionCode: "registration-reward:view", Sort: 70, Visible: true}, {ParentID: &activityID, Title: "成就配置", Code: "achievement-config", Path: "/activities/achievements", Icon: "military_tech", PermissionCode: "achievement:view", Sort: 71, Visible: true}, @@ -277,6 +285,8 @@ 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: &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}, {ParentID: &geoID, Title: "国家管理", Code: "host-org-countries", Path: "/host/countries", Icon: "public", PermissionCode: "country:view", Sort: 70, Visible: true}, {ParentID: &geoID, Title: "区域管理", Code: "host-org-regions", Path: "/host/regions", Icon: "map", PermissionCode: "region:view", Sort: 71, Visible: true}, {ParentID: &hostOrgID, Title: "Manager列表", Code: "host-org-managers", Path: "/host/managers", Icon: "users", PermissionCode: "agency:view", Sort: 80, Visible: true}, @@ -497,6 +507,7 @@ func defaultRolePermissionCodes(code string) []string { "user:view", "user:status", "app-user:view", "app-user:update", "app-user:status", "app-user:password", "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", "app-config:view", "app-config:update", @@ -513,7 +524,7 @@ func defaultRolePermissionCodes(code string) []string { "agency:view", "agency:create", "agency:status", "agency:delete", "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", "payment-bill:view", "payment-product:view", "payment-product:create", "payment-product:update", "payment-product:delete", + "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "coin-adjustment:create", "report:view", "gift-diamond:view", "gift-diamond:update", "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", "game:view", "game:create", "game:update", "game:status", "game:delete", "daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status", @@ -529,13 +540,14 @@ func defaultRolePermissionCodes(code string) []string { "upload:create", } case "auditor": - return []string{"overview:view", "log:view", "user:view", "app-user:view", "level-config:view", "region-block:view", "room:view", "room-pin:view", "room-config: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", "lucky-gift:view", "payment-bill: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", "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", "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", "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", "role:view", "permission:view", "job:view"} case "readonly": return []string{ "overview:view", "user:view", "app-user:view", "level-config:view", + "pretty-id:view", "region-block:view", "room:view", "room-pin:view", @@ -564,6 +576,7 @@ func defaultRolePermissionCodes(code string) []string { "gift-diamond:view", "lucky-gift:view", "payment-bill:view", + "payment-third-party:view", "payment-product:view", "game:view", "daily-task:view", @@ -594,6 +607,7 @@ func defaultRolePermissionMigrationCodes(code string) []string { "app-config:view", "app-config:update", "app-version:view", "app-version:create", "app-version:update", "app-version:delete", "level-config:view", "level-config:update", + "pretty-id:view", "pretty-id:update", "pretty-id:generate", "pretty-id:grant", "region-block:view", "region-block:update", "resource:view", "resource:create", "resource:update", "resource-shop:view", "resource-shop:update", @@ -606,7 +620,7 @@ func defaultRolePermissionMigrationCodes(code string) []string { "region:view", "region:create", "region:update", "region:status", "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", "payment-bill:view", "payment-product:view", "payment-product:create", "payment-product:update", "payment-product:delete", + "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "coin-adjustment:create", "report:view", "gift-diamond:view", "gift-diamond:update", "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", "game:view", "game:create", "game:update", "game:status", "game:delete", "daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status", @@ -619,7 +633,7 @@ func defaultRolePermissionMigrationCodes(code string) []string { "weekly-star:view", "weekly-star:create", "weekly-star:update", "weekly-star:settle", } case "auditor", "readonly": - return []string{"level-config:view", "region-block:view", "room:view", "room-pin:view", "room-config: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", "lucky-gift:view", "payment-bill: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"} + return []string{"level-config:view", "pretty-id:view", "region-block:view", "room:view", "room-pin:view", "room-config: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", "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"} default: return nil } diff --git a/server/admin/internal/router/router.go b/server/admin/internal/router/router.go index 66e96a59..4397e8ed 100644 --- a/server/admin/internal/router/router.go +++ b/server/admin/internal/router/router.go @@ -28,6 +28,7 @@ import ( "hyapp-admin-server/internal/modules/luckygift" "hyapp-admin-server/internal/modules/menu" "hyapp-admin-server/internal/modules/payment" + "hyapp-admin-server/internal/modules/prettyid" "hyapp-admin-server/internal/modules/rbac" "hyapp-admin-server/internal/modules/redpacket" "hyapp-admin-server/internal/modules/regionblock" @@ -77,6 +78,7 @@ type Handlers struct { LuckyGift *luckygift.Handler Menu *menu.Handler Payment *payment.Handler + PrettyID *prettyid.Handler RBAC *rbac.Handler RedPacket *redpacket.Handler Report *reportmodule.Handler @@ -137,6 +139,7 @@ func New(cfg config.Config, auth *service.AuthService, h Handlers) *gin.Engine { hostorg.RegisterRoutes(protected, h.HostOrg) audit.RegisterRoutes(protected, h.Audit) payment.RegisterRoutes(protected, h.Payment) + prettyid.RegisterRoutes(protected, h.PrettyID) job.RegisterRoutes(protected, h.Job) levelconfig.RegisterRoutes(protected, h.LevelConfig) luckygift.RegisterRoutes(protected, h.LuckyGift) diff --git a/server/admin/migrations/009_admin_navigation_seed.sql b/server/admin/migrations/009_admin_navigation_seed.sql index 8e93c67e..2d485162 100644 --- a/server/admin/migrations/009_admin_navigation_seed.sql +++ b/server/admin/migrations/009_admin_navigation_seed.sql @@ -9,6 +9,8 @@ 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 ('金币流水查看', 'coin-ledger:view', 'menu', '', @now_ms, @now_ms), ('支付账单查看', 'payment-bill:view', 'menu', '', @now_ms, @now_ms), + ('三方支付查看', 'payment-third-party:view', 'menu', '', @now_ms, @now_ms), + ('三方支付更新', 'payment-third-party:update', 'button', '', @now_ms, @now_ms), ('内购配置查看', 'payment-product:view', 'menu', '', @now_ms, @now_ms), ('内购配置创建', 'payment-product:create', 'button', '', @now_ms, @now_ms), ('内购配置更新', 'payment-product:update', 'button', '', @now_ms, @now_ms), @@ -47,9 +49,9 @@ ON DUPLICATE KEY UPDATE 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, '内购配置', 'payment-recharge-products', '/app-config/recharge-products', 'wallet', 'payment-product:view', 68, TRUE, @now_ms, @now_ms +SELECT parent.id, '内购配置', 'payment-recharge-products', '/payment/recharge-products', 'wallet', 'payment-product:view', 70, TRUE, @now_ms, @now_ms FROM admin_menus parent -WHERE parent.code = 'app-config' +WHERE parent.code = 'payment' ON DUPLICATE KEY UPDATE parent_id = VALUES(parent_id), title = VALUES(title), @@ -88,6 +90,20 @@ ON DUPLICATE KEY UPDATE 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, '三方支付', 'payment-third-party', '/payment/third-party', 'wallet', 'payment-third-party:view', 69, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'payment' +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 INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) SELECT parent.id, '每日任务', 'daily-task-list', '/activities/daily-tasks', 'task', 'daily-task:view', 69, TRUE, @now_ms, @now_ms FROM admin_menus parent @@ -130,6 +146,34 @@ ON DUPLICATE KEY UPDATE 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, '自研游戏', 'self-games', '/games/self-games', 'settings', 'game:view', 80, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'games' +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 INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) +SELECT parent.id, '游戏机器人', 'game-robots', '/games/robots', 'team', 'game:view', 90, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'games' +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 INTO admin_roles (name, code, description, created_at_ms, updated_at_ms) VALUES ('平台管理员', 'platform-admin', '拥有管理后台全部权限', @now_ms, @now_ms) ON DUPLICATE KEY UPDATE diff --git a/server/admin/migrations/043_admin_app_splash_display_duration.sql b/server/admin/migrations/043_admin_app_splash_display_duration.sql new file mode 100644 index 00000000..d24eb124 --- /dev/null +++ b/server/admin/migrations/043_admin_app_splash_display_duration.sql @@ -0,0 +1,5 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +ALTER TABLE admin_app_splash_screens + ADD COLUMN display_duration_ms INT NOT NULL DEFAULT 3000 COMMENT '开屏展示时长,毫秒;0 表示客户端默认值' + AFTER description; diff --git a/server/admin/migrations/044_pretty_id_navigation.sql b/server/admin/migrations/044_pretty_id_navigation.sql new file mode 100644 index 00000000..7d5e4378 --- /dev/null +++ b/server/admin/migrations/044_pretty_id_navigation.sql @@ -0,0 +1,47 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +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 + ('靓号管理查看', 'pretty-id:view', 'menu', '', @now_ms, @now_ms), + ('靓号池更新', 'pretty-id:update', 'button', '', @now_ms, @now_ms), + ('靓号批量生成', 'pretty-id:generate', 'button', '', @now_ms, @now_ms), + ('靓号后台发放', 'pretty-id:grant', '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) +SELECT parent.id, '靓号管理', 'app-user-pretty-ids', '/app/users/pretty-ids', 'tag', 'pretty-id:view', 64, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'app-users' +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; + +UPDATE admin_menus +SET sort = 65, updated_at_ms = @now_ms +WHERE code = 'app-user-region-blocks' + AND sort = 64; + +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 ('platform-admin', 'ops-admin') + AND admin_permission.code IN ('pretty-id:view', 'pretty-id:update', 'pretty-id:generate', 'pretty-id:grant'); + +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 = 'pretty-id:view'; diff --git a/server/admin/migrations/045_payment_third_party_navigation.sql b/server/admin/migrations/045_payment_third_party_navigation.sql new file mode 100644 index 00000000..593183a0 --- /dev/null +++ b/server/admin/migrations/045_payment_third_party_navigation.sql @@ -0,0 +1,50 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 三方支付是支付管理下的功能;内购配置也归到支付管理,不再挂在 APP 配置下。 +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 + ('三方支付查看', 'payment-third-party:view', 'menu', '', @now_ms, @now_ms), + ('三方支付更新', 'payment-third-party: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) +SELECT parent.id, '三方支付', 'payment-third-party', '/payment/third-party', 'wallet', 'payment-third-party:view', 69, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'payment' +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; + +UPDATE admin_menus child +JOIN admin_menus parent ON parent.code = 'payment' +SET child.parent_id = parent.id, + child.path = '/payment/recharge-products', + child.sort = 70, + child.visible = TRUE, + child.updated_at_ms = @now_ms +WHERE child.code = 'payment-recharge-products'; + +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 ('platform-admin', 'ops-admin') + AND admin_permission.code IN ('payment-third-party:view', 'payment-third-party: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 = 'payment-third-party:view'; diff --git a/server/admin/migrations/046_first_recharge_gift_copy.sql b/server/admin/migrations/046_first_recharge_gift_copy.sql new file mode 100644 index 00000000..2cb663a0 --- /dev/null +++ b/server/admin/migrations/046_first_recharge_gift_copy.sql @@ -0,0 +1,16 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 只修正后台展示文案,权限码、菜单 code 和路由保持不变,避免破坏已分配角色和前端路由映射。 +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +UPDATE admin_permissions +SET name = '首充礼包查看', updated_at_ms = @now_ms +WHERE code = 'first-recharge-reward:view'; + +UPDATE admin_permissions +SET name = '首充礼包更新', updated_at_ms = @now_ms +WHERE code = 'first-recharge-reward:update'; + +UPDATE admin_menus +SET title = '首充礼包', updated_at_ms = @now_ms +WHERE code = 'first-recharge-reward'; diff --git a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql index 7cc95271..33ae021d 100644 --- a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql +++ b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql @@ -463,15 +463,19 @@ CREATE TABLE IF NOT EXISTS first_recharge_reward_tiers ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', tier_code VARCHAR(64) NOT NULL COMMENT '档位编码', tier_name VARCHAR(96) NOT NULL COMMENT '档位名称', - min_coin_amount BIGINT NOT NULL COMMENT '最小充值金币数量', + min_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '历史字段:最小充值金币数量', max_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '最大充值金币数量,0 表示无上限', + usd_minor_amount BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 商品美元美分档位', + google_product_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Google Play 商品 ID', + discount_percent INT NOT NULL DEFAULT 0 COMMENT '展示优惠百分比,不参与结算', resource_group_id BIGINT NOT NULL COMMENT '奖励资源分组 ID', status VARCHAR(32) NOT NULL COMMENT '业务状态', sort_order INT 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', UNIQUE KEY uk_first_recharge_reward_tier_code (app_code, tier_code), - KEY idx_first_recharge_reward_tier_active (app_code, status, sort_order, min_coin_amount) + UNIQUE KEY uk_first_recharge_reward_google_product (app_code, google_product_id), + KEY idx_first_recharge_reward_tier_active (app_code, status, sort_order, usd_minor_amount) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='首冲奖励档位表'; CREATE TABLE IF NOT EXISTS first_recharge_reward_claims ( @@ -484,7 +488,10 @@ CREATE TABLE IF NOT EXISTS first_recharge_reward_claims ( tier_id BIGINT NOT NULL COMMENT '命中奖励档位 ID', tier_code VARCHAR(64) NOT NULL COMMENT '命中奖励档位编码', resource_group_id BIGINT NOT NULL COMMENT '奖励资源分组 ID', + tier_usd_minor_amount BIGINT NOT NULL DEFAULT 0 COMMENT '命中档位美元美分', + google_product_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '命中 Google Play 商品 ID', recharge_coin_amount BIGINT NOT NULL COMMENT '充值金币数量', + recharge_usd_minor BIGINT NOT NULL DEFAULT 0 COMMENT '本次充值美元美分', recharge_sequence BIGINT NOT NULL COMMENT '该用户充值序号', recharge_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '充值来源类型', status VARCHAR(32) NOT NULL COMMENT '业务状态', @@ -498,7 +505,7 @@ CREATE TABLE IF NOT EXISTS first_recharge_reward_claims ( PRIMARY KEY (app_code, claim_id), UNIQUE KEY uk_first_recharge_reward_event (app_code, event_id), UNIQUE KEY uk_first_recharge_reward_transaction (app_code, transaction_id), - UNIQUE KEY uk_first_recharge_reward_user_once (app_code, user_id), + UNIQUE KEY uk_first_recharge_reward_user_tier (app_code, user_id, tier_id), KEY idx_first_recharge_reward_list (app_code, created_at_ms, claim_id), KEY idx_first_recharge_reward_status (app_code, status, updated_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='首冲奖励领取表'; diff --git a/services/activity-service/internal/app/wallet_events.go b/services/activity-service/internal/app/wallet_events.go index 08f337c3..ce17ed76 100644 --- a/services/activity-service/internal/app/wallet_events.go +++ b/services/activity-service/internal/app/wallet_events.go @@ -19,7 +19,7 @@ func firstRechargeEventFromWalletMessage(body []byte) (firstrechargedomain.Recha if message.EventType != firstrechargedomain.RechargeEventWalletRecorded { return firstrechargedomain.RechargeEvent{}, false, nil } - sequence, rechargeType := rechargeFactFromWalletPayload(message.PayloadJSON) + fields := firstRechargeFactFromWalletPayload(message.PayloadJSON) return firstrechargedomain.RechargeEvent{ AppCode: appcode.Normalize(message.AppCode), EventID: message.EventID, @@ -28,8 +28,10 @@ func firstRechargeEventFromWalletMessage(body []byte) (firstrechargedomain.Recha CommandID: message.CommandID, UserID: message.UserID, RechargeCoinAmount: message.AvailableDelta, - RechargeSequence: sequence, - RechargeType: rechargeType, + RechargeSequence: fields.sequence, + RechargeType: fields.rechargeType, + RechargeUSDMinor: fields.rechargeUSDMinor, + GoogleProductID: fields.googleProductID, PayloadJSON: message.PayloadJSON, OccurredAtMS: message.OccurredAtMS, }, true, nil @@ -101,25 +103,42 @@ func redPacketEventFromWalletMessage(body []byte) (broadcastdomain.RedPacketWall }, true, nil } -func rechargeFactFromWalletPayload(payload string) (int64, string) { +type firstRechargePayloadFields struct { + sequence int64 + rechargeType string + rechargeUSDMinor int64 + googleProductID string +} + +func firstRechargeFactFromWalletPayload(payload string) firstRechargePayloadFields { var decoded map[string]any if err := json.Unmarshal([]byte(payload), &decoded); err != nil { - return 0, firstrechargedomain.RechargeTypeCoinSeller + decoded = map[string]any{} } - var sequence int64 - switch value := decoded["recharge_sequence"].(type) { - case float64: - sequence = int64(value) - case int64: - sequence = value - case json.Number: - sequence, _ = value.Int64() + rechargeType := firstNonEmptyString( + stringFromDecoded(decoded, "recharge_type"), + stringFromDecoded(decoded, "channel"), + stringFromDecoded(decoded, "provider"), + firstrechargedomain.RechargeTypeCoinSeller, + ) + rechargeUSDMinor := firstNonZeroInt64( + int64FromDecoded(decoded, "recharge_usd_minor"), + int64FromDecoded(decoded, "usd_minor_amount"), + int64FromDecoded(decoded, "amount_usd_minor"), + ) + if rechargeUSDMinor <= 0 && isCumulativeGoogleRecharge(rechargeType) { + rechargeUSDMinor = int64FromDecoded(decoded, "amount_micro") / 10000 } - rechargeType := firstrechargedomain.RechargeTypeCoinSeller - if value, ok := decoded["recharge_type"].(string); ok && value != "" { - rechargeType = value + return firstRechargePayloadFields{ + sequence: int64FromDecoded(decoded, "recharge_sequence"), + rechargeType: rechargeType, + rechargeUSDMinor: rechargeUSDMinor, + googleProductID: firstNonEmptyString( + stringFromDecoded(decoded, "google_product_id"), + stringFromDecoded(decoded, "product_name"), + stringFromDecoded(decoded, "product_code"), + ), } - return sequence, rechargeType } type cumulativeRechargePayloadFields struct { diff --git a/services/activity-service/internal/client/user_profile_source.go b/services/activity-service/internal/client/user_profile_source.go index 2136b3e7..7d53ce1b 100644 --- a/services/activity-service/internal/client/user_profile_source.go +++ b/services/activity-service/internal/client/user_profile_source.go @@ -36,10 +36,11 @@ func (s *GRPCUserProfileSource) GetSenderProfile(ctx context.Context, userID int return broadcastservice.SenderProfile{UserID: userID}, nil } return broadcastservice.SenderProfile{ - UserID: user.GetUserId(), - Account: user.GetDisplayUserId(), - Nickname: user.GetUsername(), - Avatar: user.GetAvatar(), - RegionCode: user.GetRegionCode(), + UserID: user.GetUserId(), + Account: user.GetDisplayUserId(), + Nickname: user.GetUsername(), + Avatar: user.GetAvatar(), + AvatarFrameURL: "", + RegionCode: user.GetRegionCode(), }, nil } diff --git a/services/activity-service/internal/domain/firstrecharge/first_recharge.go b/services/activity-service/internal/domain/firstrecharge/first_recharge.go index 213b891c..c54234ba 100644 --- a/services/activity-service/internal/domain/firstrecharge/first_recharge.go +++ b/services/activity-service/internal/domain/firstrecharge/first_recharge.go @@ -12,7 +12,6 @@ const ( ReasonNotConfigured = "not_configured" ReasonDisabled = "disabled" ReasonInvalidConfig = "invalid_config" - ReasonNotFirst = "not_first_recharge" ReasonTierNotMatched = "tier_not_matched" ReasonAlreadyGranted = "already_granted" ReasonPendingReward = "pending_reward" @@ -21,7 +20,7 @@ const ( RechargeTypeCoinSeller = "coin_seller_transfer" ) -// Tier 是首冲奖励的金额档位,max_coin_amount=0 表达无上限。 +// Tier 是首冲奖励的 Google 商品档位;旧金币区间字段只用于兼容老配置。 type Tier struct { TierID int64 TierCode string @@ -33,6 +32,9 @@ type Tier struct { SortOrder int32 CreatedAtMS int64 UpdatedAtMS int64 + USDMinorAmount int64 + GoogleProductID string + DiscountPercent int32 } // Config 是首冲奖励当前配置;档位快照由 activity-service 持有。 @@ -45,7 +47,7 @@ type Config struct { UpdatedAtMS int64 } -// Claim 是首笔充值触发后的发奖事实;app_code + user_id 唯一。 +// Claim 是首充档位购买后的发奖事实;app_code + user_id + tier_id 唯一。 type Claim struct { ClaimID string EventID string @@ -66,6 +68,9 @@ type Claim struct { GrantedAtMS int64 CreatedAtMS int64 UpdatedAtMS int64 + TierUSDMinorAmount int64 + GoogleProductID string + RechargeUSDMinor int64 } // StatusResult 是 App 首冲奖励面板读取的稳定投影。 @@ -74,6 +79,7 @@ type StatusResult struct { Tiers []Tier Rewarded bool Claim Claim + Claims []Claim ServerTimeMS int64 } @@ -88,6 +94,8 @@ type RechargeEvent struct { RechargeCoinAmount int64 RechargeSequence int64 RechargeType string + RechargeUSDMinor int64 + GoogleProductID string PayloadJSON string OccurredAtMS int64 } diff --git a/services/activity-service/internal/service/broadcast/service.go b/services/activity-service/internal/service/broadcast/service.go index 9ccfe21b..52c2b98a 100644 --- a/services/activity-service/internal/service/broadcast/service.go +++ b/services/activity-service/internal/service/broadcast/service.go @@ -55,11 +55,12 @@ type SenderProfileSource interface { // SenderProfile 是播报 IM payload 的发送者展示字段。 // activity-service 不持久化该资料,只在消费事实时组装一次播报消息。 type SenderProfile struct { - UserID int64 - Account string - Nickname string - Avatar string - RegionCode string + UserID int64 + Account string + Nickname string + Avatar string + AvatarFrameURL string + RegionCode string } // Config 保存播报策略和 worker 参数。 @@ -322,12 +323,21 @@ func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.Ev if err := proto.Unmarshal(envelope.GetBody(), &gift); err != nil { return broadcastdomain.ConsumeRoomEventResult{}, err } - if gift.GetVisibleRegionId() <= 0 || gift.GetGiftValue() < s.cfg.SuperGiftMinValue { + if gift.GetVisibleRegionId() <= 0 || !s.shouldBroadcastGift(&gift) { // visible_region_id 来自房间/主播可见区域,不能用客户端当前 IP 或本地时区推断。 return result, nil } broadcastEventID := superGiftBroadcastEventID(envelope, &gift) - payloadJSON, err := superGiftPayload(envelope, &gift, broadcastEventID, s.now().UTC().UnixMilli()) + // 区域播报需要携带双方展示资料;资料服务不可用时返回错误,避免发出缺头像昵称的误导性大礼消息。 + senderProfile, err := s.broadcastUserProfile(ctx, gift.GetSenderUserId()) + if err != nil { + return broadcastdomain.ConsumeRoomEventResult{}, err + } + receiverProfile, err := s.broadcastUserProfile(ctx, gift.GetTargetUserId()) + if err != nil { + return broadcastdomain.ConsumeRoomEventResult{}, err + } + payloadJSON, err := superGiftPayload(envelope, &gift, broadcastEventID, s.now().UTC().UnixMilli(), senderProfile, receiverProfile) if err != nil { return broadcastdomain.ConsumeRoomEventResult{}, err } @@ -349,6 +359,38 @@ func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.Ev }, nil } +func (s *Service) shouldBroadcastGift(gift *roomeventsv1.RoomGiftSent) bool { + if gift == nil { + return false + } + // 老规则仍按礼物总价值触发;新规则额外允许后台显式标记 global_broadcast 的低价礼物触发区域播报。 + if gift.GetGiftValue() >= s.cfg.SuperGiftMinValue { + return true + } + for _, effectType := range gift.GetGiftEffectTypes() { + if strings.TrimSpace(strings.ToLower(effectType)) == "global_broadcast" { + return true + } + } + return false +} + +func (s *Service) broadcastUserProfile(ctx context.Context, userID int64) (SenderProfile, error) { + profile := SenderProfile{UserID: userID} + // 没有 user_id 或未注入资料源时保留 user_id 兜底,保证测试和无资料源部署不会阻断原有播报链路。 + if userID <= 0 || s.senderProfileSource == nil { + return profile, nil + } + resolved, err := s.senderProfileSource.GetSenderProfile(ctx, userID) + if err != nil { + return SenderProfile{}, err + } + if resolved.UserID <= 0 { + resolved.UserID = userID + } + return resolved, nil +} + func (s *Service) handleRoomPasswordChanged(ctx context.Context, envelope *roomeventsv1.EventEnvelope) (broadcastdomain.ConsumeRoomEventResult, error) { result := broadcastdomain.ConsumeRoomEventResult{EventID: envelope.GetEventId(), Status: broadcastdomain.StatusSkipped} var event roomeventsv1.RoomPasswordChanged @@ -582,22 +624,38 @@ func superGiftBroadcastEventID(envelope *roomeventsv1.EventEnvelope, gift *roome return "gift_broadcast:" + envelope.GetEventId() } -func superGiftPayload(envelope *roomeventsv1.EventEnvelope, gift *roomeventsv1.RoomGiftSent, eventID string, sentAtMS int64) (string, error) { +func superGiftPayload(envelope *roomeventsv1.EventEnvelope, gift *roomeventsv1.RoomGiftSent, eventID string, sentAtMS int64, sender SenderProfile, receiver SenderProfile) (string, error) { + // payload 同时保留扁平字段和 sender/receiver 对象,旧客户端继续读原字段,新客户端可直接取完整用户资料。 payload := map[string]any{ - "event_id": eventID, - "broadcast_type": broadcastdomain.TypeSuperGift, - "scope": broadcastdomain.ScopeRegion, - "app_code": envelope.GetAppCode(), - "region_id": gift.GetVisibleRegionId(), - "room_id": envelope.GetRoomId(), - "sender_user_id": gift.GetSenderUserId(), - "target_user_id": gift.GetTargetUserId(), - "gift_id": gift.GetGiftId(), - "gift_count": gift.GetGiftCount(), - "gift_value": gift.GetGiftValue(), - "sent_at_ms": sentAtMS, - "room_version": envelope.GetRoomVersion(), - "source_event_id": envelope.GetEventId(), + "event_id": eventID, + "broadcast_type": broadcastdomain.TypeSuperGift, + "scope": broadcastdomain.ScopeRegion, + "app_code": envelope.GetAppCode(), + "region_id": gift.GetVisibleRegionId(), + "room_id": envelope.GetRoomId(), + "sender_user_id": gift.GetSenderUserId(), + "sender_display_user_id": sender.Account, + "sender_nickname": sender.Nickname, + "sender_avatar": sender.Avatar, + "sender_avatar_frame_url": sender.AvatarFrameURL, + "target_user_id": gift.GetTargetUserId(), + "receiver_user_id": gift.GetTargetUserId(), + "receiver_display_user_id": receiver.Account, + "receiver_nickname": receiver.Nickname, + "receiver_avatar": receiver.Avatar, + "receiver_avatar_frame_url": receiver.AvatarFrameURL, + "gift_id": gift.GetGiftId(), + "gift_name": gift.GetGiftName(), + "gift_icon_url": gift.GetGiftIconUrl(), + "gift_animation_url": gift.GetGiftAnimationUrl(), + "gift_effect_types": gift.GetGiftEffectTypes(), + "gift_count": gift.GetGiftCount(), + "gift_value": gift.GetGiftValue(), + "sent_at_ms": sentAtMS, + "room_version": envelope.GetRoomVersion(), + "source_event_id": envelope.GetEventId(), + "sender": broadcastProfilePayload(sender), + "receiver": broadcastProfilePayload(receiver), "action": map[string]any{ "type": "enter_room", "room_id": envelope.GetRoomId(), @@ -607,6 +665,17 @@ func superGiftPayload(envelope *roomeventsv1.EventEnvelope, gift *roomeventsv1.R return string(encoded), err } +func broadcastProfilePayload(profile SenderProfile) map[string]any { + // 嵌套资料对象只放展示字段,不放钱包或权限数据,避免 IM 广播泄露服务端内部状态。 + return map[string]any{ + "user_id": profile.UserID, + "display_user_id": profile.Account, + "nickname": profile.Nickname, + "avatar": profile.Avatar, + "avatar_frame_url": profile.AvatarFrameURL, + } +} + func roomRocketBroadcastEventID(envelope *roomeventsv1.EventEnvelope, rocket *roomeventsv1.RoomRocketIgnited) string { if strings.TrimSpace(rocket.GetRocketId()) != "" { return fmt.Sprintf("room_rocket_broadcast:%s:%s", envelope.GetRoomId(), rocket.GetRocketId()) diff --git a/services/activity-service/internal/service/broadcast/service_test.go b/services/activity-service/internal/service/broadcast/service_test.go index 251dbe29..c3c0bb36 100644 --- a/services/activity-service/internal/service/broadcast/service_test.go +++ b/services/activity-service/internal/service/broadcast/service_test.go @@ -18,6 +18,10 @@ func TestHandleRoomGiftSentCreatesRegionBroadcast(t *testing.T) { repository := newFakeRepository() service := New(Config{NodeID: "node-a", SuperGiftMinValue: 100}, repository, nil, nil) service.SetClock(func() time.Time { return time.UnixMilli(1_700_000_000_000) }) + service.SetSenderProfileSource(fakeSenderProfileSource{profiles: map[int64]SenderProfile{ + 42: {UserID: 42, Account: "160042", Nickname: "Sender", Avatar: "https://cdn.example/sender.png", AvatarFrameURL: "https://cdn.example/sender-frame.png"}, + 43: {UserID: 43, Account: "160043", Nickname: "Receiver", Avatar: "https://cdn.example/receiver.png", AvatarFrameURL: "https://cdn.example/receiver-frame.png"}, + }}) envelope := mustGiftEnvelope(t, &roomeventsv1.RoomGiftSent{ SenderUserId: 42, TargetUserId: 43, @@ -26,6 +30,9 @@ func TestHandleRoomGiftSentCreatesRegionBroadcast(t *testing.T) { GiftValue: 999, VisibleRegionId: 1001, CommandId: "cmd-send-gift-1", + GiftName: "Rocket", + GiftIconUrl: "https://cdn.example/rocket.png", + GiftEffectTypes: []string{"animation", "global_broadcast"}, }) result, err := service.HandleRoomEvent(appcode.WithContext(context.Background(), "lalu"), envelope) @@ -43,9 +50,14 @@ func TestHandleRoomGiftSentCreatesRegionBroadcast(t *testing.T) { if err := json.Unmarshal([]byte(record.PayloadJSON), &payload); err != nil { t.Fatalf("payload is not json: %v", err) } - if payload["room_id"] != "room-1001" || payload["gift_id"] != "rocket" || payload["scope"] != "region" { + if payload["room_id"] != "room-1001" || payload["gift_id"] != "rocket" || payload["scope"] != "region" || payload["gift_icon_url"] != "https://cdn.example/rocket.png" { t.Fatalf("payload mismatch: %+v", payload) } + sender, _ := payload["sender"].(map[string]any) + receiver, _ := payload["receiver"].(map[string]any) + if sender["nickname"] != "Sender" || sender["avatar_frame_url"] != "https://cdn.example/sender-frame.png" || receiver["nickname"] != "Receiver" { + t.Fatalf("payload user profile mismatch: sender=%+v receiver=%+v", sender, receiver) + } again, err := service.HandleRoomEvent(appcode.WithContext(context.Background(), "lalu"), envelope) if err != nil { @@ -56,6 +68,33 @@ func TestHandleRoomGiftSentCreatesRegionBroadcast(t *testing.T) { } } +func TestHandleRoomGiftSentCreatesRegionBroadcastForTaggedGiftBelowThreshold(t *testing.T) { + repository := newFakeRepository() + service := New(Config{NodeID: "node-a", SuperGiftMinValue: 1000}, repository, nil, nil) + envelope := mustGiftEnvelope(t, &roomeventsv1.RoomGiftSent{ + SenderUserId: 42, + TargetUserId: 43, + GiftId: "tagged-gift", + GiftCount: 1, + GiftValue: 10, + VisibleRegionId: 1001, + CommandId: "cmd-tagged-gift", + GiftEffectTypes: []string{"animation", "global_broadcast"}, + }) + + result, err := service.HandleRoomEvent(appcode.WithContext(context.Background(), "lalu"), envelope) + if err != nil { + t.Fatalf("HandleRoomEvent tagged gift failed: %v", err) + } + if result.Status != broadcastdomain.StatusPending || !result.BroadcastCreated { + t.Fatalf("tagged gift should create broadcast: %+v", result) + } + record := repository.records[result.BroadcastEventID] + if record.GroupID != "hy_lalu_bc_r_1001" || record.BroadcastType != broadcastdomain.TypeSuperGift { + t.Fatalf("unexpected tagged gift broadcast record: %+v", record) + } +} + func TestHandleRoomPasswordChangedCreatesRegionBroadcast(t *testing.T) { repository := newFakeRepository() service := New(Config{NodeID: "node-a"}, repository, nil, nil) @@ -461,10 +500,20 @@ func (p *fakePublisher) DeleteGroupMember(_ context.Context, groupID string, use } type fakeSenderProfileSource struct { - profile SenderProfile - err error + profile SenderProfile + profiles map[int64]SenderProfile + err error } -func (s fakeSenderProfileSource) GetSenderProfile(context.Context, int64) (SenderProfile, error) { - return s.profile, s.err +func (s fakeSenderProfileSource) GetSenderProfile(_ context.Context, userID int64) (SenderProfile, error) { + if s.profiles != nil { + if profile, ok := s.profiles[userID]; ok { + return profile, s.err + } + } + profile := s.profile + if profile.UserID <= 0 { + profile.UserID = userID + } + return profile, s.err } diff --git a/services/activity-service/internal/service/firstrecharge/service.go b/services/activity-service/internal/service/firstrecharge/service.go index 017edbbe..e94bb859 100644 --- a/services/activity-service/internal/service/firstrecharge/service.go +++ b/services/activity-service/internal/service/firstrecharge/service.go @@ -22,7 +22,7 @@ const ( type Repository interface { GetFirstRechargeRewardConfig(ctx context.Context) (domain.Config, bool, error) UpdateFirstRechargeRewardConfig(ctx context.Context, config domain.Config, nowMS int64) (domain.Config, error) - GetFirstRechargeRewardClaimByUser(ctx context.Context, userID int64) (domain.Claim, bool, error) + ListFirstRechargeRewardClaimsByUser(ctx context.Context, userID int64) ([]domain.Claim, error) PrepareFirstRechargeRewardClaim(ctx context.Context, event domain.RechargeEvent, nowMS int64) (domain.PrepareResult, error) MarkFirstRechargeRewardClaimGranted(ctx context.Context, claimID string, walletGrantID string, grantedAtMS int64) (domain.Claim, error) MarkFirstRechargeRewardClaimFailed(ctx context.Context, claimID string, failureReason string, nowMS int64) error @@ -51,7 +51,7 @@ func (s *Service) SetClock(now func() time.Time) { } } -// GetStatus 返回 App 可展示的当前配置和用户发放状态;未配置时不是错误。 +// GetStatus 返回 App 可展示的当前配置和用户各档位发放状态;未配置时不是错误。 func (s *Service) GetStatus(ctx context.Context, userID int64) (domain.StatusResult, error) { if err := s.requireRepository(); err != nil { return domain.StatusResult{}, err @@ -65,18 +65,22 @@ func (s *Service) GetStatus(ctx context.Context, userID int64) (domain.StatusRes if err != nil { return domain.StatusResult{}, err } + active := []domain.Tier{} if exists { result.Enabled = config.Enabled - result.Tiers = activeTiers(config.Tiers) + active = activeTiers(config.Tiers) } - claim, claimed, err := s.repository.GetFirstRechargeRewardClaimByUser(ctx, userID) + claims, err := s.repository.ListFirstRechargeRewardClaimsByUser(ctx, userID) if err != nil { return domain.StatusResult{}, err } - if claimed { - result.Claim = claim - result.Rewarded = claim.Status == domain.StatusGranted + result.Claims = claims + if len(claims) > 0 { + result.Claim = claims[0] } + claimedTierIDs := grantedFirstRechargeTierIDs(claims) + result.Tiers = remainingFirstRechargeTiers(active, claimedTierIDs) + result.Rewarded = len(active) > 0 && len(result.Tiers) == 0 return result, nil } @@ -106,7 +110,7 @@ func (s *Service) UpdateConfig(ctx context.Context, config domain.Config) (domai return s.repository.UpdateFirstRechargeRewardConfig(ctx, config, s.now().UnixMilli()) } -// Consume 处理 wallet 成功充值事实;只有 recharge_sequence=1 且命中启用档位才发奖。 +// Consume 处理 wallet 成功充值事实;只有 Google 商品 ID 命中启用档位才发奖。 func (s *Service) Consume(ctx context.Context, event domain.RechargeEvent) (domain.Claim, bool, string, error) { if err := s.requireRepository(); err != nil { return domain.Claim{}, false, "", err @@ -115,9 +119,6 @@ func (s *Service) Consume(ctx context.Context, event domain.RechargeEvent) (doma if err := validateRechargeEvent(event); err != nil { return domain.Claim{}, false, "", err } - if event.RechargeSequence != 1 { - return domain.Claim{}, false, domain.ReasonNotFirst, nil - } prepared, err := s.repository.PrepareFirstRechargeRewardClaim(ctx, event, s.now().UnixMilli()) if err != nil { return domain.Claim{}, false, "", err @@ -209,11 +210,14 @@ func normalizeTiers(tiers []domain.Tier) []domain.Tier { ResourceGroupID: tier.ResourceGroupID, Status: status, SortOrder: tier.SortOrder, + USDMinorAmount: tier.USDMinorAmount, + GoogleProductID: strings.TrimSpace(tier.GoogleProductID), + DiscountPercent: tier.DiscountPercent, }) } sort.SliceStable(items, func(i, j int) bool { if items[i].SortOrder == items[j].SortOrder { - return items[i].MinCoinAmount < items[j].MinCoinAmount + return items[i].USDMinorAmount < items[j].USDMinorAmount } return items[i].SortOrder < items[j].SortOrder }) @@ -225,6 +229,7 @@ func validateConfig(config domain.Config) error { return xerr.New(xerr.InvalidArgument, "operator_admin_id is required") } seen := make(map[string]struct{}, len(config.Tiers)) + seenGoogleProducts := make(map[string]struct{}, len(config.Tiers)) active := make([]domain.Tier, 0, len(config.Tiers)) for _, tier := range config.Tiers { if tier.TierCode == "" { @@ -237,11 +242,19 @@ func validateConfig(config domain.Config) error { if tier.TierName == "" { return xerr.New(xerr.InvalidArgument, "tier_name is required") } - if tier.MinCoinAmount <= 0 { - return xerr.New(xerr.InvalidArgument, "min_coin_amount must be greater than zero") + if tier.USDMinorAmount <= 0 { + return xerr.New(xerr.InvalidArgument, "usd_minor_amount must be greater than zero") } - if tier.MaxCoinAmount > 0 && tier.MaxCoinAmount < tier.MinCoinAmount { - return xerr.New(xerr.InvalidArgument, "max_coin_amount must be greater than min_coin_amount") + googleProductID := strings.ToLower(strings.TrimSpace(tier.GoogleProductID)) + if googleProductID == "" { + return xerr.New(xerr.InvalidArgument, "google_product_id is required") + } + if _, exists := seenGoogleProducts[googleProductID]; exists { + return xerr.New(xerr.InvalidArgument, "google_product_id is duplicated") + } + seenGoogleProducts[googleProductID] = struct{}{} + if tier.DiscountPercent < 0 || tier.DiscountPercent > 100 { + return xerr.New(xerr.InvalidArgument, "discount_percent must be between 0 and 100") } if tier.ResourceGroupID <= 0 { return xerr.New(xerr.InvalidArgument, "resource_group_id is required") @@ -257,24 +270,9 @@ func validateConfig(config domain.Config) error { if config.Enabled && len(active) == 0 { return xerr.New(xerr.InvalidArgument, "enabled config requires at least one active tier") } - if hasOverlappedTiers(active) { - return xerr.New(xerr.InvalidArgument, "active tiers overlap") - } return nil } -func hasOverlappedTiers(tiers []domain.Tier) bool { - sort.SliceStable(tiers, func(i, j int) bool { return tiers[i].MinCoinAmount < tiers[j].MinCoinAmount }) - var previousMax int64 - for i, tier := range tiers { - if i > 0 && (previousMax == 0 || tier.MinCoinAmount <= previousMax) { - return true - } - previousMax = tier.MaxCoinAmount - } - return false -} - func activeTiers(tiers []domain.Tier) []domain.Tier { items := make([]domain.Tier, 0, len(tiers)) for _, tier := range tiers { @@ -285,6 +283,27 @@ func activeTiers(tiers []domain.Tier) []domain.Tier { return items } +func grantedFirstRechargeTierIDs(claims []domain.Claim) map[int64]struct{} { + ids := make(map[int64]struct{}, len(claims)) + for _, claim := range claims { + if claim.TierID > 0 && claim.Status == domain.StatusGranted { + ids[claim.TierID] = struct{}{} + } + } + return ids +} + +func remainingFirstRechargeTiers(tiers []domain.Tier, granted map[int64]struct{}) []domain.Tier { + items := make([]domain.Tier, 0, len(tiers)) + for _, tier := range tiers { + if _, ok := granted[tier.TierID]; ok { + continue + } + items = append(items, tier) + } + return items +} + func normalizeRechargeEvent(event domain.RechargeEvent) domain.RechargeEvent { event.AppCode = appcode.Normalize(event.AppCode) event.EventID = strings.TrimSpace(event.EventID) @@ -292,6 +311,7 @@ func normalizeRechargeEvent(event domain.RechargeEvent) domain.RechargeEvent { event.TransactionID = strings.TrimSpace(event.TransactionID) event.CommandID = strings.TrimSpace(event.CommandID) event.RechargeType = strings.TrimSpace(event.RechargeType) + event.GoogleProductID = strings.TrimSpace(event.GoogleProductID) if event.RechargeType == "" { event.RechargeType = domain.RechargeTypeCoinSeller } @@ -308,6 +328,9 @@ func validateRechargeEvent(event domain.RechargeEvent) error { if event.RechargeCoinAmount <= 0 { return xerr.New(xerr.InvalidArgument, "recharge_coin_amount must be greater than zero") } + if event.GoogleProductID == "" { + return xerr.New(xerr.InvalidArgument, "google_product_id is required") + } if event.OccurredAtMS <= 0 { return xerr.New(xerr.InvalidArgument, "occurred_at_ms is required") } diff --git a/services/activity-service/internal/service/firstrecharge/service_test.go b/services/activity-service/internal/service/firstrecharge/service_test.go index be76fea6..afeb31d8 100644 --- a/services/activity-service/internal/service/firstrecharge/service_test.go +++ b/services/activity-service/internal/service/firstrecharge/service_test.go @@ -21,18 +21,29 @@ func TestFirstRechargeRewardConsumesRechargeFactWithMySQL(t *testing.T) { Enabled: true, UpdatedByAdminID: 90001, Tiers: []domain.Tier{{ - TierCode: "first_100", - TierName: "首冲 100+", - MinCoinAmount: 100, - MaxCoinAmount: 0, + TierCode: "first_099", + TierName: "首冲 $0.99", + USDMinorAmount: 99, + GoogleProductID: "first_recharge_google_099", + DiscountPercent: 70, ResourceGroupID: 88001, Status: domain.TierStatusActive, + SortOrder: 1, + }, { + TierCode: "first_199", + TierName: "首冲 $1.99", + USDMinorAmount: 199, + GoogleProductID: "first_recharge_google_199", + DiscountPercent: 60, + ResourceGroupID: 88002, + Status: domain.TierStatusActive, + SortOrder: 2, }}, }) if err != nil { t.Fatalf("UpdateConfig failed: %v", err) } - if len(config.Tiers) != 1 || config.Tiers[0].TierID <= 0 { + if len(config.Tiers) != 2 || config.Tiers[0].TierID <= 0 || config.Tiers[1].TierID <= 0 { t.Fatalf("tier id was not generated: %+v", config.Tiers) } @@ -44,8 +55,10 @@ func TestFirstRechargeRewardConsumesRechargeFactWithMySQL(t *testing.T) { CommandID: "seller-transfer-1", UserID: 42001, RechargeCoinAmount: 500, - RechargeSequence: 1, - RechargeType: domain.RechargeTypeCoinSeller, + RechargeSequence: 2, + RechargeType: "google", + RechargeUSDMinor: 99, + GoogleProductID: "first_recharge_google_099", OccurredAtMS: 1710000000000, } err = svc.ConsumeWalletRechargeEvent(ctx, event) @@ -56,7 +69,7 @@ func TestFirstRechargeRewardConsumesRechargeFactWithMySQL(t *testing.T) { if err != nil { t.Fatalf("GetFirstRechargeRewardClaimByUser failed: %v", err) } - if !exists || claim.Status != domain.StatusGranted || claim.ResourceGroupID != 88001 { + if !exists || claim.Status != domain.StatusGranted || claim.ResourceGroupID != 88001 || claim.GoogleProductID != "first_recharge_google_099" { t.Fatalf("consume result mismatch: exists=%v claim=%+v", exists, claim) } if len(wallet.grants) != 1 || wallet.grants[0].GroupId != 88001 || wallet.grants[0].TargetUserId != 42001 { @@ -67,8 +80,8 @@ func TestFirstRechargeRewardConsumesRechargeFactWithMySQL(t *testing.T) { if err != nil { t.Fatalf("GetStatus failed: %v", err) } - if !status.Rewarded || status.Claim.ClaimID != claim.ClaimID { - t.Fatalf("status should include granted claim: %+v", status) + if status.Rewarded || len(status.Tiers) != 1 || status.Tiers[0].GoogleProductID != "first_recharge_google_199" || len(status.Claims) != 1 { + t.Fatalf("status should keep the unpurchased tier visible: %+v", status) } err = svc.ConsumeWalletRechargeEvent(ctx, event) @@ -82,13 +95,21 @@ func TestFirstRechargeRewardConsumesRechargeFactWithMySQL(t *testing.T) { event.EventID = "evt-recharge-2" event.TransactionID = "wtx-recharge-2" event.CommandID = "seller-transfer-2" - event.RechargeSequence = 2 + event.RechargeUSDMinor = 199 + event.GoogleProductID = "first_recharge_google_199" err = svc.ConsumeWalletRechargeEvent(ctx, event) if err != nil { t.Fatalf("second recharge ConsumeWalletRechargeEvent failed: %v", err) } - if len(wallet.grants) != 1 { - t.Fatalf("second recharge should be ignored, grants=%d", len(wallet.grants)) + if len(wallet.grants) != 2 || wallet.grants[1].GroupId != 88002 { + t.Fatalf("second tier should be granted, grants=%+v", wallet.grants) + } + status, err = svc.GetStatus(ctx, 42001) + if err != nil { + t.Fatalf("GetStatus after all tiers failed: %v", err) + } + if !status.Rewarded || len(status.Tiers) != 0 || len(status.Claims) != 2 { + t.Fatalf("status should complete after all tiers are purchased: %+v", status) } } diff --git a/services/activity-service/internal/storage/mysql/first_recharge_repository.go b/services/activity-service/internal/storage/mysql/first_recharge_repository.go index 670cb7f0..abe0bbf6 100644 --- a/services/activity-service/internal/storage/mysql/first_recharge_repository.go +++ b/services/activity-service/internal/storage/mysql/first_recharge_repository.go @@ -76,10 +76,12 @@ func (r *Repository) UpdateFirstRechargeRewardConfig(ctx context.Context, config if _, err := tx.ExecContext(ctx, ` INSERT INTO first_recharge_reward_tiers ( tier_id, app_code, tier_code, tier_name, min_coin_amount, max_coin_amount, - resource_group_id, status, sort_order, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + resource_group_id, status, sort_order, created_at_ms, updated_at_ms, + usd_minor_amount, google_product_id, discount_percent + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, tier.TierID, appcode.FromContext(ctx), tier.TierCode, tier.TierName, tier.MinCoinAmount, tier.MaxCoinAmount, tier.ResourceGroupID, tier.Status, tier.SortOrder, tier.CreatedAtMS, tier.UpdatedAtMS, + tier.USDMinorAmount, tier.GoogleProductID, tier.DiscountPercent, ); err != nil { return domain.Config{}, err } @@ -88,10 +90,12 @@ func (r *Repository) UpdateFirstRechargeRewardConfig(ctx context.Context, config result, err := tx.ExecContext(ctx, ` INSERT INTO first_recharge_reward_tiers ( app_code, tier_code, tier_name, min_coin_amount, max_coin_amount, - resource_group_id, status, sort_order, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + resource_group_id, status, sort_order, created_at_ms, updated_at_ms, + usd_minor_amount, google_product_id, discount_percent + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, appcode.FromContext(ctx), tier.TierCode, tier.TierName, tier.MinCoinAmount, tier.MaxCoinAmount, tier.ResourceGroupID, tier.Status, tier.SortOrder, tier.CreatedAtMS, tier.UpdatedAtMS, + tier.USDMinorAmount, tier.GoogleProductID, tier.DiscountPercent, ) if err != nil { return domain.Config{}, err @@ -115,13 +119,33 @@ func (r *Repository) UpdateFirstRechargeRewardConfig(ctx context.Context, config return updated, nil } -// GetFirstRechargeRewardClaimByUser 返回 App 查询需要的用户首冲奖励事实。 +// ListFirstRechargeRewardClaimsByUser 返回 App 查询需要的用户各档位首冲奖励事实。 +func (r *Repository) ListFirstRechargeRewardClaimsByUser(ctx context.Context, userID int64) ([]domain.Claim, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + rows, err := r.db.QueryContext(ctx, firstRechargeRewardClaimSelectSQL()+` + WHERE app_code = ? AND user_id = ? + ORDER BY created_at_ms DESC, claim_id DESC`, + appcode.FromContext(ctx), userID, + ) + if err != nil { + return nil, err + } + defer rows.Close() + claims, _, err := scanFirstRechargeRewardClaims(rows, 0) + return claims, err +} + +// GetFirstRechargeRewardClaimByUser 保留给旧测试和内部兼容;返回最近一条首充奖励事实。 func (r *Repository) GetFirstRechargeRewardClaimByUser(ctx context.Context, userID int64) (domain.Claim, bool, error) { if r == nil || r.db == nil { return domain.Claim{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured") } row := r.db.QueryRowContext(ctx, firstRechargeRewardClaimSelectSQL()+` - WHERE app_code = ? AND user_id = ?`, + WHERE app_code = ? AND user_id = ? + ORDER BY created_at_ms DESC, claim_id DESC + LIMIT 1`, appcode.FromContext(ctx), userID, ) claim, err := scanFirstRechargeRewardClaim(row) @@ -142,9 +166,6 @@ func (r *Repository) PrepareFirstRechargeRewardClaim(ctx context.Context, event } defer func() { _ = tx.Rollback() }() - if event.RechargeSequence != 1 { - return domain.PrepareResult{Reason: domain.ReasonNotFirst}, tx.Commit() - } config, exists, err := r.getFirstRechargeRewardConfigForUpdate(ctx, tx) if err != nil { return domain.PrepareResult{}, err @@ -159,7 +180,7 @@ func (r *Repository) PrepareFirstRechargeRewardClaim(ctx context.Context, event if err != nil { return domain.PrepareResult{}, err } - tier, matched := matchFirstRechargeRewardTier(tiers, event.RechargeCoinAmount) + tier, matched := matchFirstRechargeRewardTier(tiers, event.GoogleProductID) if !matched { return domain.PrepareResult{Reason: domain.ReasonTierNotMatched}, tx.Commit() } @@ -172,7 +193,7 @@ func (r *Repository) PrepareFirstRechargeRewardClaim(ctx context.Context, event if err != nil || exists { return r.prepareExistingFirstRechargeRewardClaim(ctx, tx, existing, exists, err, event, nowMS) } - existing, exists, err = r.getFirstRechargeRewardClaimByUserForUpdate(ctx, tx, event.UserID) + existing, exists, err = r.getFirstRechargeRewardClaimByUserTierForUpdate(ctx, tx, event.UserID, tier.TierID) if err != nil || exists { return r.prepareExistingFirstRechargeRewardClaim(ctx, tx, existing, exists, err, event, nowMS) } @@ -193,6 +214,9 @@ func (r *Repository) PrepareFirstRechargeRewardClaim(ctx context.Context, event RechargedAtMS: event.OccurredAtMS, CreatedAtMS: nowMS, UpdatedAtMS: nowMS, + TierUSDMinorAmount: tier.USDMinorAmount, + GoogleProductID: tier.GoogleProductID, + RechargeUSDMinor: event.RechargeUSDMinor, } claim.WalletCommandID = walletFirstRechargeRewardCommandID(claim.ClaimID) if err := r.insertFirstRechargeRewardClaim(ctx, tx, claim); err != nil { @@ -214,6 +238,9 @@ func (r *Repository) prepareExistingFirstRechargeRewardClaim(ctx context.Context if claim.UserID != event.UserID { return domain.PrepareResult{}, xerr.New(xerr.RequestConflict, "first recharge reward event payload conflicts") } + if claim.GoogleProductID != "" && event.GoogleProductID != "" && !strings.EqualFold(claim.GoogleProductID, event.GoogleProductID) { + return domain.PrepareResult{}, xerr.New(xerr.RequestConflict, "first recharge reward product payload conflicts") + } if claim.Status == domain.StatusGranted { if err := tx.Commit(); err != nil { return domain.PrepareResult{}, err @@ -322,13 +349,13 @@ func (r *Repository) listFirstRechargeRewardTiers(ctx context.Context, tx *sql.T if tx != nil { rows, err = tx.QueryContext(ctx, firstRechargeRewardTierSelectSQL()+` WHERE app_code = ? - ORDER BY sort_order ASC, min_coin_amount ASC, tier_id ASC`, + ORDER BY sort_order ASC, usd_minor_amount ASC, tier_id ASC`, appcode.FromContext(ctx), ) } else { rows, err = r.db.QueryContext(ctx, firstRechargeRewardTierSelectSQL()+` WHERE app_code = ? - ORDER BY sort_order ASC, min_coin_amount ASC, tier_id ASC`, + ORDER BY sort_order ASC, usd_minor_amount ASC, tier_id ASC`, appcode.FromContext(ctx), ) } @@ -342,7 +369,7 @@ func (r *Repository) listFirstRechargeRewardTiers(ctx context.Context, tx *sql.T func (r *Repository) listFirstRechargeRewardTiersForUpdate(ctx context.Context, tx *sql.Tx) ([]domain.Tier, error) { rows, err := tx.QueryContext(ctx, firstRechargeRewardTierSelectSQL()+` WHERE app_code = ? - ORDER BY sort_order ASC, min_coin_amount ASC, tier_id ASC + ORDER BY sort_order ASC, usd_minor_amount ASC, tier_id ASC FOR UPDATE`, appcode.FromContext(ctx), ) @@ -379,11 +406,11 @@ func (r *Repository) getFirstRechargeRewardClaimByTransactionForUpdate(ctx conte return claim, err == nil, err } -func (r *Repository) getFirstRechargeRewardClaimByUserForUpdate(ctx context.Context, tx *sql.Tx, userID int64) (domain.Claim, bool, error) { +func (r *Repository) getFirstRechargeRewardClaimByUserTierForUpdate(ctx context.Context, tx *sql.Tx, userID int64, tierID int64) (domain.Claim, bool, error) { row := tx.QueryRowContext(ctx, firstRechargeRewardClaimSelectSQL()+` - WHERE app_code = ? AND user_id = ? + WHERE app_code = ? AND user_id = ? AND tier_id = ? FOR UPDATE`, - appcode.FromContext(ctx), userID, + appcode.FromContext(ctx), userID, tierID, ) claim, err := scanFirstRechargeRewardClaim(row) if errors.Is(err, sql.ErrNoRows) { @@ -410,11 +437,13 @@ func (r *Repository) insertFirstRechargeRewardClaim(ctx context.Context, tx *sql app_code, claim_id, event_id, transaction_id, command_id, user_id, tier_id, tier_code, resource_group_id, recharge_coin_amount, recharge_sequence, recharge_type, status, wallet_command_id, wallet_grant_id, failure_reason, - recharged_at_ms, granted_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', ?, 0, ?, ?)`, + recharged_at_ms, granted_at_ms, created_at_ms, updated_at_ms, + tier_usd_minor_amount, google_product_id, recharge_usd_minor + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', ?, 0, ?, ?, ?, ?, ?)`, appcode.FromContext(ctx), claim.ClaimID, claim.EventID, claim.TransactionID, claim.CommandID, claim.UserID, claim.TierID, claim.TierCode, claim.ResourceGroupID, claim.RechargeCoinAmount, claim.RechargeSequence, claim.RechargeType, claim.Status, claim.WalletCommandID, claim.RechargedAtMS, claim.CreatedAtMS, claim.UpdatedAtMS, + claim.TierUSDMinorAmount, claim.GoogleProductID, claim.RechargeUSDMinor, ) return err } @@ -454,6 +483,9 @@ func scanFirstRechargeRewardTiers(rows *sql.Rows) ([]domain.Tier, error) { &item.SortOrder, &item.CreatedAtMS, &item.UpdatedAtMS, + &item.USDMinorAmount, + &item.GoogleProductID, + &item.DiscountPercent, ); err != nil { return nil, err } @@ -499,6 +531,9 @@ func scanFirstRechargeRewardClaim(row rowScanner) (domain.Claim, error) { &claim.GrantedAtMS, &claim.CreatedAtMS, &claim.UpdatedAtMS, + &claim.TierUSDMinorAmount, + &claim.GoogleProductID, + &claim.RechargeUSDMinor, ) return claim, err } @@ -510,7 +545,8 @@ func firstRechargeRewardConfigSelectSQL() string { func firstRechargeRewardTierSelectSQL() string { return `SELECT tier_id, tier_code, tier_name, min_coin_amount, max_coin_amount, - resource_group_id, status, sort_order, created_at_ms, updated_at_ms + resource_group_id, status, sort_order, created_at_ms, updated_at_ms, + usd_minor_amount, google_product_id, discount_percent FROM first_recharge_reward_tiers ` } @@ -518,7 +554,8 @@ func firstRechargeRewardClaimSelectSQL() string { return `SELECT claim_id, event_id, transaction_id, command_id, user_id, tier_id, tier_code, resource_group_id, recharge_coin_amount, recharge_sequence, recharge_type, status, wallet_command_id, wallet_grant_id, failure_reason, recharged_at_ms, - granted_at_ms, created_at_ms, updated_at_ms + granted_at_ms, created_at_ms, updated_at_ms, + tier_usd_minor_amount, google_product_id, recharge_usd_minor FROM first_recharge_reward_claims ` } @@ -536,18 +573,18 @@ func firstRechargeRewardClaimWhere(ctx context.Context, query domain.ClaimQuery) return "WHERE " + strings.Join(conditions, " AND "), args } -func matchFirstRechargeRewardTier(tiers []domain.Tier, coinAmount int64) (domain.Tier, bool) { +func matchFirstRechargeRewardTier(tiers []domain.Tier, googleProductID string) (domain.Tier, bool) { + normalizedGoogleProductID := strings.ToLower(strings.TrimSpace(googleProductID)) + if normalizedGoogleProductID == "" { + return domain.Tier{}, false + } for _, tier := range tiers { - if tier.Status != domain.TierStatusActive || tier.ResourceGroupID <= 0 || tier.MinCoinAmount <= 0 { + if tier.Status != domain.TierStatusActive || tier.ResourceGroupID <= 0 { continue } - if coinAmount < tier.MinCoinAmount { - continue + if strings.ToLower(strings.TrimSpace(tier.GoogleProductID)) == normalizedGoogleProductID { + return tier, true } - if tier.MaxCoinAmount > 0 && coinAmount > tier.MaxCoinAmount { - continue - } - return tier, true } return domain.Tier{}, false } diff --git a/services/activity-service/internal/transport/grpc/first_recharge_server.go b/services/activity-service/internal/transport/grpc/first_recharge_server.go index 7fd7b67c..0dda2944 100644 --- a/services/activity-service/internal/transport/grpc/first_recharge_server.go +++ b/services/activity-service/internal/transport/grpc/first_recharge_server.go @@ -41,6 +41,8 @@ func (s *FirstRechargeRewardServer) ConsumeFirstRechargeReward(ctx context.Conte RechargeCoinAmount: req.GetRechargeCoinAmount(), RechargeSequence: req.GetRechargeSequence(), RechargeType: req.GetRechargeType(), + RechargeUSDMinor: req.GetRechargeUsdMinor(), + GoogleProductID: req.GetGoogleProductId(), OccurredAtMS: req.GetOccurredAtMs(), }) if err != nil { @@ -86,6 +88,9 @@ func (s *AdminFirstRechargeRewardServer) UpdateFirstRechargeRewardConfig(ctx con ResourceGroupID: tier.GetResourceGroupId(), Status: tier.GetStatus(), SortOrder: tier.GetSortOrder(), + USDMinorAmount: tier.GetUsdMinorAmount(), + GoogleProductID: tier.GetGoogleProductId(), + DiscountPercent: tier.GetDiscountPercent(), }) } config, err := s.svc.UpdateConfig(ctx, domain.Config{ @@ -123,11 +128,15 @@ func firstRechargeRewardStatusToProto(status domain.StatusResult) *activityv1.Fi Tiers: make([]*activityv1.FirstRechargeRewardTier, 0, len(status.Tiers)), Rewarded: status.Rewarded, Claim: firstRechargeRewardClaimToProto(status.Claim), + Claims: make([]*activityv1.FirstRechargeRewardClaim, 0, len(status.Claims)), ServerTimeMs: status.ServerTimeMS, } for _, tier := range status.Tiers { resp.Tiers = append(resp.Tiers, firstRechargeRewardTierToProto(tier)) } + for _, claim := range status.Claims { + resp.Claims = append(resp.Claims, firstRechargeRewardClaimToProto(claim)) + } return resp } @@ -158,6 +167,9 @@ func firstRechargeRewardTierToProto(tier domain.Tier) *activityv1.FirstRechargeR SortOrder: tier.SortOrder, CreatedAtMs: tier.CreatedAtMS, UpdatedAtMs: tier.UpdatedAtMS, + UsdMinorAmount: tier.USDMinorAmount, + GoogleProductId: tier.GoogleProductID, + DiscountPercent: tier.DiscountPercent, } } @@ -185,5 +197,8 @@ func firstRechargeRewardClaimToProto(claim domain.Claim) *activityv1.FirstRechar GrantedAtMs: claim.GrantedAtMS, CreatedAtMs: claim.CreatedAtMS, UpdatedAtMs: claim.UpdatedAtMS, + TierUsdMinorAmount: claim.TierUSDMinorAmount, + GoogleProductId: claim.GoogleProductID, + RechargeUsdMinor: claim.RechargeUSDMinor, } } diff --git a/services/game-service/deploy/mysql/initdb/001_game_service.sql b/services/game-service/deploy/mysql/initdb/001_game_service.sql index 712855b5..1522744c 100644 --- a/services/game-service/deploy/mysql/initdb/001_game_service.sql +++ b/services/game-service/deploy/mysql/initdb/001_game_service.sql @@ -112,6 +112,223 @@ CREATE TABLE IF NOT EXISTS game_orders ( KEY idx_game_order_round(app_code, platform_code, provider_round_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏钱包订单表'; +CREATE TABLE IF NOT EXISTS game_dice_matches ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + match_id VARCHAR(96) NOT NULL COMMENT '骰子局 ID', + game_id VARCHAR(96) NOT NULL COMMENT '内部游戏 ID', + platform_code VARCHAR(64) NOT NULL COMMENT '平台编码快照', + provider_game_id VARCHAR(128) NOT NULL COMMENT '提供方游戏 ID 快照', + room_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '房间 ID', + region_id BIGINT NOT NULL DEFAULT 0 COMMENT '用户区域 ID', + min_players INT NOT NULL COMMENT '最少开局人数', + max_players INT NOT NULL COMMENT '最多参与人数', + current_players INT NOT NULL DEFAULT 0 COMMENT '当前参与人数', + stake_coin BIGINT NOT NULL COMMENT '单人下注金币', + round_no INT NOT NULL DEFAULT 1 COMMENT '轮次', + status VARCHAR(32) NOT NULL COMMENT '局状态', + result VARCHAR(64) NOT NULL DEFAULT '' COMMENT '局结果', + join_deadline_ms BIGINT NOT NULL DEFAULT 0 COMMENT '加入截止时间,UTC epoch ms', + ready_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '匹配完成时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + settled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '结算时间,UTC epoch ms', + canceled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '取消时间,UTC epoch ms', + fee_bps INT NOT NULL DEFAULT 500 COMMENT '本局平台抽水万分比快照', + pool_bps INT NOT NULL DEFAULT 100 COMMENT '本局奖池入池万分比快照', + match_mode VARCHAR(32) NOT NULL DEFAULT '' COMMENT '匹配模式 human/robot', + forced_result VARCHAR(32) NOT NULL DEFAULT '' COMMENT '机器人奖池不足时的强制结果', + pool_delta_coin BIGINT NOT NULL DEFAULT 0 COMMENT '本局对奖池的净变化', + PRIMARY KEY(app_code, match_id), + KEY idx_game_dice_room_active(app_code, room_id, status, created_at_ms), + KEY idx_game_dice_status_time(app_code, status, updated_at_ms, match_id), + KEY idx_game_dice_waiting(app_code, game_id, stake_coin, status, created_at_ms, match_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='骰子游戏局表'; + +CREATE TABLE IF NOT EXISTS game_dice_participants ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + match_id VARCHAR(96) NOT NULL COMMENT '骰子局 ID', + user_id BIGINT NOT NULL COMMENT '用户 ID', + participant_type VARCHAR(32) NOT NULL DEFAULT 'user' COMMENT '参与者类型 user/robot', + seat_no INT NOT NULL COMMENT '座位号', + status VARCHAR(32) NOT NULL COMMENT '参与状态', + stake_coin BIGINT NOT NULL COMMENT '下注金币', + dice_points_json JSON NULL COMMENT '骰子点数', + result VARCHAR(32) NOT NULL DEFAULT '' COMMENT '参与结果', + payout_coin BIGINT NOT NULL DEFAULT 0 COMMENT '返奖金币', + debit_order_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '下注订单 ID', + payout_order_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '派奖订单 ID', + refund_order_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '退款订单 ID', + balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '最近一次钱包账后余额', + joined_at_ms BIGINT NOT NULL COMMENT '加入时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY(app_code, match_id, user_id), + UNIQUE KEY uk_game_dice_seat(app_code, match_id, seat_no), + KEY idx_game_dice_participant_user(app_code, user_id, updated_at_ms), + KEY idx_game_dice_participant_status(app_code, match_id, status, seat_no) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='骰子游戏参与者表'; + +-- 骰子旧库已经存在 match/participant 表时,CREATE TABLE 不会补列;这里把 H5 实时流程依赖的阶段、费率、机器人和奖池快照列幂等补齐。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'ready_at_ms') = 0, + 'ALTER TABLE game_dice_matches ADD COLUMN ready_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''匹配完成时间,UTC epoch ms'' AFTER join_deadline_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'canceled_at_ms') = 0, + 'ALTER TABLE game_dice_matches ADD COLUMN canceled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''取消时间,UTC epoch ms'' AFTER settled_at_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'fee_bps') = 0, + 'ALTER TABLE game_dice_matches ADD COLUMN fee_bps INT NOT NULL DEFAULT 500 COMMENT ''本局平台抽水万分比快照'' AFTER canceled_at_ms', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'pool_bps') = 0, + 'ALTER TABLE game_dice_matches ADD COLUMN pool_bps INT NOT NULL DEFAULT 100 COMMENT ''本局奖池入池万分比快照'' AFTER fee_bps', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'match_mode') = 0, + 'ALTER TABLE game_dice_matches ADD COLUMN match_mode VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''匹配模式 human/robot'' AFTER pool_bps', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'forced_result') = 0, + 'ALTER TABLE game_dice_matches ADD COLUMN forced_result VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''机器人奖池不足时的强制结果'' AFTER match_mode', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND COLUMN_NAME = 'pool_delta_coin') = 0, + 'ALTER TABLE game_dice_matches ADD COLUMN pool_delta_coin BIGINT NOT NULL DEFAULT 0 COMMENT ''本局对奖池的净变化'' AFTER forced_result', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 参与者类型是机器人扩展的事实字段;旧局默认真人,避免历史数据阻塞查询和结算。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_participants' AND COLUMN_NAME = 'participant_type') = 0, + 'ALTER TABLE game_dice_participants ADD COLUMN participant_type VARCHAR(32) NOT NULL DEFAULT ''user'' COMMENT ''参与者类型 user/robot'' AFTER user_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 全服等待队列按 app/game/stake/status 命中;旧库没有这个索引时,高并发匹配会退化成扫描历史局。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND INDEX_NAME = 'idx_game_dice_waiting') = 0, + 'ALTER TABLE game_dice_matches ADD INDEX idx_game_dice_waiting (app_code, game_id, stake_coin, status, created_at_ms, match_id)', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_participants' AND INDEX_NAME = 'idx_game_dice_participant_status') = 0, + 'ALTER TABLE game_dice_participants ADD INDEX idx_game_dice_participant_status (app_code, match_id, status, seat_no)', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +CREATE TABLE IF NOT EXISTS game_self_game_configs ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '配置状态', + stake_options_json JSON NULL COMMENT '可下注金币档位', + fee_bps INT NOT NULL DEFAULT 500 COMMENT '平台抽水万分比', + pool_bps INT NOT NULL DEFAULT 100 COMMENT '奖池入池万分比', + min_players INT NOT NULL DEFAULT 2 COMMENT '最少开局人数', + max_players INT NOT NULL DEFAULT 2 COMMENT '最多参与人数', + robot_enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用机器人补位', + robot_match_wait_ms BIGINT NOT NULL DEFAULT 3000 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, game_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏配置表'; + +CREATE TABLE IF NOT EXISTS game_self_game_pools ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID', + balance_coin 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, game_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏机器人奖池表'; + +-- 默认骰子配置只在新环境插入一次;后台后续修改必须保留,不用初始化脚本覆盖线上运营参数。 +SET @now_ms := CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED); +INSERT INTO game_self_game_configs ( + app_code, game_id, status, stake_options_json, fee_bps, pool_bps, + min_players, max_players, robot_enabled, robot_match_wait_ms, created_at_ms, updated_at_ms +) VALUES ( + 'lalu', 'dice', 'active', + CAST('[{"stake_coin":100,"enabled":true,"sort_order":10},{"stake_coin":500,"enabled":true,"sort_order":20},{"stake_coin":1000,"enabled":true,"sort_order":30},{"stake_coin":8000,"enabled":true,"sort_order":40}]' AS JSON), + 500, 100, 2, 2, 1, 3000, @now_ms, @now_ms +) ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; + +INSERT INTO game_self_game_pools (app_code, game_id, balance_coin, created_at_ms, updated_at_ms) +VALUES ('lalu', 'dice', 0, @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; + +CREATE TABLE IF NOT EXISTS game_self_game_pool_transactions ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + adjustment_id VARCHAR(96) NOT NULL COMMENT '奖池流水 ID', + game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID', + match_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '关联骰子局 ID', + user_id BIGINT NOT NULL DEFAULT 0 COMMENT '关联真实用户 ID', + direction VARCHAR(16) NOT NULL COMMENT '方向 in/out', + amount_coin BIGINT NOT NULL COMMENT '变化金币', + reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '变化原因', + balance_after BIGINT NOT NULL COMMENT '变化后奖池余额', + created_by BIGINT NOT NULL DEFAULT 0 COMMENT '后台操作者或系统用户', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + PRIMARY KEY(app_code, adjustment_id), + KEY idx_game_pool_tx_game_time(app_code, game_id, created_at_ms), + KEY idx_game_pool_tx_match(app_code, match_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏奖池流水表'; + +CREATE TABLE IF NOT EXISTS game_self_game_robots ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + game_id VARCHAR(96) NOT NULL COMMENT '自研游戏 ID', + user_id BIGINT NOT NULL COMMENT '真实 App 机器人用户 ID', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '机器人状态', + created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY(app_code, game_id, user_id), + KEY idx_game_robot_status(app_code, game_id, status, updated_at_ms, user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏机器人登记表'; + CREATE TABLE IF NOT EXISTS game_callback_logs ( app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', callback_id VARCHAR(96) NOT NULL COMMENT '回调 ID', diff --git a/services/game-service/internal/app/app.go b/services/game-service/internal/app/app.go index 2a22a74b..32fbf17b 100644 --- a/services/game-service/internal/app/app.go +++ b/services/game-service/internal/app/app.go @@ -20,6 +20,7 @@ import ( "hyapp/pkg/rocketmqx" "hyapp/services/game-service/internal/client" "hyapp/services/game-service/internal/config" + diceservice "hyapp/services/game-service/internal/service/dice" gameservice "hyapp/services/game-service/internal/service/game" mysqlstorage "hyapp/services/game-service/internal/storage/mysql" grpcserver "hyapp/services/game-service/internal/transport/grpc" @@ -91,8 +92,11 @@ func New(cfg config.Config) (*App, error) { } server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("game-service"))) - svc := gameservice.New(gameservice.Config{LaunchSessionTTL: cfg.LaunchSessionTTL}, repo, client.NewWalletClient(walletConn), client.NewUserClient(userConn), client.NewActivityGrowthClient(activityConn)) - transport := grpcserver.NewServer(svc) + walletClient := client.NewWalletClient(walletConn) + svc := gameservice.New(gameservice.Config{LaunchSessionTTL: cfg.LaunchSessionTTL}, repo, walletClient, client.NewUserClient(userConn), client.NewActivityGrowthClient(activityConn)) + // 骰子服务复用同一个 MySQL repository 和 wallet client;局事实走 dice 表,金币事实仍走 game_orders + wallet-service。 + diceSvc := diceservice.New(diceservice.Config{}, repo, walletClient) + transport := grpcserver.NewServer(svc, diceSvc) gamev1.RegisterGameAppServiceServer(server, transport) gamev1.RegisterGameCallbackServiceServer(server, transport) gamev1.RegisterGameAdminServiceServer(server, transport) diff --git a/services/game-service/internal/domain/dice/dice.go b/services/game-service/internal/domain/dice/dice.go new file mode 100644 index 00000000..f93095ef --- /dev/null +++ b/services/game-service/internal/domain/dice/dice.go @@ -0,0 +1,393 @@ +package dice + +import ( + "encoding/json" + "sort" + "strings" + + "hyapp/pkg/xerr" +) + +const ( + // 默认平台值只用于自研骰子没有第三方 provider 配置时的订单快照,保证 game_orders 唯一键仍有稳定平台维度。 + DefaultPlatformCode = "dice" + DefaultProviderGameID = "dice" + // DefaultFeeBPS 用万分比表达平台费,结算时先从总奖池扣除,再把剩余奖池均分给最高分赢家。 + DefaultFeeBPS = int64(500) + DefaultPoolBPS = int64(100) + DefaultJoinTTLMillis = int64(60_000) + DefaultRobotWaitMS = int64(3_000) + DefaultGameID = "dice" + // 首版禁止 1 人局,避免单人场景下“自己赢自己”的派奖语义不清;3 人局只是多一行 participant。 + DefaultMinPlayers = int32(2) + DefaultMaxPlayers = int32(2) + HardMaxPlayers = int32(6) + // 单局胜场制每个参与者只扔一颗骰子;数组仍保留是为了兼容旧接口字段和未来玩法扩展。 + PointsPerParticipant = 1 + ProviderOrderPrefix = "dice" + MatchIDPrefix = "dice_match" + ParticipantResultWin = "win" + ParticipantResultLose = "lose" + ParticipantResultDraw = "draw" + + MatchStatusCreated = "created" + MatchStatusJoining = "joining" + MatchStatusReady = "ready" + MatchStatusSettling = "settling" + MatchStatusPayoutApplying = "payout_applying" + MatchStatusSettled = "settled" + MatchStatusFailed = "failed" + MatchStatusCanceled = "canceled" + + MatchPhaseWaiting = "waiting" + MatchPhaseCountdown = "countdown" + MatchPhaseRolling = "rolling" + MatchPhaseComparing = "comparing" + MatchPhaseSettled = "settled" + MatchPhaseCanceled = "canceled" + MatchPhaseFailed = "failed" + CountdownMillis = int64(3_000) + RollingMillis = int64(3_000) + ComparingMillis = int64(3_000) + DrawRerollMillis = int64(2_000) + SettlementShowMillis = int64(3_000) + + ParticipantStatusJoined = "joined" + ParticipantStatusDebitSucceeded = "debit_succeeded" + ParticipantStatusDebitFailed = "debit_failed" + ParticipantStatusPayoutSucceeded = "payout_succeeded" + ParticipantStatusRefundSucceeded = "refund_succeeded" + ParticipantStatusSettled = "settled" + + ParticipantTypeUser = "user" + ParticipantTypeRobot = "robot" + + MatchModeHuman = "human" + MatchModeRobot = "robot" + + ForcedResultPlayerLose = "player_lose" + + ConfigStatusActive = "active" + ConfigStatusDisabled = "disabled" + + RobotStatusActive = "active" + RobotStatusDisabled = "disabled" + + PoolDirectionIn = "in" + PoolDirectionOut = "out" +) + +// StakeOption 是后台配置给 H5 展示的下注档位;排序字段只影响展示,不参与结算公式。 +type StakeOption struct { + StakeCoin int64 + Enabled bool + SortOrder int32 +} + +// Config 是自研骰子游戏的运营配置快照;每局创建时会把费率复制到 match,保证历史局按当时配置结算。 +type Config struct { + AppCode string + GameID string + Status string + StakeOptions []StakeOption + FeeBPS int32 + PoolBPS int32 + MinPlayers int32 + MaxPlayers int32 + RobotEnabled bool + RobotMatchWaitMS int64 + PoolBalanceCoin int64 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// Robot 是 game-service 对“哪些真实 App 用户可作为机器人”的最小登记,不复制用户头像昵称。 +type Robot struct { + AppCode string + GameID string + UserID int64 + Status string + CreatedByAdminID int64 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// PoolAdjustment 是奖池余额变化事实;所有机器人对局和后台手工调整都必须留下流水。 +type PoolAdjustment struct { + AdjustmentID string + AppCode string + GameID string + MatchID string + UserID int64 + Direction string + AmountCoin int64 + Reason string + BalanceAfter int64 + CreatedBy int64 + CreatedAtMS int64 +} + +// Match 是骰子局的持久化事实;平台和 provider_game_id 是下单快照,不直接暴露给 App。 +type Match struct { + AppCode string + MatchID string + GameID string + PlatformCode string + ProviderGameID string + RoomID string + RegionID int64 + MinPlayers int32 + MaxPlayers int32 + CurrentPlayers int32 + StakeCoin int64 + RoundNo int32 + Status string + Result string + Participants []Participant + JoinDeadlineMS int64 + ReadyAtMS int64 + CreatedAtMS int64 + UpdatedAtMS int64 + SettledAtMS int64 + CanceledAtMS int64 + FeeBPS int32 + PoolBPS int32 + MatchMode string + ForcedResult string + PoolDeltaCoin int64 +} + +// Participant 是一局内单个用户的座位、扣款、投骰和派奖快照。 +type Participant struct { + AppCode string + MatchID string + UserID int64 + ParticipantType string + SeatNo int32 + Status string + StakeCoin int64 + DicePoints []int32 + Result string + PayoutCoin int64 + DebitOrderID string + PayoutOrderID string + RefundOrderID string + BalanceAfter int64 + JoinedAtMS int64 + UpdatedAtMS int64 +} + +// NormalizeConfig 把后台配置收口成可执行值;空配置仍返回安全默认,保证新环境可以直接打开骰子 H5。 +func NormalizeConfig(config Config) (Config, error) { + config.GameID = strings.TrimSpace(config.GameID) + if config.GameID == "" { + config.GameID = DefaultGameID + } + config.Status = strings.TrimSpace(config.Status) + if config.Status == "" { + config.Status = ConfigStatusActive + } + if config.Status != ConfigStatusActive && config.Status != ConfigStatusDisabled { + return Config{}, xerr.New(xerr.InvalidArgument, "dice config status is invalid") + } + if config.FeeBPS < 0 || config.FeeBPS >= 10_000 { + return Config{}, xerr.New(xerr.InvalidArgument, "dice fee_bps is invalid") + } + if config.FeeBPS == 0 { + config.FeeBPS = int32(DefaultFeeBPS) + } + if config.PoolBPS < 0 || int64(config.FeeBPS)+int64(config.PoolBPS) >= 10_000 { + return Config{}, xerr.New(xerr.InvalidArgument, "dice pool_bps is invalid") + } + if config.PoolBPS == 0 { + config.PoolBPS = int32(DefaultPoolBPS) + } + minPlayers, maxPlayers, err := NormalizePlayerBounds(config.MinPlayers, config.MaxPlayers) + if err != nil { + return Config{}, err + } + config.MinPlayers = minPlayers + config.MaxPlayers = maxPlayers + if config.RobotMatchWaitMS <= 0 { + config.RobotMatchWaitMS = DefaultRobotWaitMS + } + config.StakeOptions = NormalizeStakeOptions(config.StakeOptions) + return config, nil +} + +// NormalizeStakeOptions 去重、过滤非法档位并保持后台排序;空配置给出本地可用的保底档位。 +func NormalizeStakeOptions(options []StakeOption) []StakeOption { + if len(options) == 0 { + return []StakeOption{ + {StakeCoin: 100, Enabled: true, SortOrder: 10}, + {StakeCoin: 500, Enabled: true, SortOrder: 20}, + {StakeCoin: 1000, Enabled: true, SortOrder: 30}, + {StakeCoin: 8000, Enabled: true, SortOrder: 40}, + } + } + seen := make(map[int64]struct{}, len(options)) + out := make([]StakeOption, 0, len(options)) + for _, option := range options { + if option.StakeCoin <= 0 { + continue + } + if _, ok := seen[option.StakeCoin]; ok { + continue + } + seen[option.StakeCoin] = struct{}{} + out = append(out, option) + } + sort.SliceStable(out, func(left int, right int) bool { + if out[left].SortOrder == out[right].SortOrder { + return out[left].StakeCoin < out[right].StakeCoin + } + return out[left].SortOrder < out[right].SortOrder + }) + if len(out) == 0 { + return NormalizeStakeOptions(nil) + } + return out +} + +// StakeEnabled 明确校验下注金额是否来自后台启用档位,避免 H5 篡改 stake_coin 打出任意金额。 +func StakeEnabled(config Config, stakeCoin int64) bool { + for _, option := range config.StakeOptions { + if option.StakeCoin == stakeCoin && option.Enabled { + return true + } + } + return false +} + +// StakeOptionsJSON 把后台档位保存成稳定 JSON;仓储不做字符串拼接,避免后续字段扩展破坏解析。 +func StakeOptionsJSON(options []StakeOption) string { + raw, err := json.Marshal(NormalizeStakeOptions(options)) + if err != nil { + return "[]" + } + return string(raw) +} + +// ParseStakeOptions 解析数据库 JSON;坏配置回退为空,后续 NormalizeConfig 会补安全默认。 +func ParseStakeOptions(raw string) []StakeOption { + var options []StakeOption + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &options); err != nil { + return nil + } + return NormalizeStakeOptions(options) +} + +// NormalizePlayerBounds 把客户端可调人数压回当前可承载范围;多人扩展只需要调上限,不需要换表。 +func NormalizePlayerBounds(minPlayers int32, maxPlayers int32) (int32, int32, error) { + // 客户端不传人数时走产品默认值;传了非法值则直接拒绝,不在后端静默纠正成另一种玩法。 + if minPlayers <= 0 { + minPlayers = DefaultMinPlayers + } + if maxPlayers <= 0 { + maxPlayers = DefaultMaxPlayers + } + if minPlayers < 2 { + return 0, 0, xerr.New(xerr.InvalidArgument, "dice min_players must be at least 2") + } + if maxPlayers < minPlayers { + return 0, 0, xerr.New(xerr.InvalidArgument, "dice max_players must be greater than or equal to min_players") + } + if maxPlayers > HardMaxPlayers { + return 0, 0, xerr.New(xerr.InvalidArgument, "dice max_players is too large") + } + return minPlayers, maxPlayers, nil +} + +// Score 只读取单颗骰子点数;校验长度可以防止旧三骰数据或未投骰数据被当作新规则结算。 +func Score(points []int32) (int32, error) { + // 点数必须来自服务端随机或已落库事实;长度不对说明本局还没完成投骰,不能推导结果。 + if len(points) != PointsPerParticipant { + return 0, xerr.New(xerr.InvalidArgument, "dice points are incomplete") + } + point := points[0] + if point < 1 || point > 6 { + return 0, xerr.New(xerr.InvalidArgument, "dice point is invalid") + } + return point, nil +} + +// SettleByHighestScore 使用单骰最高点数结算;全部平局只标记 draw,由 service 保持同一局继续重投。 +func SettleByHighestScore(participants []Participant, feeBPS int64) ([]Participant, string, error) { + return SettleByHighestScoreWithPool(participants, feeBPS, 0) +} + +// SettleByHighestScoreWithPool 使用单骰最高点数结算,并把平台抽水和入池比例都从总奖池里扣出。 +func SettleByHighestScoreWithPool(participants []Participant, feeBPS int64, poolBPS int64) ([]Participant, string, error) { + // 规则层再次兜底人数,避免绕过 service 或仓储测试桩时把未成局数据结算成有效账务。 + if len(participants) < int(DefaultMinPlayers) { + return nil, "", xerr.New(xerr.Conflict, "dice match does not have enough participants") + } + if feeBPS < 0 || feeBPS >= 10_000 { + return nil, "", xerr.New(xerr.InvalidArgument, "dice fee_bps is invalid") + } + if poolBPS < 0 || feeBPS+poolBPS >= 10_000 { + return nil, "", xerr.New(xerr.InvalidArgument, "dice pool_bps is invalid") + } + settled := append([]Participant(nil), participants...) + // 按座位稳定排序后再输出结果,客户端刷新时参与者顺序不会因为 map/查询顺序变化而跳动。 + sort.SliceStable(settled, func(left int, right int) bool { + return settled[left].SeatNo < settled[right].SeatNo + }) + + var highScore int32 + winnerIndexes := make([]int, 0, len(settled)) + for index := range settled { + // 每个参与者先取单骰点数,同时借 Score 校验 dice_points_json 是否完整合法。 + score, err := Score(settled[index].DicePoints) + if err != nil { + return nil, "", err + } + if index == 0 || score > highScore { + highScore = score + winnerIndexes = winnerIndexes[:0] + winnerIndexes = append(winnerIndexes, index) + continue + } + if score == highScore { + winnerIndexes = append(winnerIndexes, index) + } + } + + if len(winnerIndexes) == len(settled) { + // 全员最高分相同视为和局;首版产品要求 2 秒后继续扔,所以这里不产生 payout,也不进入平台费和奖池抽成。 + for index := range settled { + settled[index].Result = ParticipantResultDraw + settled[index].PayoutCoin = 0 + } + return settled, ParticipantResultDraw, nil + } + + var totalStake int64 + for index := range settled { + // 押注必须是正数;一旦有坏账务快照,整局停止结算,由上层标记 failed 等待人工或补偿。 + if settled[index].StakeCoin <= 0 { + return nil, "", xerr.New(xerr.InvalidArgument, "dice stake_coin is invalid") + } + totalStake += settled[index].StakeCoin + settled[index].Result = ParticipantResultLose + settled[index].PayoutCoin = 0 + } + // 非和局时先扣平台费和入池金额,再把整数奖池按最高分赢家均分;除不尽的余数留在平台费侧,避免多发币。 + payoutPool := totalStake * (10_000 - feeBPS - poolBPS) / 10_000 + share := payoutPool / int64(len(winnerIndexes)) + for _, index := range winnerIndexes { + settled[index].Result = ParticipantResultWin + settled[index].PayoutCoin = share + } + return settled, ParticipantResultWin, nil +} + +// HasRolls 判断本局是否已经保存过骰子点数;重试结算必须复用旧点数,不能重新随机。 +func HasRolls(participants []Participant) bool { + for _, participant := range participants { + if len(participant.DicePoints) > 0 { + return true + } + } + return false +} diff --git a/services/game-service/internal/service/dice/service.go b/services/game-service/internal/service/dice/service.go new file mode 100644 index 00000000..177c6b1d --- /dev/null +++ b/services/game-service/internal/service/dice/service.go @@ -0,0 +1,898 @@ +package dice + +import ( + "context" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + "math/big" + "strings" + "time" + + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + dicedomain "hyapp/services/game-service/internal/domain/dice" + gamedomain "hyapp/services/game-service/internal/domain/game" +) + +// Repository 是骰子用例层需要的持久化边界;局表只保存游戏事实,金币流水仍复用 game_orders。 +type Repository interface { + GetLaunchableGame(ctx context.Context, appCode string, gameID string) (gamedomain.LaunchableGame, error) + CreateGameOrder(ctx context.Context, order gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) + MarkOrderSucceeded(ctx context.Context, appCode string, orderID string, walletTransactionID string, balanceAfter int64, nowMs int64) error + MarkOrderFailed(ctx context.Context, appCode string, orderID string, status string, code string, message string, nowMs int64) error + + CreateDiceMatch(ctx context.Context, match dicedomain.Match) (dicedomain.Match, error) + JoinDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) + GetDiceMatch(ctx context.Context, appCode string, matchID string) (dicedomain.Match, error) + ClaimDiceMatchForRoll(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) + SaveDiceRolls(ctx context.Context, match dicedomain.Match, nowMs int64) (dicedomain.Match, error) + SaveDiceDrawForReroll(ctx context.Context, match dicedomain.Match, nowMs int64) (dicedomain.Match, error) + MarkDiceParticipantDebitSucceeded(ctx context.Context, appCode string, matchID string, userID int64, orderID string, balanceAfter int64, nowMs int64) error + MarkDiceParticipantDebitFailed(ctx context.Context, appCode string, matchID string, userID int64, orderID string, nowMs int64) error + MarkDiceParticipantPayoutSucceeded(ctx context.Context, appCode string, matchID string, userID int64, orderID string, balanceAfter int64, nowMs int64) error + MarkDiceParticipantRefundSucceeded(ctx context.Context, appCode string, matchID string, userID int64, orderID string, balanceAfter int64, nowMs int64) error + MarkDiceMatchSettled(ctx context.Context, appCode string, matchID string, result string, nowMs int64) error + MarkDiceMatchFailed(ctx context.Context, appCode string, matchID string, result string, nowMs int64) error + GetDiceConfig(ctx context.Context, appCode string, gameID string) (dicedomain.Config, error) + ListSelfGameConfigs(ctx context.Context, appCode string) ([]dicedomain.Config, error) + UpsertDiceConfig(ctx context.Context, config dicedomain.Config, nowMs int64) (dicedomain.Config, error) + FindAndJoinWaitingDiceMatch(ctx context.Context, appCode string, gameID string, userID int64, stakeCoin int64, nowMs int64) (dicedomain.Match, error) + JoinDiceRobot(ctx context.Context, appCode string, matchID string, robotUserID int64, forcedResult string, nowMs int64) (dicedomain.Match, error) + CancelDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) + PickActiveDiceRobot(ctx context.Context, appCode string, gameID string, matchID string) (dicedomain.Robot, error) + AdjustDicePool(ctx context.Context, adjustment dicedomain.PoolAdjustment) (dicedomain.PoolAdjustment, error) + ListDiceRobots(ctx context.Context, appCode string, gameID string, status string, pageSize int32, cursor string) ([]dicedomain.Robot, string, error) + RegisterDiceRobots(ctx context.Context, appCode string, gameID string, userIDs []int64, adminID int64, nowMs int64) ([]dicedomain.Robot, error) + SetDiceRobotStatus(ctx context.Context, appCode string, gameID string, userID int64, status string, nowMs int64) (dicedomain.Robot, error) + DeleteDiceRobot(ctx context.Context, appCode string, gameID string, userID int64) error +} + +// WalletClient 是 game-service 调 wallet-service 的游戏改账入口。 +type WalletClient interface { + ApplyGameCoinChange(ctx context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) + GetBalances(ctx context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) +} + +type Config struct { + JoinTTL time.Duration + FeeBPS int64 + MaxPlayers int32 +} + +type Service struct { + config Config + repository Repository + wallet WalletClient + // now/randomInt 作为依赖注入点,保证测试能锁定时间和骰子点数,不需要碰全局随机源。 + now func() time.Time + randomInt func(max int) (int, error) +} + +type CreateMatchCommand struct { + AppCode string + RequestID string + UserID int64 + GameID string + RoomID string + RegionID int64 + StakeCoin int64 + MinPlayers int32 + MaxPlayers int32 +} + +type JoinMatchCommand struct { + AppCode string + RequestID string + UserID int64 + MatchID string +} + +type GetMatchCommand struct { + AppCode string + UserID int64 + MatchID string +} + +type RollMatchCommand struct { + AppCode string + RequestID string + UserID int64 + MatchID string +} + +type MatchCommand struct { + AppCode string + RequestID string + UserID int64 + GameID string + RoomID string + RegionID int64 + StakeCoin int64 +} + +type CancelMatchCommand struct { + AppCode string + RequestID string + UserID int64 + MatchID string +} + +// New 创建骰子游戏用例层;随机数、时间和费率都集中在这里,便于测试和后续按房间配置扩展。 +func New(config Config, repository Repository, wallet WalletClient) *Service { + // 默认值在构造阶段一次性收口,后面的业务分支只看规范化后的配置,避免每个入口重复判断。 + if config.JoinTTL <= 0 { + config.JoinTTL = time.Duration(dicedomain.DefaultJoinTTLMillis) * time.Millisecond + } + if config.FeeBPS <= 0 { + config.FeeBPS = dicedomain.DefaultFeeBPS + } + if config.MaxPlayers <= 0 || config.MaxPlayers > dicedomain.HardMaxPlayers { + config.MaxPlayers = dicedomain.HardMaxPlayers + } + return &Service{ + config: config, + repository: repository, + wallet: wallet, + now: time.Now, + randomInt: cryptoRandomInt, + } +} + +// CreateMatch 只创建局和首个参与者,不提前扣金币;真正扣款在 roll 阶段统一串到钱包和 outbox。 +func (s *Service) CreateMatch(ctx context.Context, command CreateMatchCommand) (dicedomain.Match, int64, error) { + // 创建局必须先校验 game_catalog/platform 当前可用,防止下架或维护中的游戏继续生成可结算局。 + if s.repository == nil { + return dicedomain.Match{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + if command.UserID <= 0 || strings.TrimSpace(command.GameID) == "" || command.StakeCoin <= 0 { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice match command is incomplete") + } + minPlayers, maxPlayers, err := dicedomain.NormalizePlayerBounds(command.MinPlayers, command.MaxPlayers) + if err != nil { + return dicedomain.Match{}, 0, err + } + if maxPlayers > s.config.MaxPlayers { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice max_players is too large") + } + app := appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, app) + + game, err := s.repository.GetLaunchableGame(ctx, app, strings.TrimSpace(command.GameID)) + if err != nil { + return dicedomain.Match{}, 0, err + } + if game.PlatformStatus != gamedomain.StatusActive || game.Status != gamedomain.StatusActive { + return dicedomain.Match{}, 0, xerr.New(xerr.Conflict, "dice game is not active") + } + config, err := s.configForMatch(ctx, app, game.GameID) + if err != nil { + return dicedomain.Match{}, 0, err + } + if config.Status != dicedomain.ConfigStatusActive { + return dicedomain.Match{}, 0, xerr.New(xerr.Conflict, "dice game config is disabled") + } + if !dicedomain.StakeEnabled(config, command.StakeCoin) { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice stake_coin is not configured") + } + if command.MinPlayers <= 0 { + command.MinPlayers = config.MinPlayers + } + if command.MaxPlayers <= 0 { + command.MaxPlayers = config.MaxPlayers + } + minPlayers, maxPlayers, err = dicedomain.NormalizePlayerBounds(command.MinPlayers, command.MaxPlayers) + if err != nil { + return dicedomain.Match{}, 0, err + } + if maxPlayers > s.config.MaxPlayers { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice max_players is too large") + } + + now := s.now() + nowMS := now.UnixMilli() + // match_id 用 app/user/time/request 混合生成;真正防重靠数据库主键,hash 只负责长度稳定和不暴露业务输入。 + matchID := dicedomain.MatchIDPrefix + "_" + stableHash(fmt.Sprintf("%s|%d|%d|%s", app, command.UserID, now.UnixNano(), command.RequestID))[:20] + match := dicedomain.Match{ + AppCode: app, + MatchID: matchID, + GameID: game.GameID, + PlatformCode: strings.TrimSpace(game.PlatformCode), + ProviderGameID: strings.TrimSpace(game.ProviderGameID), + RoomID: strings.TrimSpace(command.RoomID), + RegionID: command.RegionID, + MinPlayers: minPlayers, + MaxPlayers: maxPlayers, + CurrentPlayers: 1, + StakeCoin: command.StakeCoin, + RoundNo: 1, + Status: dicedomain.MatchStatusCreated, + FeeBPS: config.FeeBPS, + PoolBPS: config.PoolBPS, + MatchMode: dicedomain.MatchModeHuman, + JoinDeadlineMS: now.Add(s.config.JoinTTL).UnixMilli(), + CreatedAtMS: nowMS, + UpdatedAtMS: nowMS, + Participants: []dicedomain.Participant{{ + // 创建人天然占 1 号座位;后续 join 在仓储行锁内按 max(seat_no)+1 分配,避免并发抢座冲突。 + AppCode: app, + MatchID: matchID, + UserID: command.UserID, + ParticipantType: dicedomain.ParticipantTypeUser, + SeatNo: 1, + Status: dicedomain.ParticipantStatusJoined, + StakeCoin: command.StakeCoin, + JoinedAtMS: nowMS, + UpdatedAtMS: nowMS, + }}, + } + if match.PlatformCode == "" { + // 自研骰子理论上仍应在 catalog 配好平台;这里兜底是为了订单唯一键和统计 outbox 字段始终非空。 + match.PlatformCode = dicedomain.DefaultPlatformCode + } + if match.ProviderGameID == "" { + match.ProviderGameID = dicedomain.DefaultProviderGameID + } + saved, err := s.repository.CreateDiceMatch(ctx, match) + if err != nil { + return dicedomain.Match{}, 0, err + } + return saved, nowMS, nil +} + +// GetConfig 返回 H5 大厅需要的骰子配置;没有后台配置时返回内置默认,不阻断新环境联调。 +func (s *Service) GetConfig(ctx context.Context, appCode string, gameID string) (dicedomain.Config, int64, error) { + if s.repository == nil { + return dicedomain.Config{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + app := appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, app) + config, err := s.configForMatch(ctx, app, strings.TrimSpace(gameID)) + if err != nil { + return dicedomain.Config{}, 0, err + } + return config, s.now().UnixMilli(), nil +} + +func (s *Service) ListSelfGames(ctx context.Context, appCode string) ([]dicedomain.Config, int64, error) { + if s.repository == nil { + return nil, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + app := appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, app) + configs, err := s.repository.ListSelfGameConfigs(ctx, app) + if err != nil { + return nil, 0, err + } + if len(configs) == 0 { + config, err := s.configForMatch(ctx, app, dicedomain.DefaultGameID) + if err != nil { + return nil, 0, err + } + configs = append(configs, config) + } + return configs, s.now().UnixMilli(), nil +} + +func (s *Service) UpdateConfig(ctx context.Context, config dicedomain.Config) (dicedomain.Config, int64, error) { + if s.repository == nil { + return dicedomain.Config{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + config.AppCode = appcode.Normalize(config.AppCode) + ctx = appcode.WithContext(ctx, config.AppCode) + nowMS := s.now().UnixMilli() + saved, err := s.repository.UpsertDiceConfig(ctx, config, nowMS) + return saved, nowMS, err +} + +func (s *Service) AdjustPool(ctx context.Context, adjustment dicedomain.PoolAdjustment) (dicedomain.PoolAdjustment, dicedomain.Config, int64, error) { + if s.repository == nil { + return dicedomain.PoolAdjustment{}, dicedomain.Config{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + adjustment.AppCode = appcode.Normalize(adjustment.AppCode) + ctx = appcode.WithContext(ctx, adjustment.AppCode) + nowMS := s.now().UnixMilli() + adjustment.CreatedAtMS = nowMS + if adjustment.AdjustmentID == "" { + adjustment.AdjustmentID = "dpool_admin_" + stableHash(fmt.Sprintf("%s|%s|%s|%d|%d|%s", adjustment.AppCode, adjustment.GameID, adjustment.Direction, adjustment.AmountCoin, adjustment.CreatedBy, adjustment.Reason))[:20] + } + saved, err := s.repository.AdjustDicePool(ctx, adjustment) + if err != nil { + return dicedomain.PoolAdjustment{}, dicedomain.Config{}, 0, err + } + config, err := s.configForMatch(ctx, adjustment.AppCode, adjustment.GameID) + return saved, config, nowMS, err +} + +func (s *Service) ListRobots(ctx context.Context, appCode string, gameID string, status string, pageSize int32, cursor string) ([]dicedomain.Robot, string, int64, error) { + if s.repository == nil { + return nil, "", 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + app := appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, app) + items, next, err := s.repository.ListDiceRobots(ctx, app, gameID, status, pageSize, cursor) + return items, next, s.now().UnixMilli(), err +} + +func (s *Service) RegisterRobots(ctx context.Context, appCode string, gameID string, userIDs []int64, adminID int64) ([]dicedomain.Robot, int64, error) { + if s.repository == nil { + return nil, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + app := appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, app) + nowMS := s.now().UnixMilli() + items, err := s.repository.RegisterDiceRobots(ctx, app, gameID, userIDs, adminID, nowMS) + return items, nowMS, err +} + +func (s *Service) SetRobotStatus(ctx context.Context, appCode string, gameID string, userID int64, status string) (dicedomain.Robot, int64, error) { + if s.repository == nil { + return dicedomain.Robot{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + app := appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, app) + nowMS := s.now().UnixMilli() + item, err := s.repository.SetDiceRobotStatus(ctx, app, gameID, userID, status, nowMS) + return item, nowMS, err +} + +// DeleteRobot 删除当前 app 和游戏下的机器人登记,真实用户资料仍由 user-service 保留用于历史数据追溯。 +func (s *Service) DeleteRobot(ctx context.Context, appCode string, gameID string, userID int64) (int64, error) { + if s.repository == nil { + return 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + if userID <= 0 { + return 0, xerr.New(xerr.InvalidArgument, "dice robot user id is invalid") + } + app := appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, app) + nowMS := s.now().UnixMilli() + err := s.repository.DeleteDiceRobot(ctx, app, gameID, userID) + return nowMS, err +} + +// Match 按全服 app_code + game_id + stake_coin 匹配;没有等待真人时创建等待局,不提前扣金币。 +func (s *Service) Match(ctx context.Context, command MatchCommand) (dicedomain.Match, int64, error) { + if s.repository == nil { + return dicedomain.Match{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + if s.wallet == nil { + return dicedomain.Match{}, 0, xerr.New(xerr.Unavailable, "wallet client is not configured") + } + if command.UserID <= 0 || strings.TrimSpace(command.GameID) == "" || command.StakeCoin <= 0 { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice match command is incomplete") + } + app := appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, app) + config, err := s.configForMatch(ctx, app, strings.TrimSpace(command.GameID)) + if err != nil { + return dicedomain.Match{}, 0, err + } + if config.Status != dicedomain.ConfigStatusActive { + return dicedomain.Match{}, 0, xerr.New(xerr.Conflict, "dice game config is disabled") + } + if !dicedomain.StakeEnabled(config, command.StakeCoin) { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice stake_coin is not configured") + } + if err := s.ensureCoinBalance(ctx, command.RequestID, app, command.UserID, command.StakeCoin); err != nil { + return dicedomain.Match{}, 0, err + } + nowMS := s.now().UnixMilli() + match, err := s.repository.FindAndJoinWaitingDiceMatch(ctx, app, config.GameID, command.UserID, command.StakeCoin, nowMS) + if err == nil { + return s.attachRobotIfDue(ctx, command.RequestID, match, nowMS) + } + if !xerr.IsCode(err, xerr.NotFound) { + return dicedomain.Match{}, 0, err + } + // 没有可加入的真人局时创建新局;room_id 仅落字段预留,首版全服匹配强制不按房间隔离。 + return s.CreateMatch(ctx, CreateMatchCommand{ + AppCode: app, + RequestID: command.RequestID, + UserID: command.UserID, + GameID: config.GameID, + RoomID: "", + RegionID: command.RegionID, + StakeCoin: command.StakeCoin, + MinPlayers: config.MinPlayers, + MaxPlayers: config.MaxPlayers, + }) +} + +// JoinMatch 在 match 行锁内分配座位;钱包不在这里扣款,避免未开局匹配占用用户余额。 +func (s *Service) JoinMatch(ctx context.Context, command JoinMatchCommand) (dicedomain.Match, int64, error) { + if s.repository == nil { + return dicedomain.Match{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + if command.UserID <= 0 || strings.TrimSpace(command.MatchID) == "" { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice join command is incomplete") + } + app := appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, app) + nowMS := s.now().UnixMilli() + // 加入只修改参与者事实;如果用户重复点击 join,仓储按唯一参与者返回已有局快照。 + match, err := s.repository.JoinDiceMatch(ctx, app, strings.TrimSpace(command.MatchID), command.UserID, nowMS) + if err != nil { + return dicedomain.Match{}, 0, err + } + return match, nowMS, nil +} + +// GetMatch 只读取 MySQL 当前事实;如果后续加 Redis,也只能作为这个结果的旁路缓存。 +func (s *Service) GetMatch(ctx context.Context, command GetMatchCommand) (dicedomain.Match, int64, error) { + if s.repository == nil { + return dicedomain.Match{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + if command.UserID <= 0 || strings.TrimSpace(command.MatchID) == "" { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice get command is incomplete") + } + app := appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, app) + nowMS := s.now().UnixMilli() + match, err := s.repository.GetDiceMatch(ctx, app, strings.TrimSpace(command.MatchID)) + if err != nil { + return dicedomain.Match{}, 0, err + } + return s.attachRobotIfDue(ctx, "", match, nowMS) +} + +// CancelMatch 只允许等待中的创建人撤销局;没有扣款,因此取消不会触发钱包补偿。 +func (s *Service) CancelMatch(ctx context.Context, command CancelMatchCommand) (dicedomain.Match, int64, error) { + if s.repository == nil { + return dicedomain.Match{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + if command.UserID <= 0 || strings.TrimSpace(command.MatchID) == "" { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice cancel command is incomplete") + } + app := appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, app) + nowMS := s.now().UnixMilli() + match, err := s.repository.CancelDiceMatch(ctx, app, strings.TrimSpace(command.MatchID), command.UserID, nowMS) + return match, nowMS, err +} + +// RollMatch 先用 MySQL 抢占结算权,再串行调用钱包;钱包幂等键保证重试不会重复扣款或派奖。 +func (s *Service) RollMatch(ctx context.Context, command RollMatchCommand) (dicedomain.Match, int64, error) { + if s.repository == nil { + return dicedomain.Match{}, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + if s.wallet == nil { + return dicedomain.Match{}, 0, xerr.New(xerr.Unavailable, "wallet client is not configured") + } + if command.UserID <= 0 || strings.TrimSpace(command.MatchID) == "" { + return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice roll command is incomplete") + } + app := appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, app) + nowMS := s.now().UnixMilli() + + // ClaimDiceMatchForRoll 用 match 行锁把局切到 settling;锁提交后才调用钱包,减少数据库锁持有时间。 + match, err := s.repository.ClaimDiceMatchForRoll(ctx, app, strings.TrimSpace(command.MatchID), command.UserID, nowMS) + if err != nil { + return dicedomain.Match{}, 0, err + } + if match.Status == dicedomain.MatchStatusSettled { + // roll 重试命中已结算局时直接返回结果,客户端可以安全重复请求。 + return match, nowMS, nil + } + if len(match.Participants) < int(match.MinPlayers) { + return dicedomain.Match{}, 0, xerr.New(xerr.Conflict, "dice match does not have enough participants") + } + normalizeMatchEconomicSnapshot(&match) + + for index := range match.Participants { + if match.Participants[index].ParticipantType == dicedomain.ParticipantTypeRobot { + // 机器人是真实 App 用户资料,但对局资金来自共享奖池;不能对机器人用户钱包扣本金。 + match.Participants[index].Status = dicedomain.ParticipantStatusDebitSucceeded + continue + } + if alreadyDebited(match.Participants[index].Status) { + // 补偿或重试进入这里时,已扣款/已派奖/已退款的参与者不能再次扣本金。 + continue + } + // 每个参与者独立生成 provider_order_id;wallet-service 用 command_id/request_hash 做最终账务幂等。 + order, balanceAfter, err := s.applyGameCoinChange(ctx, command.RequestID, match, match.Participants[index], "debit", match.Participants[index].StakeCoin) + if err != nil { + // 任一扣款失败,本局不能继续开奖;已扣成功的参与者尽量立即退款,失败细节留在 game_orders 和 match failed 状态里。 + _ = s.repository.MarkDiceParticipantDebitFailed(ctx, app, match.MatchID, match.Participants[index].UserID, order.OrderID, nowMS) + _ = s.refundDebitedParticipants(ctx, command.RequestID, match, nowMS) + _ = s.repository.MarkDiceMatchFailed(ctx, app, match.MatchID, "debit_failed", nowMS) + return dicedomain.Match{}, 0, err + } + match.Participants[index].Status = dicedomain.ParticipantStatusDebitSucceeded + match.Participants[index].DebitOrderID = order.OrderID + match.Participants[index].BalanceAfter = balanceAfter + if err := s.repository.MarkDiceParticipantDebitSucceeded(ctx, app, match.MatchID, match.Participants[index].UserID, order.OrderID, balanceAfter, nowMS); err != nil { + return dicedomain.Match{}, 0, err + } + } + + if !dicedomain.HasRolls(match.Participants) { + // 只有首次结算可以随机点数;点数一旦落库,所有后续重试必须复用旧点数,避免改变输赢结果。 + if err := s.rollParticipants(match.Participants); err != nil { + return dicedomain.Match{}, 0, err + } + if match.ForcedResult == dicedomain.ForcedResultPlayerLose { + // 奖池不足的机器人局在匹配完成时已经冻结强制结果;roll 阶段只负责生成与结果一致的点数。 + s.forceHumanLose(match.Participants) + } + settledParticipants, result, err := dicedomain.SettleByHighestScoreWithPool(match.Participants, int64(match.FeeBPS), int64(match.PoolBPS)) + if err != nil { + return dicedomain.Match{}, 0, err + } + if result == dicedomain.ParticipantResultDraw { + // 和局不退本金也不派奖;保存本轮点数给 H5 展示,局保持 ready,2 秒后客户端继续调用 roll。 + match.Participants = settledParticipants + match.Result = result + saved, err := s.repository.SaveDiceDrawForReroll(ctx, match, nowMS) + return saved, nowMS, err + } + // SaveDiceRolls 原子写入每个参与者点数、结果和 payout_coin,同时把局推进到 payout_applying。 + match.Participants = settledParticipants + match.Result = result + match, err = s.repository.SaveDiceRolls(ctx, match, nowMS) + if err != nil { + return dicedomain.Match{}, 0, err + } + } else if match.Result == "" { + // 兼容“点数已落库但 match.result 未写入”的半完成状态,按旧点数重新推导派彩金额。 + settledParticipants, result, err := dicedomain.SettleByHighestScoreWithPool(match.Participants, int64(match.FeeBPS), int64(match.PoolBPS)) + if err != nil { + return dicedomain.Match{}, 0, err + } + match.Participants = settledParticipants + match.Result = result + } + + for index := range match.Participants { + participant := match.Participants[index] + if participant.ParticipantType == dicedomain.ParticipantTypeRobot { + // 机器人输赢只影响奖池,不走用户钱包 credit/refund。 + continue + } + if participant.PayoutCoin <= 0 || participant.Status == dicedomain.ParticipantStatusPayoutSucceeded || participant.Status == dicedomain.ParticipantStatusRefundSucceeded || participant.Status == dicedomain.ParticipantStatusSettled { + // 输家 payout_coin 为 0;已处理过的赢家或和局用户不重复调用钱包。 + continue + } + opType := "credit" + if participant.Result == dicedomain.ParticipantResultDraw { + // 和局走 refund 而不是 credit,账务语义上明确这是返还本金,不算中奖派奖。 + opType = "refund" + } + order, balanceAfter, err := s.applyGameCoinChange(ctx, command.RequestID, match, participant, opType, participant.PayoutCoin) + if err != nil { + // 点数已经固定后派奖失败不能重投,只标记 failed 交给后续补偿继续按同一 provider_order_id 处理。 + _ = s.repository.MarkDiceMatchFailed(ctx, app, match.MatchID, "payout_failed", nowMS) + return dicedomain.Match{}, 0, err + } + match.Participants[index].BalanceAfter = balanceAfter + if opType == "refund" { + match.Participants[index].Status = dicedomain.ParticipantStatusRefundSucceeded + if err := s.repository.MarkDiceParticipantRefundSucceeded(ctx, app, match.MatchID, participant.UserID, order.OrderID, balanceAfter, nowMS); err != nil { + return dicedomain.Match{}, 0, err + } + continue + } + match.Participants[index].Status = dicedomain.ParticipantStatusPayoutSucceeded + if err := s.repository.MarkDiceParticipantPayoutSucceeded(ctx, app, match.MatchID, participant.UserID, order.OrderID, balanceAfter, nowMS); err != nil { + return dicedomain.Match{}, 0, err + } + } + + if err := s.applyPoolDelta(ctx, command.RequestID, match, nowMS); err != nil { + // 钱包账务已经按固定点数推进后,奖池流水失败不能重投;把局留在 failed 便于人工按 match_id 对账补偿。 + _ = s.repository.MarkDiceMatchFailed(ctx, app, match.MatchID, "pool_failed", nowMS) + return dicedomain.Match{}, 0, err + } + + // 所有应付账务都成功后才把 match 和 participants 统一标记 settled,客户端看到 settled 即可认为结果不可变。 + if err := s.repository.MarkDiceMatchSettled(ctx, app, match.MatchID, match.Result, nowMS); err != nil { + return dicedomain.Match{}, 0, err + } + saved, err := s.repository.GetDiceMatch(ctx, app, match.MatchID) + return saved, nowMS, err +} + +func (s *Service) configForMatch(ctx context.Context, appCode string, gameID string) (dicedomain.Config, error) { + config, err := s.repository.GetDiceConfig(ctx, appCode, strings.TrimSpace(gameID)) + if err == nil { + return config, nil + } + if !xerr.IsCode(err, xerr.NotFound) { + return dicedomain.Config{}, err + } + // 没有后台配置时把默认配置写成 MySQL 事实,再重新读取;这样 H5、大厅后台和机器人奖池判断都使用同一份配置/奖池快照。 + normalized, err := dicedomain.NormalizeConfig(dicedomain.Config{ + AppCode: appcode.Normalize(appCode), + GameID: strings.TrimSpace(gameID), + Status: dicedomain.ConfigStatusActive, + FeeBPS: int32(dicedomain.DefaultFeeBPS), + PoolBPS: int32(dicedomain.DefaultPoolBPS), + MinPlayers: dicedomain.DefaultMinPlayers, + MaxPlayers: dicedomain.DefaultMaxPlayers, + RobotEnabled: true, + RobotMatchWaitMS: dicedomain.DefaultRobotWaitMS, + }) + if err != nil { + return dicedomain.Config{}, err + } + return s.repository.UpsertDiceConfig(ctx, normalized, s.now().UnixMilli()) +} + +func (s *Service) ensureCoinBalance(ctx context.Context, requestID string, appCode string, userID int64, stakeCoin int64) error { + resp, err := s.wallet.GetBalances(ctx, &walletv1.GetBalancesRequest{ + RequestId: strings.TrimSpace(requestID), + UserId: userID, + AppCode: appCode, + AssetTypes: []string{"COIN"}, + }) + if err != nil { + return err + } + for _, balance := range resp.GetBalances() { + if strings.EqualFold(balance.GetAssetType(), "COIN") && balance.GetAvailableAmount() >= stakeCoin { + return nil + } + } + return xerr.New(xerr.InsufficientBalance, "coin balance is insufficient") +} + +func (s *Service) attachRobotIfDue(ctx context.Context, requestID string, match dicedomain.Match, nowMS int64) (dicedomain.Match, int64, error) { + if match.Status != dicedomain.MatchStatusCreated && match.Status != dicedomain.MatchStatusJoining { + return match, nowMS, nil + } + if match.CurrentPlayers >= match.MinPlayers { + return match, nowMS, nil + } + config, err := s.configForMatch(ctx, match.AppCode, match.GameID) + if err != nil { + return dicedomain.Match{}, 0, err + } + if !config.RobotEnabled || nowMS < match.CreatedAtMS+config.RobotMatchWaitMS { + return match, nowMS, nil + } + robot, err := s.repository.PickActiveDiceRobot(ctx, match.AppCode, match.GameID, match.MatchID) + if err != nil { + if xerr.IsCode(err, xerr.NotFound) { + return match, nowMS, nil + } + return dicedomain.Match{}, 0, err + } + forcedResult := "" + requiredPool := robotRequiredPoolCoin(match) + if config.PoolBalanceCoin < requiredPool { + forcedResult = dicedomain.ForcedResultPlayerLose + } + joined, err := s.repository.JoinDiceRobot(ctx, match.AppCode, match.MatchID, robot.UserID, forcedResult, nowMS) + if err != nil { + return dicedomain.Match{}, 0, err + } + return joined, nowMS, nil +} + +func (s *Service) forceHumanLose(participants []dicedomain.Participant) { + for index := range participants { + if participants[index].ParticipantType == dicedomain.ParticipantTypeRobot { + // 奖池不足时仍然落真实单骰格式;机器人最大点数、真人最小点数,保证结算规则自然判定玩家输。 + participants[index].DicePoints = []int32{6} + continue + } + participants[index].DicePoints = []int32{1} + } +} + +func (s *Service) applyPoolDelta(ctx context.Context, requestID string, match dicedomain.Match, nowMS int64) error { + direction, amount, userID := dicePoolDelta(match) + if amount <= 0 { + return nil + } + _, err := s.repository.AdjustDicePool(ctx, dicedomain.PoolAdjustment{ + AdjustmentID: "dpool_" + stableHash(fmt.Sprintf("%s|%s|%s|%d|%s", match.AppCode, match.MatchID, direction, amount, requestID))[:24], + AppCode: match.AppCode, + GameID: match.GameID, + MatchID: match.MatchID, + UserID: userID, + Direction: direction, + AmountCoin: amount, + Reason: "dice_settlement", + CreatedAtMS: nowMS, + }) + return err +} + +func normalizeMatchEconomicSnapshot(match *dicedomain.Match) { + if match.FeeBPS == 0 { + match.FeeBPS = int32(dicedomain.DefaultFeeBPS) + } + if match.PoolBPS == 0 { + match.PoolBPS = int32(dicedomain.DefaultPoolBPS) + } + for index := range match.Participants { + if strings.TrimSpace(match.Participants[index].ParticipantType) == "" { + match.Participants[index].ParticipantType = dicedomain.ParticipantTypeUser + } + } +} + +func robotRequiredPoolCoin(match dicedomain.Match) int64 { + normalizeMatchEconomicSnapshot(&match) + robotNetStake := match.StakeCoin * (10_000 - int64(match.FeeBPS) - int64(match.PoolBPS)) / 10_000 + if robotNetStake < 0 { + return 0 + } + return robotNetStake +} + +func dicePoolDelta(match dicedomain.Match) (string, int64, int64) { + normalizeMatchEconomicSnapshot(&match) + var totalStake int64 + var firstHuman dicedomain.Participant + for _, participant := range match.Participants { + totalStake += participant.StakeCoin + if participant.ParticipantType != dicedomain.ParticipantTypeRobot && firstHuman.UserID == 0 { + firstHuman = participant + } + } + if match.Result == dicedomain.ParticipantResultDraw { + return "", 0, firstHuman.UserID + } + if match.MatchMode != dicedomain.MatchModeRobot { + amount := totalStake * int64(match.PoolBPS) / 10_000 + return dicedomain.PoolDirectionIn, amount, firstHuman.UserID + } + if firstHuman.Result == dicedomain.ParticipantResultWin { + netOut := firstHuman.PayoutCoin - firstHuman.StakeCoin + if netOut <= 0 { + return "", 0, firstHuman.UserID + } + return dicedomain.PoolDirectionOut, netOut, firstHuman.UserID + } + if firstHuman.Result == dicedomain.ParticipantResultLose { + amount := firstHuman.StakeCoin * (10_000 - int64(match.FeeBPS)) / 10_000 + return dicedomain.PoolDirectionIn, amount, firstHuman.UserID + } + return "", 0, firstHuman.UserID +} + +func (s *Service) rollParticipants(participants []dicedomain.Participant) error { + for index := range participants { + points := make([]int32, 0, dicedomain.PointsPerParticipant) + for roll := 0; roll < dicedomain.PointsPerParticipant; roll++ { + // crypto/rand 返回 0-5,这里加 1 映射成真实骰子点数 1-6。 + value, err := s.randomInt(6) + if err != nil { + return err + } + points = append(points, int32(value+1)) + } + participants[index].DicePoints = points + } + return nil +} + +func (s *Service) refundDebitedParticipants(ctx context.Context, requestID string, match dicedomain.Match, nowMS int64) error { + var firstErr error + for _, participant := range match.Participants { + if !alreadyDebited(participant.Status) { + continue + } + // 扣款阶段失败时的退款同样走 game_orders + wallet 幂等,重复补偿不会多退。 + order, balanceAfter, err := s.applyGameCoinChange(ctx, requestID, match, participant, "refund", participant.StakeCoin) + if err != nil { + if firstErr == nil { + firstErr = err + } + continue + } + if err := s.repository.MarkDiceParticipantRefundSucceeded(ctx, match.AppCode, match.MatchID, participant.UserID, order.OrderID, balanceAfter, nowMS); err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} + +func (s *Service) applyGameCoinChange(ctx context.Context, requestID string, match dicedomain.Match, participant dicedomain.Participant, opType string, amount int64) (gamedomain.GameOrder, int64, error) { + if amount <= 0 { + return gamedomain.GameOrder{}, participant.BalanceAfter, xerr.New(xerr.InvalidArgument, "coin_amount must be positive") + } + // provider_order_id 按 match/user/op 固定,确保同一参与者同一动作在 game_orders 和 wallet-service 两边都可幂等重放。 + providerOrderID := diceProviderOrderID(match.MatchID, participant.UserID, opType) + // request_hash 覆盖会影响账务结果的字段;同一个 provider_order_id 携带不同金额或方向会被订单层拒绝。 + requestHash := stableHash(fmt.Sprintf("%s|%s|%s|%d|%d|%d", match.AppCode, match.MatchID, opType, participant.UserID, amount, match.RoundNo)) + order := gamedomain.GameOrder{ + AppCode: match.AppCode, + OrderID: "gord_" + stableHash(match.AppCode+"|"+match.PlatformCode+"|"+providerOrderID), + PlatformCode: match.PlatformCode, + ProviderOrderID: providerOrderID, + ProviderRoundID: match.MatchID, + GameID: match.GameID, + ProviderGameID: match.ProviderGameID, + UserID: participant.UserID, + RoomID: match.RoomID, + RegionID: match.RegionID, + OpType: opType, + CoinAmount: amount, + Status: gamedomain.OrderStatusWalletApplying, + RequestHash: requestHash, + } + order, exists, err := s.repository.CreateGameOrder(ctx, order) + if err != nil { + return gamedomain.GameOrder{}, 0, err + } + if exists && order.Status == gamedomain.OrderStatusSucceeded { + // game_orders 已成功说明 wallet 改账和 outbox 都已经落库;直接返回旧账后余额,避免再次触碰钱包。 + return order, order.WalletBalanceAfter, nil + } + + // 钱包是 COIN owner;game-service 只提交游戏改账命令,余额版本、资产账户和账本分录都由 wallet-service 保证。 + walletResp, err := s.wallet.ApplyGameCoinChange(ctx, &walletv1.ApplyGameCoinChangeRequest{ + RequestId: strings.TrimSpace(requestID), + AppCode: match.AppCode, + CommandId: "game:" + providerOrderID, + UserId: participant.UserID, + PlatformCode: match.PlatformCode, + GameId: match.GameID, + ProviderOrderId: providerOrderID, + ProviderRoundId: match.MatchID, + OpType: opType, + CoinAmount: amount, + RoomId: match.RoomID, + RequestHash: requestHash, + }) + nowMS := s.now().UnixMilli() + if err != nil { + status := gamedomain.OrderStatusFailed + if xerr.IsCode(err, xerr.IdempotencyConflict) { + // 钱包侧幂等冲突代表 command_id 重放但 payload 不一致,订单状态必须标 conflict 便于排查。 + status = gamedomain.OrderStatusConflict + } + _ = s.repository.MarkOrderFailed(ctx, match.AppCode, order.OrderID, status, walletFailureCode(err), err.Error(), nowMS) + return order, 0, err + } + // MarkOrderSucceeded 会同步写 game_outbox,并且 debit 订单额外写 game_level_event_outbox。 + if err := s.repository.MarkOrderSucceeded(ctx, match.AppCode, order.OrderID, walletResp.GetWalletTransactionId(), walletResp.GetBalanceAfter(), nowMS); err != nil { + return order, 0, err + } + order.WalletTransactionID = walletResp.GetWalletTransactionId() + order.WalletBalanceAfter = walletResp.GetBalanceAfter() + order.Status = gamedomain.OrderStatusSucceeded + return order, walletResp.GetBalanceAfter(), nil +} + +func alreadyDebited(status string) bool { + switch status { + case dicedomain.ParticipantStatusDebitSucceeded, dicedomain.ParticipantStatusPayoutSucceeded, dicedomain.ParticipantStatusRefundSucceeded, dicedomain.ParticipantStatusSettled: + return true + default: + return false + } +} + +func diceProviderOrderID(matchID string, userID int64, opType string) string { + return fmt.Sprintf("%s:%s:%d:%s", dicedomain.ProviderOrderPrefix, strings.TrimSpace(matchID), userID, strings.TrimSpace(opType)) +} + +func stableHash(value string) string { + sum := sha256.Sum256([]byte(value)) + return hex.EncodeToString(sum[:]) +} + +func cryptoRandomInt(max int) (int, error) { + if max <= 0 { + return 0, xerr.New(xerr.InvalidArgument, "random max is invalid") + } + value, err := rand.Int(rand.Reader, big.NewInt(int64(max))) + if err != nil { + return 0, err + } + return int(value.Int64()), nil +} + +func walletFailureCode(err error) string { + if code := xerr.CodeOf(err); code != "" { + return string(code) + } + return string(xerr.ReasonFromGRPC(err)) +} diff --git a/services/game-service/internal/service/dice/service_test.go b/services/game-service/internal/service/dice/service_test.go new file mode 100644 index 00000000..9932e862 --- /dev/null +++ b/services/game-service/internal/service/dice/service_test.go @@ -0,0 +1,512 @@ +package dice + +import ( + "context" + "strings" + "testing" + "time" + + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/xerr" + dicedomain "hyapp/services/game-service/internal/domain/dice" + gamedomain "hyapp/services/game-service/internal/domain/game" +) + +func TestRollMatchDebitsRollsAndPaysWinner(t *testing.T) { + repo := newFakeDiceRepository() + wallet := &fakeDiceWallet{balances: map[int64]int64{101: 1000, 202: 1000}} + svc := New(Config{FeeBPS: 500}, repo, wallet) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + rolls := []int{5, 0} + svc.randomInt = func(max int) (int, error) { + if len(rolls) == 0 { + t.Fatal("randomInt called too many times") + } + value := rolls[0] + rolls = rolls[1:] + return value, nil + } + + created, _, err := svc.CreateMatch(context.Background(), CreateMatchCommand{ + AppCode: "lalu", + RequestID: "req-create", + UserID: 101, + GameID: "dice", + RoomID: "room_1", + RegionID: 100, + StakeCoin: 100, + MinPlayers: 2, + MaxPlayers: 3, + }) + if err != nil { + t.Fatalf("CreateMatch failed: %v", err) + } + if _, _, err := svc.JoinMatch(context.Background(), JoinMatchCommand{AppCode: "lalu", RequestID: "req-join", UserID: 202, MatchID: created.MatchID}); err != nil { + t.Fatalf("JoinMatch failed: %v", err) + } + + settled, _, err := svc.RollMatch(context.Background(), RollMatchCommand{AppCode: "lalu", RequestID: "req-roll", UserID: 101, MatchID: created.MatchID}) + if err != nil { + t.Fatalf("RollMatch failed: %v", err) + } + if settled.Status != dicedomain.MatchStatusSettled || settled.Result != dicedomain.ParticipantResultWin { + t.Fatalf("match not settled as win: %+v", settled) + } + if len(settled.Participants) != 2 { + t.Fatalf("participants len = %d", len(settled.Participants)) + } + winner := participantByUser(settled.Participants, 101) + loser := participantByUser(settled.Participants, 202) + if len(winner.DicePoints) != 1 || winner.DicePoints[0] != 6 { + t.Fatalf("winner should have one dice point, got %+v", winner.DicePoints) + } + if len(loser.DicePoints) != 1 || loser.DicePoints[0] != 1 { + t.Fatalf("loser should have one dice point, got %+v", loser.DicePoints) + } + if winner.Result != dicedomain.ParticipantResultWin || winner.PayoutCoin != 188 || winner.BalanceAfter != 1088 { + t.Fatalf("winner settlement mismatch: %+v", winner) + } + if loser.Result != dicedomain.ParticipantResultLose || loser.PayoutCoin != 0 || loser.BalanceAfter != 900 { + t.Fatalf("loser settlement mismatch: %+v", loser) + } + if wallet.calls != 3 { + t.Fatalf("wallet calls = %d, want 3", wallet.calls) + } + if repo.levelDebitOrders != 2 { + t.Fatalf("debit orders should still feed game order success path, got %d", repo.levelDebitOrders) + } +} + +func TestRollMatchRequiresMinimumParticipantsBeforeWalletDebit(t *testing.T) { + repo := newFakeDiceRepository() + wallet := &fakeDiceWallet{balances: map[int64]int64{101: 1000}} + svc := New(Config{}, repo, wallet) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + + created, _, err := svc.CreateMatch(context.Background(), CreateMatchCommand{ + AppCode: "lalu", + RequestID: "req-create", + UserID: 101, + GameID: "dice", + StakeCoin: 100, + MinPlayers: 2, + MaxPlayers: 3, + }) + if err != nil { + t.Fatalf("CreateMatch failed: %v", err) + } + _, _, err = svc.RollMatch(context.Background(), RollMatchCommand{AppCode: "lalu", RequestID: "req-roll", UserID: 101, MatchID: created.MatchID}) + if !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("RollMatch should reject not enough participants, got %v", err) + } + if wallet.calls != 0 { + t.Fatalf("wallet should not be called, got %d", wallet.calls) + } +} + +func TestRollMatchDrawRerollsUntilWinner(t *testing.T) { + repo := newFakeDiceRepository() + wallet := &fakeDiceWallet{balances: map[int64]int64{101: 1000, 202: 1000}} + svc := New(Config{FeeBPS: 500}, repo, wallet) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + rolls := []int{2, 2, 5, 0} + svc.randomInt = func(max int) (int, error) { + if len(rolls) == 0 { + t.Fatal("randomInt called too many times") + } + value := rolls[0] + rolls = rolls[1:] + return value, nil + } + + created, _, err := svc.CreateMatch(context.Background(), CreateMatchCommand{ + AppCode: "lalu", + RequestID: "req-create", + UserID: 101, + GameID: "dice", + StakeCoin: 100, + MinPlayers: 2, + MaxPlayers: 2, + }) + if err != nil { + t.Fatalf("CreateMatch failed: %v", err) + } + if _, _, err := svc.JoinMatch(context.Background(), JoinMatchCommand{AppCode: "lalu", RequestID: "req-join", UserID: 202, MatchID: created.MatchID}); err != nil { + t.Fatalf("JoinMatch failed: %v", err) + } + + draw, _, err := svc.RollMatch(context.Background(), RollMatchCommand{AppCode: "lalu", RequestID: "req-roll-1", UserID: 101, MatchID: created.MatchID}) + if err != nil { + t.Fatalf("first RollMatch failed: %v", err) + } + if draw.Status != dicedomain.MatchStatusReady || draw.Result != dicedomain.ParticipantResultDraw || draw.RoundNo != 2 { + t.Fatalf("draw should keep match ready for reroll: %+v", draw) + } + if wallet.calls != 2 { + t.Fatalf("draw should only debit both users, wallet calls = %d", wallet.calls) + } + for _, participant := range draw.Participants { + if participant.Result != dicedomain.ParticipantResultDraw || participant.PayoutCoin != 0 || len(participant.DicePoints) != 1 || participant.DicePoints[0] != 3 { + t.Fatalf("draw participant mismatch: %+v", participant) + } + } + + settled, _, err := svc.RollMatch(context.Background(), RollMatchCommand{AppCode: "lalu", RequestID: "req-roll-2", UserID: 101, MatchID: created.MatchID}) + if err != nil { + t.Fatalf("second RollMatch failed: %v", err) + } + if settled.Status != dicedomain.MatchStatusSettled || settled.Result != dicedomain.ParticipantResultWin { + t.Fatalf("reroll should settle with winner: %+v", settled) + } + if wallet.calls != 3 { + t.Fatalf("reroll should not debit again and should only pay winner, wallet calls = %d", wallet.calls) + } + winner := participantByUser(settled.Participants, 101) + loser := participantByUser(settled.Participants, 202) + if winner.DicePoints[0] != 6 || winner.PayoutCoin != 188 || winner.BalanceAfter != 1088 { + t.Fatalf("winner after reroll mismatch: %+v", winner) + } + if loser.DicePoints[0] != 1 || loser.PayoutCoin != 0 || loser.BalanceAfter != 900 { + t.Fatalf("loser after reroll mismatch: %+v", loser) + } +} + +type fakeDiceRepository struct { + game gamedomain.LaunchableGame + match dicedomain.Match + config dicedomain.Config + poolBalance int64 + orders map[string]gamedomain.GameOrder + levelDebitOrders int +} + +func newFakeDiceRepository() *fakeDiceRepository { + return &fakeDiceRepository{ + game: gamedomain.LaunchableGame{ + CatalogItem: gamedomain.CatalogItem{ + AppCode: "lalu", + GameID: "dice", + PlatformCode: "dice", + ProviderGameID: "dice", + GameName: "Dice", + Status: gamedomain.StatusActive, + }, + PlatformStatus: gamedomain.StatusActive, + }, + config: dicedomain.Config{ + AppCode: "lalu", + GameID: "dice", + Status: dicedomain.ConfigStatusActive, + StakeOptions: dicedomain.NormalizeStakeOptions(nil), + FeeBPS: int32(dicedomain.DefaultFeeBPS), + PoolBPS: int32(dicedomain.DefaultPoolBPS), + MinPlayers: dicedomain.DefaultMinPlayers, + MaxPlayers: dicedomain.DefaultMaxPlayers, + RobotEnabled: true, + RobotMatchWaitMS: dicedomain.DefaultRobotWaitMS, + }, + orders: map[string]gamedomain.GameOrder{}, + } +} + +func (r *fakeDiceRepository) GetLaunchableGame(context.Context, string, string) (gamedomain.LaunchableGame, error) { + return r.game, nil +} + +func (r *fakeDiceRepository) CreateGameOrder(_ context.Context, order gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) { + if existing, ok := r.orders[order.OrderID]; ok { + if existing.RequestHash != order.RequestHash { + return gamedomain.GameOrder{}, true, xerr.New(xerr.IdempotencyConflict, "provider order payload conflict") + } + return existing, true, nil + } + r.orders[order.OrderID] = order + return order, false, nil +} + +func (r *fakeDiceRepository) MarkOrderSucceeded(_ context.Context, _ string, orderID string, walletTransactionID string, balanceAfter int64, nowMs int64) error { + order := r.orders[orderID] + order.Status = gamedomain.OrderStatusSucceeded + order.WalletTransactionID = walletTransactionID + order.WalletBalanceAfter = balanceAfter + order.UpdatedAtMS = nowMs + r.orders[orderID] = order + if order.OpType == "debit" { + r.levelDebitOrders++ + } + return nil +} + +func (r *fakeDiceRepository) MarkOrderFailed(_ context.Context, _ string, orderID string, status string, code string, message string, nowMs int64) error { + order := r.orders[orderID] + order.Status = status + order.FailureCode = code + order.FailureMessage = message + order.UpdatedAtMS = nowMs + r.orders[orderID] = order + return nil +} + +func (r *fakeDiceRepository) CreateDiceMatch(_ context.Context, match dicedomain.Match) (dicedomain.Match, error) { + r.match = cloneDiceMatch(match) + return cloneDiceMatch(r.match), nil +} + +func (r *fakeDiceRepository) JoinDiceMatch(_ context.Context, _ string, _ string, userID int64, nowMs int64) (dicedomain.Match, error) { + for _, participant := range r.match.Participants { + if participant.UserID == userID { + return cloneDiceMatch(r.match), nil + } + } + seatNo := int32(len(r.match.Participants) + 1) + r.match.Participants = append(r.match.Participants, dicedomain.Participant{ + AppCode: r.match.AppCode, + MatchID: r.match.MatchID, + UserID: userID, + SeatNo: seatNo, + Status: dicedomain.ParticipantStatusJoined, + StakeCoin: r.match.StakeCoin, + JoinedAtMS: nowMs, + UpdatedAtMS: nowMs, + }) + r.match.CurrentPlayers = int32(len(r.match.Participants)) + r.match.Status = dicedomain.MatchStatusJoining + r.match.UpdatedAtMS = nowMs + return cloneDiceMatch(r.match), nil +} + +func (r *fakeDiceRepository) GetDiceMatch(context.Context, string, string) (dicedomain.Match, error) { + return cloneDiceMatch(r.match), nil +} + +func (r *fakeDiceRepository) ClaimDiceMatchForRoll(_ context.Context, _ string, _ string, userID int64, nowMs int64) (dicedomain.Match, error) { + if !hasFakeDiceParticipant(r.match.Participants, userID) { + return dicedomain.Match{}, xerr.New(xerr.PermissionDenied, "dice participant is required") + } + if len(r.match.Participants) < int(r.match.MinPlayers) { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match does not have enough participants") + } + if r.match.Result == dicedomain.ParticipantResultDraw { + r.match.Result = "" + for index := range r.match.Participants { + r.match.Participants[index].DicePoints = nil + r.match.Participants[index].Result = "" + r.match.Participants[index].PayoutCoin = 0 + r.match.Participants[index].UpdatedAtMS = nowMs + } + } + r.match.Status = dicedomain.MatchStatusSettling + r.match.UpdatedAtMS = nowMs + return cloneDiceMatch(r.match), nil +} + +func (r *fakeDiceRepository) SaveDiceRolls(_ context.Context, match dicedomain.Match, nowMs int64) (dicedomain.Match, error) { + for _, source := range match.Participants { + for index := range r.match.Participants { + if r.match.Participants[index].UserID == source.UserID { + r.match.Participants[index].DicePoints = append([]int32(nil), source.DicePoints...) + r.match.Participants[index].Result = source.Result + r.match.Participants[index].PayoutCoin = source.PayoutCoin + r.match.Participants[index].UpdatedAtMS = nowMs + } + } + } + r.match.Status = dicedomain.MatchStatusPayoutApplying + r.match.Result = match.Result + r.match.UpdatedAtMS = nowMs + return cloneDiceMatch(r.match), nil +} + +func (r *fakeDiceRepository) SaveDiceDrawForReroll(_ context.Context, match dicedomain.Match, nowMs int64) (dicedomain.Match, error) { + for _, source := range match.Participants { + for index := range r.match.Participants { + if r.match.Participants[index].UserID == source.UserID { + r.match.Participants[index].DicePoints = append([]int32(nil), source.DicePoints...) + r.match.Participants[index].Result = dicedomain.ParticipantResultDraw + r.match.Participants[index].PayoutCoin = 0 + r.match.Participants[index].UpdatedAtMS = nowMs + } + } + } + r.match.Status = dicedomain.MatchStatusReady + r.match.Result = dicedomain.ParticipantResultDraw + r.match.RoundNo++ + r.match.UpdatedAtMS = nowMs + return cloneDiceMatch(r.match), nil +} + +func (r *fakeDiceRepository) MarkDiceParticipantDebitSucceeded(_ context.Context, _ string, _ string, userID int64, orderID string, balanceAfter int64, nowMs int64) error { + return r.updateParticipant(userID, func(participant *dicedomain.Participant) { + participant.Status = dicedomain.ParticipantStatusDebitSucceeded + participant.DebitOrderID = orderID + participant.BalanceAfter = balanceAfter + participant.UpdatedAtMS = nowMs + }) +} + +func (r *fakeDiceRepository) MarkDiceParticipantDebitFailed(_ context.Context, _ string, _ string, userID int64, orderID string, nowMs int64) error { + return r.updateParticipant(userID, func(participant *dicedomain.Participant) { + participant.Status = dicedomain.ParticipantStatusDebitFailed + participant.DebitOrderID = orderID + participant.UpdatedAtMS = nowMs + }) +} + +func (r *fakeDiceRepository) MarkDiceParticipantPayoutSucceeded(_ context.Context, _ string, _ string, userID int64, orderID string, balanceAfter int64, nowMs int64) error { + return r.updateParticipant(userID, func(participant *dicedomain.Participant) { + participant.Status = dicedomain.ParticipantStatusPayoutSucceeded + participant.PayoutOrderID = orderID + participant.BalanceAfter = balanceAfter + participant.UpdatedAtMS = nowMs + }) +} + +func (r *fakeDiceRepository) MarkDiceParticipantRefundSucceeded(_ context.Context, _ string, _ string, userID int64, orderID string, balanceAfter int64, nowMs int64) error { + return r.updateParticipant(userID, func(participant *dicedomain.Participant) { + participant.Status = dicedomain.ParticipantStatusRefundSucceeded + participant.RefundOrderID = orderID + participant.BalanceAfter = balanceAfter + participant.UpdatedAtMS = nowMs + }) +} + +func (r *fakeDiceRepository) MarkDiceMatchSettled(_ context.Context, _ string, _ string, result string, nowMs int64) error { + r.match.Status = dicedomain.MatchStatusSettled + r.match.Result = result + r.match.UpdatedAtMS = nowMs + r.match.SettledAtMS = nowMs + for index := range r.match.Participants { + r.match.Participants[index].Status = dicedomain.ParticipantStatusSettled + r.match.Participants[index].UpdatedAtMS = nowMs + } + return nil +} + +func (r *fakeDiceRepository) MarkDiceMatchFailed(_ context.Context, _ string, _ string, result string, nowMs int64) error { + r.match.Status = dicedomain.MatchStatusFailed + r.match.Result = result + r.match.UpdatedAtMS = nowMs + return nil +} + +func (r *fakeDiceRepository) GetDiceConfig(context.Context, string, string) (dicedomain.Config, error) { + return r.config, nil +} + +func (r *fakeDiceRepository) ListSelfGameConfigs(context.Context, string) ([]dicedomain.Config, error) { + return []dicedomain.Config{r.config}, nil +} + +func (r *fakeDiceRepository) UpsertDiceConfig(_ context.Context, config dicedomain.Config, _ int64) (dicedomain.Config, error) { + r.config = config + return config, nil +} + +func (r *fakeDiceRepository) FindAndJoinWaitingDiceMatch(context.Context, string, string, int64, int64, int64) (dicedomain.Match, error) { + return dicedomain.Match{}, xerr.New(xerr.NotFound, "waiting dice match not found") +} + +func (r *fakeDiceRepository) JoinDiceRobot(context.Context, string, string, int64, string, int64) (dicedomain.Match, error) { + return dicedomain.Match{}, xerr.New(xerr.NotFound, "dice robot not found") +} + +func (r *fakeDiceRepository) CancelDiceMatch(context.Context, string, string, int64, int64) (dicedomain.Match, error) { + r.match.Status = dicedomain.MatchStatusCanceled + return cloneDiceMatch(r.match), nil +} + +func (r *fakeDiceRepository) PickActiveDiceRobot(context.Context, string, string, string) (dicedomain.Robot, error) { + return dicedomain.Robot{}, xerr.New(xerr.NotFound, "dice robot not found") +} + +func (r *fakeDiceRepository) AdjustDicePool(_ context.Context, adjustment dicedomain.PoolAdjustment) (dicedomain.PoolAdjustment, error) { + if adjustment.Direction == dicedomain.PoolDirectionOut { + r.poolBalance -= adjustment.AmountCoin + } else { + r.poolBalance += adjustment.AmountCoin + } + adjustment.BalanceAfter = r.poolBalance + return adjustment, nil +} + +func (r *fakeDiceRepository) ListDiceRobots(context.Context, string, string, string, int32, string) ([]dicedomain.Robot, string, error) { + return nil, "", nil +} + +func (r *fakeDiceRepository) RegisterDiceRobots(context.Context, string, string, []int64, int64, int64) ([]dicedomain.Robot, error) { + return nil, nil +} + +func (r *fakeDiceRepository) SetDiceRobotStatus(context.Context, string, string, int64, string, int64) (dicedomain.Robot, error) { + return dicedomain.Robot{}, nil +} + +func (r *fakeDiceRepository) DeleteDiceRobot(context.Context, string, string, int64) error { + return nil +} + +func (r *fakeDiceRepository) updateParticipant(userID int64, apply func(*dicedomain.Participant)) error { + for index := range r.match.Participants { + if r.match.Participants[index].UserID == userID { + apply(&r.match.Participants[index]) + return nil + } + } + return xerr.New(xerr.NotFound, "dice participant not found") +} + +type fakeDiceWallet struct { + balances map[int64]int64 + calls int +} + +func (w *fakeDiceWallet) ApplyGameCoinChange(_ context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) { + w.calls++ + if req.GetCoinAmount() <= 0 { + return nil, xerr.New(xerr.InvalidArgument, "coin_amount must be positive") + } + switch req.GetOpType() { + case "debit": + if w.balances[req.GetUserId()] < req.GetCoinAmount() { + return nil, xerr.New(xerr.InsufficientBalance, "insufficient balance") + } + w.balances[req.GetUserId()] -= req.GetCoinAmount() + case "credit", "refund": + w.balances[req.GetUserId()] += req.GetCoinAmount() + default: + return nil, xerr.New(xerr.InvalidArgument, "game op_type is invalid") + } + return &walletv1.ApplyGameCoinChangeResponse{ + WalletTransactionId: "wtx_" + strings.ReplaceAll(req.GetProviderOrderId(), ":", "_"), + BalanceAfter: w.balances[req.GetUserId()], + }, nil +} + +func (w *fakeDiceWallet) GetBalances(_ context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { + return &walletv1.GetBalancesResponse{Balances: []*walletv1.AssetBalance{{ + AssetType: "COIN", + AvailableAmount: w.balances[req.GetUserId()], + }}}, nil +} + +func participantByUser(participants []dicedomain.Participant, userID int64) dicedomain.Participant { + for _, participant := range participants { + if participant.UserID == userID { + return participant + } + } + return dicedomain.Participant{} +} + +func hasFakeDiceParticipant(participants []dicedomain.Participant, userID int64) bool { + return participantByUser(participants, userID).UserID == userID +} + +func cloneDiceMatch(match dicedomain.Match) dicedomain.Match { + cloned := match + cloned.Participants = append([]dicedomain.Participant(nil), match.Participants...) + for index := range cloned.Participants { + cloned.Participants[index].DicePoints = append([]int32(nil), match.Participants[index].DicePoints...) + } + return cloned +} diff --git a/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go b/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go new file mode 100644 index 00000000..c464f01a --- /dev/null +++ b/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go @@ -0,0 +1,571 @@ +package mysql + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + dicedomain "hyapp/services/game-service/internal/domain/dice" +) + +// GetDiceConfig 读取自研骰子配置并带上奖池余额;配置表和奖池表分离,避免频繁结算更新污染配置更新时间。 +func (r *Repository) GetDiceConfig(ctx context.Context, appCode string, gameID string) (dicedomain.Config, error) { + app := appcode.Normalize(appCode) + gameID = normalizeDiceGameID(gameID) + query := `SELECT c.app_code, c.game_id, c.status, COALESCE(CAST(c.stake_options_json AS CHAR), '[]'), + c.fee_bps, c.pool_bps, c.min_players, c.max_players, c.robot_enabled, + c.robot_match_wait_ms, c.created_at_ms, c.updated_at_ms, + COALESCE(p.balance_coin, 0) + FROM game_self_game_configs c + LEFT JOIN game_self_game_pools p ON p.app_code = c.app_code AND p.game_id = c.game_id + WHERE c.app_code = ? AND c.game_id = ?` + var config dicedomain.Config + var optionsJSON string + if err := r.db.QueryRowContext(ctx, query, app, gameID).Scan( + &config.AppCode, &config.GameID, &config.Status, &optionsJSON, + &config.FeeBPS, &config.PoolBPS, &config.MinPlayers, &config.MaxPlayers, &config.RobotEnabled, + &config.RobotMatchWaitMS, &config.CreatedAtMS, &config.UpdatedAtMS, &config.PoolBalanceCoin, + ); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return dicedomain.Config{}, xerr.New(xerr.NotFound, "dice config not found") + } + return dicedomain.Config{}, err + } + config.StakeOptions = dicedomain.ParseStakeOptions(optionsJSON) + return dicedomain.NormalizeConfig(config) +} + +// ListSelfGameConfigs 当前只有 dice 一个自研游戏;仍返回数组,方便后台后续追加更多自研游戏。 +func (r *Repository) ListSelfGameConfigs(ctx context.Context, appCode string) ([]dicedomain.Config, error) { + app := appcode.Normalize(appCode) + rows, err := r.db.QueryContext(ctx, + `SELECT c.app_code, c.game_id, c.status, COALESCE(CAST(c.stake_options_json AS CHAR), '[]'), + c.fee_bps, c.pool_bps, c.min_players, c.max_players, c.robot_enabled, + c.robot_match_wait_ms, c.created_at_ms, c.updated_at_ms, COALESCE(p.balance_coin, 0) + FROM game_self_game_configs c + LEFT JOIN game_self_game_pools p ON p.app_code = c.app_code AND p.game_id = c.game_id + WHERE c.app_code = ? + ORDER BY c.game_id ASC`, + app, + ) + if err != nil { + return nil, err + } + defer rows.Close() + + configs := []dicedomain.Config{} + for rows.Next() { + var config dicedomain.Config + var optionsJSON string + if err := rows.Scan( + &config.AppCode, &config.GameID, &config.Status, &optionsJSON, + &config.FeeBPS, &config.PoolBPS, &config.MinPlayers, &config.MaxPlayers, &config.RobotEnabled, + &config.RobotMatchWaitMS, &config.CreatedAtMS, &config.UpdatedAtMS, &config.PoolBalanceCoin, + ); err != nil { + return nil, err + } + config.StakeOptions = dicedomain.ParseStakeOptions(optionsJSON) + normalized, err := dicedomain.NormalizeConfig(config) + if err != nil { + return nil, err + } + configs = append(configs, normalized) + } + return configs, rows.Err() +} + +// UpsertDiceConfig 保存后台可热更新配置,并确保对应奖池行存在;具体余额变更必须走 AdjustDicePool。 +func (r *Repository) UpsertDiceConfig(ctx context.Context, config dicedomain.Config, nowMs int64) (dicedomain.Config, error) { + normalized, err := dicedomain.NormalizeConfig(config) + if err != nil { + return dicedomain.Config{}, err + } + normalized.AppCode = appcode.Normalize(normalized.AppCode) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Config{}, err + } + defer func() { _ = tx.Rollback() }() + + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_self_game_configs ( + app_code, game_id, status, stake_options_json, fee_bps, pool_bps, + min_players, max_players, robot_enabled, robot_match_wait_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, CAST(? AS JSON), ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + status = VALUES(status), + stake_options_json = VALUES(stake_options_json), + fee_bps = VALUES(fee_bps), + pool_bps = VALUES(pool_bps), + min_players = VALUES(min_players), + max_players = VALUES(max_players), + robot_enabled = VALUES(robot_enabled), + robot_match_wait_ms = VALUES(robot_match_wait_ms), + updated_at_ms = VALUES(updated_at_ms)`, + normalized.AppCode, normalized.GameID, normalized.Status, dicedomain.StakeOptionsJSON(normalized.StakeOptions), + normalized.FeeBPS, normalized.PoolBPS, normalized.MinPlayers, normalized.MaxPlayers, normalized.RobotEnabled, + normalized.RobotMatchWaitMS, nowMs, nowMs, + ); err != nil { + return dicedomain.Config{}, err + } + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_self_game_pools (app_code, game_id, balance_coin, created_at_ms, updated_at_ms) + VALUES (?, ?, 0, ?, ?) + ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms`, + normalized.AppCode, normalized.GameID, nowMs, nowMs, + ); err != nil { + return dicedomain.Config{}, err + } + if err := tx.Commit(); err != nil { + return dicedomain.Config{}, err + } + return r.GetDiceConfig(ctx, normalized.AppCode, normalized.GameID) +} + +// FindAndJoinWaitingDiceMatch 在全服等待队列里原子抢一个真人局;没有命中时返回 NotFound,让 service 创建新局。 +func (r *Repository) FindAndJoinWaitingDiceMatch(ctx context.Context, appCode string, gameID string, userID int64, stakeCoin int64, nowMs int64) (dicedomain.Match, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Match{}, err + } + defer func() { _ = tx.Rollback() }() + + app := appcode.Normalize(appCode) + gameID = normalizeDiceGameID(gameID) + var matchID string + err = tx.QueryRowContext(ctx, + `SELECT m.match_id + FROM game_dice_matches m + WHERE m.app_code = ? + AND m.game_id = ? + AND m.room_id = '' + AND m.stake_coin = ? + AND m.status IN (?, ?) + AND m.current_players < m.max_players + AND (m.join_deadline_ms = 0 OR m.join_deadline_ms >= ?) + AND (m.match_mode = '' OR m.match_mode = ?) + AND NOT EXISTS ( + SELECT 1 FROM game_dice_participants p + WHERE p.app_code = m.app_code AND p.match_id = m.match_id AND p.user_id = ? + ) + ORDER BY m.created_at_ms ASC + LIMIT 1 + FOR UPDATE SKIP LOCKED`, + app, gameID, stakeCoin, dicedomain.MatchStatusCreated, dicedomain.MatchStatusJoining, nowMs, dicedomain.MatchModeHuman, userID, + ).Scan(&matchID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return dicedomain.Match{}, xerr.New(xerr.NotFound, "waiting dice match not found") + } + return dicedomain.Match{}, err + } + match, err := queryDiceMatch(ctx, tx, app, matchID, true) + if err != nil { + return dicedomain.Match{}, err + } + if match.MatchMode == "" { + match.MatchMode = dicedomain.MatchModeHuman + } + joined, err := insertDiceParticipantLocked(ctx, tx, match, userID, dicedomain.ParticipantTypeUser, nowMs) + if err != nil { + return dicedomain.Match{}, err + } + if err := updateDiceMatchAfterJoinLocked(ctx, tx, joined, match.MatchMode, match.ForcedResult, nowMs); err != nil { + return dicedomain.Match{}, err + } + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return r.GetDiceMatch(ctx, app, matchID) +} + +// JoinDiceRobot 把一个已登记真实 App 用户作为机器人补位;机器人只参与局事实,不触发钱包扣款。 +func (r *Repository) JoinDiceRobot(ctx context.Context, appCode string, matchID string, robotUserID int64, forcedResult string, nowMs int64) (dicedomain.Match, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Match{}, err + } + defer func() { _ = tx.Rollback() }() + + app := appcode.Normalize(appCode) + match, err := queryDiceMatch(ctx, tx, app, strings.TrimSpace(matchID), true) + if err != nil { + return dicedomain.Match{}, err + } + if !diceMatchAllowsJoin(match.Status) { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match is not joinable") + } + if match.CurrentPlayers >= match.MaxPlayers { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match is full") + } + joined, err := insertDiceParticipantLocked(ctx, tx, match, robotUserID, dicedomain.ParticipantTypeRobot, nowMs) + if err != nil { + return dicedomain.Match{}, err + } + if err := updateDiceMatchAfterJoinLocked(ctx, tx, joined, dicedomain.MatchModeRobot, strings.TrimSpace(forcedResult), nowMs); err != nil { + return dicedomain.Match{}, err + } + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return r.GetDiceMatch(ctx, app, match.MatchID) +} + +// CancelDiceMatch 只取消还未成局的等待局;已匹配成功后必须走正常结算或失败补偿。 +func (r *Repository) CancelDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Match{}, err + } + defer func() { _ = tx.Rollback() }() + + app := appcode.Normalize(appCode) + match, err := queryDiceMatch(ctx, tx, app, strings.TrimSpace(matchID), true) + if err != nil { + return dicedomain.Match{}, err + } + participants, err := queryDiceParticipants(ctx, tx, app, match.MatchID, true) + if err != nil { + return dicedomain.Match{}, err + } + if !diceMatchHasParticipant(participants, userID) { + return dicedomain.Match{}, xerr.New(xerr.PermissionDenied, "dice participant is required") + } + if match.Status != dicedomain.MatchStatusCreated && match.Status != dicedomain.MatchStatusJoining { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match is not cancelable") + } + if len(participants) > 1 { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match already has opponent") + } + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_matches + SET status = ?, canceled_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + dicedomain.MatchStatusCanceled, nowMs, nowMs, app, match.MatchID, + ); err != nil { + return dicedomain.Match{}, err + } + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return r.GetDiceMatch(ctx, app, match.MatchID) +} + +// PickActiveDiceRobot 选择一个未在本局中的可用机器人;首版用更新时间排序,后续可替换成权重或 Redis 活跃池。 +func (r *Repository) PickActiveDiceRobot(ctx context.Context, appCode string, gameID string, matchID string) (dicedomain.Robot, error) { + app := appcode.Normalize(appCode) + gameID = normalizeDiceGameID(gameID) + var robot dicedomain.Robot + err := r.db.QueryRowContext(ctx, + `SELECT r.app_code, r.game_id, r.user_id, r.status, r.created_by_admin_id, r.created_at_ms, r.updated_at_ms + FROM game_self_game_robots r + WHERE r.app_code = ? AND r.game_id = ? AND r.status = ? + AND NOT EXISTS ( + SELECT 1 FROM game_dice_participants p + WHERE p.app_code = r.app_code AND p.match_id = ? AND p.user_id = r.user_id + ) + ORDER BY r.updated_at_ms ASC, r.user_id ASC + LIMIT 1`, + app, gameID, dicedomain.RobotStatusActive, strings.TrimSpace(matchID), + ).Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return dicedomain.Robot{}, xerr.New(xerr.NotFound, "dice robot not found") + } + return dicedomain.Robot{}, err + } + return robot, nil +} + +// AdjustDicePool 用一把奖池行锁串行更新余额并写流水,防止多个机器人局并发时把余额扣成负数。 +func (r *Repository) AdjustDicePool(ctx context.Context, adjustment dicedomain.PoolAdjustment) (dicedomain.PoolAdjustment, error) { + if adjustment.AmountCoin <= 0 { + return dicedomain.PoolAdjustment{}, xerr.New(xerr.InvalidArgument, "dice pool amount is invalid") + } + adjustment.AppCode = appcode.Normalize(adjustment.AppCode) + adjustment.GameID = normalizeDiceGameID(adjustment.GameID) + adjustment.Direction = strings.TrimSpace(adjustment.Direction) + if adjustment.Direction != dicedomain.PoolDirectionIn && adjustment.Direction != dicedomain.PoolDirectionOut { + return dicedomain.PoolAdjustment{}, xerr.New(xerr.InvalidArgument, "dice pool direction is invalid") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.PoolAdjustment{}, err + } + defer func() { _ = tx.Rollback() }() + + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_self_game_pools (app_code, game_id, balance_coin, created_at_ms, updated_at_ms) + VALUES (?, ?, 0, ?, ?) + ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms`, + adjustment.AppCode, adjustment.GameID, adjustment.CreatedAtMS, adjustment.CreatedAtMS, + ); err != nil { + return dicedomain.PoolAdjustment{}, err + } + var balance int64 + if err := tx.QueryRowContext(ctx, + `SELECT balance_coin FROM game_self_game_pools WHERE app_code = ? AND game_id = ? FOR UPDATE`, + adjustment.AppCode, adjustment.GameID, + ).Scan(&balance); err != nil { + return dicedomain.PoolAdjustment{}, err + } + next := balance + adjustment.AmountCoin + if adjustment.Direction == dicedomain.PoolDirectionOut { + next = balance - adjustment.AmountCoin + if next < 0 { + return dicedomain.PoolAdjustment{}, xerr.New(xerr.Conflict, "dice pool balance is insufficient") + } + } + if _, err := tx.ExecContext(ctx, + `UPDATE game_self_game_pools SET balance_coin = ?, updated_at_ms = ? WHERE app_code = ? AND game_id = ?`, + next, adjustment.CreatedAtMS, adjustment.AppCode, adjustment.GameID, + ); err != nil { + return dicedomain.PoolAdjustment{}, err + } + adjustment.BalanceAfter = next + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_self_game_pool_transactions ( + app_code, adjustment_id, game_id, match_id, user_id, direction, amount_coin, + reason, balance_after, created_by, created_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + adjustment.AppCode, adjustment.AdjustmentID, adjustment.GameID, adjustment.MatchID, adjustment.UserID, + adjustment.Direction, adjustment.AmountCoin, adjustment.Reason, adjustment.BalanceAfter, adjustment.CreatedBy, adjustment.CreatedAtMS, + ); err != nil { + return dicedomain.PoolAdjustment{}, err + } + if adjustment.MatchID != "" { + delta := adjustment.AmountCoin + if adjustment.Direction == dicedomain.PoolDirectionOut { + delta = -delta + } + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_matches + SET pool_delta_coin = pool_delta_coin + ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + delta, adjustment.CreatedAtMS, adjustment.AppCode, adjustment.MatchID, + ); err != nil { + return dicedomain.PoolAdjustment{}, err + } + } + if err := tx.Commit(); err != nil { + return dicedomain.PoolAdjustment{}, err + } + return adjustment, nil +} + +func (r *Repository) ListDiceRobots(ctx context.Context, appCode string, gameID string, status string, pageSize int32, cursor string) ([]dicedomain.Robot, string, error) { + app := appcode.Normalize(appCode) + gameID = normalizeDiceGameID(gameID) + if pageSize <= 0 || pageSize > 200 { + pageSize = 50 + } + status = strings.TrimSpace(status) + cursorID := int64(0) + if strings.TrimSpace(cursor) != "" { + _, _ = fmt.Sscan(strings.TrimSpace(cursor), &cursorID) + } + args := []any{app, gameID} + query := `SELECT app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms + FROM game_self_game_robots + WHERE app_code = ? AND game_id = ?` + if status != "" { + query += ` AND status = ?` + args = append(args, status) + } + if cursorID > 0 { + query += ` AND user_id > ?` + args = append(args, cursorID) + } + query += ` ORDER BY user_id ASC LIMIT ?` + args = append(args, int(pageSize)+1) + rows, err := r.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, "", err + } + defer rows.Close() + + robots := []dicedomain.Robot{} + for rows.Next() { + var robot dicedomain.Robot + if err := rows.Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS); err != nil { + return nil, "", err + } + robots = append(robots, robot) + } + if err := rows.Err(); err != nil { + return nil, "", err + } + nextCursor := "" + if len(robots) > int(pageSize) { + nextCursor = fmt.Sprintf("%d", robots[pageSize-1].UserID) + robots = robots[:pageSize] + } + return robots, nextCursor, nil +} + +func (r *Repository) RegisterDiceRobots(ctx context.Context, appCode string, gameID string, userIDs []int64, adminID int64, nowMs int64) ([]dicedomain.Robot, error) { + app := appcode.Normalize(appCode) + gameID = normalizeDiceGameID(gameID) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + for _, userID := range uniquePositiveInt64(userIDs) { + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_self_game_robots ( + app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE status = VALUES(status), updated_at_ms = VALUES(updated_at_ms)`, + app, gameID, userID, dicedomain.RobotStatusActive, adminID, nowMs, nowMs, + ); err != nil { + return nil, err + } + } + if err := tx.Commit(); err != nil { + return nil, err + } + robots, _, err := r.ListDiceRobots(ctx, app, gameID, "", 200, "") + return robots, err +} + +func (r *Repository) SetDiceRobotStatus(ctx context.Context, appCode string, gameID string, userID int64, status string, nowMs int64) (dicedomain.Robot, error) { + app := appcode.Normalize(appCode) + gameID = normalizeDiceGameID(gameID) + status = strings.TrimSpace(status) + if status != dicedomain.RobotStatusActive && status != dicedomain.RobotStatusDisabled { + return dicedomain.Robot{}, xerr.New(xerr.InvalidArgument, "dice robot status is invalid") + } + res, err := r.db.ExecContext(ctx, + `UPDATE game_self_game_robots + SET status = ?, updated_at_ms = ? + WHERE app_code = ? AND game_id = ? AND user_id = ?`, + status, nowMs, app, gameID, userID, + ) + if err != nil { + return dicedomain.Robot{}, err + } + affected, _ := res.RowsAffected() + if affected == 0 { + return dicedomain.Robot{}, xerr.New(xerr.NotFound, "dice robot not found") + } + var robot dicedomain.Robot + err = r.db.QueryRowContext(ctx, + `SELECT app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms + FROM game_self_game_robots + WHERE app_code = ? AND game_id = ? AND user_id = ?`, + app, gameID, userID, + ).Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS) + return robot, err +} + +// DeleteDiceRobot 只删除机器人登记关系,不删除 user-service 主账号,避免历史对局和钱包流水失去用户归属。 +func (r *Repository) DeleteDiceRobot(ctx context.Context, appCode string, gameID string, userID int64) error { + app := appcode.Normalize(appCode) + gameID = normalizeDiceGameID(gameID) + res, err := r.db.ExecContext(ctx, + `DELETE FROM game_self_game_robots + WHERE app_code = ? AND game_id = ? AND user_id = ?`, + app, gameID, userID, + ) + if err != nil { + return err + } + affected, _ := res.RowsAffected() + if affected == 0 { + return xerr.New(xerr.NotFound, "dice robot not found") + } + return nil +} + +func insertDiceParticipantLocked(ctx context.Context, tx *sql.Tx, match dicedomain.Match, userID int64, participantType string, nowMs int64) (dicedomain.Match, error) { + var seatNo int32 + if err := tx.QueryRowContext(ctx, + `SELECT COALESCE(MAX(seat_no), 0) + 1 + FROM game_dice_participants + WHERE app_code = ? AND match_id = ?`, + match.AppCode, match.MatchID, + ).Scan(&seatNo); err != nil { + return dicedomain.Match{}, err + } + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_dice_participants ( + app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, result, + payout_coin, debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), '', 0, '', '', '', 0, ?, ?)`, + match.AppCode, match.MatchID, userID, normalizeParticipantType(participantType), seatNo, + dicedomain.ParticipantStatusJoined, match.StakeCoin, "[]", nowMs, nowMs, + ); err != nil { + return dicedomain.Match{}, err + } + match.CurrentPlayers++ + if match.Participants == nil { + match.Participants = []dicedomain.Participant{} + } + match.Participants = append(match.Participants, dicedomain.Participant{ + AppCode: match.AppCode, + MatchID: match.MatchID, + UserID: userID, + ParticipantType: normalizeParticipantType(participantType), + SeatNo: seatNo, + Status: dicedomain.ParticipantStatusJoined, + StakeCoin: match.StakeCoin, + JoinedAtMS: nowMs, + UpdatedAtMS: nowMs, + }) + return match, nil +} + +func updateDiceMatchAfterJoinLocked(ctx context.Context, tx *sql.Tx, match dicedomain.Match, matchMode string, forcedResult string, nowMs int64) error { + nextStatus := dicedomain.MatchStatusJoining + readyAtMS := match.ReadyAtMS + if match.CurrentPlayers >= match.MinPlayers { + nextStatus = dicedomain.MatchStatusReady + if readyAtMS == 0 { + readyAtMS = nowMs + } + } + _, err := tx.ExecContext(ctx, + `UPDATE game_dice_matches + SET current_players = ?, status = ?, ready_at_ms = ?, match_mode = ?, forced_result = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + match.CurrentPlayers, nextStatus, readyAtMS, strings.TrimSpace(matchMode), strings.TrimSpace(forcedResult), nowMs, + match.AppCode, match.MatchID, + ) + return err +} + +func normalizeDiceGameID(gameID string) string { + gameID = strings.TrimSpace(gameID) + if gameID == "" { + return dicedomain.DefaultGameID + } + return gameID +} + +func normalizeParticipantType(value string) string { + if strings.TrimSpace(value) == dicedomain.ParticipantTypeRobot { + return dicedomain.ParticipantTypeRobot + } + return dicedomain.ParticipantTypeUser +} + +func uniquePositiveInt64(values []int64) []int64 { + seen := make(map[int64]struct{}, len(values)) + out := make([]int64, 0, len(values)) + for _, value := range values { + if value <= 0 { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + return out +} diff --git a/services/game-service/internal/storage/mysql/dice_repository.go b/services/game-service/internal/storage/mysql/dice_repository.go new file mode 100644 index 00000000..db8a3ad5 --- /dev/null +++ b/services/game-service/internal/storage/mysql/dice_repository.go @@ -0,0 +1,510 @@ +package mysql + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + dicedomain "hyapp/services/game-service/internal/domain/dice" +) + +type diceQuerier interface { + QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) + QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row +} + +// CreateDiceMatch 在同一个事务内写入 match 和首个参与者,保证创建后不会出现空局。 +func (r *Repository) CreateDiceMatch(ctx context.Context, match dicedomain.Match) (dicedomain.Match, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Match{}, err + } + defer func() { _ = tx.Rollback() }() + + match.AppCode = appcode.Normalize(match.AppCode) + // match 和首个 participant 必须同事务提交;否则创建成功但参与者缺失会导致后续 roll 永远人数不足。 + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_dice_matches ( + app_code, match_id, game_id, platform_code, provider_game_id, room_id, region_id, + min_players, max_players, current_players, stake_coin, round_no, status, result, + join_deadline_ms, ready_at_ms, created_at_ms, updated_at_ms, settled_at_ms, canceled_at_ms, + fee_bps, pool_bps, match_mode, forced_result, pool_delta_coin + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + match.AppCode, match.MatchID, match.GameID, match.PlatformCode, match.ProviderGameID, match.RoomID, match.RegionID, + match.MinPlayers, match.MaxPlayers, match.CurrentPlayers, match.StakeCoin, match.RoundNo, match.Status, match.Result, + match.JoinDeadlineMS, match.ReadyAtMS, match.CreatedAtMS, match.UpdatedAtMS, match.SettledAtMS, match.CanceledAtMS, + match.FeeBPS, match.PoolBPS, match.MatchMode, match.ForcedResult, match.PoolDeltaCoin, + ); err != nil { + return dicedomain.Match{}, err + } + for _, participant := range match.Participants { + // dice_points_json 创建阶段固定写 [],后续 SaveDiceRolls 才允许写真实点数。 + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_dice_participants ( + app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, result, + payout_coin, debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), ?, ?, ?, ?, ?, ?, ?, ?)`, + match.AppCode, match.MatchID, participant.UserID, normalizeParticipantType(participant.ParticipantType), participant.SeatNo, participant.Status, participant.StakeCoin, + dicePointsJSON(participant.DicePoints), participant.Result, participant.PayoutCoin, participant.DebitOrderID, + participant.PayoutOrderID, participant.RefundOrderID, participant.BalanceAfter, participant.JoinedAtMS, participant.UpdatedAtMS, + ); err != nil { + return dicedomain.Match{}, err + } + } + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + // 提交后重新读取,返回值使用数据库里的排序、默认值和 JSON 解析结果,不信任入参原对象。 + return r.GetDiceMatch(ctx, match.AppCode, match.MatchID) +} + +// JoinDiceMatch 只在 match 行锁内分配座位和增加人数;外部钱包调用不会持有这把锁。 +func (r *Repository) JoinDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Match{}, err + } + defer func() { _ = tx.Rollback() }() + + app := appcode.Normalize(appCode) + // 锁 match 主行后再检查状态和人数,保证并发 join 时只有一个事务能看到并更新同一份 current_players。 + match, err := queryDiceMatch(ctx, tx, app, strings.TrimSpace(matchID), true) + if err != nil { + return dicedomain.Match{}, err + } + if !diceMatchAllowsJoin(match.Status) { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match is not joinable") + } + if match.JoinDeadlineMS > 0 && nowMs > match.JoinDeadlineMS { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match join window is closed") + } + + var existing int + if err := tx.QueryRowContext(ctx, + `SELECT COUNT(*) + FROM game_dice_participants + WHERE app_code = ? AND match_id = ? AND user_id = ?`, + app, match.MatchID, userID, + ).Scan(&existing); err != nil { + return dicedomain.Match{}, err + } + if existing > 0 { + // 重复点击 join 直接返回当前局快照,不制造第二个座位,也不把重复请求当错误打断客户端刷新。 + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return r.GetDiceMatch(ctx, app, match.MatchID) + } + if match.CurrentPlayers >= match.MaxPlayers { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match is full") + } + + var seatNo int32 + // seat_no 在同一把 match 锁下用 max+1 分配;配合唯一索引防止未来修改锁粒度后出现重复座位。 + if err := tx.QueryRowContext(ctx, + `SELECT COALESCE(MAX(seat_no), 0) + 1 + FROM game_dice_participants + WHERE app_code = ? AND match_id = ?`, + app, match.MatchID, + ).Scan(&seatNo); err != nil { + return dicedomain.Match{}, err + } + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_dice_participants ( + app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, result, + payout_coin, debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), '', 0, '', '', '', 0, ?, ?)`, + app, match.MatchID, userID, dicedomain.ParticipantTypeUser, seatNo, dicedomain.ParticipantStatusJoined, match.StakeCoin, "[]", nowMs, nowMs, + ); err != nil { + return dicedomain.Match{}, err + } + nextPlayers := match.CurrentPlayers + 1 + nextStatus := dicedomain.MatchStatusJoining + readyAtMS := match.ReadyAtMS + if nextPlayers >= match.MinPlayers { + nextStatus = dicedomain.MatchStatusReady + if readyAtMS == 0 { + readyAtMS = nowMs + } + } + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_matches + SET current_players = current_players + 1, status = ?, ready_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + nextStatus, readyAtMS, nowMs, app, match.MatchID, + ); err != nil { + return dicedomain.Match{}, err + } + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + // join 返回完整 participants,H5 可以直接刷新座位,不需要再额外查一次。 + return r.GetDiceMatch(ctx, app, match.MatchID) +} + +func (r *Repository) GetDiceMatch(ctx context.Context, appCode string, matchID string) (dicedomain.Match, error) { + app := appcode.Normalize(appCode) + // 查询接口不加行锁,只读取当前已提交事实;所有会改变状态的入口都有单独的锁定方法。 + match, err := queryDiceMatch(ctx, r.db, app, strings.TrimSpace(matchID), false) + if err != nil { + return dicedomain.Match{}, err + } + participants, err := queryDiceParticipants(ctx, r.db, app, match.MatchID, false) + if err != nil { + return dicedomain.Match{}, err + } + match.Participants = participants + return match, nil +} + +// ClaimDiceMatchForRoll 把可结算局切到 settling;重复 roll 只能拿到同一局事实继续幂等补偿。 +func (r *Repository) ClaimDiceMatchForRoll(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Match{}, err + } + defer func() { _ = tx.Rollback() }() + + app := appcode.Normalize(appCode) + // 先锁 match,再锁 participants,所有结算入口保持固定锁顺序,降低死锁概率。 + match, err := queryDiceMatch(ctx, tx, app, strings.TrimSpace(matchID), true) + if err != nil { + return dicedomain.Match{}, err + } + participants, err := queryDiceParticipants(ctx, tx, app, match.MatchID, true) + if err != nil { + return dicedomain.Match{}, err + } + match.Participants = participants + if match.Status == dicedomain.MatchStatusSettled { + // 已结算局允许 roll 重试读取结果,但不再改变任何状态。 + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return match, nil + } + if !diceMatchAllowsRoll(match.Status) { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match is not rollable") + } + if len(participants) < int(match.MinPlayers) { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match does not have enough participants") + } + if !diceMatchHasParticipant(participants, userID) { + // 非参与者不能替别人开奖;权限判断放在锁内,避免刚 join 的并发事务未提交时误判状态。 + return dicedomain.Match{}, xerr.New(xerr.PermissionDenied, "dice participant is required") + } + if match.Result == dicedomain.ParticipantResultDraw { + // 上一轮和局只用于 H5 展示点数;再次 roll 时必须清空旧点数和旧结果,避免 SaveDiceRolls 误判为幂等重试。 + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_participants + SET dice_points_json = CAST('[]' AS JSON), result = '', payout_coin = 0, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + nowMs, app, match.MatchID, + ); err != nil { + return dicedomain.Match{}, err + } + for index := range match.Participants { + match.Participants[index].DicePoints = nil + match.Participants[index].Result = "" + match.Participants[index].PayoutCoin = 0 + match.Participants[index].UpdatedAtMS = nowMs + } + match.Result = "" + } + // 状态推进到 settling 后立即提交,外部钱包调用不持有行锁;失败补偿靠订单幂等和状态字段继续推进。 + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_matches + SET status = ?, result = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + dicedomain.MatchStatusSettling, match.Result, nowMs, app, match.MatchID, + ); err != nil { + return dicedomain.Match{}, err + } + match.Status = dicedomain.MatchStatusSettling + match.UpdatedAtMS = nowMs + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return match, nil +} + +// SaveDiceDrawForReroll 保存本轮和局点数,但不推进派奖;match 保持 ready,让 H5 2 秒后继续调用 roll。 +func (r *Repository) SaveDiceDrawForReroll(ctx context.Context, match dicedomain.Match, nowMs int64) (dicedomain.Match, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Match{}, err + } + defer func() { _ = tx.Rollback() }() + + app := appcode.Normalize(match.AppCode) + locked, err := queryDiceMatch(ctx, tx, app, strings.TrimSpace(match.MatchID), true) + if err != nil { + return dicedomain.Match{}, err + } + if locked.Status == dicedomain.MatchStatusSettled { + participants, err := queryDiceParticipants(ctx, tx, app, locked.MatchID, true) + if err != nil { + return dicedomain.Match{}, err + } + locked.Participants = participants + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return locked, nil + } + if locked.Status != dicedomain.MatchStatusSettling { + return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match is not saving draw") + } + for _, participant := range match.Participants { + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_participants + SET dice_points_json = CAST(? AS JSON), result = ?, payout_coin = 0, updated_at_ms = ? + WHERE app_code = ? AND match_id = ? AND user_id = ?`, + dicePointsJSON(participant.DicePoints), dicedomain.ParticipantResultDraw, nowMs, + app, match.MatchID, participant.UserID, + ); err != nil { + return dicedomain.Match{}, err + } + } + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_matches + SET status = ?, result = ?, round_no = round_no + 1, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + dicedomain.MatchStatusReady, dicedomain.ParticipantResultDraw, nowMs, app, match.MatchID, + ); err != nil { + return dicedomain.Match{}, err + } + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return r.GetDiceMatch(ctx, app, match.MatchID) +} + +// SaveDiceRolls 保存首次随机结果;如果重试时已经有点数,直接返回旧结果,保证不会重复随机。 +func (r *Repository) SaveDiceRolls(ctx context.Context, match dicedomain.Match, nowMs int64) (dicedomain.Match, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return dicedomain.Match{}, err + } + defer func() { _ = tx.Rollback() }() + + app := appcode.Normalize(match.AppCode) + // 保存点数前再次锁局和参与者,防止两个补偿 worker 同时为同一局写入不同随机结果。 + locked, err := queryDiceMatch(ctx, tx, app, strings.TrimSpace(match.MatchID), true) + if err != nil { + return dicedomain.Match{}, err + } + existing, err := queryDiceParticipants(ctx, tx, app, locked.MatchID, true) + if err != nil { + return dicedomain.Match{}, err + } + if dicedomain.HasRolls(existing) { + // 点数已经存在时只返回旧事实;这条分支是“重试不重投”的最后防线。 + locked.Participants = existing + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + return locked, nil + } + + for _, participant := range match.Participants { + // 点数、结果和 payout_coin 同时落参与者行,补偿任务可以只靠 participants 重建派奖命令。 + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_participants + SET dice_points_json = CAST(? AS JSON), result = ?, payout_coin = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ? AND user_id = ?`, + dicePointsJSON(participant.DicePoints), participant.Result, participant.PayoutCoin, nowMs, + app, match.MatchID, participant.UserID, + ); err != nil { + return dicedomain.Match{}, err + } + } + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_matches + SET status = ?, result = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + dicedomain.MatchStatusPayoutApplying, match.Result, nowMs, app, match.MatchID, + ); err != nil { + return dicedomain.Match{}, err + } + if err := tx.Commit(); err != nil { + return dicedomain.Match{}, err + } + // 返回提交后的完整事实,确保 service 后续派奖使用数据库中已固定的点数和 payout。 + return r.GetDiceMatch(ctx, app, match.MatchID) +} + +func (r *Repository) MarkDiceParticipantDebitSucceeded(ctx context.Context, appCode string, matchID string, userID int64, orderID string, balanceAfter int64, nowMs int64) error { + return r.updateDiceParticipantOrder(ctx, appCode, matchID, userID, dicedomain.ParticipantStatusDebitSucceeded, "debit_order_id", orderID, balanceAfter, nowMs) +} + +func (r *Repository) MarkDiceParticipantDebitFailed(ctx context.Context, appCode string, matchID string, userID int64, orderID string, nowMs int64) error { + return r.updateDiceParticipantOrder(ctx, appCode, matchID, userID, dicedomain.ParticipantStatusDebitFailed, "debit_order_id", orderID, 0, nowMs) +} + +func (r *Repository) MarkDiceParticipantPayoutSucceeded(ctx context.Context, appCode string, matchID string, userID int64, orderID string, balanceAfter int64, nowMs int64) error { + return r.updateDiceParticipantOrder(ctx, appCode, matchID, userID, dicedomain.ParticipantStatusPayoutSucceeded, "payout_order_id", orderID, balanceAfter, nowMs) +} + +func (r *Repository) MarkDiceParticipantRefundSucceeded(ctx context.Context, appCode string, matchID string, userID int64, orderID string, balanceAfter int64, nowMs int64) error { + return r.updateDiceParticipantOrder(ctx, appCode, matchID, userID, dicedomain.ParticipantStatusRefundSucceeded, "refund_order_id", orderID, balanceAfter, nowMs) +} + +func (r *Repository) updateDiceParticipantOrder(ctx context.Context, appCode string, matchID string, userID int64, status string, orderColumn string, orderID string, balanceAfter int64, nowMs int64) error { + // orderColumn 是 SQL 标识符,不能作为参数绑定;必须用白名单避免外部输入拼进 SQL。 + switch orderColumn { + case "debit_order_id", "payout_order_id", "refund_order_id": + default: + return xerr.New(xerr.InvalidArgument, "dice participant order column is invalid") + } + _, err := r.db.ExecContext(ctx, + `UPDATE game_dice_participants + SET status = ?, `+orderColumn+` = ?, balance_after = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ? AND user_id = ?`, + status, strings.TrimSpace(orderID), balanceAfter, nowMs, appcode.Normalize(appCode), strings.TrimSpace(matchID), userID, + ) + return err +} + +func (r *Repository) MarkDiceMatchSettled(ctx context.Context, appCode string, matchID string, result string, nowMs int64) error { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + app := appcode.Normalize(appCode) + // match 和 participants 在同一事务内改为 settled,客户端不会看到局已结算但参与者还停在 payout 状态。 + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_matches + SET status = ?, result = ?, updated_at_ms = ?, settled_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + dicedomain.MatchStatusSettled, strings.TrimSpace(result), nowMs, nowMs, app, strings.TrimSpace(matchID), + ); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, + `UPDATE game_dice_participants + SET status = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + dicedomain.ParticipantStatusSettled, nowMs, app, strings.TrimSpace(matchID), + ); err != nil { + return err + } + return tx.Commit() +} + +func (r *Repository) MarkDiceMatchFailed(ctx context.Context, appCode string, matchID string, result string, nowMs int64) error { + _, err := r.db.ExecContext(ctx, + `UPDATE game_dice_matches + SET status = ?, result = ?, updated_at_ms = ? + WHERE app_code = ? AND match_id = ?`, + dicedomain.MatchStatusFailed, strings.TrimSpace(result), nowMs, appcode.Normalize(appCode), strings.TrimSpace(matchID), + ) + return err +} + +func queryDiceMatch(ctx context.Context, q diceQuerier, appCode string, matchID string, forUpdate bool) (dicedomain.Match, error) { + // forUpdate 只给状态推进路径使用;读路径不加锁,避免普通刷新阻塞 join/roll。 + query := `SELECT app_code, match_id, game_id, platform_code, provider_game_id, room_id, region_id, + min_players, max_players, current_players, stake_coin, round_no, status, result, + join_deadline_ms, ready_at_ms, created_at_ms, updated_at_ms, settled_at_ms, canceled_at_ms, + fee_bps, pool_bps, match_mode, forced_result, pool_delta_coin + FROM game_dice_matches + WHERE app_code = ? AND match_id = ?` + if forUpdate { + query += ` FOR UPDATE` + } + row := q.QueryRowContext(ctx, query, appcode.Normalize(appCode), strings.TrimSpace(matchID)) + var match dicedomain.Match + if err := row.Scan(&match.AppCode, &match.MatchID, &match.GameID, &match.PlatformCode, &match.ProviderGameID, &match.RoomID, &match.RegionID, &match.MinPlayers, &match.MaxPlayers, &match.CurrentPlayers, &match.StakeCoin, &match.RoundNo, &match.Status, &match.Result, &match.JoinDeadlineMS, &match.ReadyAtMS, &match.CreatedAtMS, &match.UpdatedAtMS, &match.SettledAtMS, &match.CanceledAtMS, &match.FeeBPS, &match.PoolBPS, &match.MatchMode, &match.ForcedResult, &match.PoolDeltaCoin); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return dicedomain.Match{}, xerr.New(xerr.NotFound, "dice match not found") + } + return dicedomain.Match{}, err + } + return match, nil +} + +func queryDiceParticipants(ctx context.Context, q diceQuerier, appCode string, matchID string, forUpdate bool) ([]dicedomain.Participant, error) { + // participants 始终按座位排序,HTTP 响应和补偿重放都能得到稳定顺序。 + query := `SELECT app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, + COALESCE(CAST(dice_points_json AS CHAR), '[]'), result, payout_coin, + debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms + FROM game_dice_participants + WHERE app_code = ? AND match_id = ? + ORDER BY seat_no ASC` + if forUpdate { + query += ` FOR UPDATE` + } + rows, err := q.QueryContext(ctx, query, appcode.Normalize(appCode), strings.TrimSpace(matchID)) + if err != nil { + return nil, err + } + defer rows.Close() + + participants := []dicedomain.Participant{} + for rows.Next() { + var participant dicedomain.Participant + var pointsJSON string + if err := rows.Scan(&participant.AppCode, &participant.MatchID, &participant.UserID, &participant.ParticipantType, &participant.SeatNo, &participant.Status, &participant.StakeCoin, &pointsJSON, &participant.Result, &participant.PayoutCoin, &participant.DebitOrderID, &participant.PayoutOrderID, &participant.RefundOrderID, &participant.BalanceAfter, &participant.JoinedAtMS, &participant.UpdatedAtMS); err != nil { + return nil, err + } + participant.ParticipantType = normalizeParticipantType(participant.ParticipantType) + participant.DicePoints = parseDicePoints(pointsJSON) + participants = append(participants, participant) + } + return participants, rows.Err() +} + +func diceMatchAllowsJoin(status string) bool { + // 只有未进入结算的局允许加入;settling 之后人数和奖池都必须冻结。 + switch status { + case dicedomain.MatchStatusCreated, dicedomain.MatchStatusJoining: + return true + default: + return false + } +} + +func diceMatchAllowsRoll(status string) bool { + // settling/payout_applying 允许再次进入,是为了补偿同一局未完成的钱包步骤,不代表重新开奖。 + switch status { + case dicedomain.MatchStatusCreated, dicedomain.MatchStatusJoining, dicedomain.MatchStatusReady, dicedomain.MatchStatusSettling, dicedomain.MatchStatusPayoutApplying: + return true + default: + return false + } +} + +func diceMatchHasParticipant(participants []dicedomain.Participant, userID int64) bool { + for _, participant := range participants { + if participant.UserID == userID { + return true + } + } + return false +} + +func dicePointsJSON(points []int32) string { + if len(points) == 0 { + return "[]" + } + raw, err := json.Marshal(points) + if err != nil { + return "[]" + } + return string(raw) +} + +func parseDicePoints(raw string) []int32 { + var points []int32 + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &points); err != nil { + // JSON 异常按空点数处理,让上层 Score/HasRolls 决定是否能继续结算,不在扫描阶段吞掉整行。 + return nil + } + return points +} diff --git a/services/game-service/internal/storage/mysql/repository.go b/services/game-service/internal/storage/mysql/repository.go index 961cadc2..a50613a8 100644 --- a/services/game-service/internal/storage/mysql/repository.go +++ b/services/game-service/internal/storage/mysql/repository.go @@ -160,6 +160,50 @@ func (r *Repository) Migrate(ctx context.Context) error { KEY idx_game_order_user_time(app_code, user_id, created_at_ms), KEY idx_game_order_round(app_code, platform_code, provider_round_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_dice_matches ( + app_code VARCHAR(32) NOT NULL, + match_id VARCHAR(96) NOT NULL, + game_id VARCHAR(96) NOT NULL, + platform_code VARCHAR(64) NOT NULL, + provider_game_id VARCHAR(128) NOT NULL, + room_id VARCHAR(96) NOT NULL DEFAULT '', + region_id BIGINT NOT NULL DEFAULT 0, + min_players INT NOT NULL, + max_players INT NOT NULL, + current_players INT NOT NULL DEFAULT 0, + stake_coin BIGINT NOT NULL, + round_no INT NOT NULL DEFAULT 1, + status VARCHAR(32) NOT NULL, + result VARCHAR(64) NOT NULL DEFAULT '', + join_deadline_ms BIGINT NOT NULL DEFAULT 0, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + settled_at_ms BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY(app_code, match_id), + KEY idx_game_dice_room_active(app_code, room_id, status, created_at_ms), + KEY idx_game_dice_status_time(app_code, status, updated_at_ms, match_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_dice_participants ( + app_code VARCHAR(32) NOT NULL, + match_id VARCHAR(96) NOT NULL, + user_id BIGINT NOT NULL, + seat_no INT NOT NULL, + status VARCHAR(32) NOT NULL, + stake_coin BIGINT NOT NULL, + dice_points_json JSON NULL, + result VARCHAR(32) NOT NULL DEFAULT '', + payout_coin BIGINT NOT NULL DEFAULT 0, + debit_order_id VARCHAR(96) NOT NULL DEFAULT '', + payout_order_id VARCHAR(96) NOT NULL DEFAULT '', + refund_order_id VARCHAR(96) NOT NULL DEFAULT '', + balance_after BIGINT NOT NULL DEFAULT 0, + joined_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, match_id, user_id), + UNIQUE KEY uk_game_dice_seat(app_code, match_id, seat_no), + KEY idx_game_dice_participant_user(app_code, user_id, updated_at_ms), + KEY idx_game_dice_participant_status(app_code, match_id, status, seat_no) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, `CREATE TABLE IF NOT EXISTS game_callback_logs ( app_code VARCHAR(32) NOT NULL, callback_id VARCHAR(96) NOT NULL, @@ -296,6 +340,21 @@ func (r *Repository) Migrate(ctx context.Context) error { if err := r.ensureIndexDefinition(ctx, "game_level_event_outbox", "idx_game_level_event_retention", []string{"app_code", "status", "updated_at_ms", "event_id"}, "ALTER TABLE game_level_event_outbox ADD INDEX idx_game_level_event_retention(app_code, status, updated_at_ms, event_id)"); err != nil { return err } + if err := r.ensureIndexDefinition(ctx, "game_dice_matches", "idx_game_dice_room_active", []string{"app_code", "room_id", "status", "created_at_ms"}, "ALTER TABLE game_dice_matches ADD INDEX idx_game_dice_room_active(app_code, room_id, status, created_at_ms)"); err != nil { + return err + } + if err := r.ensureIndexDefinition(ctx, "game_dice_matches", "idx_game_dice_status_time", []string{"app_code", "status", "updated_at_ms", "match_id"}, "ALTER TABLE game_dice_matches ADD INDEX idx_game_dice_status_time(app_code, status, updated_at_ms, match_id)"); err != nil { + return err + } + if err := r.ensureIndexDefinition(ctx, "game_dice_participants", "uk_game_dice_seat", []string{"app_code", "match_id", "seat_no"}, "ALTER TABLE game_dice_participants ADD UNIQUE INDEX uk_game_dice_seat(app_code, match_id, seat_no)"); err != nil { + return err + } + if err := r.ensureIndexDefinition(ctx, "game_dice_participants", "idx_game_dice_participant_user", []string{"app_code", "user_id", "updated_at_ms"}, "ALTER TABLE game_dice_participants ADD INDEX idx_game_dice_participant_user(app_code, user_id, updated_at_ms)"); err != nil { + return err + } + if err := r.ensureIndexDefinition(ctx, "game_dice_participants", "idx_game_dice_participant_status", []string{"app_code", "match_id", "status", "seat_no"}, "ALTER TABLE game_dice_participants ADD INDEX idx_game_dice_participant_status(app_code, match_id, status, seat_no)"); err != nil { + return err + } return nil } diff --git a/services/game-service/internal/transport/grpc/server.go b/services/game-service/internal/transport/grpc/server.go index 42235d34..466311c1 100644 --- a/services/game-service/internal/transport/grpc/server.go +++ b/services/game-service/internal/transport/grpc/server.go @@ -8,7 +8,9 @@ import ( gamev1 "hyapp.local/api/proto/game/v1" "hyapp/pkg/appcode" "hyapp/pkg/xerr" + dicedomain "hyapp/services/game-service/internal/domain/dice" gamedomain "hyapp/services/game-service/internal/domain/game" + diceservice "hyapp/services/game-service/internal/service/dice" gameservice "hyapp/services/game-service/internal/service/game" ) @@ -19,11 +21,17 @@ type Server struct { gamev1.UnimplementedGameAdminServiceServer gamev1.UnimplementedGameCronServiceServer - svc *gameservice.Service + svc *gameservice.Service + diceSvc *diceservice.Service } -func NewServer(svc *gameservice.Service) *Server { - return &Server{svc: svc} +func NewServer(svc *gameservice.Service, diceServices ...*diceservice.Service) *Server { + // diceSvc 用可选参数接入,保留旧测试和非骰子场景的构造方式;生产 app 会显式传入骰子服务。 + var diceSvc *diceservice.Service + if len(diceServices) > 0 { + diceSvc = diceServices[0] + } + return &Server{svc: svc, diceSvc: diceSvc} } func (s *Server) ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) { @@ -110,6 +118,131 @@ func (s *Server) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) }, nil } +func (s *Server) GetDiceConfig(ctx context.Context, req *gamev1.GetDiceConfigRequest) (*gamev1.DiceConfigResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + config, serverTimeMS, err := s.diceSvc.GetConfig(ctx, req.GetMeta().GetAppCode(), req.GetGameId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceConfigResponse{Config: diceConfigToProto(config), ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) MatchDice(ctx context.Context, req *gamev1.MatchDiceRequest) (*gamev1.DiceMatchResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + match, serverTimeMS, err := s.diceSvc.Match(ctx, diceservice.MatchCommand{ + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + GameID: req.GetGameId(), + RoomID: req.GetRoomId(), + RegionID: req.GetRegionId(), + StakeCoin: req.GetStakeCoin(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceMatchResponse{Match: diceMatchToProtoAt(match, serverTimeMS), ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) CreateDiceMatch(ctx context.Context, req *gamev1.CreateDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + // gRPC 层只把 proto 字段转成 command;人数、金额、游戏状态和钱包逻辑都留给 service 层验证。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + match, serverTimeMS, err := s.diceSvc.CreateMatch(ctx, diceservice.CreateMatchCommand{ + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + GameID: req.GetGameId(), + RoomID: req.GetRoomId(), + RegionID: req.GetRegionId(), + StakeCoin: req.GetStakeCoin(), + MinPlayers: req.GetMinPlayers(), + MaxPlayers: req.GetMaxPlayers(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceMatchResponse{Match: diceMatchToProtoAt(match, serverTimeMS), ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) JoinDiceMatch(ctx context.Context, req *gamev1.JoinDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + // user_id 由 gateway 从登录态注入;transport 不允许用 actor_user_id 推导业务用户,避免调用方混淆身份字段。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + match, serverTimeMS, err := s.diceSvc.JoinMatch(ctx, diceservice.JoinMatchCommand{ + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + MatchID: req.GetMatchId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceMatchResponse{Match: diceMatchToProtoAt(match, serverTimeMS), ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) GetDiceMatch(ctx context.Context, req *gamev1.GetDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + // 查询接口仍要求 user_id,后续如果要做观战/隐私控制,可以在 service 层基于同一 command 加规则。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + match, serverTimeMS, err := s.diceSvc.GetMatch(ctx, diceservice.GetMatchCommand{ + AppCode: req.GetMeta().GetAppCode(), + UserID: req.GetUserId(), + MatchID: req.GetMatchId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceMatchResponse{Match: diceMatchToProtoAt(match, serverTimeMS), ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) RollDiceMatch(ctx context.Context, req *gamev1.RollDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + // roll 是唯一会触发钱包改账的骰子 RPC;gRPC 层不拆分扣款/派奖,避免客户端绕过局状态机。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + match, serverTimeMS, err := s.diceSvc.RollMatch(ctx, diceservice.RollMatchCommand{ + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + MatchID: req.GetMatchId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceMatchResponse{Match: diceMatchToProtoAt(match, serverTimeMS), ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) CancelDiceMatch(ctx context.Context, req *gamev1.CancelDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + match, serverTimeMS, err := s.diceSvc.CancelMatch(ctx, diceservice.CancelMatchCommand{ + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + MatchID: req.GetMatchId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceMatchResponse{Match: diceMatchToProtoAt(match, serverTimeMS), ServerTimeMs: serverTimeMS}, nil +} + func (s *Server) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) raw, contentType, err := s.svc.HandleCallback(ctx, req) @@ -218,6 +351,112 @@ func (s *Server) DeleteCatalog(ctx context.Context, req *gamev1.DeleteCatalogReq return &gamev1.DeleteCatalogResponse{ServerTimeMs: time.Now().UnixMilli()}, nil } +func (s *Server) ListSelfGames(ctx context.Context, req *gamev1.ListSelfGamesRequest) (*gamev1.ListSelfGamesResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + items, serverTimeMS, err := s.diceSvc.ListSelfGames(ctx, req.GetMeta().GetAppCode()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &gamev1.ListSelfGamesResponse{ServerTimeMs: serverTimeMS, Games: make([]*gamev1.DiceConfig, 0, len(items))} + for _, item := range items { + resp.Games = append(resp.Games, diceConfigToProto(item)) + } + return resp, nil +} + +func (s *Server) UpdateDiceConfig(ctx context.Context, req *gamev1.UpdateDiceConfigRequest) (*gamev1.DiceConfigResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + config := diceConfigFromProto(req.GetConfig()) + config.AppCode = req.GetMeta().GetAppCode() + saved, serverTimeMS, err := s.diceSvc.UpdateConfig(ctx, config) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceConfigResponse{Config: diceConfigToProto(saved), ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) AdjustDicePool(ctx context.Context, req *gamev1.AdjustDicePoolRequest) (*gamev1.AdjustDicePoolResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + adjustment, config, serverTimeMS, err := s.diceSvc.AdjustPool(ctx, dicedomain.PoolAdjustment{ + AppCode: req.GetMeta().GetAppCode(), + GameID: req.GetGameId(), + Direction: req.GetDirection(), + AmountCoin: req.GetAmountCoin(), + Reason: req.GetReason(), + CreatedBy: req.GetMeta().GetActorUserId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.AdjustDicePoolResponse{Adjustment: dicePoolAdjustmentToProto(adjustment), Config: diceConfigToProto(config), ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) ListDiceRobots(ctx context.Context, req *gamev1.ListDiceRobotsRequest) (*gamev1.ListDiceRobotsResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + items, next, serverTimeMS, err := s.diceSvc.ListRobots(ctx, req.GetMeta().GetAppCode(), req.GetGameId(), req.GetStatus(), req.GetPageSize(), req.GetCursor()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &gamev1.ListDiceRobotsResponse{NextCursor: next, ServerTimeMs: serverTimeMS, Robots: make([]*gamev1.DiceRobot, 0, len(items))} + for _, item := range items { + resp.Robots = append(resp.Robots, diceRobotToProto(item)) + } + return resp, nil +} + +func (s *Server) RegisterDiceRobots(ctx context.Context, req *gamev1.RegisterDiceRobotsRequest) (*gamev1.RegisterDiceRobotsResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + items, serverTimeMS, err := s.diceSvc.RegisterRobots(ctx, req.GetMeta().GetAppCode(), req.GetGameId(), req.GetUserIds(), req.GetMeta().GetActorUserId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &gamev1.RegisterDiceRobotsResponse{ServerTimeMs: serverTimeMS, Robots: make([]*gamev1.DiceRobot, 0, len(items))} + for _, item := range items { + resp.Robots = append(resp.Robots, diceRobotToProto(item)) + } + return resp, nil +} + +func (s *Server) SetDiceRobotStatus(ctx context.Context, req *gamev1.SetDiceRobotStatusRequest) (*gamev1.DiceRobotResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + item, serverTimeMS, err := s.diceSvc.SetRobotStatus(ctx, req.GetMeta().GetAppCode(), req.GetGameId(), req.GetUserId(), req.GetStatus()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DiceRobotResponse{Robot: diceRobotToProto(item), ServerTimeMs: serverTimeMS}, nil +} + +// DeleteDiceRobot 只对管理端暴露机器人池删除能力,删除范围由 app_code、game_id、user_id 三元组限定。 +func (s *Server) DeleteDiceRobot(ctx context.Context, req *gamev1.DeleteDiceRobotRequest) (*gamev1.DeleteDiceRobotResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + serverTimeMS, err := s.diceSvc.DeleteRobot(ctx, req.GetMeta().GetAppCode(), req.GetGameId(), req.GetUserId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.DeleteDiceRobotResponse{Deleted: true, ServerTimeMs: serverTimeMS}, nil +} + func (s *Server) svcRepository() gameservice.Repository { if s == nil || s.svc == nil || s.svc.Repository() == nil { return unavailableRepository{} @@ -313,6 +552,161 @@ func appGameToProto(item gamedomain.AppGame) *gamev1.AppGame { } } +func diceMatchToProto(match dicedomain.Match) *gamev1.DiceMatch { + return diceMatchToProtoAt(match, time.Now().UnixMilli()) +} + +func diceMatchToProtoAt(match dicedomain.Match, nowMS int64) *gamev1.DiceMatch { + participants := make([]*gamev1.DiceParticipant, 0, len(match.Participants)) + for _, participant := range match.Participants { + participants = append(participants, diceParticipantToProto(participant)) + } + phase, phaseDeadlineMS := dicePhase(match, nowMS) + // proto 只暴露客户端需要的局事实;订单 ID 和 provider 快照留在 MySQL,避免把账务内部键泄漏到 H5。 + return &gamev1.DiceMatch{ + AppCode: match.AppCode, + MatchId: match.MatchID, + GameId: match.GameID, + RoomId: match.RoomID, + RegionId: match.RegionID, + MinPlayers: match.MinPlayers, + MaxPlayers: match.MaxPlayers, + CurrentPlayers: match.CurrentPlayers, + StakeCoin: match.StakeCoin, + RoundNo: match.RoundNo, + Status: match.Status, + Result: match.Result, + Participants: participants, + JoinDeadlineMs: match.JoinDeadlineMS, + CreatedAtMs: match.CreatedAtMS, + UpdatedAtMs: match.UpdatedAtMS, + SettledAtMs: match.SettledAtMS, + Phase: phase, + PhaseDeadlineMs: phaseDeadlineMS, + FeeBps: match.FeeBPS, + PoolBps: match.PoolBPS, + MatchMode: match.MatchMode, + ForcedResult: match.ForcedResult, + ReadyAtMs: match.ReadyAtMS, + CanceledAtMs: match.CanceledAtMS, + PoolDeltaCoin: match.PoolDeltaCoin, + } +} + +func diceParticipantToProto(participant dicedomain.Participant) *gamev1.DiceParticipant { + // balance_after 是最近一次钱包操作后的快照,供 H5 结算弹层刷新余额;不是局内可计算字段。 + return &gamev1.DiceParticipant{ + UserId: participant.UserID, + SeatNo: participant.SeatNo, + Status: participant.Status, + StakeCoin: participant.StakeCoin, + DicePoints: participant.DicePoints, + Result: participant.Result, + PayoutCoin: participant.PayoutCoin, + BalanceAfter: participant.BalanceAfter, + JoinedAtMs: participant.JoinedAtMS, + UpdatedAtMs: participant.UpdatedAtMS, + ParticipantType: participant.ParticipantType, + IsRobot: participant.ParticipantType == dicedomain.ParticipantTypeRobot, + } +} + +func diceConfigToProto(config dicedomain.Config) *gamev1.DiceConfig { + options := make([]*gamev1.DiceStakeOption, 0, len(config.StakeOptions)) + for _, option := range config.StakeOptions { + options = append(options, &gamev1.DiceStakeOption{StakeCoin: option.StakeCoin, Enabled: option.Enabled, SortOrder: option.SortOrder}) + } + return &gamev1.DiceConfig{ + AppCode: config.AppCode, + GameId: config.GameID, + Status: config.Status, + StakeOptions: options, + FeeBps: config.FeeBPS, + PoolBps: config.PoolBPS, + MinPlayers: config.MinPlayers, + MaxPlayers: config.MaxPlayers, + RobotEnabled: config.RobotEnabled, + RobotMatchWaitMs: config.RobotMatchWaitMS, + PoolBalanceCoin: config.PoolBalanceCoin, + CreatedAtMs: config.CreatedAtMS, + UpdatedAtMs: config.UpdatedAtMS, + } +} + +func diceConfigFromProto(config *gamev1.DiceConfig) dicedomain.Config { + if config == nil { + return dicedomain.Config{} + } + options := make([]dicedomain.StakeOption, 0, len(config.GetStakeOptions())) + for _, option := range config.GetStakeOptions() { + options = append(options, dicedomain.StakeOption{StakeCoin: option.GetStakeCoin(), Enabled: option.GetEnabled(), SortOrder: option.GetSortOrder()}) + } + return dicedomain.Config{ + AppCode: config.GetAppCode(), + GameID: config.GetGameId(), + Status: config.GetStatus(), + StakeOptions: options, + FeeBPS: config.GetFeeBps(), + PoolBPS: config.GetPoolBps(), + MinPlayers: config.GetMinPlayers(), + MaxPlayers: config.GetMaxPlayers(), + RobotEnabled: config.GetRobotEnabled(), + RobotMatchWaitMS: config.GetRobotMatchWaitMs(), + } +} + +func dicePoolAdjustmentToProto(adjustment dicedomain.PoolAdjustment) *gamev1.DicePoolAdjustment { + return &gamev1.DicePoolAdjustment{ + AdjustmentId: adjustment.AdjustmentID, + GameId: adjustment.GameID, + AmountCoin: adjustment.AmountCoin, + Direction: adjustment.Direction, + Reason: adjustment.Reason, + BalanceAfter: adjustment.BalanceAfter, + CreatedAtMs: adjustment.CreatedAtMS, + } +} + +func diceRobotToProto(robot dicedomain.Robot) *gamev1.DiceRobot { + return &gamev1.DiceRobot{ + AppCode: robot.AppCode, + GameId: robot.GameID, + UserId: robot.UserID, + Status: robot.Status, + CreatedByAdminId: robot.CreatedByAdminID, + CreatedAtMs: robot.CreatedAtMS, + UpdatedAtMs: robot.UpdatedAtMS, + } +} + +func dicePhase(match dicedomain.Match, nowMS int64) (string, int64) { + switch match.Status { + case dicedomain.MatchStatusCanceled: + return dicedomain.MatchPhaseCanceled, match.CanceledAtMS + case dicedomain.MatchStatusFailed: + return dicedomain.MatchPhaseFailed, match.UpdatedAtMS + case dicedomain.MatchStatusSettled: + return dicedomain.MatchPhaseSettled, match.SettledAtMS + dicedomain.SettlementShowMillis + } + if match.Result == dicedomain.ParticipantResultDraw { + // 和局不是终局,只给客户端一个 2 秒比点展示窗口,窗口结束后 H5 可以继续调用 roll。 + return dicedomain.MatchPhaseComparing, match.UpdatedAtMS + dicedomain.DrawRerollMillis + } + if match.ReadyAtMS <= 0 { + return dicedomain.MatchPhaseWaiting, match.JoinDeadlineMS + } + countdownEnd := match.ReadyAtMS + dicedomain.CountdownMillis + rollingEnd := countdownEnd + dicedomain.RollingMillis + comparingEnd := rollingEnd + dicedomain.ComparingMillis + if nowMS < countdownEnd { + return dicedomain.MatchPhaseCountdown, countdownEnd + } + if nowMS < rollingEnd { + return dicedomain.MatchPhaseRolling, rollingEnd + } + return dicedomain.MatchPhaseComparing, comparingEnd +} + func platformToProto(item gamedomain.Platform) *gamev1.GamePlatform { return &gamev1.GamePlatform{ AppCode: item.AppCode, diff --git a/services/gateway-service/configs/config.docker.yaml b/services/gateway-service/configs/config.docker.yaml index bc8f8869..a6b24894 100644 --- a/services/gateway-service/configs/config.docker.yaml +++ b/services/gateway-service/configs/config.docker.yaml @@ -11,21 +11,7 @@ http_addr: ":13000" cors: enabled: true allowed_origins: - - "http://localhost:3000" - - "http://127.0.0.1:3000" - - "http://localhost:5173" - - "http://127.0.0.1:5173" - # 本地工具页会用 10300/8787/8788 承载静态 HTML;精确放行测试端口,避免用 "*" 破坏凭证请求边界。 - - "http://localhost:10300" - - "http://127.0.0.1:10300" - - "http://localhost:8787" - - "http://127.0.0.1:8787" - - "http://localhost:8788" - - "http://127.0.0.1:8788" - - "https://global-interaction.com" - - "https://www.global-interaction.com" - - "https://h5.global-interaction.com" - - "https://api.global-interaction.com" + - "*" allowed_methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allowed_headers: - "Authorization" diff --git a/services/gateway-service/configs/config.yaml b/services/gateway-service/configs/config.yaml index ba2490de..243d2a3b 100644 --- a/services/gateway-service/configs/config.yaml +++ b/services/gateway-service/configs/config.yaml @@ -11,23 +11,7 @@ http_addr: ":13000" cors: enabled: true allowed_origins: - - "http://localhost:3000" - - "http://127.0.0.1:3000" - - "http://localhost:5173" - - "http://127.0.0.1:5173" - - "http://localhost:30000" - - "http://127.0.0.1:30000" - # 本地工具页会用 10300/8787/8788 承载静态 HTML;精确放行测试端口,避免用 "*" 破坏凭证请求边界。 - - "http://localhost:10300" - - "http://127.0.0.1:10300" - - "http://localhost:8787" - - "http://127.0.0.1:8787" - - "http://localhost:8788" - - "http://127.0.0.1:8788" - - "https://global-interaction.com" - - "https://www.global-interaction.com" - - "https://h5.global-interaction.com" - - "https://api.global-interaction.com" + - "*" allowed_methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] allowed_headers: - "Authorization" diff --git a/services/gateway-service/internal/appconfig/mysql.go b/services/gateway-service/internal/appconfig/mysql.go index eaded1b0..dbbc3172 100644 --- a/services/gateway-service/internal/appconfig/mysql.go +++ b/services/gateway-service/internal/appconfig/mysql.go @@ -36,7 +36,7 @@ const listAppBannersSQL = ` AND (ends_at_ms = 0 OR ends_at_ms > ?) ORDER BY sort_order ASC, id DESC` const listSplashScreensSQL = ` - SELECT id, app_code, cover_url, splash_type, param, platform, sort_order, region_id, country_code, description, starts_at_ms, ends_at_ms, updated_at_ms + SELECT id, app_code, cover_url, splash_type, param, platform, sort_order, region_id, country_code, description, display_duration_ms, starts_at_ms, ends_at_ms, updated_at_ms FROM admin_app_splash_screens WHERE app_code = ? AND status = 'active' AND (? = '' OR platform = ?) @@ -115,18 +115,19 @@ type SplashScreenQuery struct { // SplashScreen 是 gateway 下发给 App 的开屏配置;type 语义和 banner 保持一致,h5 参数是链接,app 参数由客户端解释。 type SplashScreen struct { - ID uint `json:"id"` - CoverURL string `json:"cover_url"` - SplashType string `json:"type"` - Param string `json:"param"` - Platform string `json:"platform"` - SortOrder int `json:"sort_order"` - RegionID int64 `json:"region_id"` - CountryCode string `json:"country_code"` - Description string `json:"description"` - StartsAtMs int64 `json:"starts_at_ms"` - EndsAtMs int64 `json:"ends_at_ms"` - UpdatedAtMs int64 `json:"updated_at_ms"` + ID uint `json:"id"` + CoverURL string `json:"cover_url"` + SplashType string `json:"type"` + Param string `json:"param"` + Platform string `json:"platform"` + SortOrder int `json:"sort_order"` + RegionID int64 `json:"region_id"` + CountryCode string `json:"country_code"` + Description string `json:"description"` + DisplayDurationMs int `json:"display_duration_ms"` + StartsAtMs int64 `json:"starts_at_ms"` + EndsAtMs int64 `json:"ends_at_ms"` + UpdatedAtMs int64 `json:"updated_at_ms"` } // VersionQuery 是 App 检查更新的公开筛选条件。 @@ -350,6 +351,7 @@ func (r *MySQLReader) ListSplashScreens(ctx context.Context, query SplashScreenQ &item.RegionID, &item.CountryCode, &item.Description, + &item.DisplayDurationMs, &item.StartsAtMs, &item.EndsAtMs, &updatedAtMS, diff --git a/services/gateway-service/internal/auth/jwt.go b/services/gateway-service/internal/auth/jwt.go index c879aad6..dc36e713 100644 --- a/services/gateway-service/internal/auth/jwt.go +++ b/services/gateway-service/internal/auth/jwt.go @@ -20,11 +20,12 @@ const ( // Claims 是 gateway 从 access token 中消费的最小用户状态快照。 // 它只用于入口鉴权和 profile gate,不替代 user-service 的用户主数据。 type Claims struct { - AppCode string - UserID int64 - SessionID string - ProfileCompleted bool - OnboardingStatus string + AppCode string + UserID int64 + SessionID string + ProfileCompleted bool + OnboardingStatus string + AccessTokenExpiresAtMS int64 } // Verifier 负责在 gateway 入口层校验用户 JWT。 @@ -84,16 +85,46 @@ func (v *Verifier) Verify(header string) (Claims, error) { onboardingStatus, _ := claims["onboarding_status"].(string) sessionID, _ := claims["sid"].(string) appCode, _ := claims["app_code"].(string) + expiresAtMS := claimUnixSecondsMS(claims["exp"]) return Claims{ - AppCode: appcode.Normalize(appCode), - UserID: userID, - SessionID: strings.TrimSpace(sessionID), - ProfileCompleted: profileCompleted, - OnboardingStatus: strings.TrimSpace(onboardingStatus), + AppCode: appcode.Normalize(appCode), + UserID: userID, + SessionID: strings.TrimSpace(sessionID), + ProfileCompleted: profileCompleted, + OnboardingStatus: strings.TrimSpace(onboardingStatus), + AccessTokenExpiresAtMS: expiresAtMS, }, nil } +func claimUnixSecondsMS(value any) int64 { + switch typed := value.(type) { + case float64: + if typed <= 0 { + return 0 + } + return int64(typed) * 1000 + case int64: + if typed <= 0 { + return 0 + } + return typed * 1000 + case int: + if typed <= 0 { + return 0 + } + return int64(typed) * 1000 + case string: + parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64) + if err != nil || parsed <= 0 { + return 0 + } + return parsed * 1000 + default: + return 0 + } +} + // WithUserID 把已鉴权用户写入请求上下文,供 gateway 组装 RequestMeta。 func WithUserID(ctx context.Context, userID int64) context.Context { return context.WithValue(ctx, userContextKey, userID) @@ -130,3 +161,9 @@ func OnboardingStatusFromContext(ctx context.Context) string { claims, _ := ctx.Value(claimsContextKey).(Claims) return claims.OnboardingStatus } + +// AccessTokenExpiresAtMSFromContext 返回当前 access token 的过期时间;旧测试 token 没有 exp 时返回 0。 +func AccessTokenExpiresAtMSFromContext(ctx context.Context) int64 { + claims, _ := ctx.Value(claimsContextKey).(Claims) + return claims.AccessTokenExpiresAtMS +} diff --git a/services/gateway-service/internal/client/game_client.go b/services/gateway-service/internal/client/game_client.go index 19d68c01..54778dc4 100644 --- a/services/gateway-service/internal/client/game_client.go +++ b/services/gateway-service/internal/client/game_client.go @@ -13,6 +13,13 @@ type GameClient interface { ListRecentGames(ctx context.Context, req *gamev1.ListRecentGamesRequest) (*gamev1.ListGamesResponse, error) GetBridgeScript(ctx context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) + GetDiceConfig(ctx context.Context, req *gamev1.GetDiceConfigRequest) (*gamev1.DiceConfigResponse, error) + MatchDice(ctx context.Context, req *gamev1.MatchDiceRequest) (*gamev1.DiceMatchResponse, error) + CreateDiceMatch(ctx context.Context, req *gamev1.CreateDiceMatchRequest) (*gamev1.DiceMatchResponse, error) + JoinDiceMatch(ctx context.Context, req *gamev1.JoinDiceMatchRequest) (*gamev1.DiceMatchResponse, error) + GetDiceMatch(ctx context.Context, req *gamev1.GetDiceMatchRequest) (*gamev1.DiceMatchResponse, error) + RollDiceMatch(ctx context.Context, req *gamev1.RollDiceMatchRequest) (*gamev1.DiceMatchResponse, error) + CancelDiceMatch(ctx context.Context, req *gamev1.CancelDiceMatchRequest) (*gamev1.DiceMatchResponse, error) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) } @@ -44,6 +51,34 @@ func (c *grpcGameClient) LaunchGame(ctx context.Context, req *gamev1.LaunchGameR return c.app.LaunchGame(ctx, req) } +func (c *grpcGameClient) GetDiceConfig(ctx context.Context, req *gamev1.GetDiceConfigRequest) (*gamev1.DiceConfigResponse, error) { + return c.app.GetDiceConfig(ctx, req) +} + +func (c *grpcGameClient) MatchDice(ctx context.Context, req *gamev1.MatchDiceRequest) (*gamev1.DiceMatchResponse, error) { + return c.app.MatchDice(ctx, req) +} + +func (c *grpcGameClient) CreateDiceMatch(ctx context.Context, req *gamev1.CreateDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return c.app.CreateDiceMatch(ctx, req) +} + +func (c *grpcGameClient) JoinDiceMatch(ctx context.Context, req *gamev1.JoinDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return c.app.JoinDiceMatch(ctx, req) +} + +func (c *grpcGameClient) GetDiceMatch(ctx context.Context, req *gamev1.GetDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return c.app.GetDiceMatch(ctx, req) +} + +func (c *grpcGameClient) RollDiceMatch(ctx context.Context, req *gamev1.RollDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return c.app.RollDiceMatch(ctx, req) +} + +func (c *grpcGameClient) CancelDiceMatch(ctx context.Context, req *gamev1.CancelDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return c.app.CancelDiceMatch(ctx, req) +} + func (c *grpcGameClient) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { return c.callback.HandleCallback(ctx, req) } diff --git a/services/gateway-service/internal/client/user_client.go b/services/gateway-service/internal/client/user_client.go index dd935f7e..db3ea61c 100644 --- a/services/gateway-service/internal/client/user_client.go +++ b/services/gateway-service/internal/client/user_client.go @@ -26,6 +26,8 @@ type UserIdentityClient interface { ResolveDisplayUserID(ctx context.Context, req *userv1.ResolveDisplayUserIDRequest) (*userv1.ResolveDisplayUserIDResponse, error) ChangeDisplayUserID(ctx context.Context, req *userv1.ChangeDisplayUserIDRequest) (*userv1.ChangeDisplayUserIDResponse, error) ApplyPrettyDisplayUserID(ctx context.Context, req *userv1.ApplyPrettyDisplayUserIDRequest) (*userv1.ApplyPrettyDisplayUserIDResponse, error) + ListAvailablePrettyDisplayIDs(ctx context.Context, req *userv1.ListAvailablePrettyDisplayIDsRequest) (*userv1.ListAvailablePrettyDisplayIDsResponse, error) + ApplyPrettyDisplayIDFromPool(ctx context.Context, req *userv1.ApplyPrettyDisplayIDFromPoolRequest) (*userv1.ApplyPrettyDisplayIDFromPoolResponse, error) } // UserProfileClient 抽象 gateway 对 user-service 用户资料能力的依赖。 @@ -270,6 +272,14 @@ func (c *grpcUserIdentityClient) ApplyPrettyDisplayUserID(ctx context.Context, r return c.client.ApplyPrettyDisplayUserID(ctx, req) } +func (c *grpcUserIdentityClient) ListAvailablePrettyDisplayIDs(ctx context.Context, req *userv1.ListAvailablePrettyDisplayIDsRequest) (*userv1.ListAvailablePrettyDisplayIDsResponse, error) { + return c.client.ListAvailablePrettyDisplayIDs(ctx, req) +} + +func (c *grpcUserIdentityClient) ApplyPrettyDisplayIDFromPool(ctx context.Context, req *userv1.ApplyPrettyDisplayIDFromPoolRequest) (*userv1.ApplyPrettyDisplayIDFromPoolResponse, error) { + return c.client.ApplyPrettyDisplayIDFromPool(ctx, req) +} + func (c *grpcUserProfileClient) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) { return c.client.GetUser(ctx, req) } diff --git a/services/gateway-service/internal/client/wallet_client.go b/services/gateway-service/internal/client/wallet_client.go index 624480d5..b28f54a7 100644 --- a/services/gateway-service/internal/client/wallet_client.go +++ b/services/gateway-service/internal/client/wallet_client.go @@ -17,6 +17,11 @@ type WalletClient interface { GetWalletValueSummary(ctx context.Context, req *walletv1.GetWalletValueSummaryRequest) (*walletv1.GetWalletValueSummaryResponse, error) GetUserGiftWall(ctx context.Context, req *walletv1.GetUserGiftWallRequest) (*walletv1.GetUserGiftWallResponse, error) ListRechargeProducts(ctx context.Context, req *walletv1.ListRechargeProductsRequest) (*walletv1.ListRechargeProductsResponse, error) + ListH5RechargeOptions(ctx context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error) + CreateH5RechargeOrder(ctx context.Context, req *walletv1.CreateH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) + SubmitH5RechargeTx(ctx context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error) + GetH5RechargeOrder(ctx context.Context, req *walletv1.GetH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) + HandleMifapayNotify(ctx context.Context, req *walletv1.HandleMifapayNotifyRequest) (*walletv1.HandleMifapayNotifyResponse, error) ConfirmGooglePayment(ctx context.Context, req *walletv1.ConfirmGooglePaymentRequest) (*walletv1.ConfirmGooglePaymentResponse, error) GetDiamondExchangeConfig(ctx context.Context, req *walletv1.GetDiamondExchangeConfigRequest) (*walletv1.GetDiamondExchangeConfigResponse, error) ListWalletTransactions(ctx context.Context, req *walletv1.ListWalletTransactionsRequest) (*walletv1.ListWalletTransactionsResponse, error) @@ -85,6 +90,26 @@ func (c *grpcWalletClient) ListRechargeProducts(ctx context.Context, req *wallet return c.client.ListRechargeProducts(ctx, req) } +func (c *grpcWalletClient) ListH5RechargeOptions(ctx context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error) { + return c.client.ListH5RechargeOptions(ctx, req) +} + +func (c *grpcWalletClient) CreateH5RechargeOrder(ctx context.Context, req *walletv1.CreateH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) { + return c.client.CreateH5RechargeOrder(ctx, req) +} + +func (c *grpcWalletClient) SubmitH5RechargeTx(ctx context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error) { + return c.client.SubmitH5RechargeTx(ctx, req) +} + +func (c *grpcWalletClient) GetH5RechargeOrder(ctx context.Context, req *walletv1.GetH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) { + return c.client.GetH5RechargeOrder(ctx, req) +} + +func (c *grpcWalletClient) HandleMifapayNotify(ctx context.Context, req *walletv1.HandleMifapayNotifyRequest) (*walletv1.HandleMifapayNotifyResponse, error) { + return c.client.HandleMifapayNotify(ctx, req) +} + func (c *grpcWalletClient) ConfirmGooglePayment(ctx context.Context, req *walletv1.ConfirmGooglePaymentRequest) (*walletv1.ConfirmGooglePaymentResponse, error) { return c.client.ConfirmGooglePayment(ctx, req) } diff --git a/services/gateway-service/internal/config/config_test.go b/services/gateway-service/internal/config/config_test.go index 35e639d6..2a95b3af 100644 --- a/services/gateway-service/internal/config/config_test.go +++ b/services/gateway-service/internal/config/config_test.go @@ -22,7 +22,7 @@ func TestLoad(t *testing.T) { if cfg.RoomServiceAddr != "127.0.0.1:13001" { t.Fatalf("unexpected room addr: %s", cfg.RoomServiceAddr) } - if !cfg.CORS.Enabled || !containsString(cfg.CORS.AllowedOrigins, "https://h5.global-interaction.com") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Code") { + if !cfg.CORS.Enabled || !containsString(cfg.CORS.AllowedOrigins, "*") || !containsString(cfg.CORS.AllowedHeaders, "X-App-Code") { t.Fatalf("unexpected cors config: %+v", cfg.CORS) } if cfg.GRPCClient.DefaultTimeout != 5*time.Second || cfg.GRPCClient.RetryMaxAttempts != 2 || len(cfg.GRPCClient.RetryableStatusCodes) != 1 || cfg.GRPCClient.RetryableStatusCodes[0] != "UNAVAILABLE" { diff --git a/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler.go b/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler.go index cccb26f3..4d0cef8f 100644 --- a/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler.go +++ b/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler.go @@ -2,6 +2,7 @@ package activityapi import ( "net/http" + "strings" activityv1 "hyapp.local/api/proto/activity/v1" "hyapp/services/gateway-service/internal/auth" @@ -9,14 +10,31 @@ import ( ) type firstRechargeRewardTierData struct { - TierID int64 `json:"tier_id"` - TierCode string `json:"tier_code"` - TierName string `json:"tier_name"` - MinCoinAmount int64 `json:"min_coin_amount"` - MaxCoinAmount int64 `json:"max_coin_amount"` - ResourceGroupID int64 `json:"resource_group_id"` - Status string `json:"status"` - SortOrder int32 `json:"sort_order"` + TierID int64 `json:"tier_id"` + TierCode string `json:"tier_code"` + TierName string `json:"tier_name"` + MinCoinAmount int64 `json:"min_coin_amount"` + MaxCoinAmount int64 `json:"max_coin_amount"` + USDMinorAmount int64 `json:"usd_minor_amount"` + GoogleProductID string `json:"google_product_id"` + DiscountPercent int32 `json:"discount_percent"` + ResourceGroupID int64 `json:"resource_group_id"` + ResourceGroup *checkinResourceGroupData `json:"resource_group,omitempty"` + Rewards []firstRechargeRewardItemData `json:"rewards"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` +} + +type firstRechargeRewardItemData struct { + ResourceID int64 `json:"resource_id,omitempty"` + ResourceType string `json:"resource_type"` + ResourceName string `json:"resource_name"` + CoverURL string `json:"cover_url"` + EffectURL string `json:"effect_url"` + DurationMS int64 `json:"duration_ms"` + Quantity int64 `json:"quantity"` + WalletAssetType string `json:"wallet_asset_type,omitempty"` + WalletAssetAmount int64 `json:"wallet_asset_amount,omitempty"` } type firstRechargeRewardClaimData struct { @@ -29,6 +47,9 @@ type firstRechargeRewardClaimData struct { RechargeCoinAmount int64 `json:"recharge_coin_amount"` RechargeSequence int64 `json:"recharge_sequence"` RechargeType string `json:"recharge_type"` + TierUSDMinorAmount int64 `json:"tier_usd_minor_amount"` + GoogleProductID string `json:"google_product_id"` + RechargeUSDMinor int64 `json:"recharge_usd_minor"` Status string `json:"status"` WalletGrantID string `json:"wallet_grant_id"` FailureReason string `json:"failure_reason"` @@ -39,11 +60,12 @@ type firstRechargeRewardClaimData struct { } type firstRechargeRewardStatusData struct { - Enabled bool `json:"enabled"` - Tiers []firstRechargeRewardTierData `json:"tiers"` - Rewarded bool `json:"rewarded"` - Claim *firstRechargeRewardClaimData `json:"claim,omitempty"` - ServerTimeMS int64 `json:"server_time_ms"` + Enabled bool `json:"enabled"` + Tiers []firstRechargeRewardTierData `json:"tiers"` + Rewarded bool `json:"rewarded"` + Claim *firstRechargeRewardClaimData `json:"claim,omitempty"` + Claims []firstRechargeRewardClaimData `json:"claims"` + ServerTimeMS int64 `json:"server_time_ms"` } func (h *Handler) getFirstRechargeRewardStatus(writer http.ResponseWriter, request *http.Request) { @@ -59,45 +81,66 @@ func (h *Handler) getFirstRechargeRewardStatus(writer http.ResponseWriter, reque httpkit.WriteRPCError(writer, request, err) return } - httpkit.WriteOK(writer, request, firstRechargeRewardStatusFromProto(resp.GetStatus())) + status := resp.GetStatus() + groups, ok := h.resolveCheckinResourceGroups(writer, request, firstRechargeRewardGroupIDs(status)) + if !ok { + return + } + httpkit.WriteOK(writer, request, firstRechargeRewardStatusFromProto(status, groups)) } -func firstRechargeRewardStatusFromProto(item *activityv1.FirstRechargeRewardStatus) firstRechargeRewardStatusData { +func firstRechargeRewardStatusFromProto(item *activityv1.FirstRechargeRewardStatus, groups map[int64]checkinResourceGroupData) firstRechargeRewardStatusData { if item == nil { - return firstRechargeRewardStatusData{Tiers: []firstRechargeRewardTierData{}} + return firstRechargeRewardStatusData{Tiers: []firstRechargeRewardTierData{}, Claims: []firstRechargeRewardClaimData{}} } tiers := make([]firstRechargeRewardTierData, 0, len(item.GetTiers())) for _, tier := range item.GetTiers() { - tiers = append(tiers, firstRechargeRewardTierFromProto(tier)) + tiers = append(tiers, firstRechargeRewardTierFromProto(tier, groups)) } var claim *firstRechargeRewardClaimData if item.GetClaim() != nil { converted := firstRechargeRewardClaimFromProto(item.GetClaim()) claim = &converted } + claims := make([]firstRechargeRewardClaimData, 0, len(item.GetClaims())) + for _, item := range item.GetClaims() { + claims = append(claims, firstRechargeRewardClaimFromProto(item)) + } return firstRechargeRewardStatusData{ Enabled: item.GetEnabled(), Tiers: tiers, Rewarded: item.GetRewarded(), Claim: claim, + Claims: claims, ServerTimeMS: item.GetServerTimeMs(), } } -func firstRechargeRewardTierFromProto(item *activityv1.FirstRechargeRewardTier) firstRechargeRewardTierData { +func firstRechargeRewardTierFromProto(item *activityv1.FirstRechargeRewardTier, groups map[int64]checkinResourceGroupData) firstRechargeRewardTierData { if item == nil { return firstRechargeRewardTierData{} } - return firstRechargeRewardTierData{ + data := firstRechargeRewardTierData{ TierID: item.GetTierId(), TierCode: item.GetTierCode(), TierName: item.GetTierName(), MinCoinAmount: item.GetMinCoinAmount(), MaxCoinAmount: item.GetMaxCoinAmount(), + USDMinorAmount: item.GetUsdMinorAmount(), + GoogleProductID: item.GetGoogleProductId(), + DiscountPercent: item.GetDiscountPercent(), ResourceGroupID: item.GetResourceGroupId(), Status: item.GetStatus(), SortOrder: item.GetSortOrder(), } + if group, ok := groups[item.GetResourceGroupId()]; ok { + data.ResourceGroup = &group + data.Rewards = firstRechargeRewardItemsFromGroup(group) + } + if data.Rewards == nil { + data.Rewards = []firstRechargeRewardItemData{} + } + return data } func firstRechargeRewardClaimFromProto(item *activityv1.FirstRechargeRewardClaim) firstRechargeRewardClaimData { @@ -114,6 +157,9 @@ func firstRechargeRewardClaimFromProto(item *activityv1.FirstRechargeRewardClaim RechargeCoinAmount: item.GetRechargeCoinAmount(), RechargeSequence: item.GetRechargeSequence(), RechargeType: item.GetRechargeType(), + TierUSDMinorAmount: item.GetTierUsdMinorAmount(), + GoogleProductID: item.GetGoogleProductId(), + RechargeUSDMinor: item.GetRechargeUsdMinor(), Status: item.GetStatus(), WalletGrantID: item.GetWalletGrantId(), FailureReason: item.GetFailureReason(), @@ -123,3 +169,68 @@ func firstRechargeRewardClaimFromProto(item *activityv1.FirstRechargeRewardClaim UpdatedAtMS: item.GetUpdatedAtMs(), } } + +func firstRechargeRewardGroupIDs(item *activityv1.FirstRechargeRewardStatus) []int64 { + if item == nil { + return nil + } + ids := make([]int64, 0, len(item.GetTiers())) + for _, tier := range item.GetTiers() { + if tier.GetResourceGroupId() > 0 { + ids = append(ids, tier.GetResourceGroupId()) + } + } + return ids +} + +func firstRechargeRewardItemsFromGroup(group checkinResourceGroupData) []firstRechargeRewardItemData { + items := make([]firstRechargeRewardItemData, 0, len(group.Items)) + for _, item := range group.Items { + reward := firstRechargeRewardItemData{ + ResourceID: item.ResourceID, + DurationMS: item.DurationMS, + Quantity: item.Quantity, + WalletAssetType: item.WalletAssetType, + WalletAssetAmount: item.WalletAssetAmount, + } + if item.Resource != nil { + reward.ResourceType = item.Resource.ResourceType + reward.ResourceName = item.Resource.Name + reward.CoverURL = firstRechargeFirstNonEmpty(item.Resource.AssetURL, item.Resource.PreviewURL, item.Resource.AnimationURL) + reward.EffectURL = firstRechargeFirstNonEmpty(item.Resource.AnimationURL, item.Resource.PreviewURL, item.Resource.AssetURL) + } else { + reward.ResourceType = firstRechargeWalletAssetType(item.WalletAssetType) + reward.ResourceName = firstRechargeWalletAssetType(item.WalletAssetType) + reward.Quantity = firstRechargePositiveOr(item.WalletAssetAmount, item.Quantity) + } + items = append(items, reward) + } + return items +} + +func firstRechargeWalletAssetType(value string) string { + normalized := strings.ToLower(strings.TrimSpace(value)) + if normalized == "" { + return "wallet_asset" + } + if normalized == "coin" || normalized == "coins" { + return "coin" + } + return normalized +} + +func firstRechargePositiveOr(primary int64, fallback int64) int64 { + if primary > 0 { + return primary + } + return fallback +} + +func firstRechargeFirstNonEmpty(values ...string) string { + for _, value := range values { + if normalized := strings.TrimSpace(value); normalized != "" { + return normalized + } + } + return "" +} diff --git a/services/gateway-service/internal/transport/http/appapi/heartbeat_handler.go b/services/gateway-service/internal/transport/http/appapi/heartbeat_handler.go index a5c63a1e..8d8db274 100644 --- a/services/gateway-service/internal/transport/http/appapi/heartbeat_handler.go +++ b/services/gateway-service/internal/transport/http/appapi/heartbeat_handler.go @@ -2,6 +2,7 @@ package appapi import ( "net/http" + "strconv" userv1 "hyapp.local/api/proto/user/v1" "hyapp/services/gateway-service/internal/auth" @@ -9,11 +10,20 @@ import ( ) type appHeartbeatData struct { - Accepted bool `json:"accepted"` - HeartbeatAtMS int64 `json:"heartbeat_at_ms"` - ServerTimeMS int64 `json:"server_time_ms"` + Accepted bool `json:"accepted"` + HeartbeatAtMS int64 `json:"heartbeat_at_ms"` + ServerTimeMS int64 `json:"server_time_ms"` + AccessToken string `json:"access_token,omitempty"` + TokenType string `json:"token_type,omitempty"` + ExpiresInSec int64 `json:"expires_in_sec,omitempty"` } +const ( + renewedAccessTokenHeader = "X-Hyapp-Access-Token" + renewedTokenTypeHeader = "X-Hyapp-Token-Type" + renewedExpiresInHeader = "X-Hyapp-Expires-In" +) + // appHeartbeat 刷新当前登录 App 会话,不写房间 presence,也不改变麦位在线状态。 func (h *Handler) appHeartbeat(writer http.ResponseWriter, request *http.Request) { if h.userAuthClient == nil { @@ -32,10 +42,20 @@ func (h *Handler) appHeartbeat(writer http.ResponseWriter, request *http.Request httpkit.WriteRPCError(writer, request, err) return } + token := resp.GetToken() + if token.GetAccessToken() != "" { + // HTTP header 让 H5 通用请求层无感更新 token;JSON 字段保留给原生端显式消费。 + writer.Header().Set(renewedAccessTokenHeader, token.GetAccessToken()) + writer.Header().Set(renewedTokenTypeHeader, token.GetTokenType()) + writer.Header().Set(renewedExpiresInHeader, strconv.FormatInt(token.GetExpiresInSec(), 10)) + } httpkit.WriteOK(writer, request, appHeartbeatData{ Accepted: resp.GetAccepted(), HeartbeatAtMS: resp.GetHeartbeatAtMs(), ServerTimeMS: resp.GetServerTimeMs(), + AccessToken: token.GetAccessToken(), + TokenType: token.GetTokenType(), + ExpiresInSec: token.GetExpiresInSec(), }) } diff --git a/services/gateway-service/internal/transport/http/cors.go b/services/gateway-service/internal/transport/http/cors.go index e8a6664a..32bc8fdd 100644 --- a/services/gateway-service/internal/transport/http/cors.go +++ b/services/gateway-service/internal/transport/http/cors.go @@ -6,7 +6,7 @@ import ( "strings" ) -// CORSConfig 是 gateway transport 层的浏览器跨域策略;生产只回显白名单 Origin。 +// CORSConfig 是 gateway transport 层的浏览器跨域策略;生产可用白名单,联调环境可用 "*" 放开所有 Origin。 type CORSConfig struct { Enabled bool AllowedOrigins []string @@ -24,7 +24,7 @@ func WithCORS(config CORSConfig, next http.Handler) http.Handler { return next } allowedOrigins := allowedOriginSet(config.AllowedOrigins) - allowAnyOrigin := allowedOrigins["*"] && !config.AllowCredentials + allowAnyOrigin := allowedOrigins["*"] allowedMethods := strings.Join(config.AllowedMethods, ", ") allowedHeaders := strings.Join(config.AllowedHeaders, ", ") exposeHeaders := strings.Join(config.ExposeHeaders, ", ") @@ -52,9 +52,10 @@ func WithCORS(config CORSConfig, next http.Handler) http.Handler { header.Add("Vary", "Access-Control-Request-Method") header.Add("Vary", "Access-Control-Request-Headers") } - if allowAnyOrigin { + if allowAnyOrigin && !config.AllowCredentials { header.Set("Access-Control-Allow-Origin", "*") } else { + // 浏览器规范不允许 "*" 和 Allow-Credentials 同时使用;全放开且允许凭证时必须回显当前 Origin。 header.Set("Access-Control-Allow-Origin", origin) } if config.AllowCredentials { @@ -103,7 +104,7 @@ func normalizeCORSConfig(config CORSConfig) CORSConfig { config.AllowedHeaders = []string{"Authorization", "Content-Type", "X-Request-ID", "X-App-Code", "X-HY-App-Code", "X-App-Package", "X-Package-Name", "X-App-Bundle-ID", "X-Bundle-ID", "X-App-Platform", "X-Platform"} } if len(config.ExposeHeaders) == 0 { - config.ExposeHeaders = []string{"X-Request-ID"} + config.ExposeHeaders = []string{"X-Request-ID", "X-Hyapp-Access-Token", "X-Hyapp-Token-Type", "X-Hyapp-Expires-In"} } if config.MaxAgeSec <= 0 { config.MaxAgeSec = 600 diff --git a/services/gateway-service/internal/transport/http/cors_test.go b/services/gateway-service/internal/transport/http/cors_test.go index 527565de..ed223986 100644 --- a/services/gateway-service/internal/transport/http/cors_test.go +++ b/services/gateway-service/internal/transport/http/cors_test.go @@ -3,6 +3,7 @@ package http import ( "net/http" "net/http/httptest" + "strings" "testing" ) @@ -99,3 +100,40 @@ func TestCORSAddsHeadersForAllowedActualRequest(t *testing.T) { t.Fatalf("unexpected expose headers: %q", got) } } + +func TestCORSWildcardWithCredentialsEchoesAnyOrigin(t *testing.T) { + called := false + handler := WithCORS(CORSConfig{ + Enabled: true, + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "OPTIONS"}, + AllowedHeaders: []string{"Authorization", "Content-Type", "X-App-Code"}, + AllowCredentials: true, + }, http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + called = true + })) + + request := httptest.NewRequest(http.MethodOptions, "/api/v1/users/me/appearance", nil) + request.Header.Set("Origin", "http://127.0.0.1:8081") + request.Header.Set("Access-Control-Request-Method", "GET") + request.Header.Set("Access-Control-Request-Headers", "authorization,x-app-code") + recorder := httptest.NewRecorder() + + handler.ServeHTTP(recorder, request) + + if called { + t.Fatal("wildcard preflight must not reach business handler") + } + if recorder.Code != http.StatusNoContent { + t.Fatalf("unexpected wildcard preflight status: %d", recorder.Code) + } + if got := recorder.Header().Get("Access-Control-Allow-Origin"); got != "http://127.0.0.1:8081" { + t.Fatalf("wildcard with credentials must echo request origin, got %q", got) + } + if got := recorder.Header().Get("Access-Control-Allow-Credentials"); got != "true" { + t.Fatalf("unexpected allow credentials: %q", got) + } + if got := recorder.Header().Get("Access-Control-Expose-Headers"); !strings.Contains(got, "X-Hyapp-Access-Token") { + t.Fatalf("renewed token header must be exposed to browser javascript, got %q", got) + } +} diff --git a/services/gateway-service/internal/transport/http/game_handler_test.go b/services/gateway-service/internal/transport/http/game_handler_test.go index a32d42f9..ee8680e0 100644 --- a/services/gateway-service/internal/transport/http/game_handler_test.go +++ b/services/gateway-service/internal/transport/http/game_handler_test.go @@ -3,10 +3,15 @@ package http import ( "bytes" "context" + "encoding/base64" "encoding/json" + "io" "net/http" "net/http/httptest" + "net/url" + "strconv" "testing" + "time" gamev1 "hyapp.local/api/proto/game/v1" "hyapp/services/gateway-service/internal/auth" @@ -154,6 +159,34 @@ func TestLaunchGameForwardsDisplayUserID(t *testing.T) { } } +func TestDiceMatchRenewsAccessTokenWhenTokenNearExpiry(t *testing.T) { + userClient := &fakeUserAuthClient{} + gameClient := &fakeGatewayGameClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, userClient, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/games/dice/match", bytes.NewReader([]byte(`{"game_id":"dice","stake_coin":100}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithExpiresAt(t, "secret", 42, true, time.Now().Add(2*time.Minute))) + request.Header.Set("X-Device-ID", "dev-dice") + request.Header.Set("Content-Type", "application/json") + 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 userClient.lastAppHeartbeat == nil || userClient.lastAppHeartbeat.GetUserId() != 42 || userClient.lastAppHeartbeat.GetSessionId() != "sess-test" { + t.Fatalf("dice match must renew with authenticated user/session: %+v", userClient.lastAppHeartbeat) + } + if userClient.lastAppHeartbeat.GetMeta().GetDeviceId() != "dev-dice" { + t.Fatalf("dice match must forward device meta during token renew: %+v", userClient.lastAppHeartbeat.GetMeta()) + } + if recorder.Header().Get("X-Hyapp-Access-Token") != "access-heartbeat" || recorder.Header().Get("X-Hyapp-Expires-In") != "1800" { + t.Fatalf("dice match must expose renewed token headers: %+v", recorder.Header()) + } +} + func TestGameCallbackIsRawPublicResponse(t *testing.T) { gameClient := &fakeGatewayGameClient{callbackResp: &gamev1.CallbackResponse{RawBody: []byte(`{"code":0}`), ContentType: "application/json"}} handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) @@ -173,6 +206,86 @@ func TestGameCallbackIsRawPublicResponse(t *testing.T) { } } +func TestHotgameCompatFallbacksToHyappReyouCallback(t *testing.T) { + body := `{"gameId":"101","uid":"420001","token":"hyapp-token","sign":"s"}` + gameClient := &fakeGatewayGameClient{callbackResp: &gamev1.CallbackResponse{RawBody: []byte(`{"errorCode":0}`), ContentType: "application/json"}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/getUserInfo?nonce=1", bytes.NewReader([]byte(body))) + request.Header.Set("X-App-Code", "lalu") + request.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK || recorder.Body.String() != `{"errorCode":0}` { + t.Fatalf("hotgame hyapp callback response mismatch: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + if gameClient.lastCallback == nil || gameClient.lastCallback.GetPlatformCode() != "reyou" || gameClient.lastCallback.GetOperation() != "getUserInfo" || string(gameClient.lastCallback.GetRawBody()) != body || gameClient.lastCallback.GetQuery()["nonce"] != "1" { + t.Fatalf("hotgame hyapp callback request mismatch: %+v", gameClient.lastCallback) + } +} + +func TestHotgameCompatProxiesChatapp3TokenWithoutGameCallback(t *testing.T) { + token := chatapp3HotgameToken("v1", 1001, "LIKEI", 4102444800000, 1700000000000) + body := `{"orderId":"o1","gameId":"13","roundId":"r1","uid":"1001","coin":12,"type":1,"token":"` + token + `","sign":"s"}` + var gotHost string + var gotPath string + var gotQuery string + var gotBody string + var gotContentType string + oldTransport := http.DefaultTransport + http.DefaultTransport = roundTripFunc(func(request *http.Request) (*http.Response, error) { + gotHost = request.URL.Host + gotPath = request.URL.Path + gotQuery = request.URL.RawQuery + gotContentType = request.Header.Get("Content-Type") + raw, _ := io.ReadAll(request.Body) + gotBody = string(raw) + header := http.Header{} + header.Set("Content-Type", "application/json") + return &http.Response{ + StatusCode: http.StatusOK, + Header: header, + Body: io.NopCloser(bytes.NewReader([]byte(`{"errorCode":0,"data":{"source":"chatapp3"}}`))), + Request: request, + }, nil + }) + defer func() { http.DefaultTransport = oldTransport }() + gameClient := &fakeGatewayGameClient{callbackResp: &gamev1.CallbackResponse{RawBody: []byte(`{"errorCode":0}`), ContentType: "application/json"}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/updateBalance?nonce=1", bytes.NewReader([]byte(body))) + request.Header.Set("X-App-Code", "lalu") + request.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK || recorder.Body.String() != `{"errorCode":0,"data":{"source":"chatapp3"}}` { + t.Fatalf("hotgame chatapp3 proxy response mismatch: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + if gotHost != "jvapi.haiyihy.com" || gotPath != "/go/updateBalance" || gotQuery != "nonce=1" || gotBody != body || gotContentType != "application/json" { + t.Fatalf("hotgame chatapp3 proxy request mismatch: host=%q path=%q query=%q content_type=%q body=%s", gotHost, gotPath, gotQuery, gotContentType, gotBody) + } + if gameClient.lastCallback != nil { + t.Fatalf("chatapp3 token must not enter hyapp game callback: %+v", gameClient.lastCallback) + } +} + +func chatapp3HotgameToken(version string, userID int64, sysOrigin string, expireMs int64, releaseMs int64) string { + payload := url.QueryEscape(version + ":" + strconv.FormatInt(userID, 10) + ":" + sysOrigin + ":" + strconv.FormatInt(expireMs, 10) + ":" + strconv.FormatInt(releaseMs, 10)) + return "sign." + base64.StdEncoding.EncodeToString([]byte(payload)) +} + +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (fn roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) { + return fn(request) +} + type fakeGatewayGameClient struct { listResp *gamev1.ListGamesResponse recentResp *gamev1.ListGamesResponse @@ -218,6 +331,34 @@ func (f *fakeGatewayGameClient) LaunchGame(_ context.Context, req *gamev1.Launch return &gamev1.LaunchGameResponse{}, nil } +func (f *fakeGatewayGameClient) GetDiceConfig(context.Context, *gamev1.GetDiceConfigRequest) (*gamev1.DiceConfigResponse, error) { + return &gamev1.DiceConfigResponse{}, nil +} + +func (f *fakeGatewayGameClient) MatchDice(context.Context, *gamev1.MatchDiceRequest) (*gamev1.DiceMatchResponse, error) { + return &gamev1.DiceMatchResponse{}, nil +} + +func (f *fakeGatewayGameClient) CreateDiceMatch(context.Context, *gamev1.CreateDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return &gamev1.DiceMatchResponse{}, nil +} + +func (f *fakeGatewayGameClient) JoinDiceMatch(context.Context, *gamev1.JoinDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return &gamev1.DiceMatchResponse{}, nil +} + +func (f *fakeGatewayGameClient) GetDiceMatch(context.Context, *gamev1.GetDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return &gamev1.DiceMatchResponse{}, nil +} + +func (f *fakeGatewayGameClient) RollDiceMatch(context.Context, *gamev1.RollDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return &gamev1.DiceMatchResponse{}, nil +} + +func (f *fakeGatewayGameClient) CancelDiceMatch(context.Context, *gamev1.CancelDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + return &gamev1.DiceMatchResponse{}, nil +} + func (f *fakeGatewayGameClient) HandleCallback(_ context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { f.lastCallback = req if f.callbackResp != nil { diff --git a/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go b/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go new file mode 100644 index 00000000..5551542b --- /dev/null +++ b/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go @@ -0,0 +1,109 @@ +package gameapi + +import ( + "net/http" + + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +const diceAvatarFrameResourceType = "avatar_frame" + +// enrichDiceMatchParticipants 聚合用户基础资料和佩戴头像框;展示资料失败不影响游戏事实返回。 +func (h *Handler) enrichDiceMatchParticipants(request *http.Request, match *diceMatchItemData) { + if h == nil || match == nil || len(match.Participants) == 0 { + return + } + userIDs := diceParticipantUserIDs(match.Participants) + users := h.diceUsersByID(request, userIDs) + frames := h.diceAvatarFramesByID(request, userIDs) + for index := range match.Participants { + userID := match.Participants[index].UserIDNumber + if user, ok := users[userID]; ok { + match.Participants[index].User = user + } else { + match.Participants[index].User = diceUserData{UserID: int64String(userID)} + } + if frame, ok := frames[userID]; ok { + match.Participants[index].AvatarFrame = frame + } + } +} + +func diceParticipantUserIDs(participants []diceParticipantItemData) []int64 { + seen := make(map[int64]struct{}, len(participants)) + out := make([]int64, 0, len(participants)) + for _, participant := range participants { + if participant.UserIDNumber <= 0 { + continue + } + if _, ok := seen[participant.UserIDNumber]; ok { + continue + } + seen[participant.UserIDNumber] = struct{}{} + out = append(out, participant.UserIDNumber) + } + return out +} + +func (h *Handler) diceUsersByID(request *http.Request, userIDs []int64) map[int64]diceUserData { + out := make(map[int64]diceUserData, len(userIDs)) + if h.userProfileClient == nil || len(userIDs) == 0 { + return out + } + resp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{ + Meta: httpkit.UserMeta(request, ""), + UserIds: userIDs, + }) + if err != nil { + return out + } + for userID, user := range resp.GetUsers() { + out[userID] = diceUserData{ + UserID: int64String(user.GetUserId()), + DisplayUserID: user.GetDisplayUserId(), + Nickname: user.GetUsername(), + Avatar: user.GetAvatar(), + } + } + return out +} + +func (h *Handler) diceAvatarFramesByID(request *http.Request, userIDs []int64) map[int64]map[string]any { + out := make(map[int64]map[string]any, len(userIDs)) + if h.walletClient == nil || len(userIDs) == 0 { + return out + } + resp, err := h.walletClient.BatchGetUserEquippedResources(request.Context(), &walletv1.BatchGetUserEquippedResourcesRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + UserIds: userIDs, + ResourceTypes: []string{diceAvatarFrameResourceType}, + }) + if err != nil { + return out + } + for _, item := range resp.GetUsers() { + for _, entitlement := range item.GetResources() { + resource := entitlement.GetResource() + if resource.GetResourceType() != diceAvatarFrameResourceType { + continue + } + out[item.GetUserId()] = map[string]any{ + "resource_id": int64String(resource.GetResourceId()), + "resource_code": resource.GetResourceCode(), + "name": resource.GetName(), + "asset_url": resource.GetAssetUrl(), + "preview_url": resource.GetPreviewUrl(), + "animation_url": resource.GetAnimationUrl(), + "metadata_json": resource.GetMetadataJson(), + "entitlement_id": entitlement.GetEntitlementId(), + "expires_at_ms": entitlement.GetExpiresAtMs(), + } + break + } + } + return out +} diff --git a/services/gateway-service/internal/transport/http/gameapi/game_handler.go b/services/gateway-service/internal/transport/http/gameapi/game_handler.go index c2937151..fb88afb2 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_handler.go @@ -2,18 +2,28 @@ package gameapi import ( "io" + "log/slog" "net/http" + "strconv" "strings" "time" gamev1 "hyapp.local/api/proto/game/v1" userv1 "hyapp.local/api/proto/user/v1" "hyapp/pkg/appcode" + "hyapp/pkg/logx" "hyapp/pkg/xerr" "hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/transport/http/httpkit" ) +const ( + diceAccessTokenRenewBefore = 10 * time.Minute + hyappAccessTokenHeader = "X-Hyapp-Access-Token" + hyappTokenTypeHeader = "X-Hyapp-Token-Type" + hyappExpiresInHeader = "X-Hyapp-Expires-In" +) + func (h *Handler) listGames(writer http.ResponseWriter, request *http.Request) { if h.gameClient == nil || h.userProfileClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -124,20 +134,246 @@ func (h *Handler) launchGame(writer http.ResponseWriter, request *http.Request) httpkit.Write(writer, request, gameLaunchDataFromProto(resp), err) } -func (h *Handler) handleGameCallback(writer http.ResponseWriter, request *http.Request) { +func (h *Handler) getDiceConfig(writer http.ResponseWriter, request *http.Request) { if h.gameClient == nil { - http.Error(writer, "upstream service error", http.StatusBadGateway) + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } + gameID := strings.TrimSpace(request.URL.Query().Get("game_id")) + if gameID == "" { + gameID = "dice" + } + // 配置接口只返回当前 App 的可下注档位和费率,不接受客户端传 app_code。 + resp, err := h.gameClient.GetDiceConfig(request.Context(), &gamev1.GetDiceConfigRequest{ + Meta: gameMeta(request), + GameId: gameID, + }) + if err == nil { + h.renewDiceAccessTokenIfNeeded(writer, request) + } + httpkit.Write(writer, request, diceConfigDataFromProto(resp), err) +} + +func (h *Handler) matchDice(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body struct { + GameID string `json:"game_id"` + RoomID string `json:"room_id"` + StakeCoin int64 `json:"stake_coin"` + } + if !httpkit.Decode(writer, request, &body) { + return + } + if strings.TrimSpace(body.GameID) == "" { + body.GameID = "dice" + } + if body.StakeCoin <= 0 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + userID := auth.UserIDFromContext(request.Context()) + userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + resp, err := h.gameClient.MatchDice(request.Context(), &gamev1.MatchDiceRequest{ + Meta: gameMeta(request), + UserId: userID, + GameId: strings.TrimSpace(body.GameID), + RoomId: strings.TrimSpace(body.RoomID), + RegionId: userResp.GetUser().GetRegionId(), + StakeCoin: body.StakeCoin, + }) + h.writeDiceMatch(writer, request, resp, err) +} + +func (h *Handler) createDiceMatch(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + // gateway 只接受 H5 的展示输入;user_id、region_id、app_code 和 request_id 都由网关上下文补齐。 + var body struct { + GameID string `json:"game_id"` + RoomID string `json:"room_id"` + StakeCoin int64 `json:"stake_coin"` + MinPlayers int32 `json:"min_players"` + MaxPlayers int32 `json:"max_players"` + } + if !httpkit.Decode(writer, request, &body) { + return + } + if strings.TrimSpace(body.GameID) == "" || body.StakeCoin <= 0 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + userID := auth.UserIDFromContext(request.Context()) + userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + // region_id 来自 user-service 当前用户资料,不信任客户端 body,避免跨区域创建可结算局。 + resp, err := h.gameClient.CreateDiceMatch(request.Context(), &gamev1.CreateDiceMatchRequest{ + Meta: gameMeta(request), + UserId: userID, + GameId: strings.TrimSpace(body.GameID), + RoomId: strings.TrimSpace(body.RoomID), + RegionId: userResp.GetUser().GetRegionId(), + StakeCoin: body.StakeCoin, + MinPlayers: body.MinPlayers, + MaxPlayers: body.MaxPlayers, + }) + h.writeDiceMatch(writer, request, resp, err) +} + +func (h *Handler) joinDiceMatch(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + matchID := strings.TrimSpace(request.PathValue("match_id")) + if matchID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // join 不接受 body 里的 user_id 或 stake_coin;参与者身份来自 JWT,押注金额来自 match 当前事实。 + resp, err := h.gameClient.JoinDiceMatch(request.Context(), &gamev1.JoinDiceMatchRequest{ + Meta: gameMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + MatchId: matchID, + }) + h.writeDiceMatch(writer, request, resp, err) +} + +func (h *Handler) getDiceMatch(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + matchID := strings.TrimSpace(request.PathValue("match_id")) + if matchID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // 查询接口只是透传当前局事实,前端轮询或 IM 事件后的刷新都可以复用这条路径。 + resp, err := h.gameClient.GetDiceMatch(request.Context(), &gamev1.GetDiceMatchRequest{ + Meta: gameMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + MatchId: matchID, + }) + h.writeDiceMatch(writer, request, resp, err) +} + +func (h *Handler) rollDiceMatch(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + matchID := strings.TrimSpace(request.PathValue("match_id")) + if matchID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // roll 不接收骰子点数、结果或派奖金额;这些全部由 game-service 服务端随机和结算。 + resp, err := h.gameClient.RollDiceMatch(request.Context(), &gamev1.RollDiceMatchRequest{ + Meta: gameMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + MatchId: matchID, + }) + h.writeDiceMatch(writer, request, resp, err) +} + +func (h *Handler) cancelDiceMatch(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + matchID := strings.TrimSpace(request.PathValue("match_id")) + if matchID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.gameClient.CancelDiceMatch(request.Context(), &gamev1.CancelDiceMatchRequest{ + Meta: gameMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + MatchId: matchID, + }) + h.writeDiceMatch(writer, request, resp, err) +} + +func (h *Handler) writeDiceMatch(writer http.ResponseWriter, request *http.Request, resp *gamev1.DiceMatchResponse, err error) { + if err != nil { + httpkit.Write(writer, request, diceMatchData{}, err) + return + } + data := diceMatchDataFromProto(resp) + h.enrichDiceMatchParticipants(request, &data.Match) + h.renewDiceAccessTokenIfNeeded(writer, request) + httpkit.Write(writer, request, data, nil) +} + +func (h *Handler) renewDiceAccessTokenIfNeeded(writer http.ResponseWriter, request *http.Request) { + if h.userAuthClient == nil { + return + } + expiresAtMS := auth.AccessTokenExpiresAtMSFromContext(request.Context()) + if expiresAtMS <= 0 || time.Until(time.UnixMilli(expiresAtMS)) > diceAccessTokenRenewBefore { + // 旧测试 token 或距离过期还很久的 token 不做续签,避免每秒轮询把 user-service 和登录审计打满。 + return + } + sessionID := auth.SessionIDFromContext(request.Context()) + if strings.TrimSpace(sessionID) == "" { + return + } + resp, err := h.userAuthClient.AppHeartbeat(request.Context(), &userv1.AppHeartbeatRequest{ + Meta: httpkit.UserMeta(request, httpkit.FirstHeader(request, "X-Device-ID", "X-HY-Device-ID")), + UserId: auth.UserIDFromContext(request.Context()), + SessionId: sessionID, + }) + if err != nil { + // 游戏事实响应已经成功,续签失败只记录日志;客户端下一次仍可走 refresh token 兜底。 + logx.Warn(request.Context(), "dice_access_token_renew_failed", slog.String("component", "gateway_game"), slog.String("session_id", sessionID), slog.String("error", err.Error())) + return + } + token := resp.GetToken() + accessToken := strings.TrimSpace(token.GetAccessToken()) + if accessToken == "" { + return + } + writer.Header().Set(hyappAccessTokenHeader, accessToken) + writer.Header().Set(hyappTokenTypeHeader, strings.TrimSpace(token.GetTokenType())) + writer.Header().Set(hyappExpiresInHeader, strconv.FormatInt(token.GetExpiresInSec(), 10)) +} + +func (h *Handler) handleGameCallback(writer http.ResponseWriter, request *http.Request) { raw, err := io.ReadAll(request.Body) if err != nil { http.Error(writer, "invalid request body", http.StatusBadRequest) return } + h.handleGameCallbackRaw(writer, request, raw, strings.TrimSpace(request.PathValue("platform_code")), strings.TrimSpace(request.PathValue("operation"))) +} + +func (h *Handler) handleGameCallbackRaw(writer http.ResponseWriter, request *http.Request, raw []byte, platformCode string, operation string) { + if h.gameClient == nil { + http.Error(writer, "upstream service error", http.StatusBadGateway) + return + } resp, err := h.gameClient.HandleCallback(request.Context(), &gamev1.CallbackRequest{ Meta: gameMeta(request), - PlatformCode: strings.TrimSpace(request.PathValue("platform_code")), - Operation: strings.TrimSpace(request.PathValue("operation")), + PlatformCode: strings.TrimSpace(platformCode), + Operation: strings.TrimSpace(operation), RawBody: raw, Headers: flattenedHeaders(request), Query: flattenedQuery(request), diff --git a/services/gateway-service/internal/transport/http/gameapi/game_view.go b/services/gateway-service/internal/transport/http/gameapi/game_view.go index bcaba428..aa13db70 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_view.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_view.go @@ -1,6 +1,10 @@ package gameapi -import gamev1 "hyapp.local/api/proto/game/v1" +import ( + "fmt" + + gamev1 "hyapp.local/api/proto/game/v1" +) type gameListData struct { Games []gameItemData `json:"games"` @@ -45,6 +49,90 @@ type gameBridgeScriptData struct { ServerTimeMS int64 `json:"server_time_ms"` } +type diceMatchData struct { + Match diceMatchItemData `json:"match"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type diceConfigData struct { + Config diceConfigItemData `json:"config"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type diceConfigItemData struct { + AppCode string `json:"app_code"` + GameID string `json:"game_id"` + Status string `json:"status"` + StakeOptions []diceStakeOptionData `json:"stake_options"` + FeeBPS int32 `json:"fee_bps"` + PoolBPS int32 `json:"pool_bps"` + MinPlayers int32 `json:"min_players"` + MaxPlayers int32 `json:"max_players"` + RobotEnabled bool `json:"robot_enabled"` + RobotMatchWaitMS int64 `json:"robot_match_wait_ms"` + PoolBalanceCoin int64 `json:"pool_balance_coin"` +} + +type diceStakeOptionData struct { + StakeCoin int64 `json:"stake_coin"` + Enabled bool `json:"enabled"` + SortOrder int32 `json:"sort_order"` +} + +type diceMatchItemData struct { + AppCode string `json:"app_code"` + MatchID string `json:"match_id"` + GameID string `json:"game_id"` + RoomID string `json:"room_id"` + RegionID int64 `json:"region_id"` + MinPlayers int32 `json:"min_players"` + MaxPlayers int32 `json:"max_players"` + CurrentPlayers int32 `json:"current_players"` + StakeCoin int64 `json:"stake_coin"` + RoundNo int32 `json:"round_no"` + Status string `json:"status"` + Result string `json:"result"` + Participants []diceParticipantItemData `json:"participants"` + JoinDeadlineMS int64 `json:"join_deadline_ms"` + ReadyAtMS int64 `json:"ready_at_ms"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` + SettledAtMS int64 `json:"settled_at_ms"` + CanceledAtMS int64 `json:"canceled_at_ms"` + Phase string `json:"phase"` + PhaseDeadlineMS int64 `json:"phase_deadline_ms"` + FeeBPS int32 `json:"fee_bps"` + PoolBPS int32 `json:"pool_bps"` + MatchMode string `json:"match_mode"` + ForcedResult string `json:"forced_result"` + PoolDeltaCoin int64 `json:"pool_delta_coin"` +} + +type diceParticipantItemData struct { + UserID string `json:"user_id"` + UserIDNumber int64 `json:"user_id_number"` + SeatNo int32 `json:"seat_no"` + Status string `json:"status"` + StakeCoin int64 `json:"stake_coin"` + DicePoints []int32 `json:"dice_points"` + Result string `json:"result"` + PayoutCoin int64 `json:"payout_coin"` + BalanceAfter int64 `json:"balance_after"` + JoinedAtMS int64 `json:"joined_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` + ParticipantType string `json:"participant_type"` + IsRobot bool `json:"is_robot"` + User diceUserData `json:"user"` + AvatarFrame map[string]any `json:"avatar_frame"` +} + +type diceUserData struct { + UserID string `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` +} + func gameListDataFromProto(resp *gamev1.ListGamesResponse) gameListData { if resp == nil { return gameListData{} @@ -87,6 +175,107 @@ func gameLaunchDataFromProto(resp *gamev1.LaunchGameResponse) gameLaunchData { } } +func diceConfigDataFromProto(resp *gamev1.DiceConfigResponse) diceConfigData { + if resp == nil { + return diceConfigData{} + } + config := resp.GetConfig() + options := make([]diceStakeOptionData, 0, len(config.GetStakeOptions())) + for _, option := range config.GetStakeOptions() { + options = append(options, diceStakeOptionData{ + StakeCoin: option.GetStakeCoin(), + Enabled: option.GetEnabled(), + SortOrder: option.GetSortOrder(), + }) + } + return diceConfigData{ + Config: diceConfigItemData{ + AppCode: config.GetAppCode(), + GameID: config.GetGameId(), + Status: config.GetStatus(), + StakeOptions: options, + FeeBPS: config.GetFeeBps(), + PoolBPS: config.GetPoolBps(), + MinPlayers: config.GetMinPlayers(), + MaxPlayers: config.GetMaxPlayers(), + RobotEnabled: config.GetRobotEnabled(), + RobotMatchWaitMS: config.GetRobotMatchWaitMs(), + PoolBalanceCoin: config.GetPoolBalanceCoin(), + }, + ServerTimeMS: resp.GetServerTimeMs(), + } +} + +func diceMatchDataFromProto(resp *gamev1.DiceMatchResponse) diceMatchData { + if resp == nil { + return diceMatchData{} + } + return diceMatchData{ + Match: diceMatchItemDataFromProto(resp.GetMatch()), + ServerTimeMS: resp.GetServerTimeMs(), + } +} + +func diceMatchItemDataFromProto(match *gamev1.DiceMatch) diceMatchItemData { + if match == nil { + return diceMatchItemData{} + } + participants := make([]diceParticipantItemData, 0, len(match.GetParticipants())) + for _, participant := range match.GetParticipants() { + participants = append(participants, diceParticipantItemDataFromProto(participant)) + } + return diceMatchItemData{ + AppCode: match.GetAppCode(), + MatchID: match.GetMatchId(), + GameID: match.GetGameId(), + RoomID: match.GetRoomId(), + RegionID: match.GetRegionId(), + MinPlayers: match.GetMinPlayers(), + MaxPlayers: match.GetMaxPlayers(), + CurrentPlayers: match.GetCurrentPlayers(), + StakeCoin: match.GetStakeCoin(), + RoundNo: match.GetRoundNo(), + Status: match.GetStatus(), + Result: match.GetResult(), + Participants: participants, + JoinDeadlineMS: match.GetJoinDeadlineMs(), + ReadyAtMS: match.GetReadyAtMs(), + CreatedAtMS: match.GetCreatedAtMs(), + UpdatedAtMS: match.GetUpdatedAtMs(), + SettledAtMS: match.GetSettledAtMs(), + CanceledAtMS: match.GetCanceledAtMs(), + Phase: match.GetPhase(), + PhaseDeadlineMS: match.GetPhaseDeadlineMs(), + FeeBPS: match.GetFeeBps(), + PoolBPS: match.GetPoolBps(), + MatchMode: match.GetMatchMode(), + ForcedResult: match.GetForcedResult(), + PoolDeltaCoin: match.GetPoolDeltaCoin(), + } +} + +func diceParticipantItemDataFromProto(participant *gamev1.DiceParticipant) diceParticipantItemData { + if participant == nil { + return diceParticipantItemData{} + } + return diceParticipantItemData{ + UserID: int64String(participant.GetUserId()), + UserIDNumber: participant.GetUserId(), + SeatNo: participant.GetSeatNo(), + Status: participant.GetStatus(), + StakeCoin: participant.GetStakeCoin(), + DicePoints: participant.GetDicePoints(), + Result: participant.GetResult(), + PayoutCoin: participant.GetPayoutCoin(), + BalanceAfter: participant.GetBalanceAfter(), + JoinedAtMS: participant.GetJoinedAtMs(), + UpdatedAtMS: participant.GetUpdatedAtMs(), + ParticipantType: participant.GetParticipantType(), + IsRobot: participant.GetIsRobot(), + AvatarFrame: map[string]any{}, + } +} + func gameBridgeScriptDataFromProto(resp *gamev1.GetBridgeScriptResponse) gameBridgeScriptData { if resp == nil { return gameBridgeScriptData{} @@ -102,3 +291,7 @@ func gameBridgeScriptDataFromProto(resp *gamev1.GetBridgeScriptResponse) gameBri ServerTimeMS: resp.GetServerTimeMs(), } } + +func int64String(value int64) string { + return fmt.Sprintf("%d", value) +} diff --git a/services/gateway-service/internal/transport/http/gameapi/handler.go b/services/gateway-service/internal/transport/http/gameapi/handler.go index 05af7d43..42b35f2b 100644 --- a/services/gateway-service/internal/transport/http/gameapi/handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/handler.go @@ -11,28 +11,46 @@ import ( // gateway only translates app/user context into game-service RPCs; game session state stays in game-service. type Handler struct { gameClient client.GameClient + userAuthClient client.UserAuthClient userProfileClient client.UserProfileClient + walletClient client.WalletClient + // 热游固定回调地址先进入 hyapp gateway;chatapp3 老 token 需要原样转给 chatapp3,hyapp token 继续落到 game-service。 + hotgameCompatHTTPClient *http.Client } type Config struct { GameClient client.GameClient + UserAuthClient client.UserAuthClient UserProfileClient client.UserProfileClient + WalletClient client.WalletClient } func New(config Config) *Handler { return &Handler{ - gameClient: config.GameClient, - userProfileClient: config.UserProfileClient, + gameClient: config.GameClient, + userAuthClient: config.UserAuthClient, + userProfileClient: config.UserProfileClient, + walletClient: config.WalletClient, + hotgameCompatHTTPClient: hotgameCompatHTTPClient(), } } func (h *Handler) Handlers() httproutes.GameHandlers { return httproutes.GameHandlers{ - ListGames: h.listGames, - ListRecentGames: h.listRecentGames, - GetBridgeScript: h.getBridgeScript, - LaunchGame: h.launchGame, - HandleCallback: h.handleGameCallback, + ListGames: h.listGames, + ListRecentGames: h.listRecentGames, + GetBridgeScript: h.getBridgeScript, + LaunchGame: h.launchGame, + GetDiceConfig: h.getDiceConfig, + MatchDice: h.matchDice, + CreateDiceMatch: h.createDiceMatch, + JoinDiceMatch: h.joinDiceMatch, + GetDiceMatch: h.getDiceMatch, + RollDiceMatch: h.rollDiceMatch, + CancelDiceMatch: h.cancelDiceMatch, + HandleCallback: h.handleGameCallback, + HandleHotgameGetUserInfo: h.handleHotgameGetUserInfo, + HandleHotgameUpdateBalance: h.handleHotgameUpdateBalance, } } @@ -52,6 +70,42 @@ func (h *Handler) LaunchGame(writer http.ResponseWriter, request *http.Request) h.launchGame(writer, request) } +func (h *Handler) GetDiceConfig(writer http.ResponseWriter, request *http.Request) { + h.getDiceConfig(writer, request) +} + +func (h *Handler) MatchDice(writer http.ResponseWriter, request *http.Request) { + h.matchDice(writer, request) +} + +func (h *Handler) CreateDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.createDiceMatch(writer, request) +} + +func (h *Handler) JoinDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.joinDiceMatch(writer, request) +} + +func (h *Handler) GetDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.getDiceMatch(writer, request) +} + +func (h *Handler) RollDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.rollDiceMatch(writer, request) +} + +func (h *Handler) CancelDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.cancelDiceMatch(writer, request) +} + func (h *Handler) HandleCallback(writer http.ResponseWriter, request *http.Request) { h.handleGameCallback(writer, request) } + +func (h *Handler) HandleHotgameGetUserInfo(writer http.ResponseWriter, request *http.Request) { + h.handleHotgameGetUserInfo(writer, request) +} + +func (h *Handler) HandleHotgameUpdateBalance(writer http.ResponseWriter, request *http.Request) { + h.handleHotgameUpdateBalance(writer, request) +} diff --git a/services/gateway-service/internal/transport/http/gameapi/hotgame_compat_handler.go b/services/gateway-service/internal/transport/http/gameapi/hotgame_compat_handler.go new file mode 100644 index 00000000..830833ca --- /dev/null +++ b/services/gateway-service/internal/transport/http/gameapi/hotgame_compat_handler.go @@ -0,0 +1,243 @@ +package gameapi + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "io" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +const ( + hotgameCompatPlatformCode = "reyou" + hotgameCompatProxyDefaultBaseURL = "https://jvapi.haiyihy.com/go" + hotgameCompatProxyDefaultTimeout = 10 * time.Second + hotgameCompatGetUserInfoOperation = "getUserInfo" + hotgameCompatUpdateBalanceOperation = "updateBalance" +) + +func (h *Handler) handleHotgameGetUserInfo(writer http.ResponseWriter, request *http.Request) { + h.handleHotgameCompatCallback(writer, request, hotgameCompatGetUserInfoOperation) +} + +func (h *Handler) handleHotgameUpdateBalance(writer http.ResponseWriter, request *http.Request) { + h.handleHotgameCompatCallback(writer, request, hotgameCompatUpdateBalanceOperation) +} + +func (h *Handler) handleHotgameCompatCallback(writer http.ResponseWriter, request *http.Request, operation string) { + raw, err := io.ReadAll(request.Body) + if err != nil { + http.Error(writer, "invalid request body", http.StatusBadRequest) + return + } + // 热游固定回调地址无法按域名区分 hyapp 和 chatapp3;这里用 token 结构做最小路由键。 + // chatapp3 的用户 token 是 sign.base64(version:userId:sysOrigin:expire:release),hyapp 当前热游 token 是 JWT 或本服务 session。 + if isChatapp3HotgameToken(hotgameCompatCallbackToken(raw)) { + h.proxyChatapp3HotgameCallback(writer, request, raw, operation) + return + } + // 非 chatapp3 token 全部继续走 hyapp 自己的 reyou 平台配置,避免 hyapp 自有热游游戏误转到 chatapp3。 + h.handleGameCallbackRaw(writer, request, raw, hotgameCompatPlatformCode, operation) +} + +func (h *Handler) proxyChatapp3HotgameCallback(writer http.ResponseWriter, request *http.Request, raw []byte, operation string) { + target, err := hotgameCompatProxyURL(operation, request.URL.RawQuery) + if err != nil { + http.Error(writer, "proxy config error", http.StatusBadGateway) + return + } + proxyReq, err := http.NewRequestWithContext(request.Context(), http.MethodPost, target, bytes.NewReader(raw)) + if err != nil { + http.Error(writer, "proxy request error", http.StatusBadGateway) + return + } + copyHotgameProxyRequestHeaders(proxyReq, request) + appendHotgameForwardedHeaders(proxyReq, request) + + resp, err := h.hotgameCompatHTTPClient.Do(proxyReq) + if err != nil { + http.Error(writer, "proxy upstream error", http.StatusBadGateway) + return + } + defer resp.Body.Close() + copyHotgameProxyResponseHeaders(writer.Header(), resp.Header) + writer.WriteHeader(resp.StatusCode) + _, _ = io.Copy(writer, resp.Body) +} + +func hotgameCompatHTTPClient() *http.Client { + return &http.Client{Timeout: hotgameCompatProxyDefaultTimeout} +} + +func hotgameCompatCallbackToken(raw []byte) string { + var payload map[string]json.RawMessage + if err := json.Unmarshal(raw, &payload); err != nil { + return "" + } + return hotgameCompatJSONText(payload["token"]) +} + +func hotgameCompatJSONText(raw json.RawMessage) string { + text := strings.TrimSpace(string(raw)) + if text == "" || text == "null" { + return "" + } + var decoded string + if err := json.Unmarshal(raw, &decoded); err == nil { + return strings.TrimSpace(decoded) + } + return text +} + +func isChatapp3HotgameToken(token string) bool { + token = strings.TrimSpace(token) + if chatapp3HotgameTokenCandidateMatches(token) { + return true + } + if decoded, err := url.QueryUnescape(token); err == nil && strings.TrimSpace(decoded) != token { + return chatapp3HotgameTokenCandidateMatches(decoded) + } + return false +} + +func chatapp3HotgameTokenCandidateMatches(token string) bool { + token = normalizeChatapp3HotgameTokenCandidate(token) + parts := strings.SplitN(token, ".", 2) + if len(parts) != 2 || strings.TrimSpace(parts[0]) == "" || strings.TrimSpace(parts[1]) == "" { + return false + } + decoded, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return false + } + payload := strings.TrimSpace(string(decoded)) + if unescaped, err := url.QueryUnescape(payload); err == nil { + payload = strings.TrimSpace(unescaped) + } + fields := strings.Split(payload, ":") + if len(fields) != 5 || strings.TrimSpace(fields[0]) == "" || strings.TrimSpace(fields[2]) == "" { + return false + } + userID, userErr := strconv.ParseInt(strings.TrimSpace(fields[1]), 10, 64) + _, expireErr := strconv.ParseInt(strings.TrimSpace(fields[3]), 10, 64) + _, releaseErr := strconv.ParseInt(strings.TrimSpace(fields[4]), 10, 64) + // 这里只判定归属,不判定过期;过期 token 仍必须转给 chatapp3,让原项目按自己的错误码回复热游。 + return userID > 0 && userErr == nil && expireErr == nil && releaseErr == nil +} + +func normalizeChatapp3HotgameTokenCandidate(token string) string { + token = strings.TrimSpace(token) + if strings.HasPrefix(strings.ToLower(token), "bearer ") { + token = strings.TrimSpace(token[len("bearer "):]) + } + parts := strings.SplitN(token, ".", 2) + if len(parts) != 2 || parts[1] == "" { + return token + } + switch len(parts[1]) % 4 { + case 2: + parts[1] += "==" + case 3: + parts[1] += "=" + } + return strings.TrimSpace(parts[0]) + "." + strings.TrimSpace(parts[1]) +} + +func hotgameCompatProxyURL(operation string, rawQuery string) (string, error) { + parsed, err := url.Parse(hotgameCompatProxyDefaultBaseURL) + if err != nil { + return "", err + } + parsed.Path = singleJoiningSlash(parsed.Path, operation) + if strings.TrimSpace(rawQuery) != "" { + if parsed.RawQuery == "" { + parsed.RawQuery = rawQuery + } else { + parsed.RawQuery += "&" + rawQuery + } + } + return parsed.String(), nil +} + +func singleJoiningSlash(left string, right string) string { + leftSlash := strings.HasSuffix(left, "/") + rightSlash := strings.HasPrefix(right, "/") + switch { + case leftSlash && rightSlash: + return left + strings.TrimLeft(right, "/") + case !leftSlash && !rightSlash: + return left + "/" + right + default: + return left + right + } +} + +func copyHotgameProxyRequestHeaders(dst *http.Request, src *http.Request) { + for key, values := range src.Header { + if isHopByHopHeader(key) { + continue + } + for _, value := range values { + dst.Header.Add(key, value) + } + } + if dst.Header.Get("Content-Type") == "" { + dst.Header.Set("Content-Type", "application/json") + } +} + +func appendHotgameForwardedHeaders(dst *http.Request, src *http.Request) { + if host := strings.TrimSpace(src.Host); host != "" { + dst.Header.Set("X-Forwarded-Host", host) + } + if proto := strings.TrimSpace(src.Header.Get("X-Forwarded-Proto")); proto != "" { + dst.Header.Set("X-Forwarded-Proto", proto) + } else if src.TLS != nil { + dst.Header.Set("X-Forwarded-Proto", "https") + } else { + dst.Header.Set("X-Forwarded-Proto", "http") + } + if ip := requestClientIP(src); ip != "" { + if prior := strings.TrimSpace(src.Header.Get("X-Forwarded-For")); prior != "" { + dst.Header.Set("X-Forwarded-For", prior+", "+ip) + } else { + dst.Header.Set("X-Forwarded-For", ip) + } + } +} + +func requestClientIP(request *http.Request) string { + host, _, err := net.SplitHostPort(strings.TrimSpace(request.RemoteAddr)) + if err == nil { + return host + } + return strings.TrimSpace(request.RemoteAddr) +} + +func copyHotgameProxyResponseHeaders(dst http.Header, src http.Header) { + for key, values := range src { + if isHopByHopHeader(key) { + continue + } + for _, value := range values { + dst.Add(key, value) + } + } + if dst.Get("Content-Type") == "" { + dst.Set("Content-Type", "application/json") + } +} + +func isHopByHopHeader(key string) bool { + switch strings.ToLower(strings.TrimSpace(key)) { + case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", "host", "content-length": + return true + default: + return false + } +} diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index 0f444c5a..69da32c4 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -95,6 +95,8 @@ type UserHandlers struct { ChangeMyCountry http.HandlerFunc ChangeMyDisplayUserID http.HandlerFunc ApplyMyPrettyDisplayUserID http.HandlerFunc + ListAvailablePrettyDisplayIDs http.HandlerFunc + ApplyPrettyDisplayIDFromPool http.HandlerFunc ListMyProfileVisitors http.HandlerFunc ListMyFollowing http.HandlerFunc ListMyFriends http.HandlerFunc @@ -205,6 +207,12 @@ type WalletHandlers struct { GetWalletOverview http.HandlerFunc GetMyBalances http.HandlerFunc ListRechargeProducts http.HandlerFunc + GetH5RechargeContext http.HandlerFunc + ListH5RechargeOptions http.HandlerFunc + CreateH5RechargeOrder http.HandlerFunc + SubmitH5RechargeTx http.HandlerFunc + GetH5RechargeOrder http.HandlerFunc + HandleMifapayNotify http.HandlerFunc ConfirmGooglePayment http.HandlerFunc GetDiamondExchangeConfig http.HandlerFunc ListCoinTransactions http.HandlerFunc @@ -237,11 +245,20 @@ type VIPHandlers struct { } type GameHandlers struct { - ListGames http.HandlerFunc - ListRecentGames http.HandlerFunc - GetBridgeScript http.HandlerFunc - LaunchGame http.HandlerFunc - HandleCallback http.HandlerFunc + ListGames http.HandlerFunc + ListRecentGames http.HandlerFunc + GetBridgeScript http.HandlerFunc + LaunchGame http.HandlerFunc + GetDiceConfig http.HandlerFunc + MatchDice http.HandlerFunc + CreateDiceMatch http.HandlerFunc + JoinDiceMatch http.HandlerFunc + GetDiceMatch http.HandlerFunc + RollDiceMatch http.HandlerFunc + CancelDiceMatch http.HandlerFunc + HandleCallback http.HandlerFunc + HandleHotgameGetUserInfo http.HandlerFunc + HandleHotgameUpdateBalance http.HandlerFunc } type routes struct { @@ -275,6 +292,10 @@ func (r routes) public(path string, method string, next http.HandlerFunc) { r.register(path, method, r.config.PublicWrap, next) } +func (r routes) rootPublic(path string, method string, next http.HandlerFunc) { + r.registerRaw(path, method, r.config.PublicWrap, next) +} + func (r routes) auth(path string, method string, next http.HandlerFunc) { r.register(path, method, r.config.AuthWrap, next) } @@ -284,11 +305,15 @@ func (r routes) profile(path string, method string, next http.HandlerFunc) { } func (r routes) register(path string, method string, wrap Wrapper, next http.HandlerFunc) { + r.registerRaw(APIV1Prefix+path, method, wrap, next) +} + +func (r routes) registerRaw(path string, method string, wrap Wrapper, next http.HandlerFunc) { if method != "" { // method 继续走 gateway envelope,而不是使用 ServeMux 自动 405。 next = httpkit.RequireMethod(method, next) } - r.mux.Handle(APIV1Prefix+path, wrap(next)) + r.mux.Handle(path, wrap(next)) } func (r routes) registerAuthRoutes() { @@ -361,6 +386,8 @@ func (r routes) registerUserRoutes() { r.profile("/users/me/country/change", "", h.ChangeMyCountry) r.auth("/users/me/display-id/change", "", h.ChangeMyDisplayUserID) r.auth("/users/me/display-id/pretty/apply", "", h.ApplyMyPrettyDisplayUserID) + r.auth("/users/pretty-ids/available", http.MethodGet, h.ListAvailablePrettyDisplayIDs) + r.auth("/users/pretty-ids/apply", http.MethodPost, h.ApplyPrettyDisplayIDFromPool) r.profile("/users/me/visitors", http.MethodGet, h.ListMyProfileVisitors) r.profile("/users/me/following", http.MethodGet, h.ListMyFollowing) r.profile("/users/me/friends", http.MethodGet, h.ListMyFriends) @@ -477,6 +504,12 @@ func (r routes) registerWalletRoutes() { r.profile("/wallet/me/overview", http.MethodGet, h.GetWalletOverview) r.profile("/wallet/me/balances", "", h.GetMyBalances) r.profile("/wallet/recharge/products", http.MethodGet, h.ListRechargeProducts) + r.public("/recharge/h5/context", http.MethodGet, h.GetH5RechargeContext) + r.public("/recharge/h5/options", http.MethodGet, h.ListH5RechargeOptions) + r.public("/recharge/h5/orders", http.MethodPost, h.CreateH5RechargeOrder) + r.public("/recharge/h5/orders/{order_id}/tx", http.MethodPost, h.SubmitH5RechargeTx) + r.public("/recharge/h5/orders/{order_id}", http.MethodGet, h.GetH5RechargeOrder) + r.public("/payment/mifapay/notify", http.MethodPost, h.HandleMifapayNotify) r.profile("/wallet/payments/google/confirm", http.MethodPost, h.ConfirmGooglePayment) r.profile("/wallet/diamond-exchange/config", http.MethodGet, h.GetDiamondExchangeConfig) r.profile("/wallet/coin-transactions", http.MethodGet, h.ListCoinTransactions) @@ -516,5 +549,15 @@ func (r routes) registerGameRoutes() { r.profile("/games/recent", http.MethodGet, h.ListRecentGames) r.profile("/games/bridge-script", http.MethodGet, h.GetBridgeScript) r.profile("/games/{game_id}/launch", http.MethodPost, h.LaunchGame) + r.profile("/games/dice/config", http.MethodGet, h.GetDiceConfig) + r.profile("/games/dice/match", http.MethodPost, h.MatchDice) + r.profile("/games/dice/matches", http.MethodPost, h.CreateDiceMatch) + r.profile("/games/dice/matches/{match_id}/join", http.MethodPost, h.JoinDiceMatch) + r.profile("/games/dice/matches/{match_id}", http.MethodGet, h.GetDiceMatch) + r.profile("/games/dice/matches/{match_id}/roll", http.MethodPost, h.RollDiceMatch) + r.profile("/games/dice/matches/{match_id}/cancel", http.MethodPost, h.CancelDiceMatch) + // 热游后台只能配置根路径回调;这里仍复用 public wrapper,让请求日志、request_id 和 app_code 解析保持一致。 + r.rootPublic("/getUserInfo", http.MethodPost, h.HandleHotgameGetUserInfo) + r.rootPublic("/updateBalance", http.MethodPost, h.HandleHotgameUpdateBalance) r.public("/game-callbacks/{platform_code}/{operation}", http.MethodPost, h.HandleCallback) } diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index 67911736..afb50431 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -481,6 +481,16 @@ type fakeWalletClient struct { giftWallResp *walletv1.GetUserGiftWallResponse lastRechargeProducts *walletv1.ListRechargeProductsRequest rechargeProductsResp *walletv1.ListRechargeProductsResponse + lastH5Options *walletv1.H5RechargeOptionsRequest + h5OptionsResp *walletv1.H5RechargeOptionsResponse + lastH5CreateOrder *walletv1.CreateH5RechargeOrderRequest + h5CreateOrderResp *walletv1.H5RechargeOrderResponse + lastH5SubmitTx *walletv1.SubmitH5RechargeTxRequest + h5SubmitTxResp *walletv1.H5RechargeOrderResponse + lastH5GetOrder *walletv1.GetH5RechargeOrderRequest + h5GetOrderResp *walletv1.H5RechargeOrderResponse + lastMifaPayNotify *walletv1.HandleMifapayNotifyRequest + mifaPayNotifyResp *walletv1.HandleMifapayNotifyResponse lastGoogleConfirm *walletv1.ConfirmGooglePaymentRequest googleConfirmResp *walletv1.ConfirmGooglePaymentResponse lastDiamondExchange *walletv1.GetDiamondExchangeConfigRequest @@ -653,7 +663,16 @@ func (f *fakeUserAuthClient) RecordLoginBlocked(_ context.Context, req *userv1.R func (f *fakeUserAuthClient) AppHeartbeat(_ context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error) { f.lastAppHeartbeat = req - return &userv1.AppHeartbeatResponse{Accepted: true, HeartbeatAtMs: 1_778_000_005_000, ServerTimeMs: 1_778_000_005_000}, nil + return &userv1.AppHeartbeatResponse{Accepted: true, HeartbeatAtMs: 1_778_000_005_000, ServerTimeMs: 1_778_000_005_000, Token: &userv1.AuthToken{ + UserId: req.GetUserId(), + SessionId: req.GetSessionId(), + AccessToken: "access-heartbeat", + ExpiresInSec: 1800, + TokenType: "Bearer", + ProfileCompleted: true, + OnboardingStatus: "completed", + AppCode: req.GetMeta().GetAppCode(), + }}, nil } func (f *fakeUserProfileClient) GetUser(_ context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) { @@ -901,6 +920,30 @@ func (f *fakeUserIdentityClient) ApplyPrettyDisplayUserID(_ context.Context, req }}, nil } +func (f *fakeUserIdentityClient) ListAvailablePrettyDisplayIDs(_ context.Context, req *userv1.ListAvailablePrettyDisplayIDsRequest) (*userv1.ListAvailablePrettyDisplayIDsResponse, error) { + return &userv1.ListAvailablePrettyDisplayIDsResponse{ + Items: []*userv1.PrettyDisplayID{{ + PrettyId: "pretty-test", + DisplayUserId: "VIP2026", + Status: "available", + Source: "pool", + AppCode: req.GetMeta().GetAppCode(), + }}, + Total: 1, + }, nil +} + +func (f *fakeUserIdentityClient) ApplyPrettyDisplayIDFromPool(_ context.Context, req *userv1.ApplyPrettyDisplayIDFromPoolRequest) (*userv1.ApplyPrettyDisplayIDFromPoolResponse, error) { + return &userv1.ApplyPrettyDisplayIDFromPoolResponse{Identity: &userv1.UserIdentity{ + UserId: req.GetUserId(), + DisplayUserId: "VIP2026", + PrettyId: req.GetPrettyId(), + PrettyDisplayUserId: "VIP2026", + Status: "active", + AppCode: req.GetMeta().GetAppCode(), + }}, nil +} + func (f *fakeRoomQueryClient) ListRooms(_ context.Context, req *roomv1.ListRoomsRequest) (*roomv1.ListRoomsResponse, error) { f.lastList = req if f.err != nil { @@ -1520,6 +1563,61 @@ func (f *fakeWalletClient) ListRechargeProducts(_ context.Context, req *walletv1 return &walletv1.ListRechargeProductsResponse{}, nil } +func (f *fakeWalletClient) ListH5RechargeOptions(_ context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error) { + f.lastH5Options = req + if f.err != nil { + return nil, f.err + } + if f.h5OptionsResp != nil { + return f.h5OptionsResp, nil + } + return &walletv1.H5RechargeOptionsResponse{}, nil +} + +func (f *fakeWalletClient) CreateH5RechargeOrder(_ context.Context, req *walletv1.CreateH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) { + f.lastH5CreateOrder = req + if f.err != nil { + return nil, f.err + } + if f.h5CreateOrderResp != nil { + return f.h5CreateOrderResp, nil + } + return &walletv1.H5RechargeOrderResponse{Order: &walletv1.ExternalRechargeOrder{OrderId: "h5-order-1", Status: "pending"}}, nil +} + +func (f *fakeWalletClient) SubmitH5RechargeTx(_ context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error) { + f.lastH5SubmitTx = req + if f.err != nil { + return nil, f.err + } + if f.h5SubmitTxResp != nil { + return f.h5SubmitTxResp, nil + } + return &walletv1.H5RechargeOrderResponse{Order: &walletv1.ExternalRechargeOrder{OrderId: req.GetOrderId(), TxHash: req.GetTxHash(), Status: "pending"}}, nil +} + +func (f *fakeWalletClient) GetH5RechargeOrder(_ context.Context, req *walletv1.GetH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) { + f.lastH5GetOrder = req + if f.err != nil { + return nil, f.err + } + if f.h5GetOrderResp != nil { + return f.h5GetOrderResp, nil + } + return &walletv1.H5RechargeOrderResponse{Order: &walletv1.ExternalRechargeOrder{OrderId: req.GetOrderId(), Status: "pending"}}, nil +} + +func (f *fakeWalletClient) HandleMifapayNotify(_ context.Context, req *walletv1.HandleMifapayNotifyRequest) (*walletv1.HandleMifapayNotifyResponse, error) { + f.lastMifaPayNotify = req + if f.err != nil { + return nil, f.err + } + if f.mifaPayNotifyResp != nil { + return f.mifaPayNotifyResp, nil + } + return &walletv1.HandleMifapayNotifyResponse{Accepted: true, ResponseText: "SUCCESS", OrderId: "h5-order-1"}, nil +} + func (f *fakeWalletClient) ConfirmGooglePayment(_ context.Context, req *walletv1.ConfirmGooglePaymentRequest) (*walletv1.ConfirmGooglePaymentResponse, error) { f.lastGoogleConfirm = req if f.err != nil { @@ -2920,6 +3018,9 @@ func TestAppHeartbeatUsesAuthenticatedSession(t *testing.T) { if !ok || data["accepted"] != true || data["heartbeat_at_ms"].(float64) != 1_778_000_005_000 { t.Fatalf("app heartbeat response mismatch: %+v", response.Data) } + if recorder.Header().Get("X-Hyapp-Access-Token") != "access-heartbeat" || data["access_token"] != "access-heartbeat" { + t.Fatalf("app heartbeat must expose renewed access token: header=%q data=%+v", recorder.Header().Get("X-Hyapp-Access-Token"), data) + } } func TestDeletePushTokenUsesAuthenticatedUser(t *testing.T) { @@ -3557,18 +3658,19 @@ func TestListSplashScreensReturnsAdminAppConfig(t *testing.T) { handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) reader := &captureAppConfigReader{StaticReader: appconfig.StaticReader{SplashScreens: []appconfig.SplashScreen{ { - ID: 12, - CoverURL: "https://cdn.example.com/splash.png", - SplashType: "h5", - Param: "https://h5.example.com/splash", - Platform: "ios", - SortOrder: 2, - RegionID: 7, - CountryCode: "BR", - Description: "launch splash", - StartsAtMs: 1700000000000, - EndsAtMs: 1800000000000, - UpdatedAtMs: 1700000003000, + ID: 12, + CoverURL: "https://cdn.example.com/splash.png", + SplashType: "h5", + Param: "https://h5.example.com/splash", + Platform: "ios", + SortOrder: 2, + RegionID: 7, + CountryCode: "BR", + Description: "launch splash", + DisplayDurationMs: 4500, + StartsAtMs: 1700000000000, + EndsAtMs: 1800000000000, + UpdatedAtMs: 1700000003000, }, }}} handler.SetAppConfigReader(reader) @@ -3608,7 +3710,7 @@ func TestListSplashScreensReturnsAdminAppConfig(t *testing.T) { if first["platform"] != "ios" || first["sort_order"].(float64) != 2 || first["region_id"].(float64) != 7 || first["country_code"] != "BR" { t.Fatalf("splash scope mismatch: %+v", first) } - if first["starts_at_ms"].(float64) != 1700000000000 || first["ends_at_ms"].(float64) != 1800000000000 || first["updated_at_ms"].(float64) != 1700000003000 { + if first["display_duration_ms"].(float64) != 4500 || first["starts_at_ms"].(float64) != 1700000000000 || first["ends_at_ms"].(float64) != 1800000000000 || first["updated_at_ms"].(float64) != 1700000003000 { t.Fatalf("splash metadata mismatch: %+v", first) } } @@ -3628,7 +3730,7 @@ func TestSplashScreenJSONIncludesEmptyScopeFields(t *testing.T) { if err := json.Unmarshal(payload, &item); err != nil { t.Fatalf("unmarshal splash screen failed: %v", err) } - for _, key := range []string{"region_id", "country_code", "description", "starts_at_ms", "ends_at_ms"} { + for _, key := range []string{"region_id", "country_code", "description", "display_duration_ms", "starts_at_ms", "ends_at_ms"} { if _, ok := item[key]; !ok { t.Fatalf("splash json missing %s: %+v", key, item) } @@ -7665,12 +7767,19 @@ func signGatewayToken(t *testing.T, secret string, userID int64) string { func signGatewayTokenWithProfile(t *testing.T, secret string, userID int64, profileCompleted bool) string { t.Helper() + return signGatewayTokenWithExpiresAt(t, secret, userID, profileCompleted, time.Now().Add(time.Hour)) +} + +func signGatewayTokenWithExpiresAt(t *testing.T, secret string, userID int64, profileCompleted bool, expiresAt time.Time) string { + t.Helper() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": strconv.FormatInt(userID, 10), "app_code": "lalu", "sid": "sess-test", "profile_completed": profileCompleted, "onboarding_status": map[bool]string{true: "completed", false: "profile_required"}[profileCompleted], + "exp": expiresAt.Unix(), }) signed, err := token.SignedString([]byte(secret)) if err != nil { diff --git a/services/gateway-service/internal/transport/http/roomapi/room_handler.go b/services/gateway-service/internal/transport/http/roomapi/room_handler.go index 61f3a28c..e36ec81c 100644 --- a/services/gateway-service/internal/transport/http/roomapi/room_handler.go +++ b/services/gateway-service/internal/transport/http/roomapi/room_handler.go @@ -1630,24 +1630,26 @@ func (h *Handler) roomDisplayProfileMap(request *http.Request, userIDs []int64) appearance := appearanceMap[userID] levels := levelMap[userID] profiles[userID] = roomDisplayProfileData{ - UserID: formatOptionalUserID(user.GetUserId()), - Username: user.GetUsername(), - Avatar: user.GetAvatar(), - DisplayUserID: user.GetDisplayUserId(), - Gender: strings.TrimSpace(user.GetGender()), - Age: roomProfileAgeFromBirth(user.GetBirth(), time.Now().UTC()), - Country: strings.ToUpper(strings.TrimSpace(user.GetCountry())), - CountryName: strings.TrimSpace(user.GetCountryName()), - CountryDisplayName: strings.TrimSpace(user.GetCountryDisplayName()), - CountryFlag: roomProfileCountryFlag(user.GetCountry()), - VIP: map[string]any{}, - Level: levels.Levels, - LevelBadges: levels.Badges, - Badges: roomMergeBadgeItems(badgeMap[userID], appearance.Badges), - AvatarFrame: appearance.AvatarFrame, - ProfileCard: appearance.ProfileCard, - Vehicle: appearance.Vehicle, - Charm: 0, + UserID: formatOptionalUserID(user.GetUserId()), + Username: user.GetUsername(), + Avatar: user.GetAvatar(), + DisplayUserID: user.GetDisplayUserId(), + PrettyID: user.GetPrettyId(), + PrettyDisplayUserID: user.GetPrettyDisplayUserId(), + Gender: strings.TrimSpace(user.GetGender()), + Age: roomProfileAgeFromBirth(user.GetBirth(), time.Now().UTC()), + Country: strings.ToUpper(strings.TrimSpace(user.GetCountry())), + CountryName: strings.TrimSpace(user.GetCountryName()), + CountryDisplayName: strings.TrimSpace(user.GetCountryDisplayName()), + CountryFlag: roomProfileCountryFlag(user.GetCountry()), + VIP: map[string]any{}, + Level: levels.Levels, + LevelBadges: levels.Badges, + Badges: roomMergeBadgeItems(badgeMap[userID], appearance.Badges), + AvatarFrame: appearance.AvatarFrame, + ProfileCard: appearance.ProfileCard, + Vehicle: appearance.Vehicle, + Charm: 0, } // 显式补空对象/空数组,避免客户端在房间资料中同时处理 null 和空值。 if profiles[userID].Badges == nil { diff --git a/services/gateway-service/internal/transport/http/roomapi/room_view.go b/services/gateway-service/internal/transport/http/roomapi/room_view.go index f936f29b..6a156379 100644 --- a/services/gateway-service/internal/transport/http/roomapi/room_view.go +++ b/services/gateway-service/internal/transport/http/roomapi/room_view.go @@ -153,24 +153,26 @@ type roomPermissionsData struct { } type roomDisplayProfileData struct { - UserID string `json:"user_id"` - Username string `json:"username"` - Avatar string `json:"avatar"` - DisplayUserID string `json:"display_user_id"` - Gender string `json:"gender"` - Age int32 `json:"age"` - Country string `json:"country"` - CountryName string `json:"country_name"` - CountryDisplayName string `json:"country_display_name"` - CountryFlag string `json:"country_flag"` - VIP map[string]any `json:"vip"` - Level map[string]any `json:"level"` - LevelBadges map[string]any `json:"level_badges"` - Badges []map[string]any `json:"badges"` - AvatarFrame map[string]any `json:"avatar_frame"` - ProfileCard map[string]any `json:"profile_card"` - Vehicle map[string]any `json:"vehicle"` - Charm int64 `json:"charm"` + UserID string `json:"user_id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + DisplayUserID string `json:"display_user_id"` + PrettyID string `json:"pretty_id,omitempty"` + PrettyDisplayUserID string `json:"pretty_display_user_id,omitempty"` + Gender string `json:"gender"` + Age int32 `json:"age"` + Country string `json:"country"` + CountryName string `json:"country_name"` + CountryDisplayName string `json:"country_display_name"` + CountryFlag string `json:"country_flag"` + VIP map[string]any `json:"vip"` + Level map[string]any `json:"level"` + LevelBadges map[string]any `json:"level_badges"` + Badges []map[string]any `json:"badges"` + AvatarFrame map[string]any `json:"avatar_frame"` + ProfileCard map[string]any `json:"profile_card"` + Vehicle map[string]any `json:"vehicle"` + Charm int64 `json:"charm"` } type roomGiftPanelData struct { diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index cc088df6..49467482 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -85,7 +85,9 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { }) gameAPI := gameapi.New(gameapi.Config{ GameClient: h.gameClient, + UserAuthClient: h.userClient, UserProfileClient: h.userProfileClient, + WalletClient: h.walletClient, }) resourceAPI := resourceapi.New(resourceapi.Config{ WalletClient: h.walletClient, diff --git a/services/gateway-service/internal/transport/http/userapi/handler.go b/services/gateway-service/internal/transport/http/userapi/handler.go index e5dbf787..932b1d80 100644 --- a/services/gateway-service/internal/transport/http/userapi/handler.go +++ b/services/gateway-service/internal/transport/http/userapi/handler.go @@ -80,6 +80,8 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers { ChangeMyCountry: h.changeMyCountry, ChangeMyDisplayUserID: h.changeMyDisplayUserID, ApplyMyPrettyDisplayUserID: h.applyMyPrettyDisplayUserID, + ListAvailablePrettyDisplayIDs: h.listAvailablePrettyDisplayIDs, + ApplyPrettyDisplayIDFromPool: h.applyPrettyDisplayIDFromPool, ListMyProfileVisitors: h.listMyProfileVisitors, ListMyFollowing: h.listMyFollowing, ListMyFriends: h.listMyFriends, diff --git a/services/gateway-service/internal/transport/http/userapi/user_handler.go b/services/gateway-service/internal/transport/http/userapi/user_handler.go index 35265e4c..a1f1c85a 100644 --- a/services/gateway-service/internal/transport/http/userapi/user_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/user_handler.go @@ -18,6 +18,8 @@ import ( type userIdentityData struct { UserID string `json:"user_id"` DisplayUserID string `json:"display_user_id"` + PrettyID string `json:"pretty_id,omitempty"` + PrettyDisplayUserID string `json:"pretty_display_user_id,omitempty"` Status string `json:"status"` DefaultDisplayUserID string `json:"default_display_user_id"` DisplayUserIDKind string `json:"display_user_id_kind"` @@ -36,6 +38,8 @@ type resolveDisplayUserData struct { type userProfileData struct { UserID string `json:"user_id"` DisplayUserID string `json:"display_user_id"` + PrettyID string `json:"pretty_id,omitempty"` + PrettyDisplayUserID string `json:"pretty_display_user_id,omitempty"` DefaultDisplayUserID string `json:"default_display_user_id"` DisplayUserIDKind string `json:"display_user_id_kind"` DisplayUserIDExpiresAtMs int64 `json:"display_user_id_expires_at_ms"` @@ -61,6 +65,14 @@ type userProfileData struct { NextCountryChangeAtMs int64 `json:"next_country_change_allowed_at_ms,omitempty"` } +type prettyDisplayIDData struct { + PrettyID string `json:"pretty_id"` + DisplayUserID string `json:"display_user_id"` + LevelTrack string `json:"level_track"` + MinLevel int32 `json:"min_level"` + MaxLevel int32 `json:"max_level"` +} + // completeOnboardingData 在保留资料字段顶层兼容性的同时返回新的 access token。 // token.refresh_token 首版为空,客户端继续持有登录时的 refresh token,仅替换 access_token。 type completeOnboardingData struct { @@ -432,14 +444,83 @@ func (h *Handler) applyMyPrettyDisplayUserID(writer http.ResponseWriter, request httpkit.WriteOK(writer, request, identityData(resp.GetIdentity(), resp.GetLeaseId())) } +// listAvailablePrettyDisplayIDs 返回当前登录用户等级可申请的靓号池号码。 +func (h *Handler) listAvailablePrettyDisplayIDs(writer http.ResponseWriter, request *http.Request) { + if h.userIdentityClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + page, ok := httpkit.PositiveInt32Query(request, "page", 1) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 20) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // App 只能传分页参数;用户等级、池状态和号码可用性全部由 user-service 按当前登录用户重新计算。 + resp, err := h.userIdentityClient.ListAvailablePrettyDisplayIDs(request.Context(), &userv1.ListAvailablePrettyDisplayIDsRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: auth.UserIDFromContext(request.Context()), + Page: page, + PageSize: pageSize, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + items := make([]prettyDisplayIDData, 0, len(resp.GetItems())) + for _, item := range resp.GetItems() { + // 这里返回申请所需 pretty_id 和展示用 display_user_id,等级区间只用于客户端展示申请门槛。 + items = append(items, prettyDisplayIDData{ + PrettyID: item.GetPrettyId(), + DisplayUserID: item.GetDisplayUserId(), + LevelTrack: item.GetPool().GetLevelTrack(), + MinLevel: item.GetPool().GetMinLevel(), + MaxLevel: item.GetPool().GetMaxLevel(), + }) + } + httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": resp.GetTotal(), "page": page, "page_size": pageSize}) +} + +// applyPrettyDisplayIDFromPool 按 pretty_id 申请靓号池号码,租期由 user-service 固定为长期。 +func (h *Handler) applyPrettyDisplayIDFromPool(writer http.ResponseWriter, request *http.Request) { + var body struct { + PrettyID string `json:"pretty_id"` + } + if !httpkit.Decode(writer, request, &body) { + return + } + if h.userIdentityClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + // 新靓号池申请只接收 pretty_id,不接受客户端传靓号内容或租期,防止绕过池状态和等级区间。 + resp, err := h.userIdentityClient.ApplyPrettyDisplayIDFromPool(request.Context(), &userv1.ApplyPrettyDisplayIDFromPoolRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: auth.UserIDFromContext(request.Context()), + PrettyId: strings.TrimSpace(body.PrettyID), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, identityData(resp.GetIdentity(), resp.GetLeaseId())) +} + func identityData(identity *userv1.UserIdentity, leaseID string) userIdentityData { if identity == nil { return userIdentityData{LeaseID: leaseID} } + // identity 投影必须带上 pretty_id 和 pretty_display_user_id,App 可区分真实用户 ID、默认短号和当前靓号。 return userIdentityData{ UserID: userIDStringFromIdentity(identity), DisplayUserID: identity.GetDisplayUserId(), + PrettyID: identity.GetPrettyId(), + PrettyDisplayUserID: identity.GetPrettyDisplayUserId(), Status: identity.GetStatus(), DefaultDisplayUserID: identity.GetDefaultDisplayUserId(), DisplayUserIDKind: identity.GetDisplayUserIdKind(), @@ -453,9 +534,12 @@ func profileData(user *userv1.User, nextCountryChangeAtMs int64) userProfileData return userProfileData{NextCountryChangeAtMs: nextCountryChangeAtMs} } + // 所有资料卡共用该投影,pretty 字段在个人信息、房间资料卡等调用点保持同名返回。 return userProfileData{ UserID: userIDStringFromUser(user), DisplayUserID: user.GetDisplayUserId(), + PrettyID: user.GetPrettyId(), + PrettyDisplayUserID: user.GetPrettyDisplayUserId(), DefaultDisplayUserID: user.GetDefaultDisplayUserId(), DisplayUserIDKind: user.GetDisplayUserIdKind(), DisplayUserIDExpiresAtMs: user.GetDisplayUserIdExpiresAtMs(), diff --git a/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go b/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go index a36e377f..690bccbe 100644 --- a/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go @@ -16,6 +16,7 @@ type rechargeProductData struct { ProductCode string `json:"product_code"` ProductName string `json:"product_name"` Description string `json:"description"` + AudienceType string `json:"audience_type"` Platform string `json:"platform"` Channel string `json:"channel"` CurrencyCode string `json:"currency_code"` @@ -196,11 +197,12 @@ func (h *Handler) listRechargeProducts(writer http.ResponseWriter, request *http return } resp, err := h.walletClient.ListRechargeProducts(request.Context(), &walletv1.ListRechargeProductsRequest{ - RequestId: httpkit.RequestIDFromContext(request.Context()), - AppCode: appcode.FromContext(request.Context()), - UserId: userID, - RegionId: profileResp.GetUser().GetRegionId(), - Platform: httpkit.FirstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform"), + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + UserId: userID, + RegionId: profileResp.GetUser().GetRegionId(), + Platform: httpkit.FirstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform"), + AudienceType: "normal", }) if err != nil { httpkit.WriteRPCError(writer, request, err) @@ -413,6 +415,7 @@ func rechargeProductFromProto(product *walletv1.RechargeProduct) rechargeProduct ProductCode: product.GetProductCode(), ProductName: product.GetProductName(), Description: product.GetDescription(), + AudienceType: product.GetAudienceType(), Platform: product.GetPlatform(), Channel: product.GetChannel(), CurrencyCode: product.GetCurrencyCode(), diff --git a/services/gateway-service/internal/transport/http/walletapi/h5_recharge_handler.go b/services/gateway-service/internal/transport/http/walletapi/h5_recharge_handler.go new file mode 100644 index 00000000..3b377271 --- /dev/null +++ b/services/gateway-service/internal/transport/http/walletapi/h5_recharge_handler.go @@ -0,0 +1,509 @@ +package walletapi + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +const ( + h5RechargeAudienceNormal = "normal" + h5RechargeAudienceCoinSeller = "coin_seller" + h5PaymentProviderMifaPay = "mifapay" + h5PaymentProviderUSDTTRC20 = "usdt_trc20" +) + +type h5RechargeTargetContext struct { + UserID int64 + DisplayUserID string + Username string + Avatar string + CountryCode string + CountryName string + RegionID int64 + RegionCode string + RegionName string + AudienceType string + IsCoinSeller bool + CoinSellerStatus string + ResolvedByToken bool +} + +type h5RechargeContextData struct { + Account h5RechargeAccountData `json:"account"` + Role string `json:"role"` + CanPay bool `json:"can_pay"` + AuthMode string `json:"auth_mode"` +} + +type h5RechargeAccountData struct { + DisplayUserID string `json:"display_user_id"` + Username string `json:"username"` + Avatar string `json:"avatar,omitempty"` + CountryCode string `json:"country_code,omitempty"` + CountryName string `json:"country_name,omitempty"` + RegionID int64 `json:"region_id"` + RegionCode string `json:"region_code,omitempty"` + RegionName string `json:"region_name,omitempty"` + IsCoinSeller bool `json:"is_coin_seller"` + CoinSellerStatus string `json:"coin_seller_status,omitempty"` + AudienceType string `json:"audience_type"` +} + +type h5RechargeOptionsData struct { + Account h5RechargeAccountData `json:"account"` + Products []rechargeProductData `json:"products"` + PaymentMethods []thirdPartyPaymentMethodData `json:"payment_methods"` + USDTTRC20Enabled bool `json:"usdt_trc20_enabled"` + USDTTRC20Address string `json:"usdt_trc20_address,omitempty"` +} + +type thirdPartyPaymentMethodData struct { + MethodID int64 `json:"method_id"` + ProviderCode string `json:"provider_code"` + ProviderName string `json:"provider_name"` + CountryCode string `json:"country_code"` + CountryName string `json:"country_name"` + CurrencyCode string `json:"currency_code"` + PayWay string `json:"pay_way"` + PayType string `json:"pay_type"` + MethodName string `json:"method_name"` + LogoURL string `json:"logo_url,omitempty"` + Status string `json:"status"` + USDToCurrencyRate string `json:"usd_to_currency_rate"` + SortOrder int32 `json:"sort_order"` +} + +type h5RechargeOrderRequestBody struct { + DisplayUserID string `json:"display_user_id"` + ProductID int64 `json:"product_id"` + ProductIDAlt int64 `json:"productId"` + ProviderCode string `json:"provider_code"` + ProviderCodeAlt string `json:"providerCode"` + PaymentMethod string `json:"payment_method"` + PaymentMethodID int64 `json:"payment_method_id"` + PaymentMethodIDAlt int64 `json:"paymentMethodId"` + CommandID string `json:"command_id"` + CommandIDAlt string `json:"commandId"` + ReturnURL string `json:"return_url"` + ReturnURLAlt string `json:"returnUrl"` +} + +type h5RechargeTxRequestBody struct { + DisplayUserID string `json:"display_user_id"` + TxHash string `json:"tx_hash"` + TxHashAlt string `json:"txHash"` +} + +type h5RechargeOrderData struct { + OrderID string `json:"order_id"` + ProductID int64 `json:"product_id"` + ProductName string `json:"product_name"` + CoinAmount int64 `json:"coin_amount"` + USDMinorAmount int64 `json:"usd_minor_amount"` + AudienceType string `json:"audience_type"` + ProviderCode string `json:"provider_code"` + PaymentMethodID int64 `json:"payment_method_id"` + CountryCode string `json:"country_code"` + CurrencyCode string `json:"currency_code"` + ProviderAmountMinor int64 `json:"provider_amount_minor"` + PayWay string `json:"pay_way"` + PayType string `json:"pay_type"` + PayURL string `json:"pay_url,omitempty"` + TxHash string `json:"tx_hash,omitempty"` + ReceiveAddress string `json:"receive_address,omitempty"` + Status string `json:"status"` + FailureReason string `json:"failure_reason,omitempty"` + TransactionID string `json:"transaction_id,omitempty"` + IdempotentReplay bool `json:"idempotent_replay"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type mifapayNotifyBody struct { + MerAccount string `json:"merAccount"` + Data string `json:"data"` + Sign string `json:"sign"` +} + +// getH5RechargeContext 确认 H5 充值目标账号;带 token 时只认 token 用户,不接受前端指定别人。 +func (h *Handler) getH5RechargeContext(writer http.ResponseWriter, request *http.Request) { + target, ok := h.resolveH5RechargeTarget(writer, request, request.URL.Query().Get("display_user_id")) + if !ok { + return + } + httpkit.WriteOK(writer, request, h5RechargeContextData{ + Account: h5RechargeAccountFromTarget(target), + Role: target.AudienceType, + CanPay: target.RegionID > 0, + AuthMode: h5RechargeAuthMode(target.ResolvedByToken), + }) +} + +// listH5RechargeOptions 返回已确认账号的商品档位、USDT 和可下单的 MiFaPay 方式。 +func (h *Handler) listH5RechargeOptions(writer http.ResponseWriter, request *http.Request) { + target, ok := h.resolveH5RechargeTarget(writer, request, request.URL.Query().Get("display_user_id")) + if !ok { + return + } + resp, err := h.walletClient.ListH5RechargeOptions(request.Context(), &walletv1.H5RechargeOptionsRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + TargetUserId: target.UserID, + TargetRegionId: target.RegionID, + TargetCountryCode: target.CountryCode, + AudienceType: target.AudienceType, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, h5RechargeOptionsFromProto(target, resp)) +} + +// createH5RechargeOrder 创建 MiFaPay 或 USDT 外部充值订单;目标账号始终由 gateway 重新解析。 +func (h *Handler) createH5RechargeOrder(writer http.ResponseWriter, request *http.Request) { + var body h5RechargeOrderRequestBody + if !httpkit.Decode(writer, request, &body) { + return + } + target, ok := h.resolveH5RechargeTarget(writer, request, body.DisplayUserID) + if !ok { + return + } + productID := firstPositiveInt64(body.ProductID, body.ProductIDAlt) + paymentMethodID := firstPositiveInt64(body.PaymentMethodID, body.PaymentMethodIDAlt) + providerCode := normalizeH5PaymentProvider(firstNonEmptyString(body.ProviderCode, body.ProviderCodeAlt, body.PaymentMethod), paymentMethodID) + commandID := strings.TrimSpace(firstNonEmptyString(body.CommandID, body.CommandIDAlt)) + if commandID == "" { + commandID = fmt.Sprintf("h5_recharge:%d:%s", target.UserID, httpkit.RequestIDFromContext(request.Context())) + } + if productID <= 0 || providerCode == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.CreateH5RechargeOrder(request.Context(), &walletv1.CreateH5RechargeOrderRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + CommandId: commandID, + TargetUserId: target.UserID, + TargetRegionId: target.RegionID, + TargetCountryCode: target.CountryCode, + AudienceType: target.AudienceType, + ProductId: productID, + ProviderCode: providerCode, + PaymentMethodId: paymentMethodID, + ReturnUrl: firstNonEmptyString(body.ReturnURL, body.ReturnURLAlt), + ClientIp: httpkit.ClientIP(request), + Language: httpkit.FirstQueryOrHeader(request, "language", "X-Language", "Accept-Language"), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{"order": h5RechargeOrderFromProto(resp.GetOrder())}) +} + +// submitH5RechargeTx 提交 USDT tx_hash;无 token 时仍用 display_user_id 校验订单归属。 +func (h *Handler) submitH5RechargeTx(writer http.ResponseWriter, request *http.Request) { + var body h5RechargeTxRequestBody + if !httpkit.Decode(writer, request, &body) { + return + } + target, ok := h.resolveH5RechargeTarget(writer, request, body.DisplayUserID) + if !ok { + return + } + orderID := strings.TrimSpace(request.PathValue("order_id")) + txHash := strings.TrimSpace(firstNonEmptyString(body.TxHash, body.TxHashAlt)) + if orderID == "" || txHash == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.SubmitH5RechargeTx(request.Context(), &walletv1.SubmitH5RechargeTxRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + OrderId: orderID, + TargetUserId: target.UserID, + TxHash: txHash, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{"order": h5RechargeOrderFromProto(resp.GetOrder())}) +} + +// getH5RechargeOrder 给 H5 轮询订单状态;gateway 每次重新解析账号,避免前端伪造 target_user_id。 +func (h *Handler) getH5RechargeOrder(writer http.ResponseWriter, request *http.Request) { + target, ok := h.resolveH5RechargeTarget(writer, request, request.URL.Query().Get("display_user_id")) + if !ok { + return + } + orderID := strings.TrimSpace(request.PathValue("order_id")) + if orderID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.GetH5RechargeOrder(request.Context(), &walletv1.GetH5RechargeOrderRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + OrderId: orderID, + TargetUserId: target.UserID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{"order": h5RechargeOrderFromProto(resp.GetOrder())}) +} + +// handleMifapayNotify 按 MiFaPay 要求返回纯文本 SUCCESS;失败不套 gateway JSON envelope。 +func (h *Handler) handleMifapayNotify(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + writeMifaPayNotifyText(writer, http.StatusBadGateway, "FAIL") + return + } + body := mifapayNotifyBody{ + MerAccount: strings.TrimSpace(request.FormValue("merAccount")), + Data: strings.TrimSpace(request.FormValue("data")), + Sign: strings.TrimSpace(request.FormValue("sign")), + } + if body.MerAccount == "" && body.Data == "" && body.Sign == "" { + if err := json.NewDecoder(request.Body).Decode(&body); err != nil { + writeMifaPayNotifyText(writer, http.StatusBadRequest, "FAIL") + return + } + } + resp, err := h.walletClient.HandleMifapayNotify(request.Context(), &walletv1.HandleMifapayNotifyRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + MerAccount: body.MerAccount, + Data: body.Data, + Sign: body.Sign, + }) + if err != nil || !resp.GetAccepted() { + writeMifaPayNotifyText(writer, http.StatusBadRequest, "FAIL") + return + } + writeMifaPayNotifyText(writer, http.StatusOK, "SUCCESS") +} + +func (h *Handler) resolveH5RechargeTarget(writer http.ResponseWriter, request *http.Request, displayUserID string) (h5RechargeTargetContext, bool) { + if h.walletClient == nil || h.userProfileClient == nil || h.userIdentityClient == nil || h.userHostClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return h5RechargeTargetContext{}, false + } + userID := auth.UserIDFromContext(request.Context()) + resolvedByToken := userID > 0 + if !resolvedByToken { + displayUserID = strings.TrimSpace(displayUserID) + if displayUserID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return h5RechargeTargetContext{}, false + } + resp, err := h.userIdentityClient.ResolveDisplayUserID(request.Context(), &userv1.ResolveDisplayUserIDRequest{ + Meta: httpkit.UserMeta(request, ""), + DisplayUserId: displayUserID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return h5RechargeTargetContext{}, false + } + userID = resp.GetIdentity().GetUserId() + } + if userID <= 0 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return h5RechargeTargetContext{}, false + } + profileResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return h5RechargeTargetContext{}, false + } + user := profileResp.GetUser() + if user == nil || user.GetUserId() <= 0 || user.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE || user.GetRegionId() <= 0 { + httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied") + return h5RechargeTargetContext{}, false + } + audienceType, isCoinSeller, coinSellerStatus, ok := h.resolveH5RechargeAudience(writer, request, userID) + if !ok { + return h5RechargeTargetContext{}, false + } + return h5RechargeTargetContext{ + UserID: user.GetUserId(), + DisplayUserID: user.GetDisplayUserId(), + Username: user.GetUsername(), + Avatar: user.GetAvatar(), + CountryCode: strings.ToUpper(strings.TrimSpace(user.GetCountry())), + CountryName: firstNonEmptyString(user.GetCountryDisplayName(), user.GetCountryName()), + RegionID: user.GetRegionId(), + RegionCode: user.GetRegionCode(), + RegionName: user.GetRegionName(), + AudienceType: audienceType, + IsCoinSeller: isCoinSeller, + CoinSellerStatus: coinSellerStatus, + ResolvedByToken: resolvedByToken, + }, true +} + +func (h *Handler) resolveH5RechargeAudience(writer http.ResponseWriter, request *http.Request, userID int64) (string, bool, string, bool) { + resp, err := h.userHostClient.GetUserRoleSummary(request.Context(), &userv1.GetUserRoleSummaryRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return "", false, "", false + } + summary := resp.GetSummary() + if summary != nil && summary.GetIsCoinSeller() && strings.EqualFold(summary.GetCoinSellerStatus(), "active") { + return h5RechargeAudienceCoinSeller, true, summary.GetCoinSellerStatus(), true + } + status := "" + if summary != nil { + status = summary.GetCoinSellerStatus() + } + return h5RechargeAudienceNormal, false, status, true +} + +func h5RechargeOptionsFromProto(target h5RechargeTargetContext, resp *walletv1.H5RechargeOptionsResponse) h5RechargeOptionsData { + data := h5RechargeOptionsData{Account: h5RechargeAccountFromTarget(target)} + if resp == nil { + return data + } + data.Products = make([]rechargeProductData, 0, len(resp.GetProducts())) + for _, product := range resp.GetProducts() { + data.Products = append(data.Products, rechargeProductFromProto(product)) + } + data.PaymentMethods = make([]thirdPartyPaymentMethodData, 0, len(resp.GetPaymentMethods())) + for _, method := range resp.GetPaymentMethods() { + data.PaymentMethods = append(data.PaymentMethods, thirdPartyPaymentMethodFromProto(method)) + } + data.USDTTRC20Enabled = resp.GetUsdtTrc20Enabled() + data.USDTTRC20Address = resp.GetUsdtTrc20Address() + return data +} + +func h5RechargeAccountFromTarget(target h5RechargeTargetContext) h5RechargeAccountData { + return h5RechargeAccountData{ + DisplayUserID: target.DisplayUserID, + Username: target.Username, + Avatar: target.Avatar, + CountryCode: target.CountryCode, + CountryName: target.CountryName, + RegionID: target.RegionID, + RegionCode: target.RegionCode, + RegionName: target.RegionName, + IsCoinSeller: target.IsCoinSeller, + CoinSellerStatus: target.CoinSellerStatus, + AudienceType: target.AudienceType, + } +} + +func thirdPartyPaymentMethodFromProto(method *walletv1.ThirdPartyPaymentMethod) thirdPartyPaymentMethodData { + if method == nil { + return thirdPartyPaymentMethodData{} + } + return thirdPartyPaymentMethodData{ + MethodID: method.GetMethodId(), + ProviderCode: method.GetProviderCode(), + ProviderName: method.GetProviderName(), + CountryCode: method.GetCountryCode(), + CountryName: method.GetCountryName(), + CurrencyCode: method.GetCurrencyCode(), + PayWay: method.GetPayWay(), + PayType: method.GetPayType(), + MethodName: method.GetMethodName(), + LogoURL: method.GetLogoUrl(), + Status: method.GetStatus(), + USDToCurrencyRate: method.GetUsdToCurrencyRate(), + SortOrder: method.GetSortOrder(), + } +} + +func h5RechargeOrderFromProto(order *walletv1.ExternalRechargeOrder) h5RechargeOrderData { + if order == nil { + return h5RechargeOrderData{} + } + return h5RechargeOrderData{ + OrderID: order.GetOrderId(), + ProductID: order.GetProductId(), + ProductName: order.GetProductName(), + CoinAmount: order.GetCoinAmount(), + USDMinorAmount: order.GetUsdMinorAmount(), + AudienceType: order.GetAudienceType(), + ProviderCode: order.GetProviderCode(), + PaymentMethodID: order.GetPaymentMethodId(), + CountryCode: order.GetCountryCode(), + CurrencyCode: order.GetCurrencyCode(), + ProviderAmountMinor: order.GetProviderAmountMinor(), + PayWay: order.GetPayWay(), + PayType: order.GetPayType(), + PayURL: order.GetPayUrl(), + TxHash: order.GetTxHash(), + ReceiveAddress: order.GetReceiveAddress(), + Status: order.GetStatus(), + FailureReason: order.GetFailureReason(), + TransactionID: order.GetTransactionId(), + IdempotentReplay: order.GetIdempotentReplay(), + CreatedAtMS: order.GetCreatedAtMs(), + UpdatedAtMS: order.GetUpdatedAtMs(), + } +} + +func normalizeH5PaymentProvider(value string, paymentMethodID int64) string { + value = strings.ToLower(strings.TrimSpace(value)) + switch value { + case "usdt", "usdt-trc20", "usdt_trc20", "trc20": + return h5PaymentProviderUSDTTRC20 + case "mifa", "mifapay", "third_party": + return h5PaymentProviderMifaPay + case "": + if paymentMethodID > 0 { + return h5PaymentProviderMifaPay + } + } + return value +} + +func h5RechargeAuthMode(resolvedByToken bool) string { + if resolvedByToken { + return "token" + } + return "display_user_id" +} + +func firstPositiveInt64(values ...int64) int64 { + for _, value := range values { + if value > 0 { + return value + } + } + return 0 +} + +func firstNonEmptyString(values ...string) string { + for _, value := range values { + if value = strings.TrimSpace(value); value != "" { + return value + } + } + return "" +} + +func writeMifaPayNotifyText(writer http.ResponseWriter, statusCode int, body string) { + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") + writer.WriteHeader(statusCode) + _, _ = writer.Write([]byte(body)) +} diff --git a/services/gateway-service/internal/transport/http/walletapi/handler.go b/services/gateway-service/internal/transport/http/walletapi/handler.go index 0291b2ce..f9fbd602 100644 --- a/services/gateway-service/internal/transport/http/walletapi/handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/handler.go @@ -46,6 +46,12 @@ func (h *Handler) WalletHandlers() httproutes.WalletHandlers { GetWalletOverview: h.getWalletOverview, GetMyBalances: h.getMyBalances, ListRechargeProducts: h.listRechargeProducts, + GetH5RechargeContext: h.getH5RechargeContext, + ListH5RechargeOptions: h.listH5RechargeOptions, + CreateH5RechargeOrder: h.createH5RechargeOrder, + SubmitH5RechargeTx: h.submitH5RechargeTx, + GetH5RechargeOrder: h.getH5RechargeOrder, + HandleMifapayNotify: h.handleMifapayNotify, ConfirmGooglePayment: h.confirmGooglePayment, GetDiamondExchangeConfig: h.getDiamondExchangeConfig, ListCoinTransactions: h.listCoinTransactions, diff --git a/services/room-service/internal/integration/tencent_im.go b/services/room-service/internal/integration/tencent_im.go index 501239c7..4d19f35d 100644 --- a/services/room-service/internal/integration/tencent_im.go +++ b/services/room-service/internal/integration/tencent_im.go @@ -296,12 +296,23 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room base.ActorUserID = body.GetSenderUserId() base.TargetUserID = body.GetTargetUserId() base.GiftValue = body.GetGiftValue() + // 房间内 IM 也透传 effect_types,客户端可以在房间流和区域广播流中使用同一套礼物展示判断。 + effectTypesJSON, err := json.Marshal(body.GetGiftEffectTypes()) + if err != nil { + return tencentim.RoomEvent{}, false, err + } base.Attributes = map[string]string{ "gift_id": body.GetGiftId(), "gift_count": fmt.Sprintf("%d", body.GetGiftCount()), "coin_spent": fmt.Sprintf("%d", body.GetCoinSpent()), "billing_receipt_id": body.GetBillingReceiptId(), "target_gift_value": fmt.Sprintf("%d", body.GetTargetGiftValue()), + "gift_type_code": body.GetGiftTypeCode(), + "cp_relation_type": body.GetCpRelationType(), + "gift_name": body.GetGiftName(), + "gift_icon_url": body.GetGiftIconUrl(), + "gift_animation_url": body.GetGiftAnimationUrl(), + "gift_effect_types": string(effectTypesJSON), } return base, true, nil case "RoomRocketFuelChanged": diff --git a/services/room-service/internal/room/service/gift.go b/services/room-service/internal/room/service/gift.go index df9491e1..da504482 100644 --- a/services/room-service/internal/room/service/gift.go +++ b/services/room-service/internal/room/service/gift.go @@ -205,6 +205,8 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r GiftIconUrl: targetBilling.Billing.GetGiftIconUrl(), GiftAnimationUrl: targetBilling.Billing.GetGiftAnimationUrl(), TargetGiftValue: targetCurrentGiftValues[targetBilling.TargetUserID], + // effect_types 来自后台礼物配置,房间事件必须原样带出,后续 activity-service 才能识别全服广播标识。 + GiftEffectTypes: targetBilling.Billing.GetGiftEffectTypes(), }) if err != nil { return mutationResult{}, nil, err diff --git a/services/statistics-service/internal/storage/mysql/repository.go b/services/statistics-service/internal/storage/mysql/repository.go index 3c10b6a9..721b758b 100644 --- a/services/statistics-service/internal/storage/mysql/repository.go +++ b/services/statistics-service/internal/storage/mysql/repository.go @@ -386,6 +386,9 @@ func (r *Repository) ConsumeRecharge(ctx context.Context, event RechargeEvent) e googleUSD = event.USDMinor case "mifapay": mifapayUSD = event.USDMinor + case "usdt_trc20": + // USDT-TRC20 是站外 H5 充值渠道;当前大盘只单列 MiFaPay/Google/币商转账, + // 因此只进入总充值和付费用户,不误归到币商美元充值桶。 case "coin_seller_transfer", "coin_seller": coinSellerTransferCoin = event.CoinAmount default: diff --git a/services/user-service/deploy/mysql/initdb/001_user_service.sql b/services/user-service/deploy/mysql/initdb/001_user_service.sql index 02cd9290..0beef04b 100644 --- a/services/user-service/deploy/mysql/initdb/001_user_service.sql +++ b/services/user-service/deploy/mysql/initdb/001_user_service.sql @@ -32,8 +32,8 @@ ON DUPLICATE KEY UPDATE CREATE TABLE IF NOT EXISTS users ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', - default_display_user_id VARCHAR(32) NOT NULL COMMENT '默认展示用户 ID', - current_display_user_id VARCHAR(32) NOT NULL COMMENT '当前展示用户 ID', + default_display_user_id VARCHAR(64) NOT NULL COMMENT '默认展示用户 ID', + current_display_user_id VARCHAR(64) NOT NULL COMMENT '当前展示用户 ID', current_display_user_id_kind VARCHAR(32) NOT NULL COMMENT '当前展示用户 ID 类型', current_display_user_id_expires_at_ms BIGINT NULL COMMENT '当前展示用户 ID 过期时间,UTC epoch ms', username VARCHAR(64) NULL COMMENT '用户名', @@ -1365,7 +1365,7 @@ WHERE app_code = 'lalu' CREATE TABLE IF NOT EXISTS user_display_user_ids ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID', - display_user_id VARCHAR(32) NOT NULL COMMENT '展示用户 ID', + display_user_id VARCHAR(64) NOT NULL COMMENT '展示用户 ID', user_id BIGINT NOT NULL COMMENT '用户 ID', display_user_id_kind VARCHAR(32) NOT NULL COMMENT '展示用户 ID分类', status VARCHAR(32) NOT NULL COMMENT '业务状态', @@ -1376,7 +1376,7 @@ CREATE TABLE IF NOT EXISTS user_display_user_ids ( release_reason VARCHAR(64) NULL COMMENT '释放原因', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', - live_display_user_id VARCHAR(32) + live_display_user_id VARCHAR(64) GENERATED ALWAYS AS ( CASE WHEN status IN ('active', 'held', 'reserved', 'blocked') THEN display_user_id @@ -1395,16 +1395,18 @@ CREATE TABLE IF NOT EXISTS user_display_user_ids ( CREATE TABLE IF NOT EXISTS pretty_display_user_id_leases ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', lease_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '租约 ID', - display_user_id VARCHAR(32) NOT NULL COMMENT '展示用户 ID', + display_user_id VARCHAR(64) NOT NULL COMMENT '展示用户 ID', user_id BIGINT NOT NULL COMMENT '用户 ID', status VARCHAR(32) NOT NULL COMMENT '业务状态', starts_at_ms BIGINT NOT NULL COMMENT '开始时间,UTC epoch ms', expires_at_ms BIGINT NOT NULL COMMENT '过期时间,UTC epoch ms', + released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间,UTC epoch ms', + release_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '释放原因', payment_receipt_id VARCHAR(64) NULL COMMENT '支付凭据 ID', source VARCHAR(32) NOT NULL COMMENT '来源', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', - active_display_user_id VARCHAR(32) + active_display_user_id VARCHAR(64) GENERATED ALWAYS AS ( CASE WHEN status = 'active' THEN display_user_id ELSE NULL END ) STORED COMMENT '启用展示用户 ID', @@ -1418,12 +1420,73 @@ CREATE TABLE IF NOT EXISTS pretty_display_user_id_leases ( KEY idx_pretty_display_user_id_leases_expire (app_code, status, expires_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号展示用户 ID 租约表'; +CREATE TABLE IF NOT EXISTS pretty_display_id_pools ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + pool_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '靓号池 ID', + name VARCHAR(128) NOT NULL COMMENT '靓号池名称', + level_track VARCHAR(32) NOT NULL COMMENT '等级轨道:wealth/game/charm', + min_level INT NOT NULL COMMENT '最小等级,包含', + max_level INT NOT NULL COMMENT '最大等级,包含', + rule_type VARCHAR(32) NOT NULL COMMENT '生成规则', + rule_config_json JSON NULL COMMENT '生成规则配置', + status VARCHAR(32) NOT NULL COMMENT '业务状态:active/disabled', + sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重', + created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID', + updated_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '更新管理员 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + UNIQUE KEY uk_pretty_pool_range_rule (app_code, level_track, min_level, max_level, name), + KEY idx_pretty_pool_status (app_code, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池配置表'; + +CREATE TABLE IF NOT EXISTS pretty_display_ids ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + pretty_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '靓号记录 ID', + pool_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属靓号池 ID,后台发放为空', + source VARCHAR(32) NOT NULL DEFAULT 'pool' COMMENT '来源:pool/admin_grant', + display_user_id VARCHAR(64) NOT NULL COMMENT '靓号展示内容', + status VARCHAR(32) NOT NULL COMMENT '业务状态', + assigned_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '当前占用用户 ID', + assigned_lease_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '当前占用租约 ID', + assigned_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '占用时间,UTC epoch ms', + released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间,UTC epoch ms', + release_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '释放原因', + generated_batch_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '生成批次 ID', + created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + live_display_user_id VARCHAR(64) + GENERATED ALWAYS AS ( + CASE WHEN status IN ('available', 'assigned', 'disabled', 'reserved') THEN display_user_id ELSE NULL END + ) STORED COMMENT '未删除且未释放的靓号内容', + UNIQUE KEY uk_pretty_display_ids_live (app_code, live_display_user_id), + KEY idx_pretty_display_ids_pool_status (app_code, pool_id, status), + KEY idx_pretty_display_ids_assigned_user (app_code, assigned_user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池号码表'; + +CREATE TABLE IF NOT EXISTS pretty_display_id_generation_batches ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + batch_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '生成批次 ID', + pool_id VARCHAR(64) NOT NULL COMMENT '靓号池 ID', + rule_type VARCHAR(32) NOT NULL COMMENT '生成规则', + rule_config_json JSON NULL COMMENT '生成规则配置', + requested_count INT NOT NULL COMMENT '请求生成数量', + generated_count INT NOT NULL COMMENT '实际生成数量', + skipped_conflict_count INT NOT NULL COMMENT '冲突跳过数量', + status VARCHAR(32) NOT NULL COMMENT '批次状态', + operator_admin_id BIGINT NOT NULL COMMENT '操作管理员 ID', + request_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '链路请求 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + KEY idx_pretty_batch_pool (app_code, pool_id, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号批量生成记录表'; + CREATE TABLE IF NOT EXISTS display_user_id_change_logs ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID', user_id BIGINT NOT NULL COMMENT '用户 ID', - old_display_user_id VARCHAR(32) NOT NULL COMMENT '原展示用户 ID', - new_display_user_id VARCHAR(32) NOT NULL COMMENT '新展示用户 ID', + old_display_user_id VARCHAR(64) NOT NULL COMMENT '原展示用户 ID', + new_display_user_id VARCHAR(64) NOT NULL COMMENT '新展示用户 ID', old_display_user_id_kind VARCHAR(32) NOT NULL COMMENT '原展示用户 ID分类', new_display_user_id_kind VARCHAR(32) NOT NULL COMMENT '新展示用户 ID分类', change_type VARCHAR(32) NOT NULL COMMENT '变更类型', diff --git a/services/user-service/internal/app/app.go b/services/user-service/internal/app/app.go index 76678919..ddc73a77 100644 --- a/services/user-service/internal/app/app.go +++ b/services/user-service/internal/app/app.go @@ -174,6 +174,7 @@ func New(cfg config.Config) (*App, error) { var activityConn *grpc.ClientConn var registrationRewardIssuer authservice.RegistrationRewardIssuer var registrationLevelBadgeIssuer authservice.RegistrationLevelBadgeIssuer + var levelProfileReader userservice.LevelProfileReader if cfg.ActivityService.Enabled { activityConn, err = grpcclient.Dial(cfg.ActivityService.Addr, grpcclient.Config{ DefaultTimeout: time.Duration(cfg.ActivityService.RequestTimeoutMs) * time.Millisecond, @@ -192,6 +193,7 @@ func New(cfg config.Config) (*App, error) { activityClient := activityclient.NewGRPC(activityConn) registrationRewardIssuer = activityClient registrationLevelBadgeIssuer = activityClient + levelProfileReader = activityClient } // auth service 负责登录、session 和 token;用户主数据和短号事务仍通过各自领域存储完成。 @@ -229,6 +231,7 @@ func New(cfg config.Config) (*App, error) { userSvc := userservice.New(userRepo, userservice.WithAppRegistryRepository(appRepo), userservice.WithIdentityRepository(identityRepo), + userservice.WithLevelProfileReader(levelProfileReader), userservice.WithCountryRegionRepository(regionRepo), userservice.WithCountryAdminRepository(regionRepo), userservice.WithRegionAdminRepository(regionRepo), @@ -292,6 +295,7 @@ func New(cfg config.Config) (*App, error) { userv1.RegisterUserDeviceServiceServer(server, userServer) userv1.RegisterAppRegistryServiceServer(server, userServer) userv1.RegisterUserIdentityServiceServer(server, userServer) + userv1.RegisterUserPrettyDisplayIDAdminServiceServer(server, userServer) userv1.RegisterCountryAdminServiceServer(server, userServer) userv1.RegisterCountryQueryServiceServer(server, userServer) userv1.RegisterRegionAdminServiceServer(server, userServer) diff --git a/services/user-service/internal/domain/user/user.go b/services/user-service/internal/domain/user/user.go index 30cec989..2b2abc9c 100644 --- a/services/user-service/internal/domain/user/user.go +++ b/services/user-service/internal/domain/user/user.go @@ -3,6 +3,9 @@ package user import ( "regexp" + "strings" + "unicode" + "unicode/utf8" invitedomain "hyapp/services/user-service/internal/domain/invite" ) @@ -74,6 +77,115 @@ const ( // displayUserIDPattern 限制短号为 5 到 11 位非 0 开头数字。 var displayUserIDPattern = regexp.MustCompile(`^[1-9][0-9]{4,10}$`) +const ( + // LevelTrackWealth 是财富等级轨道。 + LevelTrackWealth = "wealth" + // LevelTrackGame 是游戏等级轨道。 + LevelTrackGame = "game" + // LevelTrackCharm 是魅力等级轨道。 + LevelTrackCharm = "charm" +) + +const ( + // PrettyDisplayIDPoolStatusActive 表示靓号池允许 App 用户申请。 + PrettyDisplayIDPoolStatusActive = "active" + // PrettyDisplayIDPoolStatusDisabled 表示靓号池暂停申请。 + PrettyDisplayIDPoolStatusDisabled = "disabled" +) + +const ( + // PrettyDisplayIDStatusAvailable 表示靓号池号码可被申请。 + PrettyDisplayIDStatusAvailable = "available" + // PrettyDisplayIDStatusAssigned 表示靓号已被用户占用。 + PrettyDisplayIDStatusAssigned = "assigned" + // PrettyDisplayIDStatusDisabled 表示靓号被后台下架。 + PrettyDisplayIDStatusDisabled = "disabled" + // PrettyDisplayIDStatusReserved 表示靓号被系统预留。 + PrettyDisplayIDStatusReserved = "reserved" + // PrettyDisplayIDStatusReleased 表示后台发放靓号已经释放,仅保留历史。 + PrettyDisplayIDStatusReleased = "released" + // PrettyDisplayIDStatusDeleted 表示靓号被软删除。 + PrettyDisplayIDStatusDeleted = "deleted" +) + +const ( + // PrettyDisplayIDSourcePool 表示靓号来自等级池批量生成。 + PrettyDisplayIDSourcePool = "pool" + // PrettyDisplayIDSourceAdminGrant 表示靓号来自后台直接发放。 + PrettyDisplayIDSourceAdminGrant = "admin_grant" +) + +const ( + // PrettyDisplayIDRuleAA 生成两个相同数字。 + PrettyDisplayIDRuleAA = "aa" + // PrettyDisplayIDRuleAAA 生成三连号。 + PrettyDisplayIDRuleAAA = "aaa" + // PrettyDisplayIDRuleAAAA 生成四连号。 + PrettyDisplayIDRuleAAAA = "aaaa" + // PrettyDisplayIDRuleAAAAA 生成五连号。 + PrettyDisplayIDRuleAAAAA = "aaaaa" + // PrettyDisplayIDRuleAAAAAA 生成六连号。 + PrettyDisplayIDRuleAAAAAA = "aaaaaa" + // PrettyDisplayIDRuleAAAAAAA 生成七连号。 + PrettyDisplayIDRuleAAAAAAA = "aaaaaaa" + // PrettyDisplayIDRuleAABB 生成两组对子。 + PrettyDisplayIDRuleAABB = "aabb" + // PrettyDisplayIDRuleAABBCC 生成三组对子。 + PrettyDisplayIDRuleAABBCC = "aabbcc" + // PrettyDisplayIDRuleAABBCCDD 生成四组对子。 + PrettyDisplayIDRuleAABBCCDD = "aabbccdd" + // PrettyDisplayIDRuleABAB 生成两组交替号。 + PrettyDisplayIDRuleABAB = "abab" + // PrettyDisplayIDRuleABABAB 生成三组交替号。 + PrettyDisplayIDRuleABABAB = "ababab" + // PrettyDisplayIDRuleABBA 生成四位回文号。 + PrettyDisplayIDRuleABBA = "abba" + // PrettyDisplayIDRuleABCBA 生成五位回文号。 + PrettyDisplayIDRuleABCBA = "abcba" + // PrettyDisplayIDRuleABCCBA 生成六位回文号。 + PrettyDisplayIDRuleABCCBA = "abccba" + // PrettyDisplayIDRuleAAAB 生成三拖一号码。 + PrettyDisplayIDRuleAAAB = "aaab" + // PrettyDisplayIDRuleAAAAB 生成四拖一号码。 + PrettyDisplayIDRuleAAAAB = "aaaab" + // PrettyDisplayIDRuleAAAAAB 生成五拖一号码。 + PrettyDisplayIDRuleAAAAAB = "aaaaab" + // PrettyDisplayIDRuleAAABBB 生成双豹子号码。 + PrettyDisplayIDRuleAAABBB = "aaabbb" + // PrettyDisplayIDRuleAAAABBBB 生成双炸弹号码。 + PrettyDisplayIDRuleAAAABBBB = "aaaabbbb" + // PrettyDisplayIDRuleABC 生成三顺号。 + PrettyDisplayIDRuleABC = "abc" + // PrettyDisplayIDRuleABCD 生成四顺号。 + PrettyDisplayIDRuleABCD = "abcd" + // PrettyDisplayIDRuleABCDE 生成五顺号。 + PrettyDisplayIDRuleABCDE = "abcde" + // PrettyDisplayIDRuleABCDEF 生成六顺号。 + PrettyDisplayIDRuleABCDEF = "abcdef" + // PrettyDisplayIDRuleCBA 生成三位倒顺号。 + PrettyDisplayIDRuleCBA = "cba" + // PrettyDisplayIDRuleDCBA 生成四位倒顺号。 + PrettyDisplayIDRuleDCBA = "dcba" + // PrettyDisplayIDRuleEDCBA 生成五位倒顺号。 + PrettyDisplayIDRuleEDCBA = "edcba" + // PrettyDisplayIDRuleFEDCBA 生成六位倒顺号。 + PrettyDisplayIDRuleFEDCBA = "fedcba" + // PrettyDisplayIDRuleABCABC 生成顺子重复号。 + PrettyDisplayIDRuleABCABC = "abcabc" + // PrettyDisplayIDRuleDateYYYYMMDD 生成生日或纪念日八位日期号。 + PrettyDisplayIDRuleDateYYYYMMDD = "date_yyyymmdd" + // PrettyDisplayIDRuleDateYYMMDD 生成生日或纪念日六位日期号。 + PrettyDisplayIDRuleDateYYMMDD = "date_yymmdd" + // PrettyDisplayIDRuleLove 生成 520、521、1314 等寓意号码。 + PrettyDisplayIDRuleLove = "love" + // PrettyDisplayIDRuleCustomValues 按 rule_config_json.values 生成固定候选。 + PrettyDisplayIDRuleCustomValues = "custom_values" + // PrettyDisplayIDRuleCustomPattern 按 rule_config_json.pattern 生成自定义字母模式。 + PrettyDisplayIDRuleCustomPattern = "custom_pattern" + // PrettyDisplayIDBatchStatusCompleted 表示批量生成已经完成。 + PrettyDisplayIDBatchStatusCompleted = "completed" +) + // User 是 user-service 的主数据实体。 // 这里只保存注册时形成的用户资料和来源快照,不承载关系链、房间内临时态或连接态。 type User struct { @@ -89,6 +201,10 @@ type User struct { CurrentDisplayUserIDKind DisplayUserIDKind // CurrentDisplayUserIDExpiresAtMs 是当前靓号过期时间,默认短号为 0。 CurrentDisplayUserIDExpiresAtMs int64 + // PrettyID 是当前 active 靓号记录 ID,没有靓号时为空。 + PrettyID string + // PrettyDisplayUserID 是当前 active 靓号内容,没有靓号时为空。 + PrettyDisplayUserID string // Username 是注册入口传入的展示名快照,后续资料编辑可在专门用例中覆盖。 Username string // Gender 是客户端上报的性别枚举原值,v1 不在服务端推断性别含义。 @@ -288,10 +404,185 @@ type Identity struct { DisplayUserIDKind DisplayUserIDKind // DisplayUserIDExpiresAtMs 是 active 靓号过期时间。 DisplayUserIDExpiresAtMs int64 + // PrettyID 是当前 active 靓号记录 ID,没有靓号时为空。 + PrettyID string + // PrettyDisplayUserID 是当前 active 靓号内容,没有靓号时为空。 + PrettyDisplayUserID string // Status 是短号记录状态。 Status DisplayUserIDStatus } +// PrettyDisplayIDPool 保存靓号池等级区间和默认生成规则。 +type PrettyDisplayIDPool struct { + AppCode string + PoolID string + Name string + LevelTrack string + MinLevel int32 + MaxLevel int32 + RuleType string + RuleConfigJSON string + Status string + SortOrder int32 + CreatedByAdminID int64 + UpdatedByAdminID int64 + CreatedAtMs int64 + UpdatedAtMs int64 +} + +// PrettyDisplayID 保存一个可申请、已占用或后台发放的靓号。 +type PrettyDisplayID struct { + AppCode string + PrettyID string + PoolID string + Source string + DisplayUserID string + Status string + AssignedUserID int64 + AssignedLeaseID string + AssignedAtMs int64 + ReleasedAtMs int64 + ReleaseReason string + GeneratedBatchID string + CreatedByAdminID int64 + CreatedAtMs int64 + UpdatedAtMs int64 + Pool PrettyDisplayIDPool +} + +// PrettyDisplayIDGenerationBatch 记录一次后台批量生成结果。 +type PrettyDisplayIDGenerationBatch struct { + AppCode string + BatchID string + PoolID string + RuleType string + RuleConfigJSON string + RequestedCount int32 + GeneratedCount int32 + SkippedConflictCount int32 + Status string + OperatorAdminID int64 + RequestID string + CreatedAtMs int64 + UpdatedAtMs int64 +} + +// UserLevelProfile 是 activity-service 给靓号池校验使用的轻量等级投影。 +type UserLevelProfile struct { + UserID int64 + WealthLevel int32 + GameLevel int32 + CharmLevel int32 +} + +// LevelForTrack 按靓号池保存的轨道取出用户当前等级。 +func (p UserLevelProfile) LevelForTrack(track string) (int32, bool) { + switch track { + case LevelTrackWealth: + return p.WealthLevel, true + case LevelTrackGame: + return p.GameLevel, true + case LevelTrackCharm: + return p.CharmLevel, true + default: + return 0, false + } +} + +// PrettyDisplayIDPoolQuery 描述后台靓号池分页查询条件。 +type PrettyDisplayIDPoolQuery struct { + AppCode string + Status string + LevelTrack string + Page int32 + PageSize int32 +} + +// PrettyDisplayIDQuery 描述后台靓号列表分页查询条件。 +type PrettyDisplayIDQuery struct { + AppCode string + PoolID string + Source string + Status string + Keyword string + AssignedUserID int64 + Page int32 + PageSize int32 +} + +// AvailablePrettyDisplayIDQuery 描述 App 用户可申请靓号查询条件。 +type AvailablePrettyDisplayIDQuery struct { + AppCode string + UserID int64 + Page int32 + PageSize int32 + Levels UserLevelProfile +} + +// PrettyDisplayIDPoolCommand 描述创建或更新靓号池的输入。 +type PrettyDisplayIDPoolCommand struct { + AppCode string + PoolID string + Name string + LevelTrack string + MinLevel int32 + MaxLevel int32 + RuleType string + RuleConfigJSON string + Status string + SortOrder int32 + OperatorAdminID int64 + NowMs int64 +} + +// PrettyDisplayIDGenerateCommand 描述一次后台批量生成请求。 +type PrettyDisplayIDGenerateCommand struct { + AppCode string + BatchID string + PoolID string + RuleType string + RuleConfigJSON string + Count int32 + OperatorAdminID int64 + RequestID string + NowMs int64 +} + +// PrettyDisplayIDStatusCommand 描述后台切换未占用靓号状态的输入。 +type PrettyDisplayIDStatusCommand struct { + AppCode string + PrettyID string + Status string + Reason string + OperatorAdminID int64 + NowMs int64 +} + +// PrettyDisplayIDPoolApplyCommand 描述 App 靓号池申请事务输入。 +type PrettyDisplayIDPoolApplyCommand struct { + AppCode string + UserID int64 + PrettyID string + LeaseID string + Levels UserLevelProfile + RequestID string + NowMs int64 +} + +// AdminGrantPrettyDisplayIDCommand 描述后台直接发放靓号事务输入。 +type AdminGrantPrettyDisplayIDCommand struct { + AppCode string + PrettyID string + LeaseID string + TargetUserID int64 + DisplayUserID string + DurationMs int64 + Reason string + OperatorAdminID int64 + RequestID string + NowMs int64 +} + // DisplayUserIDChangeCommand 描述短号变更的事务输入。 type DisplayUserIDChangeCommand struct { // AppCode 是本次短号变更所属 App。 @@ -522,6 +813,8 @@ func (u User) EffectiveIdentity() Identity { DefaultDisplayUserID: u.DefaultDisplayUserID, DisplayUserIDKind: u.CurrentDisplayUserIDKind, DisplayUserIDExpiresAtMs: u.CurrentDisplayUserIDExpiresAtMs, + PrettyID: u.PrettyID, + PrettyDisplayUserID: u.PrettyDisplayUserID, Status: DisplayUserIDStatusActive, } } @@ -531,3 +824,79 @@ func ValidDisplayUserID(value string) bool { // 格式校验只判断形态,唯一性和可用性由 repository 事务决定。 return displayUserIDPattern.MatchString(value) } + +// ValidAdminPrettyDisplayUserID 校验后台直接发放的靓号内容。 +func ValidAdminPrettyDisplayUserID(value string) bool { + // 后台发放支持阿拉伯语、英文和数字;禁止空白、控制字符、emoji 与标点,避免登录和展示链路出现不可输入字符。 + if value == "" || strings.TrimSpace(value) != value { + return false + } + if utf8.RuneCountInString(value) > 64 { + return false + } + for _, r := range value { + if unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsMark(r) { + continue + } + return false + } + return true +} + +// ValidPrettyDisplayIDPoolTrack 校验靓号池等级轨道。 +func ValidPrettyDisplayIDPoolTrack(track string) bool { + return track == LevelTrackWealth || track == LevelTrackGame || track == LevelTrackCharm +} + +// ValidPrettyDisplayIDPoolStatus 校验靓号池状态。 +func ValidPrettyDisplayIDPoolStatus(status string) bool { + return status == PrettyDisplayIDPoolStatusActive || status == PrettyDisplayIDPoolStatusDisabled +} + +// ValidPrettyDisplayIDRuleType 校验后台批量生成支持的靓号规则。 +func ValidPrettyDisplayIDRuleType(ruleType string) bool { + // 规则类型只表达候选生成方式;具体数量、冲突过滤和 rule_config_json 解析由 service/repository 继续处理。 + switch ruleType { + case PrettyDisplayIDRuleAA, + PrettyDisplayIDRuleAAA, + PrettyDisplayIDRuleAAAA, + PrettyDisplayIDRuleAAAAA, + PrettyDisplayIDRuleAAAAAA, + PrettyDisplayIDRuleAAAAAAA, + PrettyDisplayIDRuleAABB, + PrettyDisplayIDRuleAABBCC, + PrettyDisplayIDRuleAABBCCDD, + PrettyDisplayIDRuleABAB, + PrettyDisplayIDRuleABABAB, + PrettyDisplayIDRuleABBA, + PrettyDisplayIDRuleABCBA, + PrettyDisplayIDRuleABCCBA, + PrettyDisplayIDRuleAAAB, + PrettyDisplayIDRuleAAAAB, + PrettyDisplayIDRuleAAAAAB, + PrettyDisplayIDRuleAAABBB, + PrettyDisplayIDRuleAAAABBBB, + PrettyDisplayIDRuleABC, + PrettyDisplayIDRuleABCD, + PrettyDisplayIDRuleABCDE, + PrettyDisplayIDRuleABCDEF, + PrettyDisplayIDRuleCBA, + PrettyDisplayIDRuleDCBA, + PrettyDisplayIDRuleEDCBA, + PrettyDisplayIDRuleFEDCBA, + PrettyDisplayIDRuleABCABC, + PrettyDisplayIDRuleDateYYYYMMDD, + PrettyDisplayIDRuleDateYYMMDD, + PrettyDisplayIDRuleLove, + PrettyDisplayIDRuleCustomValues, + PrettyDisplayIDRuleCustomPattern: + return true + default: + return false + } +} + +// ValidPrettyDisplayIDStatusChange 校验后台允许切换的未占用靓号状态。 +func ValidPrettyDisplayIDStatusChange(status string) bool { + return status == PrettyDisplayIDStatusAvailable || status == PrettyDisplayIDStatusDisabled || status == PrettyDisplayIDStatusReserved +} diff --git a/services/user-service/internal/domain/user/user_test.go b/services/user-service/internal/domain/user/user_test.go index d3ee992f..9d1386b9 100644 --- a/services/user-service/internal/domain/user/user_test.go +++ b/services/user-service/internal/domain/user/user_test.go @@ -19,3 +19,48 @@ func TestValidDisplayUserIDAllowsFiveDigitSeedAccounts(t *testing.T) { } } } + +func TestValidAdminPrettyDisplayUserID(t *testing.T) { + t.Parallel() + + valid := []string{"VIP2026", "ملك123", "Cafe\u0301123"} + for _, value := range valid { + if !ValidAdminPrettyDisplayUserID(value) { + t.Fatalf("admin pretty display_user_id %q should be valid", value) + } + } + + invalid := []string{"", " ", "VIP-2026", "VIP 2026", "VIP😀", "\nVIP", "VIP!"} + for _, value := range invalid { + if ValidAdminPrettyDisplayUserID(value) { + t.Fatalf("admin pretty display_user_id %q should be invalid", value) + } + } +} + +func TestValidPrettyDisplayIDRuleType(t *testing.T) { + t.Parallel() + + valid := []string{ + PrettyDisplayIDRuleAABBCC, + PrettyDisplayIDRuleAAAA, + PrettyDisplayIDRuleABAB, + PrettyDisplayIDRuleABCDEF, + PrettyDisplayIDRuleFEDCBA, + PrettyDisplayIDRuleDateYYYYMMDD, + PrettyDisplayIDRuleLove, + PrettyDisplayIDRuleCustomValues, + PrettyDisplayIDRuleCustomPattern, + } + for _, ruleType := range valid { + if !ValidPrettyDisplayIDRuleType(ruleType) { + t.Fatalf("rule_type %s should be valid", ruleType) + } + } + + for _, ruleType := range []string{"", "random", "aabbccddxx"} { + if ValidPrettyDisplayIDRuleType(ruleType) { + t.Fatalf("rule_type %s should be invalid", ruleType) + } + } +} diff --git a/services/user-service/internal/integration/activityclient/client.go b/services/user-service/internal/integration/activityclient/client.go index 1fae382e..2ff9c002 100644 --- a/services/user-service/internal/integration/activityclient/client.go +++ b/services/user-service/internal/integration/activityclient/client.go @@ -7,6 +7,7 @@ import ( "google.golang.org/grpc" activityv1 "hyapp.local/api/proto/activity/v1" + userdomain "hyapp/services/user-service/internal/domain/user" authservice "hyapp/services/user-service/internal/service/auth" ) @@ -57,3 +58,33 @@ func (c *Client) IssueRegistrationLevelBadges(ctx context.Context, command auths }) return err } + +// GetUserLevelProfile 读取用户当前三条等级轨道,供 user-service 靓号池申请做准入校验。 +func (c *Client) GetUserLevelProfile(ctx context.Context, appCode string, userID int64, requestID string) (userdomain.UserLevelProfile, error) { + if c == nil || c.growthLevelClient == nil { + return userdomain.UserLevelProfile{}, nil + } + resp, err := c.growthLevelClient.BatchGetUserLevelDisplayProfiles(ctx, &activityv1.BatchGetUserLevelDisplayProfilesRequest{ + Meta: &activityv1.RequestMeta{ + RequestId: requestID, + Caller: "user-service", + AppCode: appCode, + SentAtMs: time.Now().UnixMilli(), + }, + UserIds: []int64{userID}, + }) + if err != nil { + return userdomain.UserLevelProfile{}, err + } + profile := userdomain.UserLevelProfile{UserID: userID} + for _, item := range resp.GetProfiles() { + if item.GetUserId() != userID { + continue + } + profile.WealthLevel = item.GetWealth().GetLevel() + profile.GameLevel = item.GetGame().GetLevel() + profile.CharmLevel = item.GetCharm().GetLevel() + return profile, nil + } + return profile, nil +} diff --git a/services/user-service/internal/service/user/pretty_id.go b/services/user-service/internal/service/user/pretty_id.go index d7964c26..f8b013f3 100644 --- a/services/user-service/internal/service/user/pretty_id.go +++ b/services/user-service/internal/service/user/pretty_id.go @@ -2,6 +2,7 @@ package user import ( "context" + "strings" "hyapp/pkg/appcode" "hyapp/pkg/idgen" @@ -97,3 +98,241 @@ func (s *Service) refreshExpiredUser(ctx context.Context, user userdomain.User, return s.userRepository.GetUser(ctx, user.UserID) } + +// ListAvailablePrettyDisplayIDs 返回当前用户等级允许申请的靓号池号码。 +func (s *Service) ListAvailablePrettyDisplayIDs(ctx context.Context, userID int64, page int32, pageSize int32, requestID string) ([]userdomain.PrettyDisplayID, int64, error) { + if userID <= 0 { + // 可申请列表必须绑定当前登录用户,避免客户端自报等级或轨道。 + return nil, 0, xerr.New(xerr.InvalidArgument, "user_id is required") + } + if s.identityRepository == nil { + // 靓号池和短号唯一性由 identity repository 统一持久化。 + return nil, 0, xerr.New(xerr.Unavailable, "identity repository is not configured") + } + if s.levelProfileReader == nil { + // 没有等级投影时不能返回可申请列表,否则会绕过等级区间限制。 + return nil, 0, xerr.New(xerr.Unavailable, "level profile reader is not configured") + } + + levels, err := s.levelProfileReader.GetUserLevelProfile(ctx, appcode.FromContext(ctx), userID, requestID) + if err != nil { + return nil, 0, err + } + return s.identityRepository.ListAvailablePrettyDisplayIDs(ctx, userdomain.AvailablePrettyDisplayIDQuery{ + AppCode: appcode.FromContext(ctx), + UserID: userID, + Page: normalizePage(page), + PageSize: normalizePageSize(pageSize), + Levels: levels, + }) +} + +// ApplyPrettyDisplayIDFromPool 申请靓号池号码,成功后长期覆盖当前展示号。 +func (s *Service) ApplyPrettyDisplayIDFromPool(ctx context.Context, userID int64, prettyID string, requestID string) (userdomain.Identity, string, error) { + prettyID = strings.TrimSpace(prettyID) + if userID <= 0 || prettyID == "" { + // 靓号池申请只接受 pretty_id,不接受客户端自报号码、等级或租期。 + return userdomain.Identity{}, "", xerr.New(xerr.InvalidArgument, "pretty_id is required") + } + if s.identityRepository == nil { + return userdomain.Identity{}, "", xerr.New(xerr.Unavailable, "identity repository is not configured") + } + if s.levelProfileReader == nil { + return userdomain.Identity{}, "", xerr.New(xerr.Unavailable, "level profile reader is not configured") + } + + levels, err := s.levelProfileReader.GetUserLevelProfile(ctx, appcode.FromContext(ctx), userID, requestID) + if err != nil { + return userdomain.Identity{}, "", err + } + leaseID := idgen.New("pdu_lease") + identity, err := s.identityRepository.ApplyPrettyDisplayIDFromPool(ctx, userdomain.PrettyDisplayIDPoolApplyCommand{ + AppCode: appcode.FromContext(ctx), + UserID: userID, + PrettyID: prettyID, + LeaseID: leaseID, + Levels: levels, + RequestID: requestID, + NowMs: s.now().UnixMilli(), + }) + if err != nil { + return userdomain.Identity{}, "", err + } + return identity, leaseID, nil +} + +// ListPrettyDisplayIDPools 返回后台靓号池分页列表。 +func (s *Service) ListPrettyDisplayIDPools(ctx context.Context, status string, levelTrack string, page int32, pageSize int32) ([]userdomain.PrettyDisplayIDPool, int64, error) { + if s.identityRepository == nil { + return nil, 0, xerr.New(xerr.Unavailable, "identity repository is not configured") + } + // 后台列表只做参数归一化,不在服务层补默认筛选;空 status/level_track 表示查询全部。 + return s.identityRepository.ListPrettyDisplayIDPools(ctx, userdomain.PrettyDisplayIDPoolQuery{ + AppCode: appcode.FromContext(ctx), + Status: strings.TrimSpace(status), + LevelTrack: strings.TrimSpace(levelTrack), + Page: normalizePage(page), + PageSize: normalizePageSize(pageSize), + }) +} + +// CreatePrettyDisplayIDPool 创建后台靓号池配置。 +func (s *Service) CreatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) { + if s.identityRepository == nil { + return userdomain.PrettyDisplayIDPool{}, xerr.New(xerr.Unavailable, "identity repository is not configured") + } + // 池 ID、应用和时间由服务端生成,后台只提交业务配置和操作者,避免跨应用或伪造创建时间。 + command.AppCode = appcode.FromContext(ctx) + command.PoolID = idgen.New("pretty_pool") + command.NowMs = s.now().UnixMilli() + normalizePrettyDisplayIDPoolCommand(&command) + // 创建和更新共用同一个校验函数,保证等级区间、轨道、状态和规则类型行为一致。 + if err := validatePrettyDisplayIDPoolCommand(command); err != nil { + return userdomain.PrettyDisplayIDPool{}, err + } + return s.identityRepository.CreatePrettyDisplayIDPool(ctx, command) +} + +// UpdatePrettyDisplayIDPool 更新后台靓号池配置。 +func (s *Service) UpdatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) { + if s.identityRepository == nil { + return userdomain.PrettyDisplayIDPool{}, xerr.New(xerr.Unavailable, "identity repository is not configured") + } + // 更新时 pool_id 来自路径,其他字段来自 body;统一 trim 后再校验,避免空格绕过必填判断。 + command.AppCode = appcode.FromContext(ctx) + command.PoolID = strings.TrimSpace(command.PoolID) + command.NowMs = s.now().UnixMilli() + normalizePrettyDisplayIDPoolCommand(&command) + if command.PoolID == "" { + return userdomain.PrettyDisplayIDPool{}, xerr.New(xerr.InvalidArgument, "pool_id is required") + } + if err := validatePrettyDisplayIDPoolCommand(command); err != nil { + return userdomain.PrettyDisplayIDPool{}, err + } + return s.identityRepository.UpdatePrettyDisplayIDPool(ctx, command) +} + +// GeneratePrettyDisplayIDs 按靓号池规则批量生成号码。 +func (s *Service) GeneratePrettyDisplayIDs(ctx context.Context, command userdomain.PrettyDisplayIDGenerateCommand) (userdomain.PrettyDisplayIDGenerationBatch, error) { + if s.identityRepository == nil { + return userdomain.PrettyDisplayIDGenerationBatch{}, xerr.New(xerr.Unavailable, "identity repository is not configured") + } + // 批量生成由服务端生成 batch_id 和当前时间,客户端只能选择池、规则和数量,审计操作者来自后台登录态。 + command.AppCode = appcode.FromContext(ctx) + command.PoolID = strings.TrimSpace(command.PoolID) + command.RuleType = strings.TrimSpace(command.RuleType) + command.RuleConfigJSON = strings.TrimSpace(command.RuleConfigJSON) + command.BatchID = idgen.New("pretty_batch") + command.NowMs = s.now().UnixMilli() + if command.PoolID == "" || command.Count <= 0 || command.OperatorAdminID <= 0 { + return userdomain.PrettyDisplayIDGenerationBatch{}, xerr.New(xerr.InvalidArgument, "generate pretty display ids argument is invalid") + } + if command.RuleType == "" { + // 空规则按历史默认 AABBCC 处理,旧后台和旧池配置不需要补字段也能继续生成。 + command.RuleType = userdomain.PrettyDisplayIDRuleAABBCC + } + if !userdomain.ValidPrettyDisplayIDRuleType(command.RuleType) { + return userdomain.PrettyDisplayIDGenerationBatch{}, xerr.New(xerr.InvalidArgument, "rule_type is invalid") + } + return s.identityRepository.GeneratePrettyDisplayIDs(ctx, command) +} + +// ListPrettyDisplayIDs 返回后台靓号分页列表。 +func (s *Service) ListPrettyDisplayIDs(ctx context.Context, query userdomain.PrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) { + if s.identityRepository == nil { + return nil, 0, xerr.New(xerr.Unavailable, "identity repository is not configured") + } + // 查询条件只做 trim 和分页收敛,来源、状态、池 ID 的合法性由 repository SQL 作为精确筛选处理。 + query.AppCode = appcode.FromContext(ctx) + query.Page = normalizePage(query.Page) + query.PageSize = normalizePageSize(query.PageSize) + query.PoolID = strings.TrimSpace(query.PoolID) + query.Source = strings.TrimSpace(query.Source) + query.Status = strings.TrimSpace(query.Status) + query.Keyword = strings.TrimSpace(query.Keyword) + return s.identityRepository.ListPrettyDisplayIDs(ctx, query) +} + +// SetPrettyDisplayIDStatus 切换未占用靓号的状态。 +func (s *Service) SetPrettyDisplayIDStatus(ctx context.Context, command userdomain.PrettyDisplayIDStatusCommand) (userdomain.PrettyDisplayID, error) { + if s.identityRepository == nil { + return userdomain.PrettyDisplayID{}, xerr.New(xerr.Unavailable, "identity repository is not configured") + } + // 状态修改不接收 display_user_id,必须以 pretty_id 定位池表记录,防止同内容历史记录被误改。 + command.AppCode = appcode.FromContext(ctx) + command.PrettyID = strings.TrimSpace(command.PrettyID) + command.Status = strings.TrimSpace(command.Status) + command.Reason = strings.TrimSpace(command.Reason) + command.NowMs = s.now().UnixMilli() + if command.PrettyID == "" || command.OperatorAdminID <= 0 || !userdomain.ValidPrettyDisplayIDStatusChange(command.Status) { + return userdomain.PrettyDisplayID{}, xerr.New(xerr.InvalidArgument, "pretty display id status argument is invalid") + } + return s.identityRepository.SetPrettyDisplayIDStatus(ctx, command) +} + +// AdminGrantPrettyDisplayID 后台直接发放靓号。 +func (s *Service) AdminGrantPrettyDisplayID(ctx context.Context, command userdomain.AdminGrantPrettyDisplayIDCommand) (userdomain.Identity, string, error) { + if s.identityRepository == nil { + return userdomain.Identity{}, "", xerr.New(xerr.Unavailable, "identity repository is not configured") + } + // 后台发放没有池限制和等级限制,但 pretty_id、lease_id、应用和时间仍由服务端生成,保证审计链完整。 + command.AppCode = appcode.FromContext(ctx) + command.PrettyID = idgen.New("pretty") + command.LeaseID = idgen.New("pdu_lease") + command.DisplayUserID = strings.TrimSpace(command.DisplayUserID) + command.Reason = strings.TrimSpace(command.Reason) + command.NowMs = s.now().UnixMilli() + // 靓号内容允许 Unicode 字母、数字和组合标记;target/admin 必须来自可信登录态或明确目标用户。 + if command.TargetUserID <= 0 || command.OperatorAdminID <= 0 || !userdomain.ValidAdminPrettyDisplayUserID(command.DisplayUserID) { + return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDInvalid, "pretty display_user_id format is invalid") + } + identity, leaseID, err := s.identityRepository.AdminGrantPrettyDisplayID(ctx, command) + if err != nil { + return userdomain.Identity{}, "", err + } + return identity, leaseID, nil +} + +func validatePrettyDisplayIDPoolCommand(command userdomain.PrettyDisplayIDPoolCommand) error { + if command.Name == "" || command.MinLevel < 0 || command.MaxLevel < command.MinLevel || command.OperatorAdminID <= 0 { + return xerr.New(xerr.InvalidArgument, "pretty display id pool argument is invalid") + } + if !userdomain.ValidPrettyDisplayIDPoolTrack(command.LevelTrack) || !userdomain.ValidPrettyDisplayIDPoolStatus(command.Status) { + return xerr.New(xerr.InvalidArgument, "pretty display id pool argument is invalid") + } + if command.RuleType == "" { + command.RuleType = userdomain.PrettyDisplayIDRuleAABBCC + } + if !userdomain.ValidPrettyDisplayIDRuleType(command.RuleType) { + return xerr.New(xerr.InvalidArgument, "rule_type is invalid") + } + return nil +} + +func normalizePrettyDisplayIDPoolCommand(command *userdomain.PrettyDisplayIDPoolCommand) { + command.Name = strings.TrimSpace(command.Name) + command.LevelTrack = strings.TrimSpace(command.LevelTrack) + command.RuleType = strings.TrimSpace(command.RuleType) + command.RuleConfigJSON = strings.TrimSpace(command.RuleConfigJSON) + command.Status = strings.TrimSpace(command.Status) + if command.RuleType == "" { + command.RuleType = userdomain.PrettyDisplayIDRuleAABBCC + } +} + +func normalizePage(page int32) int32 { + if page <= 0 { + return 1 + } + return page +} + +func normalizePageSize(pageSize int32) int32 { + if pageSize <= 0 { + return 20 + } + if pageSize > 100 { + return 100 + } + return pageSize +} diff --git a/services/user-service/internal/service/user/service.go b/services/user-service/internal/service/user/service.go index 0dab5984..d69bcc96 100644 --- a/services/user-service/internal/service/user/service.go +++ b/services/user-service/internal/service/user/service.go @@ -110,10 +110,33 @@ type IdentityRepository interface { ChangeDisplayUserID(ctx context.Context, command userdomain.DisplayUserIDChangeCommand) (userdomain.Identity, error) // ApplyPrettyDisplayUserID 原子创建靓号租约并覆盖当前展示号。 ApplyPrettyDisplayUserID(ctx context.Context, command userdomain.PrettyDisplayUserIDCommand) (userdomain.Identity, string, error) + // ListAvailablePrettyDisplayIDs 读取当前用户等级可申请的靓号池号码。 + ListAvailablePrettyDisplayIDs(ctx context.Context, query userdomain.AvailablePrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) + // ApplyPrettyDisplayIDFromPool 原子申请靓号池号码并长期覆盖当前展示号。 + ApplyPrettyDisplayIDFromPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolApplyCommand) (userdomain.Identity, error) + // ListPrettyDisplayIDPools 分页读取后台靓号池配置。 + ListPrettyDisplayIDPools(ctx context.Context, query userdomain.PrettyDisplayIDPoolQuery) ([]userdomain.PrettyDisplayIDPool, int64, error) + // CreatePrettyDisplayIDPool 创建后台靓号池配置。 + CreatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) + // UpdatePrettyDisplayIDPool 更新后台靓号池配置。 + UpdatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) + // GeneratePrettyDisplayIDs 按靓号池规则批量生成号码。 + GeneratePrettyDisplayIDs(ctx context.Context, command userdomain.PrettyDisplayIDGenerateCommand) (userdomain.PrettyDisplayIDGenerationBatch, error) + // ListPrettyDisplayIDs 分页读取后台靓号列表。 + ListPrettyDisplayIDs(ctx context.Context, query userdomain.PrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) + // SetPrettyDisplayIDStatus 切换未占用靓号的状态。 + SetPrettyDisplayIDStatus(ctx context.Context, command userdomain.PrettyDisplayIDStatusCommand) (userdomain.PrettyDisplayID, error) + // AdminGrantPrettyDisplayID 后台直接发放靓号并覆盖当前展示号。 + AdminGrantPrettyDisplayID(ctx context.Context, command userdomain.AdminGrantPrettyDisplayIDCommand) (userdomain.Identity, string, error) // ExpirePrettyDisplayUserID 原子恢复默认短号。 ExpirePrettyDisplayUserID(ctx context.Context, command userdomain.ExpirePrettyDisplayUserIDCommand) (userdomain.Identity, error) } +// LevelProfileReader 读取用户当前等级投影,供靓号池申请做准入校验。 +type LevelProfileReader interface { + GetUserLevelProfile(ctx context.Context, appCode string, userID int64, requestID string) (userdomain.UserLevelProfile, error) +} + // DeviceRepository 隔离设备 push token 的持久化。 // 推送 token 只服务离线触达,不进入认证、房间或钱包主链路。 type DeviceRepository interface { @@ -171,6 +194,8 @@ type Service struct { userRepository UserRepository // identityRepository 持有短号、靓号和变更日志事务能力。 identityRepository IdentityRepository + // levelProfileReader 读取 activity-service 等级投影,靓号池申请必须依赖它判断等级区间。 + levelProfileReader LevelProfileReader // countryRegionRepository 持有国家主数据校验和国家到区域映射解析能力。 countryRegionRepository CountryRegionRepository // countryAdminRepository 持有国家管理事务能力。 @@ -356,6 +381,16 @@ func WithIdentityRepository(repository IdentityRepository) Option { } } +// WithLevelProfileReader 注入 activity-service 等级查询边界。 +func WithLevelProfileReader(reader LevelProfileReader) Option { + return func(s *Service) { + if reader != nil { + // 等级只用于靓号池申请准入,不进入用户主表。 + s.levelProfileReader = reader + } + } +} + // WithIDGenerator 注入 user_id 发号器。 func WithIDGenerator(generator IDGenerator) Option { return func(s *Service) { diff --git a/services/user-service/internal/storage/mysql/identity/checks.go b/services/user-service/internal/storage/mysql/identity/checks.go index feda91f2..83029928 100644 --- a/services/user-service/internal/storage/mysql/identity/checks.go +++ b/services/user-service/internal/storage/mysql/identity/checks.go @@ -18,19 +18,33 @@ func (r *Repository) assertDisplayUserIDAvailable(ctx context.Context, tx *sql.T WHERE app_code = ? AND display_user_id = ? AND status IN (?, ?, ?, ?) FOR UPDATE `, appcode.FromContext(ctx), displayUserID, string(userdomain.DisplayUserIDStatusActive), string(userdomain.DisplayUserIDStatusHeld), string(userdomain.DisplayUserIDStatusReserved), string(userdomain.DisplayUserIDStatusBlocked)).Scan(&existingUserID) - if err == sql.ErrNoRows { - // 没有 active/held/reserved/blocked 记录时可用。 - return nil - } - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } - if allowSameOwner && existingUserID == userID { + if err != sql.ErrNoRows && allowSameOwner && existingUserID == userID { // 默认短号修改允许同用户幂等检查通过。 return nil } + if err != sql.ErrNoRows { + return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists") + } - return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists") + var existingPrettyID string + err = tx.QueryRowContext(ctx, ` + SELECT pretty_id + FROM pretty_display_ids + WHERE app_code = ? AND display_user_id = ? AND status IN (?, ?, ?, ?) + LIMIT 1 + `, appcode.FromContext(ctx), displayUserID, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDStatusAssigned, userdomain.PrettyDisplayIDStatusDisabled, userdomain.PrettyDisplayIDStatusReserved).Scan(&existingPrettyID) + if err != nil && err != sql.ErrNoRows { + return err + } + if existingPrettyID != "" { + // 靓号池号码和后台发放靓号也属于同一个展示号唯一空间。 + return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists") + } + + return nil } func (r *Repository) assertChangeCooldown(ctx context.Context, tx *sql.Tx, command userdomain.DisplayUserIDChangeCommand) error { diff --git a/services/user-service/internal/storage/mysql/identity/pretty.go b/services/user-service/internal/storage/mysql/identity/pretty.go index 7aab806e..e8869cb1 100644 --- a/services/user-service/internal/storage/mysql/identity/pretty.go +++ b/services/user-service/internal/storage/mysql/identity/pretty.go @@ -3,16 +3,39 @@ package identity import ( "context" "database/sql" + "strings" "hyapp/pkg/appcode" + "hyapp/pkg/idgen" "hyapp/pkg/xerr" userdomain "hyapp/services/user-service/internal/domain/user" "hyapp/services/user-service/internal/storage/mysql/shared" userstorage "hyapp/services/user-service/internal/storage/mysql/user" ) +type prettyAssignment struct { + // PrettyID 只在靓号池申请和后台发放时存在;旧付费临时靓号没有池表记录,返回身份时只返回展示号。 + PrettyID string + LeaseID string + DisplayUserID string + ExpiresAtMs int64 + PaymentReceiptID string + // Source 决定释放后的池表状态:pool 号码回到 available,admin_grant 号码进入 released 留审计。 + Source string + ChangeType string + Reason string + OperatorUserID int64 + RequestID string + NowMs int64 +} + +// ApplyPrettyDisplayUserID 兼容旧付费靓号接口,仍要求租期和支付回执,但允许覆盖旧靓号。 func (r *Repository) ApplyPrettyDisplayUserID(ctx context.Context, command userdomain.PrettyDisplayUserIDCommand) (userdomain.Identity, string, error) { - // 靓号申请涉及租约、默认短号 held、当前展示号覆盖和变更日志,必须事务化。 + // 旧接口按展示号申请,没有 pretty_id;先懒过期同名到期靓号,避免已过期记录继续占用唯一性。 + if err := r.expirePrettyByDisplayUserID(ctx, command.PrettyDisplayID, command.NowMs, command.RequestID); err != nil { + return userdomain.Identity{}, "", err + } + tx, err := r.db.BeginTx(ctx, nil) if err != nil { return userdomain.Identity{}, "", err @@ -21,107 +44,182 @@ func (r *Repository) ApplyPrettyDisplayUserID(ctx context.Context, command userd user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.UserID) if err == sql.ErrNoRows { - // 用户不存在不能创建租约。 return userdomain.Identity{}, "", xerr.New(xerr.NotFound, "user not found") } if err != nil { return userdomain.Identity{}, "", err } - if user.DisplayUserIDExpired(command.NowMs) { - // 如果旧靓号已过期,先恢复默认短号再申请新靓号。 - identity, err := r.expirePrettyInTx(ctx, tx, user, "", command.NowMs, command.RequestID) - if err != nil { - return userdomain.Identity{}, "", err - } - user.CurrentDisplayUserID = identity.DisplayUserID - user.CurrentDisplayUserIDKind = identity.DisplayUserIDKind - user.CurrentDisplayUserIDExpiresAtMs = 0 - } - if user.CurrentDisplayUserIDKind == userdomain.DisplayUserIDKindPretty { - // 同一用户不能叠加 active 靓号。 - return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDPrettyActive, "pretty display_user_id is active") - } - if err := r.assertDisplayUserIDAvailable(ctx, tx, command.PrettyDisplayID, command.UserID, false); err != nil { + // 用户锁在全局唯一性校验前拿到,保证同一用户的展示号替换和其他申请不会交叉写入。 + if err := r.assertDisplayUserIDGloballyAvailable(ctx, tx, command.PrettyDisplayID); err != nil { if xerr.IsCode(err, xerr.DisplayUserIDExists) { - // 靓号冲突使用专用 reason,便于前端展示购买失败原因。 return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display_user_id is not available") } return userdomain.Identity{}, "", err } - // 默认短号从 active 改为 held,过期时会重新激活。 - expiresAtMs := command.NowMs + command.LeaseDurationMs - _, err = tx.ExecContext(ctx, ` - UPDATE user_display_user_ids - SET status = ?, updated_at_ms = ? - WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ? - `, string(userdomain.DisplayUserIDStatusHeld), command.NowMs, appcode.Normalize(command.AppCode), command.UserID, user.DefaultDisplayUserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive)) + identity, err := r.replacePrettyInTx(ctx, tx, user, prettyAssignment{ + LeaseID: command.LeaseID, + DisplayUserID: command.PrettyDisplayID, + ExpiresAtMs: command.NowMs + command.LeaseDurationMs, + PaymentReceiptID: command.PaymentReceiptID, + Source: command.Source, + ChangeType: "pretty_apply", + Reason: "pretty_apply", + OperatorUserID: command.OperatorUserID, + RequestID: command.RequestID, + NowMs: command.NowMs, + }) if err != nil { return userdomain.Identity{}, "", err } - - _, err = tx.ExecContext(ctx, ` - INSERT INTO pretty_display_user_id_leases (app_code, lease_id, display_user_id, user_id, status, starts_at_ms, expires_at_ms, payment_receipt_id, source, created_at_ms, updated_at_ms) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.Normalize(command.AppCode), command.LeaseID, command.PrettyDisplayID, command.UserID, string(userdomain.PrettyLeaseStatusActive), command.NowMs, expiresAtMs, shared.NullableString(command.PaymentReceiptID), command.Source, command.NowMs, command.NowMs) - if err != nil { - return userdomain.Identity{}, "", shared.MapPrettyDuplicateError(err) - } - - _, err = tx.ExecContext(ctx, ` - INSERT INTO user_display_user_ids (app_code, display_user_id, user_id, display_user_id_kind, status, assigned_at_ms, activated_at_ms, expires_at_ms, created_at_ms, updated_at_ms) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.Normalize(command.AppCode), command.PrettyDisplayID, command.UserID, string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive), command.NowMs, command.NowMs, expiresAtMs, command.NowMs, command.NowMs) - if err != nil { - return userdomain.Identity{}, "", shared.MapPrettyDuplicateError(err) - } - - _, err = tx.ExecContext(ctx, ` - UPDATE users - SET current_display_user_id = ?, current_display_user_id_kind = ?, current_display_user_id_expires_at_ms = ?, updated_at_ms = ? - WHERE app_code = ? AND user_id = ? - `, command.PrettyDisplayID, string(userdomain.DisplayUserIDKindPretty), expiresAtMs, command.NowMs, appcode.Normalize(command.AppCode), command.UserID) - if err != nil { - return userdomain.Identity{}, "", shared.MapPrettyDuplicateError(err) - } - - if err := insertDisplayUserIDChangeLog(ctx, tx, displayUserIDLog{ - AppCode: appcode.Normalize(command.AppCode), - UserID: command.UserID, - OldID: user.CurrentDisplayUserID, - NewID: command.PrettyDisplayID, - OldKind: user.CurrentDisplayUserIDKind, - NewKind: userdomain.DisplayUserIDKindPretty, - ChangeType: "pretty_apply", - LeaseID: command.LeaseID, - Reason: "pretty_apply", - OperatorID: command.OperatorUserID, - RequestID: command.RequestID, - CreatedAtMs: command.NowMs, - }); err != nil { - return userdomain.Identity{}, "", err - } - if err := tx.Commit(); err != nil { - // commit 失败时租约和展示号覆盖都不能视为成功。 return userdomain.Identity{}, "", err } - return userdomain.Identity{ - AppCode: appcode.Normalize(command.AppCode), - UserID: command.UserID, - DisplayUserID: command.PrettyDisplayID, - DefaultDisplayUserID: user.DefaultDisplayUserID, - DisplayUserIDKind: userdomain.DisplayUserIDKindPretty, - DisplayUserIDExpiresAtMs: expiresAtMs, - Status: userdomain.DisplayUserIDStatusActive, - }, command.LeaseID, nil + return identity, command.LeaseID, nil +} + +// ListAvailablePrettyDisplayIDs 返回用户当前等级可申请的靓号池号码。 +func (r *Repository) ListAvailablePrettyDisplayIDs(ctx context.Context, query userdomain.AvailablePrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) { + appCode := appcode.Normalize(query.AppCode) + page, pageSize := normalizeIdentityPage(query.Page, query.PageSize) + offset := (page - 1) * pageSize + // 可申请列表不接受客户端自报轨道等级;服务层先读取用户真实等级,这里按财富、游戏、魅力三条轨道做 OR 匹配。 + levelWhere := ` + AND ( + (pool.level_track = ? AND pool.min_level <= ? AND pool.max_level >= ?) + OR (pool.level_track = ? AND pool.min_level <= ? AND pool.max_level >= ?) + OR (pool.level_track = ? AND pool.min_level <= ? AND pool.max_level >= ?) + )` + levelArgs := []any{ + userdomain.LevelTrackWealth, query.Levels.WealthLevel, query.Levels.WealthLevel, + userdomain.LevelTrackGame, query.Levels.GameLevel, query.Levels.GameLevel, + userdomain.LevelTrackCharm, query.Levels.CharmLevel, query.Levels.CharmLevel, + } + + var total int64 + countArgs := append([]any{appCode, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDSourcePool, userdomain.PrettyDisplayIDPoolStatusActive}, levelArgs...) + if err := r.db.QueryRowContext(ctx, ` + SELECT COUNT(*) + FROM pretty_display_ids pdi + JOIN pretty_display_id_pools pool + ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id + WHERE pdi.app_code = ? + AND pdi.status = ? + AND pdi.source = ? + AND pool.status = ? + `+levelWhere, countArgs...).Scan(&total); err != nil { + return nil, 0, err + } + + selectArgs := append(countArgs, int(pageSize), int(offset)) + rows, err := r.db.QueryContext(ctx, ` + SELECT `+prettyDisplayIDSelectColumns()+` + FROM pretty_display_ids pdi + JOIN pretty_display_id_pools pool + ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id + WHERE pdi.app_code = ? + AND pdi.status = ? + AND pdi.source = ? + AND pool.status = ? + `+levelWhere+` + ORDER BY pool.sort_order ASC, pdi.created_at_ms ASC, pdi.pretty_id ASC + LIMIT ? OFFSET ? + `, selectArgs...) + if err != nil { + return nil, 0, err + } + defer rows.Close() + + items, err := scanPrettyDisplayIDRows(rows) + if err != nil { + return nil, 0, err + } + return items, total, nil +} + +// ApplyPrettyDisplayIDFromPool 申请靓号池号码,号码本身、用户当前等级和用户展示号在同一事务中校验。 +func (r *Repository) ApplyPrettyDisplayIDFromPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolApplyCommand) (userdomain.Identity, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return userdomain.Identity{}, err + } + defer tx.Rollback() + + appCode := appcode.Normalize(command.AppCode) + // 先锁定 pretty_display_ids 行,保证同一个 pretty_id 只能被一个申请事务从 available 改为 assigned。 + item, err := queryPrettyDisplayIDForUpdate(ctx, tx, appCode, command.PrettyID) + if err == sql.ErrNoRows { + return userdomain.Identity{}, xerr.New(xerr.NotFound, "pretty display id not found") + } + if err != nil { + return userdomain.Identity{}, err + } + if item.Source != userdomain.PrettyDisplayIDSourcePool || item.Status != userdomain.PrettyDisplayIDStatusAvailable { + return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display id is not available") + } + + // 池配置也加锁读取,避免管理员同时禁用池或调整等级区间时出现旧规则通过的申请。 + pool, err := queryPrettyDisplayIDPoolForUpdate(ctx, tx, appCode, item.PoolID) + if err == sql.ErrNoRows { + return userdomain.Identity{}, xerr.New(xerr.NotFound, "pretty display id pool not found") + } + if err != nil { + return userdomain.Identity{}, err + } + if pool.Status != userdomain.PrettyDisplayIDPoolStatusActive { + return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display id pool is disabled") + } + // 只校验该池声明的等级轨道;用户在其他轨道等级足够也不能跨池申请。 + level, ok := command.Levels.LevelForTrack(pool.LevelTrack) + if !ok || level < pool.MinLevel || level > pool.MaxLevel { + return userdomain.Identity{}, xerr.New(xerr.PermissionDenied, "user level is not allowed for pretty display id pool") + } + + // 用户行最后锁定,后续替换展示号时以 users 当前快照为准释放旧靓号或挂起默认短号。 + user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.UserID) + if err == sql.ErrNoRows { + return userdomain.Identity{}, xerr.New(xerr.NotFound, "user not found") + } + if err != nil { + return userdomain.Identity{}, err + } + + // 池申请默认长期持有,expires_at_ms=0;替换当前靓号和写入租约先完成,再把池号码标记为 assigned。 + identity, err := r.replacePrettyInTx(ctx, tx, user, prettyAssignment{ + PrettyID: item.PrettyID, + LeaseID: command.LeaseID, + DisplayUserID: item.DisplayUserID, + ExpiresAtMs: 0, + Source: userdomain.PrettyDisplayIDSourcePool, + ChangeType: "pretty_pool_apply", + Reason: "pretty_pool_apply", + OperatorUserID: command.UserID, + RequestID: command.RequestID, + NowMs: command.NowMs, + }) + if err != nil { + return userdomain.Identity{}, err + } + + // WHERE status=available 是最后一道并发保护,确保只有锁住并仍可用的池号会被分配。 + _, err = tx.ExecContext(ctx, ` + UPDATE pretty_display_ids + SET status = ?, assigned_user_id = ?, assigned_lease_id = ?, assigned_at_ms = ?, + released_at_ms = 0, release_reason = '', updated_at_ms = ? + WHERE app_code = ? AND pretty_id = ? AND status = ? + `, userdomain.PrettyDisplayIDStatusAssigned, command.UserID, command.LeaseID, command.NowMs, command.NowMs, appCode, command.PrettyID, userdomain.PrettyDisplayIDStatusAvailable) + if err != nil { + return userdomain.Identity{}, shared.MapPrettyDuplicateError(err) + } + if err := tx.Commit(); err != nil { + return userdomain.Identity{}, err + } + return identity, nil } // ExpirePrettyDisplayUserID 把到期靓号恢复成默认短号;该方法是定时和懒过期的共同事务。 - func (r *Repository) ExpirePrettyDisplayUserID(ctx context.Context, command userdomain.ExpirePrettyDisplayUserIDCommand) (userdomain.Identity, error) { - // 定时过期、懒过期和手动过期都复用同一事务入口。 tx, err := r.db.BeginTx(ctx, nil) if err != nil { return userdomain.Identity{}, err @@ -130,7 +228,6 @@ func (r *Repository) ExpirePrettyDisplayUserID(ctx context.Context, command user user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.UserID) if err == sql.ErrNoRows { - // 用户不存在时不能恢复短号。 return userdomain.Identity{}, xerr.New(xerr.NotFound, "user not found") } if err != nil { @@ -149,7 +246,7 @@ func (r *Repository) ExpirePrettyDisplayUserID(ctx context.Context, command user } func (r *Repository) expirePrettyByDisplayUserID(ctx context.Context, displayUserID string, nowMs int64, requestID string) error { - // 只找可能受输入短号影响的过期 pretty 用户,避免每次解析全表扫描。 + // 申请同名靓号前只处理已经到期的占用;未到期的冲突仍交给唯一性校验返回不可用。 var userID int64 err := r.db.QueryRowContext(ctx, ` SELECT user_id @@ -157,12 +254,12 @@ func (r *Repository) expirePrettyByDisplayUserID(ctx context.Context, displayUse WHERE app_code = ? AND current_display_user_id_kind = ? AND current_display_user_id_expires_at_ms IS NOT NULL + AND current_display_user_id_expires_at_ms > 0 AND current_display_user_id_expires_at_ms <= ? AND (current_display_user_id = ? OR default_display_user_id = ?) LIMIT 1 `, appcode.FromContext(ctx), string(userdomain.DisplayUserIDKindPretty), nowMs, displayUserID, displayUserID).Scan(&userID) if err == sql.ErrNoRows { - // 没有过期记录时无需恢复。 return nil } if err != nil { @@ -178,43 +275,34 @@ func (r *Repository) expirePrettyByDisplayUserID(ctx context.Context, displayUse } // ExpirePrettyByDisplayUserID 给认证存储复用短号懒过期事务。 - func ExpirePrettyByDisplayUserID(ctx context.Context, db *sql.DB, displayUserID string, nowMs int64, requestID string) error { return New(db).expirePrettyByDisplayUserID(ctx, displayUserID, nowMs, requestID) } func (r *Repository) expirePrettyInTx(ctx context.Context, tx *sql.Tx, user userdomain.User, leaseID string, nowMs int64, requestID string) (userdomain.Identity, error) { - // 调用方必须已经锁住 users 行;本函数继续锁 active lease 行。 if user.CurrentDisplayUserIDKind != userdomain.DisplayUserIDKindPretty { - // 非 pretty 状态重复过期按幂等成功返回当前身份。 + // 当前已经是默认短号时,过期恢复是幂等操作,直接返回现有身份。 return user.EffectiveIdentity(), nil } - if user.CurrentDisplayUserIDExpiresAtMs > nowMs { - // 未到期时拒绝恢复,防止提前释放已购买靓号。 + if user.CurrentDisplayUserIDExpiresAtMs == 0 || user.CurrentDisplayUserIDExpiresAtMs > nowMs { + // expires_at_ms=0 表示长期靓号,正未来时间表示尚未到期,这两类都不能被过期任务释放。 return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display_user_id lease is not expired") } - var activeLeaseID string - err := tx.QueryRowContext(ctx, ` - SELECT lease_id - FROM pretty_display_user_id_leases - WHERE app_code = ? AND user_id = ? AND status = ? - FOR UPDATE - `, appcode.FromContext(ctx), user.UserID, string(userdomain.PrettyLeaseStatusActive)).Scan(&activeLeaseID) - if err != nil && err != sql.ErrNoRows { + // 锁 active lease 后再释放,lease_id 传入时作为精确校验,避免旧定时任务误释放后来替换的新租约。 + activeLeaseID, _, err := lockActivePrettyLease(ctx, tx, appcode.FromContext(ctx), user.UserID) + if err != nil { return userdomain.Identity{}, err } if leaseID != "" && activeLeaseID != "" && leaseID != activeLeaseID { - // 指定 lease_id 不匹配 active lease 时拒绝,防止旧任务误释放新租约。 return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display_user_id lease is not active") } - // 租约、pretty 短号记录、默认短号记录和 users 快照一起恢复。 _, err = tx.ExecContext(ctx, ` UPDATE pretty_display_user_id_leases - SET status = ?, updated_at_ms = ? + SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ? WHERE app_code = ? AND user_id = ? AND status = ? - `, string(userdomain.PrettyLeaseStatusExpired), nowMs, appcode.FromContext(ctx), user.UserID, string(userdomain.PrettyLeaseStatusActive)) + `, string(userdomain.PrettyLeaseStatusExpired), nowMs, "pretty_expire", nowMs, appcode.FromContext(ctx), user.UserID, string(userdomain.PrettyLeaseStatusActive)) if err != nil { return userdomain.Identity{}, err } @@ -227,7 +315,14 @@ func (r *Repository) expirePrettyInTx(ctx context.Context, tx *sql.Tx, user user if err != nil { return userdomain.Identity{}, err } + if activeLeaseID != "" { + // 池号和后台发放号释放后的状态不同,统一通过 lease 反查池表行处理。 + if err := releasePrettyDisplayIDByLeaseInTx(ctx, tx, appcode.FromContext(ctx), activeLeaseID, nowMs, "pretty_expire"); err != nil { + return userdomain.Identity{}, err + } + } + // 默认短号在用户使用靓号期间保持 held;过期恢复只把这条默认号重新激活,不重新生成短号。 _, err = tx.ExecContext(ctx, ` UPDATE user_display_user_ids SET status = ?, activated_at_ms = ?, updated_at_ms = ? @@ -271,3 +366,675 @@ func (r *Repository) expirePrettyInTx(ctx context.Context, tx *sql.Tx, user user Status: userdomain.DisplayUserIDStatusActive, }, nil } + +func (r *Repository) replacePrettyInTx(ctx context.Context, tx *sql.Tx, user userdomain.User, assignment prettyAssignment) (userdomain.Identity, error) { + appCode := appcode.FromContext(ctx) + if user.DisplayUserIDExpired(assignment.NowMs) { + // 替换前先在同一事务内清理已过期靓号,把 users 快照恢复到默认号,后续日志的 old_id 才准确。 + identity, err := r.expirePrettyInTx(ctx, tx, user, "", assignment.NowMs, assignment.RequestID) + if err != nil { + return userdomain.Identity{}, err + } + user.CurrentDisplayUserID = identity.DisplayUserID + user.CurrentDisplayUserIDKind = identity.DisplayUserIDKind + user.CurrentDisplayUserIDExpiresAtMs = 0 + user.PrettyID = "" + user.PrettyDisplayUserID = "" + } + + if user.CurrentDisplayUserIDKind == userdomain.DisplayUserIDKindPretty { + // 用户当前已有 active 靓号时,新靓号直接覆盖旧靓号,旧 lease 必须先取消并记录释放原因。 + activeLeaseID, _, err := lockActivePrettyLease(ctx, tx, appCode, user.UserID) + if err != nil { + return userdomain.Identity{}, err + } + if activeLeaseID == "" { + return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDLeaseExpired, "pretty display_user_id lease is not active") + } + _, err = tx.ExecContext(ctx, ` + UPDATE pretty_display_user_id_leases + SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ? + WHERE app_code = ? AND lease_id = ? AND status = ? + `, string(userdomain.PrettyLeaseStatusCancelled), assignment.NowMs, "replaced_by_new_pretty", assignment.NowMs, appCode, activeLeaseID, string(userdomain.PrettyLeaseStatusActive)) + if err != nil { + return userdomain.Identity{}, err + } + // active 的 user_display_user_ids 代表当前展示号占用,覆盖时释放该占用,避免同一用户出现两个 active 靓号。 + _, err = tx.ExecContext(ctx, ` + UPDATE user_display_user_ids + SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ? + WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ? + `, string(userdomain.DisplayUserIDStatusReleased), assignment.NowMs, "replaced_by_new_pretty", assignment.NowMs, appCode, user.UserID, user.CurrentDisplayUserID, string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive)) + if err != nil { + return userdomain.Identity{}, err + } + // 旧靓号对应的池表记录按来源释放:池号回可申请,后台发放号保留 released 审计态。 + if err := releasePrettyDisplayIDByLeaseInTx(ctx, tx, appCode, activeLeaseID, assignment.NowMs, "replaced_by_new_pretty"); err != nil { + return userdomain.Identity{}, err + } + } else { + // 用户从默认短号切到靓号时,不删除默认短号,只把它挂起,后续过期或释放可以原样恢复。 + _, err := tx.ExecContext(ctx, ` + UPDATE user_display_user_ids + SET status = ?, updated_at_ms = ? + WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ? + `, string(userdomain.DisplayUserIDStatusHeld), assignment.NowMs, appCode, user.UserID, user.DefaultDisplayUserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive)) + if err != nil { + return userdomain.Identity{}, err + } + } + + // 租约是靓号生命周期的主记录;长期靓号 expires_at_ms=0,限时靓号写入明确到期时间。 + _, err := tx.ExecContext(ctx, ` + INSERT INTO pretty_display_user_id_leases ( + app_code, lease_id, display_user_id, user_id, status, starts_at_ms, expires_at_ms, + released_at_ms, release_reason, payment_receipt_id, source, created_at_ms, updated_at_ms + ) + VALUES (?, ?, ?, ?, ?, ?, ?, 0, '', ?, ?, ?, ?) + `, appCode, assignment.LeaseID, assignment.DisplayUserID, user.UserID, string(userdomain.PrettyLeaseStatusActive), assignment.NowMs, assignment.ExpiresAtMs, shared.NullableString(assignment.PaymentReceiptID), assignment.Source, assignment.NowMs, assignment.NowMs) + if err != nil { + return userdomain.Identity{}, shared.MapPrettyDuplicateError(err) + } + + // 当前展示号占用单独写入 user_display_user_ids,和 users 快照互相校验,便于全局唯一性检查。 + _, err = tx.ExecContext(ctx, ` + INSERT INTO user_display_user_ids ( + app_code, display_user_id, user_id, display_user_id_kind, status, + assigned_at_ms, activated_at_ms, expires_at_ms, created_at_ms, updated_at_ms + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appCode, assignment.DisplayUserID, user.UserID, string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive), assignment.NowMs, assignment.NowMs, assignment.ExpiresAtMs, assignment.NowMs, assignment.NowMs) + if err != nil { + return userdomain.Identity{}, shared.MapPrettyDuplicateError(err) + } + + // users 表保存高频读取快照,资料接口不用每次 join 租约表也能返回当前展示号和到期时间。 + _, err = tx.ExecContext(ctx, ` + UPDATE users + SET current_display_user_id = ?, current_display_user_id_kind = ?, current_display_user_id_expires_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND user_id = ? + `, assignment.DisplayUserID, string(userdomain.DisplayUserIDKindPretty), assignment.ExpiresAtMs, assignment.NowMs, appCode, user.UserID) + if err != nil { + return userdomain.Identity{}, shared.MapPrettyDuplicateError(err) + } + + // 展示号变更日志记录 old/new、操作者、请求 ID,覆盖、池申请、后台发放和旧付费接口都复用同一审计结构。 + if err := insertDisplayUserIDChangeLog(ctx, tx, displayUserIDLog{ + AppCode: appCode, + UserID: user.UserID, + OldID: user.CurrentDisplayUserID, + NewID: assignment.DisplayUserID, + OldKind: user.CurrentDisplayUserIDKind, + NewKind: userdomain.DisplayUserIDKindPretty, + ChangeType: assignment.ChangeType, + LeaseID: assignment.LeaseID, + Reason: assignment.Reason, + OperatorID: assignment.OperatorUserID, + RequestID: assignment.RequestID, + CreatedAtMs: assignment.NowMs, + }); err != nil { + return userdomain.Identity{}, err + } + + return userdomain.Identity{ + AppCode: appCode, + UserID: user.UserID, + DisplayUserID: assignment.DisplayUserID, + DefaultDisplayUserID: user.DefaultDisplayUserID, + DisplayUserIDKind: userdomain.DisplayUserIDKindPretty, + DisplayUserIDExpiresAtMs: assignment.ExpiresAtMs, + PrettyID: assignment.PrettyID, + PrettyDisplayUserID: assignment.DisplayUserID, + Status: userdomain.DisplayUserIDStatusActive, + }, nil +} + +func lockActivePrettyLease(ctx context.Context, tx *sql.Tx, appCode string, userID int64) (string, string, error) { + var leaseID string + var displayUserID string + err := tx.QueryRowContext(ctx, ` + SELECT lease_id, display_user_id + FROM pretty_display_user_id_leases + WHERE app_code = ? AND user_id = ? AND status = ? + FOR UPDATE + `, appcode.Normalize(appCode), userID, string(userdomain.PrettyLeaseStatusActive)).Scan(&leaseID, &displayUserID) + if err == sql.ErrNoRows { + return "", "", nil + } + if err != nil { + return "", "", err + } + return leaseID, displayUserID, nil +} + +func releasePrettyDisplayIDByLeaseInTx(ctx context.Context, tx *sql.Tx, appCode string, leaseID string, nowMs int64, reason string) error { + // source=pool 的号码释放后重新进入池;后台手动发放的自由靓号没有池归属,释放后进入 released 防止自动复用。 + _, err := tx.ExecContext(ctx, ` + UPDATE pretty_display_ids + SET status = CASE + WHEN source = ? THEN ? + ELSE ? + END, + assigned_user_id = 0, + assigned_lease_id = '', + assigned_at_ms = 0, + released_at_ms = ?, + release_reason = ?, + updated_at_ms = ? + WHERE app_code = ? AND assigned_lease_id = ? AND status = ? + `, userdomain.PrettyDisplayIDSourcePool, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDStatusReleased, nowMs, reason, nowMs, appcode.Normalize(appCode), leaseID, userdomain.PrettyDisplayIDStatusAssigned) + return err +} + +func (r *Repository) assertDisplayUserIDGloballyAvailable(ctx context.Context, tx *sql.Tx, displayUserID string) error { + // users 表同时覆盖真实 user_id、默认短号和当前展示号,避免管理员发放成任何用户已使用的 ID。 + var exists int + err := tx.QueryRowContext(ctx, ` + SELECT 1 + FROM users + WHERE app_code = ? + AND (CAST(user_id AS CHAR) = ? OR default_display_user_id = ? OR current_display_user_id = ?) + LIMIT 1 + `, appcode.FromContext(ctx), displayUserID, displayUserID, displayUserID).Scan(&exists) + if err != nil && err != sql.ErrNoRows { + return err + } + if exists == 1 { + return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists") + } + + // user_display_user_ids 覆盖 active/held/reserved/blocked 历史占用,默认短号 held 时也不能被靓号抢占。 + err = tx.QueryRowContext(ctx, ` + SELECT 1 + FROM user_display_user_ids + WHERE app_code = ? AND display_user_id = ? AND status IN (?, ?, ?, ?) + LIMIT 1 + `, appcode.FromContext(ctx), displayUserID, string(userdomain.DisplayUserIDStatusActive), string(userdomain.DisplayUserIDStatusHeld), string(userdomain.DisplayUserIDStatusReserved), string(userdomain.DisplayUserIDStatusBlocked)).Scan(&exists) + if err != nil && err != sql.ErrNoRows { + return err + } + if exists == 1 { + return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists") + } + + // pretty_display_ids 覆盖池中未发放、已发放、禁用和预留号码,防止批量生成和后台发放互相撞号。 + err = tx.QueryRowContext(ctx, ` + SELECT 1 + FROM pretty_display_ids + WHERE app_code = ? AND display_user_id = ? AND status IN (?, ?, ?, ?) + LIMIT 1 + `, appcode.FromContext(ctx), displayUserID, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDStatusAssigned, userdomain.PrettyDisplayIDStatusDisabled, userdomain.PrettyDisplayIDStatusReserved).Scan(&exists) + if err != nil && err != sql.ErrNoRows { + return err + } + if exists == 1 { + return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists") + } + return nil +} + +// ListPrettyDisplayIDPools 分页读取后台靓号池配置。 +func (r *Repository) ListPrettyDisplayIDPools(ctx context.Context, query userdomain.PrettyDisplayIDPoolQuery) ([]userdomain.PrettyDisplayIDPool, int64, error) { + appCode := appcode.Normalize(query.AppCode) + page, pageSize := normalizeIdentityPage(query.Page, query.PageSize) + where := []string{"app_code = ?"} + args := []any{appCode} + if query.Status != "" { + where = append(where, "status = ?") + args = append(args, query.Status) + } + if query.LevelTrack != "" { + where = append(where, "level_track = ?") + args = append(args, query.LevelTrack) + } + whereSQL := "WHERE " + strings.Join(where, " AND ") + + var total int64 + if err := r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM pretty_display_id_pools "+whereSQL, args...).Scan(&total); err != nil { + return nil, 0, err + } + rows, err := r.db.QueryContext(ctx, ` + SELECT app_code, pool_id, name, level_track, min_level, max_level, rule_type, + COALESCE(CAST(rule_config_json AS CHAR), ''), status, sort_order, + created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms + FROM pretty_display_id_pools + `+whereSQL+` + ORDER BY sort_order ASC, created_at_ms DESC, pool_id ASC + LIMIT ? OFFSET ? + `, append(args, int(pageSize), int((page-1)*pageSize))...) + if err != nil { + return nil, 0, err + } + defer rows.Close() + + pools := make([]userdomain.PrettyDisplayIDPool, 0) + for rows.Next() { + pool, err := scanPrettyDisplayIDPool(rows) + if err != nil { + return nil, 0, err + } + pools = append(pools, pool) + } + return pools, total, rows.Err() +} + +func (r *Repository) CreatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) { + _, err := r.db.ExecContext(ctx, ` + INSERT INTO pretty_display_id_pools ( + app_code, pool_id, name, level_track, min_level, max_level, rule_type, + rule_config_json, status, sort_order, created_by_admin_id, updated_by_admin_id, + created_at_ms, updated_at_ms + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.Normalize(command.AppCode), command.PoolID, command.Name, command.LevelTrack, command.MinLevel, command.MaxLevel, command.RuleType, nullableJSON(command.RuleConfigJSON), command.Status, command.SortOrder, command.OperatorAdminID, command.OperatorAdminID, command.NowMs, command.NowMs) + if err != nil { + return userdomain.PrettyDisplayIDPool{}, err + } + return queryPrettyDisplayIDPool(ctx, r.db, appcode.Normalize(command.AppCode), command.PoolID) +} + +func (r *Repository) UpdatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) { + result, err := r.db.ExecContext(ctx, ` + UPDATE pretty_display_id_pools + SET name = ?, level_track = ?, min_level = ?, max_level = ?, rule_type = ?, + rule_config_json = ?, status = ?, sort_order = ?, updated_by_admin_id = ?, updated_at_ms = ? + WHERE app_code = ? AND pool_id = ? + `, command.Name, command.LevelTrack, command.MinLevel, command.MaxLevel, command.RuleType, nullableJSON(command.RuleConfigJSON), command.Status, command.SortOrder, command.OperatorAdminID, command.NowMs, appcode.Normalize(command.AppCode), command.PoolID) + if err != nil { + return userdomain.PrettyDisplayIDPool{}, err + } + if rows, _ := result.RowsAffected(); rows == 0 { + return userdomain.PrettyDisplayIDPool{}, xerr.New(xerr.NotFound, "pretty display id pool not found") + } + return queryPrettyDisplayIDPool(ctx, r.db, appcode.Normalize(command.AppCode), command.PoolID) +} + +func (r *Repository) GeneratePrettyDisplayIDs(ctx context.Context, command userdomain.PrettyDisplayIDGenerateCommand) (userdomain.PrettyDisplayIDGenerationBatch, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return userdomain.PrettyDisplayIDGenerationBatch{}, err + } + defer tx.Rollback() + + appCode := appcode.Normalize(command.AppCode) + pool, err := queryPrettyDisplayIDPoolForUpdate(ctx, tx, appCode, command.PoolID) + if err == sql.ErrNoRows { + return userdomain.PrettyDisplayIDGenerationBatch{}, xerr.New(xerr.NotFound, "pretty display id pool not found") + } + if err != nil { + return userdomain.PrettyDisplayIDGenerationBatch{}, err + } + // 生成必须锁池配置,确保管理员同时修改规则或禁用池时,本批次仍以一个确定的池状态落库。 + ruleType := command.RuleType + if ruleType == "" { + ruleType = pool.RuleType + } + + generated := int32(0) + skipped := int32(0) + if command.RuleConfigJSON == "" { + // 生成弹窗没有单独填写规则配置时,沿用靓号池保存的配置,保证“按池生成”不会丢掉排除数字等配置。 + command.RuleConfigJSON = pool.RuleConfigJSON + } + candidates, err := generatePrettyDisplayIDCandidates(ruleType, command.RuleConfigJSON) + if err != nil { + return userdomain.PrettyDisplayIDGenerationBatch{}, err + } + + // 候选按规则确定顺序扫描,遇到已有用户 ID、默认短号、当前靓号或池内号码就跳过,不随机重试。 + for _, candidate := range candidates { + if generated >= command.Count { + break + } + if err := r.assertDisplayUserIDGloballyAvailable(ctx, tx, candidate); err != nil { + if xerr.IsCode(err, xerr.DisplayUserIDExists) { + skipped++ + continue + } + return userdomain.PrettyDisplayIDGenerationBatch{}, err + } + _, err := tx.ExecContext(ctx, ` + INSERT INTO pretty_display_ids ( + app_code, pretty_id, pool_id, source, display_user_id, status, + generated_batch_id, created_by_admin_id, created_at_ms, updated_at_ms + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appCode, idgen.New("pretty"), command.PoolID, userdomain.PrettyDisplayIDSourcePool, candidate, userdomain.PrettyDisplayIDStatusAvailable, command.BatchID, command.OperatorAdminID, command.NowMs, command.NowMs) + if err != nil { + // 唯一键冲突和显式查重一样计入 skipped,保证并发生成时不会因为个别撞号中断整个批次。 + if xerr.IsCode(shared.MapPrettyDuplicateError(err), xerr.DisplayUserIDPrettyNotAvailable) { + skipped++ + continue + } + return userdomain.PrettyDisplayIDGenerationBatch{}, err + } + generated++ + } + if generated < command.Count { + // 候选空间被耗尽时也要记录缺口,便于后台看到“请求 1000 个但实际生成不足”的原因。 + shortfall := command.Count - generated + if shortfall > skipped { + skipped = shortfall + } + } + + // 批次记录和号码记录同事务提交,后台列表能用 batch_id 追溯一次生成实际落了哪些号码。 + _, err = tx.ExecContext(ctx, ` + INSERT INTO pretty_display_id_generation_batches ( + app_code, batch_id, pool_id, rule_type, rule_config_json, requested_count, + generated_count, skipped_conflict_count, status, operator_admin_id, + request_id, created_at_ms, updated_at_ms + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appCode, command.BatchID, command.PoolID, ruleType, nullableJSON(command.RuleConfigJSON), command.Count, generated, skipped, userdomain.PrettyDisplayIDBatchStatusCompleted, command.OperatorAdminID, command.RequestID, command.NowMs, command.NowMs) + if err != nil { + return userdomain.PrettyDisplayIDGenerationBatch{}, err + } + if err := tx.Commit(); err != nil { + return userdomain.PrettyDisplayIDGenerationBatch{}, err + } + return userdomain.PrettyDisplayIDGenerationBatch{ + AppCode: appCode, + BatchID: command.BatchID, + PoolID: command.PoolID, + RuleType: ruleType, + RuleConfigJSON: command.RuleConfigJSON, + RequestedCount: command.Count, + GeneratedCount: generated, + SkippedConflictCount: skipped, + Status: userdomain.PrettyDisplayIDBatchStatusCompleted, + OperatorAdminID: command.OperatorAdminID, + RequestID: command.RequestID, + CreatedAtMs: command.NowMs, + UpdatedAtMs: command.NowMs, + }, nil +} + +func (r *Repository) ListPrettyDisplayIDs(ctx context.Context, query userdomain.PrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) { + appCode := appcode.Normalize(query.AppCode) + page, pageSize := normalizeIdentityPage(query.Page, query.PageSize) + where := []string{"pdi.app_code = ?"} + args := []any{appCode} + if query.PoolID != "" { + where = append(where, "pdi.pool_id = ?") + args = append(args, query.PoolID) + } + if query.Source != "" { + where = append(where, "pdi.source = ?") + args = append(args, query.Source) + } + if query.Status != "" { + where = append(where, "pdi.status = ?") + args = append(args, query.Status) + } + if query.AssignedUserID > 0 { + where = append(where, "pdi.assigned_user_id = ?") + args = append(args, query.AssignedUserID) + } + if query.Keyword != "" { + where = append(where, "(pdi.pretty_id LIKE ? OR pdi.display_user_id LIKE ?)") + keyword := "%" + query.Keyword + "%" + args = append(args, keyword, keyword) + } + whereSQL := "WHERE " + strings.Join(where, " AND ") + + var total int64 + if err := r.db.QueryRowContext(ctx, ` + SELECT COUNT(*) + FROM pretty_display_ids pdi + LEFT JOIN pretty_display_id_pools pool + ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id + `+whereSQL, args...).Scan(&total); err != nil { + return nil, 0, err + } + rows, err := r.db.QueryContext(ctx, ` + SELECT `+prettyDisplayIDSelectColumns()+` + FROM pretty_display_ids pdi + LEFT JOIN pretty_display_id_pools pool + ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id + `+whereSQL+` + ORDER BY pdi.created_at_ms DESC, pdi.pretty_id DESC + LIMIT ? OFFSET ? + `, append(args, int(pageSize), int((page-1)*pageSize))...) + if err != nil { + return nil, 0, err + } + defer rows.Close() + items, err := scanPrettyDisplayIDRows(rows) + if err != nil { + return nil, 0, err + } + return items, total, rows.Err() +} + +func (r *Repository) SetPrettyDisplayIDStatus(ctx context.Context, command userdomain.PrettyDisplayIDStatusCommand) (userdomain.PrettyDisplayID, error) { + // 状态切换只允许未占用号码,assigned 号码必须通过覆盖、过期或释放链路改变,避免后台直接改坏用户当前展示号。 + result, err := r.db.ExecContext(ctx, ` + UPDATE pretty_display_ids + SET status = ?, release_reason = ?, updated_at_ms = ? + WHERE app_code = ? AND pretty_id = ? + AND assigned_user_id = 0 + AND assigned_lease_id = '' + AND status IN (?, ?, ?) + `, command.Status, command.Reason, command.NowMs, appcode.Normalize(command.AppCode), command.PrettyID, userdomain.PrettyDisplayIDStatusAvailable, userdomain.PrettyDisplayIDStatusDisabled, userdomain.PrettyDisplayIDStatusReserved) + if err != nil { + return userdomain.PrettyDisplayID{}, err + } + if rows, _ := result.RowsAffected(); rows == 0 { + return userdomain.PrettyDisplayID{}, xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display id status can not be changed") + } + return queryPrettyDisplayID(ctx, r.db, appcode.Normalize(command.AppCode), command.PrettyID) +} + +func (r *Repository) AdminGrantPrettyDisplayID(ctx context.Context, command userdomain.AdminGrantPrettyDisplayIDCommand) (userdomain.Identity, string, error) { + // 后台发放可以输入任意合规内容;如果同名旧限时靓号已到期,先懒过期释放后再做全局唯一性校验。 + if err := r.expirePrettyByDisplayUserID(ctx, command.DisplayUserID, command.NowMs, command.RequestID); err != nil { + return userdomain.Identity{}, "", err + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return userdomain.Identity{}, "", err + } + defer tx.Rollback() + + // 后台发放不经过号码池,但仍要和用户真实 ID、默认短号、当前展示号、池号和保留号做全局冲突检查。 + if err := r.assertDisplayUserIDGloballyAvailable(ctx, tx, command.DisplayUserID); err != nil { + return userdomain.Identity{}, "", err + } + // 锁目标用户后再插入租约和更新 users,确保同一用户同时被发放或申请池号时只有一个事务成功替换。 + user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.TargetUserID) + if err == sql.ErrNoRows { + return userdomain.Identity{}, "", xerr.New(xerr.NotFound, "user not found") + } + if err != nil { + return userdomain.Identity{}, "", err + } + + expiresAtMs := int64(0) + if command.DurationMs > 0 { + // duration_ms<=0 在服务层表示长期;只有正数才转换成明确过期时间,供懒过期和定时恢复使用。 + expiresAtMs = command.NowMs + command.DurationMs + } + // 自由输入的后台靓号也先写入 pretty_display_ids,并直接标记 assigned,便于列表、审计和后续释放统一处理。 + _, err = tx.ExecContext(ctx, ` + INSERT INTO pretty_display_ids ( + app_code, pretty_id, pool_id, source, display_user_id, status, + assigned_user_id, assigned_lease_id, assigned_at_ms, created_by_admin_id, + created_at_ms, updated_at_ms + ) + VALUES (?, ?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.Normalize(command.AppCode), command.PrettyID, userdomain.PrettyDisplayIDSourceAdminGrant, command.DisplayUserID, userdomain.PrettyDisplayIDStatusAssigned, command.TargetUserID, command.LeaseID, command.NowMs, command.OperatorAdminID, command.NowMs, command.NowMs) + if err != nil { + return userdomain.Identity{}, "", shared.MapPrettyDuplicateError(err) + } + + // 真正覆盖用户当前展示号仍走统一替换事务,确保旧池号释放、旧后台号 released 和默认号 held 行为完全一致。 + identity, err := r.replacePrettyInTx(ctx, tx, user, prettyAssignment{ + PrettyID: command.PrettyID, + LeaseID: command.LeaseID, + DisplayUserID: command.DisplayUserID, + ExpiresAtMs: expiresAtMs, + Source: userdomain.PrettyDisplayIDSourceAdminGrant, + ChangeType: "pretty_admin_grant", + Reason: command.Reason, + OperatorUserID: command.OperatorAdminID, + RequestID: command.RequestID, + NowMs: command.NowMs, + }) + if err != nil { + return userdomain.Identity{}, "", err + } + if err := tx.Commit(); err != nil { + return userdomain.Identity{}, "", err + } + return identity, command.LeaseID, nil +} + +func normalizeIdentityPage(page int32, pageSize int32) (int32, int32) { + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 20 + } + if pageSize > 100 { + pageSize = 100 + } + return page, pageSize +} + +func nullableJSON(value string) any { + if strings.TrimSpace(value) == "" { + return nil + } + return value +} + +type rowScanner interface { + Scan(dest ...any) error +} + +func scanPrettyDisplayIDPool(scanner rowScanner) (userdomain.PrettyDisplayIDPool, error) { + var pool userdomain.PrettyDisplayIDPool + err := scanner.Scan( + &pool.AppCode, + &pool.PoolID, + &pool.Name, + &pool.LevelTrack, + &pool.MinLevel, + &pool.MaxLevel, + &pool.RuleType, + &pool.RuleConfigJSON, + &pool.Status, + &pool.SortOrder, + &pool.CreatedByAdminID, + &pool.UpdatedByAdminID, + &pool.CreatedAtMs, + &pool.UpdatedAtMs, + ) + return pool, err +} + +func queryPrettyDisplayIDPool(ctx context.Context, q shared.Queryer, appCode string, poolID string) (userdomain.PrettyDisplayIDPool, error) { + return scanPrettyDisplayIDPool(q.QueryRowContext(ctx, ` + SELECT app_code, pool_id, name, level_track, min_level, max_level, rule_type, + COALESCE(CAST(rule_config_json AS CHAR), ''), status, sort_order, + created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms + FROM pretty_display_id_pools + WHERE app_code = ? AND pool_id = ? + `, appCode, poolID)) +} + +func queryPrettyDisplayIDPoolForUpdate(ctx context.Context, tx *sql.Tx, appCode string, poolID string) (userdomain.PrettyDisplayIDPool, error) { + return scanPrettyDisplayIDPool(tx.QueryRowContext(ctx, ` + SELECT app_code, pool_id, name, level_track, min_level, max_level, rule_type, + COALESCE(CAST(rule_config_json AS CHAR), ''), status, sort_order, + created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms + FROM pretty_display_id_pools + WHERE app_code = ? AND pool_id = ? + FOR UPDATE + `, appCode, poolID)) +} + +func prettyDisplayIDSelectColumns() string { + return ` + pdi.app_code, pdi.pretty_id, pdi.pool_id, pdi.source, pdi.display_user_id, + pdi.status, pdi.assigned_user_id, pdi.assigned_lease_id, pdi.assigned_at_ms, + pdi.released_at_ms, pdi.release_reason, pdi.generated_batch_id, + pdi.created_by_admin_id, pdi.created_at_ms, pdi.updated_at_ms, + COALESCE(pool.app_code, ''), COALESCE(pool.pool_id, ''), COALESCE(pool.name, ''), + COALESCE(pool.level_track, ''), COALESCE(pool.min_level, 0), COALESCE(pool.max_level, 0), + COALESCE(pool.rule_type, ''), COALESCE(CAST(pool.rule_config_json AS CHAR), ''), + COALESCE(pool.status, ''), COALESCE(pool.sort_order, 0), + COALESCE(pool.created_by_admin_id, 0), COALESCE(pool.updated_by_admin_id, 0), + COALESCE(pool.created_at_ms, 0), COALESCE(pool.updated_at_ms, 0)` +} + +func scanPrettyDisplayIDRows(rows *sql.Rows) ([]userdomain.PrettyDisplayID, error) { + items := make([]userdomain.PrettyDisplayID, 0) + for rows.Next() { + item, err := scanPrettyDisplayID(rows) + if err != nil { + return nil, err + } + items = append(items, item) + } + return items, rows.Err() +} + +func scanPrettyDisplayID(scanner rowScanner) (userdomain.PrettyDisplayID, error) { + var item userdomain.PrettyDisplayID + err := scanner.Scan( + &item.AppCode, + &item.PrettyID, + &item.PoolID, + &item.Source, + &item.DisplayUserID, + &item.Status, + &item.AssignedUserID, + &item.AssignedLeaseID, + &item.AssignedAtMs, + &item.ReleasedAtMs, + &item.ReleaseReason, + &item.GeneratedBatchID, + &item.CreatedByAdminID, + &item.CreatedAtMs, + &item.UpdatedAtMs, + &item.Pool.AppCode, + &item.Pool.PoolID, + &item.Pool.Name, + &item.Pool.LevelTrack, + &item.Pool.MinLevel, + &item.Pool.MaxLevel, + &item.Pool.RuleType, + &item.Pool.RuleConfigJSON, + &item.Pool.Status, + &item.Pool.SortOrder, + &item.Pool.CreatedByAdminID, + &item.Pool.UpdatedByAdminID, + &item.Pool.CreatedAtMs, + &item.Pool.UpdatedAtMs, + ) + return item, err +} + +func queryPrettyDisplayID(ctx context.Context, q shared.Queryer, appCode string, prettyID string) (userdomain.PrettyDisplayID, error) { + return scanPrettyDisplayID(q.QueryRowContext(ctx, ` + SELECT `+prettyDisplayIDSelectColumns()+` + FROM pretty_display_ids pdi + LEFT JOIN pretty_display_id_pools pool + ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id + WHERE pdi.app_code = ? AND pdi.pretty_id = ? + `, appCode, prettyID)) +} + +func queryPrettyDisplayIDForUpdate(ctx context.Context, tx *sql.Tx, appCode string, prettyID string) (userdomain.PrettyDisplayID, error) { + return scanPrettyDisplayID(tx.QueryRowContext(ctx, ` + SELECT `+prettyDisplayIDSelectColumns()+` + FROM pretty_display_ids pdi + LEFT JOIN pretty_display_id_pools pool + ON pool.app_code = pdi.app_code AND pool.pool_id = pdi.pool_id + WHERE pdi.app_code = ? AND pdi.pretty_id = ? + FOR UPDATE + `, appCode, prettyID)) +} diff --git a/services/user-service/internal/storage/mysql/identity/pretty_rules.go b/services/user-service/internal/storage/mysql/identity/pretty_rules.go new file mode 100644 index 00000000..9fee7daf --- /dev/null +++ b/services/user-service/internal/storage/mysql/identity/pretty_rules.go @@ -0,0 +1,365 @@ +package identity + +import ( + "encoding/json" + "fmt" + "strings" + "time" + "unicode/utf8" + + "hyapp/pkg/xerr" + userdomain "hyapp/services/user-service/internal/domain/user" +) + +type prettyRuleConfig struct { + // Digits 限制数字规则可使用的数字,例如 ["6","8","9"];为空时使用 0-9。 + Digits []string `json:"digits"` + // ExcludeDigits 用于排除 4 等运营常见不投放数字,排除后再进入规则组合。 + ExcludeDigits []string `json:"exclude_digits"` + // Values 是 custom_values 的固定候选,适合导入运营已经挑好的靓号。 + Values []string `json:"values"` + // Pattern 是 custom_pattern 的模式,例如 AABBC、AAAABBBB;同一字母代表同一数字。 + Pattern string `json:"pattern"` + // Prefix/Suffix 可把数字规则包装成 VIP888、888HY 等字符串靓号。 + Prefix string `json:"prefix"` + Suffix string `json:"suffix"` + // YearFrom/YearTo 控制日期号范围,未填写时覆盖常见生日和纪念日区间。 + YearFrom int `json:"year_from"` + YearTo int `json:"year_to"` + // StartDate/EndDate 用于精确限制日期号范围,格式固定为 yyyy-mm-dd。 + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` +} + +func generatePrettyDisplayIDCandidates(ruleType string, configJSON string) ([]string, error) { + config, err := parsePrettyRuleConfig(configJSON) + if err != nil { + return nil, err + } + digits, err := config.availableDigits() + if err != nil { + return nil, err + } + + var raw []string + switch ruleType { + case userdomain.PrettyDisplayIDRuleAA: + raw = repeatDigitCandidates(digits, 2) + case userdomain.PrettyDisplayIDRuleAAA: + raw = repeatDigitCandidates(digits, 3) + case userdomain.PrettyDisplayIDRuleAAAA: + raw = repeatDigitCandidates(digits, 4) + case userdomain.PrettyDisplayIDRuleAAAAA: + raw = repeatDigitCandidates(digits, 5) + case userdomain.PrettyDisplayIDRuleAAAAAA: + raw = repeatDigitCandidates(digits, 6) + case userdomain.PrettyDisplayIDRuleAAAAAAA: + raw = repeatDigitCandidates(digits, 7) + case userdomain.PrettyDisplayIDRuleAABB: + raw = patternDigitCandidates("aabb", digits) + case userdomain.PrettyDisplayIDRuleAABBCC: + // 旧规则按 000000-999999 生成 1000 个候选,保留 A/B/C 可相同的兼容语义。 + raw = patternCandidates("aabbcc", digits, false) + case userdomain.PrettyDisplayIDRuleAABBCCDD: + raw = patternDigitCandidates("aabbccdd", digits) + case userdomain.PrettyDisplayIDRuleABAB: + raw = patternDigitCandidates("abab", digits) + case userdomain.PrettyDisplayIDRuleABABAB: + raw = patternDigitCandidates("ababab", digits) + case userdomain.PrettyDisplayIDRuleABBA: + raw = patternDigitCandidates("abba", digits) + case userdomain.PrettyDisplayIDRuleABCBA: + raw = patternDigitCandidates("abcba", digits) + case userdomain.PrettyDisplayIDRuleABCCBA: + raw = patternDigitCandidates("abccba", digits) + case userdomain.PrettyDisplayIDRuleAAAB: + raw = patternDigitCandidates("aaab", digits) + case userdomain.PrettyDisplayIDRuleAAAAB: + raw = patternDigitCandidates("aaaab", digits) + case userdomain.PrettyDisplayIDRuleAAAAAB: + raw = patternDigitCandidates("aaaaab", digits) + case userdomain.PrettyDisplayIDRuleAAABBB: + raw = patternDigitCandidates("aaabbb", digits) + case userdomain.PrettyDisplayIDRuleAAAABBBB: + raw = patternDigitCandidates("aaaabbbb", digits) + case userdomain.PrettyDisplayIDRuleABC: + raw = sequenceDigitCandidates(digits, 3, false) + case userdomain.PrettyDisplayIDRuleABCD: + raw = sequenceDigitCandidates(digits, 4, false) + case userdomain.PrettyDisplayIDRuleABCDE: + raw = sequenceDigitCandidates(digits, 5, false) + case userdomain.PrettyDisplayIDRuleABCDEF: + raw = sequenceDigitCandidates(digits, 6, false) + case userdomain.PrettyDisplayIDRuleCBA: + raw = sequenceDigitCandidates(digits, 3, true) + case userdomain.PrettyDisplayIDRuleDCBA: + raw = sequenceDigitCandidates(digits, 4, true) + case userdomain.PrettyDisplayIDRuleEDCBA: + raw = sequenceDigitCandidates(digits, 5, true) + case userdomain.PrettyDisplayIDRuleFEDCBA: + raw = sequenceDigitCandidates(digits, 6, true) + case userdomain.PrettyDisplayIDRuleABCABC: + raw = repeatSequenceCandidates(sequenceDigitCandidates(digits, 3, false), 2) + case userdomain.PrettyDisplayIDRuleDateYYYYMMDD: + raw, err = dateCandidates(config, "20060102") + case userdomain.PrettyDisplayIDRuleDateYYMMDD: + raw, err = dateCandidates(config, "060102") + case userdomain.PrettyDisplayIDRuleLove: + raw = []string{"520", "521", "1314", "920", "921", "1711", "9421", "3344", "5201314", "5211314", "1314520", "1314521"} + case userdomain.PrettyDisplayIDRuleCustomValues: + raw = config.Values + case userdomain.PrettyDisplayIDRuleCustomPattern: + raw, err = customPatternCandidates(config.Pattern, digits) + default: + return nil, xerr.New(xerr.InvalidArgument, "rule_type is invalid") + } + if err != nil { + return nil, err + } + candidates := finalizePrettyCandidates(raw, config) + if len(candidates) == 0 { + return nil, xerr.New(xerr.InvalidArgument, "pretty display id rule has no candidates") + } + return candidates, nil +} + +func parsePrettyRuleConfig(configJSON string) (prettyRuleConfig, error) { + var config prettyRuleConfig + if strings.TrimSpace(configJSON) == "" { + return config, nil + } + if err := json.Unmarshal([]byte(configJSON), &config); err != nil { + return prettyRuleConfig{}, xerr.New(xerr.InvalidArgument, "rule_config_json is invalid") + } + config.Pattern = strings.TrimSpace(config.Pattern) + config.Prefix = strings.TrimSpace(config.Prefix) + config.Suffix = strings.TrimSpace(config.Suffix) + config.StartDate = strings.TrimSpace(config.StartDate) + config.EndDate = strings.TrimSpace(config.EndDate) + return config, nil +} + +func (c prettyRuleConfig) availableDigits() ([]string, error) { + digits := c.Digits + if len(digits) == 0 { + digits = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} + } + excluded := make(map[string]struct{}, len(c.ExcludeDigits)) + for _, digit := range c.ExcludeDigits { + digit = strings.TrimSpace(digit) + if digit != "" { + excluded[digit] = struct{}{} + } + } + result := make([]string, 0, len(digits)) + seen := make(map[string]struct{}, len(digits)) + for _, digit := range digits { + digit = strings.TrimSpace(digit) + if !validASCIIDigit(digit) { + return nil, xerr.New(xerr.InvalidArgument, "rule_config_json digits is invalid") + } + if _, skip := excluded[digit]; skip { + continue + } + if _, ok := seen[digit]; ok { + continue + } + seen[digit] = struct{}{} + result = append(result, digit) + } + if len(result) == 0 { + return nil, xerr.New(xerr.InvalidArgument, "rule_config_json digits is empty") + } + return result, nil +} + +func validASCIIDigit(value string) bool { + return len(value) == 1 && value[0] >= '0' && value[0] <= '9' +} + +func repeatDigitCandidates(digits []string, count int) []string { + candidates := make([]string, 0, len(digits)) + for _, digit := range digits { + candidates = append(candidates, strings.Repeat(digit, count)) + } + return candidates +} + +func patternDigitCandidates(pattern string, digits []string) []string { + return patternCandidates(pattern, digits, true) +} + +func patternCandidates(pattern string, digits []string, distinct bool) []string { + variables := make([]rune, 0) + seenVariable := make(map[rune]struct{}) + for _, r := range pattern { + if _, ok := seenVariable[r]; ok { + continue + } + seenVariable[r] = struct{}{} + variables = append(variables, r) + } + + assignments := make(map[rune]string, len(variables)) + usedDigits := make(map[string]struct{}, len(variables)) + candidates := make([]string, 0) + var visit func(index int) + visit = func(index int) { + if index == len(variables) { + var builder strings.Builder + for _, r := range pattern { + builder.WriteString(assignments[r]) + } + candidates = append(candidates, builder.String()) + return + } + variable := variables[index] + for _, digit := range digits { + if distinct { + if _, used := usedDigits[digit]; used { + continue + } + } + assignments[variable] = digit + usedDigits[digit] = struct{}{} + visit(index + 1) + delete(assignments, variable) + delete(usedDigits, digit) + } + } + visit(0) + return candidates +} + +func sequenceDigitCandidates(digits []string, length int, descending bool) []string { + allowed := make(map[string]struct{}, len(digits)) + for _, digit := range digits { + allowed[digit] = struct{}{} + } + candidates := make([]string, 0) + for start := 0; start <= 9; start++ { + end := start + length - 1 + if end > 9 { + break + } + var builder strings.Builder + valid := true + for offset := 0; offset < length; offset++ { + value := start + offset + if descending { + value = end - offset + } + digit := fmt.Sprintf("%d", value) + if _, ok := allowed[digit]; !ok { + valid = false + break + } + builder.WriteString(digit) + } + if valid { + candidates = append(candidates, builder.String()) + } + } + return candidates +} + +func repeatSequenceCandidates(values []string, count int) []string { + candidates := make([]string, 0, len(values)) + for _, value := range values { + candidates = append(candidates, strings.Repeat(value, count)) + } + return candidates +} + +func dateCandidates(config prettyRuleConfig, layout string) ([]string, error) { + start, end, err := dateRange(config) + if err != nil { + return nil, err + } + candidates := make([]string, 0) + for current := start; !current.After(end); current = current.AddDate(0, 0, 1) { + candidates = append(candidates, current.Format(layout)) + } + return candidates, nil +} + +func dateRange(config prettyRuleConfig) (time.Time, time.Time, error) { + if config.StartDate != "" || config.EndDate != "" { + start, err := parseDateOrDefault(config.StartDate, time.Date(1950, 1, 1, 0, 0, 0, 0, time.UTC)) + if err != nil { + return time.Time{}, time.Time{}, err + } + end, err := parseDateOrDefault(config.EndDate, time.Date(2035, 12, 31, 0, 0, 0, 0, time.UTC)) + if err != nil { + return time.Time{}, time.Time{}, err + } + if end.Before(start) { + return time.Time{}, time.Time{}, xerr.New(xerr.InvalidArgument, "rule_config_json date range is invalid") + } + return start, end, nil + } + + yearFrom := config.YearFrom + yearTo := config.YearTo + if yearFrom == 0 { + yearFrom = 1950 + } + if yearTo == 0 { + yearTo = 2035 + } + if yearFrom < 1900 || yearTo > 2099 || yearTo < yearFrom { + return time.Time{}, time.Time{}, xerr.New(xerr.InvalidArgument, "rule_config_json year range is invalid") + } + return time.Date(yearFrom, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(yearTo, 12, 31, 0, 0, 0, 0, time.UTC), nil +} + +func parseDateOrDefault(value string, fallback time.Time) (time.Time, error) { + if value == "" { + return fallback, nil + } + parsed, err := time.ParseInLocation("2006-01-02", value, time.UTC) + if err != nil { + return time.Time{}, xerr.New(xerr.InvalidArgument, "rule_config_json date is invalid") + } + return parsed, nil +} + +func customPatternCandidates(pattern string, digits []string) ([]string, error) { + if pattern == "" || utf8.RuneCountInString(pattern) > 16 { + return nil, xerr.New(xerr.InvalidArgument, "rule_config_json pattern is invalid") + } + normalized := strings.ToLower(pattern) + for _, r := range normalized { + if r >= 'a' && r <= 'z' { + continue + } + return nil, xerr.New(xerr.InvalidArgument, "rule_config_json pattern is invalid") + } + return patternCandidates(normalized, digits, true), nil +} + +func finalizePrettyCandidates(raw []string, config prettyRuleConfig) []string { + candidates := make([]string, 0, len(raw)) + seen := make(map[string]struct{}, len(raw)) + for _, value := range raw { + value = strings.TrimSpace(value) + if value == "" { + continue + } + value = config.Prefix + value + config.Suffix + if !userdomain.ValidAdminPrettyDisplayUserID(value) { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + candidates = append(candidates, value) + } + return candidates +} + +func generateAABBCCCandidates() []string { + // AABBCC 历史规则一共 10*10*10=1000 个候选,包含 00、01 等前导零组合,靓号内容允许字符串形式保留前导零。 + return patternCandidates("aabbcc", []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, false) +} diff --git a/services/user-service/internal/storage/mysql/identity/pretty_test.go b/services/user-service/internal/storage/mysql/identity/pretty_test.go new file mode 100644 index 00000000..2e8c35ca --- /dev/null +++ b/services/user-service/internal/storage/mysql/identity/pretty_test.go @@ -0,0 +1,98 @@ +package identity + +import ( + "strings" + "testing" + + userdomain "hyapp/services/user-service/internal/domain/user" +) + +func TestGenerateAABBCCCandidates(t *testing.T) { + t.Parallel() + + candidates := generateAABBCCCandidates() + if len(candidates) != 1000 { + t.Fatalf("candidate count = %d, want 1000", len(candidates)) + } + + seen := make(map[string]struct{}, len(candidates)) + for _, candidate := range candidates { + if len(candidate) != 6 { + t.Fatalf("candidate %q length = %d, want 6", candidate, len(candidate)) + } + if candidate[0] != candidate[1] || candidate[2] != candidate[3] || candidate[4] != candidate[5] { + t.Fatalf("candidate %q does not match AABBCC", candidate) + } + if _, ok := seen[candidate]; ok { + t.Fatalf("candidate %q is duplicated", candidate) + } + seen[candidate] = struct{}{} + } +} + +func TestGeneratePrettyDisplayIDCandidatesForMarketRules(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + ruleType string + want []string + }{ + {name: "repeat 4A", ruleType: userdomain.PrettyDisplayIDRuleAAAA, want: []string{"0000", "1111", "2222"}}, + {name: "ABAB", ruleType: userdomain.PrettyDisplayIDRuleABAB, want: []string{"0101", "0202", "0303"}}, + {name: "ABBA", ruleType: userdomain.PrettyDisplayIDRuleABBA, want: []string{"0110", "0220", "0330"}}, + {name: "ascending", ruleType: userdomain.PrettyDisplayIDRuleABCD, want: []string{"0123", "1234", "2345"}}, + {name: "descending", ruleType: userdomain.PrettyDisplayIDRuleDCBA, want: []string{"3210", "4321", "5432"}}, + {name: "ABCABC", ruleType: userdomain.PrettyDisplayIDRuleABCABC, want: []string{"012012", "123123", "234234"}}, + {name: "love", ruleType: userdomain.PrettyDisplayIDRuleLove, want: []string{"520", "521", "1314"}}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + candidates, err := generatePrettyDisplayIDCandidates(tc.ruleType, "") + if err != nil { + t.Fatalf("generate candidates failed: %v", err) + } + for index, want := range tc.want { + if candidates[index] != want { + t.Fatalf("candidate[%d] = %q, want %q", index, candidates[index], want) + } + } + }) + } +} + +func TestGeneratePrettyDisplayIDCandidatesWithConfig(t *testing.T) { + t.Parallel() + + candidates, err := generatePrettyDisplayIDCandidates(userdomain.PrettyDisplayIDRuleAABB, `{"digits":["6","8","9"],"exclude_digits":["9"],"prefix":"VIP"}`) + if err != nil { + t.Fatalf("generate candidates failed: %v", err) + } + if strings.Join(candidates, ",") != "VIP6688,VIP8866" { + t.Fatalf("candidates = %#v", candidates) + } + + custom, err := generatePrettyDisplayIDCandidates(userdomain.PrettyDisplayIDRuleCustomValues, `{"values":["VIP2026","ملك123","VIP!"]}`) + if err != nil { + t.Fatalf("generate custom candidates failed: %v", err) + } + if strings.Join(custom, ",") != "VIP2026,ملك123" { + t.Fatalf("custom candidates = %#v", custom) + } +} + +func TestGenerateDatePrettyDisplayIDCandidates(t *testing.T) { + t.Parallel() + + candidates, err := generatePrettyDisplayIDCandidates(userdomain.PrettyDisplayIDRuleDateYYYYMMDD, `{"start_date":"2026-06-09","end_date":"2026-06-11"}`) + if err != nil { + t.Fatalf("generate date candidates failed: %v", err) + } + if strings.Join(candidates, ",") != "20260609,20260610,20260611" { + t.Fatalf("date candidates = %#v", candidates) + } +} diff --git a/services/user-service/internal/storage/mysql/user/common.go b/services/user-service/internal/storage/mysql/user/common.go index 5a92893f..c245dad3 100644 --- a/services/user-service/internal/storage/mysql/user/common.go +++ b/services/user-service/internal/storage/mysql/user/common.go @@ -29,6 +29,28 @@ const userSelectColumns = ` current_display_user_id, current_display_user_id_kind, COALESCE(current_display_user_id_expires_at_ms, 0), + COALESCE(( + SELECT pdi.pretty_id + FROM pretty_display_user_id_leases pdl + LEFT JOIN pretty_display_ids pdi + ON pdi.app_code = pdl.app_code + AND pdi.assigned_lease_id = pdl.lease_id + AND pdi.assigned_user_id = pdl.user_id + WHERE pdl.app_code = users.app_code + AND pdl.user_id = users.user_id + AND pdl.status = 'active' + ORDER BY pdl.starts_at_ms DESC + LIMIT 1 + ), ''), + COALESCE(( + SELECT pdl.display_user_id + FROM pretty_display_user_id_leases pdl + WHERE pdl.app_code = users.app_code + AND pdl.user_id = users.user_id + AND pdl.status = 'active' + ORDER BY pdl.starts_at_ms DESC + LIMIT 1 + ), ''), COALESCE(username, ''), COALESCE(gender, ''), COALESCE(country, ''), @@ -127,6 +149,8 @@ func ScanUser(scanner interface { &user.CurrentDisplayUserID, &kind, &user.CurrentDisplayUserIDExpiresAtMs, + &user.PrettyID, + &user.PrettyDisplayUserID, &user.Username, &user.Gender, &user.Country, diff --git a/services/user-service/internal/testutil/mysqltest/mysqltest.go b/services/user-service/internal/testutil/mysqltest/mysqltest.go index ee33605d..59448183 100644 --- a/services/user-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/user-service/internal/testutil/mysqltest/mysqltest.go @@ -185,6 +185,51 @@ func (r *Repository) ApplyPrettyDisplayUserID(ctx context.Context, command userd return r.Repository.IdentityRepository().ApplyPrettyDisplayUserID(ctx, command) } +// ListAvailablePrettyDisplayIDs 让测试 wrapper 继续满足靓号池可申请列表接口。 +func (r *Repository) ListAvailablePrettyDisplayIDs(ctx context.Context, query userdomain.AvailablePrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) { + return r.Repository.IdentityRepository().ListAvailablePrettyDisplayIDs(ctx, query) +} + +// ApplyPrettyDisplayIDFromPool 让测试 wrapper 继续满足靓号池申请接口。 +func (r *Repository) ApplyPrettyDisplayIDFromPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolApplyCommand) (userdomain.Identity, error) { + return r.Repository.IdentityRepository().ApplyPrettyDisplayIDFromPool(ctx, command) +} + +// ListPrettyDisplayIDPools 让测试 wrapper 继续满足后台靓号池列表接口。 +func (r *Repository) ListPrettyDisplayIDPools(ctx context.Context, query userdomain.PrettyDisplayIDPoolQuery) ([]userdomain.PrettyDisplayIDPool, int64, error) { + return r.Repository.IdentityRepository().ListPrettyDisplayIDPools(ctx, query) +} + +// CreatePrettyDisplayIDPool 让测试 wrapper 继续满足后台靓号池创建接口。 +func (r *Repository) CreatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) { + return r.Repository.IdentityRepository().CreatePrettyDisplayIDPool(ctx, command) +} + +// UpdatePrettyDisplayIDPool 让测试 wrapper 继续满足后台靓号池更新接口。 +func (r *Repository) UpdatePrettyDisplayIDPool(ctx context.Context, command userdomain.PrettyDisplayIDPoolCommand) (userdomain.PrettyDisplayIDPool, error) { + return r.Repository.IdentityRepository().UpdatePrettyDisplayIDPool(ctx, command) +} + +// GeneratePrettyDisplayIDs 让测试 wrapper 继续满足后台靓号批量生成接口。 +func (r *Repository) GeneratePrettyDisplayIDs(ctx context.Context, command userdomain.PrettyDisplayIDGenerateCommand) (userdomain.PrettyDisplayIDGenerationBatch, error) { + return r.Repository.IdentityRepository().GeneratePrettyDisplayIDs(ctx, command) +} + +// ListPrettyDisplayIDs 让测试 wrapper 继续满足后台靓号列表接口。 +func (r *Repository) ListPrettyDisplayIDs(ctx context.Context, query userdomain.PrettyDisplayIDQuery) ([]userdomain.PrettyDisplayID, int64, error) { + return r.Repository.IdentityRepository().ListPrettyDisplayIDs(ctx, query) +} + +// SetPrettyDisplayIDStatus 让测试 wrapper 继续满足后台靓号状态修改接口。 +func (r *Repository) SetPrettyDisplayIDStatus(ctx context.Context, command userdomain.PrettyDisplayIDStatusCommand) (userdomain.PrettyDisplayID, error) { + return r.Repository.IdentityRepository().SetPrettyDisplayIDStatus(ctx, command) +} + +// AdminGrantPrettyDisplayID 让测试 wrapper 继续满足后台发放靓号接口。 +func (r *Repository) AdminGrantPrettyDisplayID(ctx context.Context, command userdomain.AdminGrantPrettyDisplayIDCommand) (userdomain.Identity, string, error) { + return r.Repository.IdentityRepository().AdminGrantPrettyDisplayID(ctx, command) +} + // ExpirePrettyDisplayUserID 让测试 wrapper 继续满足靓号接口。 func (r *Repository) ExpirePrettyDisplayUserID(ctx context.Context, command userdomain.ExpirePrettyDisplayUserIDCommand) (userdomain.Identity, error) { return r.Repository.IdentityRepository().ExpirePrettyDisplayUserID(ctx, command) diff --git a/services/user-service/internal/transport/grpc/convert.go b/services/user-service/internal/transport/grpc/convert.go index 592f4fbb..1e2e653b 100644 --- a/services/user-service/internal/transport/grpc/convert.go +++ b/services/user-service/internal/transport/grpc/convert.go @@ -78,6 +78,8 @@ func toProtoUser(user userdomain.User) *userv1.User { PhoneCountryCode: user.PhoneCountryCode, CountryEnabled: user.CountryEnabled, Invite: toProtoInviteOverview(user.InviteOverview), + PrettyId: user.PrettyID, + PrettyDisplayUserId: user.PrettyDisplayUserID, } } @@ -247,6 +249,7 @@ func toProtoRegion(region userdomain.Region) *userv1.Region { // toProtoIdentity 把短号领域投影转换为 protobuf 响应。 func toProtoIdentity(identity userdomain.Identity) *userv1.UserIdentity { // Identity proto 用于短号解析和修改结果,字段语义与领域 Identity 保持一致。 + // PrettyId/PrettyDisplayUserId 只表示当前 active 靓号,没有靓号时为空,不回填默认短号。 return &userv1.UserIdentity{ AppCode: identity.AppCode, UserId: identity.UserID, @@ -255,6 +258,69 @@ func toProtoIdentity(identity userdomain.Identity) *userv1.UserIdentity { DefaultDisplayUserId: identity.DefaultDisplayUserID, DisplayUserIdKind: string(identity.DisplayUserIDKind), DisplayUserIdExpiresAtMs: identity.DisplayUserIDExpiresAtMs, + PrettyId: identity.PrettyID, + PrettyDisplayUserId: identity.PrettyDisplayUserID, + } +} + +func toProtoPrettyDisplayIDPool(pool userdomain.PrettyDisplayIDPool) *userv1.PrettyDisplayIDPool { + // 池配置是后台和 App 可申请列表共用的展示信息,等级轨道和区间必须原样透出。 + return &userv1.PrettyDisplayIDPool{ + AppCode: pool.AppCode, + PoolId: pool.PoolID, + Name: pool.Name, + LevelTrack: pool.LevelTrack, + MinLevel: pool.MinLevel, + MaxLevel: pool.MaxLevel, + RuleType: pool.RuleType, + RuleConfigJson: pool.RuleConfigJSON, + Status: pool.Status, + SortOrder: pool.SortOrder, + CreatedByAdminId: pool.CreatedByAdminID, + UpdatedByAdminId: pool.UpdatedByAdminID, + CreatedAtMs: pool.CreatedAtMs, + UpdatedAtMs: pool.UpdatedAtMs, + } +} + +func toProtoPrettyDisplayID(item userdomain.PrettyDisplayID) *userv1.PrettyDisplayID { + // 后台发放号码没有 pool_id,Pool 会是零值;前端以 source 判断是否展示池信息。 + return &userv1.PrettyDisplayID{ + AppCode: item.AppCode, + PrettyId: item.PrettyID, + PoolId: item.PoolID, + Source: item.Source, + DisplayUserId: item.DisplayUserID, + Status: item.Status, + AssignedUserId: item.AssignedUserID, + AssignedLeaseId: item.AssignedLeaseID, + AssignedAtMs: item.AssignedAtMs, + ReleasedAtMs: item.ReleasedAtMs, + ReleaseReason: item.ReleaseReason, + GeneratedBatchId: item.GeneratedBatchID, + CreatedByAdminId: item.CreatedByAdminID, + CreatedAtMs: item.CreatedAtMs, + UpdatedAtMs: item.UpdatedAtMs, + Pool: toProtoPrettyDisplayIDPool(item.Pool), + } +} + +func toProtoPrettyDisplayIDBatch(batch userdomain.PrettyDisplayIDGenerationBatch) *userv1.PrettyDisplayIDGenerationBatch { + // 批次响应保留 requested/generated/skipped 三个计数,后台可以直接解释生成不足是否来自冲突。 + return &userv1.PrettyDisplayIDGenerationBatch{ + AppCode: batch.AppCode, + BatchId: batch.BatchID, + PoolId: batch.PoolID, + RuleType: batch.RuleType, + RuleConfigJson: batch.RuleConfigJSON, + RequestedCount: batch.RequestedCount, + GeneratedCount: batch.GeneratedCount, + SkippedConflictCount: batch.SkippedConflictCount, + Status: batch.Status, + OperatorAdminId: batch.OperatorAdminID, + RequestId: batch.RequestID, + CreatedAtMs: batch.CreatedAtMs, + UpdatedAtMs: batch.UpdatedAtMs, } } diff --git a/services/user-service/internal/transport/grpc/server.go b/services/user-service/internal/transport/grpc/server.go index 67ddd1e5..6db1d8d3 100644 --- a/services/user-service/internal/transport/grpc/server.go +++ b/services/user-service/internal/transport/grpc/server.go @@ -31,6 +31,8 @@ type Server struct { userv1.UnimplementedAppRegistryServiceServer // UnimplementedUserIdentityServiceServer 提供未实现 RPC 的默认返回。 userv1.UnimplementedUserIdentityServiceServer + // UnimplementedUserPrettyDisplayIDAdminServiceServer 提供靓号后台管理 RPC 的默认返回。 + userv1.UnimplementedUserPrettyDisplayIDAdminServiceServer // UnimplementedCountryAdminServiceServer 提供未实现 RPC 的默认返回。 userv1.UnimplementedCountryAdminServiceServer // UnimplementedCountryQueryServiceServer 提供注册页国家查询 RPC 的前向兼容默认返回。 @@ -217,11 +219,18 @@ func (s *Server) AppHeartbeat(ctx context.Context, req *userv1.AppHeartbeatReque if err != nil { return nil, xerr.ToGRPCError(err) } + // H5 游戏页可能停留在同一 access token 上连续轮询;心跳成功后用当前 active session 重签 access token, + // 不旋转 refresh token,避免游戏过程因为旧 access token 到期而被 gateway 拒绝。 + token, err := s.authSvc.IssueAccessTokenForSession(ctx, req.GetUserId(), req.GetSessionId(), authMeta(req.GetMeta())) + if err != nil { + return nil, xerr.ToGRPCError(err) + } return &userv1.AppHeartbeatResponse{ Accepted: true, HeartbeatAtMs: session.LastHeartbeatAtMs, ServerTimeMs: session.LastHeartbeatAtMs, + Token: toProtoToken(token), }, nil } @@ -835,6 +844,32 @@ func (s *Server) ApplyPrettyDisplayUserID(ctx context.Context, req *userv1.Apply return &userv1.ApplyPrettyDisplayUserIDResponse{Identity: toProtoIdentity(identity), LeaseId: leaseID}, nil } +// ListAvailablePrettyDisplayIDs 返回当前登录用户可申请的靓号池号码。 +func (s *Server) ListAvailablePrettyDisplayIDs(ctx context.Context, req *userv1.ListAvailablePrettyDisplayIDsRequest) (*userv1.ListAvailablePrettyDisplayIDsResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // gRPC 层不接收等级参数,service 会按 user_id 读取真实等级后再查询可申请池号。 + items, total, err := s.userSvc.ListAvailablePrettyDisplayIDs(ctx, req.GetUserId(), req.GetPage(), req.GetPageSize(), req.GetMeta().GetRequestId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &userv1.ListAvailablePrettyDisplayIDsResponse{Items: make([]*userv1.PrettyDisplayID, 0, len(items)), Total: total} + for _, item := range items { + resp.Items = append(resp.Items, toProtoPrettyDisplayID(item)) + } + return resp, nil +} + +// ApplyPrettyDisplayIDFromPool 按 pretty_id 申请等级池靓号。 +func (s *Server) ApplyPrettyDisplayIDFromPool(ctx context.Context, req *userv1.ApplyPrettyDisplayIDFromPoolRequest) (*userv1.ApplyPrettyDisplayIDFromPoolResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // 池申请只把 pretty_id 传入服务层,靓号内容、租期和等级校验都不能由客户端决定。 + identity, leaseID, err := s.userSvc.ApplyPrettyDisplayIDFromPool(ctx, req.GetUserId(), req.GetPrettyId(), req.GetMeta().GetRequestId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.ApplyPrettyDisplayIDFromPoolResponse{Identity: toProtoIdentity(identity), PrettyId: req.GetPrettyId(), LeaseId: leaseID}, nil +} + // ExpirePrettyDisplayUserID 主动触发靓号过期恢复。 func (s *Server) ExpirePrettyDisplayUserID(ctx context.Context, req *userv1.ExpirePrettyDisplayUserIDRequest) (*userv1.ExpirePrettyDisplayUserIDResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) @@ -847,6 +882,139 @@ func (s *Server) ExpirePrettyDisplayUserID(ctx context.Context, req *userv1.Expi return &userv1.ExpirePrettyDisplayUserIDResponse{Identity: toProtoIdentity(identity)}, nil } +// ListPrettyDisplayIDPools 返回后台靓号池配置列表。 +func (s *Server) ListPrettyDisplayIDPools(ctx context.Context, req *userv1.ListPrettyDisplayIDPoolsRequest) (*userv1.ListPrettyDisplayIDPoolsResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // 后台 RPC 只透传筛选和分页,应用隔离从 meta.app_code 写入 context 后由 service/repository 使用。 + items, total, err := s.userSvc.ListPrettyDisplayIDPools(ctx, req.GetStatus(), req.GetLevelTrack(), req.GetPage(), req.GetPageSize()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &userv1.ListPrettyDisplayIDPoolsResponse{Items: make([]*userv1.PrettyDisplayIDPool, 0, len(items)), Total: total} + for _, item := range items { + resp.Items = append(resp.Items, toProtoPrettyDisplayIDPool(item)) + } + return resp, nil +} + +// CreatePrettyDisplayIDPool 创建后台靓号池配置。 +func (s *Server) CreatePrettyDisplayIDPool(ctx context.Context, req *userv1.CreatePrettyDisplayIDPoolRequest) (*userv1.PrettyDisplayIDPoolResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // pool_id 和时间在服务层生成,这里只把后台填写的配置字段组成领域命令。 + pool, err := s.userSvc.CreatePrettyDisplayIDPool(ctx, userdomain.PrettyDisplayIDPoolCommand{ + Name: req.GetName(), + LevelTrack: req.GetLevelTrack(), + MinLevel: req.GetMinLevel(), + MaxLevel: req.GetMaxLevel(), + RuleType: req.GetRuleType(), + RuleConfigJSON: req.GetRuleConfigJson(), + Status: req.GetStatus(), + SortOrder: req.GetSortOrder(), + OperatorAdminID: req.GetOperatorAdminId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.PrettyDisplayIDPoolResponse{Pool: toProtoPrettyDisplayIDPool(pool)}, nil +} + +// UpdatePrettyDisplayIDPool 更新后台靓号池配置。 +func (s *Server) UpdatePrettyDisplayIDPool(ctx context.Context, req *userv1.UpdatePrettyDisplayIDPoolRequest) (*userv1.PrettyDisplayIDPoolResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // pool_id 来自请求路径对应的 RPC 字段,service 会校验目标池是否存在并保持同应用更新。 + pool, err := s.userSvc.UpdatePrettyDisplayIDPool(ctx, userdomain.PrettyDisplayIDPoolCommand{ + PoolID: req.GetPoolId(), + Name: req.GetName(), + LevelTrack: req.GetLevelTrack(), + MinLevel: req.GetMinLevel(), + MaxLevel: req.GetMaxLevel(), + RuleType: req.GetRuleType(), + RuleConfigJSON: req.GetRuleConfigJson(), + Status: req.GetStatus(), + SortOrder: req.GetSortOrder(), + OperatorAdminID: req.GetOperatorAdminId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.PrettyDisplayIDPoolResponse{Pool: toProtoPrettyDisplayIDPool(pool)}, nil +} + +// GeneratePrettyDisplayIDs 按池规则批量生成可申请靓号。 +func (s *Server) GeneratePrettyDisplayIDs(ctx context.Context, req *userv1.GeneratePrettyDisplayIDsRequest) (*userv1.GeneratePrettyDisplayIDsResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // 生成批次需要 request_id 参与审计,冲突过滤和 batch 统计由 service/repository 同事务完成。 + batch, err := s.userSvc.GeneratePrettyDisplayIDs(ctx, userdomain.PrettyDisplayIDGenerateCommand{ + PoolID: req.GetPoolId(), + RuleType: req.GetRuleType(), + RuleConfigJSON: req.GetRuleConfigJson(), + Count: req.GetCount(), + OperatorAdminID: req.GetOperatorAdminId(), + RequestID: req.GetMeta().GetRequestId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.GeneratePrettyDisplayIDsResponse{Batch: toProtoPrettyDisplayIDBatch(batch)}, nil +} + +// ListPrettyDisplayIDs 返回后台靓号明细列表。 +func (s *Server) ListPrettyDisplayIDs(ctx context.Context, req *userv1.ListPrettyDisplayIDsRequest) (*userv1.ListPrettyDisplayIDsResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // 列表查询允许按来源、状态、池、用户和关键字组合筛选,service 会统一修正分页。 + items, total, err := s.userSvc.ListPrettyDisplayIDs(ctx, userdomain.PrettyDisplayIDQuery{ + PoolID: req.GetPoolId(), + Source: req.GetSource(), + Status: req.GetStatus(), + Keyword: req.GetKeyword(), + AssignedUserID: req.GetAssignedUserId(), + Page: req.GetPage(), + PageSize: req.GetPageSize(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &userv1.ListPrettyDisplayIDsResponse{Items: make([]*userv1.PrettyDisplayID, 0, len(items)), Total: total} + for _, item := range items { + resp.Items = append(resp.Items, toProtoPrettyDisplayID(item)) + } + return resp, nil +} + +// SetPrettyDisplayIDStatus 修改未占用靓号状态。 +func (s *Server) SetPrettyDisplayIDStatus(ctx context.Context, req *userv1.SetPrettyDisplayIDStatusRequest) (*userv1.PrettyDisplayIDResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // assigned 靓号不能通过该 RPC 改状态,service/repository 会把这类请求返回不可用。 + item, err := s.userSvc.SetPrettyDisplayIDStatus(ctx, userdomain.PrettyDisplayIDStatusCommand{ + PrettyID: req.GetPrettyId(), + Status: req.GetStatus(), + OperatorAdminID: req.GetOperatorAdminId(), + Reason: req.GetReason(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.PrettyDisplayIDResponse{Item: toProtoPrettyDisplayID(item)}, nil +} + +// AdminGrantPrettyDisplayID 后台直接给用户发放自由靓号。 +func (s *Server) AdminGrantPrettyDisplayID(ctx context.Context, req *userv1.AdminGrantPrettyDisplayIDRequest) (*userv1.AdminGrantPrettyDisplayIDResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + // 后台发放不校验用户等级,duration_ms<=0 表示长期,具体内容格式和覆盖释放由 service/repository 处理。 + identity, leaseID, err := s.userSvc.AdminGrantPrettyDisplayID(ctx, userdomain.AdminGrantPrettyDisplayIDCommand{ + TargetUserID: req.GetTargetUserId(), + DisplayUserID: req.GetDisplayUserId(), + DurationMs: req.GetDurationMs(), + Reason: req.GetReason(), + OperatorAdminID: req.GetOperatorAdminId(), + RequestID: req.GetMeta().GetRequestId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.AdminGrantPrettyDisplayIDResponse{Identity: toProtoIdentity(identity), PrettyId: identity.PrettyID, LeaseId: leaseID}, nil +} + func contextWithApp(ctx context.Context, meta *userv1.RequestMeta) context.Context { if meta == nil { return appcode.WithContext(ctx, "") diff --git a/services/wallet-service/configs/config.docker.yaml b/services/wallet-service/configs/config.docker.yaml index a29f18b0..02840b98 100644 --- a/services/wallet-service/configs/config.docker.yaml +++ b/services/wallet-service/configs/config.docker.yaml @@ -51,6 +51,26 @@ google_play: token_url: "https://oauth2.googleapis.com/token" http_timeout: "10s" consume_enabled: true +mifapay: + # MiFaPay 是 H5 统一三方支付入口;容器默认打开功能但不写真实商户凭证,部署时从密钥环境注入。 + enabled: true + mer_account: "" + mer_no: "" + private_key: "" + platform_public_key: "" + api_base_url: "https://platformtest.xqdmipay.com" + http_timeout: "10s" +tron_grid: + enabled: true + api_base_url: "https://api.trongrid.io" + api_key: "" + usdt_contract_address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" + http_timeout: "10s" +external_recharge: + usdt_trc20_enabled: true + usdt_trc20_address: "" + mifapay_notify_url: "" + mifapay_return_url: "" outbox_worker: enabled: true poll_interval: "1s" diff --git a/services/wallet-service/configs/config.tencent.example.yaml b/services/wallet-service/configs/config.tencent.example.yaml index bfdf1b2f..7b808afa 100644 --- a/services/wallet-service/configs/config.tencent.example.yaml +++ b/services/wallet-service/configs/config.tencent.example.yaml @@ -51,6 +51,26 @@ google_play: token_url: "https://oauth2.googleapis.com/token" http_timeout: "10s" consume_enabled: true +mifapay: + # MiFaPay 是 H5 统一三方支付入口;以下值是占位符,线上必须通过密钥系统或环境配置提供真实值。 + enabled: true + mer_account: "MIFAPAY_MER_ACCOUNT" + mer_no: "MIFAPAY_MER_NO" + private_key: "MIFAPAY_RSA_PRIVATE_KEY_PKCS8" + platform_public_key: "MIFAPAY_PLATFORM_PUBLIC_KEY_X509" + api_base_url: "https://platform.xqdmipay.com" + http_timeout: "10s" +tron_grid: + enabled: true + api_base_url: "https://api.trongrid.io" + api_key: "TRONGRID_API_KEY" + usdt_contract_address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" + http_timeout: "10s" +external_recharge: + usdt_trc20_enabled: true + usdt_trc20_address: "TRC20_PLATFORM_RECEIVE_ADDRESS" + mifapay_notify_url: "https://api.example.com/api/v1/payment/mifapay/notify" + mifapay_return_url: "https://h5.example.com/recharge/index.html" outbox_worker: enabled: true poll_interval: "1s" diff --git a/services/wallet-service/configs/config.yaml b/services/wallet-service/configs/config.yaml index 06e51aa7..4a319097 100644 --- a/services/wallet-service/configs/config.yaml +++ b/services/wallet-service/configs/config.yaml @@ -51,6 +51,26 @@ google_play: token_url: "https://oauth2.googleapis.com/token" http_timeout: "10s" consume_enabled: true +mifapay: + # MiFaPay 是 H5 统一三方支付入口;本地默认打开功能但不配置真实商户凭证,缺凭证时 H5 不展示三方支付下单入口。 + enabled: true + mer_account: "" + mer_no: "" + private_key: "" + platform_public_key: "" + api_base_url: "https://platformtest.xqdmipay.com" + http_timeout: "10s" +tron_grid: + enabled: true + api_base_url: "https://api.trongrid.io" + api_key: "" + usdt_contract_address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" + http_timeout: "10s" +external_recharge: + usdt_trc20_enabled: true + usdt_trc20_address: "" + mifapay_notify_url: "" + mifapay_return_url: "" outbox_worker: enabled: true poll_interval: "1s" diff --git a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql index 505895d1..190dbfc9 100644 --- a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql +++ b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql @@ -479,6 +479,7 @@ CREATE TABLE IF NOT EXISTS wallet_recharge_products ( product_code VARCHAR(128) NULL COMMENT '商品编码', product_name VARCHAR(128) NOT NULL COMMENT '商品名称', description VARCHAR(512) NOT NULL DEFAULT '' COMMENT '描述信息', + audience_type VARCHAR(32) NOT NULL DEFAULT 'normal' COMMENT '购买对象:normal 普通用户,coin_seller 币商', platform VARCHAR(16) NOT NULL COMMENT '平台', channel VARCHAR(32) NOT NULL COMMENT '渠道', currency_code VARCHAR(8) NOT NULL DEFAULT 'USDT' COMMENT '币种编码', @@ -492,9 +493,18 @@ CREATE TABLE IF NOT EXISTS wallet_recharge_products ( created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', UNIQUE KEY uk_wallet_recharge_products_code (app_code, product_code), - KEY idx_wallet_recharge_products_scope (app_code, platform, status, amount_micro, product_id) + KEY idx_wallet_recharge_products_scope (app_code, platform, audience_type, status, amount_micro, product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='钱包充值商品表'; +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'wallet_recharge_products' AND COLUMN_NAME = 'audience_type') = 0, + 'ALTER TABLE wallet_recharge_products ADD COLUMN audience_type VARCHAR(32) NOT NULL DEFAULT ''normal'' COMMENT ''购买对象:normal 普通用户,coin_seller 币商'' AFTER description', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + CREATE TABLE IF NOT EXISTS wallet_recharge_product_regions ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', product_id BIGINT NOT NULL COMMENT '商品 ID', @@ -504,6 +514,124 @@ CREATE TABLE IF NOT EXISTS wallet_recharge_product_regions ( KEY idx_wallet_recharge_product_regions_region (app_code, region_id, product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='钱包充值商品区域表'; +CREATE TABLE IF NOT EXISTS third_party_payment_channels ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + provider_code VARCHAR(32) NOT NULL COMMENT '三方支付渠道编码', + provider_name VARCHAR(64) NOT NULL COMMENT '三方支付渠道名称', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '渠道状态', + sort_order INT 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, provider_code), + KEY idx_third_party_payment_channels_status (app_code, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='三方支付渠道表'; + +CREATE TABLE IF NOT EXISTS third_party_payment_methods ( + method_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '支付方式 ID', + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + provider_code VARCHAR(32) NOT NULL COMMENT '三方支付渠道编码', + country_code VARCHAR(8) NOT NULL COMMENT '国家码,GLOBAL 表示全球方式', + country_name VARCHAR(64) NOT NULL DEFAULT '' COMMENT '国家名称', + currency_code VARCHAR(8) NOT NULL COMMENT '下单币种', + pay_way VARCHAR(64) NOT NULL COMMENT 'MiFaPay payWay', + pay_type VARCHAR(64) NOT NULL COMMENT 'MiFaPay payType', + method_name VARCHAR(128) NOT NULL COMMENT '支付方式名称', + logo_url VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '支付方式图标', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '方式状态', + usd_to_currency_rate DECIMAL(20,8) NOT NULL DEFAULT 1 COMMENT 'USD 到支付币种汇率', + sort_order INT 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', + UNIQUE KEY uk_third_party_payment_methods_scope (app_code, provider_code, country_code, pay_way, pay_type), + KEY idx_third_party_payment_methods_h5 (app_code, provider_code, country_code, status, sort_order), + CONSTRAINT fk_third_party_payment_methods_channel FOREIGN KEY (app_code, provider_code) REFERENCES third_party_payment_channels(app_code, provider_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='三方支付国家方式配置表'; + +CREATE TABLE IF NOT EXISTS external_recharge_orders ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + order_id VARCHAR(96) NOT NULL COMMENT '外部充值订单号', + command_id VARCHAR(128) NOT NULL COMMENT 'H5 幂等命令 ID', + target_user_id BIGINT NOT NULL COMMENT '目标用户 ID', + target_region_id BIGINT NOT NULL COMMENT '目标区域 ID', + target_country_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '目标国家码', + audience_type VARCHAR(32) NOT NULL COMMENT '购买对象', + product_id BIGINT NOT NULL COMMENT '商品 ID', + product_code VARCHAR(128) NOT NULL COMMENT '商品编码快照', + product_name VARCHAR(128) NOT NULL COMMENT '商品名称快照', + coin_amount BIGINT NOT NULL COMMENT '到账金币数量', + usd_minor_amount BIGINT NOT NULL COMMENT 'USD cent 金额快照', + provider_code VARCHAR(32) NOT NULL COMMENT '支付渠道', + payment_method_id BIGINT NOT NULL DEFAULT 0 COMMENT '三方支付方式 ID', + country_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '支付方式国家码', + currency_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '支付币种', + provider_amount_minor BIGINT NOT NULL DEFAULT 0 COMMENT '三方支付币种最小单位金额', + pay_way VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'MiFaPay payWay', + pay_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'MiFaPay payType', + pay_url VARCHAR(2048) NOT NULL DEFAULT '' COMMENT '三方支付跳转 URL', + provider_order_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '三方支付订单号', + tx_hash VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'USDT tx hash', + receive_address VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'USDT 收款地址', + status VARCHAR(32) NOT NULL COMMENT '订单状态', + failure_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '失败原因', + wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '钱包交易 ID', + provider_payload JSON NULL 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, order_id), + UNIQUE KEY uk_external_recharge_orders_command (app_code, command_id), + KEY idx_external_recharge_orders_user_time (app_code, target_user_id, created_at_ms), + KEY idx_external_recharge_orders_provider_order (app_code, provider_code, provider_order_id), + KEY idx_external_recharge_orders_tx (app_code, provider_code, tx_hash) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='H5 外部充值订单表'; + +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +INSERT IGNORE INTO third_party_payment_channels (app_code, provider_code, provider_name, status, sort_order, created_at_ms, updated_at_ms) +VALUES ('lalu', 'mifapay', 'MiFaPay', 'active', 10, @now_ms, @now_ms); + +-- MiFaPay 是 H5 统一三方支付入口;真实商户号、商户标识、商户私钥、平台公钥只放运行环境配置。 +-- 国家和支付方式默认打开,运营可在后台逐项关闭;默认汇率为 1,避免新环境缺配置时 H5 完全不可见。 +INSERT IGNORE INTO third_party_payment_methods ( + app_code, provider_code, country_code, country_name, currency_code, pay_way, pay_type, + method_name, logo_url, status, usd_to_currency_rate, sort_order, created_at_ms, updated_at_ms +) VALUES + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BCA', 'BCA Bank', '', 'active', 1, 110, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BNI', 'BNI Bank', '', 'active', 1, 120, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'BRI', 'BRI Bank', '', 'active', 1, 130, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'Mandiri', 'Mandiri Bank', '', 'active', 1, 140, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'BankTransfer', 'Permata', 'Permata Bank', '', 'active', 1, 150, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'DANA', 'DANA', '', 'active', 1, 160, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'LinkAja', 'LinkAja', '', 'active', 1, 170, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'OVO', 'OVO', '', 'active', 1, 180, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'Ewallet', 'ShopeePay', 'ShopeePay', '', 'active', 1, 190, @now_ms, @now_ms), + ('lalu', 'mifapay', 'ID', 'Indonesia', 'IDR', 'QR', 'QRIS', 'QRIS', '', 'active', 1, 200, @now_ms, @now_ms), + ('lalu', 'mifapay', 'VN', 'Vietnam', 'VND', 'BankTransfer', 'VN_BankTransfer', 'Vietnam Bank Transfer', '', 'active', 1, 210, @now_ms, @now_ms), + ('lalu', 'mifapay', 'VN', 'Vietnam', 'VND', 'QR', 'VietQR', 'VietQR', '', 'active', 1, 220, @now_ms, @now_ms), + ('lalu', 'mifapay', 'BH', 'Bahrain', 'BHD', 'Card', 'CreditCard', 'Bahrain Credit Card', '', 'active', 1, 310, @now_ms, @now_ms), + ('lalu', 'mifapay', 'QA', 'Qatar', 'QAR', 'Card', 'CreditCard', 'Qatar Credit Card', '', 'active', 1, 410, @now_ms, @now_ms), + ('lalu', 'mifapay', 'OM', 'Oman', 'OMR', 'Card', 'CreditCard', 'Oman Credit Card', '', 'active', 1, 510, @now_ms, @now_ms), + ('lalu', 'mifapay', 'AE', 'United Arab Emirates', 'AED', 'Card', 'CreditCard', 'UAE Credit Card', '', 'active', 1, 610, @now_ms, @now_ms), + ('lalu', 'mifapay', 'KW', 'Kuwait', 'KWD', 'Card', 'Knet', 'KNET', '', 'active', 1, 710, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Card', 'MADA', 'MADA', '', 'active', 1, 810, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Ewallet', 'ApplePay', 'ApplePay', '', 'active', 1, 820, @now_ms, @now_ms), + ('lalu', 'mifapay', 'SA', 'Saudi Arabia', 'SAR', 'Ewallet', 'STCPay', 'STCPay', '', 'active', 1, 830, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'BHIM', 'BHIM', '', 'active', 1, 910, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'GooglePay', 'GooglePay', '', 'active', 1, 920, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'Mobikwik', 'Mobikwik', '', 'active', 1, 930, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'Paytm', 'Paytm', '', 'active', 1, 940, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'PhonePe', 'PhonePe', '', 'active', 1, 950, @now_ms, @now_ms), + ('lalu', 'mifapay', 'IN', 'India', 'INR', 'Ewallet', 'UPI', 'UPI', '', 'active', 1, 960, @now_ms, @now_ms), + ('lalu', 'mifapay', 'BD', 'Bangladesh', 'BDT', 'Ewallet', 'bKash', 'bKash', '', 'active', 1, 1010, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PK', 'Pakistan', 'PKR', 'Ewallet', 'Easypaisa', 'Easypaisa', '', 'active', 1, 1110, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PK', 'Pakistan', 'PKR', 'Ewallet', 'JazzCash', 'JazzCash', '', 'active', 1, 1120, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'FawryPay', 'FawryPay', '', 'active', 1, 1210, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Meeza', 'Meeza', '', 'active', 1, 1220, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Orange', 'Orange', '', 'active', 1, 1230, @now_ms, @now_ms), + ('lalu', 'mifapay', 'EG', 'Egypt', 'EGP', 'Ewallet', 'Vodafone', 'Vodafone', '', 'active', 1, 1240, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'Ewallet', 'Gcash', 'GCash', '', 'active', 1, 1310, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'Ewallet', 'PayMaya', 'PayMaya', '', 'active', 1, 1320, @now_ms, @now_ms), + ('lalu', 'mifapay', 'PH', 'Philippines', 'PHP', 'QR', 'QRPH', 'QRPH', '', 'active', 1, 1330, @now_ms, @now_ms); + CREATE TABLE IF NOT EXISTS payment_orders ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', payment_order_id VARCHAR(96) NOT NULL COMMENT '平台支付订单 ID', diff --git a/services/wallet-service/internal/app/app.go b/services/wallet-service/internal/app/app.go index c8ec00a7..88ef88a8 100644 --- a/services/wallet-service/internal/app/app.go +++ b/services/wallet-service/internal/app/app.go @@ -23,6 +23,8 @@ import ( "hyapp/pkg/walletmq" "hyapp/services/wallet-service/internal/client" "hyapp/services/wallet-service/internal/client/googleplay" + "hyapp/services/wallet-service/internal/client/mifapay" + "hyapp/services/wallet-service/internal/client/trongrid" "hyapp/services/wallet-service/internal/config" walletservice "hyapp/services/wallet-service/internal/service/wallet" mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql" @@ -99,6 +101,12 @@ func New(cfg config.Config) (*App, error) { server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("wallet-service"))) svc := walletservice.New(repository, client.NewActivityAchievementClient(activityConn)) + svc.SetExternalRechargeConfig(walletservice.ExternalRechargeConfig{ + USDTTRC20Enabled: cfg.ExternalRecharge.USDTTRC20Enabled, + USDTTRC20Address: cfg.ExternalRecharge.USDTTRC20Address, + MifaPayNotifyURL: cfg.ExternalRecharge.MifaPayNotifyURL, + MifaPayReturnURL: cfg.ExternalRecharge.MifaPayReturnURL, + }) if cfg.GooglePlay.Enabled { googleClient, err := googleplay.New(cfg.GooglePlay) if err != nil { @@ -110,6 +118,25 @@ func New(cfg config.Config) (*App, error) { } svc.SetGooglePlayClient(googleClient) } + if cfg.MifaPay.Enabled { + if mifapayConfigReady(cfg.MifaPay) { + mifaPayClient, err := mifapay.New(cfg.MifaPay) + if err != nil { + shutdownProducers(outboxProducer, realtimeOutboxProducer) + _ = activityConn.Close() + _ = listener.Close() + _ = repository.Close() + return nil, err + } + svc.SetMifaPayClient(mifaPayClient) + } else { + // 新功能配置默认打开,但商户私钥和平台公钥只能来自环境;缺失时启动不失败,只是不注入真实支付网关。 + logx.Warn(context.Background(), "mifapay_enabled_without_credentials") + } + } + if cfg.TronGrid.Enabled { + svc.SetTronUSDTClient(trongrid.New(cfg.TronGrid)) + } walletv1.RegisterWalletServiceServer(server, grpcserver.NewServer(svc)) walletv1.RegisterWalletCronServiceServer(server, grpcserver.NewCronServer(svc)) health := grpchealth.NewServingChecker("wallet-service", grpchealth.Dependency{ @@ -472,6 +499,13 @@ func rocketMQProducerConfig(cfg config.RocketMQConfig, groupName string) rocketm } } +func mifapayConfigReady(cfg config.MifaPayConfig) bool { + return strings.TrimSpace(cfg.MerAccount) != "" && + strings.TrimSpace(cfg.MerNo) != "" && + strings.TrimSpace(cfg.PrivateKey) != "" && + strings.TrimSpace(cfg.PlatformPublicKey) != "" +} + func shutdownProducers(producers ...*rocketmqx.Producer) { for _, producer := range producers { if producer != nil { diff --git a/services/wallet-service/internal/client/mifapay/client.go b/services/wallet-service/internal/client/mifapay/client.go new file mode 100644 index 00000000..86a95b72 --- /dev/null +++ b/services/wallet-service/internal/client/mifapay/client.go @@ -0,0 +1,268 @@ +package mifapay + +import ( + "bytes" + "context" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/config" + "hyapp/services/wallet-service/internal/domain/ledger" + walletservice "hyapp/services/wallet-service/internal/service/wallet" +) + +type Client struct { + cfg config.MifaPayConfig + httpClient *http.Client + privateKey *rsa.PrivateKey + publicKey *rsa.PublicKey +} + +type envelope struct { + MerAccount string `json:"merAccount"` + Data string `json:"data"` + Sign string `json:"sign"` +} + +func New(cfg config.MifaPayConfig) (*Client, error) { + privateKey, err := parsePrivateKey(cfg.PrivateKey) + if err != nil { + return nil, err + } + publicKey, err := parsePublicKey(cfg.PlatformPublicKey) + if err != nil { + return nil, err + } + if cfg.HTTPTimeout <= 0 { + cfg.HTTPTimeout = 10 * time.Second + } + return &Client{ + cfg: cfg, + httpClient: &http.Client{Timeout: cfg.HTTPTimeout}, + privateKey: privateKey, + publicKey: publicKey, + }, nil +} + +// CreateOrder 按 MiFaPay 统一 envelope 协议下单;业务层只消费 payUrl 和 mbOrderId。 +func (c *Client) CreateOrder(ctx context.Context, req walletservice.MifaPayCreateOrderRequest) (walletservice.MifaPayCreateOrderResponse, error) { + if c == nil || c.privateKey == nil { + return walletservice.MifaPayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay client is not configured") + } + data := map[string]any{ + "amount": strconv.FormatInt(req.ProviderAmountMinor, 10), + "orderId": req.OrderID, + "payWay": req.PayWay, + "language": firstNonEmpty(req.Language, "en"), + "terminalType": "WEB", + "productInfo": productInfo(req), + "payType": req.PayType, + "merNo": c.cfg.MerNo, + "countryCode": req.CountryCode, + "notifyUrl": req.NotifyURL, + "currency": req.CurrencyCode, + "time": time.Now().Unix(), + "returnUrl": req.ReturnURL, + "memberId": strconv.FormatInt(req.TargetUserID, 10), + "userIp": req.ClientIP, + } + body, err := c.signedEnvelope(data) + if err != nil { + return walletservice.MifaPayCreateOrderResponse{}, err + } + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.APIBaseURL+"/paygateway/mbpay/order/en_v2", bytes.NewReader(body)) + if err != nil { + return walletservice.MifaPayCreateOrderResponse{}, err + } + httpReq.Header.Set("Content-Type", "application/json; charset=UTF-8") + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return walletservice.MifaPayCreateOrderResponse{}, err + } + defer resp.Body.Close() + raw, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20)) + if err != nil { + return walletservice.MifaPayCreateOrderResponse{}, err + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return walletservice.MifaPayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay order request failed") + } + var outer envelope + var withCode struct { + envelope + Code string `json:"code"` + Msg string `json:"msg"` + } + if err := json.Unmarshal(raw, &withCode); err != nil { + return walletservice.MifaPayCreateOrderResponse{}, err + } + outer = withCode.envelope + if withCode.Code != "" && withCode.Code != "000000" { + return walletservice.MifaPayCreateOrderResponse{}, xerr.New(xerr.Conflict, "mifapay order rejected: "+withCode.Msg) + } + if err := c.verify(outer.Data, outer.Sign); err != nil { + return walletservice.MifaPayCreateOrderResponse{}, err + } + var inner struct { + OrderID string `json:"orderId"` + PayURL string `json:"payUrl"` + MBOrderID string `json:"mbOrderId"` + } + if err := json.Unmarshal([]byte(outer.Data), &inner); err != nil { + return walletservice.MifaPayCreateOrderResponse{}, err + } + if strings.TrimSpace(inner.PayURL) == "" || strings.TrimSpace(inner.MBOrderID) == "" { + return walletservice.MifaPayCreateOrderResponse{}, xerr.New(xerr.Unavailable, "mifapay order response is incomplete") + } + return walletservice.MifaPayCreateOrderResponse{ + OrderID: inner.OrderID, + ProviderOrderID: inner.MBOrderID, + PayURL: inner.PayURL, + RawJSON: string(raw), + }, nil +} + +// ParseNotification 验签 MiFaPay 回调并返回内层业务字段;验签失败不允许业务层入账。 +func (c *Client) ParseNotification(notification ledger.MifaPayNotification) (walletservice.MifaPayNotifyData, error) { + if c == nil || c.publicKey == nil { + return walletservice.MifaPayNotifyData{}, xerr.New(xerr.Unavailable, "mifapay client is not configured") + } + if strings.TrimSpace(notification.MerAccount) != c.cfg.MerAccount { + return walletservice.MifaPayNotifyData{}, xerr.New(xerr.PermissionDenied, "mifapay mer_account is invalid") + } + if err := c.verify(notification.Data, notification.Sign); err != nil { + return walletservice.MifaPayNotifyData{}, err + } + var inner struct { + Amount string `json:"amount"` + PayType string `json:"payType"` + OrderID string `json:"orderId"` + BankOrderNo string `json:"bankOrderNo"` + OrderStatus string `json:"orderStatus"` + Currency string `json:"currency"` + MBOrderID string `json:"mbOrderId"` + MemberID string `json:"memberId"` + } + if err := json.Unmarshal([]byte(notification.Data), &inner); err != nil { + return walletservice.MifaPayNotifyData{}, err + } + if strings.TrimSpace(inner.OrderID) == "" { + return walletservice.MifaPayNotifyData{}, xerr.New(xerr.InvalidArgument, "mifapay callback order_id is required") + } + return walletservice.MifaPayNotifyData{ + OrderID: inner.OrderID, + ProviderOrderID: inner.MBOrderID, + OrderStatus: inner.OrderStatus, + Amount: inner.Amount, + Currency: inner.Currency, + PayType: inner.PayType, + RawJSON: notification.Data, + }, nil +} + +func (c *Client) signedEnvelope(data any) ([]byte, error) { + dataJSON, err := json.Marshal(data) + if err != nil { + return nil, err + } + sign, err := c.sign(string(dataJSON)) + if err != nil { + return nil, err + } + return json.Marshal(envelope{MerAccount: c.cfg.MerAccount, Data: string(dataJSON), Sign: sign}) +} + +func (c *Client) sign(data string) (string, error) { + hash := sha256.Sum256([]byte(data)) + signature, err := rsa.SignPKCS1v15(rand.Reader, c.privateKey, crypto.SHA256, hash[:]) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(signature), nil +} + +func (c *Client) verify(data string, sign string) error { + if c.publicKey == nil { + return nil + } + signature, err := base64.StdEncoding.DecodeString(strings.TrimSpace(sign)) + if err != nil { + return err + } + hash := sha256.Sum256([]byte(data)) + if err := rsa.VerifyPKCS1v15(c.publicKey, crypto.SHA256, hash[:], signature); err != nil { + return xerr.New(xerr.PermissionDenied, "mifapay signature is invalid") + } + return nil +} + +func parsePrivateKey(value string) (*rsa.PrivateKey, error) { + block := pemBlock(value, "PRIVATE KEY") + key, err := x509.ParsePKCS8PrivateKey(block) + if err != nil { + return nil, err + } + privateKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("mifapay private key is not rsa") + } + return privateKey, nil +} + +func parsePublicKey(value string) (*rsa.PublicKey, error) { + block := pemBlock(value, "PUBLIC KEY") + key, err := x509.ParsePKIXPublicKey(block) + if err != nil { + return nil, err + } + publicKey, ok := key.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("mifapay public key is not rsa") + } + return publicKey, nil +} + +func pemBlock(value string, blockType string) []byte { + value = strings.TrimSpace(value) + if decoded, _ := pem.Decode([]byte(value)); decoded != nil { + return decoded.Bytes + } + clean := strings.NewReplacer("\n", "", "\r", "", " ", "").Replace(value) + body, err := base64.StdEncoding.DecodeString(clean) + if err == nil { + return body + } + return []byte(value) +} + +func productInfo(req walletservice.MifaPayCreateOrderRequest) string { + body, _ := json.Marshal([]map[string]string{{ + "quantity": "1", + "price": strconv.FormatInt(req.AmountMinor, 10), + "sku": req.OrderID, + "productName": req.ProductName, + }}) + return string(body) +} + +func firstNonEmpty(values ...string) string { + for _, value := range values { + if value = strings.TrimSpace(value); value != "" { + return value + } + } + return "" +} diff --git a/services/wallet-service/internal/client/trongrid/client.go b/services/wallet-service/internal/client/trongrid/client.go new file mode 100644 index 00000000..00a9082a --- /dev/null +++ b/services/wallet-service/internal/client/trongrid/client.go @@ -0,0 +1,123 @@ +package trongrid + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/config" +) + +// Client 校验用户提交的 USDT-TRC20 tx_hash;它只读链上公开数据,不保存也不接触任何私钥。 +type Client struct { + httpClient *http.Client + apiBaseURL string + apiKey string + contractAddress string +} + +// New 创建 TronGrid 兼容客户端;base_url 可以指向官方 TronGrid、自建节点代理或兼容网关。 +func New(cfg config.TronGridConfig) *Client { + timeout := cfg.HTTPTimeout + if timeout <= 0 { + timeout = 10 * time.Second + } + return &Client{ + httpClient: &http.Client{Timeout: timeout}, + apiBaseURL: strings.TrimRight(strings.TrimSpace(cfg.APIBaseURL), "/"), + apiKey: strings.TrimSpace(cfg.APIKey), + contractAddress: strings.ToLower(strings.TrimSpace(cfg.USDTContractAddress)), + } +} + +// VerifyUSDTTransfer 校验 tx_hash 指向的 confirmed Transfer 是否把足额 USDT 打到平台共享地址。 +func (c *Client) VerifyUSDTTransfer(ctx context.Context, txHash string, toAddress string, amountMinor int64) (string, error) { + if c == nil || c.apiBaseURL == "" { + return "", xerr.New(xerr.Unavailable, "tron grid client is not configured") + } + txHash = strings.TrimSpace(txHash) + toAddress = strings.ToLower(strings.TrimSpace(toAddress)) + if txHash == "" || toAddress == "" || amountMinor <= 0 { + return "", xerr.New(xerr.InvalidArgument, "usdt transfer verification request is incomplete") + } + // H5 商品金额以 USD cent 保存,USDT 使用 6 位小数;1 cent = 0.01 USDT = 10,000 atomic units。 + requiredAtomicAmount := amountMinor * 10_000 + events, rawJSON, err := c.listTransferEvents(ctx, txHash) + if err != nil { + return "", err + } + for _, event := range events { + if !strings.EqualFold(event.EventName, "Transfer") { + continue + } + if !c.contractMatches(event.ContractAddress) { + continue + } + if strings.ToLower(strings.TrimSpace(event.Result.To)) != toAddress { + continue + } + value, err := strconv.ParseInt(strings.TrimSpace(event.Result.Value), 10, 64) + if err != nil || value < requiredAtomicAmount { + continue + } + return rawJSON, nil + } + return rawJSON, xerr.New(xerr.Conflict, "usdt transfer is not confirmed or does not match order") +} + +func (c *Client) listTransferEvents(ctx context.Context, txHash string) ([]transferEvent, string, error) { + endpoint := c.apiBaseURL + "/v1/transactions/" + url.PathEscape(txHash) + "/events?event_name=Transfer&only_confirmed=true&limit=50" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, "", err + } + if c.apiKey != "" { + req.Header.Set("TRON-PRO-API-KEY", c.apiKey) + } + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, "", xerr.New(xerr.Unavailable, "tron grid request failed") + } + defer resp.Body.Close() + body, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20)) + if err != nil { + return nil, "", err + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, string(body), xerr.New(xerr.Unavailable, "tron grid returned non-success") + } + var parsed struct { + Data []transferEvent `json:"data"` + } + if err := json.Unmarshal(body, &parsed); err != nil { + return nil, string(body), err + } + return parsed.Data, string(body), nil +} + +func (c *Client) contractMatches(value string) bool { + value = strings.ToLower(strings.TrimSpace(value)) + if value == "" || c.contractAddress == "" { + return false + } + if value == c.contractAddress { + return true + } + // TronGrid 的不同接口可能返回 base58 或 hex 合约地址;这里保留主网 USDT hex 兼容,避免官方返回格式差异导致误拒。 + return c.contractAddress == "tr7nhqjekqxgtci8q8zyk4pyl8otszgjlj6t" && value == "a614f803b6fd780986a42c78ec9c7f77e6ded13c" +} + +type transferEvent struct { + EventName string `json:"event_name"` + ContractAddress string `json:"contract_address"` + Result struct { + To string `json:"to"` + Value string `json:"value"` + } `json:"result"` +} diff --git a/services/wallet-service/internal/config/config.go b/services/wallet-service/internal/config/config.go index 961244e6..6b8cd337 100644 --- a/services/wallet-service/internal/config/config.go +++ b/services/wallet-service/internal/config/config.go @@ -27,6 +27,12 @@ type Config struct { RocketMQ RocketMQConfig `yaml:"rocketmq"` // GooglePlay 控制 Google Play Developer API 购买校验。 GooglePlay GooglePlayConfig `yaml:"google_play"` + // MifaPay 控制 H5 MiFaPay 站外支付下单和回调验签。 + MifaPay MifaPayConfig `yaml:"mifapay"` + // TronGrid 控制 USDT-TRC20 tx_hash 链上校验。 + TronGrid TronGridConfig `yaml:"tron_grid"` + // ExternalRecharge 控制 H5 外部充值公开地址和回跳地址。 + ExternalRecharge ExternalRechargeConfig `yaml:"external_recharge"` // OutboxWorker 控制 wallet_outbox 到 MQ 的补偿投递。 OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"` // RealtimeOutboxWorker 控制红包等实时 UI 事件到独立 MQ topic 的补偿投递。 @@ -104,6 +110,34 @@ type GooglePlayConfig struct { ConsumeEnabled bool `yaml:"consume_enabled"` } +// MifaPayConfig 保存 MiFaPay 商户签名、验签和网关配置。 +type MifaPayConfig struct { + Enabled bool `yaml:"enabled"` + MerAccount string `yaml:"mer_account"` + MerNo string `yaml:"mer_no"` + PrivateKey string `yaml:"private_key"` + PlatformPublicKey string `yaml:"platform_public_key"` + APIBaseURL string `yaml:"api_base_url"` + HTTPTimeout time.Duration `yaml:"http_timeout"` +} + +// TronGridConfig 保存 TRON HTTP 兼容接口配置;base_url 可替换成自有代理。 +type TronGridConfig struct { + Enabled bool `yaml:"enabled"` + APIBaseURL string `yaml:"api_base_url"` + APIKey string `yaml:"api_key"` + USDTContractAddress string `yaml:"usdt_contract_address"` + HTTPTimeout time.Duration `yaml:"http_timeout"` +} + +// ExternalRechargeConfig 保存外部充值页面和支付回调所需的公开配置。 +type ExternalRechargeConfig struct { + USDTTRC20Enabled bool `yaml:"usdt_trc20_enabled"` + USDTTRC20Address string `yaml:"usdt_trc20_address"` + MifaPayNotifyURL string `yaml:"mifapay_notify_url"` + MifaPayReturnURL string `yaml:"mifapay_return_url"` +} + // Default 返回本地开发默认配置。 func Default() Config { return Config{ @@ -128,6 +162,20 @@ func Default() Config { HTTPTimeout: 10 * time.Second, ConsumeEnabled: true, }, + MifaPay: MifaPayConfig{ + Enabled: true, + APIBaseURL: "https://platformtest.xqdmipay.com", + HTTPTimeout: 10 * time.Second, + }, + TronGrid: TronGridConfig{ + Enabled: true, + APIBaseURL: "https://api.trongrid.io", + USDTContractAddress: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", + HTTPTimeout: 10 * time.Second, + }, + ExternalRecharge: ExternalRechargeConfig{ + USDTTRC20Enabled: true, + }, OutboxWorker: OutboxWorkerConfig{ Enabled: false, PollInterval: time.Second, @@ -252,6 +300,34 @@ func Load(path string) (Config, error) { return Config{}, errors.New("google_play service account is required") } } + cfg.MifaPay.MerAccount = strings.TrimSpace(cfg.MifaPay.MerAccount) + cfg.MifaPay.MerNo = strings.TrimSpace(cfg.MifaPay.MerNo) + cfg.MifaPay.PrivateKey = strings.TrimSpace(cfg.MifaPay.PrivateKey) + cfg.MifaPay.PlatformPublicKey = strings.TrimSpace(cfg.MifaPay.PlatformPublicKey) + cfg.MifaPay.APIBaseURL = strings.TrimRight(strings.TrimSpace(cfg.MifaPay.APIBaseURL), "/") + if cfg.MifaPay.APIBaseURL == "" { + cfg.MifaPay.APIBaseURL = Default().MifaPay.APIBaseURL + } + if cfg.MifaPay.HTTPTimeout <= 0 { + cfg.MifaPay.HTTPTimeout = Default().MifaPay.HTTPTimeout + } + // MiFaPay 功能开关默认打开;商户密钥属于环境敏感配置,允许本地缺省启动,启动装配时再按凭证完整性决定是否接入真实网关。 + cfg.TronGrid.APIBaseURL = strings.TrimRight(strings.TrimSpace(cfg.TronGrid.APIBaseURL), "/") + if cfg.TronGrid.APIBaseURL == "" { + cfg.TronGrid.APIBaseURL = Default().TronGrid.APIBaseURL + } + cfg.TronGrid.APIKey = strings.TrimSpace(cfg.TronGrid.APIKey) + cfg.TronGrid.USDTContractAddress = strings.TrimSpace(cfg.TronGrid.USDTContractAddress) + if cfg.TronGrid.USDTContractAddress == "" { + cfg.TronGrid.USDTContractAddress = Default().TronGrid.USDTContractAddress + } + if cfg.TronGrid.HTTPTimeout <= 0 { + cfg.TronGrid.HTTPTimeout = Default().TronGrid.HTTPTimeout + } + cfg.ExternalRecharge.USDTTRC20Address = strings.TrimSpace(cfg.ExternalRecharge.USDTTRC20Address) + cfg.ExternalRecharge.MifaPayNotifyURL = strings.TrimSpace(cfg.ExternalRecharge.MifaPayNotifyURL) + cfg.ExternalRecharge.MifaPayReturnURL = strings.TrimSpace(cfg.ExternalRecharge.MifaPayReturnURL) + // USDT 功能开关默认打开;未配置收款地址时 H5 不展示 USDT 下单入口,但配置本身不被静默改成关闭。 cfg.OutboxWorker = normalizeOutboxWorkerConfig(cfg.OutboxWorker, Default().OutboxWorker) if cfg.OutboxWorker.Enabled && !cfg.RocketMQ.WalletOutbox.Enabled { return Config{}, errors.New("outbox_worker requires rocketmq.wallet_outbox.enabled") diff --git a/services/wallet-service/internal/domain/ledger/ledger.go b/services/wallet-service/internal/domain/ledger/ledger.go index 52755f69..d51275ea 100644 --- a/services/wallet-service/internal/domain/ledger/ledger.go +++ b/services/wallet-service/internal/domain/ledger/ledger.go @@ -36,12 +36,36 @@ const ( RechargeProductPlatformAndroid = "android" // RechargeProductPlatformIOS 表示 Apple IAP 侧内购商品。 RechargeProductPlatformIOS = "ios" + // RechargeProductPlatformWeb 表示 H5 站外充值商品,支付方式由订单选择,不绑定应用商店。 + RechargeProductPlatformWeb = "web" + // RechargeAudienceNormal 表示普通用户可购买的充值档位。 + RechargeAudienceNormal = "normal" + // RechargeAudienceCoinSeller 表示币商可购买的充值档位。 + RechargeAudienceCoinSeller = "coin_seller" // RechargeChannelGoogle 是 android 平台内购渠道标识。 RechargeChannelGoogle = "google" // RechargeChannelApple 是 iOS 平台内购渠道标识。 RechargeChannelApple = "apple" + // RechargeChannelExternal 是 H5 站外充值渠道,具体 provider 在外部订单上保存。 + RechargeChannelExternal = "external" // PaymentProviderGooglePlay 是 Google Play 一次性内购支付渠道。 PaymentProviderGooglePlay = "google_play" + // PaymentProviderMifaPay 是 MiFaPay 三方收银台支付渠道。 + PaymentProviderMifaPay = "mifapay" + // PaymentProviderUSDTTRC20 是用户提交 TRC20 链上交易哈希的 USDT 充值渠道。 + PaymentProviderUSDTTRC20 = "usdt_trc20" + // ThirdPartyPaymentStatusActive 表示渠道或支付方式可用于 H5 下单。 + ThirdPartyPaymentStatusActive = "active" + // ThirdPartyPaymentStatusDisabled 表示渠道或支付方式已被后台关闭。 + ThirdPartyPaymentStatusDisabled = "disabled" + // ExternalRechargeStatusPending 表示外部充值订单已创建但尚未确认到账。 + ExternalRechargeStatusPending = "pending" + // ExternalRechargeStatusRedirected 表示 MiFaPay 下单成功,用户已拿到跳转链接。 + ExternalRechargeStatusRedirected = "redirected" + // ExternalRechargeStatusCredited 表示外部充值已完成钱包入账。 + ExternalRechargeStatusCredited = "credited" + // ExternalRechargeStatusFailed 表示外部充值被验签、金额或链上校验拒绝。 + ExternalRechargeStatusFailed = "failed" // PaymentStatusCredited 表示支付已完成校验并入账。 PaymentStatusCredited = "credited" // PaymentConsumeStatePending 表示已入账但 Google consume 尚未确认完成。 @@ -169,6 +193,7 @@ type Receipt struct { GiftName string GiftIconURL string GiftAnimationURL string + GiftEffectTypes []string PriceVersion string BalanceAfter int64 // HostPeriodDiamondAdded 是本次送礼写入主播工资周期账户的钻石数;非主播恒为 0。 @@ -487,6 +512,7 @@ type UserGiftWall struct { // RechargeProduct 是用户充值页读取的充值档位。 type RechargeProduct struct { AppCode string + AudienceType string ProductID int64 ProductCode string ProductName string @@ -511,18 +537,20 @@ type RechargeProduct struct { // ListRechargeProductsQuery 是后台内购商品配置列表的筛选条件。 type ListRechargeProductsQuery struct { - AppCode string - Status string - Platform string - RegionID int64 - Keyword string - Page int32 - PageSize int32 + AppCode string + Status string + Platform string + AudienceType string + RegionID int64 + Keyword string + Page int32 + PageSize int32 } // RechargeProductCommand 是后台创建或更新内购商品配置的完整事实输入。 type RechargeProductCommand struct { AppCode string + AudienceType string ProductID int64 AmountMicro int64 CoinAmount int64 @@ -573,6 +601,136 @@ type GooglePaymentReceipt struct { ConsumeState string } +// ThirdPartyPaymentMethod 是 MiFaPay 等三方支付方式的后台配置事实。 +type ThirdPartyPaymentMethod struct { + MethodID int64 + AppCode string + ProviderCode string + ProviderName string + CountryCode string + CountryName string + CurrencyCode string + PayWay string + PayType string + MethodName string + LogoURL string + Status string + USDToCurrencyRate string + SortOrder int32 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// ThirdPartyPaymentChannel 聚合一个三方渠道及其国家/方式列表,供后台展开展示。 +type ThirdPartyPaymentChannel struct { + AppCode string + ProviderCode string + ProviderName string + Status string + SortOrder int32 + Methods []ThirdPartyPaymentMethod +} + +type ListThirdPartyPaymentChannelsQuery struct { + AppCode string + ProviderCode string + Status string + IncludeDisabledMethods bool +} + +type ThirdPartyPaymentMethodStatusCommand struct { + AppCode string + MethodID int64 + Enabled bool + OperatorUserID int64 +} + +type ThirdPartyPaymentRateCommand struct { + AppCode string + MethodID int64 + USDToCurrencyRate string + OperatorUserID int64 +} + +// H5RechargeOptionsQuery 是站外充值页在账号确认后读取商品和支付方式的最小条件。 +type H5RechargeOptionsQuery struct { + AppCode string + TargetUserID int64 + TargetRegionID int64 + TargetCountryCode string + AudienceType string +} + +type H5RechargeOptions struct { + Products []RechargeProduct + PaymentMethods []ThirdPartyPaymentMethod + USDTTRC20Enabled bool + USDTTRC20Address string +} + +// ExternalRechargeOrder 保存 H5 外部充值订单的订单、支付和入账状态。 +type ExternalRechargeOrder struct { + OrderID string + AppCode string + CommandID string + TargetUserID int64 + TargetRegionID int64 + TargetCountryCode string + AudienceType string + ProductID int64 + ProductCode string + ProductName string + CoinAmount int64 + USDMinorAmount int64 + ProviderCode string + PaymentMethodID int64 + CountryCode string + CurrencyCode string + ProviderAmountMinor int64 + PayWay string + PayType string + PayURL string + ProviderOrderID string + TxHash string + ReceiveAddress string + Status string + FailureReason string + TransactionID string + ProviderPayloadJSON string + CreatedAtMS int64 + UpdatedAtMS int64 + IdempotentReplay bool +} + +type CreateExternalRechargeOrderCommand struct { + AppCode string + CommandID string + TargetUserID int64 + TargetRegionID int64 + TargetCountryCode string + AudienceType string + ProductID int64 + ProviderCode string + PaymentMethodID int64 + ReturnURL string + NotifyURL string + ClientIP string + Language string +} + +type SubmitExternalRechargeTxCommand struct { + AppCode string + OrderID string + TargetUserID int64 + TxHash string +} + +type MifaPayNotification struct { + MerAccount string + Data string + Sign string +} + // DiamondExchangeRule 是钻石兑换金币或余额的固定规则投影。 type DiamondExchangeRule struct { ExchangeType string @@ -1012,6 +1170,8 @@ func NormalizeRechargeProductPlatform(platform string) string { return RechargeProductPlatformAndroid case RechargeProductPlatformIOS: return RechargeProductPlatformIOS + case RechargeProductPlatformWeb: + return RechargeProductPlatformWeb default: return "" } @@ -1023,6 +1183,58 @@ func RechargeChannelForPlatform(platform string) string { return RechargeChannelGoogle case RechargeProductPlatformIOS: return RechargeChannelApple + case RechargeProductPlatformWeb: + return RechargeChannelExternal + default: + return "" + } +} + +func NormalizeRechargeAudienceType(audienceType string) string { + switch strings.ToLower(strings.TrimSpace(audienceType)) { + case "", RechargeAudienceNormal: + return RechargeAudienceNormal + case RechargeAudienceCoinSeller: + return RechargeAudienceCoinSeller + default: + return "" + } +} + +func NormalizePaymentProvider(provider string) string { + switch strings.ToLower(strings.TrimSpace(provider)) { + case PaymentProviderMifaPay: + return PaymentProviderMifaPay + case PaymentProviderUSDTTRC20: + return PaymentProviderUSDTTRC20 + case PaymentProviderGooglePlay: + return PaymentProviderGooglePlay + default: + return "" + } +} + +func NormalizeThirdPartyPaymentStatus(status string) string { + switch strings.ToLower(strings.TrimSpace(status)) { + case ThirdPartyPaymentStatusActive: + return ThirdPartyPaymentStatusActive + case ThirdPartyPaymentStatusDisabled: + return ThirdPartyPaymentStatusDisabled + default: + return "" + } +} + +func NormalizeExternalRechargeStatus(status string) string { + switch strings.ToLower(strings.TrimSpace(status)) { + case ExternalRechargeStatusPending: + return ExternalRechargeStatusPending + case ExternalRechargeStatusRedirected: + return ExternalRechargeStatusRedirected + case ExternalRechargeStatusCredited: + return ExternalRechargeStatusCredited + case ExternalRechargeStatusFailed: + return ExternalRechargeStatusFailed default: return "" } diff --git a/services/wallet-service/internal/service/wallet/external_recharge.go b/services/wallet-service/internal/service/wallet/external_recharge.go new file mode 100644 index 00000000..380afff0 --- /dev/null +++ b/services/wallet-service/internal/service/wallet/external_recharge.go @@ -0,0 +1,410 @@ +package wallet + +import ( + "context" + "encoding/json" + "math" + "strconv" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" +) + +// ExternalRechargeConfig 保存 H5 外部充值需要公开给业务链路的运行配置。 +type ExternalRechargeConfig struct { + USDTTRC20Enabled bool + USDTTRC20Address string + MifaPayNotifyURL string + MifaPayReturnURL string +} + +// MifaPayCreateOrderRequest 是 wallet-service 传给 MiFaPay client 的已校验下单快照。 +type MifaPayCreateOrderRequest struct { + OrderID string + TargetUserID int64 + ProductName string + CoinAmount int64 + AmountMinor int64 + CurrencyCode string + CountryCode string + PayWay string + PayType string + NotifyURL string + ReturnURL string + ClientIP string + Language string + ProviderAmountMinor int64 +} + +type MifaPayCreateOrderResponse struct { + OrderID string + ProviderOrderID string + PayURL string + RawJSON string +} + +type MifaPayNotifyData struct { + OrderID string + ProviderOrderID string + OrderStatus string + Amount string + Currency string + PayType string + RawJSON string +} + +// MifaPayClient 隔离 MiFaPay RSA 签名、验签和 HTTP 协议细节,service 只处理订单状态。 +type MifaPayClient interface { + CreateOrder(ctx context.Context, req MifaPayCreateOrderRequest) (MifaPayCreateOrderResponse, error) + ParseNotification(notification ledger.MifaPayNotification) (MifaPayNotifyData, error) +} + +// TronUSDTClient 校验用户提交的 TRC20 tx_hash 是否真实打到平台共享地址。 +type TronUSDTClient interface { + VerifyUSDTTransfer(ctx context.Context, txHash string, toAddress string, amountMinor int64) (string, error) +} + +func normalizeExternalRechargeConfig(config ExternalRechargeConfig) ExternalRechargeConfig { + config.USDTTRC20Address = strings.TrimSpace(config.USDTTRC20Address) + config.MifaPayNotifyURL = strings.TrimSpace(config.MifaPayNotifyURL) + config.MifaPayReturnURL = strings.TrimSpace(config.MifaPayReturnURL) + return config +} + +// ListThirdPartyPaymentChannels 返回后台三方支付管理页需要的渠道、国家和方式树。 +func (s *Service) ListThirdPartyPaymentChannels(ctx context.Context, query ledger.ListThirdPartyPaymentChannelsQuery) ([]ledger.ThirdPartyPaymentChannel, error) { + if s.repository == nil { + return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + query.AppCode = appcode.Normalize(query.AppCode) + if strings.TrimSpace(query.Status) != "" { + query.Status = ledger.NormalizeThirdPartyPaymentStatus(query.Status) + if query.Status == "" { + return nil, xerr.New(xerr.InvalidArgument, "status is invalid") + } + } + ctx = appcode.WithContext(ctx, query.AppCode) + return s.repository.ListThirdPartyPaymentChannels(ctx, query) +} + +func (s *Service) SetThirdPartyPaymentMethodStatus(ctx context.Context, command ledger.ThirdPartyPaymentMethodStatusCommand) (ledger.ThirdPartyPaymentMethod, error) { + if command.MethodID <= 0 || command.OperatorUserID <= 0 { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.InvalidArgument, "payment method and operator are required") + } + if s.repository == nil { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.SetThirdPartyPaymentMethodStatus(ctx, command) +} + +func (s *Service) UpdateThirdPartyPaymentRate(ctx context.Context, command ledger.ThirdPartyPaymentRateCommand) (ledger.ThirdPartyPaymentMethod, error) { + command.AppCode = appcode.Normalize(command.AppCode) + command.USDToCurrencyRate = strings.TrimSpace(command.USDToCurrencyRate) + if command.MethodID <= 0 || command.OperatorUserID <= 0 || !validNonNegativeDecimal(command.USDToCurrencyRate) { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.InvalidArgument, "payment rate command is invalid") + } + if s.repository == nil { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.UpdateThirdPartyPaymentRate(ctx, command) +} + +// ListH5RechargeOptions 只返回目标用户当前可买商品和已配置汇率的支付方式。 +func (s *Service) ListH5RechargeOptions(ctx context.Context, query ledger.H5RechargeOptionsQuery) (ledger.H5RechargeOptions, error) { + query.AppCode = appcode.Normalize(query.AppCode) + query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType) + query.TargetCountryCode = strings.ToUpper(strings.TrimSpace(query.TargetCountryCode)) + if query.TargetUserID <= 0 || query.TargetRegionID <= 0 || query.AudienceType == "" { + return ledger.H5RechargeOptions{}, xerr.New(xerr.InvalidArgument, "h5 recharge options query is incomplete") + } + if s.repository == nil { + return ledger.H5RechargeOptions{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, query.AppCode) + options, err := s.repository.ListH5RechargeOptions(ctx, query) + if err != nil { + return ledger.H5RechargeOptions{}, err + } + if s.mifaPay == nil { + options.PaymentMethods = nil + } + options.USDTTRC20Enabled = s.externalRecharge.USDTTRC20Enabled && s.externalRecharge.USDTTRC20Address != "" + options.USDTTRC20Address = s.externalRecharge.USDTTRC20Address + return options, nil +} + +// CreateH5RechargeOrder 创建 H5 外部充值订单;只有回调或链上校验成功后才会入账。 +func (s *Service) CreateH5RechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand) (ledger.ExternalRechargeOrder, error) { + command = normalizeCreateExternalRechargeOrderCommand(command) + if err := validateCreateExternalRechargeOrderCommand(command); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if s.repository == nil { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, command.AppCode) + product, err := s.repository.GetRechargeProduct(ctx, command.AppCode, command.ProductID) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if err := validateExternalRechargeProduct(product, command); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + switch command.ProviderCode { + case ledger.PaymentProviderUSDTTRC20: + return s.createUSDTRechargeOrder(ctx, command, product) + case ledger.PaymentProviderMifaPay: + return s.createMifaPayRechargeOrder(ctx, command, product) + default: + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.InvalidArgument, "payment provider is invalid") + } +} + +func (s *Service) createUSDTRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct) (ledger.ExternalRechargeOrder, error) { + if !s.externalRecharge.USDTTRC20Enabled || s.externalRecharge.USDTTRC20Address == "" { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "usdt trc20 recharge is not configured") + } + return s.repository.CreateExternalRechargeOrder(ctx, command, product, nil, amountMicroToUSDMinor(product.AmountMicro), s.externalRecharge.USDTTRC20Address) +} + +func (s *Service) createMifaPayRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct) (ledger.ExternalRechargeOrder, error) { + if s.mifaPay == nil { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "mifapay client is not configured") + } + method, err := s.repository.GetThirdPartyPaymentMethod(ctx, command.AppCode, command.PaymentMethodID) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if err := validateMifaPayMethodForOrder(method, command); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + providerAmountMinor, err := calculateProviderAmountMinor(amountMicroToUSDMinor(product.AmountMicro), method.USDToCurrencyRate) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + order, err := s.repository.CreateExternalRechargeOrder(ctx, command, product, &method, providerAmountMinor, "") + if err != nil || order.IdempotentReplay { + return order, err + } + notifyURL := firstNonEmpty(command.NotifyURL, s.externalRecharge.MifaPayNotifyURL) + returnURL := firstNonEmpty(command.ReturnURL, s.externalRecharge.MifaPayReturnURL) + if notifyURL == "" { + _, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, "mifapay notify_url is not configured", "") + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "mifapay notify_url is not configured") + } + resp, err := s.mifaPay.CreateOrder(ctx, MifaPayCreateOrderRequest{ + OrderID: order.OrderID, + TargetUserID: order.TargetUserID, + ProductName: order.ProductName, + CoinAmount: order.CoinAmount, + AmountMinor: order.USDMinorAmount, + CurrencyCode: order.CurrencyCode, + CountryCode: order.CountryCode, + PayWay: order.PayWay, + PayType: order.PayType, + NotifyURL: notifyURL, + ReturnURL: returnURL, + ClientIP: command.ClientIP, + Language: command.Language, + ProviderAmountMinor: providerAmountMinor, + }) + if err != nil { + _, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, err.Error(), "") + return ledger.ExternalRechargeOrder{}, err + } + return s.repository.MarkExternalRechargeOrderRedirected(ctx, command.AppCode, order.OrderID, resp.ProviderOrderID, resp.PayURL, resp.RawJSON) +} + +// SubmitH5RechargeTx 把用户提交的 tx_hash 交给 TRON 适配器校验,通过后立即入账。 +func (s *Service) SubmitH5RechargeTx(ctx context.Context, command ledger.SubmitExternalRechargeTxCommand) (ledger.ExternalRechargeOrder, error) { + command.AppCode = appcode.Normalize(command.AppCode) + command.OrderID = strings.TrimSpace(command.OrderID) + command.TxHash = strings.TrimSpace(command.TxHash) + if command.OrderID == "" || command.TargetUserID <= 0 || command.TxHash == "" { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.InvalidArgument, "usdt tx command is incomplete") + } + if s.repository == nil { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, command.AppCode) + order, err := s.repository.GetExternalRechargeOrder(ctx, command.AppCode, command.OrderID) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if order.TargetUserID != command.TargetUserID { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.PermissionDenied, "external recharge order does not belong to user") + } + if order.ProviderCode != ledger.PaymentProviderUSDTTRC20 || order.Status != ledger.ExternalRechargeStatusPending { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Conflict, "external recharge order is not pending usdt order") + } + if s.tronUSDT == nil { + return s.repository.AttachExternalRechargeTx(ctx, command, "") + } + rawJSON, err := s.tronUSDT.VerifyUSDTTransfer(ctx, command.TxHash, order.ReceiveAddress, order.USDMinorAmount) + if err != nil { + _, _ = s.repository.AttachExternalRechargeTx(ctx, command, rawJSON) + return ledger.ExternalRechargeOrder{}, err + } + if _, err := s.repository.AttachExternalRechargeTx(ctx, command, rawJSON); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + return s.repository.CreditExternalRechargeOrder(ctx, command.AppCode, command.OrderID, "", command.TxHash, rawJSON) +} + +func (s *Service) GetH5RechargeOrder(ctx context.Context, appCode string, orderID string, targetUserID int64) (ledger.ExternalRechargeOrder, error) { + if s.repository == nil { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, appcode.Normalize(appCode)) + order, err := s.repository.GetExternalRechargeOrder(ctx, appcode.FromContext(ctx), strings.TrimSpace(orderID)) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if targetUserID > 0 && order.TargetUserID != targetUserID { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.PermissionDenied, "external recharge order does not belong to user") + } + return order, nil +} + +// HandleMifapayNotify 只在验签、订单号、币种和金额全部匹配后入账;重复回调返回 SUCCESS。 +func (s *Service) HandleMifapayNotify(ctx context.Context, appCode string, notification ledger.MifaPayNotification) (ledger.ExternalRechargeOrder, bool, error) { + if s.mifaPay == nil { + return ledger.ExternalRechargeOrder{}, false, xerr.New(xerr.Unavailable, "mifapay client is not configured") + } + if s.repository == nil { + return ledger.ExternalRechargeOrder{}, false, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + ctx = appcode.WithContext(ctx, appcode.Normalize(appCode)) + data, err := s.mifaPay.ParseNotification(notification) + if err != nil { + return ledger.ExternalRechargeOrder{}, false, err + } + order, err := s.repository.GetExternalRechargeOrder(ctx, appcode.FromContext(ctx), data.OrderID) + if err != nil { + return ledger.ExternalRechargeOrder{}, false, err + } + if !strings.EqualFold(data.OrderStatus, "SUCCESS") { + order, err = s.repository.MarkExternalRechargeOrderFailed(ctx, order.AppCode, order.OrderID, "mifapay order status "+data.OrderStatus, data.RawJSON) + return order, true, err + } + if !mifaPayNotifyMatchesOrder(data, order) { + order, err = s.repository.MarkExternalRechargeOrderFailed(ctx, order.AppCode, order.OrderID, "mifapay callback amount or currency mismatch", data.RawJSON) + return order, false, err + } + credited, err := s.repository.CreditExternalRechargeOrder(ctx, order.AppCode, order.OrderID, data.ProviderOrderID, "", data.RawJSON) + return credited, err == nil, err +} + +func normalizeCreateExternalRechargeOrderCommand(command ledger.CreateExternalRechargeOrderCommand) ledger.CreateExternalRechargeOrderCommand { + command.AppCode = appcode.Normalize(command.AppCode) + command.CommandID = strings.TrimSpace(command.CommandID) + command.TargetCountryCode = strings.ToUpper(strings.TrimSpace(command.TargetCountryCode)) + command.AudienceType = ledger.NormalizeRechargeAudienceType(command.AudienceType) + command.ProviderCode = ledger.NormalizePaymentProvider(command.ProviderCode) + command.ReturnURL = strings.TrimSpace(command.ReturnURL) + command.NotifyURL = strings.TrimSpace(command.NotifyURL) + command.ClientIP = strings.TrimSpace(command.ClientIP) + command.Language = strings.TrimSpace(command.Language) + if command.Language == "" { + command.Language = "en" + } + return command +} + +func validateCreateExternalRechargeOrderCommand(command ledger.CreateExternalRechargeOrderCommand) error { + if command.CommandID == "" || command.TargetUserID <= 0 || command.TargetRegionID <= 0 || command.ProductID <= 0 { + return xerr.New(xerr.InvalidArgument, "external recharge command is incomplete") + } + if command.AudienceType == "" || command.ProviderCode == "" { + return xerr.New(xerr.InvalidArgument, "external recharge command type is invalid") + } + if len(command.CommandID) > 128 { + return xerr.New(xerr.InvalidArgument, "command_id is too long") + } + return nil +} + +func validateExternalRechargeProduct(product ledger.RechargeProduct, command ledger.CreateExternalRechargeOrderCommand) error { + if product.Status != ledger.RechargeProductStatusActive || !product.Enabled { + return xerr.New(xerr.Conflict, "recharge product is not active") + } + if product.Platform != ledger.RechargeProductPlatformWeb { + return xerr.New(xerr.InvalidArgument, "recharge product is not h5 product") + } + if product.AudienceType != command.AudienceType { + return xerr.New(xerr.Conflict, "recharge product audience does not match") + } + if !rechargeProductSupportsRegion(product, command.TargetRegionID) { + return xerr.New(xerr.Conflict, "recharge product is not available in user region") + } + return nil +} + +func validateMifaPayMethodForOrder(method ledger.ThirdPartyPaymentMethod, command ledger.CreateExternalRechargeOrderCommand) error { + if method.ProviderCode != ledger.PaymentProviderMifaPay || method.Status != ledger.ThirdPartyPaymentStatusActive { + return xerr.New(xerr.Conflict, "mifapay method is not active") + } + if method.CountryCode != "GLOBAL" && !strings.EqualFold(method.CountryCode, command.TargetCountryCode) { + return xerr.New(xerr.Conflict, "mifapay method country does not match") + } + if _, err := calculateProviderAmountMinor(100, method.USDToCurrencyRate); err != nil { + return err + } + return nil +} + +func calculateProviderAmountMinor(usdMinor int64, rate string) (int64, error) { + if usdMinor <= 0 { + return 0, xerr.New(xerr.InvalidArgument, "usd amount is invalid") + } + value, err := strconv.ParseFloat(strings.TrimSpace(rate), 64) + if err != nil || value <= 0 { + return 0, xerr.New(xerr.InvalidArgument, "payment exchange rate is invalid") + } + return int64(math.Round(float64(usdMinor) * value)), nil +} + +func amountMicroToUSDMinor(amountMicro int64) int64 { + if amountMicro <= 0 { + return 0 + } + return amountMicro / 10_000 +} + +func validNonNegativeDecimal(value string) bool { + parsed, err := strconv.ParseFloat(strings.TrimSpace(value), 64) + return err == nil && parsed >= 0 +} + +func mifaPayNotifyMatchesOrder(data MifaPayNotifyData, order ledger.ExternalRechargeOrder) bool { + if !strings.EqualFold(data.Currency, order.CurrencyCode) { + return false + } + if data.PayType != "" && !strings.EqualFold(data.PayType, order.PayType) { + return false + } + amount, err := strconv.ParseInt(strings.TrimSpace(data.Amount), 10, 64) + return err == nil && amount == order.ProviderAmountMinor +} + +func firstNonEmpty(values ...string) string { + for _, value := range values { + if value = strings.TrimSpace(value); value != "" { + return value + } + } + return "" +} + +func mustJSONText(value any) string { + body, err := json.Marshal(value) + if err != nil { + return "{}" + } + return string(body) +} diff --git a/services/wallet-service/internal/service/wallet/service.go b/services/wallet-service/internal/service/wallet/service.go index 8c611de7..fe3c4e6e 100644 --- a/services/wallet-service/internal/service/wallet/service.go +++ b/services/wallet-service/internal/service/wallet/service.go @@ -45,7 +45,7 @@ type Repository interface { GetWalletOverview(ctx context.Context, userID int64) (ledger.WalletOverview, error) GetWalletValueSummary(ctx context.Context, userID int64) (ledger.WalletValueSummary, error) GetUserGiftWall(ctx context.Context, userID int64) (ledger.UserGiftWall, error) - ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string) ([]ledger.RechargeProduct, []string, error) + ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) ListAdminRechargeProducts(ctx context.Context, query ledger.ListRechargeProductsQuery) ([]ledger.RechargeProduct, int64, error) GetRechargeProduct(ctx context.Context, appCode string, productID int64) (ledger.RechargeProduct, error) ConfirmGooglePayment(ctx context.Context, command ledger.GooglePaymentCommand, product ledger.RechargeProduct, purchase ledger.GooglePlayPurchase) (ledger.GooglePaymentReceipt, error) @@ -53,6 +53,17 @@ type Repository interface { CreateRechargeProduct(ctx context.Context, command ledger.RechargeProductCommand) (ledger.RechargeProduct, error) UpdateRechargeProduct(ctx context.Context, command ledger.RechargeProductCommand) (ledger.RechargeProduct, error) DeleteRechargeProduct(ctx context.Context, appCode string, productID int64) error + ListThirdPartyPaymentChannels(ctx context.Context, query ledger.ListThirdPartyPaymentChannelsQuery) ([]ledger.ThirdPartyPaymentChannel, error) + SetThirdPartyPaymentMethodStatus(ctx context.Context, command ledger.ThirdPartyPaymentMethodStatusCommand) (ledger.ThirdPartyPaymentMethod, error) + UpdateThirdPartyPaymentRate(ctx context.Context, command ledger.ThirdPartyPaymentRateCommand) (ledger.ThirdPartyPaymentMethod, error) + GetThirdPartyPaymentMethod(ctx context.Context, appCode string, methodID int64) (ledger.ThirdPartyPaymentMethod, error) + ListH5RechargeOptions(ctx context.Context, query ledger.H5RechargeOptionsQuery) (ledger.H5RechargeOptions, error) + CreateExternalRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct, method *ledger.ThirdPartyPaymentMethod, providerAmountMinor int64, receiveAddress string) (ledger.ExternalRechargeOrder, error) + MarkExternalRechargeOrderRedirected(ctx context.Context, appCode string, orderID string, providerOrderID string, payURL string, payloadJSON string) (ledger.ExternalRechargeOrder, error) + AttachExternalRechargeTx(ctx context.Context, command ledger.SubmitExternalRechargeTxCommand, payloadJSON string) (ledger.ExternalRechargeOrder, error) + CreditExternalRechargeOrder(ctx context.Context, appCode string, orderID string, providerOrderID string, txHash string, payloadJSON string) (ledger.ExternalRechargeOrder, error) + MarkExternalRechargeOrderFailed(ctx context.Context, appCode string, orderID string, reason string, payloadJSON string) (ledger.ExternalRechargeOrder, error) + GetExternalRechargeOrder(ctx context.Context, appCode string, orderID string) (ledger.ExternalRechargeOrder, error) GetDiamondExchangeConfig(ctx context.Context, userID int64) ([]ledger.DiamondExchangeRule, error) ListWalletTransactions(ctx context.Context, query ledger.ListWalletTransactionsQuery) ([]ledger.WalletTransaction, int64, error) ListVipPackages(ctx context.Context, userID int64) (ledger.UserVip, []ledger.VipLevel, error) @@ -111,10 +122,13 @@ type GooglePlayClient interface { // Service 承载钱包账务用例。 type Service struct { - repository Repository - activity ActivityBadgeClient - googlePlay GooglePlayClient - now func() time.Time + repository Repository + activity ActivityBadgeClient + googlePlay GooglePlayClient + mifaPay MifaPayClient + tronUSDT TronUSDTClient + externalRecharge ExternalRechargeConfig + now func() time.Time } // New 创建钱包服务。 @@ -131,6 +145,21 @@ func (s *Service) SetGooglePlayClient(client GooglePlayClient) { s.googlePlay = client } +// SetMifaPayClient 注入 MiFaPay 下单和验签客户端;未配置时 H5 MiFaPay 下单返回依赖不可用。 +func (s *Service) SetMifaPayClient(client MifaPayClient) { + s.mifaPay = client +} + +// SetTronUSDTClient 注入 TRON TRC20 交易校验客户端;未配置时 USDT tx 提交只会保留待确认订单。 +func (s *Service) SetTronUSDTClient(client TronUSDTClient) { + s.tronUSDT = client +} + +// SetExternalRechargeConfig 保存 H5 外部充值的站内公共配置,不把密钥或地址散落到 handler。 +func (s *Service) SetExternalRechargeConfig(config ExternalRechargeConfig) { + s.externalRecharge = normalizeExternalRechargeConfig(config) +} + // DebitGift 校验送礼扣费命令,并交给 repository 做原子落账和幂等。 func (s *Service) DebitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) { if command.CommandID == "" || command.RoomID == "" || command.SenderUserID <= 0 || command.TargetUserID <= 0 || command.GiftID == "" { @@ -739,7 +768,7 @@ func isBadgeGrantProjectionStatusTerminal(status string) bool { } // ListRechargeProducts 返回区域化充值档位;region_id 必须由 gateway 从 user-service 资料解析。 -func (s *Service) ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string) ([]ledger.RechargeProduct, []string, error) { +func (s *Service) ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) { if userID <= 0 || regionID <= 0 { return nil, nil, xerr.New(xerr.InvalidArgument, "user_id and region_id are required") } @@ -749,11 +778,15 @@ func (s *Service) ListRechargeProducts(ctx context.Context, userID int64, region return nil, nil, xerr.New(xerr.InvalidArgument, "platform is invalid") } } + audienceType = ledger.NormalizeRechargeAudienceType(audienceType) + if audienceType == "" { + return nil, nil, xerr.New(xerr.InvalidArgument, "audience_type is invalid") + } if s.repository == nil { return nil, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") } ctx = appcode.WithContext(ctx, appcode.FromContext(ctx)) - return s.repository.ListRechargeProducts(ctx, userID, regionID, platform) + return s.repository.ListRechargeProducts(ctx, userID, regionID, platform, audienceType) } // ListAdminRechargeProducts 返回后台配置列表;后台入口负责 RBAC 和审计。 @@ -774,6 +807,12 @@ func (s *Service) ListAdminRechargeProducts(ctx context.Context, query ledger.Li return nil, 0, xerr.New(xerr.InvalidArgument, "platform is invalid") } } + if strings.TrimSpace(query.AudienceType) != "" { + query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType) + if query.AudienceType == "" { + return nil, 0, xerr.New(xerr.InvalidArgument, "audience_type is invalid") + } + } ctx = appcode.WithContext(ctx, query.AppCode) return s.repository.ListAdminRechargeProducts(ctx, query) } @@ -1062,6 +1101,9 @@ func validateRechargeProductCommand(command ledger.RechargeProductCommand, requi if ledger.NormalizeRechargeProductPlatform(command.Platform) == "" { return xerr.New(xerr.InvalidArgument, "platform is invalid") } + if ledger.NormalizeRechargeAudienceType(command.AudienceType) == "" { + return xerr.New(xerr.InvalidArgument, "audience_type is invalid") + } if len(command.RegionIDs) == 0 { return xerr.New(xerr.InvalidArgument, "region_ids are required") } diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index e7daa3e3..96b9b686 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -3808,7 +3808,7 @@ func TestRechargeProductsAreConfiguredByPlatformRegionAndStatus(t *testing.T) { t.Fatalf("created product regions mismatch: %+v", product.RegionIDs) } - products, channels, err := svc.ListRechargeProducts(context.Background(), 53001, 1001, ledger.RechargeProductPlatformAndroid) + products, channels, err := svc.ListRechargeProducts(context.Background(), 53001, 1001, ledger.RechargeProductPlatformAndroid, ledger.RechargeAudienceNormal) if err != nil { t.Fatalf("ListRechargeProducts android failed: %v", err) } @@ -3819,7 +3819,7 @@ func TestRechargeProductsAreConfiguredByPlatformRegionAndStatus(t *testing.T) { t.Fatalf("android recharge channels mismatch: %+v", channels) } - iosProducts, _, err := svc.ListRechargeProducts(context.Background(), 53001, 1001, ledger.RechargeProductPlatformIOS) + iosProducts, _, err := svc.ListRechargeProducts(context.Background(), 53001, 1001, ledger.RechargeProductPlatformIOS, ledger.RechargeAudienceNormal) if err != nil { t.Fatalf("ListRechargeProducts ios failed: %v", err) } @@ -3843,7 +3843,7 @@ func TestRechargeProductsAreConfiguredByPlatformRegionAndStatus(t *testing.T) { t.Fatalf("UpdateRechargeProduct failed: %v", err) } - inactiveProducts, _, err := svc.ListRechargeProducts(context.Background(), 53001, 1002, ledger.RechargeProductPlatformAndroid) + inactiveProducts, _, err := svc.ListRechargeProducts(context.Background(), 53001, 1002, ledger.RechargeProductPlatformAndroid, ledger.RechargeAudienceNormal) if err != nil { t.Fatalf("ListRechargeProducts inactive failed: %v", err) } diff --git a/services/wallet-service/internal/storage/mysql/recharge_product_repository.go b/services/wallet-service/internal/storage/mysql/recharge_product_repository.go index 875f0d02..8f4b13c4 100644 --- a/services/wallet-service/internal/storage/mysql/recharge_product_repository.go +++ b/services/wallet-service/internal/storage/mysql/recharge_product_repository.go @@ -17,15 +17,16 @@ import ( ) // ListRechargeProducts 返回 App 当前区域和平台可购买的内购商品;只读取已上架配置。 -func (r *Repository) ListRechargeProducts(ctx context.Context, _ int64, regionID int64, platform string) ([]ledger.RechargeProduct, []string, error) { +func (r *Repository) ListRechargeProducts(ctx context.Context, _ int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) { if r == nil || r.db == nil { return nil, nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") } app := appcode.FromContext(ctx) platform = ledger.NormalizeRechargeProductPlatform(platform) + audienceType = ledger.NormalizeRechargeAudienceType(audienceType) where := ` - WHERE p.app_code = ? AND p.status = ? AND pr.region_id = ?` - args := []any{app, ledger.RechargeProductStatusActive, regionID} + WHERE p.app_code = ? AND p.status = ? AND p.audience_type = ? AND pr.region_id = ?` + args := []any{app, ledger.RechargeProductStatusActive, audienceType, regionID} if platform != "" { where += ` AND p.platform = ?` args = append(args, platform) @@ -119,13 +120,13 @@ func (r *Repository) CreateRechargeProduct(ctx context.Context, command ledger.R nowMs := time.Now().UnixMilli() result, err := tx.ExecContext(ctx, ` INSERT INTO wallet_recharge_products ( - app_code, product_name, description, platform, channel, currency_code, amount_micro, + app_code, audience_type, product_name, description, platform, channel, currency_code, amount_micro, coin_amount, resource_asset_type, status, sort_order, created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - command.AppCode, command.ProductName, command.Description, command.Platform, + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + command.AppCode, command.AudienceType, command.ProductName, command.Description, command.Platform, ledger.RechargeChannelForPlatform(command.Platform), ledger.PaidCurrencyUSDT, command.AmountMicro, - command.CoinAmount, ledger.AssetCoin, productStatus(command.Enabled), 0, + command.CoinAmount, rechargeProductAssetType(command.AudienceType), productStatus(command.Enabled), 0, command.OperatorUserID, command.OperatorUserID, nowMs, nowMs, ) if err != nil { @@ -171,12 +172,12 @@ func (r *Repository) UpdateRechargeProduct(ctx context.Context, command ledger.R nowMs := time.Now().UnixMilli() result, err := tx.ExecContext(ctx, ` UPDATE wallet_recharge_products - SET product_name = ?, description = ?, platform = ?, channel = ?, currency_code = ?, + SET audience_type = ?, product_name = ?, description = ?, platform = ?, channel = ?, currency_code = ?, amount_micro = ?, coin_amount = ?, resource_asset_type = ?, status = ?, updated_by_user_id = ?, updated_at_ms = ? WHERE app_code = ? AND product_id = ?`, - command.ProductName, command.Description, command.Platform, ledger.RechargeChannelForPlatform(command.Platform), - ledger.PaidCurrencyUSDT, command.AmountMicro, command.CoinAmount, ledger.AssetCoin, productStatus(command.Enabled), + command.AudienceType, command.ProductName, command.Description, command.Platform, ledger.RechargeChannelForPlatform(command.Platform), + ledger.PaidCurrencyUSDT, command.AmountMicro, command.CoinAmount, rechargeProductAssetType(command.AudienceType), productStatus(command.Enabled), command.OperatorUserID, nowMs, command.AppCode, command.ProductID, ) if err != nil { @@ -268,7 +269,8 @@ func rechargeProductSelectSQL() string { SELECT p.app_code, p.product_id, COALESCE(p.product_code, ''), p.product_name, p.description, p.platform, p.channel, p.currency_code, p.amount_micro, p.coin_amount, p.resource_asset_type, p.status, p.sort_order, - p.created_by_user_id, p.updated_by_user_id, p.created_at_ms, p.updated_at_ms + p.created_by_user_id, p.updated_by_user_id, p.created_at_ms, p.updated_at_ms, + COALESCE(p.audience_type, 'normal') FROM wallet_recharge_products p ` } @@ -295,6 +297,7 @@ func scanRechargeProduct(scanner scanTarget) (ledger.RechargeProduct, error) { &updatedBy, &product.CreatedAtMS, &product.UpdatedAtMS, + &product.AudienceType, ); err != nil { return ledger.RechargeProduct{}, err } @@ -306,6 +309,7 @@ func scanRechargeProduct(scanner scanTarget) (ledger.RechargeProduct, error) { } product.AmountMinor = product.AmountMicro product.Enabled = product.Status == ledger.RechargeProductStatusActive + product.AudienceType = ledger.NormalizeRechargeAudienceType(product.AudienceType) if createdBy.Valid { product.CreatedByUserID = createdBy.Int64 } @@ -372,6 +376,7 @@ func replaceRechargeProductRegions(ctx context.Context, tx *sql.Tx, appCode stri func normalizeRechargeProductQuery(query ledger.ListRechargeProductsQuery) ledger.ListRechargeProductsQuery { query.Status = ledger.NormalizeRechargeProductStatus(query.Status) query.Platform = ledger.NormalizeRechargeProductPlatform(query.Platform) + query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType) query.Keyword = strings.TrimSpace(query.Keyword) query.Page, query.PageSize = normalizePage(query.Page, query.PageSize) return query @@ -388,6 +393,10 @@ func rechargeProductWhereSQL(query ledger.ListRechargeProductsQuery) (string, [] where += ` AND p.platform = ?` args = append(args, query.Platform) } + if query.AudienceType != "" { + where += ` AND p.audience_type = ?` + args = append(args, query.AudienceType) + } if query.RegionID > 0 { where += ` AND EXISTS ( SELECT 1 FROM wallet_recharge_product_regions pr @@ -419,6 +428,7 @@ func normalizeRechargeProductCommand(command ledger.RechargeProductCommand) ledg command.ProductName = strings.TrimSpace(command.ProductName) command.Description = strings.TrimSpace(command.Description) command.Platform = ledger.NormalizeRechargeProductPlatform(command.Platform) + command.AudienceType = ledger.NormalizeRechargeAudienceType(command.AudienceType) command.RegionIDs = normalizeRechargeProductRegions(command.RegionIDs) return command } @@ -447,6 +457,13 @@ func productStatus(enabled bool) string { return ledger.RechargeProductStatusDisabled } +func rechargeProductAssetType(audienceType string) string { + if ledger.NormalizeRechargeAudienceType(audienceType) == ledger.RechargeAudienceCoinSeller { + return ledger.AssetCoinSellerCoin + } + return ledger.AssetCoin +} + func mapRechargeProductWriteError(err error) error { var mysqlErr *mysqlDriver.MySQLError if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 { diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index 6edf95ca..2fe4428f 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -34,6 +34,8 @@ const ( bizTypeSalaryExchangeToCoin = "salary_exchange_to_coin" bizTypeSalaryTransferToCoinSeller = "salary_transfer_to_coin_seller" bizTypeGooglePlayRecharge = "google_play_recharge" + bizTypeMifaPayRecharge = "mifapay" + bizTypeUSDTTRC20Recharge = "usdt_trc20" bizTypeGameDebit = "game_debit" bizTypeGameCredit = "game_credit" bizTypeGameRefund = "game_refund" @@ -108,10 +110,99 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { _ = db.Close() return nil, err } + if err := ensureExternalRechargeSchema(ctx, db); err != nil { + _ = db.Close() + return nil, err + } return &Repository{db: db}, nil } +func ensureExternalRechargeSchema(ctx context.Context, db *sql.DB) error { + if _, err := db.ExecContext(ctx, ` + ALTER TABLE wallet_recharge_products + ADD COLUMN audience_type VARCHAR(32) NOT NULL DEFAULT 'normal' COMMENT '充值档位适用用户类型:normal/coin_seller' AFTER app_code`); err != nil && !isDuplicateColumnError(err) { + return err + } + if _, err := db.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS third_party_payment_channels ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + provider_code VARCHAR(32) NOT NULL COMMENT '三方支付渠道编码', + provider_name VARCHAR(64) NOT NULL COMMENT '三方支付渠道名称', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '状态:active/disabled', + sort_order INT 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, provider_code), + KEY idx_third_party_payment_channels_status (app_code, status, sort_order) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='三方支付渠道配置表'`); err != nil { + return err + } + if _, err := db.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS third_party_payment_methods ( + method_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '支付方式 ID', + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + provider_code VARCHAR(32) NOT NULL COMMENT '三方支付渠道编码', + country_code VARCHAR(16) NOT NULL COMMENT 'MiFaPay countryCode,GLOBAL 表示全球方式', + country_name VARCHAR(64) NOT NULL DEFAULT '' COMMENT '国家/地区名称', + currency_code VARCHAR(8) NOT NULL COMMENT '下单币种', + pay_way VARCHAR(64) NOT NULL COMMENT 'MiFaPay payWay', + pay_type VARCHAR(64) NOT NULL COMMENT 'MiFaPay payType', + method_name VARCHAR(128) NOT NULL COMMENT '支付方式展示名', + logo_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '支付方式 logo', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '状态:active/disabled', + usd_to_currency_rate DECIMAL(24,8) NOT NULL DEFAULT 0 COMMENT '1 USD 可兑换本币金额;0 表示未配置,不在 H5 展示', + sort_order INT 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 (method_id), + UNIQUE KEY uk_third_party_payment_method (app_code, provider_code, country_code, pay_way, pay_type), + KEY idx_third_party_payment_methods_country (app_code, provider_code, country_code, status, sort_order) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='三方支付国家和方式配置表'`); err != nil { + return err + } + if _, err := db.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS external_recharge_orders ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + order_id VARCHAR(96) NOT NULL COMMENT '平台外部充值订单 ID', + command_id VARCHAR(128) NOT NULL COMMENT '客户端命令幂等 ID', + target_user_id BIGINT NOT NULL COMMENT '充值目标用户 ID', + target_region_id BIGINT NOT NULL COMMENT '充值目标区域 ID', + target_country_code VARCHAR(16) NOT NULL DEFAULT '' COMMENT '充值目标国家码', + audience_type VARCHAR(32) NOT NULL DEFAULT 'normal' COMMENT '商品适用用户类型', + product_id BIGINT NOT NULL COMMENT '充值商品 ID', + product_code VARCHAR(128) NOT NULL DEFAULT '' COMMENT '充值商品编码快照', + product_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '充值商品名称快照', + coin_amount BIGINT NOT NULL COMMENT '到账金币数量', + usd_minor_amount BIGINT NOT NULL COMMENT '美元最小单位金额', + provider_code VARCHAR(32) NOT NULL COMMENT '支付渠道:mifapay/usdt_trc20', + payment_method_id BIGINT NOT NULL DEFAULT 0 COMMENT '三方支付方式 ID', + country_code VARCHAR(16) NOT NULL DEFAULT '' COMMENT '支付国家码', + currency_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '支付币种', + provider_amount_minor BIGINT NOT NULL DEFAULT 0 COMMENT '三方下单金额最小单位', + pay_way VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'MiFaPay payWay', + pay_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'MiFaPay payType', + pay_url TEXT NULL COMMENT '三方收银台地址', + provider_order_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '三方订单号', + tx_hash VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'USDT-TRC20 交易哈希', + receive_address VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'USDT 收款地址', + status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/redirected/credited/failed', + failure_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '失败原因', + wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '入账钱包交易 ID', + provider_payload JSON NULL 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, order_id), + UNIQUE KEY uk_external_recharge_command (app_code, command_id), + KEY idx_external_recharge_user_time (app_code, target_user_id, created_at_ms), + KEY idx_external_recharge_provider_order (app_code, provider_code, provider_order_id), + KEY idx_external_recharge_tx (app_code, provider_code, tx_hash) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='H5 外部充值订单表'`); err != nil { + return err + } + return seedThirdPartyPaymentDefaults(ctx, db) +} + func ensureGiftDiamondRatioSchema(ctx context.Context, db *sql.DB) error { if _, err := db.ExecContext(ctx, ` CREATE TABLE IF NOT EXISTS gift_diamond_ratio_configs ( @@ -318,11 +409,13 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm } transactionID := transactionID(command.AppCode, command.CommandID) metadata := giftMetadata{ - AppCode: command.AppCode, - GiftID: command.GiftID, - GiftName: giftConfig.Name, - GiftIconURL: giftDisplayIconURL(giftConfig.Resource), - GiftAnimationURL: strings.TrimSpace(giftConfig.Resource.AnimationURL), + AppCode: command.AppCode, + GiftID: command.GiftID, + GiftName: giftConfig.Name, + GiftIconURL: giftDisplayIconURL(giftConfig.Resource), + GiftAnimationURL: strings.TrimSpace(giftConfig.Resource.AnimationURL), + // effect_types 是后台控制动画、音乐、全局广播的单一来源,账务快照保存后可以被房间事件稳定复用。 + GiftEffectTypes: normalizeGiftEffectTypes(giftConfig.EffectTypes), ResourceID: giftConfig.ResourceID, ResourceSnapshot: mustJSON(giftConfig.Resource), GiftTypeCode: giftConfig.GiftTypeCode, @@ -538,11 +631,13 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb transactionID := transactionID(command.AppCode, target.CommandID) senderAfter := sender.AvailableAmount - chargeAmount metadata := giftMetadata{ - AppCode: command.AppCode, - GiftID: command.GiftID, - GiftName: giftConfig.Name, - GiftIconURL: giftDisplayIconURL(giftConfig.Resource), - GiftAnimationURL: strings.TrimSpace(giftConfig.Resource.AnimationURL), + AppCode: command.AppCode, + GiftID: command.GiftID, + GiftName: giftConfig.Name, + GiftIconURL: giftDisplayIconURL(giftConfig.Resource), + GiftAnimationURL: strings.TrimSpace(giftConfig.Resource.AnimationURL), + // 批量送礼复用单礼物配置快照,确保每个接收方产生的 RoomGiftSent 都带同一份 effect_types。 + GiftEffectTypes: normalizeGiftEffectTypes(giftConfig.EffectTypes), ResourceID: giftConfig.ResourceID, ResourceSnapshot: mustJSON(giftConfig.Resource), GiftTypeCode: giftConfig.GiftTypeCode, @@ -2723,20 +2818,21 @@ func (r *Repository) balanceAfterTransaction(ctx context.Context, tx *sql.Tx, tr } type giftMetadata struct { - AppCode string `json:"app_code"` - GiftID string `json:"gift_id"` - GiftName string `json:"gift_name"` - GiftIconURL string `json:"gift_icon_url"` - GiftAnimationURL string `json:"gift_animation_url"` - ResourceID int64 `json:"resource_id"` - ResourceSnapshot string `json:"resource_snapshot_json"` - GiftTypeCode string `json:"gift_type_code"` - CPRelationType string `json:"cp_relation_type"` - PresentationJSON string `json:"presentation_json"` - SortOrder int32 `json:"sort_order"` - GiftCount int32 `json:"gift_count"` - PriceVersion string `json:"price_version"` - CoinPrice int64 `json:"coin_price"` + AppCode string `json:"app_code"` + GiftID string `json:"gift_id"` + GiftName string `json:"gift_name"` + GiftIconURL string `json:"gift_icon_url"` + GiftAnimationURL string `json:"gift_animation_url"` + GiftEffectTypes []string `json:"gift_effect_types"` + ResourceID int64 `json:"resource_id"` + ResourceSnapshot string `json:"resource_snapshot_json"` + GiftTypeCode string `json:"gift_type_code"` + CPRelationType string `json:"cp_relation_type"` + PresentationJSON string `json:"presentation_json"` + SortOrder int32 `json:"sort_order"` + GiftCount int32 `json:"gift_count"` + PriceVersion string `json:"price_version"` + CoinPrice int64 `json:"coin_price"` // GiftPointAmount 是历史礼物积分单价字段,新送礼固定写 0,业务统计只读真实扣费和热度。 GiftPointAmount int64 `json:"gift_point_amount"` HeatUnitValue int64 `json:"heat_unit_value"` @@ -2848,6 +2944,9 @@ type gameCoinMetadata struct { type coinSellerTransferMetadata struct { AppCode string `json:"app_code"` + PaymentOrderID string `json:"payment_order_id,omitempty"` + ProviderCode string `json:"provider,omitempty"` + PaymentMethodID int64 `json:"payment_method_id,omitempty"` SellerUserID int64 `json:"seller_user_id"` TargetUserID int64 `json:"target_user_id"` TargetCountryID int64 `json:"target_country_id"` @@ -2866,6 +2965,7 @@ type coinSellerTransferMetadata struct { RechargePolicyVersion string `json:"recharge_policy_version"` RechargePolicyCoinAmount int64 `json:"recharge_policy_coin_amount"` RechargePolicyUSDMinorAmount int64 `json:"recharge_policy_usd_minor_amount"` + RechargeType string `json:"recharge_type,omitempty"` } type salaryExchangeMetadata struct { @@ -2938,6 +3038,7 @@ func receiptFromGiftMetadata(transactionID string, metadata giftMetadata) ledger GiftName: metadata.GiftName, GiftIconURL: metadata.GiftIconURL, GiftAnimationURL: metadata.GiftAnimationURL, + GiftEffectTypes: normalizeGiftEffectTypes(metadata.GiftEffectTypes), PriceVersion: metadata.PriceVersion, BalanceAfter: metadata.BalanceAfter, HostPeriodDiamondAdded: metadata.HostPeriodDiamondAdded, @@ -3270,10 +3371,21 @@ func coinSellerTransferredEvent(transactionID string, commandID string, sellerUs } func rechargeRecordedEvent(transactionID string, commandID string, targetUserID int64, availableDelta int64, payload any, nowMs int64) walletOutboxEvent { + assetType := ledger.AssetCoin if metadata, ok := payload.(coinSellerTransferMetadata); ok { + if strings.TrimSpace(metadata.TargetAssetType) != "" { + assetType = metadata.TargetAssetType + } + rechargeType := strings.TrimSpace(metadata.RechargeType) + if rechargeType == "" { + rechargeType = "coin_seller_transfer" + } // WalletRechargeRecorded 是统计服务的充值入口;币商转账没有 USDT 金额,但必须带目标用户真实国家/区域,避免统计继续把 region 当 country。 payload = map[string]any{ "app_code": metadata.AppCode, + "payment_order_id": metadata.PaymentOrderID, + "provider": metadata.ProviderCode, + "payment_method_id": metadata.PaymentMethodID, "seller_user_id": metadata.SellerUserID, "target_user_id": metadata.TargetUserID, "target_country_id": metadata.TargetCountryID, @@ -3294,17 +3406,17 @@ func rechargeRecordedEvent(transactionID string, commandID string, targetUserID "recharge_policy_version": metadata.RechargePolicyVersion, "recharge_policy_coin_amount": metadata.RechargePolicyCoinAmount, "recharge_policy_usd_minor_amount": metadata.RechargePolicyUSDMinorAmount, - "recharge_type": "coin_seller_transfer", + "recharge_type": rechargeType, "created_at_ms": nowMs, } } return walletOutboxEvent{ - EventID: eventID(transactionID, "WalletRechargeRecorded", targetUserID, ledger.AssetCoin), + EventID: eventID(transactionID, "WalletRechargeRecorded", targetUserID, assetType), EventType: "WalletRechargeRecorded", TransactionID: transactionID, CommandID: commandID, UserID: targetUserID, - AssetType: ledger.AssetCoin, + AssetType: assetType, AvailableDelta: availableDelta, FrozenDelta: 0, Payload: payload, diff --git a/services/wallet-service/internal/storage/mysql/third_party_payment_repository.go b/services/wallet-service/internal/storage/mysql/third_party_payment_repository.go new file mode 100644 index 00000000..a8d1150f --- /dev/null +++ b/services/wallet-service/internal/storage/mysql/third_party_payment_repository.go @@ -0,0 +1,675 @@ +package mysql + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + "hyapp/services/wallet-service/internal/domain/ledger" + + mysqlDriver "github.com/go-sql-driver/mysql" +) + +func isDuplicateColumnError(err error) bool { + var mysqlErr *mysqlDriver.MySQLError + return errors.As(err, &mysqlErr) && mysqlErr.Number == 1060 +} + +type seedPaymentMethod struct { + CountryCode string + CountryName string + CurrencyCode string + PayWay string + PayType string + MethodName string + Rate string + SortOrder int +} + +func seedThirdPartyPaymentDefaults(ctx context.Context, db *sql.DB) error { + nowMS := time.Now().UnixMilli() + if _, err := db.ExecContext(ctx, ` + INSERT INTO third_party_payment_channels ( + app_code, provider_code, provider_name, status, sort_order, created_at_ms, updated_at_ms + ) VALUES ('lalu', 'mifapay', 'MiFaPay', 'active', 10, ?, ?) + ON DUPLICATE KEY UPDATE provider_name = VALUES(provider_name), status = VALUES(status), sort_order = VALUES(sort_order), updated_at_ms = VALUES(updated_at_ms)`, nowMS, nowMS); err != nil { + return err + } + // MiFaPay 是 H5 的统一三方收银台入口:后台只管理 MiFaPay 自己支持的国家和支付产品,不再关心任何下游通道名称。 + // 这里只 seed 可运营的 payWay/payType 清单;商户号、商户标识、商户私钥、平台公钥都属于运行环境密钥,不进入数据库 seed。 + methods := []seedPaymentMethod{ + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "BankTransfer", PayType: "BCA", MethodName: "BCA Bank", Rate: "1.00000000", SortOrder: 110}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "BankTransfer", PayType: "BNI", MethodName: "BNI Bank", Rate: "1.00000000", SortOrder: 120}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "BankTransfer", PayType: "BRI", MethodName: "BRI Bank", Rate: "1.00000000", SortOrder: 130}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "BankTransfer", PayType: "Mandiri", MethodName: "Mandiri Bank", Rate: "1.00000000", SortOrder: 140}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "BankTransfer", PayType: "Permata", MethodName: "Permata Bank", Rate: "1.00000000", SortOrder: 150}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "Ewallet", PayType: "DANA", MethodName: "DANA", Rate: "1.00000000", SortOrder: 160}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "Ewallet", PayType: "LinkAja", MethodName: "LinkAja", Rate: "1.00000000", SortOrder: 170}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "Ewallet", PayType: "OVO", MethodName: "OVO", Rate: "1.00000000", SortOrder: 180}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "Ewallet", PayType: "ShopeePay", MethodName: "ShopeePay", Rate: "1.00000000", SortOrder: 190}, + {CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", PayWay: "QR", PayType: "QRIS", MethodName: "QRIS", Rate: "1.00000000", SortOrder: 200}, + {CountryCode: "VN", CountryName: "Vietnam", CurrencyCode: "VND", PayWay: "BankTransfer", PayType: "VN_BankTransfer", MethodName: "Vietnam Bank Transfer", Rate: "1.00000000", SortOrder: 210}, + {CountryCode: "VN", CountryName: "Vietnam", CurrencyCode: "VND", PayWay: "QR", PayType: "VietQR", MethodName: "VietQR", Rate: "1.00000000", SortOrder: 220}, + {CountryCode: "BH", CountryName: "Bahrain", CurrencyCode: "BHD", PayWay: "Card", PayType: "CreditCard", MethodName: "Bahrain Credit Card", Rate: "1.00000000", SortOrder: 310}, + {CountryCode: "QA", CountryName: "Qatar", CurrencyCode: "QAR", PayWay: "Card", PayType: "CreditCard", MethodName: "Qatar Credit Card", Rate: "1.00000000", SortOrder: 410}, + {CountryCode: "OM", CountryName: "Oman", CurrencyCode: "OMR", PayWay: "Card", PayType: "CreditCard", MethodName: "Oman Credit Card", Rate: "1.00000000", SortOrder: 510}, + {CountryCode: "AE", CountryName: "United Arab Emirates", CurrencyCode: "AED", PayWay: "Card", PayType: "CreditCard", MethodName: "UAE Credit Card", Rate: "1.00000000", SortOrder: 610}, + {CountryCode: "KW", CountryName: "Kuwait", CurrencyCode: "KWD", PayWay: "Card", PayType: "Knet", MethodName: "KNET", Rate: "1.00000000", SortOrder: 710}, + {CountryCode: "SA", CountryName: "Saudi Arabia", CurrencyCode: "SAR", PayWay: "Card", PayType: "MADA", MethodName: "MADA", Rate: "1.00000000", SortOrder: 810}, + {CountryCode: "SA", CountryName: "Saudi Arabia", CurrencyCode: "SAR", PayWay: "Ewallet", PayType: "ApplePay", MethodName: "ApplePay", Rate: "1.00000000", SortOrder: 820}, + {CountryCode: "SA", CountryName: "Saudi Arabia", CurrencyCode: "SAR", PayWay: "Ewallet", PayType: "STCPay", MethodName: "STCPay", Rate: "1.00000000", SortOrder: 830}, + {CountryCode: "IN", CountryName: "India", CurrencyCode: "INR", PayWay: "Ewallet", PayType: "BHIM", MethodName: "BHIM", Rate: "1.00000000", SortOrder: 910}, + {CountryCode: "IN", CountryName: "India", CurrencyCode: "INR", PayWay: "Ewallet", PayType: "GooglePay", MethodName: "GooglePay", Rate: "1.00000000", SortOrder: 920}, + {CountryCode: "IN", CountryName: "India", CurrencyCode: "INR", PayWay: "Ewallet", PayType: "Mobikwik", MethodName: "Mobikwik", Rate: "1.00000000", SortOrder: 930}, + {CountryCode: "IN", CountryName: "India", CurrencyCode: "INR", PayWay: "Ewallet", PayType: "Paytm", MethodName: "Paytm", Rate: "1.00000000", SortOrder: 940}, + {CountryCode: "IN", CountryName: "India", CurrencyCode: "INR", PayWay: "Ewallet", PayType: "PhonePe", MethodName: "PhonePe", Rate: "1.00000000", SortOrder: 950}, + {CountryCode: "IN", CountryName: "India", CurrencyCode: "INR", PayWay: "Ewallet", PayType: "UPI", MethodName: "UPI", Rate: "1.00000000", SortOrder: 960}, + {CountryCode: "BD", CountryName: "Bangladesh", CurrencyCode: "BDT", PayWay: "Ewallet", PayType: "bKash", MethodName: "bKash", Rate: "1.00000000", SortOrder: 1010}, + {CountryCode: "PK", CountryName: "Pakistan", CurrencyCode: "PKR", PayWay: "Ewallet", PayType: "Easypaisa", MethodName: "Easypaisa", Rate: "1.00000000", SortOrder: 1110}, + {CountryCode: "PK", CountryName: "Pakistan", CurrencyCode: "PKR", PayWay: "Ewallet", PayType: "JazzCash", MethodName: "JazzCash", Rate: "1.00000000", SortOrder: 1120}, + {CountryCode: "EG", CountryName: "Egypt", CurrencyCode: "EGP", PayWay: "Ewallet", PayType: "FawryPay", MethodName: "FawryPay", Rate: "1.00000000", SortOrder: 1210}, + {CountryCode: "EG", CountryName: "Egypt", CurrencyCode: "EGP", PayWay: "Ewallet", PayType: "Meeza", MethodName: "Meeza", Rate: "1.00000000", SortOrder: 1220}, + {CountryCode: "EG", CountryName: "Egypt", CurrencyCode: "EGP", PayWay: "Ewallet", PayType: "Orange", MethodName: "Orange", Rate: "1.00000000", SortOrder: 1230}, + {CountryCode: "EG", CountryName: "Egypt", CurrencyCode: "EGP", PayWay: "Ewallet", PayType: "Vodafone", MethodName: "Vodafone", Rate: "1.00000000", SortOrder: 1240}, + {CountryCode: "PH", CountryName: "Philippines", CurrencyCode: "PHP", PayWay: "Ewallet", PayType: "Gcash", MethodName: "GCash", Rate: "1.00000000", SortOrder: 1310}, + {CountryCode: "PH", CountryName: "Philippines", CurrencyCode: "PHP", PayWay: "Ewallet", PayType: "PayMaya", MethodName: "PayMaya", Rate: "1.00000000", SortOrder: 1320}, + {CountryCode: "PH", CountryName: "Philippines", CurrencyCode: "PHP", PayWay: "QR", PayType: "QRPH", MethodName: "QRPH", Rate: "1.00000000", SortOrder: 1330}, + } + for _, method := range methods { + rate := strings.TrimSpace(method.Rate) + if rate == "" { + rate = "1.00000000" + } + if _, err := db.ExecContext(ctx, ` + INSERT INTO third_party_payment_methods ( + app_code, provider_code, country_code, country_name, currency_code, pay_way, pay_type, + method_name, logo_url, status, usd_to_currency_rate, sort_order, created_at_ms, updated_at_ms + ) VALUES ('lalu', 'mifapay', ?, ?, ?, ?, ?, ?, '', 'active', ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + country_name = VALUES(country_name), + currency_code = VALUES(currency_code), + method_name = VALUES(method_name), + sort_order = VALUES(sort_order), + usd_to_currency_rate = IF(usd_to_currency_rate <= 0, VALUES(usd_to_currency_rate), usd_to_currency_rate), + updated_at_ms = VALUES(updated_at_ms)`, + strings.ToUpper(method.CountryCode), method.CountryName, strings.ToUpper(method.CurrencyCode), + method.PayWay, method.PayType, method.MethodName, rate, method.SortOrder, nowMS, nowMS, + ); err != nil { + return err + } + } + return nil +} + +func (r *Repository) ListThirdPartyPaymentChannels(ctx context.Context, query ledger.ListThirdPartyPaymentChannelsQuery) ([]ledger.ThirdPartyPaymentChannel, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, query.AppCode) + app := appcode.FromContext(ctx) + providerCode := strings.ToLower(strings.TrimSpace(query.ProviderCode)) + status := ledger.NormalizeThirdPartyPaymentStatus(query.Status) + where := `WHERE app_code = ?` + args := []any{app} + if providerCode != "" { + where += ` AND provider_code = ?` + args = append(args, providerCode) + } + if status != "" { + where += ` AND status = ?` + args = append(args, status) + } + rows, err := r.db.QueryContext(ctx, ` + SELECT app_code, provider_code, provider_name, status, sort_order + FROM third_party_payment_channels + `+where+` + ORDER BY sort_order ASC, provider_code ASC`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + channels := make([]ledger.ThirdPartyPaymentChannel, 0) + for rows.Next() { + var channel ledger.ThirdPartyPaymentChannel + if err := rows.Scan(&channel.AppCode, &channel.ProviderCode, &channel.ProviderName, &channel.Status, &channel.SortOrder); err != nil { + return nil, err + } + channels = append(channels, channel) + } + if err := rows.Err(); err != nil { + return nil, err + } + for index := range channels { + methods, err := r.listThirdPartyPaymentMethods(ctx, app, channels[index].ProviderCode, "", query.IncludeDisabledMethods, false) + if err != nil { + return nil, err + } + channels[index].Methods = methods + } + return channels, nil +} + +func (r *Repository) SetThirdPartyPaymentMethodStatus(ctx context.Context, command ledger.ThirdPartyPaymentMethodStatusCommand) (ledger.ThirdPartyPaymentMethod, error) { + if r == nil || r.db == nil { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + status := ledger.ThirdPartyPaymentStatusDisabled + if command.Enabled { + status = ledger.ThirdPartyPaymentStatusActive + } + result, err := r.db.ExecContext(ctx, ` + UPDATE third_party_payment_methods + SET status = ?, updated_at_ms = ? + WHERE app_code = ? AND method_id = ?`, + status, time.Now().UnixMilli(), appcode.FromContext(ctx), command.MethodID, + ) + if err != nil { + return ledger.ThirdPartyPaymentMethod{}, err + } + if affected, err := result.RowsAffected(); err != nil { + return ledger.ThirdPartyPaymentMethod{}, err + } else if affected == 0 { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.NotFound, "payment method not found") + } + return r.GetThirdPartyPaymentMethod(ctx, appcode.FromContext(ctx), command.MethodID) +} + +func (r *Repository) UpdateThirdPartyPaymentRate(ctx context.Context, command ledger.ThirdPartyPaymentRateCommand) (ledger.ThirdPartyPaymentMethod, error) { + if r == nil || r.db == nil { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + result, err := r.db.ExecContext(ctx, ` + UPDATE third_party_payment_methods + SET usd_to_currency_rate = ?, updated_at_ms = ? + WHERE app_code = ? AND method_id = ?`, + command.USDToCurrencyRate, time.Now().UnixMilli(), appcode.FromContext(ctx), command.MethodID, + ) + if err != nil { + return ledger.ThirdPartyPaymentMethod{}, err + } + if affected, err := result.RowsAffected(); err != nil { + return ledger.ThirdPartyPaymentMethod{}, err + } else if affected == 0 { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.NotFound, "payment method not found") + } + return r.GetThirdPartyPaymentMethod(ctx, appcode.FromContext(ctx), command.MethodID) +} + +func (r *Repository) GetThirdPartyPaymentMethod(ctx context.Context, appCode string, methodID int64) (ledger.ThirdPartyPaymentMethod, error) { + ctx = contextWithCommandApp(ctx, appCode) + row := r.db.QueryRowContext(ctx, thirdPartyPaymentMethodSelectSQL()+` + WHERE m.app_code = ? AND m.method_id = ?`, + appcode.FromContext(ctx), methodID, + ) + method, err := scanThirdPartyPaymentMethod(row) + if errors.Is(err, sql.ErrNoRows) { + return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.NotFound, "payment method not found") + } + return method, err +} + +func (r *Repository) ListH5RechargeOptions(ctx context.Context, query ledger.H5RechargeOptionsQuery) (ledger.H5RechargeOptions, error) { + if r == nil || r.db == nil { + return ledger.H5RechargeOptions{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, query.AppCode) + products, _, err := r.ListRechargeProducts(ctx, query.TargetUserID, query.TargetRegionID, ledger.RechargeProductPlatformWeb, query.AudienceType) + if err != nil { + return ledger.H5RechargeOptions{}, err + } + methods, err := r.listThirdPartyPaymentMethods(ctx, appcode.FromContext(ctx), ledger.PaymentProviderMifaPay, strings.ToUpper(strings.TrimSpace(query.TargetCountryCode)), false, true) + if err != nil { + return ledger.H5RechargeOptions{}, err + } + return ledger.H5RechargeOptions{Products: products, PaymentMethods: methods}, nil +} + +func (r *Repository) listThirdPartyPaymentMethods(ctx context.Context, appCode string, providerCode string, countryCode string, includeDisabled bool, requireRate bool) ([]ledger.ThirdPartyPaymentMethod, error) { + where := `WHERE m.app_code = ? AND m.provider_code = ?` + args := []any{appCode, providerCode} + if countryCode != "" { + where += ` AND m.country_code IN (?, 'GLOBAL')` + args = append(args, countryCode) + } + if !includeDisabled { + where += ` AND m.status = 'active'` + } + if requireRate { + where += ` AND m.usd_to_currency_rate > 0` + } + rows, err := r.db.QueryContext(ctx, thirdPartyPaymentMethodSelectSQL()+` + `+where+` + ORDER BY CASE WHEN m.country_code = 'GLOBAL' THEN 1 ELSE 0 END ASC, m.sort_order ASC, m.method_id ASC`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + methods := make([]ledger.ThirdPartyPaymentMethod, 0) + for rows.Next() { + method, err := scanThirdPartyPaymentMethod(rows) + if err != nil { + return nil, err + } + methods = append(methods, method) + } + return methods, rows.Err() +} + +func thirdPartyPaymentMethodSelectSQL() string { + return ` + SELECT m.method_id, m.app_code, m.provider_code, c.provider_name, m.country_code, m.country_name, + m.currency_code, m.pay_way, m.pay_type, m.method_name, m.logo_url, m.status, + CAST(m.usd_to_currency_rate AS CHAR), m.sort_order, m.created_at_ms, m.updated_at_ms + FROM third_party_payment_methods m + JOIN third_party_payment_channels c ON c.app_code = m.app_code AND c.provider_code = m.provider_code + ` +} + +func scanThirdPartyPaymentMethod(scanner scanTarget) (ledger.ThirdPartyPaymentMethod, error) { + var method ledger.ThirdPartyPaymentMethod + if err := scanner.Scan( + &method.MethodID, + &method.AppCode, + &method.ProviderCode, + &method.ProviderName, + &method.CountryCode, + &method.CountryName, + &method.CurrencyCode, + &method.PayWay, + &method.PayType, + &method.MethodName, + &method.LogoURL, + &method.Status, + &method.USDToCurrencyRate, + &method.SortOrder, + &method.CreatedAtMS, + &method.UpdatedAtMS, + ); err != nil { + return ledger.ThirdPartyPaymentMethod{}, err + } + return method, nil +} + +func (r *Repository) CreateExternalRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct, method *ledger.ThirdPartyPaymentMethod, providerAmountMinor int64, receiveAddress string) (ledger.ExternalRechargeOrder, error) { + if r == nil || r.db == nil { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + if existing, found, err := r.lookupExternalRechargeOrderByCommand(ctx, command.AppCode, command.CommandID); err != nil || found { + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + existing.IdempotentReplay = true + return existing, nil + } + order := externalRechargeOrderFromCommand(command, product, method, providerAmountMinor, receiveAddress, time.Now().UnixMilli()) + _, err := r.db.ExecContext(ctx, ` + INSERT INTO external_recharge_orders ( + app_code, order_id, command_id, target_user_id, target_region_id, target_country_code, audience_type, + product_id, product_code, product_name, coin_amount, usd_minor_amount, provider_code, payment_method_id, + country_code, currency_code, provider_amount_minor, pay_way, pay_type, receive_address, status, + created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + order.AppCode, order.OrderID, order.CommandID, order.TargetUserID, order.TargetRegionID, order.TargetCountryCode, + order.AudienceType, order.ProductID, order.ProductCode, order.ProductName, order.CoinAmount, order.USDMinorAmount, + order.ProviderCode, order.PaymentMethodID, order.CountryCode, order.CurrencyCode, order.ProviderAmountMinor, + order.PayWay, order.PayType, order.ReceiveAddress, order.Status, order.CreatedAtMS, order.UpdatedAtMS, + ) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + return order, nil +} + +func (r *Repository) MarkExternalRechargeOrderRedirected(ctx context.Context, appCode string, orderID string, providerOrderID string, payURL string, payloadJSON string) (ledger.ExternalRechargeOrder, error) { + ctx = contextWithCommandApp(ctx, appCode) + nowMS := time.Now().UnixMilli() + _, err := r.db.ExecContext(ctx, ` + UPDATE external_recharge_orders + SET provider_order_id = ?, pay_url = ?, provider_payload = ?, status = ?, updated_at_ms = ? + WHERE app_code = ? AND order_id = ? AND status = ?`, + strings.TrimSpace(providerOrderID), strings.TrimSpace(payURL), nullableJSON(payloadJSON), ledger.ExternalRechargeStatusRedirected, + nowMS, appcode.FromContext(ctx), strings.TrimSpace(orderID), ledger.ExternalRechargeStatusPending, + ) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + return r.GetExternalRechargeOrder(ctx, appcode.FromContext(ctx), orderID) +} + +func (r *Repository) AttachExternalRechargeTx(ctx context.Context, command ledger.SubmitExternalRechargeTxCommand, payloadJSON string) (ledger.ExternalRechargeOrder, error) { + ctx = contextWithCommandApp(ctx, command.AppCode) + txHash := strings.TrimSpace(command.TxHash) + result, err := r.db.ExecContext(ctx, ` + UPDATE external_recharge_orders + SET tx_hash = ?, provider_payload = ?, updated_at_ms = ? + WHERE app_code = ? AND order_id = ? AND target_user_id = ? AND provider_code = ? AND status = ?`, + txHash, nullableJSON(payloadJSON), time.Now().UnixMilli(), appcode.FromContext(ctx), strings.TrimSpace(command.OrderID), + command.TargetUserID, ledger.PaymentProviderUSDTTRC20, ledger.ExternalRechargeStatusPending, + ) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if affected, err := result.RowsAffected(); err != nil { + return ledger.ExternalRechargeOrder{}, err + } else if affected == 0 { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Conflict, "external recharge order is not pending usdt order") + } + return r.GetExternalRechargeOrder(ctx, appcode.FromContext(ctx), command.OrderID) +} + +func (r *Repository) CreditExternalRechargeOrder(ctx context.Context, appCode string, orderID string, providerOrderID string, txHash string, payloadJSON string) (ledger.ExternalRechargeOrder, error) { + if r == nil || r.db == nil { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, appCode) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + defer func() { _ = tx.Rollback() }() + + order, err := r.lockExternalRechargeOrder(ctx, tx, appcode.FromContext(ctx), strings.TrimSpace(orderID)) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if order.Status == ledger.ExternalRechargeStatusCredited { + order.IdempotentReplay = true + return order, nil + } + if order.Status == ledger.ExternalRechargeStatusFailed { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Conflict, "external recharge order is failed") + } + if strings.TrimSpace(txHash) != "" { + if err := r.ensureExternalTxHashUnused(ctx, tx, order.AppCode, order.ProviderCode, strings.TrimSpace(txHash), order.OrderID); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + order.TxHash = strings.TrimSpace(txHash) + } + if strings.TrimSpace(providerOrderID) != "" { + order.ProviderOrderID = strings.TrimSpace(providerOrderID) + } + + nowMS := time.Now().UnixMilli() + assetType := ledger.AssetCoin + rechargeType := order.ProviderCode + bizType := providerRechargeBizType(order.ProviderCode) + isCoinSellerRecharge := order.AudienceType == ledger.RechargeAudienceCoinSeller + if isCoinSellerRecharge { + assetType = ledger.AssetCoinSellerCoin + rechargeType = "coin_seller_recharge" + bizType = "coin_seller_recharge" + } + account, err := r.lockAccount(ctx, tx, order.TargetUserID, assetType, true, nowMS) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + transactionID := transactionID(order.AppCode, order.CommandID) + if _, exists, err := r.lookupTransaction(ctx, tx, order.CommandID, externalRechargeRequestHash(order, assetType), bizType); err != nil || exists { + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + return r.lockExternalRechargeOrder(ctx, tx, order.AppCode, order.OrderID) + } + balanceAfter, err := checkedAdd(account.AvailableAmount, order.CoinAmount) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + metadata := coinSellerTransferMetadata{ + AppCode: order.AppCode, + PaymentOrderID: order.OrderID, + ProviderCode: order.ProviderCode, + PaymentMethodID: order.PaymentMethodID, + TargetUserID: order.TargetUserID, + TargetRegionID: order.TargetRegionID, + Amount: order.CoinAmount, + TargetAssetType: assetType, + TargetBalanceAfter: balanceAfter, + RechargeUSDMinor: order.USDMinorAmount, + RechargeCurrencyCode: order.CurrencyCode, + RechargePolicyID: order.ProductID, + RechargePolicyVersion: order.ProductCode, + RechargePolicyCoinAmount: order.CoinAmount, + RechargePolicyUSDMinorAmount: order.USDMinorAmount, + RechargeType: rechargeType, + } + if err := r.insertTransaction(ctx, tx, transactionID, order.CommandID, bizType, externalRechargeRequestHash(order, assetType), order.OrderID, metadata, nowMS); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if err := r.applyAccountDelta(ctx, tx, account, order.CoinAmount, 0, nowMS); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: order.TargetUserID, + AssetType: assetType, + AvailableDelta: order.CoinAmount, + AvailableAfter: balanceAfter, + FrozenAfter: account.FrozenAmount, + CreatedAtMS: nowMS, + }); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if err := r.insertRechargeRecord(ctx, tx, transactionID, metadata, nowMS); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if _, err := tx.ExecContext(ctx, ` + UPDATE external_recharge_orders + SET status = ?, provider_order_id = ?, tx_hash = ?, wallet_transaction_id = ?, provider_payload = ?, updated_at_ms = ? + WHERE app_code = ? AND order_id = ?`, + ledger.ExternalRechargeStatusCredited, order.ProviderOrderID, order.TxHash, transactionID, nullableJSON(payloadJSON), + nowMS, order.AppCode, order.OrderID, + ); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + balanceChangedEvent(transactionID, order.CommandID, order.TargetUserID, assetType, order.CoinAmount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMS), + rechargeRecordedEvent(transactionID, order.CommandID, order.TargetUserID, order.CoinAmount, metadata, nowMS), + }); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + if err := tx.Commit(); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + return r.GetExternalRechargeOrder(ctx, order.AppCode, order.OrderID) +} + +func (r *Repository) MarkExternalRechargeOrderFailed(ctx context.Context, appCode string, orderID string, reason string, payloadJSON string) (ledger.ExternalRechargeOrder, error) { + ctx = contextWithCommandApp(ctx, appCode) + _, err := r.db.ExecContext(ctx, ` + UPDATE external_recharge_orders + SET status = ?, failure_reason = ?, provider_payload = ?, updated_at_ms = ? + WHERE app_code = ? AND order_id = ? AND status <> ?`, + ledger.ExternalRechargeStatusFailed, trimMax(reason, 512), nullableJSON(payloadJSON), time.Now().UnixMilli(), + appcode.FromContext(ctx), strings.TrimSpace(orderID), ledger.ExternalRechargeStatusCredited, + ) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + return r.GetExternalRechargeOrder(ctx, appcode.FromContext(ctx), orderID) +} + +func (r *Repository) GetExternalRechargeOrder(ctx context.Context, appCode string, orderID string) (ledger.ExternalRechargeOrder, error) { + ctx = contextWithCommandApp(ctx, appCode) + order, err := scanExternalRechargeOrder(r.db.QueryRowContext(ctx, externalRechargeOrderSelectSQL()+` + WHERE app_code = ? AND order_id = ?`, + appcode.FromContext(ctx), strings.TrimSpace(orderID), + )) + if errors.Is(err, sql.ErrNoRows) { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.NotFound, "external recharge order not found") + } + return order, err +} + +func (r *Repository) lookupExternalRechargeOrderByCommand(ctx context.Context, appCode string, commandID string) (ledger.ExternalRechargeOrder, bool, error) { + order, err := scanExternalRechargeOrder(r.db.QueryRowContext(ctx, externalRechargeOrderSelectSQL()+` + WHERE app_code = ? AND command_id = ?`, + appCode, strings.TrimSpace(commandID), + )) + if errors.Is(err, sql.ErrNoRows) { + return ledger.ExternalRechargeOrder{}, false, nil + } + return order, err == nil, err +} + +func (r *Repository) lockExternalRechargeOrder(ctx context.Context, tx *sql.Tx, appCode string, orderID string) (ledger.ExternalRechargeOrder, error) { + order, err := scanExternalRechargeOrder(tx.QueryRowContext(ctx, externalRechargeOrderSelectSQL()+` + WHERE app_code = ? AND order_id = ? + FOR UPDATE`, + appCode, strings.TrimSpace(orderID), + )) + if errors.Is(err, sql.ErrNoRows) { + return ledger.ExternalRechargeOrder{}, xerr.New(xerr.NotFound, "external recharge order not found") + } + return order, err +} + +func externalRechargeOrderSelectSQL() string { + return ` + SELECT app_code, order_id, command_id, target_user_id, target_region_id, target_country_code, audience_type, + product_id, product_code, product_name, coin_amount, usd_minor_amount, provider_code, payment_method_id, + country_code, currency_code, provider_amount_minor, pay_way, pay_type, COALESCE(pay_url, ''), + provider_order_id, tx_hash, receive_address, status, failure_reason, wallet_transaction_id, + COALESCE(CAST(provider_payload AS CHAR), ''), created_at_ms, updated_at_ms + FROM external_recharge_orders + ` +} + +func scanExternalRechargeOrder(scanner scanTarget) (ledger.ExternalRechargeOrder, error) { + var order ledger.ExternalRechargeOrder + if err := scanner.Scan( + &order.AppCode, + &order.OrderID, + &order.CommandID, + &order.TargetUserID, + &order.TargetRegionID, + &order.TargetCountryCode, + &order.AudienceType, + &order.ProductID, + &order.ProductCode, + &order.ProductName, + &order.CoinAmount, + &order.USDMinorAmount, + &order.ProviderCode, + &order.PaymentMethodID, + &order.CountryCode, + &order.CurrencyCode, + &order.ProviderAmountMinor, + &order.PayWay, + &order.PayType, + &order.PayURL, + &order.ProviderOrderID, + &order.TxHash, + &order.ReceiveAddress, + &order.Status, + &order.FailureReason, + &order.TransactionID, + &order.ProviderPayloadJSON, + &order.CreatedAtMS, + &order.UpdatedAtMS, + ); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + return order, nil +} + +func externalRechargeOrderFromCommand(command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct, method *ledger.ThirdPartyPaymentMethod, providerAmountMinor int64, receiveAddress string, nowMS int64) ledger.ExternalRechargeOrder { + order := ledger.ExternalRechargeOrder{ + OrderID: "h5r_" + stableHash(command.AppCode+"|"+command.CommandID), + AppCode: command.AppCode, + CommandID: strings.TrimSpace(command.CommandID), + TargetUserID: command.TargetUserID, + TargetRegionID: command.TargetRegionID, + TargetCountryCode: strings.ToUpper(strings.TrimSpace(command.TargetCountryCode)), + AudienceType: ledger.NormalizeRechargeAudienceType(command.AudienceType), + ProductID: product.ProductID, + ProductCode: product.ProductCode, + ProductName: product.ProductName, + CoinAmount: product.CoinAmount, + USDMinorAmount: googleAmountMicroToUSDMinor(product.AmountMicro), + ProviderCode: ledger.NormalizePaymentProvider(command.ProviderCode), + ProviderAmountMinor: providerAmountMinor, + ReceiveAddress: strings.TrimSpace(receiveAddress), + Status: ledger.ExternalRechargeStatusPending, + CreatedAtMS: nowMS, + UpdatedAtMS: nowMS, + } + if method != nil { + order.PaymentMethodID = method.MethodID + order.CountryCode = method.CountryCode + order.CurrencyCode = method.CurrencyCode + order.PayWay = method.PayWay + order.PayType = method.PayType + } + if order.ProviderCode == ledger.PaymentProviderUSDTTRC20 { + order.CountryCode = "GLOBAL" + order.CurrencyCode = ledger.PaidCurrencyUSDT + } + return order +} + +func (r *Repository) ensureExternalTxHashUnused(ctx context.Context, tx *sql.Tx, appCode string, providerCode string, txHash string, orderID string) error { + var existing string + err := tx.QueryRowContext(ctx, ` + SELECT order_id + FROM external_recharge_orders + WHERE app_code = ? AND provider_code = ? AND tx_hash = ? AND order_id <> ? AND status = ? + LIMIT 1`, + appCode, providerCode, txHash, orderID, ledger.ExternalRechargeStatusCredited, + ).Scan(&existing) + if errors.Is(err, sql.ErrNoRows) { + return nil + } + if err != nil { + return err + } + return xerr.New(xerr.Conflict, "tx_hash already credited") +} + +func providerRechargeBizType(providerCode string) string { + switch ledger.NormalizePaymentProvider(providerCode) { + case ledger.PaymentProviderMifaPay: + return bizTypeMifaPayRecharge + case ledger.PaymentProviderUSDTTRC20: + return bizTypeUSDTTRC20Recharge + default: + return strings.ToLower(strings.TrimSpace(providerCode)) + } +} + +func externalRechargeRequestHash(order ledger.ExternalRechargeOrder, assetType string) string { + return stableHash(fmt.Sprintf("external_recharge|%s|%s|%d|%d|%d|%s|%s|%s", + order.AppCode, order.OrderID, order.TargetUserID, order.ProductID, order.CoinAmount, + order.ProviderCode, order.ProviderOrderID, assetType, + )) +} + +func nullableJSON(payload string) any { + payload = strings.TrimSpace(payload) + if payload == "" { + return nil + } + return payload +} + +func trimMax(value string, max int) string { + value = strings.TrimSpace(value) + if len([]rune(value)) <= max { + return value + } + return string([]rune(value)[:max]) +} + +func decimalRateIsPositive(rate string) bool { + value, err := strconv.ParseFloat(strings.TrimSpace(rate), 64) + return err == nil && value > 0 +} diff --git a/services/wallet-service/internal/transport/grpc/server.go b/services/wallet-service/internal/transport/grpc/server.go index c5f00c8e..3993b7fa 100644 --- a/services/wallet-service/internal/transport/grpc/server.go +++ b/services/wallet-service/internal/transport/grpc/server.go @@ -106,6 +106,7 @@ func debitGiftResponseFromReceipt(receipt ledger.Receipt) *walletv1.DebitGiftRes GiftName: receipt.GiftName, GiftIconUrl: receipt.GiftIconURL, GiftAnimationUrl: receipt.GiftAnimationURL, + GiftEffectTypes: receipt.GiftEffectTypes, PriceVersion: receipt.PriceVersion, HostPeriodDiamondAdded: receipt.HostPeriodDiamondAdded, HostPeriodCycleKey: receipt.HostPeriodCycleKey, @@ -254,7 +255,7 @@ func (s *Server) GetUserGiftWall(ctx context.Context, req *walletv1.GetUserGiftW // ListRechargeProducts 返回当前用户区域可用的充值渠道和档位。 func (s *Server) ListRechargeProducts(ctx context.Context, req *walletv1.ListRechargeProductsRequest) (*walletv1.ListRechargeProductsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) - products, channels, err := s.svc.ListRechargeProducts(ctx, req.GetUserId(), req.GetRegionId(), req.GetPlatform()) + products, channels, err := s.svc.ListRechargeProducts(ctx, req.GetUserId(), req.GetRegionId(), req.GetPlatform(), req.GetAudienceType()) if err != nil { return nil, xerr.ToGRPCError(err) } @@ -300,13 +301,14 @@ func (s *Server) ConfirmGooglePayment(ctx context.Context, req *walletv1.Confirm func (s *Server) ListAdminRechargeProducts(ctx context.Context, req *walletv1.ListAdminRechargeProductsRequest) (*walletv1.ListAdminRechargeProductsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) products, total, err := s.svc.ListAdminRechargeProducts(ctx, ledger.ListRechargeProductsQuery{ - AppCode: req.GetAppCode(), - Status: req.GetStatus(), - Platform: req.GetPlatform(), - RegionID: req.GetRegionId(), - Keyword: req.GetKeyword(), - Page: req.GetPage(), - PageSize: req.GetPageSize(), + AppCode: req.GetAppCode(), + Status: req.GetStatus(), + Platform: req.GetPlatform(), + RegionID: req.GetRegionId(), + Keyword: req.GetKeyword(), + Page: req.GetPage(), + PageSize: req.GetPageSize(), + AudienceType: req.GetAudienceType(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -331,6 +333,7 @@ func (s *Server) CreateRechargeProduct(ctx context.Context, req *walletv1.Create RegionIDs: req.GetRegionIds(), Enabled: req.GetEnabled(), OperatorUserID: req.GetOperatorUserId(), + AudienceType: req.GetAudienceType(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -352,6 +355,7 @@ func (s *Server) UpdateRechargeProduct(ctx context.Context, req *walletv1.Update RegionIDs: req.GetRegionIds(), Enabled: req.GetEnabled(), OperatorUserID: req.GetOperatorUserId(), + AudienceType: req.GetAudienceType(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -368,6 +372,150 @@ func (s *Server) DeleteRechargeProduct(ctx context.Context, req *walletv1.Delete return &walletv1.DeleteRechargeProductResponse{Deleted: true}, nil } +// ListThirdPartyPaymentChannels 返回三方支付渠道、国家和方式树,后台用它直接渲染二级展开列表。 +func (s *Server) ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + channels, err := s.svc.ListThirdPartyPaymentChannels(ctx, ledger.ListThirdPartyPaymentChannelsQuery{ + AppCode: req.GetAppCode(), + ProviderCode: req.GetProviderCode(), + Status: req.GetStatus(), + IncludeDisabledMethods: req.GetIncludeDisabledMethods(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &walletv1.ListThirdPartyPaymentChannelsResponse{Channels: make([]*walletv1.ThirdPartyPaymentChannel, 0, len(channels))} + for _, channel := range channels { + resp.Channels = append(resp.Channels, thirdPartyPaymentChannelToProto(channel)) + } + return resp, nil +} + +// SetThirdPartyPaymentMethodStatus 开关某个国家下的具体支付方式;渠道本身不被连带关闭。 +func (s *Server) SetThirdPartyPaymentMethodStatus(ctx context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + method, err := s.svc.SetThirdPartyPaymentMethodStatus(ctx, ledger.ThirdPartyPaymentMethodStatusCommand{ + AppCode: req.GetAppCode(), + MethodID: req.GetMethodId(), + Enabled: req.GetEnabled(), + OperatorUserID: req.GetOperatorUserId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.ThirdPartyPaymentMethodResponse{Method: thirdPartyPaymentMethodToProto(method)}, nil +} + +// UpdateThirdPartyPaymentRate 保存 USD 到国家本币汇率;H5 只展示已配置正汇率的国家方式。 +func (s *Server) UpdateThirdPartyPaymentRate(ctx context.Context, req *walletv1.UpdateThirdPartyPaymentRateRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + method, err := s.svc.UpdateThirdPartyPaymentRate(ctx, ledger.ThirdPartyPaymentRateCommand{ + AppCode: req.GetAppCode(), + MethodID: req.GetMethodId(), + USDToCurrencyRate: req.GetUsdToCurrencyRate(), + OperatorUserID: req.GetOperatorUserId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.ThirdPartyPaymentMethodResponse{Method: thirdPartyPaymentMethodToProto(method)}, nil +} + +// ListH5RechargeOptions 返回 H5 当前账号的商品和支付方式,普通用户和币商由 audience_type 分流。 +func (s *Server) ListH5RechargeOptions(ctx context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + options, err := s.svc.ListH5RechargeOptions(ctx, ledger.H5RechargeOptionsQuery{ + AppCode: req.GetAppCode(), + TargetUserID: req.GetTargetUserId(), + TargetRegionID: req.GetTargetRegionId(), + TargetCountryCode: req.GetTargetCountryCode(), + AudienceType: req.GetAudienceType(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &walletv1.H5RechargeOptionsResponse{ + Products: make([]*walletv1.RechargeProduct, 0, len(options.Products)), + PaymentMethods: make([]*walletv1.ThirdPartyPaymentMethod, 0, len(options.PaymentMethods)), + UsdtTrc20Enabled: options.USDTTRC20Enabled, + UsdtTrc20Address: options.USDTTRC20Address, + } + for _, product := range options.Products { + resp.Products = append(resp.Products, rechargeProductToProto(product)) + } + for _, method := range options.PaymentMethods { + resp.PaymentMethods = append(resp.PaymentMethods, thirdPartyPaymentMethodToProto(method)) + } + return resp, nil +} + +// CreateH5RechargeOrder 创建外部充值订单;MiFaPay 只返回 pay_url,USDT 只返回共享收款地址。 +func (s *Server) CreateH5RechargeOrder(ctx context.Context, req *walletv1.CreateH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + order, err := s.svc.CreateH5RechargeOrder(ctx, ledger.CreateExternalRechargeOrderCommand{ + AppCode: req.GetAppCode(), + CommandID: req.GetCommandId(), + TargetUserID: req.GetTargetUserId(), + TargetRegionID: req.GetTargetRegionId(), + TargetCountryCode: req.GetTargetCountryCode(), + AudienceType: req.GetAudienceType(), + ProductID: req.GetProductId(), + ProviderCode: req.GetProviderCode(), + PaymentMethodID: req.GetPaymentMethodId(), + ReturnURL: req.GetReturnUrl(), + NotifyURL: req.GetNotifyUrl(), + ClientIP: req.GetClientIp(), + Language: req.GetLanguage(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.H5RechargeOrderResponse{Order: externalRechargeOrderToProto(order)}, nil +} + +// SubmitH5RechargeTx 接收 USDT tx_hash;链上校验和幂等入账仍在 wallet-service 事务里完成。 +func (s *Server) SubmitH5RechargeTx(ctx context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + order, err := s.svc.SubmitH5RechargeTx(ctx, ledger.SubmitExternalRechargeTxCommand{ + AppCode: req.GetAppCode(), + OrderID: req.GetOrderId(), + TargetUserID: req.GetTargetUserId(), + TxHash: req.GetTxHash(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.H5RechargeOrderResponse{Order: externalRechargeOrderToProto(order)}, nil +} + +// GetH5RechargeOrder 给 H5 轮询订单状态;target_user_id 用于避免无 token 场景越权看单。 +func (s *Server) GetH5RechargeOrder(ctx context.Context, req *walletv1.GetH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + order, err := s.svc.GetH5RechargeOrder(ctx, req.GetAppCode(), req.GetOrderId(), req.GetTargetUserId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.H5RechargeOrderResponse{Order: externalRechargeOrderToProto(order)}, nil +} + +// HandleMifapayNotify 验签并处理 MiFaPay 回调;成功或重复成功由 HTTP 层按 response_text 返回大写 SUCCESS。 +func (s *Server) HandleMifapayNotify(ctx context.Context, req *walletv1.HandleMifapayNotifyRequest) (*walletv1.HandleMifapayNotifyResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + order, accepted, err := s.svc.HandleMifapayNotify(ctx, req.GetAppCode(), ledger.MifaPayNotification{ + MerAccount: req.GetMerAccount(), + Data: req.GetData(), + Sign: req.GetSign(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + responseText := "FAIL" + if accepted { + responseText = "SUCCESS" + } + return &walletv1.HandleMifapayNotifyResponse{Accepted: accepted, ResponseText: responseText, OrderId: order.OrderID}, nil +} + // GetDiamondExchangeConfig 返回钻石兑换配置。 func (s *Server) GetDiamondExchangeConfig(ctx context.Context, req *walletv1.GetDiamondExchangeConfigRequest) (*walletv1.GetDiamondExchangeConfigResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) @@ -392,6 +540,7 @@ func (s *Server) GetDiamondExchangeConfig(ctx context.Context, req *walletv1.Get func rechargeProductToProto(product ledger.RechargeProduct) *walletv1.RechargeProduct { return &walletv1.RechargeProduct{ AppCode: product.AppCode, + AudienceType: product.AudienceType, ProductId: product.ProductID, ProductCode: product.ProductCode, ProductName: product.ProductName, @@ -415,6 +564,74 @@ func rechargeProductToProto(product ledger.RechargeProduct) *walletv1.RechargePr } } +func thirdPartyPaymentChannelToProto(channel ledger.ThirdPartyPaymentChannel) *walletv1.ThirdPartyPaymentChannel { + resp := &walletv1.ThirdPartyPaymentChannel{ + AppCode: channel.AppCode, + ProviderCode: channel.ProviderCode, + ProviderName: channel.ProviderName, + Status: channel.Status, + SortOrder: channel.SortOrder, + Methods: make([]*walletv1.ThirdPartyPaymentMethod, 0, len(channel.Methods)), + } + for _, method := range channel.Methods { + resp.Methods = append(resp.Methods, thirdPartyPaymentMethodToProto(method)) + } + return resp +} + +func thirdPartyPaymentMethodToProto(method ledger.ThirdPartyPaymentMethod) *walletv1.ThirdPartyPaymentMethod { + return &walletv1.ThirdPartyPaymentMethod{ + MethodId: method.MethodID, + AppCode: method.AppCode, + ProviderCode: method.ProviderCode, + ProviderName: method.ProviderName, + CountryCode: method.CountryCode, + CountryName: method.CountryName, + CurrencyCode: method.CurrencyCode, + PayWay: method.PayWay, + PayType: method.PayType, + MethodName: method.MethodName, + LogoUrl: method.LogoURL, + Status: method.Status, + UsdToCurrencyRate: method.USDToCurrencyRate, + SortOrder: method.SortOrder, + CreatedAtMs: method.CreatedAtMS, + UpdatedAtMs: method.UpdatedAtMS, + } +} + +func externalRechargeOrderToProto(order ledger.ExternalRechargeOrder) *walletv1.ExternalRechargeOrder { + return &walletv1.ExternalRechargeOrder{ + OrderId: order.OrderID, + AppCode: order.AppCode, + TargetUserId: order.TargetUserID, + TargetRegionId: order.TargetRegionID, + TargetCountryCode: order.TargetCountryCode, + AudienceType: order.AudienceType, + ProductId: order.ProductID, + ProductName: order.ProductName, + CoinAmount: order.CoinAmount, + UsdMinorAmount: order.USDMinorAmount, + ProviderCode: order.ProviderCode, + PaymentMethodId: order.PaymentMethodID, + CountryCode: order.CountryCode, + CurrencyCode: order.CurrencyCode, + ProviderAmountMinor: order.ProviderAmountMinor, + PayWay: order.PayWay, + PayType: order.PayType, + PayUrl: order.PayURL, + ProviderOrderId: order.ProviderOrderID, + TxHash: order.TxHash, + ReceiveAddress: order.ReceiveAddress, + Status: order.Status, + FailureReason: order.FailureReason, + TransactionId: order.TransactionID, + CreatedAtMs: order.CreatedAtMS, + UpdatedAtMs: order.UpdatedAtMS, + IdempotentReplay: order.IdempotentReplay, + } +} + // ListWalletTransactions 返回当前用户钱包流水。 func (s *Server) ListWalletTransactions(ctx context.Context, req *walletv1.ListWalletTransactionsRequest) (*walletv1.ListWalletTransactionsResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) From ecf03ae362f341ca9f099aac01677a6cc6e8a136 Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 01:02:20 +0800 Subject: [PATCH 16/28] =?UTF-8?q?=E6=B8=B8=E6=88=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 1 - scripts/apply-local-mysql-initdb.sh | 1 - .../mysql/038_split_bd_leader_profiles.sql | 9 +- .../043_remove_host_org_region_snapshots.sql | 104 ++++ server/admin/docs/靓号管理服务端技术文档.md | 50 +- .../admin/internal/modules/hostorg/reader.go | 81 ++- .../internal/modules/hostorg/service_test.go | 31 +- .../internal/modules/payment/handler_test.go | 227 ++++++++ .../modules/teamsalarysettlement/service.go | 32 +- .../service_integration_test.go | 28 +- .../internal/transport/http/response_test.go | 277 ++++++++++ .../transport/http/useridinput/resolve.go | 56 ++ .../http/useridinput/resolve_test.go | 74 +++ .../http/walletapi/h5_recharge_handler.go | 15 +- .../deploy/mysql/initdb/001_user_service.sql | 503 +----------------- .../user-service/internal/domain/user/user.go | 10 + .../internal/domain/user/user_test.go | 18 + .../internal/service/auth/password.go | 4 +- .../internal/service/auth/service_test.go | 47 ++ .../internal/service/host/service_test.go | 42 +- .../internal/service/user/display_id.go | 4 +- .../internal/storage/mysql/auth/password.go | 59 +- .../internal/storage/mysql/host/admin.go | 4 +- .../storage/mysql/host/applications.go | 14 +- .../internal/storage/mysql/host/common.go | 84 ++- .../storage/mysql/host/invitations.go | 33 +- .../internal/storage/mysql/host/queries.go | 4 +- .../internal/storage/mysql/host/writes.go | 48 +- .../storage/mysql/identity/identity.go | 117 +++- .../internal/testutil/mysqltest/mysqltest.go | 28 +- .../internal/transport/grpc/server_test.go | 49 +- .../wallet-service/configs/config.docker.yaml | 9 +- .../configs/config.tencent.example.yaml | 9 +- services/wallet-service/configs/config.yaml | 11 +- .../wallet-service/internal/config/config.go | 1 + .../internal/config/config_test.go | 42 +- .../internal/service/wallet/service_test.go | 316 +++++++++++ .../mysql/recharge_product_repository.go | 6 +- .../internal/storage/mysql/repository.go | 3 + .../mysql/third_party_payment_repository.go | 61 ++- .../internal/testutil/mysqltest/mysqltest.go | 41 ++ 41 files changed, 1825 insertions(+), 728 deletions(-) create mode 100644 scripts/mysql/043_remove_host_org_region_snapshots.sql create mode 100644 server/admin/internal/modules/payment/handler_test.go create mode 100644 services/gateway-service/internal/transport/http/useridinput/resolve.go create mode 100644 services/gateway-service/internal/transport/http/useridinput/resolve_test.go diff --git a/docker-compose.yml b/docker-compose.yml index c170cf88..9c1e27c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -287,7 +287,6 @@ services: - ${DEPLOY_PLATFORM_ROOT:-../deploy-platform}/deploy/mysql/initdb/005_utf8mb4_chinese_support.sql:/docker-entrypoint-initdb.d/005_utf8mb4_chinese_support.sql:ro - ${DEPLOY_PLATFORM_ROOT:-../deploy-platform}/deploy/mysql/initdb/006_admin_database.sql:/docker-entrypoint-initdb.d/006_admin_database.sql:ro - ${DEPLOY_PLATFORM_ROOT:-../deploy-platform}/deploy/mysql/initdb/007_resource_group_wallet_asset_items.sql:/docker-entrypoint-initdb.d/007_resource_group_wallet_asset_items.sql:ro - - ${DEPLOY_PLATFORM_ROOT:-../deploy-platform}/deploy/mysql/initdb/008_remove_taiwan_country.sql:/docker-entrypoint-initdb.d/008_remove_taiwan_country.sql:ro - ./services/cron-service/deploy/mysql/initdb/001_cron_service.sql:/docker-entrypoint-initdb.d/009_cron_service.sql:ro - ./services/game-service/deploy/mysql/initdb/001_game_service.sql:/docker-entrypoint-initdb.d/010_game_service.sql:ro - ./services/notice-service/deploy/mysql/initdb/001_notice_service.sql:/docker-entrypoint-initdb.d/011_notice_service.sql:ro diff --git a/scripts/apply-local-mysql-initdb.sh b/scripts/apply-local-mysql-initdb.sh index af9deb96..c5b35cdb 100755 --- a/scripts/apply-local-mysql-initdb.sh +++ b/scripts/apply-local-mysql-initdb.sh @@ -19,7 +19,6 @@ SQL_FILES=( "${DEPLOY_PLATFORM_ROOT}/deploy/mysql/initdb/005_utf8mb4_chinese_support.sql" "${DEPLOY_PLATFORM_ROOT}/deploy/mysql/initdb/006_admin_database.sql" "${DEPLOY_PLATFORM_ROOT}/deploy/mysql/initdb/007_resource_group_wallet_asset_items.sql" - "${DEPLOY_PLATFORM_ROOT}/deploy/mysql/initdb/008_remove_taiwan_country.sql" "services/cron-service/deploy/mysql/initdb/001_cron_service.sql" "services/game-service/deploy/mysql/initdb/001_game_service.sql" "services/notice-service/deploy/mysql/initdb/001_notice_service.sql" diff --git a/scripts/mysql/038_split_bd_leader_profiles.sql b/scripts/mysql/038_split_bd_leader_profiles.sql index f6d3771f..f72caa91 100644 --- a/scripts/mysql/038_split_bd_leader_profiles.sql +++ b/scripts/mysql/038_split_bd_leader_profiles.sql @@ -1,22 +1,19 @@ CREATE TABLE IF NOT EXISTS bd_leader_profiles ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', - region_id BIGINT NOT NULL COMMENT '区域 ID', status VARCHAR(32) NOT NULL COMMENT '业务状态', created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', - updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', - KEY idx_bd_leader_profiles_region_status (app_code, region_id, status) + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BD Leader资料表'; INSERT INTO bd_leader_profiles ( - app_code, user_id, region_id, status, created_by_user_id, created_at_ms, updated_at_ms + app_code, user_id, status, created_by_user_id, created_at_ms, updated_at_ms ) -SELECT app_code, user_id, region_id, status, created_by_user_id, created_at_ms, updated_at_ms +SELECT app_code, user_id, status, created_by_user_id, created_at_ms, updated_at_ms FROM bd_profiles WHERE role = 'bd_leader' ON DUPLICATE KEY UPDATE - region_id = VALUES(region_id), status = VALUES(status), created_by_user_id = VALUES(created_by_user_id), created_at_ms = VALUES(created_at_ms), diff --git a/scripts/mysql/043_remove_host_org_region_snapshots.sql b/scripts/mysql/043_remove_host_org_region_snapshots.sql new file mode 100644 index 00000000..9e02991f --- /dev/null +++ b/scripts/mysql/043_remove_host_org_region_snapshots.sql @@ -0,0 +1,104 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_user; + +-- BD/Agency/Host 的区域统一由 users 当前区域或用户国家映射区域派生。 +-- 这些身份表里的 region_id 是旧快照,先删除依赖索引,再删除列,避免后续继续写入或误读。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'host_profiles' AND INDEX_NAME = 'idx_host_profiles_region_status') > 0, + 'ALTER TABLE host_profiles DROP INDEX idx_host_profiles_region_status', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'bd_profiles' AND INDEX_NAME = 'idx_bd_profiles_region_role') > 0, + 'ALTER TABLE bd_profiles DROP INDEX idx_bd_profiles_region_role', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'bd_leader_profiles' AND INDEX_NAME = 'idx_bd_leader_profiles_region_status') > 0, + 'ALTER TABLE bd_leader_profiles DROP INDEX idx_bd_leader_profiles_region_status', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'agencies' AND INDEX_NAME = 'idx_agencies_region_status') > 0, + 'ALTER TABLE agencies DROP INDEX idx_agencies_region_status', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'host_profiles' AND COLUMN_NAME = 'region_id') > 0, + 'ALTER TABLE host_profiles DROP COLUMN region_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'bd_profiles' AND COLUMN_NAME = 'region_id') > 0, + 'ALTER TABLE bd_profiles DROP COLUMN region_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'bd_leader_profiles' AND COLUMN_NAME = 'region_id') > 0, + 'ALTER TABLE bd_leader_profiles DROP COLUMN region_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'agencies' AND COLUMN_NAME = 'region_id') > 0, + 'ALTER TABLE agencies DROP COLUMN region_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'agency_memberships' AND COLUMN_NAME = 'region_id') > 0, + 'ALTER TABLE agency_memberships DROP COLUMN region_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'agency_applications' AND COLUMN_NAME = 'region_id') > 0, + 'ALTER TABLE agency_applications DROP COLUMN region_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'role_invitations' AND COLUMN_NAME = 'region_id') > 0, + 'ALTER TABLE role_invitations DROP COLUMN region_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/server/admin/docs/靓号管理服务端技术文档.md b/server/admin/docs/靓号管理服务端技术文档.md index c532e9b8..eba78d11 100644 --- a/server/admin/docs/靓号管理服务端技术文档.md +++ b/server/admin/docs/靓号管理服务端技术文档.md @@ -231,31 +231,37 @@ CREATE TABLE pretty_display_id_generation_batches ( ## 靓号池生成规则 -靓号池批量生成首版支持 `aabbcc`: - -- 必须是 6 位数字。 -- 第 1、2 位相同。 -- 第 3、4 位相同。 -- 第 5、6 位相同。 -- 第 1 位不能是 `0`。 -- 生成后仍然复用数字短号格式校验,避免生成出 App 用户难以输入的池号码。 - -示例: - -```text -112233 -223344 -667788 -``` - -后续可以扩展: +靓号池批量生成支持数字规则、日期规则、寓意号和自定义规则。数字规则允许前导 0,号码以字符串保存。 | rule_type | 说明 | | --- | --- | +| `aa`、`aaa`、`aaaa`、`aaaaa`、`aaaaaa`、`aaaaaaa` | 2A 到 7A 连号 | +| `aabb` | 两组对子 | | `aabbcc` | 三组重复数字 | -| `aaaaaa` | 6 位相同数字 | -| `abcabc` | 前三位重复一次 | -| `manual` | 后台手动导入号码列表 | +| `aabbccdd` | 四组对子 | +| `abab`、`ababab` | 交替号 | +| `abba`、`abcba`、`abccba` | 回文号 | +| `aaab`、`aaaab`、`aaaaab` | 拖一号 | +| `aaabbb`、`aaaabbbb` | 双组号 | +| `abc`、`abcd`、`abcde`、`abcdef` | 顺子号 | +| `cba`、`dcba`、`edcba`、`fedcba` | 倒顺号 | +| `abcabc` | 顺子重复号 | +| `date_yyyymmdd`、`date_yymmdd` | 日期号 | +| `love` | 520、521、1314 等寓意号 | +| `custom_values` | 使用 `rule_config_json.values` 固定候选 | +| `custom_pattern` | 使用 `rule_config_json.pattern` 自定义模式 | + +`rule_config_json` 可选字段: + +| 字段 | 说明 | +| --- | --- | +| `digits` | 限制可用数字,例如 `["6","8","9"]` | +| `exclude_digits` | 排除数字,例如 `["4"]` | +| `prefix`、`suffix` | 给生成候选加前缀或后缀 | +| `values` | `custom_values` 的候选数组 | +| `pattern` | `custom_pattern` 的模式,例如 `AAAABBBB` | +| `year_from`、`year_to` | 日期号年份范围 | +| `start_date`、`end_date` | 日期号精确范围,格式 `yyyy-mm-dd` | 后台直接发放靓号不受本节数字生成规则限制,按“现有短号表扩展”里的后台发放字符规则校验。 @@ -618,7 +624,7 @@ message UserIdentity { - 用户等级低于池区间时,申请返回 `PRETTY_ID_LEVEL_NOT_MATCH`。 - 用户等级在池区间内时,申请成功并更新: - `pretty_display_ids.status='assigned'` - - `pretty_display_user_id_leases.source='level_pool'` + - `pretty_display_user_id_leases.source='pool'` - `user_display_user_ids.display_user_id_kind='pretty'` - `users.current_display_user_id_kind='pretty'` - 用户已有靓号时,再申请另一个靓号会覆盖旧靓号,旧靓号释放;旧池靓号回到 `available`,旧后台发放靓号进入 `released`。 diff --git a/server/admin/internal/modules/hostorg/reader.go b/server/admin/internal/modules/hostorg/reader.go index ee119d99..16a29c80 100644 --- a/server/admin/internal/modules/hostorg/reader.go +++ b/server/admin/internal/modules/hostorg/reader.go @@ -53,6 +53,33 @@ type CoinSellerSalaryRateTier struct { UpdatedAtMs int64 `json:"updatedAtMs"` } +func userCountryRegionJoin(userAlias, countryRegionAlias, regionAlias string) string { + // 组织列表里的区域只用于后台展示、搜索和筛选,必须以用户当前国家归属的 active 区域为准。 + // 身份表不再保存区域;这里始终按用户国家映射 active 区域,避免用户国家调整后列表继续显示旧值。 + return fmt.Sprintf(` + LEFT JOIN region_countries %s ON %s.app_code = %s.app_code + AND %s.country_code = %s.country + AND %s.status = 'active' + LEFT JOIN regions %s ON %s.app_code = %s.app_code + AND %s.region_id = %s.region_id + AND %s.status = 'active'`, + countryRegionAlias, countryRegionAlias, userAlias, + countryRegionAlias, userAlias, + countryRegionAlias, + regionAlias, regionAlias, countryRegionAlias, + regionAlias, countryRegionAlias, + regionAlias, + ) +} + +func userCountryRegionIDSQL(regionAlias string) string { + return fmt.Sprintf("COALESCE(%s.region_id, 0)", regionAlias) +} + +func userCountryRegionNameSQL(regionAlias string) string { + return fmt.Sprintf("COALESCE(%s.name, '')", regionAlias) +} + // ListBDProfiles 只读 user-service 的 BD/BD Leader 事实表,后台列表按角色选择物理表,避免把两种身份重新混在一起。 func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role string) ([]*userclient.BDProfile, int64, error) { if r == nil || r.db == nil { @@ -65,9 +92,9 @@ func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role strin whereSQL := ` FROM bd_profiles bp LEFT JOIN users u ON u.app_code = bp.app_code AND u.user_id = bp.user_id + ` + userCountryRegionJoin("u", "user_country_region", "user_region") + ` LEFT JOIN users parent_leader ON parent_leader.app_code = bp.app_code AND parent_leader.user_id = bp.parent_leader_user_id LEFT JOIN users creator ON creator.app_code = bp.app_code AND creator.user_id = bp.created_by_user_id - LEFT JOIN regions r ON r.app_code = bp.app_code AND r.region_id = bp.region_id WHERE bp.app_code = ? AND bp.role = ?` args := []any{appCode, role} if query.Status != "" { @@ -75,7 +102,7 @@ func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role strin args = append(args, query.Status) } if query.RegionID > 0 { - whereSQL += " AND bp.region_id = ?" + whereSQL += " AND user_region.region_id = ?" args = append(args, query.RegionID) } if query.ParentLeaderUserID > 0 { @@ -84,7 +111,7 @@ func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role strin } if keyword := strings.TrimSpace(query.Keyword); keyword != "" { like := "%" + keyword + "%" - whereSQL += " AND (CAST(bp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR r.name LIKE ?)" + whereSQL += " AND (CAST(bp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR user_region.name LIKE ?)" args = append(args, like, like, like, like) } @@ -93,10 +120,10 @@ func (r *Reader) ListBDProfiles(ctx context.Context, query listQuery, role strin return nil, 0, err } rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` - SELECT bp.user_id, bp.role, bp.region_id, COALESCE(bp.parent_leader_user_id, 0), + SELECT bp.user_id, bp.role, `+userCountryRegionIDSQL("user_region")+`, COALESCE(bp.parent_leader_user_id, 0), bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms, COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), - COALESCE(u.avatar, ''), COALESCE(r.name, ''), + COALESCE(u.avatar, ''), `+userCountryRegionNameSQL("user_region")+`, COALESCE(parent_leader.current_display_user_id, ''), COALESCE(parent_leader.username, ''), COALESCE(parent_leader.avatar, ''), COALESCE(creator.current_display_user_id, ''), COALESCE(creator.username, ''), @@ -168,8 +195,8 @@ func (r *Reader) listBDLeaderProfiles(ctx context.Context, query listQuery) ([]* whereSQL := ` FROM bd_leader_profiles bp LEFT JOIN users u ON u.app_code = bp.app_code AND u.user_id = bp.user_id + ` + userCountryRegionJoin("u", "user_country_region", "user_region") + ` LEFT JOIN users creator ON creator.app_code = bp.app_code AND creator.user_id = bp.created_by_user_id - LEFT JOIN regions r ON r.app_code = bp.app_code AND r.region_id = bp.region_id WHERE bp.app_code = ?` args := []any{appCode} if query.Status != "" { @@ -177,12 +204,12 @@ func (r *Reader) listBDLeaderProfiles(ctx context.Context, query listQuery) ([]* args = append(args, query.Status) } if query.RegionID > 0 { - whereSQL += " AND bp.region_id = ?" + whereSQL += " AND user_region.region_id = ?" args = append(args, query.RegionID) } if keyword := strings.TrimSpace(query.Keyword); keyword != "" { like := "%" + keyword + "%" - whereSQL += " AND (CAST(bp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR r.name LIKE ?)" + whereSQL += " AND (CAST(bp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR user_region.name LIKE ?)" args = append(args, like, like, like, like) } @@ -191,10 +218,10 @@ func (r *Reader) listBDLeaderProfiles(ctx context.Context, query listQuery) ([]* return nil, 0, err } rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` - SELECT bp.user_id, 'bd_leader', bp.region_id, 0, + SELECT bp.user_id, 'bd_leader', `+userCountryRegionIDSQL("user_region")+`, 0, bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms, COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), - COALESCE(u.avatar, ''), COALESCE(r.name, ''), + COALESCE(u.avatar, ''), `+userCountryRegionNameSQL("user_region")+`, '', '', '', COALESCE(creator.current_display_user_id, ''), COALESCE(creator.username, ''), COALESCE(creator.avatar, ''), @@ -261,10 +288,10 @@ func (r *Reader) GetBDLeader(ctx context.Context, userID int64) (*userclient.BDP appCode := appctx.FromContext(ctx) item := &userclient.BDProfile{} err := r.db.QueryRowContext(ctx, ` - SELECT bp.user_id, 'bd_leader', bp.region_id, 0, + SELECT bp.user_id, 'bd_leader', `+userCountryRegionIDSQL("user_region")+`, 0, bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms, COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), - COALESCE(u.avatar, ''), COALESCE(r.name, ''), + COALESCE(u.avatar, ''), `+userCountryRegionNameSQL("user_region")+`, COALESCE(creator.current_display_user_id, ''), COALESCE(creator.username, ''), COALESCE(creator.avatar, ''), ( @@ -276,8 +303,8 @@ func (r *Reader) GetBDLeader(ctx context.Context, userID int64) (*userclient.BDP ) FROM bd_leader_profiles bp LEFT JOIN users u ON u.app_code = bp.app_code AND u.user_id = bp.user_id + `+userCountryRegionJoin("u", "user_country_region", "user_region")+` LEFT JOIN users creator ON creator.app_code = bp.app_code AND creator.user_id = bp.created_by_user_id - LEFT JOIN regions r ON r.app_code = bp.app_code AND r.region_id = bp.region_id WHERE bp.app_code = ? AND bp.user_id = ? `, appCode, userID).Scan( &item.UserID, @@ -448,12 +475,14 @@ func (r *Reader) DeleteBDLeader(ctx context.Context, userID int64) (*userclient. } appCode := appctx.FromContext(ctx) item := &userclient.BDProfile{} - err := r.db.QueryRowContext(ctx, ` - SELECT bp.user_id, 'bd_leader', bp.region_id, 0, + err := r.db.QueryRowContext(ctx, fmt.Sprintf(` + SELECT bp.user_id, 'bd_leader', %s, 0, bp.status, bp.created_by_user_id, bp.created_at_ms, bp.updated_at_ms FROM bd_leader_profiles bp + LEFT JOIN users u ON u.app_code = bp.app_code AND u.user_id = bp.user_id + %s WHERE bp.app_code = ? AND bp.user_id = ? - `, appCode, userID).Scan( + `, userCountryRegionIDSQL("user_region"), userCountryRegionJoin("u", "user_country_region", "user_region")), appCode, userID).Scan( &item.UserID, &item.Role, &item.RegionID, @@ -501,8 +530,8 @@ func (r *Reader) ListAgencies(ctx context.Context, query listQuery) ([]*userclie whereSQL := ` FROM agencies a LEFT JOIN users owner ON owner.app_code = a.app_code AND owner.user_id = a.owner_user_id + ` + userCountryRegionJoin("owner", "owner_country_region", "owner_region") + ` LEFT JOIN users parent_bd ON parent_bd.app_code = a.app_code AND parent_bd.user_id = a.parent_bd_user_id - LEFT JOIN regions r ON r.app_code = a.app_code AND r.region_id = a.region_id WHERE a.app_code = ?` args := []any{appCode} if query.Status != "" { @@ -513,7 +542,7 @@ func (r *Reader) ListAgencies(ctx context.Context, query listQuery) ([]*userclie args = append(args, "deleted") } if query.RegionID > 0 { - whereSQL += " AND a.region_id = ?" + whereSQL += " AND owner_region.region_id = ?" args = append(args, query.RegionID) } if query.ParentBDUserID > 0 { @@ -522,7 +551,7 @@ func (r *Reader) ListAgencies(ctx context.Context, query listQuery) ([]*userclie } if keyword := strings.TrimSpace(query.Keyword); keyword != "" { like := "%" + keyword + "%" - whereSQL += " AND (a.name LIKE ? OR CAST(a.agency_id AS CHAR) LIKE ? OR CAST(a.owner_user_id AS CHAR) LIKE ? OR owner.current_display_user_id LIKE ? OR owner.username LIKE ? OR CAST(a.parent_bd_user_id AS CHAR) LIKE ? OR parent_bd.current_display_user_id LIKE ? OR parent_bd.username LIKE ? OR r.name LIKE ?)" + whereSQL += " AND (a.name LIKE ? OR CAST(a.agency_id AS CHAR) LIKE ? OR CAST(a.owner_user_id AS CHAR) LIKE ? OR owner.current_display_user_id LIKE ? OR owner.username LIKE ? OR CAST(a.parent_bd_user_id AS CHAR) LIKE ? OR parent_bd.current_display_user_id LIKE ? OR parent_bd.username LIKE ? OR owner_region.name LIKE ?)" args = append(args, like, like, like, like, like, like, like, like, like) } @@ -531,13 +560,13 @@ func (r *Reader) ListAgencies(ctx context.Context, query listQuery) ([]*userclie return nil, 0, err } rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` - SELECT a.agency_id, a.owner_user_id, a.region_id, a.parent_bd_user_id, + SELECT a.agency_id, a.owner_user_id, `+userCountryRegionIDSQL("owner_region")+`, a.parent_bd_user_id, a.name, a.status, a.join_enabled, a.max_hosts, a.created_by_user_id, a.created_at_ms, a.updated_at_ms, COALESCE(owner.current_display_user_id, ''), COALESCE(owner.username, ''), COALESCE(owner.avatar, ''), COALESCE(parent_bd.current_display_user_id, ''), COALESCE(parent_bd.username, ''), - COALESCE(parent_bd.avatar, ''), COALESCE(r.name, '') + COALESCE(parent_bd.avatar, ''), `+userCountryRegionNameSQL("owner_region")+` %s ORDER BY a.created_at_ms DESC, a.agency_id DESC LIMIT ? OFFSET ? @@ -586,9 +615,9 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user whereSQL := ` FROM host_profiles hp LEFT JOIN users u ON u.app_code = hp.app_code AND u.user_id = hp.user_id + ` + userCountryRegionJoin("u", "user_country_region", "user_region") + ` LEFT JOIN agencies a ON a.app_code = hp.app_code AND a.agency_id = hp.current_agency_id LEFT JOIN users agency_owner ON agency_owner.app_code = a.app_code AND agency_owner.user_id = a.owner_user_id - LEFT JOIN regions r ON r.app_code = hp.app_code AND r.region_id = hp.region_id WHERE hp.app_code = ?` args := []any{appCode} if query.Status != "" { @@ -596,7 +625,7 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user args = append(args, query.Status) } if query.RegionID > 0 { - whereSQL += " AND hp.region_id = ?" + whereSQL += " AND user_region.region_id = ?" args = append(args, query.RegionID) } if query.AgencyID > 0 { @@ -605,7 +634,7 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user } if keyword := strings.TrimSpace(query.Keyword); keyword != "" { like := "%" + keyword + "%" - whereSQL += " AND (CAST(hp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR a.name LIKE ? OR agency_owner.current_display_user_id LIKE ? OR agency_owner.username LIKE ? OR r.name LIKE ?)" + whereSQL += " AND (CAST(hp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR a.name LIKE ? OR agency_owner.current_display_user_id LIKE ? OR agency_owner.username LIKE ? OR user_region.name LIKE ?)" args = append(args, like, like, like, like, like, like, like) } @@ -620,11 +649,11 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user queryArgs = args } rows, err := r.db.QueryContext(ctx, fmt.Sprintf(` - SELECT hp.user_id, hp.status, hp.region_id, COALESCE(hp.current_agency_id, 0), + SELECT hp.user_id, hp.status, `+userCountryRegionIDSQL("user_region")+`, COALESCE(hp.current_agency_id, 0), COALESCE(hp.current_membership_id, 0), hp.source, hp.first_became_host_at_ms, hp.created_at_ms, hp.updated_at_ms, COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''), - COALESCE(u.avatar, ''), COALESCE(r.name, ''), COALESCE(a.name, ''), + COALESCE(u.avatar, ''), `+userCountryRegionNameSQL("user_region")+`, COALESCE(a.name, ''), COALESCE(a.owner_user_id, 0), COALESCE(agency_owner.current_display_user_id, ''), COALESCE(agency_owner.username, ''), COALESCE(agency_owner.avatar, '') %s diff --git a/server/admin/internal/modules/hostorg/service_test.go b/server/admin/internal/modules/hostorg/service_test.go index a5935319..a4e0acae 100644 --- a/server/admin/internal/modules/hostorg/service_test.go +++ b/server/admin/internal/modules/hostorg/service_test.go @@ -1,6 +1,9 @@ package hostorg -import "testing" +import ( + "strings" + "testing" +) // TestNormalizeListQueryAllowsCoinSellerComputedSorts 锁定币商余额和累充 USDT 排序字段会被后台列表查询接受。 func TestNormalizeListQueryAllowsCoinSellerComputedSorts(t *testing.T) { @@ -25,6 +28,32 @@ func TestBDRoleFromStatusPathUsesLeaderTableWithAPIPrefix(t *testing.T) { } } +// TestUserCountryRegionSQLUsesCountryMapping 锁定组织列表区域展示口径:从用户国家映射 active 区域,不从身份表区域快照取值。 +func TestUserCountryRegionSQLUsesCountryMapping(t *testing.T) { + joinSQL := userCountryRegionJoin("owner", "owner_country_region", "owner_region") + for _, want := range []string{ + "LEFT JOIN region_countries owner_country_region", + "owner_country_region.country_code = owner.country", + "owner_country_region.status = 'active'", + "LEFT JOIN regions owner_region", + "owner_region.region_id = owner_country_region.region_id", + "owner_region.status = 'active'", + } { + if !strings.Contains(joinSQL, want) { + t.Fatalf("country region join missing %q in %s", want, joinSQL) + } + } + if strings.Contains(joinSQL, "owner.region_id") { + t.Fatalf("country region join must not read identity or user region snapshots: %s", joinSQL) + } + if got := userCountryRegionIDSQL("owner_region"); got != "COALESCE(owner_region.region_id, 0)" { + t.Fatalf("region id projection mismatch: %s", got) + } + if got := userCountryRegionNameSQL("owner_region"); got != "COALESCE(owner_region.name, '')" { + t.Fatalf("region name projection mismatch: %s", got) + } +} + // TestSortCoinSellerListItemsComputedFields 验证 wallet 聚合字段排序只改变展示顺序,不改动币商身份数据。 func TestSortCoinSellerListItemsComputedFields(t *testing.T) { items := []*CoinSellerListItem{ diff --git a/server/admin/internal/modules/payment/handler_test.go b/server/admin/internal/modules/payment/handler_test.go new file mode 100644 index 00000000..8b438926 --- /dev/null +++ b/server/admin/internal/modules/payment/handler_test.go @@ -0,0 +1,227 @@ +package payment + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/integration/walletclient" + "hyapp-admin-server/internal/middleware" + walletv1 "hyapp.local/api/proto/wallet/v1" + + "github.com/gin-gonic/gin" +) + +func TestListThirdPartyPaymentChannelsForwardsIncludeDisabledMethods(t *testing.T) { + wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{ + Channels: []*walletv1.ThirdPartyPaymentChannel{{ + AppCode: "lalu", + ProviderCode: "mifapay", + ProviderName: "MiFaPay", + Status: "active", + SortOrder: 10, + Methods: []*walletv1.ThirdPartyPaymentMethod{{ + MethodId: 810, + AppCode: "lalu", + ProviderCode: "mifapay", + ProviderName: "MiFaPay", + CountryCode: "SA", + CountryName: "Saudi Arabia", + CurrencyCode: "SAR", + PayWay: "Card", + PayType: "MADA", + MethodName: "MADA", + Status: "active", + UsdToCurrencyRate: "1.00000000", + }}, + }}, + }} + router := newPaymentHandlerTestRouter(New(wallet, nil, nil)) + request := httptest.NewRequest(http.MethodGet, "/admin/payment/third-party-channels?provider_code=mifapay&status=active", 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 wallet.lastThirdPartyChannels == nil || + wallet.lastThirdPartyChannels.GetAppCode() != "lalu" || + wallet.lastThirdPartyChannels.GetProviderCode() != "mifapay" || + wallet.lastThirdPartyChannels.GetStatus() != "active" || + !wallet.lastThirdPartyChannels.GetIncludeDisabledMethods() { + t.Fatalf("third party channels request mismatch: %+v", wallet.lastThirdPartyChannels) + } + var response adminPaymentTestResponse + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + items := response.Data["items"].([]any) + channel := items[0].(map[string]any) + methods := channel["methods"].([]any) + if response.Code != 0 || channel["providerCode"] != "mifapay" || len(methods) != 1 || methods[0].(map[string]any)["payType"] != "MADA" { + t.Fatalf("third party channels response mismatch: %+v", response) + } +} + +func TestSetThirdPartyPaymentMethodStatusForwardsOperator(t *testing.T) { + wallet := &mockPaymentWallet{} + router := newPaymentHandlerTestRouter(New(wallet, nil, nil)) + request := httptest.NewRequest(http.MethodPatch, "/admin/payment/third-party-methods/810/status", bytes.NewBufferString(`{"enabled":false}`)) + request.Header.Set("Content-Type", "application/json") + 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 wallet.lastSetMethodStatus == nil || + wallet.lastSetMethodStatus.GetMethodId() != 810 || + wallet.lastSetMethodStatus.GetEnabled() || + wallet.lastSetMethodStatus.GetOperatorUserId() != 7 { + t.Fatalf("method status request mismatch: %+v", wallet.lastSetMethodStatus) + } +} + +func TestUpdateThirdPartyPaymentRateTrimsRateAndForwardsOperator(t *testing.T) { + wallet := &mockPaymentWallet{} + router := newPaymentHandlerTestRouter(New(wallet, nil, nil)) + request := httptest.NewRequest(http.MethodPatch, "/admin/payment/third-party-rates/810", bytes.NewBufferString(`{"usdToCurrencyRate":" 3.75000000 "}`)) + request.Header.Set("Content-Type", "application/json") + 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 wallet.lastUpdateRate == nil || + wallet.lastUpdateRate.GetMethodId() != 810 || + wallet.lastUpdateRate.GetUsdToCurrencyRate() != "3.75000000" || + wallet.lastUpdateRate.GetOperatorUserId() != 7 { + t.Fatalf("payment rate request mismatch: %+v", wallet.lastUpdateRate) + } +} + +func TestCreateRechargeProductForwardsWebCoinSellerAudience(t *testing.T) { + wallet := &mockPaymentWallet{} + router := newPaymentHandlerTestRouter(New(wallet, nil, nil)) + body := `{"amountUsdt":"10.000000","coinAmount":880000,"productName":"Seller 10 USD","description":"coin seller tier","audienceType":"coin_seller","platform":"web","regionIds":[7100],"enabled":true}` + request := httptest.NewRequest(http.MethodPost, "/admin/payment/recharge-products", bytes.NewBufferString(body)) + request.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusCreated { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if wallet.lastCreateProduct == nil || + wallet.lastCreateProduct.GetAmountMicro() != 10_000_000 || + wallet.lastCreateProduct.GetCoinAmount() != 880000 || + wallet.lastCreateProduct.GetAudienceType() != "coin_seller" || + wallet.lastCreateProduct.GetPlatform() != "web" || + len(wallet.lastCreateProduct.GetRegionIds()) != 1 || + wallet.lastCreateProduct.GetRegionIds()[0] != 7100 || + !wallet.lastCreateProduct.GetEnabled() || + wallet.lastCreateProduct.GetOperatorUserId() != 7 { + t.Fatalf("create recharge product request mismatch: %+v", wallet.lastCreateProduct) + } +} + +func newPaymentHandlerTestRouter(handler *Handler) *gin.Engine { + gin.SetMode(gin.TestMode) + router := gin.New() + router.Use(func(c *gin.Context) { + // 后台 handler 只依赖 app_code、request_id 和操作者;测试在这里模拟鉴权中间件已完成后的上下文。 + c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "lalu")) + c.Set(middleware.ContextRequestID, "payment-handler-test") + c.Set(middleware.ContextUserID, uint(7)) + c.Set(middleware.ContextUsername, "tester") + c.Next() + }) + router.GET("/admin/payment/third-party-channels", handler.ListThirdPartyPaymentChannels) + router.PATCH("/admin/payment/third-party-methods/:method_id/status", handler.SetThirdPartyPaymentMethodStatus) + router.PATCH("/admin/payment/third-party-rates/:method_id", handler.UpdateThirdPartyPaymentRate) + router.POST("/admin/payment/recharge-products", handler.CreateRechargeProduct) + return router +} + +type adminPaymentTestResponse struct { + Code int `json:"code"` + Data map[string]any `json:"data"` +} + +type mockPaymentWallet struct { + walletclient.Client + + lastThirdPartyChannels *walletv1.ListThirdPartyPaymentChannelsRequest + thirdPartyChannelsResp *walletv1.ListThirdPartyPaymentChannelsResponse + lastSetMethodStatus *walletv1.SetThirdPartyPaymentMethodStatusRequest + lastUpdateRate *walletv1.UpdateThirdPartyPaymentRateRequest + lastCreateProduct *walletv1.CreateRechargeProductRequest +} + +func (m *mockPaymentWallet) ListThirdPartyPaymentChannels(_ context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) { + m.lastThirdPartyChannels = req + if m.thirdPartyChannelsResp != nil { + return m.thirdPartyChannelsResp, nil + } + return &walletv1.ListThirdPartyPaymentChannelsResponse{}, nil +} + +func (m *mockPaymentWallet) SetThirdPartyPaymentMethodStatus(_ context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) { + m.lastSetMethodStatus = req + status := "disabled" + if req.GetEnabled() { + status = "active" + } + return &walletv1.ThirdPartyPaymentMethodResponse{Method: &walletv1.ThirdPartyPaymentMethod{ + MethodId: req.GetMethodId(), + AppCode: req.GetAppCode(), + ProviderCode: "mifapay", + ProviderName: "MiFaPay", + CountryCode: "SA", + PayWay: "Card", + PayType: "MADA", + Status: status, + }}, nil +} + +func (m *mockPaymentWallet) UpdateThirdPartyPaymentRate(_ context.Context, req *walletv1.UpdateThirdPartyPaymentRateRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) { + m.lastUpdateRate = req + return &walletv1.ThirdPartyPaymentMethodResponse{Method: &walletv1.ThirdPartyPaymentMethod{ + MethodId: req.GetMethodId(), + AppCode: req.GetAppCode(), + ProviderCode: "mifapay", + ProviderName: "MiFaPay", + CountryCode: "SA", + PayWay: "Card", + PayType: "MADA", + Status: "active", + UsdToCurrencyRate: req.GetUsdToCurrencyRate(), + }}, nil +} + +func (m *mockPaymentWallet) CreateRechargeProduct(_ context.Context, req *walletv1.CreateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) { + m.lastCreateProduct = req + return &walletv1.RechargeProductResponse{Product: &walletv1.RechargeProduct{ + AppCode: req.GetAppCode(), + ProductId: 9001, + ProductCode: "h5_seller_10", + ProductName: req.GetProductName(), + Description: req.GetDescription(), + AudienceType: req.GetAudienceType(), + Platform: req.GetPlatform(), + AmountMicro: req.GetAmountMicro(), + CoinAmount: req.GetCoinAmount(), + RegionIds: append([]int64(nil), req.GetRegionIds()...), + Enabled: req.GetEnabled(), + Status: "active", + }}, nil +} diff --git a/server/admin/internal/modules/teamsalarysettlement/service.go b/server/admin/internal/modules/teamsalarysettlement/service.go index b910a269..adec83f8 100644 --- a/server/admin/internal/modules/teamsalarysettlement/service.go +++ b/server/admin/internal/modules/teamsalarysettlement/service.go @@ -354,7 +354,7 @@ func (s *Service) collectBDCandidates(ctx context.Context, appCode string, trigg if !ok || agency.ParentBDUserID <= 0 || income <= 0 { continue } - // 区域来自 Agency 配置,不从主播记录推导,保证 BD 政策和组织管理后台保持同一口径。 + // 区域来自 Agency 拥有者 users 当前区域,不从主播记录或 Agency 历史快照推导。 if len(regions) > 0 { if _, ok := regions[agency.RegionID]; !ok { continue @@ -399,7 +399,7 @@ func (s *Service) collectAdminCandidates(ctx context.Context, appCode string, tr if !ok { continue } - // Admin 的适用区域以 bd_leader profile 为准,避免一个 BD 跨区域时把收入错配到错误政策。 + // Admin 的适用区域以 BD Leader 用户当前区域为准,不再读取 bd_leader_profiles 的历史 region_id。 if len(regions) > 0 { if _, ok := regions[leader.RegionID]; !ok { continue @@ -614,7 +614,7 @@ type agencyInfo struct { RegionID int64 } -// activeAgenciesByOwner 只使用 active agency,避免历史或停用 Agency 继续给 BD 产生工资。 +// activeAgenciesByOwner 只使用 active agency,并从 owner users 行读取当前区域,避免历史快照继续影响 BD 工资。 func (s *Service) activeAgenciesByOwner(ctx context.Context, appCode string, ownerIDs []int64) (map[int64]agencyInfo, error) { ownerIDs = uniquePositiveUserIDs(ownerIDs) if len(ownerIDs) == 0 { @@ -627,9 +627,11 @@ func (s *Service) activeAgenciesByOwner(ctx context.Context, appCode string, own args = append(args, id) } rows, err := s.userDB.QueryContext(ctx, ` - SELECT owner_user_id, parent_bd_user_id, region_id - FROM agencies - WHERE app_code = ? AND status = 'active' AND owner_user_id IN (`+placeholders+`)`, args...) + SELECT a.owner_user_id, a.parent_bd_user_id, COALESCE(u.region_id, 0) + FROM agencies a + INNER JOIN users u + ON u.app_code = a.app_code AND u.user_id = a.owner_user_id + WHERE a.app_code = ? AND a.status = 'active' AND a.owner_user_id IN (`+placeholders+`)`, args...) if err != nil { return nil, err } @@ -653,7 +655,7 @@ type bdProfile struct { ParentLeaderUserID int64 } -// bdProfilesByUser 按角色取 active profile,普通 BD 和 BD Leader 已拆成两张物理表,调用方必须显式传 role。 +// bdProfilesByUser 按角色取 active profile,RegionID 始终来自用户当前区域,调用方必须显式传 role。 func (s *Service) bdProfilesByUser(ctx context.Context, appCode string, userIDs []int64, role string) (map[int64]bdProfile, error) { userIDs = uniquePositiveUserIDs(userIDs) if len(userIDs) == 0 { @@ -666,14 +668,18 @@ func (s *Service) bdProfilesByUser(ctx context.Context, appCode string, userIDs args = append(args, id) } query := ` - SELECT user_id, role, region_id, COALESCE(parent_leader_user_id, 0) - FROM bd_profiles - WHERE app_code = ? AND role = 'bd' AND status = 'active' AND user_id IN (` + placeholders + `)` + SELECT bp.user_id, bp.role, COALESCE(u.region_id, 0), COALESCE(bp.parent_leader_user_id, 0) + FROM bd_profiles bp + INNER JOIN users u + ON u.app_code = bp.app_code AND u.user_id = bp.user_id + WHERE bp.app_code = ? AND bp.role = 'bd' AND bp.status = 'active' AND bp.user_id IN (` + placeholders + `)` if role == "bd_leader" { query = ` - SELECT user_id, 'bd_leader', region_id, 0 - FROM bd_leader_profiles - WHERE app_code = ? AND status = 'active' AND user_id IN (` + placeholders + `)` + SELECT bl.user_id, 'bd_leader', COALESCE(u.region_id, 0), 0 + FROM bd_leader_profiles bl + INNER JOIN users u + ON u.app_code = bl.app_code AND u.user_id = bl.user_id + WHERE bl.app_code = ? AND bl.status = 'active' AND bl.user_id IN (` + placeholders + `)` } rows, err := s.userDB.QueryContext(ctx, ` `+query, args...) diff --git a/server/admin/internal/modules/teamsalarysettlement/service_integration_test.go b/server/admin/internal/modules/teamsalarysettlement/service_integration_test.go index 564dfe1e..05013990 100644 --- a/server/admin/internal/modules/teamsalarysettlement/service_integration_test.go +++ b/server/admin/internal/modules/teamsalarysettlement/service_integration_test.go @@ -148,18 +148,18 @@ func seedTeamSalaryFlow(t *testing.T, adminDB *sql.DB, userDB *sql.DB, walletDB `INSERT INTO regions (app_code, region_id, name) VALUES ('lalu', 686, 'Middle East')`, `INSERT INTO countries (app_code, country_id, country_code, name) VALUES ('lalu', 971, 'AE', 'United Arab Emirates')`, `INSERT INTO region_countries (app_code, region_id, country_code, status) VALUES ('lalu', 686, 'AE', 'active')`, - `INSERT INTO users (app_code, user_id, current_display_user_id, username, avatar) VALUES - ('lalu', 3001, 'AG3001', 'Agency One', ''), - ('lalu', 3002, 'AG3002', 'Agency Two', ''), - ('lalu', 4001, 'BD4001', 'BD One', ''), - ('lalu', 5001, 'AD5001', 'Admin One', '')`, - `INSERT INTO agencies (app_code, owner_user_id, parent_bd_user_id, region_id, status) VALUES - ('lalu', 3001, 4001, 686, 'active'), - ('lalu', 3002, 4001, 686, 'active')`, - `INSERT INTO bd_profiles (app_code, user_id, role, region_id, parent_leader_user_id, status) VALUES - ('lalu', 4001, 'bd', 686, 5001, 'active')`, - `INSERT INTO bd_leader_profiles (app_code, user_id, region_id, status) VALUES - ('lalu', 5001, 686, 'active')`, + `INSERT INTO users (app_code, user_id, current_display_user_id, username, avatar, region_id) VALUES + ('lalu', 3001, 'AG3001', 'Agency One', '', 686), + ('lalu', 3002, 'AG3002', 'Agency Two', '', 686), + ('lalu', 4001, 'BD4001', 'BD One', '', 686), + ('lalu', 5001, 'AD5001', 'Admin One', '', 686)`, + `INSERT INTO agencies (app_code, owner_user_id, parent_bd_user_id, status) VALUES + ('lalu', 3001, 4001, 'active'), + ('lalu', 3002, 4001, 'active')`, + `INSERT INTO bd_profiles (app_code, user_id, role, parent_leader_user_id, status) VALUES + ('lalu', 4001, 'bd', 5001, 'active')`, + `INSERT INTO bd_leader_profiles (app_code, user_id, status) VALUES + ('lalu', 5001, 'active')`, ) execAll(t, walletDB, fmt.Sprintf(`INSERT INTO host_salary_settlement_records (app_code, settlement_id, command_id, transaction_id, settlement_type, user_id, agency_owner_user_id, @@ -217,6 +217,7 @@ func teamSalaryUserDDL() []string { current_display_user_id VARCHAR(64) NOT NULL DEFAULT '', username VARCHAR(128) NULL, avatar VARCHAR(512) NULL, + region_id BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (app_code, user_id) )`, `CREATE TABLE regions ( @@ -243,7 +244,6 @@ func teamSalaryUserDDL() []string { app_code VARCHAR(32) NOT NULL, owner_user_id BIGINT NOT NULL, parent_bd_user_id BIGINT NOT NULL DEFAULT 0, - region_id BIGINT NOT NULL, status VARCHAR(24) NOT NULL, PRIMARY KEY (app_code, owner_user_id) )`, @@ -251,7 +251,6 @@ func teamSalaryUserDDL() []string { app_code VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, role VARCHAR(24) NOT NULL, - region_id BIGINT NOT NULL, parent_leader_user_id BIGINT NOT NULL DEFAULT 0, status VARCHAR(24) NOT NULL, PRIMARY KEY (app_code, user_id, role) @@ -259,7 +258,6 @@ func teamSalaryUserDDL() []string { `CREATE TABLE bd_leader_profiles ( app_code VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, - region_id BIGINT NOT NULL, status VARCHAR(24) NOT NULL, PRIMARY KEY (app_code, user_id) )`, diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index afb50431..a07e71b7 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -6446,6 +6446,283 @@ func TestConfirmGooglePaymentUsesAuthenticatedUserAndProfileRegion(t *testing.T) } } +func TestH5RechargeOptionsUsesTokenCountryAndCoinSellerAudience(t *testing.T) { + walletClient := &fakeWalletClient{h5OptionsResp: &walletv1.H5RechargeOptionsResponse{ + Products: []*walletv1.RechargeProduct{{ + ProductId: 91001, + ProductName: "Seller 10 USD", + AmountMicro: 10_000_000, + CoinAmount: 880000, + Platform: "web", + AudienceType: "coin_seller", + Enabled: true, + }}, + PaymentMethods: []*walletv1.ThirdPartyPaymentMethod{{ + MethodId: 810, + ProviderCode: "mifapay", + ProviderName: "MiFaPay", + CountryCode: "SA", + CountryName: "Saudi Arabia", + CurrencyCode: "SAR", + PayWay: "Card", + PayType: "MADA", + MethodName: "MADA", + Status: "active", + UsdToCurrencyRate: "1.00000000", + SortOrder: 810, + }}, + UsdtTrc20Enabled: true, + UsdtTrc20Address: "TPlatformReceiveAddress", + }} + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{42: { + UserId: 42, + DisplayUserId: "163042", + Username: "seller42", + Status: userv1.UserStatus_USER_STATUS_ACTIVE, + Country: "SA", + CountryDisplayName: "Saudi Arabia", + RegionId: 7100, + RegionCode: "mena-sa", + RegionName: "Saudi Arabia", + }}} + identityClient := &fakeUserIdentityClient{resolveByDisplay: map[string]int64{"should-ignore": 900001}} + hostClient := &fakeUserHostClient{roleSummary: &userv1.UserRoleSummary{UserId: 42, IsCoinSeller: true, CoinSellerStatus: "active"}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, identityClient, profileClient) + handler.SetWalletClient(walletClient) + handler.SetUserHostClient(hostClient) + router := handler.Routes(auth.NewVerifier("secret")) + // H5 带 token 时 display_user_id 只能作为无效噪声;gateway 必须用 token 用户重新查国家和币商角色。 + request := httptest.NewRequest(http.MethodGet, "/api/v1/recharge/h5/options?display_user_id=should-ignore", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-h5-seller-options") + 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 identityClient.lastResolve != nil { + t.Fatalf("token h5 options must not resolve display id: %+v", identityClient.lastResolve) + } + if walletClient.lastH5Options == nil || + walletClient.lastH5Options.GetTargetUserId() != 42 || + walletClient.lastH5Options.GetTargetRegionId() != 7100 || + walletClient.lastH5Options.GetTargetCountryCode() != "SA" || + walletClient.lastH5Options.GetAudienceType() != "coin_seller" { + t.Fatalf("h5 options wallet request mismatch: %+v", walletClient.lastH5Options) + } + if hostClient.lastRoleSummary == nil || hostClient.lastRoleSummary.GetUserId() != 42 { + t.Fatalf("h5 options role summary request mismatch: %+v", hostClient.lastRoleSummary) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data, ok := response.Data.(map[string]any) + if response.Code != httpkit.CodeOK || !ok { + t.Fatalf("h5 options response mismatch: %+v", response) + } + account := data["account"].(map[string]any) + methods := data["payment_methods"].([]any) + if account["audience_type"] != "coin_seller" || account["country_code"] != "SA" || len(methods) != 1 || methods[0].(map[string]any)["pay_type"] != "MADA" { + t.Fatalf("h5 options response data mismatch: %+v", data) + } +} + +func TestH5RechargeOptionsResolvesDisplayUserCountryWithoutToken(t *testing.T) { + walletClient := &fakeWalletClient{h5OptionsResp: &walletv1.H5RechargeOptionsResponse{ + PaymentMethods: []*walletv1.ThirdPartyPaymentMethod{{ + MethodId: 1310, + ProviderCode: "mifapay", + ProviderName: "MiFaPay", + CountryCode: "PH", + CountryName: "Philippines", + CurrencyCode: "PHP", + PayWay: "Ewallet", + PayType: "Gcash", + MethodName: "GCash", + Status: "active", + UsdToCurrencyRate: "1.00000000", + }}, + }} + identityClient := &fakeUserIdentityClient{resolveByDisplay: map[string]int64{"163066": 66}} + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{66: { + UserId: 66, + DisplayUserId: "163066", + Username: "normal66", + Status: userv1.UserStatus_USER_STATUS_ACTIVE, + Country: "PH", + CountryDisplayName: "Philippines", + RegionId: 7200, + }}} + hostClient := &fakeUserHostClient{roleSummary: &userv1.UserRoleSummary{UserId: 66}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, identityClient, profileClient) + handler.SetWalletClient(walletClient) + handler.SetUserHostClient(hostClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/recharge/h5/options?display_user_id=163066", 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 identityClient.lastResolve == nil || identityClient.lastResolve.GetDisplayUserId() != "163066" { + t.Fatalf("display user id was not resolved: %+v", identityClient.lastResolve) + } + if walletClient.lastH5Options == nil || + walletClient.lastH5Options.GetTargetUserId() != 66 || + walletClient.lastH5Options.GetTargetRegionId() != 7200 || + walletClient.lastH5Options.GetTargetCountryCode() != "PH" || + walletClient.lastH5Options.GetAudienceType() != "normal" { + t.Fatalf("display h5 options wallet request mismatch: %+v", walletClient.lastH5Options) + } +} + +func TestH5RechargeOptionsFallsBackToNumericUserIDWhenDisplayIDIsHeld(t *testing.T) { + walletClient := &fakeWalletClient{h5OptionsResp: &walletv1.H5RechargeOptionsResponse{ + PaymentMethods: []*walletv1.ThirdPartyPaymentMethod{{ + MethodId: 1810, + ProviderCode: "mifapay", + ProviderName: "MiFaPay", + CountryCode: "SA", + CountryName: "Saudi Arabia", + CurrencyCode: "SAR", + PayWay: "Card", + PayType: "MADA", + MethodName: "MADA", + Status: "active", + UsdToCurrencyRate: "1.00000000", + }}, + }} + identityClient := &fakeUserIdentityClient{resolveErr: xerr.New(xerr.DisplayUserIDNotFound, "not found")} + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{123456: { + UserId: 123456, + DisplayUserId: "1111cc1111", + Username: "test_123456", + Status: userv1.UserStatus_USER_STATUS_ACTIVE, + Country: "SA", + CountryDisplayName: "Saudi Arabia", + RegionId: 686, + }}} + hostClient := &fakeUserHostClient{roleSummary: &userv1.UserRoleSummary{UserId: 123456}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, identityClient, profileClient) + handler.SetWalletClient(walletClient) + handler.SetUserHostClient(hostClient) + router := handler.Routes(auth.NewVerifier("secret")) + // 123456 在用户换靓号后可能不再是 active display_user_id,但 H5 的无 token 入口文案是输入用户 ID; + // display 解析 NOT_FOUND 时,纯数字输入必须回退到真实 user_id,才能按该用户国家返回沙特支付方式。 + request := httptest.NewRequest(http.MethodGet, "/api/v1/recharge/h5/options?display_user_id=123456", 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 identityClient.lastResolve == nil || identityClient.lastResolve.GetDisplayUserId() != "123456" { + t.Fatalf("display user id was not tried first: %+v", identityClient.lastResolve) + } + if profileClient.lastGet == nil || profileClient.lastGet.GetUserId() != 123456 { + t.Fatalf("numeric user id fallback did not load profile: %+v", profileClient.lastGet) + } + if walletClient.lastH5Options == nil || + walletClient.lastH5Options.GetTargetUserId() != 123456 || + walletClient.lastH5Options.GetTargetRegionId() != 686 || + walletClient.lastH5Options.GetTargetCountryCode() != "SA" || + walletClient.lastH5Options.GetAudienceType() != "normal" { + t.Fatalf("numeric h5 options wallet request mismatch: %+v", walletClient.lastH5Options) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data, ok := response.Data.(map[string]any) + if response.Code != httpkit.CodeOK || !ok { + t.Fatalf("h5 options response mismatch: %+v", response) + } + account := data["account"].(map[string]any) + methods := data["payment_methods"].([]any) + if account["display_user_id"] != "1111cc1111" || account["country_code"] != "SA" || len(methods) != 1 || methods[0].(map[string]any)["pay_type"] != "MADA" { + t.Fatalf("numeric h5 options response data mismatch: %+v", data) + } +} + +func TestH5RechargeCreateOrderUsesProfileCountryForNormalUser(t *testing.T) { + walletClient := &fakeWalletClient{h5CreateOrderResp: &walletv1.H5RechargeOrderResponse{Order: &walletv1.ExternalRechargeOrder{ + OrderId: "h5-order-eg", + TargetUserId: 55, + TargetRegionId: 7300, + TargetCountryCode: "EG", + AudienceType: "normal", + ProductId: 93001, + ProductName: "Normal 1.5 USD", + CoinAmount: 120000, + UsdMinorAmount: 150, + ProviderCode: "mifapay", + PaymentMethodId: 1210, + CountryCode: "EG", + CurrencyCode: "EGP", + ProviderAmountMinor: 150, + PayWay: "Ewallet", + PayType: "FawryPay", + PayUrl: "https://pay.example/h5-order-eg", + Status: "pending", + }}} + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{55: { + UserId: 55, + DisplayUserId: "163055", + Username: "normal55", + Status: userv1.UserStatus_USER_STATUS_ACTIVE, + Country: "EG", + CountryDisplayName: "Egypt", + RegionId: 7300, + }}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, &fakeUserIdentityClient{}, profileClient) + handler.SetWalletClient(walletClient) + handler.SetUserHostClient(&fakeUserHostClient{roleSummary: &userv1.UserRoleSummary{UserId: 55}}) + router := handler.Routes(auth.NewVerifier("secret")) + body := []byte(`{"commandId":"cmd-h5-create-eg","productId":93001,"paymentMethodId":1210,"returnUrl":"https://h5.example/return"}`) + request := httptest.NewRequest(http.MethodPost, "/api/v1/recharge/h5/orders", bytes.NewReader(body)) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 55)) + request.Header.Set("X-Forwarded-For", "203.0.113.9") + request.Header.Set("Accept-Language", "ar-EG") + 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 walletClient.lastH5CreateOrder == nil || + walletClient.lastH5CreateOrder.GetCommandId() != "cmd-h5-create-eg" || + walletClient.lastH5CreateOrder.GetTargetUserId() != 55 || + walletClient.lastH5CreateOrder.GetTargetRegionId() != 7300 || + walletClient.lastH5CreateOrder.GetTargetCountryCode() != "EG" || + walletClient.lastH5CreateOrder.GetAudienceType() != "normal" || + walletClient.lastH5CreateOrder.GetProductId() != 93001 || + walletClient.lastH5CreateOrder.GetProviderCode() != "mifapay" || + walletClient.lastH5CreateOrder.GetPaymentMethodId() != 1210 || + walletClient.lastH5CreateOrder.GetReturnUrl() != "https://h5.example/return" || + walletClient.lastH5CreateOrder.GetLanguage() != "ar-EG" { + t.Fatalf("h5 create order wallet request mismatch: %+v", walletClient.lastH5CreateOrder) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data, ok := response.Data.(map[string]any) + if response.Code != httpkit.CodeOK || !ok { + t.Fatalf("h5 create order response mismatch: %+v", response) + } + order := data["order"].(map[string]any) + if order["order_id"] != "h5-order-eg" || order["country_code"] != "EG" || order["pay_type"] != "FawryPay" || order["pay_url"] != "https://pay.example/h5-order-eg" { + t.Fatalf("h5 create order response data mismatch: %+v", data) + } +} + func TestCoinSellerTransferChecksIdentityAndPropagatesWalletCommand(t *testing.T) { walletClient := &fakeWalletClient{transferResp: &walletv1.TransferCoinFromSellerResponse{ TransactionId: "wtx-coin-seller", diff --git a/services/gateway-service/internal/transport/http/useridinput/resolve.go b/services/gateway-service/internal/transport/http/useridinput/resolve.go new file mode 100644 index 00000000..c464d395 --- /dev/null +++ b/services/gateway-service/internal/transport/http/useridinput/resolve.go @@ -0,0 +1,56 @@ +package useridinput + +import ( + "context" + "strconv" + "strings" + + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/xerr" +) + +type DisplayResolver interface { + ResolveDisplayUserID(ctx context.Context, req *userv1.ResolveDisplayUserIDRequest) (*userv1.ResolveDisplayUserIDResponse, error) +} + +// Resolve 把 H5、后台、运营入口里用户手填的“用户标识”统一转换成服务内部 user_id。 +// 解析顺序必须先查 active display_user_id:靓号、当前短号、数字短号都属于 display 体系,优先级高于纯数字 user_id。 +// 只有 user-service 明确返回 DISPLAY_USER_ID_NOT_FOUND,或者旧 fake 返回空 identity 时,纯数字输入才回退成 user_id。 +// 非 NOT_FOUND 错误不能被兜底吞掉,否则 user-service 抖动时会把请求错误地路由到同名数字 user_id。 +func Resolve(ctx context.Context, resolver DisplayResolver, meta *userv1.RequestMeta, raw string) (int64, error) { + input := strings.TrimSpace(raw) + if input == "" { + return 0, xerr.New(xerr.InvalidArgument, "user identifier is required") + } + if resolver == nil { + return 0, xerr.New(xerr.Unavailable, "user identity resolver is not configured") + } + resp, err := resolver.ResolveDisplayUserID(ctx, &userv1.ResolveDisplayUserIDRequest{ + Meta: meta, + DisplayUserId: input, + }) + if err == nil { + if userID := resp.GetIdentity().GetUserId(); userID > 0 { + return userID, nil + } + if userID, ok := numericUserID(input); ok { + return userID, nil + } + return 0, xerr.New(xerr.DisplayUserIDNotFound, "not found") + } + if !xerr.IsCode(err, xerr.DisplayUserIDNotFound) { + return 0, err + } + if userID, ok := numericUserID(input); ok { + return userID, nil + } + return 0, err +} + +func numericUserID(value string) (int64, bool) { + userID, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64) + if err != nil || userID <= 0 { + return 0, false + } + return userID, true +} diff --git a/services/gateway-service/internal/transport/http/useridinput/resolve_test.go b/services/gateway-service/internal/transport/http/useridinput/resolve_test.go new file mode 100644 index 00000000..5b0fe749 --- /dev/null +++ b/services/gateway-service/internal/transport/http/useridinput/resolve_test.go @@ -0,0 +1,74 @@ +package useridinput + +import ( + "context" + "testing" + + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/xerr" +) + +type fakeDisplayResolver struct { + userID int64 + err error + last *userv1.ResolveDisplayUserIDRequest +} + +func (f *fakeDisplayResolver) ResolveDisplayUserID(_ context.Context, req *userv1.ResolveDisplayUserIDRequest) (*userv1.ResolveDisplayUserIDResponse, error) { + f.last = req + if f.err != nil { + return nil, f.err + } + return &userv1.ResolveDisplayUserIDResponse{Identity: &userv1.UserIdentity{ + UserId: f.userID, + DisplayUserId: req.GetDisplayUserId(), + Status: "active", + AppCode: req.GetMeta().GetAppCode(), + }}, nil +} + +func TestResolvePrefersActiveDisplayIDBeforeNumericUserID(t *testing.T) { + resolver := &fakeDisplayResolver{userID: 900001} + userID, err := Resolve(context.Background(), resolver, &userv1.RequestMeta{AppCode: "lalu"}, "123456") + if err != nil { + t.Fatalf("Resolve failed: %v", err) + } + if userID != 900001 { + t.Fatalf("active display id should win over numeric fallback, got %d", userID) + } + if resolver.last == nil || resolver.last.GetDisplayUserId() != "123456" { + t.Fatalf("display resolver was not called first: %+v", resolver.last) + } +} + +func TestResolveFallsBackToNumericUserIDWhenDisplayIDNotFound(t *testing.T) { + resolver := &fakeDisplayResolver{err: xerr.New(xerr.DisplayUserIDNotFound, "not found")} + userID, err := Resolve(context.Background(), resolver, &userv1.RequestMeta{AppCode: "lalu"}, "123456") + if err != nil { + t.Fatalf("Resolve failed: %v", err) + } + if userID != 123456 { + t.Fatalf("numeric user id fallback mismatch: got %d", userID) + } +} + +func TestResolveKeepsPrettyDisplayIDOnDisplayResolver(t *testing.T) { + resolver := &fakeDisplayResolver{userID: 123456} + userID, err := Resolve(context.Background(), resolver, &userv1.RequestMeta{AppCode: "lalu"}, "1111cc1111") + if err != nil { + t.Fatalf("Resolve failed: %v", err) + } + if userID != 123456 { + t.Fatalf("pretty display id resolve mismatch: got %d", userID) + } + if resolver.last == nil || resolver.last.GetDisplayUserId() != "1111cc1111" { + t.Fatalf("pretty display id was not passed through: %+v", resolver.last) + } +} + +func TestResolveDoesNotHideNonNotFoundErrors(t *testing.T) { + resolver := &fakeDisplayResolver{err: xerr.New(xerr.Unavailable, "user-service unavailable")} + if _, err := Resolve(context.Background(), resolver, &userv1.RequestMeta{AppCode: "lalu"}, "123456"); !xerr.IsCode(err, xerr.Unavailable) { + t.Fatalf("non-not-found error should pass through, got %v", err) + } +} diff --git a/services/gateway-service/internal/transport/http/walletapi/h5_recharge_handler.go b/services/gateway-service/internal/transport/http/walletapi/h5_recharge_handler.go index 3b377271..de7eac44 100644 --- a/services/gateway-service/internal/transport/http/walletapi/h5_recharge_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/h5_recharge_handler.go @@ -11,6 +11,7 @@ import ( "hyapp/pkg/appcode" "hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/transport/http/httpkit" + "hyapp/services/gateway-service/internal/transport/http/useridinput" ) const ( @@ -304,20 +305,14 @@ func (h *Handler) resolveH5RechargeTarget(writer http.ResponseWriter, request *h userID := auth.UserIDFromContext(request.Context()) resolvedByToken := userID > 0 if !resolvedByToken { - displayUserID = strings.TrimSpace(displayUserID) - if displayUserID == "" { - httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") - return h5RechargeTargetContext{}, false - } - resp, err := h.userIdentityClient.ResolveDisplayUserID(request.Context(), &userv1.ResolveDisplayUserIDRequest{ - Meta: httpkit.UserMeta(request, ""), - DisplayUserId: displayUserID, - }) + // 无 token 入口的输入是“用户标识”,可能是 active 靓号、当前短号、默认数字 ID 或真实 user_id。 + // 所有传给 wallet-service 的目标都必须先在 gateway 统一收敛成内部 user_id,避免各业务入口自行解析导致靓号报错。 + resolvedUserID, err := useridinput.Resolve(request.Context(), h.userIdentityClient, httpkit.UserMeta(request, ""), displayUserID) if err != nil { httpkit.WriteRPCError(writer, request, err) return h5RechargeTargetContext{}, false } - userID = resp.GetIdentity().GetUserId() + userID = resolvedUserID } if userID <= 0 { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") diff --git a/services/user-service/deploy/mysql/initdb/001_user_service.sql b/services/user-service/deploy/mysql/initdb/001_user_service.sql index 0beef04b..4595e509 100644 --- a/services/user-service/deploy/mysql/initdb/001_user_service.sql +++ b/services/user-service/deploy/mysql/initdb/001_user_service.sql @@ -725,14 +725,12 @@ CREATE TABLE IF NOT EXISTS host_profiles ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', status VARCHAR(32) NOT NULL COMMENT '业务状态', - region_id BIGINT NOT NULL COMMENT '区域 ID', current_agency_id BIGINT NULL COMMENT '当前公会 ID', current_membership_id BIGINT NULL COMMENT '当前成员关系 ID', source VARCHAR(64) NOT NULL COMMENT '来源', first_became_host_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', - KEY idx_host_profiles_region_status (app_code, region_id, status), KEY idx_host_profiles_agency (app_code, current_agency_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主播资料表'; @@ -740,25 +738,21 @@ CREATE TABLE IF NOT EXISTS bd_profiles ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', role VARCHAR(32) NOT NULL COMMENT '角色', - region_id BIGINT NOT NULL COMMENT '区域 ID', parent_leader_user_id BIGINT NULL COMMENT '父级负责人用户 ID', status VARCHAR(32) NOT NULL COMMENT '业务状态', created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', - KEY idx_bd_profiles_region_role (app_code, region_id, role, status), KEY idx_bd_profiles_parent_leader (app_code, parent_leader_user_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BD资料表'; CREATE TABLE IF NOT EXISTS bd_leader_profiles ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID', - region_id BIGINT NOT NULL COMMENT '区域 ID', status VARCHAR(32) NOT NULL COMMENT '业务状态', created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', - updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', - KEY idx_bd_leader_profiles_region_status (app_code, region_id, status) + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BD Leader资料表'; CREATE TABLE IF NOT EXISTS coin_seller_profiles ( @@ -778,7 +772,6 @@ CREATE TABLE IF NOT EXISTS agencies ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', agency_id BIGINT NOT NULL PRIMARY KEY COMMENT '公会 ID', owner_user_id BIGINT NOT NULL COMMENT '房主用户 ID', - region_id BIGINT NOT NULL COMMENT '区域 ID', parent_bd_user_id BIGINT NOT NULL COMMENT '父级BD用户 ID', name VARCHAR(128) NOT NULL COMMENT '名称', status VARCHAR(32) NOT NULL COMMENT '业务状态', @@ -791,7 +784,6 @@ CREATE TABLE IF NOT EXISTS agencies ( CASE WHEN status = 'active' THEN owner_user_id ELSE NULL END ) STORED COMMENT '启用房主用户 ID', UNIQUE KEY uk_agencies_active_owner (app_code, active_owner_user_id), - KEY idx_agencies_region_status (app_code, region_id, status, join_enabled), KEY idx_agencies_parent_bd (app_code, parent_bd_user_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公会表'; @@ -800,7 +792,6 @@ CREATE TABLE IF NOT EXISTS agency_memberships ( membership_id BIGINT NOT NULL PRIMARY KEY COMMENT '成员关系 ID', agency_id BIGINT NOT NULL COMMENT '公会 ID', host_user_id BIGINT NOT NULL COMMENT '主播用户 ID', - region_id BIGINT NOT NULL COMMENT '区域 ID', membership_type VARCHAR(32) NOT NULL COMMENT '成员关系类型', status VARCHAR(32) NOT NULL COMMENT '业务状态', joined_at_ms BIGINT NOT NULL COMMENT '加入时间,UTC epoch ms', @@ -824,7 +815,6 @@ CREATE TABLE IF NOT EXISTS agency_applications ( command_id VARCHAR(96) NOT NULL COMMENT '业务命令幂等 ID', applicant_user_id BIGINT NOT NULL COMMENT '申请人用户 ID', agency_id BIGINT NOT NULL COMMENT '公会 ID', - region_id BIGINT NOT NULL COMMENT '区域 ID', status VARCHAR(32) NOT NULL COMMENT '业务状态', reviewed_by_user_id BIGINT NULL COMMENT '审核用户 ID', review_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '审核原因', @@ -849,7 +839,6 @@ CREATE TABLE IF NOT EXISTS role_invitations ( inviter_user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', inviter_bd_user_id BIGINT NOT NULL COMMENT '邀请人BD用户 ID', target_user_id BIGINT NOT NULL COMMENT '目标用户 ID', - region_id BIGINT NOT NULL COMMENT '区域 ID', agency_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '公会名称', parent_bd_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '父级BD用户 ID', parent_leader_user_id BIGINT NULL COMMENT '父级负责人用户 ID', @@ -894,160 +883,7 @@ CREATE TABLE IF NOT EXISTS host_outbox ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主播发件箱表'; --- 国家种子由 services/user-service/deploy/data/countries.json 生成,避免 SQL 与数据源分叉。 -- 区域种子使用产品定义的业务分区,不按原始地理来源自动推断。 -INSERT INTO countries (country_name, country_code, iso_alpha3, iso_numeric, country_display_name, phone_country_code, flag, enabled, sort_order, created_at_ms, updated_at_ms) VALUES - ('United Arab Emirates', 'AE', 'ARE', '784', '阿拉伯联合酋长国', '+971', '🇦🇪', TRUE, 20, 0, 0), - ('Afghanistan', 'AF', 'AFG', '004', '阿富汗', '+93', '🇦🇫', TRUE, 30, 0, 0), - ('Albania', 'AL', 'ALB', '008', '阿尔巴尼亚', '+355', '🇦🇱', TRUE, 60, 0, 0), - ('Armenia', 'AM', 'ARM', '051', '亚美尼亚', '+374', '🇦🇲', TRUE, 70, 0, 0), - ('Angola', 'AO', 'AGO', '024', '安哥拉', '+244', '🇦🇴', TRUE, 80, 0, 0), - ('Argentina', 'AR', 'ARG', '032', '阿根廷', '+54', '🇦🇷', TRUE, 100, 0, 0), - ('Austria', 'AT', 'AUT', '040', '奥地利', '+43', '🇦🇹', TRUE, 120, 0, 0), - ('Australia', 'AU', 'AUS', '036', '澳大利亚', '+61', '🇦🇺', TRUE, 130, 0, 0), - ('Bangladesh', 'BD', 'BGD', '050', '孟加拉国', '+880', '🇧🇩', TRUE, 190, 0, 0), - ('Belgium', 'BE', 'BEL', '056', '比利时', '+32', '🇧🇪', TRUE, 200, 0, 0), - ('Bulgaria', 'BG', 'BGR', '100', '保加利亚', '+359', '🇧🇬', TRUE, 220, 0, 0), - ('Bahrain', 'BH', 'BHR', '048', '巴林', '+973', '🇧🇭', TRUE, 230, 0, 0), - ('Benin', 'BJ', 'BEN', '204', '贝宁', '+229', '🇧🇯', TRUE, 250, 0, 0), - ('Bolivia', 'BO', 'BOL', '068', '玻利维亚', '+591', '🇧🇴', TRUE, 290, 0, 0), - ('Brazil', 'BR', 'BRA', '076', '巴西', '+55', '🇧🇷', TRUE, 310, 0, 0), - ('Botswana', 'BW', 'BWA', '072', '博茨瓦纳', '+267', '🇧🇼', TRUE, 350, 0, 0), - ('Belarus', 'BY', 'BLR', '112', '白俄罗斯', '+375', '🇧🇾', TRUE, 360, 0, 0), - ('Canada', 'CA', 'CAN', '124', '加拿大', '+1', '🇨🇦', TRUE, 380, 0, 0), - ('DR Congo', 'CD', 'COD', '180', '民主刚果', '+243', '🇨🇩', TRUE, 400, 0, 0), - ('Central African Republic', 'CF', 'CAF', '140', '中非共和国', '+236', '🇨🇫', TRUE, 410, 0, 0), - ('Congo', 'CG', 'COG', '178', '刚果', '+242', '🇨🇬', TRUE, 420, 0, 0), - ('Switzerland', 'CH', 'CHE', '756', '瑞士', '+41', '🇨🇭', TRUE, 430, 0, 0), - ('Ivory Coast', 'CI', 'CIV', '384', '科特迪瓦', '+225', '🇨🇮', TRUE, 440, 0, 0), - ('Chile', 'CL', 'CHL', '152', '智利', '+56', '🇨🇱', TRUE, 460, 0, 0), - ('Cameroon', 'CM', 'CMR', '120', '喀麦隆', '+237', '🇨🇲', TRUE, 470, 0, 0), - ('Colombia', 'CO', 'COL', '170', '哥伦比亚', '+57', '🇨🇴', TRUE, 490, 0, 0), - ('Costa Rica', 'CR', 'CRI', '188', '哥斯达黎加', '+506', '🇨🇷', TRUE, 500, 0, 0), - ('Cuba', 'CU', 'CUB', '192', '古巴', '+53', '🇨🇺', TRUE, 510, 0, 0), - ('Cyprus', 'CY', 'CYP', '196', '塞浦路斯', '+357', '🇨🇾', TRUE, 550, 0, 0), - ('Czechia', 'CZ', 'CZE', '203', '捷克', '+420', '🇨🇿', TRUE, 560, 0, 0), - ('Germany', 'DE', 'DEU', '276', '德国', '+49', '🇩🇪', TRUE, 570, 0, 0), - ('Djibouti', 'DJ', 'DJI', '262', '吉布提', '+253', '🇩🇯', TRUE, 580, 0, 0), - ('Denmark', 'DK', 'DNK', '208', '丹麦', '+45', '🇩🇰', TRUE, 590, 0, 0), - ('Dominican Republic', 'DO', 'DOM', '214', '多明尼加', '+1', '🇩🇴', TRUE, 610, 0, 0), - ('Algeria', 'DZ', 'DZA', '012', '阿尔及利亚', '+213', '🇩🇿', TRUE, 620, 0, 0), - ('Ecuador', 'EC', 'ECU', '218', '厄瓜多尔', '+593', '🇪🇨', TRUE, 630, 0, 0), - ('Estonia', 'EE', 'EST', '233', '爱沙尼亚', '+372', '🇪🇪', TRUE, 640, 0, 0), - ('Egypt', 'EG', 'EGY', '818', '埃及', '+20', '🇪🇬', TRUE, 650, 0, 0), - ('Eritrea', 'ER', 'ERI', '232', '厄立特里亚', '+291', '🇪🇷', TRUE, 670, 0, 0), - ('Spain', 'ES', 'ESP', '724', '西班牙', '+34', '🇪🇸', TRUE, 680, 0, 0), - ('Ethiopia', 'ET', 'ETH', '231', '埃塞俄比亚', '+251', '🇪🇹', TRUE, 690, 0, 0), - ('Finland', 'FI', 'FIN', '246', '芬兰', '+358', '🇫🇮', TRUE, 700, 0, 0), - ('France', 'FR', 'FRA', '250', '法国', '+33', '🇫🇷', TRUE, 750, 0, 0), - ('Gabon', 'GA', 'GAB', '266', '加蓬', '+241', '🇬🇦', TRUE, 760, 0, 0), - ('United Kingdom', 'GB', 'GBR', '826', '英国', '+44', '🇬🇧', TRUE, 770, 0, 0), - ('Georgia', 'GE', 'GEO', '268', '格鲁吉亚', '+995', '🇬🇪', TRUE, 790, 0, 0), - ('Ghana', 'GH', 'GHA', '288', '加纳', '+233', '🇬🇭', TRUE, 820, 0, 0), - ('Gambia', 'GM', 'GMB', '270', '冈比亚', '+220', '🇬🇲', TRUE, 850, 0, 0), - ('Guinea', 'GN', 'GIN', '324', '几内亚', '+224', '🇬🇳', TRUE, 860, 0, 0), - ('Equatorial Guinea', 'GQ', 'GNQ', '226', '赤道几内亚', '+240', '🇬🇶', TRUE, 880, 0, 0), - ('Greece', 'GR', 'GRC', '300', '希腊', '+30', '🇬🇷', TRUE, 890, 0, 0), - ('Guatemala', 'GT', 'GTM', '320', '危地马拉', '+502', '🇬🇹', TRUE, 910, 0, 0), - ('Honduras', 'HN', 'HND', '340', '洪都拉斯', '+504', '🇭🇳', TRUE, 970, 0, 0), - ('Croatia', 'HR', 'HRV', '191', '克罗地亚', '+385', '🇭🇷', TRUE, 980, 0, 0), - ('Haiti', 'HT', 'HTI', '332', '海地', '+509', '🇭🇹', TRUE, 990, 0, 0), - ('Hungary', 'HU', 'HUN', '348', '匈牙利', '+36', '🇭🇺', TRUE, 1000, 0, 0), - ('Indonesia', 'ID', 'IDN', '360', '印度尼西亚', '+62', '🇮🇩', TRUE, 1010, 0, 0), - ('Ireland', 'IE', 'IRL', '372', '爱尔兰', '+353', '🇮🇪', TRUE, 1020, 0, 0), - ('Israel', 'IL', 'ISR', '376', '以色列', '+972', '🇮🇱', TRUE, 1030, 0, 0), - ('India', 'IN', 'IND', '356', '印度', '+91', '🇮🇳', TRUE, 1050, 0, 0), - ('Iraq', 'IQ', 'IRQ', '368', '伊拉克', '+964', '🇮🇶', TRUE, 1070, 0, 0), - ('Iran', 'IR', 'IRN', '364', '伊朗', '+98', '🇮🇷', TRUE, 1080, 0, 0), - ('Italy', 'IT', 'ITA', '380', '意大利', '+39', '🇮🇹', TRUE, 1100, 0, 0), - ('Jamaica', 'JM', 'JAM', '388', '牙买加', '+1', '🇯🇲', TRUE, 1120, 0, 0), - ('Jordan', 'JO', 'JOR', '400', '约旦', '+962', '🇯🇴', TRUE, 1130, 0, 0), - ('Japan', 'JP', 'JPN', '392', '日本', '+81', '🇯🇵', TRUE, 1140, 0, 0), - ('Kenya', 'KE', 'KEN', '404', '肯尼亚', '+254', '🇰🇪', TRUE, 1150, 0, 0), - ('Kyrgyzstan', 'KG', 'KGZ', '417', '吉尔吉斯斯坦', '+996', '🇰🇬', TRUE, 1160, 0, 0), - ('Cambodia', 'KH', 'KHM', '116', '柬埔寨', '+855', '🇰🇭', TRUE, 1170, 0, 0), - ('North Korea', 'KP', 'PRK', '408', '朝鲜', '+850', '🇰🇵', TRUE, 1210, 0, 0), - ('South Korea', 'KR', 'KOR', '410', '韩国', '+82', '🇰🇷', TRUE, 1220, 0, 0), - ('Kuwait', 'KW', 'KWT', '414', '科威特', '+965', '🇰🇼', TRUE, 1230, 0, 0), - ('Kazakhstan', 'KZ', 'KAZ', '398', '哈萨克斯坦', '+7', '🇰🇿', TRUE, 1250, 0, 0), - ('Laos', 'LA', 'LAO', '418', '老挝', '+856', '🇱🇦', TRUE, 1260, 0, 0), - ('Lebanon', 'LB', 'LBN', '422', '黎巴嫩', '+961', '🇱🇧', TRUE, 1270, 0, 0), - ('Sri Lanka', 'LK', 'LKA', '144', '斯里兰卡', '+94', '🇱🇰', TRUE, 1300, 0, 0), - ('Liberia', 'LR', 'LBR', '430', '利比里亚', '+231', '🇱🇷', TRUE, 1310, 0, 0), - ('Libya', 'LY', 'LBY', '434', '利比亚', '+218', '🇱🇾', TRUE, 1360, 0, 0), - ('Morocco', 'MA', 'MAR', '504', '摩洛哥', '+212', '🇲🇦', TRUE, 1370, 0, 0), - ('Madagascar', 'MG', 'MDG', '450', '马达加斯加', '+261', '🇲🇬', TRUE, 1420, 0, 0), - ('Mali', 'ML', 'MLI', '466', '马里', '+223', '🇲🇱', TRUE, 1450, 0, 0), - ('Myanmar', 'MM', 'MMR', '104', '缅甸', '+95', '🇲🇲', TRUE, 1460, 0, 0), - ('Mongolia', 'MN', 'MNG', '496', '蒙古', '+976', '🇲🇳', TRUE, 1470, 0, 0), - ('Mauritius', 'MU', 'MUS', '480', '毛里求斯', '+230', '🇲🇺', TRUE, 1540, 0, 0), - ('Maldives', 'MV', 'MDV', '462', '马尔代夫', '+960', '🇲🇻', TRUE, 1550, 0, 0), - ('Mexico', 'MX', 'MEX', '484', '墨西哥', '+52', '🇲🇽', TRUE, 1570, 0, 0), - ('Malaysia', 'MY', 'MYS', '458', '马来西亚', '+60', '🇲🇾', TRUE, 1580, 0, 0), - ('Mozambique', 'MZ', 'MOZ', '508', '莫桑比克', '+258', '🇲🇿', TRUE, 1590, 0, 0), - ('Namibia', 'NA', 'NAM', '516', '纳米比亚', '+264', '🇳🇦', TRUE, 1600, 0, 0), - ('Niger', 'NE', 'NER', '562', '尼日尔', '+227', '🇳🇪', TRUE, 1620, 0, 0), - ('Nigeria', 'NG', 'NGA', '566', '尼日利亚', '+234', '🇳🇬', TRUE, 1640, 0, 0), - ('Nicaragua', 'NI', 'NIC', '558', '尼加拉瓜', '+505', '🇳🇮', TRUE, 1650, 0, 0), - ('Netherlands', 'NL', 'NLD', '528', '荷兰', '+31', '🇳🇱', TRUE, 1660, 0, 0), - ('Norway', 'NO', 'NOR', '578', '挪威', '+47', '🇳🇴', TRUE, 1670, 0, 0), - ('Nepal', 'NP', 'NPL', '524', '尼泊尔', '+977', '🇳🇵', TRUE, 1680, 0, 0), - ('New Zealand', 'NZ', 'NZL', '554', '新西兰', '+64', '🇳🇿', TRUE, 1710, 0, 0), - ('Oman', 'OM', 'OMN', '512', '阿曼', '+968', '🇴🇲', TRUE, 1720, 0, 0), - ('Panama', 'PA', 'PAN', '591', '巴拿马', '+507', '🇵🇦', TRUE, 1730, 0, 0), - ('Peru', 'PE', 'PER', '604', '秘鲁', '+51', '🇵🇪', TRUE, 1740, 0, 0), - ('Philippines', 'PH', 'PHL', '608', '菲律宾', '+63', '🇵🇭', TRUE, 1770, 0, 0), - ('Pakistan', 'PK', 'PAK', '586', '巴基斯坦', '+92', '🇵🇰', TRUE, 1780, 0, 0), - ('Poland', 'PL', 'POL', '616', '波兰', '+48', '🇵🇱', TRUE, 1790, 0, 0), - ('Puerto Rico', 'PR', 'PRI', '630', '波多黎各', '+1', '🇵🇷', TRUE, 1820, 0, 0), - ('Palestine', 'PS', 'PSE', '275', '巴勒斯坦', '+970', '🇵🇸', TRUE, 1830, 0, 0), - ('Portugal', 'PT', 'PRT', '620', '葡萄牙', '+351', '🇵🇹', TRUE, 1840, 0, 0), - ('Paraguay', 'PY', 'PRY', '600', '巴拉圭', '+595', '🇵🇾', TRUE, 1860, 0, 0), - ('Qatar', 'QA', 'QAT', '634', '卡塔尔', '+974', '🇶🇦', TRUE, 1870, 0, 0), - ('Serbia', 'RS', 'SRB', '688', '塞尔维亚', '+381', '🇷🇸', TRUE, 1900, 0, 0), - ('Russia', 'RU', 'RUS', '643', '俄罗斯', '+7', '🇷🇺', TRUE, 1910, 0, 0), - ('Rwanda', 'RW', 'RWA', '646', '卢旺达', '+250', '🇷🇼', TRUE, 1920, 0, 0), - ('Saudi Arabia', 'SA', 'SAU', '682', '沙特阿拉伯', '+966', '🇸🇦', TRUE, 1930, 0, 0), - ('Sudan', 'SD', 'SDN', '729', '苏丹', '+249', '🇸🇩', TRUE, 1960, 0, 0), - ('Sweden', 'SE', 'SWE', '752', '瑞典', '+46', '🇸🇪', TRUE, 1970, 0, 0), - ('Singapore', 'SG', 'SGP', '702', '新加坡', '+65', '🇸🇬', TRUE, 1980, 0, 0), - ('Slovakia', 'SK', 'SVK', '703', '斯洛伐克', '+421', '🇸🇰', TRUE, 2020, 0, 0), - ('Sierra Leone', 'SL', 'SLE', '694', '塞拉利昂', '+232', '🇸🇱', TRUE, 2030, 0, 0), - ('Senegal', 'SN', 'SEN', '686', '塞内加尔', '+221', '🇸🇳', TRUE, 2050, 0, 0), - ('Somalia', 'SO', 'SOM', '706', '索马里', '+252', '🇸🇴', TRUE, 2060, 0, 0), - ('South Sudan', 'SS', 'SSD', '728', '南苏丹', '+211', '🇸🇸', TRUE, 2080, 0, 0), - ('El Salvador', 'SV', 'SLV', '222', '萨尔瓦多', '+503', '🇸🇻', TRUE, 2100, 0, 0), - ('Syria', 'SY', 'SYR', '760', '叙利亚', '+963', '🇸🇾', TRUE, 2120, 0, 0), - ('Eswatini', 'SZ', 'SWZ', '748', '斯威士兰', '+268', '🇸🇿', TRUE, 2130, 0, 0), - ('Chad', 'TD', 'TCD', '148', '乍得', '+235', '🇹🇩', TRUE, 2150, 0, 0), - ('Thailand', 'TH', 'THA', '764', '泰国', '+66', '🇹🇭', TRUE, 2180, 0, 0), - ('Tajikistan', 'TJ', 'TJK', '762', '塔吉克斯坦', '+992', '🇹🇯', TRUE, 2190, 0, 0), - ('Turkmenistan', 'TM', 'TKM', '795', '土库曼斯坦', '+993', '🇹🇲', TRUE, 2220, 0, 0), - ('Tunisia', 'TN', 'TUN', '788', '突尼斯', '+216', '🇹🇳', TRUE, 2230, 0, 0), - ('Türkiye', 'TR', 'TUR', '792', '土耳其', '+90', '🇹🇷', TRUE, 2250, 0, 0), - ('Tanzania', 'TZ', 'TZA', '834', '坦桑尼亚', '+255', '🇹🇿', TRUE, 2290, 0, 0), - ('Ukraine', 'UA', 'UKR', '804', '乌克兰', '+380', '🇺🇦', TRUE, 2300, 0, 0), - ('Uganda', 'UG', 'UGA', '800', '乌干达', '+256', '🇺🇬', TRUE, 2310, 0, 0), - ('United States', 'US', 'USA', '840', '美国', '+1', '🇺🇸', TRUE, 2330, 0, 0), - ('Uruguay', 'UY', 'URY', '858', '乌拉圭', '+598', '🇺🇾', TRUE, 2340, 0, 0), - ('Uzbekistan', 'UZ', 'UZB', '860', '乌兹别克斯坦', '+998', '🇺🇿', TRUE, 2350, 0, 0), - ('Venezuela', 'VE', 'VEN', '862', '委内瑞拉', '+58', '🇻🇪', TRUE, 2380, 0, 0), - ('Vietnam', 'VN', 'VNM', '704', '越南', '+84', '🇻🇳', TRUE, 2410, 0, 0), - ('Kosovo', 'XK', 'UNK', NULL, '科索沃', '+383', '🇽🇰', TRUE, 2450, 0, 0), - ('Yemen', 'YE', 'YEM', '887', '也门', '+967', '🇾🇪', TRUE, 2460, 0, 0), - ('Zambia', 'ZM', 'ZMB', '894', '赞比亚', '+260', '🇿🇲', TRUE, 2490, 0, 0), - ('Zimbabwe', 'ZW', 'ZWE', '716', '津巴布韦', '+263', '🇿🇼', TRUE, 2500, 0, 0) -ON DUPLICATE KEY UPDATE - country_name = VALUES(country_name), - iso_alpha3 = VALUES(iso_alpha3), - iso_numeric = VALUES(iso_numeric), - country_display_name = VALUES(country_display_name), - phone_country_code = VALUES(phone_country_code), - flag = VALUES(flag), - enabled = VALUES(enabled), - sort_order = VALUES(sort_order), - updated_at_ms = VALUES(updated_at_ms); - INSERT INTO regions (region_code, name, status, sort_order, created_at_ms, updated_at_ms) VALUES ('MIDDLE_EAST', '中东区', 'active', 10, 0, 0), ('INDIA', '印度区', 'active', 20, 0, 0), @@ -1058,310 +894,11 @@ ON DUPLICATE KEY UPDATE sort_order = VALUES(sort_order), updated_at_ms = VALUES(updated_at_ms); -INSERT INTO region_countries (app_code, region_id, country_code, status, created_at_ms, updated_at_ms) -SELECT 'lalu', r.region_id, seed.country_code, 'active', 0, 0 -FROM ( - SELECT 'AE' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'AF' AS country_code, 'INDIA' AS region_code - UNION ALL - SELECT 'AL' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'AM' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'AO' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'AR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'AT' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'AU' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'BD' AS country_code, 'INDIA' AS region_code - UNION ALL - SELECT 'BE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'BG' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'BH' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'BJ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'BO' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'BR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'BW' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'BY' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CA' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CD' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CF' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CG' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CH' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CI' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CL' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CM' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CO' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CU' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CY' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'CZ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'DE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'DJ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'DK' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'DO' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'DZ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'EC' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'EE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'EG' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'ER' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'ES' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'ET' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'FI' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'FR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GA' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GB' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GH' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GM' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GN' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GQ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'GT' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'HN' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'HR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'HT' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'HU' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'ID' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'IE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'IL' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'IN' AS country_code, 'INDIA' AS region_code - UNION ALL - SELECT 'IQ' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'IR' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'IT' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'JM' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'JO' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'JP' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'KE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'KG' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'KH' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'KP' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'KR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'KW' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'KZ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'LA' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'LB' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'LK' AS country_code, 'INDIA' AS region_code - UNION ALL - SELECT 'LR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'LY' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'MA' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'MG' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'ML' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'MM' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'MN' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'MU' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'MV' AS country_code, 'INDIA' AS region_code - UNION ALL - SELECT 'MX' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'MY' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'MZ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'NA' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'NE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'NG' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'NI' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'NL' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'NO' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'NP' AS country_code, 'INDIA' AS region_code - UNION ALL - SELECT 'NZ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'OM' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'PA' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'PE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'PH' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'PK' AS country_code, 'INDIA' AS region_code - UNION ALL - SELECT 'PL' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'PR' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'PS' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'PT' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'PY' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'QA' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'RS' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'RU' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'RW' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SA' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'SD' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SG' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SK' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SL' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SN' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SO' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SS' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SV' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'SY' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'SZ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'TD' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'TH' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'TJ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'TM' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'TN' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'TR' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'TZ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'UA' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'UG' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'US' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'UY' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'UZ' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'VE' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'VN' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'XK' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'YE' AS country_code, 'MIDDLE_EAST' AS region_code - UNION ALL - SELECT 'ZM' AS country_code, 'EURASIA_AMERICA' AS region_code - UNION ALL - SELECT 'ZW' AS country_code, 'EURASIA_AMERICA' AS region_code -) AS seed -INNER JOIN regions r ON r.app_code = 'lalu' AND r.region_code = seed.region_code -INNER JOIN countries c ON c.app_code = 'lalu' AND c.country_code = seed.country_code -ON DUPLICATE KEY UPDATE - region_id = VALUES(region_id), - status = VALUES(status), - updated_at_ms = VALUES(updated_at_ms); - -- 区域管理只暴露产品定义的业务分区;GLOBAL 仅作为内部兜底,不作为后台可管理区域。 DELETE FROM regions WHERE app_code = 'lalu' AND region_code NOT IN ('MIDDLE_EAST', 'INDIA', 'EURASIA_AMERICA'); --- 产品明确排除的国家直接硬删除,保证重复导入 initdb 时新旧本地库都能对齐当前国家主数据。 -DELETE FROM region_countries -WHERE app_code = 'lalu' - AND country_code IN ('AD', 'AG', 'AI', 'AQ', 'AS', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BF', 'BI', 'BL', 'BM', 'BN', 'BQ', 'BS', 'BT', 'BV', 'BZ', 'CC', 'CK', 'CN', 'CV', 'CW', 'CX', 'DM', 'EH', 'FJ', 'FK', 'FM', 'FO', 'GD', 'GF', 'GG', 'GI', 'GL', 'GP', 'GS', 'GU', 'GW', 'GY', 'HK', 'HM', 'IM', 'IO', 'IS', 'JE', 'KI', 'KM', 'KN', 'KY', 'LC', 'LI', 'LS', 'LT', 'LU', 'LV', 'MC', 'MD', 'ME', 'MF', 'MH', 'MK', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MW', 'NC', 'NF', 'NR', 'NU', 'PF', 'PG', 'PM', 'PN', 'PW', 'RE', 'RO', 'SB', 'SC', 'SH', 'SI', 'SJ', 'SM', 'SR', 'ST', 'SX', 'TC', 'TF', 'TG', 'TK', 'TL', 'TO', 'TT', 'TV', 'TW', 'UM', 'VA', 'VC', 'VG', 'VI', 'VU', 'WF', 'WS', 'YT', 'ZA'); - -DELETE FROM countries -WHERE app_code = 'lalu' - AND country_code IN ('AD', 'AG', 'AI', 'AQ', 'AS', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BF', 'BI', 'BL', 'BM', 'BN', 'BQ', 'BS', 'BT', 'BV', 'BZ', 'CC', 'CK', 'CN', 'CV', 'CW', 'CX', 'DM', 'EH', 'FJ', 'FK', 'FM', 'FO', 'GD', 'GF', 'GG', 'GI', 'GL', 'GP', 'GS', 'GU', 'GW', 'GY', 'HK', 'HM', 'IM', 'IO', 'IS', 'JE', 'KI', 'KM', 'KN', 'KY', 'LC', 'LI', 'LS', 'LT', 'LU', 'LV', 'MC', 'MD', 'ME', 'MF', 'MH', 'MK', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MW', 'NC', 'NF', 'NR', 'NU', 'PF', 'PG', 'PM', 'PN', 'PW', 'RE', 'RO', 'SB', 'SC', 'SH', 'SI', 'SJ', 'SM', 'SR', 'ST', 'SX', 'TC', 'TF', 'TG', 'TK', 'TL', 'TO', 'TT', 'TV', 'TW', 'UM', 'VA', 'VC', 'VG', 'VI', 'VU', 'WF', 'WS', 'YT', 'ZA'); - CREATE TABLE IF NOT EXISTS user_display_user_ids ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID', @@ -1713,25 +1250,6 @@ ON DUPLICATE KEY UPDATE status = VALUES(status), updated_at_ms = VALUES(updated_at_ms); -INSERT INTO user_display_user_ids ( - app_code, - display_user_id, - user_id, - display_user_id_kind, - status, - assigned_at_ms, - activated_at_ms, - created_at_ms, - updated_at_ms -) -VALUES ('lalu', '123456', 123456, 'default', 'active', 0, 0, 0, 0) -ON DUPLICATE KEY UPDATE - display_user_id = VALUES(display_user_id), - display_user_id_kind = VALUES(display_user_id_kind), - status = VALUES(status), - activated_at_ms = VALUES(activated_at_ms), - updated_at_ms = VALUES(updated_at_ms); - INSERT INTO password_accounts ( app_code, user_id, @@ -1793,25 +1311,6 @@ ON DUPLICATE KEY UPDATE status = VALUES(status), updated_at_ms = VALUES(updated_at_ms); -INSERT INTO user_display_user_ids ( - app_code, - display_user_id, - user_id, - display_user_id_kind, - status, - assigned_at_ms, - activated_at_ms, - created_at_ms, - updated_at_ms -) -VALUES ('lalu', '12345678', 12345678, 'default', 'active', 0, 0, 0, 0) -ON DUPLICATE KEY UPDATE - display_user_id = VALUES(display_user_id), - display_user_id_kind = VALUES(display_user_id_kind), - status = VALUES(status), - activated_at_ms = VALUES(activated_at_ms), - updated_at_ms = VALUES(updated_at_ms); - INSERT INTO password_accounts ( app_code, user_id, diff --git a/services/user-service/internal/domain/user/user.go b/services/user-service/internal/domain/user/user.go index 2b2abc9c..74051383 100644 --- a/services/user-service/internal/domain/user/user.go +++ b/services/user-service/internal/domain/user/user.go @@ -825,6 +825,16 @@ func ValidDisplayUserID(value string) bool { return displayUserIDPattern.MatchString(value) } +// ValidResolvableDisplayUserID 校验客户端或后台传入的“可解析展示号”。 +func ValidResolvableDisplayUserID(value string) bool { + if ValidDisplayUserID(value) { + // 默认短号仍然走原有纯数字规则,保证注册、换号和历史默认号解析行为不变。 + return true + } + // 解析和登录入口必须允许后台发放的靓号;写入默认短号仍然只能调用 ValidDisplayUserID。 + return ValidAdminPrettyDisplayUserID(value) +} + // ValidAdminPrettyDisplayUserID 校验后台直接发放的靓号内容。 func ValidAdminPrettyDisplayUserID(value string) bool { // 后台发放支持阿拉伯语、英文和数字;禁止空白、控制字符、emoji 与标点,避免登录和展示链路出现不可输入字符。 diff --git a/services/user-service/internal/domain/user/user_test.go b/services/user-service/internal/domain/user/user_test.go index 9d1386b9..cd238451 100644 --- a/services/user-service/internal/domain/user/user_test.go +++ b/services/user-service/internal/domain/user/user_test.go @@ -20,6 +20,24 @@ func TestValidDisplayUserIDAllowsFiveDigitSeedAccounts(t *testing.T) { } } +func TestValidResolvableDisplayUserIDAllowsDefaultAndPrettyIDs(t *testing.T) { + t.Parallel() + + valid := []string{"10001", "123456", "1111cc1111", "VIP2026", "ملك123", "Cafe\u0301123"} + for _, value := range valid { + if !ValidResolvableDisplayUserID(value) { + t.Fatalf("resolvable display_user_id %q should be valid", value) + } + } + + invalid := []string{"", " ", "VIP-2026", "VIP 2026", "VIP😀", "\nVIP", "VIP!"} + for _, value := range invalid { + if ValidResolvableDisplayUserID(value) { + t.Fatalf("resolvable display_user_id %q should be invalid", value) + } + } +} + func TestValidAdminPrettyDisplayUserID(t *testing.T) { t.Parallel() diff --git a/services/user-service/internal/service/auth/password.go b/services/user-service/internal/service/auth/password.go index a88d44bc..0ab58ac5 100644 --- a/services/user-service/internal/service/auth/password.go +++ b/services/user-service/internal/service/auth/password.go @@ -24,8 +24,8 @@ func (s *Service) LoginPassword(ctx context.Context, displayUserID string, passw // 没有认证 repository 时不能读取密码身份或创建 session。 return authdomain.Token{}, xerr.New(xerr.Unavailable, "auth repository is not configured") } - if !userdomain.ValidDisplayUserID(displayUserID) { - // 非法短号和不存在短号统一映射 AUTH_FAILED,避免账号枚举。 + if !userdomain.ValidResolvableDisplayUserID(displayUserID) { + // 密码登录接受当前 active 默认短号或靓号;非法字符和不存在账号统一映射 AUTH_FAILED,避免账号枚举。 s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.AuthFailed)) return authdomain.Token{}, authFailed() } diff --git a/services/user-service/internal/service/auth/service_test.go b/services/user-service/internal/service/auth/service_test.go index 31b99fe4..473d36af 100644 --- a/services/user-service/internal/service/auth/service_test.go +++ b/services/user-service/internal/service/auth/service_test.go @@ -920,3 +920,50 @@ func TestPasswordLoginUsesCurrentDisplayUserIDWithPrettyLease(t *testing.T) { t.Fatalf("expired pretty display_user_id must not login, got %v", err) } } + +func TestPasswordLoginAllowsAlphanumericPrettyDisplayID(t *testing.T) { + // 后台发放靓号可以包含字母;密码登录必须复用同一可解析展示号规则,不能只允许纯数字靓号。 + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedCountry(t, repository, "SG") + now := time.UnixMilli(1000) + authSvc := newAuthService(repository, &now, []int64{900001}, []string{"100001"}) + userSvc := newUserService(repository, userservice.WithClock(func() time.Time { return now })) + + token, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-pretty-alpha", thirdPartyRegistration("ios"), authservice.Meta{}) + if err != nil { + t.Fatalf("LoginThirdParty failed: %v", err) + } + if err := authSvc.SetPassword(ctx, token.UserID, "secret-pass", authservice.Meta{}); err != nil { + t.Fatalf("SetPassword failed: %v", err) + } + if _, _, err := userSvc.AdminGrantPrettyDisplayID(ctx, userdomain.AdminGrantPrettyDisplayIDCommand{ + TargetUserID: token.UserID, + DisplayUserID: "1111cc1111", + OperatorAdminID: 1, + RequestID: "req-admin-pretty-alpha", + }); err != nil { + t.Fatalf("AdminGrantPrettyDisplayID failed: %v", err) + } + if _, err := repository.RawDB().ExecContext(ctx, ` + UPDATE users + SET current_display_user_id = default_display_user_id, + current_display_user_id_kind = ?, + current_display_user_id_expires_at_ms = NULL + WHERE app_code = ? AND user_id = ? + `, string(userdomain.DisplayUserIDKindDefault), "lalu", token.UserID); err != nil { + t.Fatalf("make users display snapshot stale failed: %v", err) + } + + if _, err := authSvc.LoginPassword(ctx, token.DefaultDisplayUserID, "secret-pass", "ios", authservice.Meta{}); !xerr.IsCode(err, xerr.AuthFailed) { + // 默认短号被置为 held 后不能再作为登录入口,避免两个展示号同时登录同一账号。 + t.Fatalf("default display_user_id must not login while admin pretty is active, got %v", err) + } + prettyToken, err := authSvc.LoginPassword(ctx, "1111cc1111", "secret-pass", "ios", authservice.Meta{}) + if err != nil { + t.Fatalf("alphanumeric pretty display_user_id login failed: %v", err) + } + if prettyToken.UserID != token.UserID || prettyToken.DisplayUserID != "1111cc1111" || prettyToken.DisplayUserIDKind != string(userdomain.DisplayUserIDKindPretty) { + t.Fatalf("unexpected alphanumeric pretty login token: %+v", prettyToken) + } +} diff --git a/services/user-service/internal/service/host/service_test.go b/services/user-service/internal/service/host/service_test.go index 9b6d1ffb..80254346 100644 --- a/services/user-service/internal/service/host/service_test.go +++ b/services/user-service/internal/service/host/service_test.go @@ -55,6 +55,32 @@ func displayID(userID int64) string { return fmt.Sprintf("%06d", 900000+userID%100000) } +func assertNoHostOrgRegionSnapshotColumns(t *testing.T, repository *mysqltest.Repository) { + t.Helper() + var count int64 + err := repository.RawDB().QueryRow(` + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND COLUMN_NAME = 'region_id' + AND TABLE_NAME IN ( + 'host_profiles', + 'bd_profiles', + 'bd_leader_profiles', + 'agencies', + 'agency_memberships', + 'agency_applications', + 'role_invitations' + ) + `).Scan(&count) + if err != nil { + t.Fatalf("check host org region snapshot columns failed: %v", err) + } + if count != 0 { + t.Fatalf("host org identity tables must not keep region_id snapshot columns: count=%d", count) + } +} + func TestCheckBusinessCapabilityRequiresActiveAgencyOwner(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) @@ -336,6 +362,7 @@ func TestInviteBDAcceptsSelfInviteForLeader(t *testing.T) { func TestAdminCreateBDLeaderAndBDIdempotently(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) + assertNoHostOrgRegionSnapshotColumns(t, repository) repository.PutRegion(userdomain.Region{RegionID: 20, RegionCode: "R20", Name: "Region 20"}) seedActiveUser(t, repository, 801, 20) seedActiveUser(t, repository, 802, 20) @@ -370,7 +397,7 @@ func TestAdminCreateBDLeaderAndBDIdempotently(t *testing.T) { if err != nil { t.Fatalf("retry CreateBDLeader failed: %v", err) } - if retriedLeader.UserID != leader.UserID { + if retriedLeader.UserID != leader.UserID || retriedLeader.RegionID != 20 { t.Fatalf("leader retry must replay existing result: %+v", retriedLeader) } @@ -407,6 +434,10 @@ func TestAdminCreateBDLeaderAndBDIdempotently(t *testing.T) { if bd.Role != hostdomain.BDRoleBD || bd.ParentLeaderUserID != 801 || bd.RegionID != 20 { t.Fatalf("bd mismatch: %+v", bd) } + bdAfterSnapshotDrift, err := svc.GetBDProfile(ctx, 802, hostdomain.BDRoleBD) + if err != nil || bdAfterSnapshotDrift.RegionID != 20 { + t.Fatalf("bd profile must read region from users table: bd=%+v err=%v", bdAfterSnapshotDrift, err) + } independentBD, err := svc.CreateBD(ctx, hostservice.CreateBDInput{ CommandID: "admin-create-bd-804-independent", @@ -508,10 +539,11 @@ func TestAdminCreateAgencyControlsAppSearch(t *testing.T) { seedActiveUserInCountry(t, repository, 901, 30, "CN", "https://cdn.example/owner-901.png") seedActiveUser(t, repository, 902, 30) seedActiveUserInCountry(t, repository, 903, 30, "US", "") + seedActiveUser(t, repository, 900, 30) repository.PutBDProfile(hostdomain.BDProfile{ UserID: 900, Role: hostdomain.BDRoleLeader, - RegionID: 30, + RegionID: 999, Status: hostdomain.BDStatusActive, }) svc := newHostService(repository, 5000, 6000) @@ -557,6 +589,9 @@ func TestAdminCreateAgencyControlsAppSearch(t *testing.T) { if len(agencies) != 1 || agencies[0].AgencyID != created.Agency.AgencyID || agencies[0].OwnerAvatar != "https://cdn.example/owner-901.png" || agencies[0].ActiveHostCount != 1 { t.Fatalf("created agency list should be scoped by country with display fields: %+v", agencies) } + if agencies[0].RegionID != 30 { + t.Fatalf("agency search must read region from owner users table: %+v", agencies[0]) + } agencies, err = svc.SearchAgencies(ctx, 902, "Admin Seed", 20) if err != nil { t.Fatalf("SearchAgencies failed: %v", err) @@ -676,10 +711,11 @@ func TestAdminCreateAgencyReusesDetachedHost(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) seedActiveUser(t, repository, 911, 40) + seedActiveUser(t, repository, 910, 40) repository.PutBDProfile(hostdomain.BDProfile{ UserID: 910, Role: hostdomain.BDRoleBD, - RegionID: 40, + RegionID: 999, Status: hostdomain.BDStatusActive, }) repository.PutHostProfile(hostdomain.HostProfile{ diff --git a/services/user-service/internal/service/user/display_id.go b/services/user-service/internal/service/user/display_id.go index 3c326283..c062c9ee 100644 --- a/services/user-service/internal/service/user/display_id.go +++ b/services/user-service/internal/service/user/display_id.go @@ -38,8 +38,8 @@ func (s *Service) GetUserIdentity(ctx context.Context, userID int64) (userdomain // ResolveDisplayUserID 把短号解析成系统 user_id。 func (s *Service) ResolveDisplayUserID(ctx context.Context, displayUserID string) (userdomain.Identity, error) { - if !userdomain.ValidDisplayUserID(displayUserID) { - // 格式错误和未找到使用不同 reason,便于客户端区分输入问题和占用状态。 + if !userdomain.ValidResolvableDisplayUserID(displayUserID) { + // 解析入口接受默认短号和后台发放靓号;非法字符仍然提前拒绝,避免把不可输入内容传进懒过期和查询链路。 return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDInvalid, "display_user_id format is invalid") } if s.identityRepository == nil { diff --git a/services/user-service/internal/storage/mysql/auth/password.go b/services/user-service/internal/storage/mysql/auth/password.go index 0f7826d4..75a51a35 100644 --- a/services/user-service/internal/storage/mysql/auth/password.go +++ b/services/user-service/internal/storage/mysql/auth/password.go @@ -70,21 +70,68 @@ func (r *Repository) FindPasswordByDisplayUserID(ctx context.Context, displayUse } var account authdomain.PasswordAccount + var displayUserIDKind string + var displayUserIDExpiresAtMs int64 err := r.db.QueryRowContext(ctx, ` - SELECT pa.app_code, pa.user_id, u.current_display_user_id, pa.password_hash, pa.hash_alg, pa.created_at_ms, pa.updated_at_ms - FROM users u - INNER JOIN password_accounts pa ON pa.app_code = u.app_code AND pa.user_id = u.user_id - WHERE u.app_code = ? AND u.current_display_user_id = ? - `, appcode.FromContext(ctx), displayUserID).Scan(&account.AppCode, &account.UserID, &account.DisplayUserID, &account.PasswordHash, &account.HashAlg, &account.CreatedAtMs, &account.UpdatedAtMs) + SELECT + pa.app_code, + pa.user_id, + du.display_user_id, + du.display_user_id_kind, + COALESCE(du.expires_at_ms, 0), + pa.password_hash, + pa.hash_alg, + pa.created_at_ms, + pa.updated_at_ms + FROM user_display_user_ids du + INNER JOIN password_accounts pa + ON pa.app_code = du.app_code + AND pa.user_id = du.user_id + WHERE du.app_code = ? + AND du.display_user_id = ? + AND du.status = ? + AND (du.expires_at_ms IS NULL OR du.expires_at_ms = 0 OR du.expires_at_ms > ?) + LIMIT 1 + `, appcode.FromContext(ctx), displayUserID, string(userdomain.DisplayUserIDStatusActive), nowMs).Scan(&account.AppCode, &account.UserID, &account.DisplayUserID, &displayUserIDKind, &displayUserIDExpiresAtMs, &account.PasswordHash, &account.HashAlg, &account.CreatedAtMs, &account.UpdatedAtMs) if err == sql.ErrNoRows { - // 短号不存在或未设置密码都返回 not found,由 service 统一映射 AUTH_FAILED。 + // 短号不存在、处于 held/released、已经过期或未设置密码都返回 not found,由 service 统一映射 AUTH_FAILED。 return authdomain.PasswordAccount{}, xerr.New(xerr.NotFound, "password account not found") } if err != nil { return authdomain.PasswordAccount{}, err } + if err := r.repairPasswordLoginIdentity(ctx, account.AppCode, account.UserID, account.DisplayUserID, displayUserIDKind, displayUserIDExpiresAtMs, nowMs); err != nil { + return authdomain.PasswordAccount{}, err + } return account, nil } +func (r *Repository) repairPasswordLoginIdentity(ctx context.Context, appCode string, userID int64, displayUserID string, displayUserIDKind string, expiresAtMs int64, nowMs int64) error { + // 密码登录以 user_display_user_ids 的 active 行为准;同步 users 快照后,后续 freshUser 和 token 会携带同一个当前展示号。 + _, err := r.db.ExecContext(ctx, ` + UPDATE users + SET current_display_user_id = ?, + current_display_user_id_kind = ?, + current_display_user_id_expires_at_ms = ?, + updated_at_ms = GREATEST(updated_at_ms, ?) + WHERE app_code = ? + AND user_id = ? + AND ( + current_display_user_id <> ? + OR current_display_user_id_kind <> ? + OR COALESCE(current_display_user_id_expires_at_ms, 0) <> ? + ) + `, displayUserID, displayUserIDKind, nullableExpiresAt(expiresAtMs), nowMs, + appcode.Normalize(appCode), userID, displayUserID, displayUserIDKind, expiresAtMs) + return err +} + +func nullableExpiresAt(value int64) any { + if value <= 0 { + return nil + } + return value +} + // CreateSession 创建新的 refresh session。 diff --git a/services/user-service/internal/storage/mysql/host/admin.go b/services/user-service/internal/storage/mysql/host/admin.go index 21ceaf9f..6d9834aa 100644 --- a/services/user-service/internal/storage/mysql/host/admin.go +++ b/services/user-service/internal/storage/mysql/host/admin.go @@ -86,7 +86,8 @@ func (r *Repository) CreateBD(ctx context.Context, command hostservice.CreateBDC return hostdomain.BDProfile{}, err } if command.ParentLeaderUserID > 0 { - // 独立 BD 不需要父级行锁;只有传入 Leader 时才锁定独立 Leader 表并校验启用状态和区域归属。 + // 独立 BD 不需要父级行锁;只有传入 Leader 时才锁定独立 Leader 表并校验启用状态。 + // Leader 的 RegionID 来自其 users 当前区域投影,不再相信 bd_leader_profiles 的历史快照。 leader, err := queryBDLeaderProfile(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.ParentLeaderUserID) if err != nil { return hostdomain.BDProfile{}, err @@ -298,6 +299,7 @@ func (r *Repository) CreateAgency(ctx context.Context, command hostservice.Creat } if command.ParentBDUserID > 0 { // 独立 Agency 不需要父级行锁;传入父级时可挂普通 BD 或 Leader 自身,但必须是有效身份且同区域。 + // 父级 RegionID 来自其 users 当前区域投影,避免角色表旧快照影响新 Agency 归属。 parentBD, err := queryActiveAgencyInviterProfile(ctx, tx, command.ParentBDUserID) if err != nil { return hostdomain.CreateAgencyResult{}, err diff --git a/services/user-service/internal/storage/mysql/host/applications.go b/services/user-service/internal/storage/mysql/host/applications.go index 8f43740e..89aede0b 100644 --- a/services/user-service/internal/storage/mysql/host/applications.go +++ b/services/user-service/internal/storage/mysql/host/applications.go @@ -37,8 +37,8 @@ func (r *Repository) ApplyToAgency(ctx context.Context, command hostservice.Appl return hostdomain.AgencyApplication{}, xerr.New(xerr.InvalidArgument, "user region is required") } - // Agency 行必须锁住:加入开关、状态、区域是申请可创建性的条件, - // 如果后台同时禁用加入或迁移区域,这里必须和申请创建串行。 + // Agency 行必须锁住:加入开关、状态、拥有者当前区域是申请可创建性的条件, + // 如果后台同时禁用加入或用户迁移区域,这里必须和申请创建串行。 agency, err := queryAgency(ctx, tx, "WHERE agency_id = ? FOR UPDATE", command.AgencyID) if err != nil { return hostdomain.AgencyApplication{}, err @@ -129,13 +129,13 @@ func (r *Repository) ReviewAgencyApplication(ctx context.Context, command hostse return hostdomain.ReviewAgencyApplicationResult{}, xerr.New(xerr.Conflict, "agency is not active") } - // 申请创建后用户区域可能被后台调整;通过前重新锁 users 行校验, - // 确保最终成员关系的区域仍然和申请、Agency 三方一致。 + // 申请创建后申请人或 Agency 拥有者区域都可能被后台调整;通过前重新锁申请人 users 行, + // 并用 Agency 当前拥有者区域校验,申请表不再保存区域快照。 regionID, err := r.userRegion(ctx, tx, application.ApplicantUserID, "FOR UPDATE") if err != nil { return hostdomain.ReviewAgencyApplicationResult{}, err } - if regionID != application.RegionID || regionID != agency.RegionID { + if regionID != agency.RegionID { return hostdomain.ReviewAgencyApplicationResult{}, xerr.New(xerr.PermissionDenied, "application region no longer matches") } @@ -162,7 +162,7 @@ func (r *Repository) ReviewAgencyApplication(ctx context.Context, command hostse } // host_profile 是用户成为主播的长期身份事实;成员关系是当前 Agency 归属事实。 // 两者和申请审核结果必须原子提交,否则列表页和身份页会看到不一致状态。 - hostProfile, created, err := ensureHostProfileForMembership(ctx, tx, application.ApplicantUserID, application.RegionID, command.MembershipID, application.AgencyID, hostdomain.HostSourceApplication, command.NowMs) + hostProfile, created, err := ensureHostProfileForMembership(ctx, tx, application.ApplicantUserID, regionID, command.MembershipID, application.AgencyID, hostdomain.HostSourceApplication, command.NowMs) if err != nil { return hostdomain.ReviewAgencyApplicationResult{}, err } @@ -170,7 +170,7 @@ func (r *Repository) ReviewAgencyApplication(ctx context.Context, command hostse MembershipID: command.MembershipID, AgencyID: application.AgencyID, HostUserID: application.ApplicantUserID, - RegionID: application.RegionID, + RegionID: regionID, MembershipType: hostdomain.MembershipTypeMember, Status: hostdomain.MembershipStatusActive, JoinedAtMs: command.NowMs, diff --git a/services/user-service/internal/storage/mysql/host/common.go b/services/user-service/internal/storage/mysql/host/common.go index 6b162793..5b066b43 100644 --- a/services/user-service/internal/storage/mysql/host/common.go +++ b/services/user-service/internal/storage/mysql/host/common.go @@ -6,6 +6,7 @@ import ( "context" "database/sql" "errors" + "fmt" "strings" mysqldriver "github.com/go-sql-driver/mysql" @@ -27,10 +28,17 @@ func New(db *sql.DB) *Repository { } const ( + coinSellerProfileColumns = ` + user_id, status, merchant_asset_type, + created_by_user_id, created_at_ms, updated_at_ms` +) + +var ( // 列清单集中维护,保证查询函数和结果回放使用同一组投影字段。 // 可空外键在存储边界统一转成 0,让领域结构体保持普通值类型。 - hostProfileColumns = ` - user_id, status, region_id, COALESCE(current_agency_id, 0), + // 角色表不再保存区域;领域读取统一从对应 users 行投影当前区域。 + hostProfileColumns = fmt.Sprintf(` + user_id, status, %s, COALESCE(current_agency_id, 0), COALESCE(current_membership_id, 0), COALESCE(( SELECT owner_user_id @@ -40,32 +48,29 @@ const ( AND agencies.status = 'active' LIMIT 1 ), 0), - source, first_became_host_at_ms, created_at_ms, updated_at_ms` - agencyColumns = ` - agency_id, owner_user_id, region_id, parent_bd_user_id, name, status, - join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms` - bdProfileColumns = ` - user_id, role, region_id, COALESCE(parent_leader_user_id, 0), status, - created_by_user_id, created_at_ms, updated_at_ms` - bdLeaderProfileColumns = ` - user_id, 'bd_leader', region_id, 0, status, - created_by_user_id, created_at_ms, updated_at_ms` - coinSellerProfileColumns = ` - user_id, status, merchant_asset_type, - created_by_user_id, created_at_ms, updated_at_ms` - agencyMembershipColumns = ` - membership_id, agency_id, host_user_id, region_id, membership_type, status, + source, first_became_host_at_ms, created_at_ms, updated_at_ms`, userRegionProjection("host_profiles", "user_id")) + agencyColumns = fmt.Sprintf(` + agency_id, owner_user_id, %s, parent_bd_user_id, name, status, + join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms`, userRegionProjection("agencies", "owner_user_id")) + bdProfileColumns = fmt.Sprintf(` + user_id, role, %s, COALESCE(parent_leader_user_id, 0), status, + created_by_user_id, created_at_ms, updated_at_ms`, userRegionProjection("bd_profiles", "user_id")) + bdLeaderProfileColumns = fmt.Sprintf(` + user_id, 'bd_leader', %s, 0, status, + created_by_user_id, created_at_ms, updated_at_ms`, userRegionProjection("bd_leader_profiles", "user_id")) + agencyMembershipColumns = fmt.Sprintf(` + membership_id, agency_id, host_user_id, %s, membership_type, status, joined_at_ms, COALESCE(ended_at_ms, 0), COALESCE(ended_by_user_id, 0), - ended_reason, created_at_ms, updated_at_ms` - agencyApplicationColumns = ` - application_id, command_id, applicant_user_id, agency_id, region_id, status, + ended_reason, created_at_ms, updated_at_ms`, userRegionProjection("agency_memberships", "host_user_id")) + agencyApplicationColumns = fmt.Sprintf(` + application_id, command_id, applicant_user_id, agency_id, %s, status, COALESCE(reviewed_by_user_id, 0), review_reason, COALESCE(reviewed_at_ms, 0), - created_at_ms, updated_at_ms` - roleInvitationColumns = ` + created_at_ms, updated_at_ms`, userRegionProjection("agency_applications", "applicant_user_id")) + roleInvitationColumns = fmt.Sprintf(` invitation_id, command_id, invitation_type, status, inviter_user_id, - inviter_bd_user_id, target_user_id, region_id, agency_name, parent_bd_user_id, + inviter_bd_user_id, target_user_id, %s, agency_name, parent_bd_user_id, COALESCE(parent_leader_user_id, 0), COALESCE(processed_by_user_id, 0), - process_reason, COALESCE(processed_at_ms, 0), created_at_ms, updated_at_ms` + process_reason, COALESCE(processed_at_ms, 0), created_at_ms, updated_at_ms`, userRegionProjection("role_invitations", "target_user_id")) ) type sqlQueryer interface { @@ -86,7 +91,32 @@ func nullableRegionID(value int64) any { return value } -func (r *Repository) userRegion(ctx context.Context, q sqlQueryer, userID int64, lockClause string) (int64, error) { +func userRegionProjection(tableName string, userIDColumn string) string { + return fmt.Sprintf(`COALESCE(( + SELECT u.region_id + FROM users u + WHERE u.app_code = %s.app_code + AND u.user_id = %s.%s + LIMIT 1 + ), 0)`, tableName, tableName, userIDColumn) +} + +func userRegionValue(ctx context.Context, q sqlQueryer, userID int64, lockClause string) (int64, error) { + // 只读取 users 当前区域,不校验用户状态;用于历史邀请的发起人区域对齐, + // 避免旧库残留的邀请区域快照继续参与判断,又不额外改变“邀请已发出后可处理”的状态语义。 + query := "SELECT COALESCE(region_id, 0) FROM users WHERE app_code = ? AND user_id = ?" + if strings.TrimSpace(lockClause) != "" { + query += " " + lockClause + } + var regionID int64 + err := q.QueryRowContext(ctx, query, appcode.FromContext(ctx), userID).Scan(®ionID) + if err == sql.ErrNoRows { + return 0, xerr.New(xerr.NotFound, "user not found") + } + return regionID, err +} + +func currentUserRegion(ctx context.Context, q sqlQueryer, userID int64, lockClause string) (int64, error) { // Host 领域不拥有 users 表,但所有角色流转都依赖用户当前有效区域。 // 调用方需要串行化用户身份变化时传入 FOR UPDATE 来锁住 users 行。 query := "SELECT COALESCE(region_id, 0), status FROM users WHERE app_code = ? AND user_id = ?" @@ -108,6 +138,10 @@ func (r *Repository) userRegion(ctx context.Context, q sqlQueryer, userID int64, return regionID, nil } +func (r *Repository) userRegion(ctx context.Context, q sqlQueryer, userID int64, lockClause string) (int64, error) { + return currentUserRegion(ctx, q, userID, lockClause) +} + func (r *Repository) userCountry(ctx context.Context, q sqlQueryer, userID int64, lockClause string) (string, error) { // App 侧 Agency 推荐按用户当前国家收敛;国家来自用户资料事实,不能由客户端传入。 query := "SELECT COALESCE(country, ''), status FROM users WHERE app_code = ? AND user_id = ?" diff --git a/services/user-service/internal/storage/mysql/host/invitations.go b/services/user-service/internal/storage/mysql/host/invitations.go index ae466c76..6e15cb82 100644 --- a/services/user-service/internal/storage/mysql/host/invitations.go +++ b/services/user-service/internal/storage/mysql/host/invitations.go @@ -69,7 +69,7 @@ func (r *Repository) InviteAgency(ctx context.Context, command hostservice.Invit InviterUserID: command.InviterUserID, InviterBDUserID: inviter.UserID, TargetUserID: command.TargetUserID, - RegionID: inviter.RegionID, + RegionID: targetRegionID, AgencyName: command.AgencyName, ParentBDUserID: inviter.UserID, ParentLeaderUserID: parentLeaderUserID, @@ -175,7 +175,7 @@ func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDC InviterUserID: command.InviterUserID, InviterBDUserID: inviter.UserID, TargetUserID: command.TargetUserID, - RegionID: inviter.RegionID, + RegionID: targetRegionID, ParentLeaderUserID: inviter.UserID, ProcessedByUserID: command.InviterUserID, ProcessReason: "direct_invite", @@ -335,12 +335,17 @@ func (r *Repository) ProcessRoleInvitation(ctx context.Context, command hostserv } func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, command hostservice.ProcessRoleInvitationCommand, invitation *hostdomain.RoleInvitation, result *hostdomain.ProcessRoleInvitationResult) (bool, error) { - // 邀请可能放置一段时间后才被接受,所以必须用当前用户区域再校验一次。 - regionID, err := r.userRegion(ctx, tx, invitation.TargetUserID, "FOR UPDATE") + // 邀请可能放置一段时间后才被接受,所以目标用户和邀请 BD 都用 users 当前区域重新对齐。 + // 邀请表不再保存区域快照,旧库残留的 region_id 也不能参与是否可接受的业务判断。 + targetRegionID, err := r.userRegion(ctx, tx, invitation.TargetUserID, "FOR UPDATE") if err != nil { return false, err } - if regionID != invitation.RegionID { + inviterRegionID, err := userRegionValue(ctx, tx, invitation.InviterBDUserID, "") + if err != nil { + return false, err + } + if targetRegionID != inviterRegionID { return false, xerr.New(xerr.PermissionDenied, "target region no longer matches invitation") } // 目标用户在邀请创建后可能已经加入其他 Agency;接受前必须再次阻断有效归属。 @@ -361,7 +366,7 @@ func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, com agency := hostdomain.Agency{ AgencyID: command.AgencyID, OwnerUserID: invitation.TargetUserID, - RegionID: invitation.RegionID, + RegionID: targetRegionID, ParentBDUserID: invitation.ParentBDUserID, Name: invitation.AgencyName, Status: hostdomain.AgencyStatusActive, @@ -375,7 +380,7 @@ func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, com if err := insertAgency(ctx, tx, agency); err != nil { return false, mapHostDuplicateError(err) } - hostProfile, hostProfileCreated, err := ensureHostProfileForMembership(ctx, tx, invitation.TargetUserID, invitation.RegionID, command.MembershipID, agency.AgencyID, hostdomain.HostSourceAgencyInvitation, command.NowMs) + hostProfile, hostProfileCreated, err := ensureHostProfileForMembership(ctx, tx, invitation.TargetUserID, targetRegionID, command.MembershipID, agency.AgencyID, hostdomain.HostSourceAgencyInvitation, command.NowMs) if err != nil { return false, err } @@ -383,7 +388,7 @@ func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, com MembershipID: command.MembershipID, AgencyID: agency.AgencyID, HostUserID: invitation.TargetUserID, - RegionID: invitation.RegionID, + RegionID: targetRegionID, MembershipType: hostdomain.MembershipTypeOwner, Status: hostdomain.MembershipStatusActive, JoinedAtMs: command.NowMs, @@ -400,12 +405,16 @@ func (r *Repository) acceptAgencyInvitation(ctx context.Context, tx *sql.Tx, com } func (r *Repository) acceptBDInvitation(ctx context.Context, tx *sql.Tx, command hostservice.ProcessRoleInvitationCommand, invitation *hostdomain.RoleInvitation, result *hostdomain.ProcessRoleInvitationResult) error { - // BD 邀请同样以当前用户区域为准;跨区调整后不能接受旧区域邀请。 - regionID, err := r.userRegion(ctx, tx, invitation.TargetUserID, "FOR UPDATE") + // BD 邀请同样以目标用户和发起 Leader 的 users 当前区域为准;旧邀请快照不能继续授权跨区接受。 + targetRegionID, err := r.userRegion(ctx, tx, invitation.TargetUserID, "FOR UPDATE") if err != nil { return err } - if regionID != invitation.RegionID { + leaderRegionID, err := userRegionValue(ctx, tx, invitation.InviterBDUserID, "") + if err != nil { + return err + } + if targetRegionID != leaderRegionID { return xerr.New(xerr.PermissionDenied, "target region no longer matches invitation") } // BD 身份是独立角色事实,不依赖 host_profile;任何已有普通 BD 行都不能重复插入。 @@ -417,7 +426,7 @@ func (r *Repository) acceptBDInvitation(ctx context.Context, tx *sql.Tx, command bd := hostdomain.BDProfile{ UserID: invitation.TargetUserID, Role: hostdomain.BDRoleBD, - RegionID: invitation.RegionID, + RegionID: targetRegionID, ParentLeaderUserID: invitation.ParentLeaderUserID, Status: hostdomain.BDStatusActive, CreatedByUserID: invitation.InviterUserID, diff --git a/services/user-service/internal/storage/mysql/host/queries.go b/services/user-service/internal/storage/mysql/host/queries.go index eada8c48..796ddad3 100644 --- a/services/user-service/internal/storage/mysql/host/queries.go +++ b/services/user-service/internal/storage/mysql/host/queries.go @@ -28,7 +28,7 @@ func (r *Repository) SearchAgencies(ctx context.Context, command hostservice.Sea SELECT a.agency_id, a.owner_user_id, - a.region_id, + COALESCE(owner.region_id, 0), a.parent_bd_user_id, a.name, a.status, @@ -62,7 +62,7 @@ func (r *Repository) SearchAgencies(ctx context.Context, command hostservice.Sea GROUP BY a.agency_id, a.owner_user_id, - a.region_id, + owner.region_id, a.parent_bd_user_id, a.name, a.status, diff --git a/services/user-service/internal/storage/mysql/host/writes.go b/services/user-service/internal/storage/mysql/host/writes.go index b055da46..87faf4d9 100644 --- a/services/user-service/internal/storage/mysql/host/writes.go +++ b/services/user-service/internal/storage/mysql/host/writes.go @@ -99,10 +99,10 @@ func insertHostProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.HostP // 这里只用于首次创建身份;成员关系变化走 updateHostCurrentMembership。 _, err := tx.ExecContext(ctx, ` INSERT INTO host_profiles ( - app_code, user_id, status, region_id, current_agency_id, current_membership_id, source, + app_code, user_id, status, current_agency_id, current_membership_id, source, first_became_host_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.FromContext(ctx), profile.UserID, profile.Status, profile.RegionID, nullableRegionID(profile.CurrentAgencyID), nullableRegionID(profile.CurrentMembershipID), profile.Source, profile.FirstBecameHostAtMs, profile.CreatedAtMs, profile.UpdatedAtMs) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.FromContext(ctx), profile.UserID, profile.Status, nullableRegionID(profile.CurrentAgencyID), nullableRegionID(profile.CurrentMembershipID), profile.Source, profile.FirstBecameHostAtMs, profile.CreatedAtMs, profile.UpdatedAtMs) return err } @@ -111,9 +111,9 @@ func updateHostCurrentMembership(ctx context.Context, tx *sql.Tx, profile hostdo // 状态、来源、首次成为主播时间属于身份历史,不在这里重写。 _, err := tx.ExecContext(ctx, ` UPDATE host_profiles - SET region_id = ?, current_agency_id = ?, current_membership_id = ?, updated_at_ms = ? + SET current_agency_id = ?, current_membership_id = ?, updated_at_ms = ? WHERE app_code = ? AND user_id = ? - `, profile.RegionID, nullableRegionID(profile.CurrentAgencyID), nullableRegionID(profile.CurrentMembershipID), profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID) + `, nullableRegionID(profile.CurrentAgencyID), nullableRegionID(profile.CurrentMembershipID), profile.UpdatedAtMs, appcode.FromContext(ctx), profile.UserID) return err } @@ -130,10 +130,10 @@ func insertAgency(ctx context.Context, tx *sql.Tx, agency hostdomain.Agency) err // 第一阶段只有接受 Agency 邀请时会创建 Agency。 _, err := tx.ExecContext(ctx, ` INSERT INTO agencies ( - app_code, agency_id, owner_user_id, region_id, parent_bd_user_id, name, status, + app_code, agency_id, owner_user_id, parent_bd_user_id, name, status, join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.FromContext(ctx), agency.AgencyID, agency.OwnerUserID, agency.RegionID, agency.ParentBDUserID, agency.Name, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.FromContext(ctx), agency.AgencyID, agency.OwnerUserID, agency.ParentBDUserID, agency.Name, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs) return err } @@ -151,10 +151,10 @@ func insertBDProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.BDProfi // 普通 BD 身份行和 host_profile 相互独立;BD 用户不会自动成为 Host,也不会隐式拥有 BD Leader 权限。 _, err := tx.ExecContext(ctx, ` INSERT INTO bd_profiles ( - app_code, user_id, role, region_id, parent_leader_user_id, status, + app_code, user_id, role, parent_leader_user_id, status, created_by_user_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.FromContext(ctx), profile.UserID, profile.Role, profile.RegionID, nullableRegionID(profile.ParentLeaderUserID), profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.FromContext(ctx), profile.UserID, profile.Role, nullableRegionID(profile.ParentLeaderUserID), profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) return err } @@ -162,10 +162,10 @@ func insertBDLeaderProfile(ctx context.Context, tx *sql.Tx, profile hostdomain.B // BD Leader 使用独立表承载负责人身份,避免和普通 BD 工资、状态、团队归属混成同一条事实。 _, err := tx.ExecContext(ctx, ` INSERT INTO bd_leader_profiles ( - app_code, user_id, region_id, status, + app_code, user_id, status, created_by_user_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?) - `, appcode.FromContext(ctx), profile.UserID, profile.RegionID, profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) + ) VALUES (?, ?, ?, ?, ?, ?) + `, appcode.FromContext(ctx), profile.UserID, profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) return err } @@ -214,10 +214,10 @@ func insertAgencyMembership(ctx context.Context, tx *sql.Tx, membership hostdoma // 成员关系是 Agency 关系历史行;有效且当前的关系也会冗余到身份表。 _, err := tx.ExecContext(ctx, ` INSERT INTO agency_memberships ( - app_code, membership_id, agency_id, host_user_id, region_id, membership_type, status, + app_code, membership_id, agency_id, host_user_id, membership_type, status, joined_at_ms, ended_at_ms, ended_by_user_id, ended_reason, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.FromContext(ctx), membership.MembershipID, membership.AgencyID, membership.HostUserID, membership.RegionID, membership.MembershipType, membership.Status, membership.JoinedAtMs, nullableRegionID(membership.EndedAtMs), nullableRegionID(membership.EndedByUserID), membership.EndedReason, membership.CreatedAtMs, membership.UpdatedAtMs) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.FromContext(ctx), membership.MembershipID, membership.AgencyID, membership.HostUserID, membership.MembershipType, membership.Status, membership.JoinedAtMs, nullableRegionID(membership.EndedAtMs), nullableRegionID(membership.EndedByUserID), membership.EndedReason, membership.CreatedAtMs, membership.UpdatedAtMs) return err } @@ -232,13 +232,13 @@ func endAgencyMembership(ctx context.Context, tx *sql.Tx, membership hostdomain. } func insertAgencyApplication(ctx context.Context, tx *sql.Tx, application hostdomain.AgencyApplication) error { - // 申请记录保留申请人、Agency 和区域快照,审核时基于这份快照再校验当前事实。 + // 申请记录只保留申请人和 Agency;区域每次读取时都从申请人 users 行投影。 _, err := tx.ExecContext(ctx, ` INSERT INTO agency_applications ( - app_code, application_id, command_id, applicant_user_id, agency_id, region_id, status, + app_code, application_id, command_id, applicant_user_id, agency_id, status, reviewed_by_user_id, review_reason, reviewed_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.FromContext(ctx), application.ApplicationID, application.CommandID, application.ApplicantUserID, application.AgencyID, application.RegionID, application.Status, nullableRegionID(application.ReviewedByUserID), application.ReviewReason, nullableRegionID(application.ReviewedAtMs), application.CreatedAtMs, application.UpdatedAtMs) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.FromContext(ctx), application.ApplicationID, application.CommandID, application.ApplicantUserID, application.AgencyID, application.Status, nullableRegionID(application.ReviewedByUserID), application.ReviewReason, nullableRegionID(application.ReviewedAtMs), application.CreatedAtMs, application.UpdatedAtMs) return err } @@ -257,11 +257,11 @@ func insertRoleInvitation(ctx context.Context, tx *sql.Tx, invitation hostdomain _, err := tx.ExecContext(ctx, ` INSERT INTO role_invitations ( app_code, invitation_id, command_id, invitation_type, status, inviter_user_id, - inviter_bd_user_id, target_user_id, region_id, agency_name, parent_bd_user_id, + inviter_bd_user_id, target_user_id, agency_name, parent_bd_user_id, parent_leader_user_id, processed_by_user_id, process_reason, processed_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, appcode.FromContext(ctx), invitation.InvitationID, invitation.CommandID, invitation.InvitationType, invitation.Status, invitation.InviterUserID, invitation.InviterBDUserID, invitation.TargetUserID, invitation.RegionID, invitation.AgencyName, invitation.ParentBDUserID, nullableRegionID(invitation.ParentLeaderUserID), nullableRegionID(invitation.ProcessedByUserID), invitation.ProcessReason, nullableRegionID(invitation.ProcessedAtMs), invitation.CreatedAtMs, invitation.UpdatedAtMs) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, appcode.FromContext(ctx), invitation.InvitationID, invitation.CommandID, invitation.InvitationType, invitation.Status, invitation.InviterUserID, invitation.InviterBDUserID, invitation.TargetUserID, invitation.AgencyName, invitation.ParentBDUserID, nullableRegionID(invitation.ParentLeaderUserID), nullableRegionID(invitation.ProcessedByUserID), invitation.ProcessReason, nullableRegionID(invitation.ProcessedAtMs), invitation.CreatedAtMs, invitation.UpdatedAtMs) return err } diff --git a/services/user-service/internal/storage/mysql/identity/identity.go b/services/user-service/internal/storage/mysql/identity/identity.go index 5c69a1a3..408d81e8 100644 --- a/services/user-service/internal/storage/mysql/identity/identity.go +++ b/services/user-service/internal/storage/mysql/identity/identity.go @@ -12,16 +12,19 @@ import ( ) func (r *Repository) GetUserIdentity(ctx context.Context, userID int64) (userdomain.Identity, error) { - // 直接读取 users 当前快照;是否懒过期由 service 层统一判断。 + identity, err := r.queryActiveIdentity(ctx, "du.user_id = ?", userID) + if err == nil { + return identity, nil + } + if err != sql.ErrNoRows { + return userdomain.Identity{}, err + } + + // 旧数据如果缺少 user_display_user_ids active 行,仍回退 users 快照,避免身份表补数据前直接阻断用户。 user, err := userstorage.New(r.db).GetUser(ctx, userID) if err != nil { return userdomain.Identity{}, err } - if user.CurrentDisplayUserID == "" { - // 理论上用户创建事务会保证短号存在,该分支保护脏数据。 - return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDNotFound, "display_user_id not found") - } - return user.EffectiveIdentity(), nil } @@ -34,16 +37,112 @@ func (r *Repository) ResolveDisplayUserID(ctx context.Context, displayUserID str return userdomain.Identity{}, err } - user, err := userstorage.QueryUser(ctx, r.db, "WHERE current_display_user_id = ?", displayUserID) + identity, err := r.queryActiveIdentity(ctx, "du.display_user_id = ? AND (du.expires_at_ms IS NULL OR du.expires_at_ms = 0 OR du.expires_at_ms > ?)", displayUserID, nowMs) if err == sql.ErrNoRows { - // held 默认号和过期靓号都不会匹配 current_display_user_id。 + // held 默认号、释放号和过期靓号都不能作为当前展示号解析。 return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDNotFound, "display_user_id not found") } if err != nil { return userdomain.Identity{}, err } + if err := r.repairUserCurrentIdentity(ctx, identity, nowMs); err != nil { + return userdomain.Identity{}, err + } - return user.EffectiveIdentity(), nil + return identity, nil +} + +func (r *Repository) queryActiveIdentity(ctx context.Context, clause string, args ...any) (userdomain.Identity, error) { + // user_display_user_ids 的 active 行是展示号归属事实,users.current_display_user_id 只是高频读取快照。 + // 解析入口优先读取事实表,可以容忍历史脚本或异常中断导致 users 快照暂时没有同步到最新靓号。 + appCode := appcode.FromContext(ctx) + queryArgs := []any{ + string(userdomain.DisplayUserIDKindPretty), + appCode, + string(userdomain.DisplayUserIDStatusActive), + } + queryArgs = append(queryArgs, args...) + row := r.db.QueryRowContext(ctx, ` + SELECT + du.app_code, + du.user_id, + du.display_user_id, + u.default_display_user_id, + du.display_user_id_kind, + COALESCE(du.expires_at_ms, 0), + COALESCE(( + SELECT pdi.pretty_id + FROM pretty_display_user_id_leases pdl + LEFT JOIN pretty_display_ids pdi + ON pdi.app_code = pdl.app_code + AND pdi.assigned_lease_id = pdl.lease_id + AND pdi.assigned_user_id = pdl.user_id + WHERE pdl.app_code = du.app_code + AND pdl.user_id = du.user_id + AND pdl.display_user_id = du.display_user_id + AND pdl.status = 'active' + ORDER BY pdl.starts_at_ms DESC + LIMIT 1 + ), ''), + CASE WHEN du.display_user_id_kind = ? THEN du.display_user_id ELSE '' END, + du.status + FROM user_display_user_ids du + INNER JOIN users u + ON u.app_code = du.app_code + AND u.user_id = du.user_id + WHERE du.app_code = ? + AND du.status = ? + AND `+clause+` + LIMIT 1 + `, queryArgs...) + + var identity userdomain.Identity + var kind string + var status string + if err := row.Scan( + &identity.AppCode, + &identity.UserID, + &identity.DisplayUserID, + &identity.DefaultDisplayUserID, + &kind, + &identity.DisplayUserIDExpiresAtMs, + &identity.PrettyID, + &identity.PrettyDisplayUserID, + &status, + ); err != nil { + return userdomain.Identity{}, err + } + identity.DisplayUserIDKind = userdomain.DisplayUserIDKind(kind) + identity.Status = userdomain.DisplayUserIDStatus(status) + return identity, nil +} + +func (r *Repository) repairUserCurrentIdentity(ctx context.Context, identity userdomain.Identity, nowMs int64) error { + // users.current_display_user_id 是读取快照,真正归属以 user_display_user_ids active 行为准。 + // 读路径发现快照滞后时做幂等同步,保证后续 GetUser、token 和 H5 账号确认都返回同一个当前展示号。 + _, err := r.db.ExecContext(ctx, ` + UPDATE users + SET current_display_user_id = ?, + current_display_user_id_kind = ?, + current_display_user_id_expires_at_ms = ?, + updated_at_ms = GREATEST(updated_at_ms, ?) + WHERE app_code = ? + AND user_id = ? + AND ( + current_display_user_id <> ? + OR current_display_user_id_kind <> ? + OR COALESCE(current_display_user_id_expires_at_ms, 0) <> ? + ) + `, identity.DisplayUserID, string(identity.DisplayUserIDKind), nullableExpiresAt(identity.DisplayUserIDExpiresAtMs), nowMs, + appcode.Normalize(identity.AppCode), identity.UserID, identity.DisplayUserID, string(identity.DisplayUserIDKind), identity.DisplayUserIDExpiresAtMs) + return err +} + +func nullableExpiresAt(value int64) any { + if value <= 0 { + return nil + } + return value } // ChangeDisplayUserID 修改默认短号;有 active 靓号时拒绝,避免过期恢复目标不清晰。 diff --git a/services/user-service/internal/testutil/mysqltest/mysqltest.go b/services/user-service/internal/testutil/mysqltest/mysqltest.go index 59448183..21a66711 100644 --- a/services/user-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/user-service/internal/testutil/mysqltest/mysqltest.go @@ -663,17 +663,16 @@ func (r *Repository) PutHostProfile(profile hostdomain.HostProfile) { _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO host_profiles ( - user_id, status, region_id, current_agency_id, current_membership_id, source, + user_id, status, current_agency_id, current_membership_id, source, first_became_host_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE status = VALUES(status), - region_id = VALUES(region_id), current_agency_id = VALUES(current_agency_id), current_membership_id = VALUES(current_membership_id), source = VALUES(source), updated_at_ms = VALUES(updated_at_ms) - `, profile.UserID, profile.Status, profile.RegionID, nullableInt64(profile.CurrentAgencyID), nullableInt64(profile.CurrentMembershipID), profile.Source, profile.FirstBecameHostAtMs, profile.CreatedAtMs, profile.UpdatedAtMs) + `, profile.UserID, profile.Status, nullableInt64(profile.CurrentAgencyID), nullableInt64(profile.CurrentMembershipID), profile.Source, profile.FirstBecameHostAtMs, profile.CreatedAtMs, profile.UpdatedAtMs) if err != nil { r.t.Fatalf("seed host profile %d failed: %v", profile.UserID, err) } @@ -701,13 +700,12 @@ func (r *Repository) PutBDProfile(profile hostdomain.BDProfile) { if profile.Role == hostdomain.BDRoleLeader { _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO bd_leader_profiles ( - user_id, region_id, status, created_by_user_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?) + user_id, status, created_by_user_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE - region_id = VALUES(region_id), status = VALUES(status), updated_at_ms = VALUES(updated_at_ms) - `, profile.UserID, profile.RegionID, profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) + `, profile.UserID, profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) if err != nil { r.t.Fatalf("seed bd leader profile %d failed: %v", profile.UserID, err) } @@ -716,16 +714,15 @@ func (r *Repository) PutBDProfile(profile hostdomain.BDProfile) { _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO bd_profiles ( - user_id, role, region_id, parent_leader_user_id, status, + user_id, role, parent_leader_user_id, status, created_by_user_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE role = VALUES(role), - region_id = VALUES(region_id), parent_leader_user_id = VALUES(parent_leader_user_id), status = VALUES(status), updated_at_ms = VALUES(updated_at_ms) - `, profile.UserID, profile.Role, profile.RegionID, nullableInt64(profile.ParentLeaderUserID), profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) + `, profile.UserID, profile.Role, nullableInt64(profile.ParentLeaderUserID), profile.Status, profile.CreatedByUserID, profile.CreatedAtMs, profile.UpdatedAtMs) if err != nil { r.t.Fatalf("seed bd profile %d failed: %v", profile.UserID, err) } @@ -752,19 +749,18 @@ func (r *Repository) PutAgency(agency hostdomain.Agency) { _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO agencies ( - agency_id, owner_user_id, region_id, parent_bd_user_id, name, status, + agency_id, owner_user_id, parent_bd_user_id, name, status, join_enabled, max_hosts, created_by_user_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE owner_user_id = VALUES(owner_user_id), - region_id = VALUES(region_id), parent_bd_user_id = VALUES(parent_bd_user_id), name = VALUES(name), status = VALUES(status), join_enabled = VALUES(join_enabled), max_hosts = VALUES(max_hosts), updated_at_ms = VALUES(updated_at_ms) - `, agency.AgencyID, agency.OwnerUserID, agency.RegionID, agency.ParentBDUserID, agency.Name, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs) + `, agency.AgencyID, agency.OwnerUserID, agency.ParentBDUserID, agency.Name, agency.Status, agency.JoinEnabled, agency.MaxHosts, agency.CreatedByUserID, agency.CreatedAtMs, agency.UpdatedAtMs) if err != nil { r.t.Fatalf("seed agency %d failed: %v", agency.AgencyID, err) } diff --git a/services/user-service/internal/transport/grpc/server_test.go b/services/user-service/internal/transport/grpc/server_test.go index 5f603b20..0a7d4c40 100644 --- a/services/user-service/internal/transport/grpc/server_test.go +++ b/services/user-service/internal/transport/grpc/server_test.go @@ -298,9 +298,56 @@ func TestResolveDisplayUserIDErrorCarriesReason(t *testing.T) { server := grpcserver.NewServer(authservice.New(authservice.Config{}), newUserService(mysqltest.NewRepository(t))) _, err := server.ResolveDisplayUserID(context.Background(), &userv1.ResolveDisplayUserIDRequest{ - DisplayUserId: "bad", + DisplayUserId: "bad!", }) if got := xerr.ReasonFromGRPC(err); got != xerr.DisplayUserIDInvalid { t.Fatalf("reason mismatch: got %s", got) } } + +func TestResolveDisplayUserIDAllowsPrettyDisplayID(t *testing.T) { + // 真实解析链路必须允许当前 active 靓号作为入参,后台和 H5 不应该要求调用方先知道内部 user_id。 + repository := mysqltest.NewRepository(t) + repository.PutUser(userdomain.User{ + AppCode: "lalu", + UserID: 123456, + DefaultDisplayUserID: "123456", + CurrentDisplayUserID: "123456", + CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault, + Status: userdomain.StatusActive, + CreatedAtMs: 1000, + UpdatedAtMs: 1000, + }) + if _, err := repository.RawDB().ExecContext(context.Background(), ` + UPDATE user_display_user_ids + SET status = ?, updated_at_ms = ? + WHERE app_code = ? AND user_id = ? AND display_user_id = ? + `, string(userdomain.DisplayUserIDStatusHeld), int64(1001), "lalu", int64(123456), "123456"); err != nil { + t.Fatalf("mark default display_user_id held failed: %v", err) + } + if _, err := repository.RawDB().ExecContext(context.Background(), ` + INSERT INTO user_display_user_ids (app_code, display_user_id, user_id, display_user_id_kind, status, assigned_at_ms, activated_at_ms, created_at_ms, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `, "lalu", "1111cc1111", int64(123456), string(userdomain.DisplayUserIDKindPretty), string(userdomain.DisplayUserIDStatusActive), int64(1001), int64(1001), int64(1001), int64(1001)); err != nil { + t.Fatalf("seed active pretty display_user_id failed: %v", err) + } + server := grpcserver.NewServer(authservice.New(authservice.Config{}), newUserService(repository)) + + resp, err := server.ResolveDisplayUserID(context.Background(), &userv1.ResolveDisplayUserIDRequest{ + Meta: &userv1.RequestMeta{AppCode: "lalu", RequestId: "req-pretty-resolve"}, + DisplayUserId: "1111cc1111", + }) + if err != nil { + t.Fatalf("ResolveDisplayUserID should accept pretty display_user_id: %v", err) + } + if resp.GetIdentity().GetUserId() != 123456 || resp.GetIdentity().GetDisplayUserId() != "1111cc1111" || resp.GetIdentity().GetDisplayUserIdKind() != string(userdomain.DisplayUserIDKindPretty) { + t.Fatalf("pretty display_user_id identity mismatch: %+v", resp.GetIdentity()) + } + user, err := repository.GetUser(context.Background(), 123456) + if err != nil { + t.Fatalf("GetUser after pretty resolve failed: %v", err) + } + if user.CurrentDisplayUserID != "1111cc1111" || user.CurrentDisplayUserIDKind != userdomain.DisplayUserIDKindPretty { + t.Fatalf("users current display snapshot was not repaired: %+v", user) + } +} diff --git a/services/wallet-service/configs/config.docker.yaml b/services/wallet-service/configs/config.docker.yaml index 02840b98..7c43f231 100644 --- a/services/wallet-service/configs/config.docker.yaml +++ b/services/wallet-service/configs/config.docker.yaml @@ -52,12 +52,13 @@ google_play: http_timeout: "10s" consume_enabled: true mifapay: - # MiFaPay 是 H5 统一三方支付入口;容器默认打开功能但不写真实商户凭证,部署时从密钥环境注入。 + # MiFaPay 是 H5 统一三方支付入口;商家公钥只用于记录商户侧配置,真实下单签名必须继续使用商户私钥。 enabled: true - mer_account: "" - mer_no: "" + mer_account: "6000055d34f445418124ab7c0beb81a6" + mer_no: "10001910" + merchant_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVDVrlVS+HkfLx+56v1Fyfqj5Gd+DRkPIK4EOu+SMi+wtP4dwPBeXkrL1w+nSC0UFQ2Py690qdRu1FCSTNMWC8kfzddC4xlckJKXa9NoDyeJIN/Z5Za9Fg2znfqTee9BE8NnJk4EtmRwKJQaP8sCrYsIDv/bagywM9t25lKn2bmUOZKVVUtn/Xf5OBYDE6AVHB83J+HxX5VPLie+dbRdsRR+hoZs/vzLTNlZsTcrAnWUf7cqd9XNr39gXnXgdw61kzAC9RtSLbRPAo474F03xmLZKsL70vmBgex9/paljPxNnFVq1CPxmam5yzY0NtS6iXUlU7ttqAzBolSwPWla7QIDAQAB" private_key: "" - platform_public_key: "" + platform_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Kt1qR5jwf4xUVHqo2pLZW2Xb43P9c8BjMHeq4ih+u+8Qr5YtJa2EPv/sNoAQktjaUhsg9tJvtn1Pc46xIo3XeZiicdn91eNSdC6mZ8NxT6IJyyEr5x15o6hJORkiDbYg8k3rf40bZl8B6Oz6pEGPIYrmO8DVWN0Ak7qmWhHF5ePKusfLjy4TWEt2JmQGHAz+lfkDGaQzwtrC2kt4adYwcdpY78B4fH8Ssofr2fLuMQYl2QYWIV0lplAQicuTRhgWj4tora/MoK/LbKKFCthrElGvVJtial1QCfhOOUVgdjzGKdmuer8qiuseDraVIKYkAjuY9fGBWF+2N7mNIWOKwIDAQAB" api_base_url: "https://platformtest.xqdmipay.com" http_timeout: "10s" tron_grid: diff --git a/services/wallet-service/configs/config.tencent.example.yaml b/services/wallet-service/configs/config.tencent.example.yaml index 7b808afa..78202a5c 100644 --- a/services/wallet-service/configs/config.tencent.example.yaml +++ b/services/wallet-service/configs/config.tencent.example.yaml @@ -52,12 +52,13 @@ google_play: http_timeout: "10s" consume_enabled: true mifapay: - # MiFaPay 是 H5 统一三方支付入口;以下值是占位符,线上必须通过密钥系统或环境配置提供真实值。 + # MiFaPay 是 H5 统一三方支付入口;商户私钥必须通过密钥系统或环境配置提供,不能把商家公钥写进 private_key。 enabled: true - mer_account: "MIFAPAY_MER_ACCOUNT" - mer_no: "MIFAPAY_MER_NO" + mer_account: "6000055d34f445418124ab7c0beb81a6" + mer_no: "10001910" + merchant_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVDVrlVS+HkfLx+56v1Fyfqj5Gd+DRkPIK4EOu+SMi+wtP4dwPBeXkrL1w+nSC0UFQ2Py690qdRu1FCSTNMWC8kfzddC4xlckJKXa9NoDyeJIN/Z5Za9Fg2znfqTee9BE8NnJk4EtmRwKJQaP8sCrYsIDv/bagywM9t25lKn2bmUOZKVVUtn/Xf5OBYDE6AVHB83J+HxX5VPLie+dbRdsRR+hoZs/vzLTNlZsTcrAnWUf7cqd9XNr39gXnXgdw61kzAC9RtSLbRPAo474F03xmLZKsL70vmBgex9/paljPxNnFVq1CPxmam5yzY0NtS6iXUlU7ttqAzBolSwPWla7QIDAQAB" private_key: "MIFAPAY_RSA_PRIVATE_KEY_PKCS8" - platform_public_key: "MIFAPAY_PLATFORM_PUBLIC_KEY_X509" + platform_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Kt1qR5jwf4xUVHqo2pLZW2Xb43P9c8BjMHeq4ih+u+8Qr5YtJa2EPv/sNoAQktjaUhsg9tJvtn1Pc46xIo3XeZiicdn91eNSdC6mZ8NxT6IJyyEr5x15o6hJORkiDbYg8k3rf40bZl8B6Oz6pEGPIYrmO8DVWN0Ak7qmWhHF5ePKusfLjy4TWEt2JmQGHAz+lfkDGaQzwtrC2kt4adYwcdpY78B4fH8Ssofr2fLuMQYl2QYWIV0lplAQicuTRhgWj4tora/MoK/LbKKFCthrElGvVJtial1QCfhOOUVgdjzGKdmuer8qiuseDraVIKYkAjuY9fGBWF+2N7mNIWOKwIDAQAB" api_base_url: "https://platform.xqdmipay.com" http_timeout: "10s" tron_grid: diff --git a/services/wallet-service/configs/config.yaml b/services/wallet-service/configs/config.yaml index 4a319097..c47f61d2 100644 --- a/services/wallet-service/configs/config.yaml +++ b/services/wallet-service/configs/config.yaml @@ -52,12 +52,13 @@ google_play: http_timeout: "10s" consume_enabled: true mifapay: - # MiFaPay 是 H5 统一三方支付入口;本地默认打开功能但不配置真实商户凭证,缺凭证时 H5 不展示三方支付下单入口。 + # MiFaPay 是 H5 统一三方支付入口;商家公钥只用于记录商户侧配置,真实下单签名必须继续使用商户私钥。 enabled: true - mer_account: "" - mer_no: "" + mer_account: "6000055d34f445418124ab7c0beb81a6" + mer_no: "10001910" + merchant_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVDVrlVS+HkfLx+56v1Fyfqj5Gd+DRkPIK4EOu+SMi+wtP4dwPBeXkrL1w+nSC0UFQ2Py690qdRu1FCSTNMWC8kfzddC4xlckJKXa9NoDyeJIN/Z5Za9Fg2znfqTee9BE8NnJk4EtmRwKJQaP8sCrYsIDv/bagywM9t25lKn2bmUOZKVVUtn/Xf5OBYDE6AVHB83J+HxX5VPLie+dbRdsRR+hoZs/vzLTNlZsTcrAnWUf7cqd9XNr39gXnXgdw61kzAC9RtSLbRPAo474F03xmLZKsL70vmBgex9/paljPxNnFVq1CPxmam5yzY0NtS6iXUlU7ttqAzBolSwPWla7QIDAQAB" private_key: "" - platform_public_key: "" + platform_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Kt1qR5jwf4xUVHqo2pLZW2Xb43P9c8BjMHeq4ih+u+8Qr5YtJa2EPv/sNoAQktjaUhsg9tJvtn1Pc46xIo3XeZiicdn91eNSdC6mZ8NxT6IJyyEr5x15o6hJORkiDbYg8k3rf40bZl8B6Oz6pEGPIYrmO8DVWN0Ak7qmWhHF5ePKusfLjy4TWEt2JmQGHAz+lfkDGaQzwtrC2kt4adYwcdpY78B4fH8Ssofr2fLuMQYl2QYWIV0lplAQicuTRhgWj4tora/MoK/LbKKFCthrElGvVJtial1QCfhOOUVgdjzGKdmuer8qiuseDraVIKYkAjuY9fGBWF+2N7mNIWOKwIDAQAB" api_base_url: "https://platformtest.xqdmipay.com" http_timeout: "10s" tron_grid: @@ -68,7 +69,7 @@ tron_grid: http_timeout: "10s" external_recharge: usdt_trc20_enabled: true - usdt_trc20_address: "" + usdt_trc20_address: "cccccccccccc" mifapay_notify_url: "" mifapay_return_url: "" outbox_worker: diff --git a/services/wallet-service/internal/config/config.go b/services/wallet-service/internal/config/config.go index 6b8cd337..d7ce6bc3 100644 --- a/services/wallet-service/internal/config/config.go +++ b/services/wallet-service/internal/config/config.go @@ -115,6 +115,7 @@ type MifaPayConfig struct { Enabled bool `yaml:"enabled"` MerAccount string `yaml:"mer_account"` MerNo string `yaml:"mer_no"` + MerchantPublicKey string `yaml:"merchant_public_key"` PrivateKey string `yaml:"private_key"` PlatformPublicKey string `yaml:"platform_public_key"` APIBaseURL string `yaml:"api_base_url"` diff --git a/services/wallet-service/internal/config/config_test.go b/services/wallet-service/internal/config/config_test.go index 73516c24..e54e2657 100644 --- a/services/wallet-service/internal/config/config_test.go +++ b/services/wallet-service/internal/config/config_test.go @@ -1,6 +1,12 @@ package config -import "testing" +import ( + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "strings" + "testing" +) func TestLoadLocalEnablesOutboxMQ(t *testing.T) { cfg, err := Load("../../configs/config.yaml") @@ -40,3 +46,37 @@ func TestLoadTencentExampleSplitsRealtimeOutboxAndRaisesThroughput(t *testing.T) t.Fatalf("realtime outbox event types mismatch: %+v", cfg.RealtimeOutboxWorker.EventTypes) } } + +func TestLoadMifaPayMerchantConfig(t *testing.T) { + for _, path := range []string{ + "../../configs/config.yaml", + "../../configs/config.docker.yaml", + "../../configs/config.tencent.example.yaml", + } { + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load %s failed: %v", path, err) + } + if cfg.MifaPay.MerNo != "10001910" || cfg.MifaPay.MerAccount != "6000055d34f445418124ab7c0beb81a6" { + t.Fatalf("mifapay merchant identity mismatch in %s: %+v", path, cfg.MifaPay) + } + assertRSAPublicKey(t, path+" merchant_public_key", cfg.MifaPay.MerchantPublicKey) + assertRSAPublicKey(t, path+" platform_public_key", cfg.MifaPay.PlatformPublicKey) + } +} + +func assertRSAPublicKey(t *testing.T, name string, value string) { + t.Helper() + clean := strings.NewReplacer("\n", "", "\r", "", " ", "").Replace(strings.TrimSpace(value)) + raw, err := base64.StdEncoding.DecodeString(clean) + if err != nil { + t.Fatalf("%s should be base64 DER public key: %v", name, err) + } + key, err := x509.ParsePKIXPublicKey(raw) + if err != nil { + t.Fatalf("%s should parse as PKIX public key: %v", name, err) + } + if _, ok := key.(*rsa.PublicKey); !ok { + t.Fatalf("%s should be RSA public key, got %T", name, key) + } +} diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 96b9b686..bffdafc6 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -1947,6 +1947,187 @@ func TestTransferSalaryToCoinSellerRequiresConfiguredRate(t *testing.T) { } } +// TestH5RechargeOptionsFilterMethodsByUserCountry 验证 H5 支付方式严格按目标用户国家返回,同时按普通用户/币商区分商品档位。 +func TestH5RechargeOptionsFilterMethodsByUserCountry(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + svc.SetMifaPayClient(&fakeMifaPayClient{}) + normalProduct := repository.CreateH5RechargeProduct(ledger.RechargeAudienceNormal, 7100, 1_500_000, 120000) + sellerProduct := repository.CreateH5RechargeProduct(ledger.RechargeAudienceCoinSeller, 7100, 10_000_000, 880000) + + normalOptions, err := svc.ListH5RechargeOptions(context.Background(), ledger.H5RechargeOptionsQuery{ + AppCode: appcode.Default, + TargetUserID: 41001, + TargetRegionID: 7100, + TargetCountryCode: "SA", + AudienceType: ledger.RechargeAudienceNormal, + }) + if err != nil { + t.Fatalf("ListH5RechargeOptions normal failed: %v", err) + } + if len(normalOptions.Products) != 1 || normalOptions.Products[0].ProductID != normalProduct.ProductID { + t.Fatalf("normal h5 products must only include normal product: %+v", normalOptions.Products) + } + if !paymentMethodsContain(normalOptions.PaymentMethods, "SA", "Card", "MADA") || + !paymentMethodsContain(normalOptions.PaymentMethods, "SA", "Ewallet", "ApplePay") || + !paymentMethodsContain(normalOptions.PaymentMethods, "SA", "Ewallet", "STCPay") || + paymentMethodsContain(normalOptions.PaymentMethods, "ID", "BankTransfer", "BCA") { + t.Fatalf("SA options must only expose SA MiFaPay methods: %+v", normalOptions.PaymentMethods) + } + for _, method := range normalOptions.PaymentMethods { + if method.CountryCode != "SA" && method.CountryCode != "GLOBAL" { + t.Fatalf("payment method country leaked for SA user: %+v", method) + } + } + + sellerOptions, err := svc.ListH5RechargeOptions(context.Background(), ledger.H5RechargeOptionsQuery{ + AppCode: appcode.Default, + TargetUserID: 41002, + TargetRegionID: 7100, + TargetCountryCode: "SA", + AudienceType: ledger.RechargeAudienceCoinSeller, + }) + if err != nil { + t.Fatalf("ListH5RechargeOptions coin seller failed: %v", err) + } + if len(sellerOptions.Products) != 1 || sellerOptions.Products[0].ProductID != sellerProduct.ProductID { + t.Fatalf("coin seller h5 products must only include seller product: %+v", sellerOptions.Products) + } + if !paymentMethodsContain(sellerOptions.PaymentMethods, "SA", "Card", "MADA") || paymentMethodsContain(sellerOptions.PaymentMethods, "ID", "BankTransfer", "BCA") { + t.Fatalf("coin seller SA options must use the same country-filtered MiFaPay methods: %+v", sellerOptions.PaymentMethods) + } +} + +// TestH5MifaPayOrderRejectsPaymentMethodFromAnotherCountry 锁定下单阶段再次校验国家,防止前端伪造 method_id 绕过 options 过滤。 +func TestH5MifaPayOrderRejectsPaymentMethodFromAnotherCountry(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + svc.SetMifaPayClient(&fakeMifaPayClient{}) + svc.SetExternalRechargeConfig(walletservice.ExternalRechargeConfig{MifaPayNotifyURL: "https://api.example.com/api/v1/payment/mifapay/notify"}) + product := repository.CreateH5RechargeProduct(ledger.RechargeAudienceNormal, 7200, 1_500_000, 120000) + idBankMethodID := repository.ThirdPartyPaymentMethodID("ID", "BankTransfer", "BCA") + + _, err := svc.CreateH5RechargeOrder(context.Background(), ledger.CreateExternalRechargeOrderCommand{ + AppCode: appcode.Default, + CommandID: "cmd-h5-cross-country-method", + TargetUserID: 42001, + TargetRegionID: 7200, + TargetCountryCode: "SA", + AudienceType: ledger.RechargeAudienceNormal, + ProductID: product.ProductID, + ProviderCode: ledger.PaymentProviderMifaPay, + PaymentMethodID: idBankMethodID, + }) + if !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("expected CONFLICT for cross-country MiFaPay method, got %v", err) + } +} + +// TestH5USDTRechargeCreditsNormalUserCoinWallet 验证 H5 普通用户 USDT 充值只进入普通 COIN 钱包,并写普通充值记录。 +func TestH5USDTRechargeCreditsNormalUserCoinWallet(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + tron := &fakeTronUSDTClient{} + svc.SetTronUSDTClient(tron) + svc.SetExternalRechargeConfig(walletservice.ExternalRechargeConfig{ + USDTTRC20Enabled: true, + USDTTRC20Address: "TPlatformReceiveAddress", + }) + product := repository.CreateH5RechargeProduct(ledger.RechargeAudienceNormal, 7300, 1_500_000, 120000) + + order, err := svc.CreateH5RechargeOrder(context.Background(), ledger.CreateExternalRechargeOrderCommand{ + AppCode: appcode.Default, + CommandID: "cmd-h5-normal-usdt", + TargetUserID: 43001, + TargetRegionID: 7300, + TargetCountryCode: "SA", + AudienceType: ledger.RechargeAudienceNormal, + ProductID: product.ProductID, + ProviderCode: ledger.PaymentProviderUSDTTRC20, + }) + if err != nil { + t.Fatalf("CreateH5RechargeOrder normal usdt failed: %v", err) + } + if order.Status != ledger.ExternalRechargeStatusPending || order.ReceiveAddress != "TPlatformReceiveAddress" || order.USDMinorAmount != 150 { + t.Fatalf("normal usdt order snapshot mismatch: %+v", order) + } + credited, err := svc.SubmitH5RechargeTx(context.Background(), ledger.SubmitExternalRechargeTxCommand{ + AppCode: appcode.Default, + OrderID: order.OrderID, + TargetUserID: 43001, + TxHash: "tx-normal-usdt", + }) + if err != nil { + t.Fatalf("SubmitH5RechargeTx normal failed: %v", err) + } + if credited.Status != ledger.ExternalRechargeStatusCredited || credited.TransactionID == "" { + t.Fatalf("normal usdt credited order mismatch: %+v", credited) + } + if tron.lastTxHash != "tx-normal-usdt" || tron.lastToAddress != "TPlatformReceiveAddress" || tron.lastAmountMinor != 150 { + t.Fatalf("tron verification input mismatch: %+v", tron) + } + assertBalance(t, svc, 43001, ledger.AssetCoin, 120000) + assertBalance(t, svc, 43001, ledger.AssetCoinSellerCoin, 0) + if got := repository.CountRows("wallet_recharge_records", "transaction_id = ?", credited.TransactionID); got != 1 { + t.Fatalf("normal h5 recharge should write one user recharge record, got %d", got) + } + if got := repository.CountRows("wallet_user_recharge_stats", "user_id = ?", int64(43001)); got != 1 { + t.Fatalf("normal h5 recharge should update user recharge stats, got %d", got) + } + if got := repository.CountRows("coin_seller_stock_records", "transaction_id = ?", credited.TransactionID); got != 0 { + t.Fatalf("normal h5 recharge must not write seller stock record, got %d", got) + } +} + +// TestH5USDTRechargeCreditsCoinSellerWalletOnly 验证 H5 币商充值只进入币商专用钱包,并写币商库存记录而不是普通用户充值记录。 +func TestH5USDTRechargeCreditsCoinSellerWalletOnly(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + svc.SetTronUSDTClient(&fakeTronUSDTClient{}) + svc.SetExternalRechargeConfig(walletservice.ExternalRechargeConfig{ + USDTTRC20Enabled: true, + USDTTRC20Address: "TPlatformReceiveAddress", + }) + product := repository.CreateH5RechargeProduct(ledger.RechargeAudienceCoinSeller, 7400, 10_000_000, 880000) + + order, err := svc.CreateH5RechargeOrder(context.Background(), ledger.CreateExternalRechargeOrderCommand{ + AppCode: appcode.Default, + CommandID: "cmd-h5-seller-usdt", + TargetUserID: 44001, + TargetRegionID: 7400, + TargetCountryCode: "SA", + AudienceType: ledger.RechargeAudienceCoinSeller, + ProductID: product.ProductID, + ProviderCode: ledger.PaymentProviderUSDTTRC20, + }) + if err != nil { + t.Fatalf("CreateH5RechargeOrder seller usdt failed: %v", err) + } + credited, err := svc.SubmitH5RechargeTx(context.Background(), ledger.SubmitExternalRechargeTxCommand{ + AppCode: appcode.Default, + OrderID: order.OrderID, + TargetUserID: 44001, + TxHash: "tx-seller-usdt", + }) + if err != nil { + t.Fatalf("SubmitH5RechargeTx seller failed: %v", err) + } + assertBalance(t, svc, 44001, ledger.AssetCoinSellerCoin, 880000) + assertBalance(t, svc, 44001, ledger.AssetCoin, 0) + if got := repository.CountRows("wallet_recharge_records", "transaction_id = ?", credited.TransactionID); got != 0 { + t.Fatalf("seller h5 recharge must not write user recharge record, got %d", got) + } + if got := repository.CountRows("wallet_user_recharge_stats", "user_id = ?", int64(44001)); got != 0 { + t.Fatalf("seller h5 recharge must not update user recharge stats, got %d", got) + } + if got := repository.CountRows("coin_seller_stock_records", "transaction_id = ? AND counts_as_seller_recharge = TRUE AND coin_amount = ? AND paid_currency_code = ? AND paid_amount_micro = ?", credited.TransactionID, int64(880000), ledger.PaidCurrencyUSDT, int64(10_000_000)); got != 1 { + t.Fatalf("seller h5 recharge should write one seller stock record, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ?", credited.TransactionID, "WalletCoinSellerStockPurchased"); got != 1 { + t.Fatalf("seller h5 recharge should emit seller stock outbox event, got %d", got) + } +} + // TestAdminCreditCoinSellerStockPurchaseCreditsDedicatedBalance 验证 USDT 进货只增加币商专用库存并写专用进货记录。 func TestAdminCreditCoinSellerStockPurchaseCreditsDedicatedBalance(t *testing.T) { repository := mysqltest.NewRepository(t) @@ -3877,6 +4058,86 @@ func TestRechargeProductsAreConfiguredByPlatformRegionAndStatus(t *testing.T) { } } +// TestListAdminRechargeProductsWithoutAudienceOrPlatformFilterIncludesCoinSellerWeb 锁定后台“全部用户类型/全部平台”的语义。 +// App/H5 购买入口可以把空 audience_type 解释成普通用户,但后台配置列表的空值必须表示不筛选, +// 否则运营选择“全部”时只看到 normal/android,刚配置的 coin_seller/web 档位会被后端静默过滤。 +func TestListAdminRechargeProductsWithoutAudienceOrPlatformFilterIncludesCoinSellerWeb(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + + normalProduct, err := svc.CreateRechargeProduct(context.Background(), ledger.RechargeProductCommand{ + AppCode: "lalu", + AmountMicro: 1000000, + CoinAmount: 1000000, + ProductName: "Normal Android", + Description: "normal android tier", + Platform: ledger.RechargeProductPlatformAndroid, + AudienceType: ledger.RechargeAudienceNormal, + RegionIDs: []int64{1001}, + Enabled: true, + OperatorUserID: 90001, + }) + if err != nil { + t.Fatalf("CreateRechargeProduct normal failed: %v", err) + } + coinSellerProduct, err := svc.CreateRechargeProduct(context.Background(), ledger.RechargeProductCommand{ + AppCode: "lalu", + AmountMicro: 5000000, + CoinAmount: 5000000, + ProductName: "Coin Seller Web", + Description: "coin seller web tier", + Platform: ledger.RechargeProductPlatformWeb, + AudienceType: ledger.RechargeAudienceCoinSeller, + RegionIDs: []int64{1001}, + Enabled: true, + OperatorUserID: 90002, + }) + if err != nil { + t.Fatalf("CreateRechargeProduct coin seller failed: %v", err) + } + + products, total, err := svc.ListAdminRechargeProducts(context.Background(), ledger.ListRechargeProductsQuery{ + AppCode: "lalu", + Page: 1, + PageSize: 10, + }) + if err != nil { + t.Fatalf("ListAdminRechargeProducts all filters failed: %v", err) + } + if total != 2 || len(products) != 2 { + t.Fatalf("admin all filters must include normal and coin seller products: total=%d products=%+v", total, products) + } + assertRechargeProductListed(t, products, normalProduct.ProductID, ledger.RechargeAudienceNormal, ledger.RechargeProductPlatformAndroid) + assertRechargeProductListed(t, products, coinSellerProduct.ProductID, ledger.RechargeAudienceCoinSeller, ledger.RechargeProductPlatformWeb) + + coinSellerOnly, total, err := svc.ListAdminRechargeProducts(context.Background(), ledger.ListRechargeProductsQuery{ + AppCode: "lalu", + AudienceType: ledger.RechargeAudienceCoinSeller, + Page: 1, + PageSize: 10, + }) + if err != nil { + t.Fatalf("ListAdminRechargeProducts coin seller failed: %v", err) + } + if total != 1 || len(coinSellerOnly) != 1 || coinSellerOnly[0].ProductID != coinSellerProduct.ProductID { + t.Fatalf("coin seller filter must only include coin seller product: total=%d products=%+v", total, coinSellerOnly) + } +} + +func assertRechargeProductListed(t *testing.T, products []ledger.RechargeProduct, productID int64, audienceType string, platform string) { + t.Helper() + for _, product := range products { + if product.ProductID != productID { + continue + } + if product.AudienceType != audienceType || product.Platform != platform { + t.Fatalf("product %d metadata mismatch: want audience=%s platform=%s got=%+v", productID, audienceType, platform, product) + } + return + } + t.Fatalf("product %d not found in admin list: %+v", productID, products) +} + // TestConfirmGooglePaymentUsesProductNameAsGoogleProductID 锁定后台 product_name 作为 Google Play 商品 ID。 func TestConfirmGooglePaymentUsesProductNameAsGoogleProductID(t *testing.T) { repository := mysqltest.NewRepository(t) @@ -4066,6 +4327,15 @@ func assertBalance(t *testing.T, svc *walletservice.Service, userID int64, asset } } +func paymentMethodsContain(methods []ledger.ThirdPartyPaymentMethod, countryCode string, payWay string, payType string) bool { + for _, method := range methods { + if method.CountryCode == countryCode && method.PayWay == payWay && method.PayType == payType { + return true + } + } + return false +} + func giftWallItemByID(items []ledger.GiftWallItem, giftID string) (ledger.GiftWallItem, bool) { for _, item := range items { if item.GiftID == giftID { @@ -4132,3 +4402,49 @@ func (f *fakeGooglePlayClient) ConsumeProduct(_ context.Context, _ string, produ f.consumed = append(f.consumed, productID) return nil } + +type fakeMifaPayClient struct { + createReq walletservice.MifaPayCreateOrderRequest + createResp walletservice.MifaPayCreateOrderResponse + notifyData walletservice.MifaPayNotifyData + err error +} + +func (f *fakeMifaPayClient) CreateOrder(_ context.Context, req walletservice.MifaPayCreateOrderRequest) (walletservice.MifaPayCreateOrderResponse, error) { + f.createReq = req + if f.err != nil { + return walletservice.MifaPayCreateOrderResponse{}, f.err + } + if f.createResp.PayURL == "" { + return walletservice.MifaPayCreateOrderResponse{OrderID: req.OrderID, ProviderOrderID: "mb_" + req.OrderID, PayURL: "https://pay.example/" + req.OrderID, RawJSON: `{"ok":true}`}, nil + } + return f.createResp, nil +} + +func (f *fakeMifaPayClient) ParseNotification(ledger.MifaPayNotification) (walletservice.MifaPayNotifyData, error) { + if f.err != nil { + return walletservice.MifaPayNotifyData{}, f.err + } + return f.notifyData, nil +} + +type fakeTronUSDTClient struct { + lastTxHash string + lastToAddress string + lastAmountMinor int64 + rawJSON string + err error +} + +func (f *fakeTronUSDTClient) VerifyUSDTTransfer(_ context.Context, txHash string, toAddress string, amountMinor int64) (string, error) { + f.lastTxHash = txHash + f.lastToAddress = toAddress + f.lastAmountMinor = amountMinor + if f.err != nil { + return f.rawJSON, f.err + } + if f.rawJSON != "" { + return f.rawJSON, nil + } + return `{"confirmed":true}`, nil +} diff --git a/services/wallet-service/internal/storage/mysql/recharge_product_repository.go b/services/wallet-service/internal/storage/mysql/recharge_product_repository.go index 8f4b13c4..39b4f2a1 100644 --- a/services/wallet-service/internal/storage/mysql/recharge_product_repository.go +++ b/services/wallet-service/internal/storage/mysql/recharge_product_repository.go @@ -376,7 +376,11 @@ func replaceRechargeProductRegions(ctx context.Context, tx *sql.Tx, appCode stri func normalizeRechargeProductQuery(query ledger.ListRechargeProductsQuery) ledger.ListRechargeProductsQuery { query.Status = ledger.NormalizeRechargeProductStatus(query.Status) query.Platform = ledger.NormalizeRechargeProductPlatform(query.Platform) - query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType) + // 后台列表的空 audience_type 表示“全部用户类型”,不能复用前台充值入口的默认 normal 语义; + // 否则 admin-platform 选择“全部用户类型/全部平台”时会被静默收窄成普通用户,币商 Web 档位不会出现。 + if strings.TrimSpace(query.AudienceType) != "" { + query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType) + } query.Keyword = strings.TrimSpace(query.Keyword) query.Page, query.PageSize = normalizePage(query.Page, query.PageSize) return query diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index 2fe4428f..f2b864f6 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -3002,6 +3002,9 @@ type salaryTransferToCoinSellerMetadata struct { type coinSellerStockMetadata struct { AppCode string `json:"app_code"` + PaymentOrderID string `json:"payment_order_id,omitempty"` + ProviderCode string `json:"provider,omitempty"` + PaymentMethodID int64 `json:"payment_method_id,omitempty"` SellerUserID int64 `json:"seller_user_id"` SellerCountryID int64 `json:"seller_country_id"` SellerRegionID int64 `json:"seller_region_id"` diff --git a/services/wallet-service/internal/storage/mysql/third_party_payment_repository.go b/services/wallet-service/internal/storage/mysql/third_party_payment_repository.go index a8d1150f..ec006759 100644 --- a/services/wallet-service/internal/storage/mysql/third_party_payment_repository.go +++ b/services/wallet-service/internal/storage/mysql/third_party_payment_repository.go @@ -442,6 +442,13 @@ func (r *Repository) CreditExternalRechargeOrder(ctx context.Context, appCode st RechargePolicyUSDMinorAmount: order.USDMinorAmount, RechargeType: rechargeType, } + if !isCoinSellerRecharge { + rechargeSequence, err := r.reserveUserRechargeSequence(ctx, tx, order.AppCode, order.TargetUserID, transactionID, order.CoinAmount, order.USDMinorAmount, nowMS) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + metadata.RechargeSequence = rechargeSequence + } if err := r.insertTransaction(ctx, tx, transactionID, order.CommandID, bizType, externalRechargeRequestHash(order, assetType), order.OrderID, metadata, nowMS); err != nil { return ledger.ExternalRechargeOrder{}, err } @@ -459,8 +466,44 @@ func (r *Repository) CreditExternalRechargeOrder(ctx context.Context, appCode st }); err != nil { return ledger.ExternalRechargeOrder{}, err } - if err := r.insertRechargeRecord(ctx, tx, transactionID, metadata, nowMS); err != nil { - return ledger.ExternalRechargeOrder{}, err + // 普通用户 H5 充值进入普通 COIN 钱包,并写普通充值记录/累计统计,供首充、累充、VIP 等用户侧口径消费。 + // 币商 H5 充值只进入 COIN_SELLER_COIN 库存,并写币商库存记录;不能写 wallet_recharge_records,避免被误算成普通用户充值。 + outboxEvents := []walletOutboxEvent{ + balanceChangedEvent(transactionID, order.CommandID, order.TargetUserID, assetType, order.CoinAmount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMS), + } + if isCoinSellerRecharge { + paidAmountMicro, err := checkedMul(order.ProviderAmountMinor, 10_000) + if err != nil { + return ledger.ExternalRechargeOrder{}, err + } + stockMetadata := coinSellerStockMetadata{ + AppCode: order.AppCode, + PaymentOrderID: order.OrderID, + ProviderCode: order.ProviderCode, + PaymentMethodID: order.PaymentMethodID, + SellerUserID: order.TargetUserID, + SellerRegionID: order.TargetRegionID, + StockType: ledger.StockTypeUSDTPurchase, + CoinAmount: order.CoinAmount, + PaidCurrencyCode: order.CurrencyCode, + PaidAmountMicro: paidAmountMicro, + PaymentRef: externalRechargePaymentRef(order), + EvidenceRef: order.OrderID, + Reason: "h5 external recharge", + AssetType: ledger.AssetCoinSellerCoin, + CountsAsSellerRecharge: true, + BalanceAfter: balanceAfter, + CreatedAtMS: nowMS, + } + if err := r.insertCoinSellerStockRecord(ctx, tx, transactionID, order.CommandID, stockMetadata, nowMS); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + outboxEvents = append(outboxEvents, coinSellerStockCreditedEvent(transactionID, order.CommandID, stockMetadata, nowMS)) + } else { + if err := r.insertRechargeRecord(ctx, tx, transactionID, metadata, nowMS); err != nil { + return ledger.ExternalRechargeOrder{}, err + } + outboxEvents = append(outboxEvents, rechargeRecordedEvent(transactionID, order.CommandID, order.TargetUserID, order.CoinAmount, metadata, nowMS)) } if _, err := tx.ExecContext(ctx, ` UPDATE external_recharge_orders @@ -471,10 +514,7 @@ func (r *Repository) CreditExternalRechargeOrder(ctx context.Context, appCode st ); err != nil { return ledger.ExternalRechargeOrder{}, err } - if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ - balanceChangedEvent(transactionID, order.CommandID, order.TargetUserID, assetType, order.CoinAmount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMS), - rechargeRecordedEvent(transactionID, order.CommandID, order.TargetUserID, order.CoinAmount, metadata, nowMS), - }); err != nil { + if err := r.insertWalletOutbox(ctx, tx, outboxEvents); err != nil { return ledger.ExternalRechargeOrder{}, err } if err := tx.Commit(); err != nil { @@ -582,6 +622,15 @@ func scanExternalRechargeOrder(scanner scanTarget) (ledger.ExternalRechargeOrder return order, nil } +func externalRechargePaymentRef(order ledger.ExternalRechargeOrder) string { + for _, value := range []string{order.ProviderOrderID, order.TxHash, order.OrderID} { + if value = strings.TrimSpace(value); value != "" { + return value + } + } + return "" +} + func externalRechargeOrderFromCommand(command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct, method *ledger.ThirdPartyPaymentMethod, providerAmountMinor int64, receiveAddress string, nowMS int64) ledger.ExternalRechargeOrder { order := ledger.ExternalRechargeOrder{ OrderID: "h5r_" + stableHash(command.AppCode+"|"+command.CommandID), diff --git a/services/wallet-service/internal/testutil/mysqltest/mysqltest.go b/services/wallet-service/internal/testutil/mysqltest/mysqltest.go index a5ece288..6332848c 100644 --- a/services/wallet-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/wallet-service/internal/testutil/mysqltest/mysqltest.go @@ -351,6 +351,46 @@ func (r *Repository) SetCoinSellerSalaryExchangeRateTier(appCode string, regionI } } +// CreateH5RechargeProduct 通过生产仓储写入 H5 档位,测试只提供角色、区域、美元微单位和金币数。 +func (r *Repository) CreateH5RechargeProduct(audienceType string, regionID int64, amountMicro int64, coinAmount int64) ledger.RechargeProduct { + r.t.Helper() + + product, err := r.CreateRechargeProduct(context.Background(), ledger.RechargeProductCommand{ + AppCode: "lalu", + AudienceType: audienceType, + AmountMicro: amountMicro, + CoinAmount: coinAmount, + ProductName: fmt.Sprintf("H5 %s %d", audienceType, coinAmount), + Platform: ledger.RechargeProductPlatformWeb, + RegionIDs: []int64{regionID}, + Enabled: true, + OperatorUserID: 90001, + }) + if err != nil { + r.t.Fatalf("create h5 recharge product failed: %v", err) + } + return product +} + +// ThirdPartyPaymentMethodID 返回 initdb/迁移种子里的真实 MiFaPay method_id,用于 H5 下单和跨国家拒绝测试。 +func (r *Repository) ThirdPartyPaymentMethodID(countryCode string, payWay string, payType string) int64 { + r.t.Helper() + + var methodID int64 + err := r.schema.DB.QueryRowContext(context.Background(), ` + SELECT method_id + FROM third_party_payment_methods + WHERE app_code = 'lalu' AND provider_code = 'mifapay' + AND country_code = ? AND pay_way = ? AND pay_type = ? + LIMIT 1`, + countryCode, payWay, payType, + ).Scan(&methodID) + if err != nil { + r.t.Fatalf("query third party payment method %s/%s/%s failed: %v", countryCode, payWay, payType, err) + } + return methodID +} + // SetGiftPrice overrides or inserts a gift settlement price for server-side billing tests. func (r *Repository) SetGiftPrice(giftID string, priceVersion string, coinPrice int64, giftPointAmount int64, heatValue int64) { r.t.Helper() @@ -566,6 +606,7 @@ func initDBPath(t testing.TB) string { func validateTableName(table string) string { switch table { case "wallet_transactions", "wallet_entries", "wallet_outbox", "wallet_accounts", "wallet_recharge_records", + "wallet_user_recharge_stats", "external_recharge_orders", "third_party_payment_methods", "host_period_diamond_accounts", "host_period_diamond_entries", "host_agency_salary_policies", "host_agency_salary_policy_levels", "host_salary_settlement_progress", "host_salary_settlement_records", "coin_seller_stock_records", "gift_diamond_ratio_configs", "wallet_diamond_exchange_rules", From 70c928c38b7ac42029d98df85501ecad8d30f406 Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 13:33:44 +0800 Subject: [PATCH 17/28] =?UTF-8?q?=E6=88=BF=E9=97=B4=E7=8C=9C=E6=8B=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/proto/activity/v1/activity.pb.go | 357 +++- api/proto/activity/v1/activity.proto | 22 + api/proto/game/v1/game.pb.go | 1843 ++++++++++++++--- api/proto/game/v1/game.proto | 131 ++ api/proto/game/v1/game_grpc.pb.go | 466 ++++- api/proto/user/v1/auth.pb.go | 4 +- api/proto/user/v1/auth.proto | 4 +- api/proto/user/v1/user.pb.go | 1671 +++++++++------ api/proto/user/v1/user.proto | 50 + api/proto/user/v1/user_grpc.pb.go | 84 +- docs/flutter对接/任务系统Flutter对接.md | 118 ++ docs/flutter对接/房内猜拳Flutter对接.md | 322 +++ docs/全站机器人产品文档.md | 80 + docs/房内猜拳接口文档.md | 83 + docs/房内猜拳本地真实IM测试.md | 47 + server/admin/docs/猜拳后台管理技术文档.md | 87 + .../internal/integration/gameclient/client.go | 36 + .../admin/internal/modules/appuser/service.go | 91 +- .../internal/modules/dailytask/handler.go | 157 +- .../internal/modules/gamemanagement/dto.go | 128 ++ .../modules/gamemanagement/dto_test.go | 50 + .../modules/gamemanagement/request.go | 67 + .../gamemanagement/room_rps_handler.go | 173 ++ .../internal/modules/gamemanagement/routes.go | 8 + .../gamemanagement/self_game_handler.go | 2 +- server/admin/internal/repository/seed.go | 2 +- .../migrations/009_admin_navigation_seed.sql | 2 +- .../migrations/047_room_rps_navigation.sql | 32 + .../migrations/048_game_robot_menu_rename.sql | 6 + .../configs/config.docker.yaml | 4 + .../configs/config.tencent.example.yaml | 4 + services/activity-service/configs/config.yaml | 4 + .../mysql/initdb/001_activity_service.sql | 10 + .../mysql/migrations/001_task_metadata.sql | 13 + .../internal/app/grpc_registration.go | 2 +- services/activity-service/internal/app/mq.go | 78 +- .../internal/app/task_events.go | 237 +++ .../internal/config/config.go | 21 + .../internal/domain/task/task.go | 117 +- .../internal/service/task/service.go | 227 ++ .../internal/service/task/service_test.go | 65 + .../internal/storage/mysql/repository.go | 52 + .../internal/storage/mysql/task_repository.go | 195 +- .../transport/grpc/broadcast_server.go | 10 +- .../grpc/task_flow_integration_test.go | 242 +++ .../internal/transport/grpc/task_server.go | 134 +- .../cron-service/configs/config.docker.yaml | 7 + .../configs/config.tencent.example.yaml | 7 + services/cron-service/configs/config.yaml | 7 + services/cron-service/internal/app/app.go | 10 +- .../cron-service/internal/config/config.go | 8 + .../internal/config/config_test.go | 14 + .../cron-service/internal/integration/user.go | 7 + services/game-service/internal/app/app.go | 17 +- .../internal/client/activity_client.go | 14 +- .../internal/service/game/service.go | 34 +- .../internal/service/game/service_test.go | 18 +- .../internal/service/roomrps/service.go | 778 +++++++ .../internal/transport/grpc/server.go | 130 +- .../internal/client/game_client.go | 30 + .../internal/client/user_client.go | 5 + .../http/activityapi/task_handler.go | 101 +- .../transport/http/game_handler_test.go | 167 +- .../http/gameapi/dice_profile_view.go | 51 + .../transport/http/gameapi/game_handler.go | 156 ++ .../transport/http/gameapi/game_view.go | 176 ++ .../transport/http/gameapi/handler.go | 25 + .../transport/http/httproutes/router.go | 18 + .../transport/http/roomapi/handler.go | 2 + .../http/roomapi/room_handler_test.go | 126 ++ .../transport/http/roomapi/share_handler.go | 284 +++ .../transport/http/roomapi/share_view.go | 34 + .../transport/http/userapi/cp_handler.go | 115 + .../transport/http/userapi/cp_handler_test.go | 55 + .../transport/http/userapi/handler.go | 1 + .../cmd/backfill-last7/main.go | 12 +- .../user-service/configs/config.docker.yaml | 10 + .../configs/config.tencent.example.yaml | 10 + services/user-service/configs/config.yaml | 10 + .../deploy/mysql/initdb/001_user_service.sql | 124 -- services/user-service/internal/app/app.go | 126 +- .../user-service/internal/config/config.go | 45 + .../user-service/internal/domain/auth/auth.go | 4 +- .../user-service/internal/domain/cp/cp.go | 43 + .../user-service/internal/domain/user/user.go | 15 +- .../integration/walletclient/client.go | 64 + .../internal/service/auth/password.go | 4 +- .../internal/service/auth/quick_account.go | 30 +- .../internal/service/auth/service.go | 4 +- .../internal/service/auth/service_test.go | 144 +- .../internal/service/cp/leaderboard_store.go | 180 ++ .../service/cp/leaderboard_store_test.go | 16 + .../internal/service/cp/service.go | 152 +- .../internal/service/cp/service_test.go | 107 + .../internal/storage/mysql/auth/password.go | 56 +- .../internal/storage/mysql/cp/repository.go | 53 + .../storage/mysql/cp/repository_test.go | 27 + .../internal/storage/mysql/user/repository.go | 5 + .../internal/transport/grpc/cp.go | 71 + .../internal/transport/grpc/cron.go | 21 + .../internal/transport/grpc/server.go | 2 +- .../configs/hyapp.code-workspace | 13 + .../cp_intimacy_leaderboard_real_flow_test.go | 316 +++ tests/smoke/room_rps_real_im_flow_test.go | 484 +++++ tools/task-system-smoke/main.go | 352 ++++ 105 files changed, 10996 insertions(+), 1439 deletions(-) create mode 100644 docs/flutter对接/任务系统Flutter对接.md create mode 100644 docs/flutter对接/房内猜拳Flutter对接.md create mode 100644 docs/全站机器人产品文档.md create mode 100644 docs/房内猜拳接口文档.md create mode 100644 docs/房内猜拳本地真实IM测试.md create mode 100644 server/admin/docs/猜拳后台管理技术文档.md create mode 100644 server/admin/internal/modules/gamemanagement/room_rps_handler.go create mode 100644 server/admin/migrations/047_room_rps_navigation.sql create mode 100644 server/admin/migrations/048_game_robot_menu_rename.sql create mode 100644 services/activity-service/deploy/mysql/migrations/001_task_metadata.sql create mode 100644 services/activity-service/internal/app/task_events.go create mode 100644 services/activity-service/internal/transport/grpc/task_flow_integration_test.go create mode 100644 services/game-service/internal/service/roomrps/service.go create mode 100644 services/gateway-service/internal/transport/http/roomapi/share_handler.go create mode 100644 services/gateway-service/internal/transport/http/roomapi/share_view.go create mode 100644 services/user-service/internal/integration/walletclient/client.go create mode 100644 services/user-service/internal/service/cp/leaderboard_store.go create mode 100644 services/user-service/internal/service/cp/leaderboard_store_test.go create mode 100644 services/user-service/internal/service/cp/service_test.go create mode 100644 services/wallet-service/configs/hyapp.code-workspace create mode 100644 tests/smoke/cp_intimacy_leaderboard_real_flow_test.go create mode 100644 tests/smoke/room_rps_real_im_flow_test.go create mode 100644 tools/task-system-smoke/main.go diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index cf8d5012..548011e9 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -1659,26 +1659,33 @@ func (x *CronBatchResponse) GetHasMore() bool { // TaskItem 是 App 每日任务/专属任务列表里的单个可展示任务快照。 type TaskItem struct { - state protoimpl.MessageState `protogen:"open.v1"` - TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` - TaskType string `protobuf:"bytes,2,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"` - Category string `protobuf:"bytes,3,opt,name=category,proto3" json:"category,omitempty"` - MetricType string `protobuf:"bytes,4,opt,name=metric_type,json=metricType,proto3" json:"metric_type,omitempty"` - Title string `protobuf:"bytes,5,opt,name=title,proto3" json:"title,omitempty"` - Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` - TargetValue int64 `protobuf:"varint,7,opt,name=target_value,json=targetValue,proto3" json:"target_value,omitempty"` - TargetUnit string `protobuf:"bytes,8,opt,name=target_unit,json=targetUnit,proto3" json:"target_unit,omitempty"` - ProgressValue int64 `protobuf:"varint,9,opt,name=progress_value,json=progressValue,proto3" json:"progress_value,omitempty"` - RewardCoinAmount int64 `protobuf:"varint,10,opt,name=reward_coin_amount,json=rewardCoinAmount,proto3" json:"reward_coin_amount,omitempty"` - Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` - Claimable bool `protobuf:"varint,12,opt,name=claimable,proto3" json:"claimable,omitempty"` - TaskDay string `protobuf:"bytes,13,opt,name=task_day,json=taskDay,proto3" json:"task_day,omitempty"` - ServerTimeMs int64 `protobuf:"varint,14,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` - NextRefreshAtMs int64 `protobuf:"varint,15,opt,name=next_refresh_at_ms,json=nextRefreshAtMs,proto3" json:"next_refresh_at_ms,omitempty"` - SortOrder int32 `protobuf:"varint,16,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - Version int64 `protobuf:"varint,17,opt,name=version,proto3" json:"version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + TaskType string `protobuf:"bytes,2,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"` + Category string `protobuf:"bytes,3,opt,name=category,proto3" json:"category,omitempty"` + MetricType string `protobuf:"bytes,4,opt,name=metric_type,json=metricType,proto3" json:"metric_type,omitempty"` + Title string `protobuf:"bytes,5,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` + TargetValue int64 `protobuf:"varint,7,opt,name=target_value,json=targetValue,proto3" json:"target_value,omitempty"` + TargetUnit string `protobuf:"bytes,8,opt,name=target_unit,json=targetUnit,proto3" json:"target_unit,omitempty"` + ProgressValue int64 `protobuf:"varint,9,opt,name=progress_value,json=progressValue,proto3" json:"progress_value,omitempty"` + RewardCoinAmount int64 `protobuf:"varint,10,opt,name=reward_coin_amount,json=rewardCoinAmount,proto3" json:"reward_coin_amount,omitempty"` + Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` + Claimable bool `protobuf:"varint,12,opt,name=claimable,proto3" json:"claimable,omitempty"` + TaskDay string `protobuf:"bytes,13,opt,name=task_day,json=taskDay,proto3" json:"task_day,omitempty"` + ServerTimeMs int64 `protobuf:"varint,14,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + NextRefreshAtMs int64 `protobuf:"varint,15,opt,name=next_refresh_at_ms,json=nextRefreshAtMs,proto3" json:"next_refresh_at_ms,omitempty"` + SortOrder int32 `protobuf:"varint,16,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + Version int64 `protobuf:"varint,17,opt,name=version,proto3" json:"version,omitempty"` + AudienceType string `protobuf:"bytes,18,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` + IconKey string `protobuf:"bytes,19,opt,name=icon_key,json=iconKey,proto3" json:"icon_key,omitempty"` + IconUrl string `protobuf:"bytes,20,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"` + ActionType string `protobuf:"bytes,21,opt,name=action_type,json=actionType,proto3" json:"action_type,omitempty"` + ActionParam string `protobuf:"bytes,22,opt,name=action_param,json=actionParam,proto3" json:"action_param,omitempty"` + ActionPayloadJson string `protobuf:"bytes,23,opt,name=action_payload_json,json=actionPayloadJson,proto3" json:"action_payload_json,omitempty"` + DimensionFilterJson string `protobuf:"bytes,24,opt,name=dimension_filter_json,json=dimensionFilterJson,proto3" json:"dimension_filter_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TaskItem) Reset() { @@ -1830,6 +1837,55 @@ func (x *TaskItem) GetVersion() int64 { return 0 } +func (x *TaskItem) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + +func (x *TaskItem) GetIconKey() string { + if x != nil { + return x.IconKey + } + return "" +} + +func (x *TaskItem) GetIconUrl() string { + if x != nil { + return x.IconUrl + } + return "" +} + +func (x *TaskItem) GetActionType() string { + if x != nil { + return x.ActionType + } + return "" +} + +func (x *TaskItem) GetActionParam() string { + if x != nil { + return x.ActionParam + } + return "" +} + +func (x *TaskItem) GetActionPayloadJson() string { + if x != nil { + return x.ActionPayloadJson + } + return "" +} + +func (x *TaskItem) GetDimensionFilterJson() string { + if x != nil { + return x.DimensionFilterJson + } + return "" +} + // TaskSection 固定表达 App 任务页的 daily/exclusive 两个分区。 type TaskSection struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2209,17 +2265,18 @@ func (x *ClaimTaskRewardResponse) GetClaimed() bool { // ConsumeTaskEventRequest 是异步 outbox consumer 投递给任务系统的事实事件。 type ConsumeTaskEventRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - EventId string `protobuf:"bytes,2,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` - EventType string `protobuf:"bytes,3,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` - SourceService string `protobuf:"bytes,4,opt,name=source_service,json=sourceService,proto3" json:"source_service,omitempty"` - UserId int64 `protobuf:"varint,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - MetricType string `protobuf:"bytes,6,opt,name=metric_type,json=metricType,proto3" json:"metric_type,omitempty"` - Value int64 `protobuf:"varint,7,opt,name=value,proto3" json:"value,omitempty"` - OccurredAtMs int64 `protobuf:"varint,8,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + EventId string `protobuf:"bytes,2,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` + EventType string `protobuf:"bytes,3,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` + SourceService string `protobuf:"bytes,4,opt,name=source_service,json=sourceService,proto3" json:"source_service,omitempty"` + UserId int64 `protobuf:"varint,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + MetricType string `protobuf:"bytes,6,opt,name=metric_type,json=metricType,proto3" json:"metric_type,omitempty"` + Value int64 `protobuf:"varint,7,opt,name=value,proto3" json:"value,omitempty"` + OccurredAtMs int64 `protobuf:"varint,8,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"` + DimensionsJson string `protobuf:"bytes,9,opt,name=dimensions_json,json=dimensionsJson,proto3" json:"dimensions_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ConsumeTaskEventRequest) Reset() { @@ -2308,6 +2365,13 @@ func (x *ConsumeTaskEventRequest) GetOccurredAtMs() int64 { return 0 } +func (x *ConsumeTaskEventRequest) GetDimensionsJson() string { + if x != nil { + return x.DimensionsJson + } + return "" +} + // ConsumeTaskEventResponse 返回事件消费幂等结果;matched_task_count=0 表示合法跳过。 type ConsumeTaskEventResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -3041,27 +3105,34 @@ func (x *ConsumeRoomEventResponse) GetBroadcastCreated() bool { // TaskDefinition 是后台管理任务配置的当前版本读模型。 type TaskDefinition struct { - state protoimpl.MessageState `protogen:"open.v1"` - TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` - TaskType string `protobuf:"bytes,2,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"` - Category string `protobuf:"bytes,3,opt,name=category,proto3" json:"category,omitempty"` - MetricType string `protobuf:"bytes,4,opt,name=metric_type,json=metricType,proto3" json:"metric_type,omitempty"` - Title string `protobuf:"bytes,5,opt,name=title,proto3" json:"title,omitempty"` - Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` - TargetValue int64 `protobuf:"varint,7,opt,name=target_value,json=targetValue,proto3" json:"target_value,omitempty"` - TargetUnit string `protobuf:"bytes,8,opt,name=target_unit,json=targetUnit,proto3" json:"target_unit,omitempty"` - RewardCoinAmount int64 `protobuf:"varint,9,opt,name=reward_coin_amount,json=rewardCoinAmount,proto3" json:"reward_coin_amount,omitempty"` - Status string `protobuf:"bytes,10,opt,name=status,proto3" json:"status,omitempty"` - SortOrder int32 `protobuf:"varint,11,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - Version int64 `protobuf:"varint,12,opt,name=version,proto3" json:"version,omitempty"` - EffectiveFromMs int64 `protobuf:"varint,13,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"` - EffectiveToMs int64 `protobuf:"varint,14,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"` - CreatedByAdminId int64 `protobuf:"varint,15,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"` - UpdatedByAdminId int64 `protobuf:"varint,16,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"` - CreatedAtMs int64 `protobuf:"varint,17,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` - UpdatedAtMs int64 `protobuf:"varint,18,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + TaskType string `protobuf:"bytes,2,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"` + Category string `protobuf:"bytes,3,opt,name=category,proto3" json:"category,omitempty"` + MetricType string `protobuf:"bytes,4,opt,name=metric_type,json=metricType,proto3" json:"metric_type,omitempty"` + Title string `protobuf:"bytes,5,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` + TargetValue int64 `protobuf:"varint,7,opt,name=target_value,json=targetValue,proto3" json:"target_value,omitempty"` + TargetUnit string `protobuf:"bytes,8,opt,name=target_unit,json=targetUnit,proto3" json:"target_unit,omitempty"` + RewardCoinAmount int64 `protobuf:"varint,9,opt,name=reward_coin_amount,json=rewardCoinAmount,proto3" json:"reward_coin_amount,omitempty"` + Status string `protobuf:"bytes,10,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,11,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + Version int64 `protobuf:"varint,12,opt,name=version,proto3" json:"version,omitempty"` + EffectiveFromMs int64 `protobuf:"varint,13,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"` + EffectiveToMs int64 `protobuf:"varint,14,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"` + CreatedByAdminId int64 `protobuf:"varint,15,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"` + UpdatedByAdminId int64 `protobuf:"varint,16,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,17,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,18,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + AudienceType string `protobuf:"bytes,19,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` + IconKey string `protobuf:"bytes,20,opt,name=icon_key,json=iconKey,proto3" json:"icon_key,omitempty"` + IconUrl string `protobuf:"bytes,21,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"` + ActionType string `protobuf:"bytes,22,opt,name=action_type,json=actionType,proto3" json:"action_type,omitempty"` + ActionParam string `protobuf:"bytes,23,opt,name=action_param,json=actionParam,proto3" json:"action_param,omitempty"` + ActionPayloadJson string `protobuf:"bytes,24,opt,name=action_payload_json,json=actionPayloadJson,proto3" json:"action_payload_json,omitempty"` + DimensionFilterJson string `protobuf:"bytes,25,opt,name=dimension_filter_json,json=dimensionFilterJson,proto3" json:"dimension_filter_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TaskDefinition) Reset() { @@ -3220,6 +3291,55 @@ func (x *TaskDefinition) GetUpdatedAtMs() int64 { return 0 } +func (x *TaskDefinition) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + +func (x *TaskDefinition) GetIconKey() string { + if x != nil { + return x.IconKey + } + return "" +} + +func (x *TaskDefinition) GetIconUrl() string { + if x != nil { + return x.IconUrl + } + return "" +} + +func (x *TaskDefinition) GetActionType() string { + if x != nil { + return x.ActionType + } + return "" +} + +func (x *TaskDefinition) GetActionParam() string { + if x != nil { + return x.ActionParam + } + return "" +} + +func (x *TaskDefinition) GetActionPayloadJson() string { + if x != nil { + return x.ActionPayloadJson + } + return "" +} + +func (x *TaskDefinition) GetDimensionFilterJson() string { + if x != nil { + return x.DimensionFilterJson + } + return "" +} + // ListTaskDefinitionsRequest 是后台任务配置列表筛选条件。 type ListTaskDefinitionsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -3367,24 +3487,31 @@ func (x *ListTaskDefinitionsResponse) GetTotal() int64 { // UpsertTaskDefinitionRequest 创建或编辑任务;active 关键字段变更由服务端生成新版本。 type UpsertTaskDefinitionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - TaskId string `protobuf:"bytes,2,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` - TaskType string `protobuf:"bytes,3,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"` - Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` - MetricType string `protobuf:"bytes,5,opt,name=metric_type,json=metricType,proto3" json:"metric_type,omitempty"` - Title string `protobuf:"bytes,6,opt,name=title,proto3" json:"title,omitempty"` - Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty"` - TargetValue int64 `protobuf:"varint,8,opt,name=target_value,json=targetValue,proto3" json:"target_value,omitempty"` - TargetUnit string `protobuf:"bytes,9,opt,name=target_unit,json=targetUnit,proto3" json:"target_unit,omitempty"` - RewardCoinAmount int64 `protobuf:"varint,10,opt,name=reward_coin_amount,json=rewardCoinAmount,proto3" json:"reward_coin_amount,omitempty"` - Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` - SortOrder int32 `protobuf:"varint,12,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - EffectiveFromMs int64 `protobuf:"varint,13,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"` - EffectiveToMs int64 `protobuf:"varint,14,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"` - OperatorAdminId int64 `protobuf:"varint,15,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TaskId string `protobuf:"bytes,2,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + TaskType string `protobuf:"bytes,3,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"` + Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` + MetricType string `protobuf:"bytes,5,opt,name=metric_type,json=metricType,proto3" json:"metric_type,omitempty"` + Title string `protobuf:"bytes,6,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty"` + TargetValue int64 `protobuf:"varint,8,opt,name=target_value,json=targetValue,proto3" json:"target_value,omitempty"` + TargetUnit string `protobuf:"bytes,9,opt,name=target_unit,json=targetUnit,proto3" json:"target_unit,omitempty"` + RewardCoinAmount int64 `protobuf:"varint,10,opt,name=reward_coin_amount,json=rewardCoinAmount,proto3" json:"reward_coin_amount,omitempty"` + Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,12,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + EffectiveFromMs int64 `protobuf:"varint,13,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"` + EffectiveToMs int64 `protobuf:"varint,14,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"` + OperatorAdminId int64 `protobuf:"varint,15,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + AudienceType string `protobuf:"bytes,16,opt,name=audience_type,json=audienceType,proto3" json:"audience_type,omitempty"` + IconKey string `protobuf:"bytes,17,opt,name=icon_key,json=iconKey,proto3" json:"icon_key,omitempty"` + IconUrl string `protobuf:"bytes,18,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"` + ActionType string `protobuf:"bytes,19,opt,name=action_type,json=actionType,proto3" json:"action_type,omitempty"` + ActionParam string `protobuf:"bytes,20,opt,name=action_param,json=actionParam,proto3" json:"action_param,omitempty"` + ActionPayloadJson string `protobuf:"bytes,21,opt,name=action_payload_json,json=actionPayloadJson,proto3" json:"action_payload_json,omitempty"` + DimensionFilterJson string `protobuf:"bytes,22,opt,name=dimension_filter_json,json=dimensionFilterJson,proto3" json:"dimension_filter_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpsertTaskDefinitionRequest) Reset() { @@ -3522,6 +3649,55 @@ func (x *UpsertTaskDefinitionRequest) GetOperatorAdminId() int64 { return 0 } +func (x *UpsertTaskDefinitionRequest) GetAudienceType() string { + if x != nil { + return x.AudienceType + } + return "" +} + +func (x *UpsertTaskDefinitionRequest) GetIconKey() string { + if x != nil { + return x.IconKey + } + return "" +} + +func (x *UpsertTaskDefinitionRequest) GetIconUrl() string { + if x != nil { + return x.IconUrl + } + return "" +} + +func (x *UpsertTaskDefinitionRequest) GetActionType() string { + if x != nil { + return x.ActionType + } + return "" +} + +func (x *UpsertTaskDefinitionRequest) GetActionParam() string { + if x != nil { + return x.ActionParam + } + return "" +} + +func (x *UpsertTaskDefinitionRequest) GetActionPayloadJson() string { + if x != nil { + return x.ActionPayloadJson + } + return "" +} + +func (x *UpsertTaskDefinitionRequest) GetDimensionFilterJson() string { + if x != nil { + return x.DimensionFilterJson + } + return "" +} + type UpsertTaskDefinitionResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Task *TaskDefinition `protobuf:"bytes,1,opt,name=task,proto3" json:"task,omitempty"` @@ -16727,7 +16903,7 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\x0fprocessed_count\x18\x02 \x01(\x05R\x0eprocessedCount\x12#\n" + "\rsuccess_count\x18\x03 \x01(\x05R\fsuccessCount\x12#\n" + "\rfailure_count\x18\x04 \x01(\x05R\ffailureCount\x12\x19\n" + - "\bhas_more\x18\x05 \x01(\bR\ahasMore\"\xab\x04\n" + + "\bhas_more\x18\x05 \x01(\bR\ahasMore\"\xae\x06\n" + "\bTaskItem\x12\x17\n" + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x1b\n" + "\ttask_type\x18\x02 \x01(\tR\btaskType\x12\x1a\n" + @@ -16749,7 +16925,15 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\x12next_refresh_at_ms\x18\x0f \x01(\x03R\x0fnextRefreshAtMs\x12\x1d\n" + "\n" + "sort_order\x18\x10 \x01(\x05R\tsortOrder\x12\x18\n" + - "\aversion\x18\x11 \x01(\x03R\aversion\"\xad\x01\n" + + "\aversion\x18\x11 \x01(\x03R\aversion\x12#\n" + + "\raudience_type\x18\x12 \x01(\tR\faudienceType\x12\x19\n" + + "\bicon_key\x18\x13 \x01(\tR\aiconKey\x12\x19\n" + + "\bicon_url\x18\x14 \x01(\tR\aiconUrl\x12\x1f\n" + + "\vaction_type\x18\x15 \x01(\tR\n" + + "actionType\x12!\n" + + "\faction_param\x18\x16 \x01(\tR\vactionParam\x12.\n" + + "\x13action_payload_json\x18\x17 \x01(\tR\x11actionPayloadJson\x122\n" + + "\x15dimension_filter_json\x18\x18 \x01(\tR\x13dimensionFilterJson\"\xad\x01\n" + "\vTaskSection\x12\x18\n" + "\asection\x18\x01 \x01(\tR\asection\x121\n" + "\x05items\x18\x02 \x03(\v2\x1b.hyapp.activity.v1.TaskItemR\x05items\x12$\n" + @@ -16779,7 +16963,7 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\x06status\x18\x06 \x01(\tR\x06status\x122\n" + "\x15wallet_transaction_id\x18\a \x01(\tR\x13walletTransactionId\x12\"\n" + "\rgranted_at_ms\x18\b \x01(\x03R\vgrantedAtMs\x12\x18\n" + - "\aclaimed\x18\t \x01(\bR\aclaimed\"\xa4\x02\n" + + "\aclaimed\x18\t \x01(\bR\aclaimed\"\xcd\x02\n" + "\x17ConsumeTaskEventRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x19\n" + "\bevent_id\x18\x02 \x01(\tR\aeventId\x12\x1d\n" + @@ -16790,7 +16974,8 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\vmetric_type\x18\x06 \x01(\tR\n" + "metricType\x12\x14\n" + "\x05value\x18\a \x01(\x03R\x05value\x12$\n" + - "\x0eoccurred_at_ms\x18\b \x01(\x03R\foccurredAtMs\"{\n" + + "\x0eoccurred_at_ms\x18\b \x01(\x03R\foccurredAtMs\x12'\n" + + "\x0fdimensions_json\x18\t \x01(\tR\x0edimensionsJson\"{\n" + "\x18ConsumeTaskEventResponse\x12\x19\n" + "\bevent_id\x18\x01 \x01(\tR\aeventId\x12\x16\n" + "\x06status\x18\x02 \x01(\tR\x06status\x12,\n" + @@ -16839,7 +17024,7 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\bevent_id\x18\x01 \x01(\tR\aeventId\x12\x16\n" + "\x06status\x18\x02 \x01(\tR\x06status\x12,\n" + "\x12broadcast_event_id\x18\x03 \x01(\tR\x10broadcastEventId\x12+\n" + - "\x11broadcast_created\x18\x04 \x01(\bR\x10broadcastCreated\"\xf8\x04\n" + + "\x11broadcast_created\x18\x04 \x01(\bR\x10broadcastCreated\"\xfb\x06\n" + "\x0eTaskDefinition\x12\x17\n" + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x1b\n" + "\ttask_type\x18\x02 \x01(\tR\btaskType\x12\x1a\n" + @@ -16862,7 +17047,15 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\x13created_by_admin_id\x18\x0f \x01(\x03R\x10createdByAdminId\x12-\n" + "\x13updated_by_admin_id\x18\x10 \x01(\x03R\x10updatedByAdminId\x12\"\n" + "\rcreated_at_ms\x18\x11 \x01(\x03R\vcreatedAtMs\x12\"\n" + - "\rupdated_at_ms\x18\x12 \x01(\x03R\vupdatedAtMs\"\xec\x01\n" + + "\rupdated_at_ms\x18\x12 \x01(\x03R\vupdatedAtMs\x12#\n" + + "\raudience_type\x18\x13 \x01(\tR\faudienceType\x12\x19\n" + + "\bicon_key\x18\x14 \x01(\tR\aiconKey\x12\x19\n" + + "\bicon_url\x18\x15 \x01(\tR\aiconUrl\x12\x1f\n" + + "\vaction_type\x18\x16 \x01(\tR\n" + + "actionType\x12!\n" + + "\faction_param\x18\x17 \x01(\tR\vactionParam\x12.\n" + + "\x13action_payload_json\x18\x18 \x01(\tR\x11actionPayloadJson\x122\n" + + "\x15dimension_filter_json\x18\x19 \x01(\tR\x13dimensionFilterJson\"\xec\x01\n" + "\x1aListTaskDefinitionsRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x1b\n" + "\ttask_type\x18\x02 \x01(\tR\btaskType\x12\x1a\n" + @@ -16873,7 +17066,7 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\tpage_size\x18\a \x01(\x05R\bpageSize\"l\n" + "\x1bListTaskDefinitionsResponse\x127\n" + "\x05tasks\x18\x01 \x03(\v2!.hyapp.activity.v1.TaskDefinitionR\x05tasks\x12\x14\n" + - "\x05total\x18\x02 \x01(\x03R\x05total\"\xa5\x04\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\"\xa8\x06\n" + "\x1bUpsertTaskDefinitionRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x17\n" + "\atask_id\x18\x02 \x01(\tR\x06taskId\x12\x1b\n" + @@ -16893,7 +17086,15 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "sort_order\x18\f \x01(\x05R\tsortOrder\x12*\n" + "\x11effective_from_ms\x18\r \x01(\x03R\x0feffectiveFromMs\x12&\n" + "\x0feffective_to_ms\x18\x0e \x01(\x03R\reffectiveToMs\x12*\n" + - "\x11operator_admin_id\x18\x0f \x01(\x03R\x0foperatorAdminId\"o\n" + + "\x11operator_admin_id\x18\x0f \x01(\x03R\x0foperatorAdminId\x12#\n" + + "\raudience_type\x18\x10 \x01(\tR\faudienceType\x12\x19\n" + + "\bicon_key\x18\x11 \x01(\tR\aiconKey\x12\x19\n" + + "\bicon_url\x18\x12 \x01(\tR\aiconUrl\x12\x1f\n" + + "\vaction_type\x18\x13 \x01(\tR\n" + + "actionType\x12!\n" + + "\faction_param\x18\x14 \x01(\tR\vactionParam\x12.\n" + + "\x13action_payload_json\x18\x15 \x01(\tR\x11actionPayloadJson\x122\n" + + "\x15dimension_filter_json\x18\x16 \x01(\tR\x13dimensionFilterJson\"o\n" + "\x1cUpsertTaskDefinitionResponse\x125\n" + "\x04task\x18\x01 \x01(\v2!.hyapp.activity.v1.TaskDefinitionR\x04task\x12\x18\n" + "\acreated\x18\x02 \x01(\bR\acreated\"\xb1\x01\n" + diff --git a/api/proto/activity/v1/activity.proto b/api/proto/activity/v1/activity.proto index 820b2d61..534051ee 100644 --- a/api/proto/activity/v1/activity.proto +++ b/api/proto/activity/v1/activity.proto @@ -215,6 +215,13 @@ message TaskItem { int64 next_refresh_at_ms = 15; int32 sort_order = 16; int64 version = 17; + string audience_type = 18; + string icon_key = 19; + string icon_url = 20; + string action_type = 21; + string action_param = 22; + string action_payload_json = 23; + string dimension_filter_json = 24; } // TaskSection 固定表达 App 任务页的 daily/exclusive 两个分区。 @@ -271,6 +278,7 @@ message ConsumeTaskEventRequest { string metric_type = 6; int64 value = 7; int64 occurred_at_ms = 8; + string dimensions_json = 9; } // ConsumeTaskEventResponse 返回事件消费幂等结果;matched_task_count=0 表示合法跳过。 @@ -369,6 +377,13 @@ message TaskDefinition { int64 updated_by_admin_id = 16; int64 created_at_ms = 17; int64 updated_at_ms = 18; + string audience_type = 19; + string icon_key = 20; + string icon_url = 21; + string action_type = 22; + string action_param = 23; + string action_payload_json = 24; + string dimension_filter_json = 25; } // ListTaskDefinitionsRequest 是后台任务配置列表筛选条件。 @@ -404,6 +419,13 @@ message UpsertTaskDefinitionRequest { int64 effective_from_ms = 13; int64 effective_to_ms = 14; int64 operator_admin_id = 15; + string audience_type = 16; + string icon_key = 17; + string icon_url = 18; + string action_type = 19; + string action_param = 20; + string action_payload_json = 21; + string dimension_filter_json = 22; } message UpsertTaskDefinitionResponse { diff --git a/api/proto/game/v1/game.pb.go b/api/proto/game/v1/game.pb.go index bdf7d029..aa00cc12 100644 --- a/api/proto/game/v1/game.pb.go +++ b/api/proto/game/v1/game.pb.go @@ -1825,6 +1825,1146 @@ func (x *DiceConfigResponse) GetServerTimeMs() int64 { return 0 } +type RoomRPSStakeGift struct { + state protoimpl.MessageState `protogen:"open.v1"` + GiftId int64 `protobuf:"varint,1,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + GiftName string `protobuf:"bytes,2,opt,name=gift_name,json=giftName,proto3" json:"gift_name,omitempty"` + GiftIconUrl string `protobuf:"bytes,3,opt,name=gift_icon_url,json=giftIconUrl,proto3" json:"gift_icon_url,omitempty"` + GiftPriceCoin int64 `protobuf:"varint,4,opt,name=gift_price_coin,json=giftPriceCoin,proto3" json:"gift_price_coin,omitempty"` + Enabled bool `protobuf:"varint,5,opt,name=enabled,proto3" json:"enabled,omitempty"` + SortOrder int32 `protobuf:"varint,6,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomRPSStakeGift) Reset() { + *x = RoomRPSStakeGift{} + mi := &file_proto_game_v1_game_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomRPSStakeGift) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomRPSStakeGift) ProtoMessage() {} + +func (x *RoomRPSStakeGift) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomRPSStakeGift.ProtoReflect.Descriptor instead. +func (*RoomRPSStakeGift) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{18} +} + +func (x *RoomRPSStakeGift) GetGiftId() int64 { + if x != nil { + return x.GiftId + } + return 0 +} + +func (x *RoomRPSStakeGift) GetGiftName() string { + if x != nil { + return x.GiftName + } + return "" +} + +func (x *RoomRPSStakeGift) GetGiftIconUrl() string { + if x != nil { + return x.GiftIconUrl + } + return "" +} + +func (x *RoomRPSStakeGift) GetGiftPriceCoin() int64 { + if x != nil { + return x.GiftPriceCoin + } + return 0 +} + +func (x *RoomRPSStakeGift) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *RoomRPSStakeGift) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +type RoomRPSConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,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"` + ChallengeTimeoutMs int64 `protobuf:"varint,4,opt,name=challenge_timeout_ms,json=challengeTimeoutMs,proto3" json:"challenge_timeout_ms,omitempty"` + RevealCountdownMs int64 `protobuf:"varint,5,opt,name=reveal_countdown_ms,json=revealCountdownMs,proto3" json:"reveal_countdown_ms,omitempty"` + StakeGifts []*RoomRPSStakeGift `protobuf:"bytes,6,rep,name=stake_gifts,json=stakeGifts,proto3" json:"stake_gifts,omitempty"` + CreatedAtMs int64 `protobuf:"varint,7,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,8,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomRPSConfig) Reset() { + *x = RoomRPSConfig{} + mi := &file_proto_game_v1_game_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomRPSConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomRPSConfig) ProtoMessage() {} + +func (x *RoomRPSConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomRPSConfig.ProtoReflect.Descriptor instead. +func (*RoomRPSConfig) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{19} +} + +func (x *RoomRPSConfig) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *RoomRPSConfig) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *RoomRPSConfig) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *RoomRPSConfig) GetChallengeTimeoutMs() int64 { + if x != nil { + return x.ChallengeTimeoutMs + } + return 0 +} + +func (x *RoomRPSConfig) GetRevealCountdownMs() int64 { + if x != nil { + return x.RevealCountdownMs + } + return 0 +} + +func (x *RoomRPSConfig) GetStakeGifts() []*RoomRPSStakeGift { + if x != nil { + return x.StakeGifts + } + return nil +} + +func (x *RoomRPSConfig) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *RoomRPSConfig) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type RoomRPSPlayer struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Gesture string `protobuf:"bytes,2,opt,name=gesture,proto3" json:"gesture,omitempty"` + Result string `protobuf:"bytes,3,opt,name=result,proto3" json:"result,omitempty"` + BalanceAfter int64 `protobuf:"varint,4,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` + JoinedAtMs int64 `protobuf:"varint,5,opt,name=joined_at_ms,json=joinedAtMs,proto3" json:"joined_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomRPSPlayer) Reset() { + *x = RoomRPSPlayer{} + mi := &file_proto_game_v1_game_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomRPSPlayer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomRPSPlayer) ProtoMessage() {} + +func (x *RoomRPSPlayer) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomRPSPlayer.ProtoReflect.Descriptor instead. +func (*RoomRPSPlayer) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{20} +} + +func (x *RoomRPSPlayer) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *RoomRPSPlayer) GetGesture() string { + if x != nil { + return x.Gesture + } + return "" +} + +func (x *RoomRPSPlayer) GetResult() string { + if x != nil { + return x.Result + } + return "" +} + +func (x *RoomRPSPlayer) GetBalanceAfter() int64 { + if x != nil { + return x.BalanceAfter + } + return 0 +} + +func (x *RoomRPSPlayer) GetJoinedAtMs() int64 { + if x != nil { + return x.JoinedAtMs + } + return 0 +} + +type RoomRPSChallenge struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + ChallengeId string `protobuf:"bytes,2,opt,name=challenge_id,json=challengeId,proto3" json:"challenge_id,omitempty"` + RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + RegionId int64 `protobuf:"varint,4,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + StakeGiftId int64 `protobuf:"varint,6,opt,name=stake_gift_id,json=stakeGiftId,proto3" json:"stake_gift_id,omitempty"` + StakeCoin int64 `protobuf:"varint,7,opt,name=stake_coin,json=stakeCoin,proto3" json:"stake_coin,omitempty"` + Initiator *RoomRPSPlayer `protobuf:"bytes,8,opt,name=initiator,proto3" json:"initiator,omitempty"` + Challenger *RoomRPSPlayer `protobuf:"bytes,9,opt,name=challenger,proto3" json:"challenger,omitempty"` + WinnerUserId int64 `protobuf:"varint,10,opt,name=winner_user_id,json=winnerUserId,proto3" json:"winner_user_id,omitempty"` + SettlementStatus string `protobuf:"bytes,11,opt,name=settlement_status,json=settlementStatus,proto3" json:"settlement_status,omitempty"` + FailureReason string `protobuf:"bytes,12,opt,name=failure_reason,json=failureReason,proto3" json:"failure_reason,omitempty"` + TimeoutAtMs int64 `protobuf:"varint,13,opt,name=timeout_at_ms,json=timeoutAtMs,proto3" json:"timeout_at_ms,omitempty"` + RevealAtMs int64 `protobuf:"varint,14,opt,name=reveal_at_ms,json=revealAtMs,proto3" json:"reveal_at_ms,omitempty"` + CreatedAtMs int64 `protobuf:"varint,15,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + MatchedAtMs int64 `protobuf:"varint,16,opt,name=matched_at_ms,json=matchedAtMs,proto3" json:"matched_at_ms,omitempty"` + SettledAtMs int64 `protobuf:"varint,17,opt,name=settled_at_ms,json=settledAtMs,proto3" json:"settled_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,18,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomRPSChallenge) Reset() { + *x = RoomRPSChallenge{} + mi := &file_proto_game_v1_game_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomRPSChallenge) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomRPSChallenge) ProtoMessage() {} + +func (x *RoomRPSChallenge) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomRPSChallenge.ProtoReflect.Descriptor instead. +func (*RoomRPSChallenge) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{21} +} + +func (x *RoomRPSChallenge) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *RoomRPSChallenge) GetChallengeId() string { + if x != nil { + return x.ChallengeId + } + return "" +} + +func (x *RoomRPSChallenge) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *RoomRPSChallenge) GetRegionId() int64 { + if x != nil { + return x.RegionId + } + return 0 +} + +func (x *RoomRPSChallenge) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *RoomRPSChallenge) GetStakeGiftId() int64 { + if x != nil { + return x.StakeGiftId + } + return 0 +} + +func (x *RoomRPSChallenge) GetStakeCoin() int64 { + if x != nil { + return x.StakeCoin + } + return 0 +} + +func (x *RoomRPSChallenge) GetInitiator() *RoomRPSPlayer { + if x != nil { + return x.Initiator + } + return nil +} + +func (x *RoomRPSChallenge) GetChallenger() *RoomRPSPlayer { + if x != nil { + return x.Challenger + } + return nil +} + +func (x *RoomRPSChallenge) GetWinnerUserId() int64 { + if x != nil { + return x.WinnerUserId + } + return 0 +} + +func (x *RoomRPSChallenge) GetSettlementStatus() string { + if x != nil { + return x.SettlementStatus + } + return "" +} + +func (x *RoomRPSChallenge) GetFailureReason() string { + if x != nil { + return x.FailureReason + } + return "" +} + +func (x *RoomRPSChallenge) GetTimeoutAtMs() int64 { + if x != nil { + return x.TimeoutAtMs + } + return 0 +} + +func (x *RoomRPSChallenge) GetRevealAtMs() int64 { + if x != nil { + return x.RevealAtMs + } + return 0 +} + +func (x *RoomRPSChallenge) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *RoomRPSChallenge) GetMatchedAtMs() int64 { + if x != nil { + return x.MatchedAtMs + } + return 0 +} + +func (x *RoomRPSChallenge) GetSettledAtMs() int64 { + if x != nil { + return x.SettledAtMs + } + return 0 +} + +func (x *RoomRPSChallenge) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +type GetRoomRPSConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRoomRPSConfigRequest) Reset() { + *x = GetRoomRPSConfigRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRoomRPSConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoomRPSConfigRequest) ProtoMessage() {} + +func (x *GetRoomRPSConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoomRPSConfigRequest.ProtoReflect.Descriptor instead. +func (*GetRoomRPSConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{22} +} + +func (x *GetRoomRPSConfigRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +type RoomRPSConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Config *RoomRPSConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomRPSConfigResponse) Reset() { + *x = RoomRPSConfigResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomRPSConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomRPSConfigResponse) ProtoMessage() {} + +func (x *RoomRPSConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomRPSConfigResponse.ProtoReflect.Descriptor instead. +func (*RoomRPSConfigResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{23} +} + +func (x *RoomRPSConfigResponse) GetConfig() *RoomRPSConfig { + if x != nil { + return x.Config + } + return nil +} + +func (x *RoomRPSConfigResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type CreateRoomRPSChallengeRequest 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"` + RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + RegionId int64 `protobuf:"varint,4,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + GiftId int64 `protobuf:"varint,5,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + Gesture string `protobuf:"bytes,6,opt,name=gesture,proto3" json:"gesture,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateRoomRPSChallengeRequest) Reset() { + *x = CreateRoomRPSChallengeRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateRoomRPSChallengeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRoomRPSChallengeRequest) ProtoMessage() {} + +func (x *CreateRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRoomRPSChallengeRequest.ProtoReflect.Descriptor instead. +func (*CreateRoomRPSChallengeRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{24} +} + +func (x *CreateRoomRPSChallengeRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *CreateRoomRPSChallengeRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *CreateRoomRPSChallengeRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *CreateRoomRPSChallengeRequest) GetRegionId() int64 { + if x != nil { + return x.RegionId + } + return 0 +} + +func (x *CreateRoomRPSChallengeRequest) GetGiftId() int64 { + if x != nil { + return x.GiftId + } + return 0 +} + +func (x *CreateRoomRPSChallengeRequest) GetGesture() string { + if x != nil { + return x.Gesture + } + return "" +} + +type AcceptRoomRPSChallengeRequest 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"` + ChallengeId string `protobuf:"bytes,3,opt,name=challenge_id,json=challengeId,proto3" json:"challenge_id,omitempty"` + Gesture string `protobuf:"bytes,4,opt,name=gesture,proto3" json:"gesture,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AcceptRoomRPSChallengeRequest) Reset() { + *x = AcceptRoomRPSChallengeRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AcceptRoomRPSChallengeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AcceptRoomRPSChallengeRequest) ProtoMessage() {} + +func (x *AcceptRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AcceptRoomRPSChallengeRequest.ProtoReflect.Descriptor instead. +func (*AcceptRoomRPSChallengeRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{25} +} + +func (x *AcceptRoomRPSChallengeRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *AcceptRoomRPSChallengeRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *AcceptRoomRPSChallengeRequest) GetChallengeId() string { + if x != nil { + return x.ChallengeId + } + return "" +} + +func (x *AcceptRoomRPSChallengeRequest) GetGesture() string { + if x != nil { + return x.Gesture + } + return "" +} + +type GetRoomRPSChallengeRequest 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"` + ChallengeId string `protobuf:"bytes,3,opt,name=challenge_id,json=challengeId,proto3" json:"challenge_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRoomRPSChallengeRequest) Reset() { + *x = GetRoomRPSChallengeRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRoomRPSChallengeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoomRPSChallengeRequest) ProtoMessage() {} + +func (x *GetRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoomRPSChallengeRequest.ProtoReflect.Descriptor instead. +func (*GetRoomRPSChallengeRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{26} +} + +func (x *GetRoomRPSChallengeRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *GetRoomRPSChallengeRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *GetRoomRPSChallengeRequest) GetChallengeId() string { + if x != nil { + return x.ChallengeId + } + return "" +} + +type ListRoomRPSChallengesRequest 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"` + RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + InitiatorUserId int64 `protobuf:"varint,5,opt,name=initiator_user_id,json=initiatorUserId,proto3" json:"initiator_user_id,omitempty"` + ChallengerUserId int64 `protobuf:"varint,6,opt,name=challenger_user_id,json=challengerUserId,proto3" json:"challenger_user_id,omitempty"` + StartTimeMs int64 `protobuf:"varint,7,opt,name=start_time_ms,json=startTimeMs,proto3" json:"start_time_ms,omitempty"` + EndTimeMs int64 `protobuf:"varint,8,opt,name=end_time_ms,json=endTimeMs,proto3" json:"end_time_ms,omitempty"` + PageSize int32 `protobuf:"varint,9,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + Cursor string `protobuf:"bytes,10,opt,name=cursor,proto3" json:"cursor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRoomRPSChallengesRequest) Reset() { + *x = ListRoomRPSChallengesRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoomRPSChallengesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoomRPSChallengesRequest) ProtoMessage() {} + +func (x *ListRoomRPSChallengesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoomRPSChallengesRequest.ProtoReflect.Descriptor instead. +func (*ListRoomRPSChallengesRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{27} +} + +func (x *ListRoomRPSChallengesRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListRoomRPSChallengesRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ListRoomRPSChallengesRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *ListRoomRPSChallengesRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ListRoomRPSChallengesRequest) GetInitiatorUserId() int64 { + if x != nil { + return x.InitiatorUserId + } + return 0 +} + +func (x *ListRoomRPSChallengesRequest) GetChallengerUserId() int64 { + if x != nil { + return x.ChallengerUserId + } + return 0 +} + +func (x *ListRoomRPSChallengesRequest) GetStartTimeMs() int64 { + if x != nil { + return x.StartTimeMs + } + return 0 +} + +func (x *ListRoomRPSChallengesRequest) GetEndTimeMs() int64 { + if x != nil { + return x.EndTimeMs + } + return 0 +} + +func (x *ListRoomRPSChallengesRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListRoomRPSChallengesRequest) GetCursor() string { + if x != nil { + return x.Cursor + } + return "" +} + +type RoomRPSChallengeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Challenge *RoomRPSChallenge `protobuf:"bytes,1,opt,name=challenge,proto3" json:"challenge,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomRPSChallengeResponse) Reset() { + *x = RoomRPSChallengeResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomRPSChallengeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomRPSChallengeResponse) ProtoMessage() {} + +func (x *RoomRPSChallengeResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomRPSChallengeResponse.ProtoReflect.Descriptor instead. +func (*RoomRPSChallengeResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{28} +} + +func (x *RoomRPSChallengeResponse) GetChallenge() *RoomRPSChallenge { + if x != nil { + return x.Challenge + } + return nil +} + +func (x *RoomRPSChallengeResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type ListRoomRPSChallengesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Challenges []*RoomRPSChallenge `protobuf:"bytes,1,rep,name=challenges,proto3" json:"challenges,omitempty"` + NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"` + PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + ServerTimeMs int64 `protobuf:"varint,4,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRoomRPSChallengesResponse) Reset() { + *x = ListRoomRPSChallengesResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoomRPSChallengesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoomRPSChallengesResponse) ProtoMessage() {} + +func (x *ListRoomRPSChallengesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoomRPSChallengesResponse.ProtoReflect.Descriptor instead. +func (*ListRoomRPSChallengesResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{29} +} + +func (x *ListRoomRPSChallengesResponse) GetChallenges() []*RoomRPSChallenge { + if x != nil { + return x.Challenges + } + return nil +} + +func (x *ListRoomRPSChallengesResponse) GetNextCursor() string { + if x != nil { + return x.NextCursor + } + return "" +} + +func (x *ListRoomRPSChallengesResponse) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListRoomRPSChallengesResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type UpdateRoomRPSConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Config *RoomRPSConfig `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateRoomRPSConfigRequest) Reset() { + *x = UpdateRoomRPSConfigRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateRoomRPSConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRoomRPSConfigRequest) ProtoMessage() {} + +func (x *UpdateRoomRPSConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRoomRPSConfigRequest.ProtoReflect.Descriptor instead. +func (*UpdateRoomRPSConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{30} +} + +func (x *UpdateRoomRPSConfigRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *UpdateRoomRPSConfigRequest) GetConfig() *RoomRPSConfig { + if x != nil { + return x.Config + } + return nil +} + +type RetryRoomRPSSettlementRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + ChallengeId string `protobuf:"bytes,2,opt,name=challenge_id,json=challengeId,proto3" json:"challenge_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RetryRoomRPSSettlementRequest) Reset() { + *x = RetryRoomRPSSettlementRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RetryRoomRPSSettlementRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetryRoomRPSSettlementRequest) ProtoMessage() {} + +func (x *RetryRoomRPSSettlementRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetryRoomRPSSettlementRequest.ProtoReflect.Descriptor instead. +func (*RetryRoomRPSSettlementRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{31} +} + +func (x *RetryRoomRPSSettlementRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *RetryRoomRPSSettlementRequest) GetChallengeId() string { + if x != nil { + return x.ChallengeId + } + return "" +} + +type ExpireRoomRPSChallengeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + ChallengeId string `protobuf:"bytes,2,opt,name=challenge_id,json=challengeId,proto3" json:"challenge_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExpireRoomRPSChallengeRequest) Reset() { + *x = ExpireRoomRPSChallengeRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExpireRoomRPSChallengeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpireRoomRPSChallengeRequest) ProtoMessage() {} + +func (x *ExpireRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExpireRoomRPSChallengeRequest.ProtoReflect.Descriptor instead. +func (*ExpireRoomRPSChallengeRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{32} +} + +func (x *ExpireRoomRPSChallengeRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ExpireRoomRPSChallengeRequest) GetChallengeId() string { + if x != nil { + return x.ChallengeId + } + return "" +} + type CreateDiceMatchRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -1841,7 +2981,7 @@ type CreateDiceMatchRequest struct { func (x *CreateDiceMatchRequest) Reset() { *x = CreateDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[18] + mi := &file_proto_game_v1_game_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1853,7 +2993,7 @@ func (x *CreateDiceMatchRequest) String() string { func (*CreateDiceMatchRequest) ProtoMessage() {} func (x *CreateDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[18] + mi := &file_proto_game_v1_game_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1866,7 +3006,7 @@ func (x *CreateDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateDiceMatchRequest.ProtoReflect.Descriptor instead. func (*CreateDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{18} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{33} } func (x *CreateDiceMatchRequest) GetMeta() *RequestMeta { @@ -1936,7 +3076,7 @@ type JoinDiceMatchRequest struct { func (x *JoinDiceMatchRequest) Reset() { *x = JoinDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[19] + mi := &file_proto_game_v1_game_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1948,7 +3088,7 @@ func (x *JoinDiceMatchRequest) String() string { func (*JoinDiceMatchRequest) ProtoMessage() {} func (x *JoinDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[19] + mi := &file_proto_game_v1_game_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1961,7 +3101,7 @@ func (x *JoinDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinDiceMatchRequest.ProtoReflect.Descriptor instead. func (*JoinDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{19} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{34} } func (x *JoinDiceMatchRequest) GetMeta() *RequestMeta { @@ -1996,7 +3136,7 @@ type GetDiceMatchRequest struct { func (x *GetDiceMatchRequest) Reset() { *x = GetDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[20] + mi := &file_proto_game_v1_game_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2008,7 +3148,7 @@ func (x *GetDiceMatchRequest) String() string { func (*GetDiceMatchRequest) ProtoMessage() {} func (x *GetDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[20] + mi := &file_proto_game_v1_game_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2021,7 +3161,7 @@ func (x *GetDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiceMatchRequest.ProtoReflect.Descriptor instead. func (*GetDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{20} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{35} } func (x *GetDiceMatchRequest) GetMeta() *RequestMeta { @@ -2056,7 +3196,7 @@ type RollDiceMatchRequest struct { func (x *RollDiceMatchRequest) Reset() { *x = RollDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[21] + mi := &file_proto_game_v1_game_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2068,7 +3208,7 @@ func (x *RollDiceMatchRequest) String() string { func (*RollDiceMatchRequest) ProtoMessage() {} func (x *RollDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[21] + mi := &file_proto_game_v1_game_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2081,7 +3221,7 @@ func (x *RollDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RollDiceMatchRequest.ProtoReflect.Descriptor instead. func (*RollDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{21} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{36} } func (x *RollDiceMatchRequest) GetMeta() *RequestMeta { @@ -2119,7 +3259,7 @@ type MatchDiceRequest struct { func (x *MatchDiceRequest) Reset() { *x = MatchDiceRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[22] + mi := &file_proto_game_v1_game_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2131,7 +3271,7 @@ func (x *MatchDiceRequest) String() string { func (*MatchDiceRequest) ProtoMessage() {} func (x *MatchDiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[22] + mi := &file_proto_game_v1_game_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2144,7 +3284,7 @@ func (x *MatchDiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MatchDiceRequest.ProtoReflect.Descriptor instead. func (*MatchDiceRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{22} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{37} } func (x *MatchDiceRequest) GetMeta() *RequestMeta { @@ -2200,7 +3340,7 @@ type CancelDiceMatchRequest struct { func (x *CancelDiceMatchRequest) Reset() { *x = CancelDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[23] + mi := &file_proto_game_v1_game_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2212,7 +3352,7 @@ func (x *CancelDiceMatchRequest) String() string { func (*CancelDiceMatchRequest) ProtoMessage() {} func (x *CancelDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[23] + mi := &file_proto_game_v1_game_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2225,7 +3365,7 @@ func (x *CancelDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelDiceMatchRequest.ProtoReflect.Descriptor instead. func (*CancelDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{23} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{38} } func (x *CancelDiceMatchRequest) GetMeta() *RequestMeta { @@ -2259,7 +3399,7 @@ type DiceMatchResponse struct { func (x *DiceMatchResponse) Reset() { *x = DiceMatchResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[24] + mi := &file_proto_game_v1_game_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2271,7 +3411,7 @@ func (x *DiceMatchResponse) String() string { func (*DiceMatchResponse) ProtoMessage() {} func (x *DiceMatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[24] + mi := &file_proto_game_v1_game_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2284,7 +3424,7 @@ func (x *DiceMatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceMatchResponse.ProtoReflect.Descriptor instead. func (*DiceMatchResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{24} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{39} } func (x *DiceMatchResponse) GetMatch() *DiceMatch { @@ -2316,7 +3456,7 @@ type CallbackRequest struct { func (x *CallbackRequest) Reset() { *x = CallbackRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[25] + mi := &file_proto_game_v1_game_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2328,7 +3468,7 @@ func (x *CallbackRequest) String() string { func (*CallbackRequest) ProtoMessage() {} func (x *CallbackRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[25] + mi := &file_proto_game_v1_game_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2341,7 +3481,7 @@ func (x *CallbackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackRequest.ProtoReflect.Descriptor instead. func (*CallbackRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{25} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{40} } func (x *CallbackRequest) GetMeta() *RequestMeta { @@ -2403,7 +3543,7 @@ type CallbackResponse struct { func (x *CallbackResponse) Reset() { *x = CallbackResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[26] + mi := &file_proto_game_v1_game_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2415,7 +3555,7 @@ func (x *CallbackResponse) String() string { func (*CallbackResponse) ProtoMessage() {} func (x *CallbackResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[26] + mi := &file_proto_game_v1_game_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2428,7 +3568,7 @@ func (x *CallbackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackResponse.ProtoReflect.Descriptor instead. func (*CallbackResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{26} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{41} } func (x *CallbackResponse) GetRawBody() []byte { @@ -2458,7 +3598,7 @@ type CronBatchRequest struct { func (x *CronBatchRequest) Reset() { *x = CronBatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[27] + mi := &file_proto_game_v1_game_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2470,7 +3610,7 @@ func (x *CronBatchRequest) String() string { func (*CronBatchRequest) ProtoMessage() {} func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[27] + mi := &file_proto_game_v1_game_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2483,7 +3623,7 @@ func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchRequest.ProtoReflect.Descriptor instead. func (*CronBatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{27} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{42} } func (x *CronBatchRequest) GetMeta() *RequestMeta { @@ -2534,7 +3674,7 @@ type CronBatchResponse struct { func (x *CronBatchResponse) Reset() { *x = CronBatchResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[28] + mi := &file_proto_game_v1_game_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2546,7 +3686,7 @@ func (x *CronBatchResponse) String() string { func (*CronBatchResponse) ProtoMessage() {} func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[28] + mi := &file_proto_game_v1_game_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2559,7 +3699,7 @@ func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchResponse.ProtoReflect.Descriptor instead. func (*CronBatchResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{28} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{43} } func (x *CronBatchResponse) GetClaimedCount() int32 { @@ -2607,7 +3747,7 @@ type ListPlatformsRequest struct { func (x *ListPlatformsRequest) Reset() { *x = ListPlatformsRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[29] + mi := &file_proto_game_v1_game_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2619,7 +3759,7 @@ func (x *ListPlatformsRequest) String() string { func (*ListPlatformsRequest) ProtoMessage() {} func (x *ListPlatformsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[29] + mi := &file_proto_game_v1_game_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2632,7 +3772,7 @@ func (x *ListPlatformsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPlatformsRequest.ProtoReflect.Descriptor instead. func (*ListPlatformsRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{29} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{44} } func (x *ListPlatformsRequest) GetMeta() *RequestMeta { @@ -2659,7 +3799,7 @@ type ListPlatformsResponse struct { func (x *ListPlatformsResponse) Reset() { *x = ListPlatformsResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[30] + mi := &file_proto_game_v1_game_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2671,7 +3811,7 @@ func (x *ListPlatformsResponse) String() string { func (*ListPlatformsResponse) ProtoMessage() {} func (x *ListPlatformsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[30] + mi := &file_proto_game_v1_game_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2684,7 +3824,7 @@ func (x *ListPlatformsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPlatformsResponse.ProtoReflect.Descriptor instead. func (*ListPlatformsResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{30} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{45} } func (x *ListPlatformsResponse) GetPlatforms() []*GamePlatform { @@ -2711,7 +3851,7 @@ type UpsertPlatformRequest struct { func (x *UpsertPlatformRequest) Reset() { *x = UpsertPlatformRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[31] + mi := &file_proto_game_v1_game_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2723,7 +3863,7 @@ func (x *UpsertPlatformRequest) String() string { func (*UpsertPlatformRequest) ProtoMessage() {} func (x *UpsertPlatformRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[31] + mi := &file_proto_game_v1_game_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2736,7 +3876,7 @@ func (x *UpsertPlatformRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertPlatformRequest.ProtoReflect.Descriptor instead. func (*UpsertPlatformRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{31} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{46} } func (x *UpsertPlatformRequest) GetMeta() *RequestMeta { @@ -2763,7 +3903,7 @@ type PlatformResponse struct { func (x *PlatformResponse) Reset() { *x = PlatformResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[32] + mi := &file_proto_game_v1_game_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2775,7 +3915,7 @@ func (x *PlatformResponse) String() string { func (*PlatformResponse) ProtoMessage() {} func (x *PlatformResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[32] + mi := &file_proto_game_v1_game_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2788,7 +3928,7 @@ func (x *PlatformResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PlatformResponse.ProtoReflect.Descriptor instead. func (*PlatformResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{32} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{47} } func (x *PlatformResponse) GetPlatform() *GamePlatform { @@ -2818,7 +3958,7 @@ type ListCatalogRequest struct { func (x *ListCatalogRequest) Reset() { *x = ListCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[33] + mi := &file_proto_game_v1_game_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2830,7 +3970,7 @@ func (x *ListCatalogRequest) String() string { func (*ListCatalogRequest) ProtoMessage() {} func (x *ListCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[33] + mi := &file_proto_game_v1_game_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2843,7 +3983,7 @@ func (x *ListCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCatalogRequest.ProtoReflect.Descriptor instead. func (*ListCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{33} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{48} } func (x *ListCatalogRequest) GetMeta() *RequestMeta { @@ -2892,7 +4032,7 @@ type ListCatalogResponse struct { func (x *ListCatalogResponse) Reset() { *x = ListCatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[34] + mi := &file_proto_game_v1_game_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2904,7 +4044,7 @@ func (x *ListCatalogResponse) String() string { func (*ListCatalogResponse) ProtoMessage() {} func (x *ListCatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[34] + mi := &file_proto_game_v1_game_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2917,7 +4057,7 @@ func (x *ListCatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCatalogResponse.ProtoReflect.Descriptor instead. func (*ListCatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{34} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{49} } func (x *ListCatalogResponse) GetGames() []*GameCatalogItem { @@ -2951,7 +4091,7 @@ type UpsertCatalogRequest struct { func (x *UpsertCatalogRequest) Reset() { *x = UpsertCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[35] + mi := &file_proto_game_v1_game_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2963,7 +4103,7 @@ func (x *UpsertCatalogRequest) String() string { func (*UpsertCatalogRequest) ProtoMessage() {} func (x *UpsertCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[35] + mi := &file_proto_game_v1_game_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2976,7 +4116,7 @@ func (x *UpsertCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertCatalogRequest.ProtoReflect.Descriptor instead. func (*UpsertCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{35} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{50} } func (x *UpsertCatalogRequest) GetMeta() *RequestMeta { @@ -3003,7 +4143,7 @@ type CatalogResponse struct { func (x *CatalogResponse) Reset() { *x = CatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[36] + mi := &file_proto_game_v1_game_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3015,7 +4155,7 @@ func (x *CatalogResponse) String() string { func (*CatalogResponse) ProtoMessage() {} func (x *CatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[36] + mi := &file_proto_game_v1_game_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3028,7 +4168,7 @@ func (x *CatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CatalogResponse.ProtoReflect.Descriptor instead. func (*CatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{36} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{51} } func (x *CatalogResponse) GetGame() *GameCatalogItem { @@ -3056,7 +4196,7 @@ type SetGameStatusRequest struct { func (x *SetGameStatusRequest) Reset() { *x = SetGameStatusRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[37] + mi := &file_proto_game_v1_game_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3068,7 +4208,7 @@ func (x *SetGameStatusRequest) String() string { func (*SetGameStatusRequest) ProtoMessage() {} func (x *SetGameStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[37] + mi := &file_proto_game_v1_game_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3081,7 +4221,7 @@ func (x *SetGameStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetGameStatusRequest.ProtoReflect.Descriptor instead. func (*SetGameStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{37} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{52} } func (x *SetGameStatusRequest) GetMeta() *RequestMeta { @@ -3115,7 +4255,7 @@ type DeleteCatalogRequest struct { func (x *DeleteCatalogRequest) Reset() { *x = DeleteCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[38] + mi := &file_proto_game_v1_game_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3127,7 +4267,7 @@ func (x *DeleteCatalogRequest) String() string { func (*DeleteCatalogRequest) ProtoMessage() {} func (x *DeleteCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[38] + mi := &file_proto_game_v1_game_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3140,7 +4280,7 @@ func (x *DeleteCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteCatalogRequest.ProtoReflect.Descriptor instead. func (*DeleteCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{38} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{53} } func (x *DeleteCatalogRequest) GetMeta() *RequestMeta { @@ -3166,7 +4306,7 @@ type DeleteCatalogResponse struct { func (x *DeleteCatalogResponse) Reset() { *x = DeleteCatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[39] + mi := &file_proto_game_v1_game_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3178,7 +4318,7 @@ func (x *DeleteCatalogResponse) String() string { func (*DeleteCatalogResponse) ProtoMessage() {} func (x *DeleteCatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[39] + mi := &file_proto_game_v1_game_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3191,7 +4331,7 @@ func (x *DeleteCatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteCatalogResponse.ProtoReflect.Descriptor instead. func (*DeleteCatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{39} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{54} } func (x *DeleteCatalogResponse) GetServerTimeMs() int64 { @@ -3210,7 +4350,7 @@ type ListSelfGamesRequest struct { func (x *ListSelfGamesRequest) Reset() { *x = ListSelfGamesRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[40] + mi := &file_proto_game_v1_game_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3222,7 +4362,7 @@ func (x *ListSelfGamesRequest) String() string { func (*ListSelfGamesRequest) ProtoMessage() {} func (x *ListSelfGamesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[40] + mi := &file_proto_game_v1_game_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3235,7 +4375,7 @@ func (x *ListSelfGamesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSelfGamesRequest.ProtoReflect.Descriptor instead. func (*ListSelfGamesRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{40} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{55} } func (x *ListSelfGamesRequest) GetMeta() *RequestMeta { @@ -3255,7 +4395,7 @@ type ListSelfGamesResponse struct { func (x *ListSelfGamesResponse) Reset() { *x = ListSelfGamesResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[41] + mi := &file_proto_game_v1_game_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3267,7 +4407,7 @@ func (x *ListSelfGamesResponse) String() string { func (*ListSelfGamesResponse) ProtoMessage() {} func (x *ListSelfGamesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[41] + mi := &file_proto_game_v1_game_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3280,7 +4420,7 @@ func (x *ListSelfGamesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSelfGamesResponse.ProtoReflect.Descriptor instead. func (*ListSelfGamesResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{41} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{56} } func (x *ListSelfGamesResponse) GetGames() []*DiceConfig { @@ -3307,7 +4447,7 @@ type UpdateDiceConfigRequest struct { func (x *UpdateDiceConfigRequest) Reset() { *x = UpdateDiceConfigRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[42] + mi := &file_proto_game_v1_game_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3319,7 +4459,7 @@ func (x *UpdateDiceConfigRequest) String() string { func (*UpdateDiceConfigRequest) ProtoMessage() {} func (x *UpdateDiceConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[42] + mi := &file_proto_game_v1_game_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3332,7 +4472,7 @@ func (x *UpdateDiceConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateDiceConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateDiceConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{42} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{57} } func (x *UpdateDiceConfigRequest) GetMeta() *RequestMeta { @@ -3364,7 +4504,7 @@ type DicePoolAdjustment struct { func (x *DicePoolAdjustment) Reset() { *x = DicePoolAdjustment{} - mi := &file_proto_game_v1_game_proto_msgTypes[43] + mi := &file_proto_game_v1_game_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3376,7 +4516,7 @@ func (x *DicePoolAdjustment) String() string { func (*DicePoolAdjustment) ProtoMessage() {} func (x *DicePoolAdjustment) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[43] + mi := &file_proto_game_v1_game_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3389,7 +4529,7 @@ func (x *DicePoolAdjustment) ProtoReflect() protoreflect.Message { // Deprecated: Use DicePoolAdjustment.ProtoReflect.Descriptor instead. func (*DicePoolAdjustment) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{43} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{58} } func (x *DicePoolAdjustment) GetAdjustmentId() string { @@ -3454,7 +4594,7 @@ type AdjustDicePoolRequest struct { func (x *AdjustDicePoolRequest) Reset() { *x = AdjustDicePoolRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[44] + mi := &file_proto_game_v1_game_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3466,7 +4606,7 @@ func (x *AdjustDicePoolRequest) String() string { func (*AdjustDicePoolRequest) ProtoMessage() {} func (x *AdjustDicePoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[44] + mi := &file_proto_game_v1_game_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3479,7 +4619,7 @@ func (x *AdjustDicePoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdjustDicePoolRequest.ProtoReflect.Descriptor instead. func (*AdjustDicePoolRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{44} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{59} } func (x *AdjustDicePoolRequest) GetMeta() *RequestMeta { @@ -3528,7 +4668,7 @@ type AdjustDicePoolResponse struct { func (x *AdjustDicePoolResponse) Reset() { *x = AdjustDicePoolResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[45] + mi := &file_proto_game_v1_game_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3540,7 +4680,7 @@ func (x *AdjustDicePoolResponse) String() string { func (*AdjustDicePoolResponse) ProtoMessage() {} func (x *AdjustDicePoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[45] + mi := &file_proto_game_v1_game_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3553,7 +4693,7 @@ func (x *AdjustDicePoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdjustDicePoolResponse.ProtoReflect.Descriptor instead. func (*AdjustDicePoolResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{45} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{60} } func (x *AdjustDicePoolResponse) GetAdjustment() *DicePoolAdjustment { @@ -3592,7 +4732,7 @@ type DiceRobot struct { func (x *DiceRobot) Reset() { *x = DiceRobot{} - mi := &file_proto_game_v1_game_proto_msgTypes[46] + mi := &file_proto_game_v1_game_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3604,7 +4744,7 @@ func (x *DiceRobot) String() string { func (*DiceRobot) ProtoMessage() {} func (x *DiceRobot) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[46] + mi := &file_proto_game_v1_game_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3617,7 +4757,7 @@ func (x *DiceRobot) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceRobot.ProtoReflect.Descriptor instead. func (*DiceRobot) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{46} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{61} } func (x *DiceRobot) GetAppCode() string { @@ -3682,7 +4822,7 @@ type ListDiceRobotsRequest struct { func (x *ListDiceRobotsRequest) Reset() { *x = ListDiceRobotsRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[47] + mi := &file_proto_game_v1_game_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3694,7 +4834,7 @@ func (x *ListDiceRobotsRequest) String() string { func (*ListDiceRobotsRequest) ProtoMessage() {} func (x *ListDiceRobotsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[47] + mi := &file_proto_game_v1_game_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3707,7 +4847,7 @@ func (x *ListDiceRobotsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDiceRobotsRequest.ProtoReflect.Descriptor instead. func (*ListDiceRobotsRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{47} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{62} } func (x *ListDiceRobotsRequest) GetMeta() *RequestMeta { @@ -3756,7 +4896,7 @@ type ListDiceRobotsResponse struct { func (x *ListDiceRobotsResponse) Reset() { *x = ListDiceRobotsResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[48] + mi := &file_proto_game_v1_game_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3768,7 +4908,7 @@ func (x *ListDiceRobotsResponse) String() string { func (*ListDiceRobotsResponse) ProtoMessage() {} func (x *ListDiceRobotsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[48] + mi := &file_proto_game_v1_game_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3781,7 +4921,7 @@ func (x *ListDiceRobotsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDiceRobotsResponse.ProtoReflect.Descriptor instead. func (*ListDiceRobotsResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{48} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{63} } func (x *ListDiceRobotsResponse) GetRobots() []*DiceRobot { @@ -3816,7 +4956,7 @@ type RegisterDiceRobotsRequest struct { func (x *RegisterDiceRobotsRequest) Reset() { *x = RegisterDiceRobotsRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[49] + mi := &file_proto_game_v1_game_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3828,7 +4968,7 @@ func (x *RegisterDiceRobotsRequest) String() string { func (*RegisterDiceRobotsRequest) ProtoMessage() {} func (x *RegisterDiceRobotsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[49] + mi := &file_proto_game_v1_game_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3841,7 +4981,7 @@ func (x *RegisterDiceRobotsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterDiceRobotsRequest.ProtoReflect.Descriptor instead. func (*RegisterDiceRobotsRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{49} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{64} } func (x *RegisterDiceRobotsRequest) GetMeta() *RequestMeta { @@ -3875,7 +5015,7 @@ type RegisterDiceRobotsResponse struct { func (x *RegisterDiceRobotsResponse) Reset() { *x = RegisterDiceRobotsResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[50] + mi := &file_proto_game_v1_game_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3887,7 +5027,7 @@ func (x *RegisterDiceRobotsResponse) String() string { func (*RegisterDiceRobotsResponse) ProtoMessage() {} func (x *RegisterDiceRobotsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[50] + mi := &file_proto_game_v1_game_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3900,7 +5040,7 @@ func (x *RegisterDiceRobotsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterDiceRobotsResponse.ProtoReflect.Descriptor instead. func (*RegisterDiceRobotsResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{50} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{65} } func (x *RegisterDiceRobotsResponse) GetRobots() []*DiceRobot { @@ -3929,7 +5069,7 @@ type SetDiceRobotStatusRequest struct { func (x *SetDiceRobotStatusRequest) Reset() { *x = SetDiceRobotStatusRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[51] + mi := &file_proto_game_v1_game_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3941,7 +5081,7 @@ func (x *SetDiceRobotStatusRequest) String() string { func (*SetDiceRobotStatusRequest) ProtoMessage() {} func (x *SetDiceRobotStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[51] + mi := &file_proto_game_v1_game_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3954,7 +5094,7 @@ func (x *SetDiceRobotStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetDiceRobotStatusRequest.ProtoReflect.Descriptor instead. func (*SetDiceRobotStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{51} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{66} } func (x *SetDiceRobotStatusRequest) GetMeta() *RequestMeta { @@ -3996,7 +5136,7 @@ type DeleteDiceRobotRequest struct { func (x *DeleteDiceRobotRequest) Reset() { *x = DeleteDiceRobotRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[52] + mi := &file_proto_game_v1_game_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4008,7 +5148,7 @@ func (x *DeleteDiceRobotRequest) String() string { func (*DeleteDiceRobotRequest) ProtoMessage() {} func (x *DeleteDiceRobotRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[52] + mi := &file_proto_game_v1_game_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4021,7 +5161,7 @@ func (x *DeleteDiceRobotRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteDiceRobotRequest.ProtoReflect.Descriptor instead. func (*DeleteDiceRobotRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{52} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{67} } func (x *DeleteDiceRobotRequest) GetMeta() *RequestMeta { @@ -4055,7 +5195,7 @@ type DeleteDiceRobotResponse struct { func (x *DeleteDiceRobotResponse) Reset() { *x = DeleteDiceRobotResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[53] + mi := &file_proto_game_v1_game_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4067,7 +5207,7 @@ func (x *DeleteDiceRobotResponse) String() string { func (*DeleteDiceRobotResponse) ProtoMessage() {} func (x *DeleteDiceRobotResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[53] + mi := &file_proto_game_v1_game_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4080,7 +5220,7 @@ func (x *DeleteDiceRobotResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteDiceRobotResponse.ProtoReflect.Descriptor instead. func (*DeleteDiceRobotResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{53} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{68} } func (x *DeleteDiceRobotResponse) GetDeleted() bool { @@ -4107,7 +5247,7 @@ type DiceRobotResponse struct { func (x *DiceRobotResponse) Reset() { *x = DiceRobotResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[54] + mi := &file_proto_game_v1_game_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4119,7 +5259,7 @@ func (x *DiceRobotResponse) String() string { func (*DiceRobotResponse) ProtoMessage() {} func (x *DiceRobotResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[54] + mi := &file_proto_game_v1_game_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4132,7 +5272,7 @@ func (x *DiceRobotResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceRobotResponse.ProtoReflect.Descriptor instead. func (*DiceRobotResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{54} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{69} } func (x *DiceRobotResponse) GetRobot() *DiceRobot { @@ -4347,7 +5487,109 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\agame_id\x18\x02 \x01(\tR\x06gameId\"m\n" + "\x12DiceConfigResponse\x121\n" + "\x06config\x18\x01 \x01(\v2\x19.hyapp.game.v1.DiceConfigR\x06config\x12$\n" + - "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\x91\x02\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xcd\x01\n" + + "\x10RoomRPSStakeGift\x12\x17\n" + + "\agift_id\x18\x01 \x01(\x03R\x06giftId\x12\x1b\n" + + "\tgift_name\x18\x02 \x01(\tR\bgiftName\x12\"\n" + + "\rgift_icon_url\x18\x03 \x01(\tR\vgiftIconUrl\x12&\n" + + "\x0fgift_price_coin\x18\x04 \x01(\x03R\rgiftPriceCoin\x12\x18\n" + + "\aenabled\x18\x05 \x01(\bR\aenabled\x12\x1d\n" + + "\n" + + "sort_order\x18\x06 \x01(\x05R\tsortOrder\"\xc7\x02\n" + + "\rRoomRPSConfig\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x17\n" + + "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x120\n" + + "\x14challenge_timeout_ms\x18\x04 \x01(\x03R\x12challengeTimeoutMs\x12.\n" + + "\x13reveal_countdown_ms\x18\x05 \x01(\x03R\x11revealCountdownMs\x12@\n" + + "\vstake_gifts\x18\x06 \x03(\v2\x1f.hyapp.game.v1.RoomRPSStakeGiftR\n" + + "stakeGifts\x12\"\n" + + "\rcreated_at_ms\x18\a \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\b \x01(\x03R\vupdatedAtMs\"\xa1\x01\n" + + "\rRoomRPSPlayer\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x18\n" + + "\agesture\x18\x02 \x01(\tR\agesture\x12\x16\n" + + "\x06result\x18\x03 \x01(\tR\x06result\x12#\n" + + "\rbalance_after\x18\x04 \x01(\x03R\fbalanceAfter\x12 \n" + + "\fjoined_at_ms\x18\x05 \x01(\x03R\n" + + "joinedAtMs\"\xab\x05\n" + + "\x10RoomRPSChallenge\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12!\n" + + "\fchallenge_id\x18\x02 \x01(\tR\vchallengeId\x12\x17\n" + + "\aroom_id\x18\x03 \x01(\tR\x06roomId\x12\x1b\n" + + "\tregion_id\x18\x04 \x01(\x03R\bregionId\x12\x16\n" + + "\x06status\x18\x05 \x01(\tR\x06status\x12\"\n" + + "\rstake_gift_id\x18\x06 \x01(\x03R\vstakeGiftId\x12\x1d\n" + + "\n" + + "stake_coin\x18\a \x01(\x03R\tstakeCoin\x12:\n" + + "\tinitiator\x18\b \x01(\v2\x1c.hyapp.game.v1.RoomRPSPlayerR\tinitiator\x12<\n" + + "\n" + + "challenger\x18\t \x01(\v2\x1c.hyapp.game.v1.RoomRPSPlayerR\n" + + "challenger\x12$\n" + + "\x0ewinner_user_id\x18\n" + + " \x01(\x03R\fwinnerUserId\x12+\n" + + "\x11settlement_status\x18\v \x01(\tR\x10settlementStatus\x12%\n" + + "\x0efailure_reason\x18\f \x01(\tR\rfailureReason\x12\"\n" + + "\rtimeout_at_ms\x18\r \x01(\x03R\vtimeoutAtMs\x12 \n" + + "\freveal_at_ms\x18\x0e \x01(\x03R\n" + + "revealAtMs\x12\"\n" + + "\rcreated_at_ms\x18\x0f \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rmatched_at_ms\x18\x10 \x01(\x03R\vmatchedAtMs\x12\"\n" + + "\rsettled_at_ms\x18\x11 \x01(\x03R\vsettledAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x12 \x01(\x03R\vupdatedAtMs\"I\n" + + "\x17GetRoomRPSConfigRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\"s\n" + + "\x15RoomRPSConfigResponse\x124\n" + + "\x06config\x18\x01 \x01(\v2\x1c.hyapp.game.v1.RoomRPSConfigR\x06config\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xd1\x01\n" + + "\x1dCreateRoomRPSChallengeRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x17\n" + + "\aroom_id\x18\x03 \x01(\tR\x06roomId\x12\x1b\n" + + "\tregion_id\x18\x04 \x01(\x03R\bregionId\x12\x17\n" + + "\agift_id\x18\x05 \x01(\x03R\x06giftId\x12\x18\n" + + "\agesture\x18\x06 \x01(\tR\agesture\"\xa5\x01\n" + + "\x1dAcceptRoomRPSChallengeRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12!\n" + + "\fchallenge_id\x18\x03 \x01(\tR\vchallengeId\x12\x18\n" + + "\agesture\x18\x04 \x01(\tR\agesture\"\x88\x01\n" + + "\x1aGetRoomRPSChallengeRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12!\n" + + "\fchallenge_id\x18\x03 \x01(\tR\vchallengeId\"\xeb\x02\n" + + "\x1cListRoomRPSChallengesRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x17\n" + + "\aroom_id\x18\x03 \x01(\tR\x06roomId\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x12*\n" + + "\x11initiator_user_id\x18\x05 \x01(\x03R\x0finitiatorUserId\x12,\n" + + "\x12challenger_user_id\x18\x06 \x01(\x03R\x10challengerUserId\x12\"\n" + + "\rstart_time_ms\x18\a \x01(\x03R\vstartTimeMs\x12\x1e\n" + + "\vend_time_ms\x18\b \x01(\x03R\tendTimeMs\x12\x1b\n" + + "\tpage_size\x18\t \x01(\x05R\bpageSize\x12\x16\n" + + "\x06cursor\x18\n" + + " \x01(\tR\x06cursor\"\x7f\n" + + "\x18RoomRPSChallengeResponse\x12=\n" + + "\tchallenge\x18\x01 \x01(\v2\x1f.hyapp.game.v1.RoomRPSChallengeR\tchallenge\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xc4\x01\n" + + "\x1dListRoomRPSChallengesResponse\x12?\n" + + "\n" + + "challenges\x18\x01 \x03(\v2\x1f.hyapp.game.v1.RoomRPSChallengeR\n" + + "challenges\x12\x1f\n" + + "\vnext_cursor\x18\x02 \x01(\tR\n" + + "nextCursor\x12\x1b\n" + + "\tpage_size\x18\x03 \x01(\x05R\bpageSize\x12$\n" + + "\x0eserver_time_ms\x18\x04 \x01(\x03R\fserverTimeMs\"\x82\x01\n" + + "\x1aUpdateRoomRPSConfigRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x124\n" + + "\x06config\x18\x02 \x01(\v2\x1c.hyapp.game.v1.RoomRPSConfigR\x06config\"r\n" + + "\x1dRetryRoomRPSSettlementRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12!\n" + + "\fchallenge_id\x18\x02 \x01(\tR\vchallengeId\"r\n" + + "\x1dExpireRoomRPSChallengeRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12!\n" + + "\fchallenge_id\x18\x02 \x01(\tR\vchallengeId\"\x91\x02\n" + "\x16CreateDiceMatchRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x17\n" + @@ -4527,7 +5769,7 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"i\n" + "\x11DiceRobotResponse\x12.\n" + "\x05robot\x18\x01 \x01(\v2\x18.hyapp.game.v1.DiceRobotR\x05robot\x12$\n" + - "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs2\xd8\a\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs2\xfb\v\n" + "\x0eGameAppService\x12N\n" + "\tListGames\x12\x1f.hyapp.game.v1.ListGamesRequest\x1a .hyapp.game.v1.ListGamesResponse\x12Z\n" + "\x0fListRecentGames\x12%.hyapp.game.v1.ListRecentGamesRequest\x1a .hyapp.game.v1.ListGamesResponse\x12`\n" + @@ -4540,11 +5782,16 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\rJoinDiceMatch\x12#.hyapp.game.v1.JoinDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12T\n" + "\fGetDiceMatch\x12\".hyapp.game.v1.GetDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12V\n" + "\rRollDiceMatch\x12#.hyapp.game.v1.RollDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12Z\n" + - "\x0fCancelDiceMatch\x12%.hyapp.game.v1.CancelDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse2h\n" + + "\x0fCancelDiceMatch\x12%.hyapp.game.v1.CancelDiceMatchRequest\x1a .hyapp.game.v1.DiceMatchResponse\x12`\n" + + "\x10GetRoomRPSConfig\x12&.hyapp.game.v1.GetRoomRPSConfigRequest\x1a$.hyapp.game.v1.RoomRPSConfigResponse\x12r\n" + + "\x15ListRoomRPSChallenges\x12+.hyapp.game.v1.ListRoomRPSChallengesRequest\x1a,.hyapp.game.v1.ListRoomRPSChallengesResponse\x12o\n" + + "\x16CreateRoomRPSChallenge\x12,.hyapp.game.v1.CreateRoomRPSChallengeRequest\x1a'.hyapp.game.v1.RoomRPSChallengeResponse\x12o\n" + + "\x16AcceptRoomRPSChallenge\x12,.hyapp.game.v1.AcceptRoomRPSChallengeRequest\x1a'.hyapp.game.v1.RoomRPSChallengeResponse\x12i\n" + + "\x13GetRoomRPSChallenge\x12).hyapp.game.v1.GetRoomRPSChallengeRequest\x1a'.hyapp.game.v1.RoomRPSChallengeResponse2h\n" + "\x13GameCallbackService\x12Q\n" + "\x0eHandleCallback\x12\x1e.hyapp.game.v1.CallbackRequest\x1a\x1f.hyapp.game.v1.CallbackResponse2t\n" + "\x0fGameCronService\x12a\n" + - "\x1cProcessLevelEventOutboxBatch\x12\x1f.hyapp.game.v1.CronBatchRequest\x1a .hyapp.game.v1.CronBatchResponse2\xcd\t\n" + + "\x1cProcessLevelEventOutboxBatch\x12\x1f.hyapp.game.v1.CronBatchRequest\x1a .hyapp.game.v1.CronBatchResponse2\xd8\x0e\n" + "\x10GameAdminService\x12Z\n" + "\rListPlatforms\x12#.hyapp.game.v1.ListPlatformsRequest\x1a$.hyapp.game.v1.ListPlatformsResponse\x12W\n" + "\x0eUpsertPlatform\x12$.hyapp.game.v1.UpsertPlatformRequest\x1a\x1f.hyapp.game.v1.PlatformResponse\x12T\n" + @@ -4558,7 +5805,13 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\x0eListDiceRobots\x12$.hyapp.game.v1.ListDiceRobotsRequest\x1a%.hyapp.game.v1.ListDiceRobotsResponse\x12i\n" + "\x12RegisterDiceRobots\x12(.hyapp.game.v1.RegisterDiceRobotsRequest\x1a).hyapp.game.v1.RegisterDiceRobotsResponse\x12`\n" + "\x12SetDiceRobotStatus\x12(.hyapp.game.v1.SetDiceRobotStatusRequest\x1a .hyapp.game.v1.DiceRobotResponse\x12`\n" + - "\x0fDeleteDiceRobot\x12%.hyapp.game.v1.DeleteDiceRobotRequest\x1a&.hyapp.game.v1.DeleteDiceRobotResponseB&Z$hyapp.local/api/proto/game/v1;gamev1b\x06proto3" + "\x0fDeleteDiceRobot\x12%.hyapp.game.v1.DeleteDiceRobotRequest\x1a&.hyapp.game.v1.DeleteDiceRobotResponse\x12`\n" + + "\x10GetRoomRPSConfig\x12&.hyapp.game.v1.GetRoomRPSConfigRequest\x1a$.hyapp.game.v1.RoomRPSConfigResponse\x12f\n" + + "\x13UpdateRoomRPSConfig\x12).hyapp.game.v1.UpdateRoomRPSConfigRequest\x1a$.hyapp.game.v1.RoomRPSConfigResponse\x12r\n" + + "\x15ListRoomRPSChallenges\x12+.hyapp.game.v1.ListRoomRPSChallengesRequest\x1a,.hyapp.game.v1.ListRoomRPSChallengesResponse\x12i\n" + + "\x13GetRoomRPSChallenge\x12).hyapp.game.v1.GetRoomRPSChallengeRequest\x1a'.hyapp.game.v1.RoomRPSChallengeResponse\x12o\n" + + "\x16RetryRoomRPSSettlement\x12,.hyapp.game.v1.RetryRoomRPSSettlementRequest\x1a'.hyapp.game.v1.RoomRPSChallengeResponse\x12o\n" + + "\x16ExpireRoomRPSChallenge\x12,.hyapp.game.v1.ExpireRoomRPSChallengeRequest\x1a'.hyapp.game.v1.RoomRPSChallengeResponseB&Z$hyapp.local/api/proto/game/v1;gamev1b\x06proto3" var ( file_proto_game_v1_game_proto_rawDescOnce sync.Once @@ -4572,65 +5825,80 @@ func file_proto_game_v1_game_proto_rawDescGZIP() []byte { return file_proto_game_v1_game_proto_rawDescData } -var file_proto_game_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 57) +var file_proto_game_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 72) var file_proto_game_v1_game_proto_goTypes = []any{ - (*RequestMeta)(nil), // 0: hyapp.game.v1.RequestMeta - (*GamePlatform)(nil), // 1: hyapp.game.v1.GamePlatform - (*GameCatalogItem)(nil), // 2: hyapp.game.v1.GameCatalogItem - (*AppGame)(nil), // 3: hyapp.game.v1.AppGame - (*ListGamesRequest)(nil), // 4: hyapp.game.v1.ListGamesRequest - (*ListGamesResponse)(nil), // 5: hyapp.game.v1.ListGamesResponse - (*ListRecentGamesRequest)(nil), // 6: hyapp.game.v1.ListRecentGamesRequest - (*GetBridgeScriptRequest)(nil), // 7: hyapp.game.v1.GetBridgeScriptRequest - (*GameBridgeScriptConfig)(nil), // 8: hyapp.game.v1.GameBridgeScriptConfig - (*GetBridgeScriptResponse)(nil), // 9: hyapp.game.v1.GetBridgeScriptResponse - (*LaunchGameRequest)(nil), // 10: hyapp.game.v1.LaunchGameRequest - (*LaunchGameResponse)(nil), // 11: hyapp.game.v1.LaunchGameResponse - (*DiceParticipant)(nil), // 12: hyapp.game.v1.DiceParticipant - (*DiceMatch)(nil), // 13: hyapp.game.v1.DiceMatch - (*DiceStakeOption)(nil), // 14: hyapp.game.v1.DiceStakeOption - (*DiceConfig)(nil), // 15: hyapp.game.v1.DiceConfig - (*GetDiceConfigRequest)(nil), // 16: hyapp.game.v1.GetDiceConfigRequest - (*DiceConfigResponse)(nil), // 17: hyapp.game.v1.DiceConfigResponse - (*CreateDiceMatchRequest)(nil), // 18: hyapp.game.v1.CreateDiceMatchRequest - (*JoinDiceMatchRequest)(nil), // 19: hyapp.game.v1.JoinDiceMatchRequest - (*GetDiceMatchRequest)(nil), // 20: hyapp.game.v1.GetDiceMatchRequest - (*RollDiceMatchRequest)(nil), // 21: hyapp.game.v1.RollDiceMatchRequest - (*MatchDiceRequest)(nil), // 22: hyapp.game.v1.MatchDiceRequest - (*CancelDiceMatchRequest)(nil), // 23: hyapp.game.v1.CancelDiceMatchRequest - (*DiceMatchResponse)(nil), // 24: hyapp.game.v1.DiceMatchResponse - (*CallbackRequest)(nil), // 25: hyapp.game.v1.CallbackRequest - (*CallbackResponse)(nil), // 26: hyapp.game.v1.CallbackResponse - (*CronBatchRequest)(nil), // 27: hyapp.game.v1.CronBatchRequest - (*CronBatchResponse)(nil), // 28: hyapp.game.v1.CronBatchResponse - (*ListPlatformsRequest)(nil), // 29: hyapp.game.v1.ListPlatformsRequest - (*ListPlatformsResponse)(nil), // 30: hyapp.game.v1.ListPlatformsResponse - (*UpsertPlatformRequest)(nil), // 31: hyapp.game.v1.UpsertPlatformRequest - (*PlatformResponse)(nil), // 32: hyapp.game.v1.PlatformResponse - (*ListCatalogRequest)(nil), // 33: hyapp.game.v1.ListCatalogRequest - (*ListCatalogResponse)(nil), // 34: hyapp.game.v1.ListCatalogResponse - (*UpsertCatalogRequest)(nil), // 35: hyapp.game.v1.UpsertCatalogRequest - (*CatalogResponse)(nil), // 36: hyapp.game.v1.CatalogResponse - (*SetGameStatusRequest)(nil), // 37: hyapp.game.v1.SetGameStatusRequest - (*DeleteCatalogRequest)(nil), // 38: hyapp.game.v1.DeleteCatalogRequest - (*DeleteCatalogResponse)(nil), // 39: hyapp.game.v1.DeleteCatalogResponse - (*ListSelfGamesRequest)(nil), // 40: hyapp.game.v1.ListSelfGamesRequest - (*ListSelfGamesResponse)(nil), // 41: hyapp.game.v1.ListSelfGamesResponse - (*UpdateDiceConfigRequest)(nil), // 42: hyapp.game.v1.UpdateDiceConfigRequest - (*DicePoolAdjustment)(nil), // 43: hyapp.game.v1.DicePoolAdjustment - (*AdjustDicePoolRequest)(nil), // 44: hyapp.game.v1.AdjustDicePoolRequest - (*AdjustDicePoolResponse)(nil), // 45: hyapp.game.v1.AdjustDicePoolResponse - (*DiceRobot)(nil), // 46: hyapp.game.v1.DiceRobot - (*ListDiceRobotsRequest)(nil), // 47: hyapp.game.v1.ListDiceRobotsRequest - (*ListDiceRobotsResponse)(nil), // 48: hyapp.game.v1.ListDiceRobotsResponse - (*RegisterDiceRobotsRequest)(nil), // 49: hyapp.game.v1.RegisterDiceRobotsRequest - (*RegisterDiceRobotsResponse)(nil), // 50: hyapp.game.v1.RegisterDiceRobotsResponse - (*SetDiceRobotStatusRequest)(nil), // 51: hyapp.game.v1.SetDiceRobotStatusRequest - (*DeleteDiceRobotRequest)(nil), // 52: hyapp.game.v1.DeleteDiceRobotRequest - (*DeleteDiceRobotResponse)(nil), // 53: hyapp.game.v1.DeleteDiceRobotResponse - (*DiceRobotResponse)(nil), // 54: hyapp.game.v1.DiceRobotResponse - nil, // 55: hyapp.game.v1.CallbackRequest.HeadersEntry - nil, // 56: hyapp.game.v1.CallbackRequest.QueryEntry + (*RequestMeta)(nil), // 0: hyapp.game.v1.RequestMeta + (*GamePlatform)(nil), // 1: hyapp.game.v1.GamePlatform + (*GameCatalogItem)(nil), // 2: hyapp.game.v1.GameCatalogItem + (*AppGame)(nil), // 3: hyapp.game.v1.AppGame + (*ListGamesRequest)(nil), // 4: hyapp.game.v1.ListGamesRequest + (*ListGamesResponse)(nil), // 5: hyapp.game.v1.ListGamesResponse + (*ListRecentGamesRequest)(nil), // 6: hyapp.game.v1.ListRecentGamesRequest + (*GetBridgeScriptRequest)(nil), // 7: hyapp.game.v1.GetBridgeScriptRequest + (*GameBridgeScriptConfig)(nil), // 8: hyapp.game.v1.GameBridgeScriptConfig + (*GetBridgeScriptResponse)(nil), // 9: hyapp.game.v1.GetBridgeScriptResponse + (*LaunchGameRequest)(nil), // 10: hyapp.game.v1.LaunchGameRequest + (*LaunchGameResponse)(nil), // 11: hyapp.game.v1.LaunchGameResponse + (*DiceParticipant)(nil), // 12: hyapp.game.v1.DiceParticipant + (*DiceMatch)(nil), // 13: hyapp.game.v1.DiceMatch + (*DiceStakeOption)(nil), // 14: hyapp.game.v1.DiceStakeOption + (*DiceConfig)(nil), // 15: hyapp.game.v1.DiceConfig + (*GetDiceConfigRequest)(nil), // 16: hyapp.game.v1.GetDiceConfigRequest + (*DiceConfigResponse)(nil), // 17: hyapp.game.v1.DiceConfigResponse + (*RoomRPSStakeGift)(nil), // 18: hyapp.game.v1.RoomRPSStakeGift + (*RoomRPSConfig)(nil), // 19: hyapp.game.v1.RoomRPSConfig + (*RoomRPSPlayer)(nil), // 20: hyapp.game.v1.RoomRPSPlayer + (*RoomRPSChallenge)(nil), // 21: hyapp.game.v1.RoomRPSChallenge + (*GetRoomRPSConfigRequest)(nil), // 22: hyapp.game.v1.GetRoomRPSConfigRequest + (*RoomRPSConfigResponse)(nil), // 23: hyapp.game.v1.RoomRPSConfigResponse + (*CreateRoomRPSChallengeRequest)(nil), // 24: hyapp.game.v1.CreateRoomRPSChallengeRequest + (*AcceptRoomRPSChallengeRequest)(nil), // 25: hyapp.game.v1.AcceptRoomRPSChallengeRequest + (*GetRoomRPSChallengeRequest)(nil), // 26: hyapp.game.v1.GetRoomRPSChallengeRequest + (*ListRoomRPSChallengesRequest)(nil), // 27: hyapp.game.v1.ListRoomRPSChallengesRequest + (*RoomRPSChallengeResponse)(nil), // 28: hyapp.game.v1.RoomRPSChallengeResponse + (*ListRoomRPSChallengesResponse)(nil), // 29: hyapp.game.v1.ListRoomRPSChallengesResponse + (*UpdateRoomRPSConfigRequest)(nil), // 30: hyapp.game.v1.UpdateRoomRPSConfigRequest + (*RetryRoomRPSSettlementRequest)(nil), // 31: hyapp.game.v1.RetryRoomRPSSettlementRequest + (*ExpireRoomRPSChallengeRequest)(nil), // 32: hyapp.game.v1.ExpireRoomRPSChallengeRequest + (*CreateDiceMatchRequest)(nil), // 33: hyapp.game.v1.CreateDiceMatchRequest + (*JoinDiceMatchRequest)(nil), // 34: hyapp.game.v1.JoinDiceMatchRequest + (*GetDiceMatchRequest)(nil), // 35: hyapp.game.v1.GetDiceMatchRequest + (*RollDiceMatchRequest)(nil), // 36: hyapp.game.v1.RollDiceMatchRequest + (*MatchDiceRequest)(nil), // 37: hyapp.game.v1.MatchDiceRequest + (*CancelDiceMatchRequest)(nil), // 38: hyapp.game.v1.CancelDiceMatchRequest + (*DiceMatchResponse)(nil), // 39: hyapp.game.v1.DiceMatchResponse + (*CallbackRequest)(nil), // 40: hyapp.game.v1.CallbackRequest + (*CallbackResponse)(nil), // 41: hyapp.game.v1.CallbackResponse + (*CronBatchRequest)(nil), // 42: hyapp.game.v1.CronBatchRequest + (*CronBatchResponse)(nil), // 43: hyapp.game.v1.CronBatchResponse + (*ListPlatformsRequest)(nil), // 44: hyapp.game.v1.ListPlatformsRequest + (*ListPlatformsResponse)(nil), // 45: hyapp.game.v1.ListPlatformsResponse + (*UpsertPlatformRequest)(nil), // 46: hyapp.game.v1.UpsertPlatformRequest + (*PlatformResponse)(nil), // 47: hyapp.game.v1.PlatformResponse + (*ListCatalogRequest)(nil), // 48: hyapp.game.v1.ListCatalogRequest + (*ListCatalogResponse)(nil), // 49: hyapp.game.v1.ListCatalogResponse + (*UpsertCatalogRequest)(nil), // 50: hyapp.game.v1.UpsertCatalogRequest + (*CatalogResponse)(nil), // 51: hyapp.game.v1.CatalogResponse + (*SetGameStatusRequest)(nil), // 52: hyapp.game.v1.SetGameStatusRequest + (*DeleteCatalogRequest)(nil), // 53: hyapp.game.v1.DeleteCatalogRequest + (*DeleteCatalogResponse)(nil), // 54: hyapp.game.v1.DeleteCatalogResponse + (*ListSelfGamesRequest)(nil), // 55: hyapp.game.v1.ListSelfGamesRequest + (*ListSelfGamesResponse)(nil), // 56: hyapp.game.v1.ListSelfGamesResponse + (*UpdateDiceConfigRequest)(nil), // 57: hyapp.game.v1.UpdateDiceConfigRequest + (*DicePoolAdjustment)(nil), // 58: hyapp.game.v1.DicePoolAdjustment + (*AdjustDicePoolRequest)(nil), // 59: hyapp.game.v1.AdjustDicePoolRequest + (*AdjustDicePoolResponse)(nil), // 60: hyapp.game.v1.AdjustDicePoolResponse + (*DiceRobot)(nil), // 61: hyapp.game.v1.DiceRobot + (*ListDiceRobotsRequest)(nil), // 62: hyapp.game.v1.ListDiceRobotsRequest + (*ListDiceRobotsResponse)(nil), // 63: hyapp.game.v1.ListDiceRobotsResponse + (*RegisterDiceRobotsRequest)(nil), // 64: hyapp.game.v1.RegisterDiceRobotsRequest + (*RegisterDiceRobotsResponse)(nil), // 65: hyapp.game.v1.RegisterDiceRobotsResponse + (*SetDiceRobotStatusRequest)(nil), // 66: hyapp.game.v1.SetDiceRobotStatusRequest + (*DeleteDiceRobotRequest)(nil), // 67: hyapp.game.v1.DeleteDiceRobotRequest + (*DeleteDiceRobotResponse)(nil), // 68: hyapp.game.v1.DeleteDiceRobotResponse + (*DiceRobotResponse)(nil), // 69: hyapp.game.v1.DiceRobotResponse + nil, // 70: hyapp.game.v1.CallbackRequest.HeadersEntry + nil, // 71: hyapp.game.v1.CallbackRequest.QueryEntry } var file_proto_game_v1_game_proto_depIdxs = []int32{ 0, // 0: hyapp.game.v1.ListGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta @@ -4643,100 +5911,137 @@ var file_proto_game_v1_game_proto_depIdxs = []int32{ 14, // 7: hyapp.game.v1.DiceConfig.stake_options:type_name -> hyapp.game.v1.DiceStakeOption 0, // 8: hyapp.game.v1.GetDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta 15, // 9: hyapp.game.v1.DiceConfigResponse.config:type_name -> hyapp.game.v1.DiceConfig - 0, // 10: hyapp.game.v1.CreateDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 11: hyapp.game.v1.JoinDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 12: hyapp.game.v1.GetDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 13: hyapp.game.v1.RollDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 14: hyapp.game.v1.MatchDiceRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 15: hyapp.game.v1.CancelDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 13, // 16: hyapp.game.v1.DiceMatchResponse.match:type_name -> hyapp.game.v1.DiceMatch - 0, // 17: hyapp.game.v1.CallbackRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 55, // 18: hyapp.game.v1.CallbackRequest.headers:type_name -> hyapp.game.v1.CallbackRequest.HeadersEntry - 56, // 19: hyapp.game.v1.CallbackRequest.query:type_name -> hyapp.game.v1.CallbackRequest.QueryEntry - 0, // 20: hyapp.game.v1.CronBatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 21: hyapp.game.v1.ListPlatformsRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 1, // 22: hyapp.game.v1.ListPlatformsResponse.platforms:type_name -> hyapp.game.v1.GamePlatform - 0, // 23: hyapp.game.v1.UpsertPlatformRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 1, // 24: hyapp.game.v1.UpsertPlatformRequest.platform:type_name -> hyapp.game.v1.GamePlatform - 1, // 25: hyapp.game.v1.PlatformResponse.platform:type_name -> hyapp.game.v1.GamePlatform - 0, // 26: hyapp.game.v1.ListCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 2, // 27: hyapp.game.v1.ListCatalogResponse.games:type_name -> hyapp.game.v1.GameCatalogItem - 0, // 28: hyapp.game.v1.UpsertCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 2, // 29: hyapp.game.v1.UpsertCatalogRequest.game:type_name -> hyapp.game.v1.GameCatalogItem - 2, // 30: hyapp.game.v1.CatalogResponse.game:type_name -> hyapp.game.v1.GameCatalogItem - 0, // 31: hyapp.game.v1.SetGameStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 32: hyapp.game.v1.DeleteCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 33: hyapp.game.v1.ListSelfGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 15, // 34: hyapp.game.v1.ListSelfGamesResponse.games:type_name -> hyapp.game.v1.DiceConfig - 0, // 35: hyapp.game.v1.UpdateDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 15, // 36: hyapp.game.v1.UpdateDiceConfigRequest.config:type_name -> hyapp.game.v1.DiceConfig - 0, // 37: hyapp.game.v1.AdjustDicePoolRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 43, // 38: hyapp.game.v1.AdjustDicePoolResponse.adjustment:type_name -> hyapp.game.v1.DicePoolAdjustment - 15, // 39: hyapp.game.v1.AdjustDicePoolResponse.config:type_name -> hyapp.game.v1.DiceConfig - 0, // 40: hyapp.game.v1.ListDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 46, // 41: hyapp.game.v1.ListDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot - 0, // 42: hyapp.game.v1.RegisterDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 46, // 43: hyapp.game.v1.RegisterDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot - 0, // 44: hyapp.game.v1.SetDiceRobotStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 45: hyapp.game.v1.DeleteDiceRobotRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 46, // 46: hyapp.game.v1.DiceRobotResponse.robot:type_name -> hyapp.game.v1.DiceRobot - 4, // 47: hyapp.game.v1.GameAppService.ListGames:input_type -> hyapp.game.v1.ListGamesRequest - 6, // 48: hyapp.game.v1.GameAppService.ListRecentGames:input_type -> hyapp.game.v1.ListRecentGamesRequest - 7, // 49: hyapp.game.v1.GameAppService.GetBridgeScript:input_type -> hyapp.game.v1.GetBridgeScriptRequest - 10, // 50: hyapp.game.v1.GameAppService.LaunchGame:input_type -> hyapp.game.v1.LaunchGameRequest - 16, // 51: hyapp.game.v1.GameAppService.GetDiceConfig:input_type -> hyapp.game.v1.GetDiceConfigRequest - 22, // 52: hyapp.game.v1.GameAppService.MatchDice:input_type -> hyapp.game.v1.MatchDiceRequest - 18, // 53: hyapp.game.v1.GameAppService.CreateDiceMatch:input_type -> hyapp.game.v1.CreateDiceMatchRequest - 19, // 54: hyapp.game.v1.GameAppService.JoinDiceMatch:input_type -> hyapp.game.v1.JoinDiceMatchRequest - 20, // 55: hyapp.game.v1.GameAppService.GetDiceMatch:input_type -> hyapp.game.v1.GetDiceMatchRequest - 21, // 56: hyapp.game.v1.GameAppService.RollDiceMatch:input_type -> hyapp.game.v1.RollDiceMatchRequest - 23, // 57: hyapp.game.v1.GameAppService.CancelDiceMatch:input_type -> hyapp.game.v1.CancelDiceMatchRequest - 25, // 58: hyapp.game.v1.GameCallbackService.HandleCallback:input_type -> hyapp.game.v1.CallbackRequest - 27, // 59: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:input_type -> hyapp.game.v1.CronBatchRequest - 29, // 60: hyapp.game.v1.GameAdminService.ListPlatforms:input_type -> hyapp.game.v1.ListPlatformsRequest - 31, // 61: hyapp.game.v1.GameAdminService.UpsertPlatform:input_type -> hyapp.game.v1.UpsertPlatformRequest - 33, // 62: hyapp.game.v1.GameAdminService.ListCatalog:input_type -> hyapp.game.v1.ListCatalogRequest - 35, // 63: hyapp.game.v1.GameAdminService.UpsertCatalog:input_type -> hyapp.game.v1.UpsertCatalogRequest - 37, // 64: hyapp.game.v1.GameAdminService.SetGameStatus:input_type -> hyapp.game.v1.SetGameStatusRequest - 38, // 65: hyapp.game.v1.GameAdminService.DeleteCatalog:input_type -> hyapp.game.v1.DeleteCatalogRequest - 40, // 66: hyapp.game.v1.GameAdminService.ListSelfGames:input_type -> hyapp.game.v1.ListSelfGamesRequest - 42, // 67: hyapp.game.v1.GameAdminService.UpdateDiceConfig:input_type -> hyapp.game.v1.UpdateDiceConfigRequest - 44, // 68: hyapp.game.v1.GameAdminService.AdjustDicePool:input_type -> hyapp.game.v1.AdjustDicePoolRequest - 47, // 69: hyapp.game.v1.GameAdminService.ListDiceRobots:input_type -> hyapp.game.v1.ListDiceRobotsRequest - 49, // 70: hyapp.game.v1.GameAdminService.RegisterDiceRobots:input_type -> hyapp.game.v1.RegisterDiceRobotsRequest - 51, // 71: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:input_type -> hyapp.game.v1.SetDiceRobotStatusRequest - 52, // 72: hyapp.game.v1.GameAdminService.DeleteDiceRobot:input_type -> hyapp.game.v1.DeleteDiceRobotRequest - 5, // 73: hyapp.game.v1.GameAppService.ListGames:output_type -> hyapp.game.v1.ListGamesResponse - 5, // 74: hyapp.game.v1.GameAppService.ListRecentGames:output_type -> hyapp.game.v1.ListGamesResponse - 9, // 75: hyapp.game.v1.GameAppService.GetBridgeScript:output_type -> hyapp.game.v1.GetBridgeScriptResponse - 11, // 76: hyapp.game.v1.GameAppService.LaunchGame:output_type -> hyapp.game.v1.LaunchGameResponse - 17, // 77: hyapp.game.v1.GameAppService.GetDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse - 24, // 78: hyapp.game.v1.GameAppService.MatchDice:output_type -> hyapp.game.v1.DiceMatchResponse - 24, // 79: hyapp.game.v1.GameAppService.CreateDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 24, // 80: hyapp.game.v1.GameAppService.JoinDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 24, // 81: hyapp.game.v1.GameAppService.GetDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 24, // 82: hyapp.game.v1.GameAppService.RollDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 24, // 83: hyapp.game.v1.GameAppService.CancelDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 26, // 84: hyapp.game.v1.GameCallbackService.HandleCallback:output_type -> hyapp.game.v1.CallbackResponse - 28, // 85: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:output_type -> hyapp.game.v1.CronBatchResponse - 30, // 86: hyapp.game.v1.GameAdminService.ListPlatforms:output_type -> hyapp.game.v1.ListPlatformsResponse - 32, // 87: hyapp.game.v1.GameAdminService.UpsertPlatform:output_type -> hyapp.game.v1.PlatformResponse - 34, // 88: hyapp.game.v1.GameAdminService.ListCatalog:output_type -> hyapp.game.v1.ListCatalogResponse - 36, // 89: hyapp.game.v1.GameAdminService.UpsertCatalog:output_type -> hyapp.game.v1.CatalogResponse - 36, // 90: hyapp.game.v1.GameAdminService.SetGameStatus:output_type -> hyapp.game.v1.CatalogResponse - 39, // 91: hyapp.game.v1.GameAdminService.DeleteCatalog:output_type -> hyapp.game.v1.DeleteCatalogResponse - 41, // 92: hyapp.game.v1.GameAdminService.ListSelfGames:output_type -> hyapp.game.v1.ListSelfGamesResponse - 17, // 93: hyapp.game.v1.GameAdminService.UpdateDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse - 45, // 94: hyapp.game.v1.GameAdminService.AdjustDicePool:output_type -> hyapp.game.v1.AdjustDicePoolResponse - 48, // 95: hyapp.game.v1.GameAdminService.ListDiceRobots:output_type -> hyapp.game.v1.ListDiceRobotsResponse - 50, // 96: hyapp.game.v1.GameAdminService.RegisterDiceRobots:output_type -> hyapp.game.v1.RegisterDiceRobotsResponse - 54, // 97: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:output_type -> hyapp.game.v1.DiceRobotResponse - 53, // 98: hyapp.game.v1.GameAdminService.DeleteDiceRobot:output_type -> hyapp.game.v1.DeleteDiceRobotResponse - 73, // [73:99] is the sub-list for method output_type - 47, // [47:73] is the sub-list for method input_type - 47, // [47:47] is the sub-list for extension type_name - 47, // [47:47] is the sub-list for extension extendee - 0, // [0:47] is the sub-list for field type_name + 18, // 10: hyapp.game.v1.RoomRPSConfig.stake_gifts:type_name -> hyapp.game.v1.RoomRPSStakeGift + 20, // 11: hyapp.game.v1.RoomRPSChallenge.initiator:type_name -> hyapp.game.v1.RoomRPSPlayer + 20, // 12: hyapp.game.v1.RoomRPSChallenge.challenger:type_name -> hyapp.game.v1.RoomRPSPlayer + 0, // 13: hyapp.game.v1.GetRoomRPSConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 19, // 14: hyapp.game.v1.RoomRPSConfigResponse.config:type_name -> hyapp.game.v1.RoomRPSConfig + 0, // 15: hyapp.game.v1.CreateRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 16: hyapp.game.v1.AcceptRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 17: hyapp.game.v1.GetRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 18: hyapp.game.v1.ListRoomRPSChallengesRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 21, // 19: hyapp.game.v1.RoomRPSChallengeResponse.challenge:type_name -> hyapp.game.v1.RoomRPSChallenge + 21, // 20: hyapp.game.v1.ListRoomRPSChallengesResponse.challenges:type_name -> hyapp.game.v1.RoomRPSChallenge + 0, // 21: hyapp.game.v1.UpdateRoomRPSConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 19, // 22: hyapp.game.v1.UpdateRoomRPSConfigRequest.config:type_name -> hyapp.game.v1.RoomRPSConfig + 0, // 23: hyapp.game.v1.RetryRoomRPSSettlementRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 24: hyapp.game.v1.ExpireRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 25: hyapp.game.v1.CreateDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 26: hyapp.game.v1.JoinDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 27: hyapp.game.v1.GetDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 28: hyapp.game.v1.RollDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 29: hyapp.game.v1.MatchDiceRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 30: hyapp.game.v1.CancelDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 13, // 31: hyapp.game.v1.DiceMatchResponse.match:type_name -> hyapp.game.v1.DiceMatch + 0, // 32: hyapp.game.v1.CallbackRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 70, // 33: hyapp.game.v1.CallbackRequest.headers:type_name -> hyapp.game.v1.CallbackRequest.HeadersEntry + 71, // 34: hyapp.game.v1.CallbackRequest.query:type_name -> hyapp.game.v1.CallbackRequest.QueryEntry + 0, // 35: hyapp.game.v1.CronBatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 36: hyapp.game.v1.ListPlatformsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 1, // 37: hyapp.game.v1.ListPlatformsResponse.platforms:type_name -> hyapp.game.v1.GamePlatform + 0, // 38: hyapp.game.v1.UpsertPlatformRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 1, // 39: hyapp.game.v1.UpsertPlatformRequest.platform:type_name -> hyapp.game.v1.GamePlatform + 1, // 40: hyapp.game.v1.PlatformResponse.platform:type_name -> hyapp.game.v1.GamePlatform + 0, // 41: hyapp.game.v1.ListCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 2, // 42: hyapp.game.v1.ListCatalogResponse.games:type_name -> hyapp.game.v1.GameCatalogItem + 0, // 43: hyapp.game.v1.UpsertCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 2, // 44: hyapp.game.v1.UpsertCatalogRequest.game:type_name -> hyapp.game.v1.GameCatalogItem + 2, // 45: hyapp.game.v1.CatalogResponse.game:type_name -> hyapp.game.v1.GameCatalogItem + 0, // 46: hyapp.game.v1.SetGameStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 47: hyapp.game.v1.DeleteCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 48: hyapp.game.v1.ListSelfGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 15, // 49: hyapp.game.v1.ListSelfGamesResponse.games:type_name -> hyapp.game.v1.DiceConfig + 0, // 50: hyapp.game.v1.UpdateDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 15, // 51: hyapp.game.v1.UpdateDiceConfigRequest.config:type_name -> hyapp.game.v1.DiceConfig + 0, // 52: hyapp.game.v1.AdjustDicePoolRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 58, // 53: hyapp.game.v1.AdjustDicePoolResponse.adjustment:type_name -> hyapp.game.v1.DicePoolAdjustment + 15, // 54: hyapp.game.v1.AdjustDicePoolResponse.config:type_name -> hyapp.game.v1.DiceConfig + 0, // 55: hyapp.game.v1.ListDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 61, // 56: hyapp.game.v1.ListDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot + 0, // 57: hyapp.game.v1.RegisterDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 61, // 58: hyapp.game.v1.RegisterDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot + 0, // 59: hyapp.game.v1.SetDiceRobotStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 60: hyapp.game.v1.DeleteDiceRobotRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 61, // 61: hyapp.game.v1.DiceRobotResponse.robot:type_name -> hyapp.game.v1.DiceRobot + 4, // 62: hyapp.game.v1.GameAppService.ListGames:input_type -> hyapp.game.v1.ListGamesRequest + 6, // 63: hyapp.game.v1.GameAppService.ListRecentGames:input_type -> hyapp.game.v1.ListRecentGamesRequest + 7, // 64: hyapp.game.v1.GameAppService.GetBridgeScript:input_type -> hyapp.game.v1.GetBridgeScriptRequest + 10, // 65: hyapp.game.v1.GameAppService.LaunchGame:input_type -> hyapp.game.v1.LaunchGameRequest + 16, // 66: hyapp.game.v1.GameAppService.GetDiceConfig:input_type -> hyapp.game.v1.GetDiceConfigRequest + 37, // 67: hyapp.game.v1.GameAppService.MatchDice:input_type -> hyapp.game.v1.MatchDiceRequest + 33, // 68: hyapp.game.v1.GameAppService.CreateDiceMatch:input_type -> hyapp.game.v1.CreateDiceMatchRequest + 34, // 69: hyapp.game.v1.GameAppService.JoinDiceMatch:input_type -> hyapp.game.v1.JoinDiceMatchRequest + 35, // 70: hyapp.game.v1.GameAppService.GetDiceMatch:input_type -> hyapp.game.v1.GetDiceMatchRequest + 36, // 71: hyapp.game.v1.GameAppService.RollDiceMatch:input_type -> hyapp.game.v1.RollDiceMatchRequest + 38, // 72: hyapp.game.v1.GameAppService.CancelDiceMatch:input_type -> hyapp.game.v1.CancelDiceMatchRequest + 22, // 73: hyapp.game.v1.GameAppService.GetRoomRPSConfig:input_type -> hyapp.game.v1.GetRoomRPSConfigRequest + 27, // 74: hyapp.game.v1.GameAppService.ListRoomRPSChallenges:input_type -> hyapp.game.v1.ListRoomRPSChallengesRequest + 24, // 75: hyapp.game.v1.GameAppService.CreateRoomRPSChallenge:input_type -> hyapp.game.v1.CreateRoomRPSChallengeRequest + 25, // 76: hyapp.game.v1.GameAppService.AcceptRoomRPSChallenge:input_type -> hyapp.game.v1.AcceptRoomRPSChallengeRequest + 26, // 77: hyapp.game.v1.GameAppService.GetRoomRPSChallenge:input_type -> hyapp.game.v1.GetRoomRPSChallengeRequest + 40, // 78: hyapp.game.v1.GameCallbackService.HandleCallback:input_type -> hyapp.game.v1.CallbackRequest + 42, // 79: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:input_type -> hyapp.game.v1.CronBatchRequest + 44, // 80: hyapp.game.v1.GameAdminService.ListPlatforms:input_type -> hyapp.game.v1.ListPlatformsRequest + 46, // 81: hyapp.game.v1.GameAdminService.UpsertPlatform:input_type -> hyapp.game.v1.UpsertPlatformRequest + 48, // 82: hyapp.game.v1.GameAdminService.ListCatalog:input_type -> hyapp.game.v1.ListCatalogRequest + 50, // 83: hyapp.game.v1.GameAdminService.UpsertCatalog:input_type -> hyapp.game.v1.UpsertCatalogRequest + 52, // 84: hyapp.game.v1.GameAdminService.SetGameStatus:input_type -> hyapp.game.v1.SetGameStatusRequest + 53, // 85: hyapp.game.v1.GameAdminService.DeleteCatalog:input_type -> hyapp.game.v1.DeleteCatalogRequest + 55, // 86: hyapp.game.v1.GameAdminService.ListSelfGames:input_type -> hyapp.game.v1.ListSelfGamesRequest + 57, // 87: hyapp.game.v1.GameAdminService.UpdateDiceConfig:input_type -> hyapp.game.v1.UpdateDiceConfigRequest + 59, // 88: hyapp.game.v1.GameAdminService.AdjustDicePool:input_type -> hyapp.game.v1.AdjustDicePoolRequest + 62, // 89: hyapp.game.v1.GameAdminService.ListDiceRobots:input_type -> hyapp.game.v1.ListDiceRobotsRequest + 64, // 90: hyapp.game.v1.GameAdminService.RegisterDiceRobots:input_type -> hyapp.game.v1.RegisterDiceRobotsRequest + 66, // 91: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:input_type -> hyapp.game.v1.SetDiceRobotStatusRequest + 67, // 92: hyapp.game.v1.GameAdminService.DeleteDiceRobot:input_type -> hyapp.game.v1.DeleteDiceRobotRequest + 22, // 93: hyapp.game.v1.GameAdminService.GetRoomRPSConfig:input_type -> hyapp.game.v1.GetRoomRPSConfigRequest + 30, // 94: hyapp.game.v1.GameAdminService.UpdateRoomRPSConfig:input_type -> hyapp.game.v1.UpdateRoomRPSConfigRequest + 27, // 95: hyapp.game.v1.GameAdminService.ListRoomRPSChallenges:input_type -> hyapp.game.v1.ListRoomRPSChallengesRequest + 26, // 96: hyapp.game.v1.GameAdminService.GetRoomRPSChallenge:input_type -> hyapp.game.v1.GetRoomRPSChallengeRequest + 31, // 97: hyapp.game.v1.GameAdminService.RetryRoomRPSSettlement:input_type -> hyapp.game.v1.RetryRoomRPSSettlementRequest + 32, // 98: hyapp.game.v1.GameAdminService.ExpireRoomRPSChallenge:input_type -> hyapp.game.v1.ExpireRoomRPSChallengeRequest + 5, // 99: hyapp.game.v1.GameAppService.ListGames:output_type -> hyapp.game.v1.ListGamesResponse + 5, // 100: hyapp.game.v1.GameAppService.ListRecentGames:output_type -> hyapp.game.v1.ListGamesResponse + 9, // 101: hyapp.game.v1.GameAppService.GetBridgeScript:output_type -> hyapp.game.v1.GetBridgeScriptResponse + 11, // 102: hyapp.game.v1.GameAppService.LaunchGame:output_type -> hyapp.game.v1.LaunchGameResponse + 17, // 103: hyapp.game.v1.GameAppService.GetDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse + 39, // 104: hyapp.game.v1.GameAppService.MatchDice:output_type -> hyapp.game.v1.DiceMatchResponse + 39, // 105: hyapp.game.v1.GameAppService.CreateDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 39, // 106: hyapp.game.v1.GameAppService.JoinDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 39, // 107: hyapp.game.v1.GameAppService.GetDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 39, // 108: hyapp.game.v1.GameAppService.RollDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 39, // 109: hyapp.game.v1.GameAppService.CancelDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 23, // 110: hyapp.game.v1.GameAppService.GetRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse + 29, // 111: hyapp.game.v1.GameAppService.ListRoomRPSChallenges:output_type -> hyapp.game.v1.ListRoomRPSChallengesResponse + 28, // 112: hyapp.game.v1.GameAppService.CreateRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 28, // 113: hyapp.game.v1.GameAppService.AcceptRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 28, // 114: hyapp.game.v1.GameAppService.GetRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 41, // 115: hyapp.game.v1.GameCallbackService.HandleCallback:output_type -> hyapp.game.v1.CallbackResponse + 43, // 116: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:output_type -> hyapp.game.v1.CronBatchResponse + 45, // 117: hyapp.game.v1.GameAdminService.ListPlatforms:output_type -> hyapp.game.v1.ListPlatformsResponse + 47, // 118: hyapp.game.v1.GameAdminService.UpsertPlatform:output_type -> hyapp.game.v1.PlatformResponse + 49, // 119: hyapp.game.v1.GameAdminService.ListCatalog:output_type -> hyapp.game.v1.ListCatalogResponse + 51, // 120: hyapp.game.v1.GameAdminService.UpsertCatalog:output_type -> hyapp.game.v1.CatalogResponse + 51, // 121: hyapp.game.v1.GameAdminService.SetGameStatus:output_type -> hyapp.game.v1.CatalogResponse + 54, // 122: hyapp.game.v1.GameAdminService.DeleteCatalog:output_type -> hyapp.game.v1.DeleteCatalogResponse + 56, // 123: hyapp.game.v1.GameAdminService.ListSelfGames:output_type -> hyapp.game.v1.ListSelfGamesResponse + 17, // 124: hyapp.game.v1.GameAdminService.UpdateDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse + 60, // 125: hyapp.game.v1.GameAdminService.AdjustDicePool:output_type -> hyapp.game.v1.AdjustDicePoolResponse + 63, // 126: hyapp.game.v1.GameAdminService.ListDiceRobots:output_type -> hyapp.game.v1.ListDiceRobotsResponse + 65, // 127: hyapp.game.v1.GameAdminService.RegisterDiceRobots:output_type -> hyapp.game.v1.RegisterDiceRobotsResponse + 69, // 128: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:output_type -> hyapp.game.v1.DiceRobotResponse + 68, // 129: hyapp.game.v1.GameAdminService.DeleteDiceRobot:output_type -> hyapp.game.v1.DeleteDiceRobotResponse + 23, // 130: hyapp.game.v1.GameAdminService.GetRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse + 23, // 131: hyapp.game.v1.GameAdminService.UpdateRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse + 29, // 132: hyapp.game.v1.GameAdminService.ListRoomRPSChallenges:output_type -> hyapp.game.v1.ListRoomRPSChallengesResponse + 28, // 133: hyapp.game.v1.GameAdminService.GetRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 28, // 134: hyapp.game.v1.GameAdminService.RetryRoomRPSSettlement:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 28, // 135: hyapp.game.v1.GameAdminService.ExpireRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 99, // [99:136] is the sub-list for method output_type + 62, // [62:99] is the sub-list for method input_type + 62, // [62:62] is the sub-list for extension type_name + 62, // [62:62] is the sub-list for extension extendee + 0, // [0:62] is the sub-list for field type_name } func init() { file_proto_game_v1_game_proto_init() } @@ -4750,7 +6055,7 @@ func file_proto_game_v1_game_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_game_v1_game_proto_rawDesc), len(file_proto_game_v1_game_proto_rawDesc)), NumEnums: 0, - NumMessages: 57, + NumMessages: 72, NumExtensions: 0, NumServices: 4, }, diff --git a/api/proto/game/v1/game.proto b/api/proto/game/v1/game.proto index fe2a68e8..dc2ed67d 100644 --- a/api/proto/game/v1/game.proto +++ b/api/proto/game/v1/game.proto @@ -206,6 +206,126 @@ message DiceConfigResponse { int64 server_time_ms = 2; } +message RoomRPSStakeGift { + int64 gift_id = 1; + string gift_name = 2; + string gift_icon_url = 3; + int64 gift_price_coin = 4; + bool enabled = 5; + int32 sort_order = 6; +} + +message RoomRPSConfig { + string app_code = 1; + string game_id = 2; + string status = 3; + int64 challenge_timeout_ms = 4; + int64 reveal_countdown_ms = 5; + repeated RoomRPSStakeGift stake_gifts = 6; + int64 created_at_ms = 7; + int64 updated_at_ms = 8; +} + +message RoomRPSPlayer { + int64 user_id = 1; + string gesture = 2; + string result = 3; + int64 balance_after = 4; + int64 joined_at_ms = 5; +} + +message RoomRPSChallenge { + string app_code = 1; + string challenge_id = 2; + string room_id = 3; + int64 region_id = 4; + string status = 5; + int64 stake_gift_id = 6; + int64 stake_coin = 7; + RoomRPSPlayer initiator = 8; + RoomRPSPlayer challenger = 9; + int64 winner_user_id = 10; + string settlement_status = 11; + string failure_reason = 12; + int64 timeout_at_ms = 13; + int64 reveal_at_ms = 14; + int64 created_at_ms = 15; + int64 matched_at_ms = 16; + int64 settled_at_ms = 17; + int64 updated_at_ms = 18; +} + +message GetRoomRPSConfigRequest { + RequestMeta meta = 1; +} + +message RoomRPSConfigResponse { + RoomRPSConfig config = 1; + int64 server_time_ms = 2; +} + +message CreateRoomRPSChallengeRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string room_id = 3; + int64 region_id = 4; + int64 gift_id = 5; + string gesture = 6; +} + +message AcceptRoomRPSChallengeRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string challenge_id = 3; + string gesture = 4; +} + +message GetRoomRPSChallengeRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string challenge_id = 3; +} + +message ListRoomRPSChallengesRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string room_id = 3; + string status = 4; + int64 initiator_user_id = 5; + int64 challenger_user_id = 6; + int64 start_time_ms = 7; + int64 end_time_ms = 8; + int32 page_size = 9; + string cursor = 10; +} + +message RoomRPSChallengeResponse { + RoomRPSChallenge challenge = 1; + int64 server_time_ms = 2; +} + +message ListRoomRPSChallengesResponse { + repeated RoomRPSChallenge challenges = 1; + string next_cursor = 2; + int32 page_size = 3; + int64 server_time_ms = 4; +} + +message UpdateRoomRPSConfigRequest { + RequestMeta meta = 1; + RoomRPSConfig config = 2; +} + +message RetryRoomRPSSettlementRequest { + RequestMeta meta = 1; + string challenge_id = 2; +} + +message ExpireRoomRPSChallengeRequest { + RequestMeta meta = 1; + string challenge_id = 2; +} + message CreateDiceMatchRequest { RequestMeta meta = 1; int64 user_id = 2; @@ -453,6 +573,11 @@ service GameAppService { rpc GetDiceMatch(GetDiceMatchRequest) returns (DiceMatchResponse); rpc RollDiceMatch(RollDiceMatchRequest) returns (DiceMatchResponse); rpc CancelDiceMatch(CancelDiceMatchRequest) returns (DiceMatchResponse); + rpc GetRoomRPSConfig(GetRoomRPSConfigRequest) returns (RoomRPSConfigResponse); + rpc ListRoomRPSChallenges(ListRoomRPSChallengesRequest) returns (ListRoomRPSChallengesResponse); + rpc CreateRoomRPSChallenge(CreateRoomRPSChallengeRequest) returns (RoomRPSChallengeResponse); + rpc AcceptRoomRPSChallenge(AcceptRoomRPSChallengeRequest) returns (RoomRPSChallengeResponse); + rpc GetRoomRPSChallenge(GetRoomRPSChallengeRequest) returns (RoomRPSChallengeResponse); } service GameCallbackService { @@ -478,4 +603,10 @@ service GameAdminService { rpc RegisterDiceRobots(RegisterDiceRobotsRequest) returns (RegisterDiceRobotsResponse); rpc SetDiceRobotStatus(SetDiceRobotStatusRequest) returns (DiceRobotResponse); rpc DeleteDiceRobot(DeleteDiceRobotRequest) returns (DeleteDiceRobotResponse); + rpc GetRoomRPSConfig(GetRoomRPSConfigRequest) returns (RoomRPSConfigResponse); + rpc UpdateRoomRPSConfig(UpdateRoomRPSConfigRequest) returns (RoomRPSConfigResponse); + rpc ListRoomRPSChallenges(ListRoomRPSChallengesRequest) returns (ListRoomRPSChallengesResponse); + rpc GetRoomRPSChallenge(GetRoomRPSChallengeRequest) returns (RoomRPSChallengeResponse); + rpc RetryRoomRPSSettlement(RetryRoomRPSSettlementRequest) returns (RoomRPSChallengeResponse); + rpc ExpireRoomRPSChallenge(ExpireRoomRPSChallengeRequest) returns (RoomRPSChallengeResponse); } diff --git a/api/proto/game/v1/game_grpc.pb.go b/api/proto/game/v1/game_grpc.pb.go index 9b10a0f8..ee5c8050 100644 --- a/api/proto/game/v1/game_grpc.pb.go +++ b/api/proto/game/v1/game_grpc.pb.go @@ -19,17 +19,22 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - GameAppService_ListGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListGames" - GameAppService_ListRecentGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListRecentGames" - GameAppService_GetBridgeScript_FullMethodName = "/hyapp.game.v1.GameAppService/GetBridgeScript" - GameAppService_LaunchGame_FullMethodName = "/hyapp.game.v1.GameAppService/LaunchGame" - GameAppService_GetDiceConfig_FullMethodName = "/hyapp.game.v1.GameAppService/GetDiceConfig" - GameAppService_MatchDice_FullMethodName = "/hyapp.game.v1.GameAppService/MatchDice" - GameAppService_CreateDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/CreateDiceMatch" - GameAppService_JoinDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/JoinDiceMatch" - GameAppService_GetDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/GetDiceMatch" - GameAppService_RollDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/RollDiceMatch" - GameAppService_CancelDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/CancelDiceMatch" + GameAppService_ListGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListGames" + GameAppService_ListRecentGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListRecentGames" + GameAppService_GetBridgeScript_FullMethodName = "/hyapp.game.v1.GameAppService/GetBridgeScript" + GameAppService_LaunchGame_FullMethodName = "/hyapp.game.v1.GameAppService/LaunchGame" + GameAppService_GetDiceConfig_FullMethodName = "/hyapp.game.v1.GameAppService/GetDiceConfig" + GameAppService_MatchDice_FullMethodName = "/hyapp.game.v1.GameAppService/MatchDice" + GameAppService_CreateDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/CreateDiceMatch" + GameAppService_JoinDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/JoinDiceMatch" + GameAppService_GetDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/GetDiceMatch" + GameAppService_RollDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/RollDiceMatch" + GameAppService_CancelDiceMatch_FullMethodName = "/hyapp.game.v1.GameAppService/CancelDiceMatch" + GameAppService_GetRoomRPSConfig_FullMethodName = "/hyapp.game.v1.GameAppService/GetRoomRPSConfig" + GameAppService_ListRoomRPSChallenges_FullMethodName = "/hyapp.game.v1.GameAppService/ListRoomRPSChallenges" + GameAppService_CreateRoomRPSChallenge_FullMethodName = "/hyapp.game.v1.GameAppService/CreateRoomRPSChallenge" + GameAppService_AcceptRoomRPSChallenge_FullMethodName = "/hyapp.game.v1.GameAppService/AcceptRoomRPSChallenge" + GameAppService_GetRoomRPSChallenge_FullMethodName = "/hyapp.game.v1.GameAppService/GetRoomRPSChallenge" ) // GameAppServiceClient is the client API for GameAppService service. @@ -47,6 +52,11 @@ type GameAppServiceClient interface { GetDiceMatch(ctx context.Context, in *GetDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) RollDiceMatch(ctx context.Context, in *RollDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) CancelDiceMatch(ctx context.Context, in *CancelDiceMatchRequest, opts ...grpc.CallOption) (*DiceMatchResponse, error) + GetRoomRPSConfig(ctx context.Context, in *GetRoomRPSConfigRequest, opts ...grpc.CallOption) (*RoomRPSConfigResponse, error) + ListRoomRPSChallenges(ctx context.Context, in *ListRoomRPSChallengesRequest, opts ...grpc.CallOption) (*ListRoomRPSChallengesResponse, error) + CreateRoomRPSChallenge(ctx context.Context, in *CreateRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) + AcceptRoomRPSChallenge(ctx context.Context, in *AcceptRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) + GetRoomRPSChallenge(ctx context.Context, in *GetRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) } type gameAppServiceClient struct { @@ -167,6 +177,56 @@ func (c *gameAppServiceClient) CancelDiceMatch(ctx context.Context, in *CancelDi return out, nil } +func (c *gameAppServiceClient) GetRoomRPSConfig(ctx context.Context, in *GetRoomRPSConfigRequest, opts ...grpc.CallOption) (*RoomRPSConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSConfigResponse) + err := c.cc.Invoke(ctx, GameAppService_GetRoomRPSConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) ListRoomRPSChallenges(ctx context.Context, in *ListRoomRPSChallengesRequest, opts ...grpc.CallOption) (*ListRoomRPSChallengesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRoomRPSChallengesResponse) + err := c.cc.Invoke(ctx, GameAppService_ListRoomRPSChallenges_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) CreateRoomRPSChallenge(ctx context.Context, in *CreateRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSChallengeResponse) + err := c.cc.Invoke(ctx, GameAppService_CreateRoomRPSChallenge_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) AcceptRoomRPSChallenge(ctx context.Context, in *AcceptRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSChallengeResponse) + err := c.cc.Invoke(ctx, GameAppService_AcceptRoomRPSChallenge_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAppServiceClient) GetRoomRPSChallenge(ctx context.Context, in *GetRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSChallengeResponse) + err := c.cc.Invoke(ctx, GameAppService_GetRoomRPSChallenge_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // GameAppServiceServer is the server API for GameAppService service. // All implementations must embed UnimplementedGameAppServiceServer // for forward compatibility. @@ -182,6 +242,11 @@ type GameAppServiceServer interface { GetDiceMatch(context.Context, *GetDiceMatchRequest) (*DiceMatchResponse, error) RollDiceMatch(context.Context, *RollDiceMatchRequest) (*DiceMatchResponse, error) CancelDiceMatch(context.Context, *CancelDiceMatchRequest) (*DiceMatchResponse, error) + GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) + ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) + CreateRoomRPSChallenge(context.Context, *CreateRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) + AcceptRoomRPSChallenge(context.Context, *AcceptRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) + GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) mustEmbedUnimplementedGameAppServiceServer() } @@ -225,6 +290,21 @@ func (UnimplementedGameAppServiceServer) RollDiceMatch(context.Context, *RollDic func (UnimplementedGameAppServiceServer) CancelDiceMatch(context.Context, *CancelDiceMatchRequest) (*DiceMatchResponse, error) { return nil, status.Error(codes.Unimplemented, "method CancelDiceMatch not implemented") } +func (UnimplementedGameAppServiceServer) GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetRoomRPSConfig not implemented") +} +func (UnimplementedGameAppServiceServer) ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListRoomRPSChallenges not implemented") +} +func (UnimplementedGameAppServiceServer) CreateRoomRPSChallenge(context.Context, *CreateRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateRoomRPSChallenge not implemented") +} +func (UnimplementedGameAppServiceServer) AcceptRoomRPSChallenge(context.Context, *AcceptRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AcceptRoomRPSChallenge not implemented") +} +func (UnimplementedGameAppServiceServer) GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetRoomRPSChallenge not implemented") +} func (UnimplementedGameAppServiceServer) mustEmbedUnimplementedGameAppServiceServer() {} func (UnimplementedGameAppServiceServer) testEmbeddedByValue() {} @@ -444,6 +524,96 @@ func _GameAppService_CancelDiceMatch_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _GameAppService_GetRoomRPSConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoomRPSConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).GetRoomRPSConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_GetRoomRPSConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).GetRoomRPSConfig(ctx, req.(*GetRoomRPSConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_ListRoomRPSChallenges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRoomRPSChallengesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).ListRoomRPSChallenges(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_ListRoomRPSChallenges_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).ListRoomRPSChallenges(ctx, req.(*ListRoomRPSChallengesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_CreateRoomRPSChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRoomRPSChallengeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).CreateRoomRPSChallenge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_CreateRoomRPSChallenge_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).CreateRoomRPSChallenge(ctx, req.(*CreateRoomRPSChallengeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_AcceptRoomRPSChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AcceptRoomRPSChallengeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).AcceptRoomRPSChallenge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_AcceptRoomRPSChallenge_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).AcceptRoomRPSChallenge(ctx, req.(*AcceptRoomRPSChallengeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAppService_GetRoomRPSChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoomRPSChallengeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).GetRoomRPSChallenge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_GetRoomRPSChallenge_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).GetRoomRPSChallenge(ctx, req.(*GetRoomRPSChallengeRequest)) + } + return interceptor(ctx, in, info, handler) +} + // GameAppService_ServiceDesc is the grpc.ServiceDesc for GameAppService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -495,6 +665,26 @@ var GameAppService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CancelDiceMatch", Handler: _GameAppService_CancelDiceMatch_Handler, }, + { + MethodName: "GetRoomRPSConfig", + Handler: _GameAppService_GetRoomRPSConfig_Handler, + }, + { + MethodName: "ListRoomRPSChallenges", + Handler: _GameAppService_ListRoomRPSChallenges_Handler, + }, + { + MethodName: "CreateRoomRPSChallenge", + Handler: _GameAppService_CreateRoomRPSChallenge_Handler, + }, + { + MethodName: "AcceptRoomRPSChallenge", + Handler: _GameAppService_AcceptRoomRPSChallenge_Handler, + }, + { + MethodName: "GetRoomRPSChallenge", + Handler: _GameAppService_GetRoomRPSChallenge_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/game/v1/game.proto", @@ -709,19 +899,25 @@ var GameCronService_ServiceDesc = grpc.ServiceDesc{ } const ( - GameAdminService_ListPlatforms_FullMethodName = "/hyapp.game.v1.GameAdminService/ListPlatforms" - GameAdminService_UpsertPlatform_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertPlatform" - GameAdminService_ListCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/ListCatalog" - GameAdminService_UpsertCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertCatalog" - GameAdminService_SetGameStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetGameStatus" - GameAdminService_DeleteCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteCatalog" - GameAdminService_ListSelfGames_FullMethodName = "/hyapp.game.v1.GameAdminService/ListSelfGames" - GameAdminService_UpdateDiceConfig_FullMethodName = "/hyapp.game.v1.GameAdminService/UpdateDiceConfig" - GameAdminService_AdjustDicePool_FullMethodName = "/hyapp.game.v1.GameAdminService/AdjustDicePool" - GameAdminService_ListDiceRobots_FullMethodName = "/hyapp.game.v1.GameAdminService/ListDiceRobots" - GameAdminService_RegisterDiceRobots_FullMethodName = "/hyapp.game.v1.GameAdminService/RegisterDiceRobots" - GameAdminService_SetDiceRobotStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetDiceRobotStatus" - GameAdminService_DeleteDiceRobot_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteDiceRobot" + GameAdminService_ListPlatforms_FullMethodName = "/hyapp.game.v1.GameAdminService/ListPlatforms" + GameAdminService_UpsertPlatform_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertPlatform" + GameAdminService_ListCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/ListCatalog" + GameAdminService_UpsertCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/UpsertCatalog" + GameAdminService_SetGameStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetGameStatus" + GameAdminService_DeleteCatalog_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteCatalog" + GameAdminService_ListSelfGames_FullMethodName = "/hyapp.game.v1.GameAdminService/ListSelfGames" + GameAdminService_UpdateDiceConfig_FullMethodName = "/hyapp.game.v1.GameAdminService/UpdateDiceConfig" + GameAdminService_AdjustDicePool_FullMethodName = "/hyapp.game.v1.GameAdminService/AdjustDicePool" + GameAdminService_ListDiceRobots_FullMethodName = "/hyapp.game.v1.GameAdminService/ListDiceRobots" + GameAdminService_RegisterDiceRobots_FullMethodName = "/hyapp.game.v1.GameAdminService/RegisterDiceRobots" + GameAdminService_SetDiceRobotStatus_FullMethodName = "/hyapp.game.v1.GameAdminService/SetDiceRobotStatus" + GameAdminService_DeleteDiceRobot_FullMethodName = "/hyapp.game.v1.GameAdminService/DeleteDiceRobot" + GameAdminService_GetRoomRPSConfig_FullMethodName = "/hyapp.game.v1.GameAdminService/GetRoomRPSConfig" + GameAdminService_UpdateRoomRPSConfig_FullMethodName = "/hyapp.game.v1.GameAdminService/UpdateRoomRPSConfig" + GameAdminService_ListRoomRPSChallenges_FullMethodName = "/hyapp.game.v1.GameAdminService/ListRoomRPSChallenges" + GameAdminService_GetRoomRPSChallenge_FullMethodName = "/hyapp.game.v1.GameAdminService/GetRoomRPSChallenge" + GameAdminService_RetryRoomRPSSettlement_FullMethodName = "/hyapp.game.v1.GameAdminService/RetryRoomRPSSettlement" + GameAdminService_ExpireRoomRPSChallenge_FullMethodName = "/hyapp.game.v1.GameAdminService/ExpireRoomRPSChallenge" ) // GameAdminServiceClient is the client API for GameAdminService service. @@ -741,6 +937,12 @@ type GameAdminServiceClient interface { RegisterDiceRobots(ctx context.Context, in *RegisterDiceRobotsRequest, opts ...grpc.CallOption) (*RegisterDiceRobotsResponse, error) SetDiceRobotStatus(ctx context.Context, in *SetDiceRobotStatusRequest, opts ...grpc.CallOption) (*DiceRobotResponse, error) DeleteDiceRobot(ctx context.Context, in *DeleteDiceRobotRequest, opts ...grpc.CallOption) (*DeleteDiceRobotResponse, error) + GetRoomRPSConfig(ctx context.Context, in *GetRoomRPSConfigRequest, opts ...grpc.CallOption) (*RoomRPSConfigResponse, error) + UpdateRoomRPSConfig(ctx context.Context, in *UpdateRoomRPSConfigRequest, opts ...grpc.CallOption) (*RoomRPSConfigResponse, error) + ListRoomRPSChallenges(ctx context.Context, in *ListRoomRPSChallengesRequest, opts ...grpc.CallOption) (*ListRoomRPSChallengesResponse, error) + GetRoomRPSChallenge(ctx context.Context, in *GetRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) + RetryRoomRPSSettlement(ctx context.Context, in *RetryRoomRPSSettlementRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) + ExpireRoomRPSChallenge(ctx context.Context, in *ExpireRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) } type gameAdminServiceClient struct { @@ -881,6 +1083,66 @@ func (c *gameAdminServiceClient) DeleteDiceRobot(ctx context.Context, in *Delete return out, nil } +func (c *gameAdminServiceClient) GetRoomRPSConfig(ctx context.Context, in *GetRoomRPSConfigRequest, opts ...grpc.CallOption) (*RoomRPSConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSConfigResponse) + err := c.cc.Invoke(ctx, GameAdminService_GetRoomRPSConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) UpdateRoomRPSConfig(ctx context.Context, in *UpdateRoomRPSConfigRequest, opts ...grpc.CallOption) (*RoomRPSConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSConfigResponse) + err := c.cc.Invoke(ctx, GameAdminService_UpdateRoomRPSConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) ListRoomRPSChallenges(ctx context.Context, in *ListRoomRPSChallengesRequest, opts ...grpc.CallOption) (*ListRoomRPSChallengesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRoomRPSChallengesResponse) + err := c.cc.Invoke(ctx, GameAdminService_ListRoomRPSChallenges_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) GetRoomRPSChallenge(ctx context.Context, in *GetRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSChallengeResponse) + err := c.cc.Invoke(ctx, GameAdminService_GetRoomRPSChallenge_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) RetryRoomRPSSettlement(ctx context.Context, in *RetryRoomRPSSettlementRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSChallengeResponse) + err := c.cc.Invoke(ctx, GameAdminService_RetryRoomRPSSettlement_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *gameAdminServiceClient) ExpireRoomRPSChallenge(ctx context.Context, in *ExpireRoomRPSChallengeRequest, opts ...grpc.CallOption) (*RoomRPSChallengeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RoomRPSChallengeResponse) + err := c.cc.Invoke(ctx, GameAdminService_ExpireRoomRPSChallenge_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // GameAdminServiceServer is the server API for GameAdminService service. // All implementations must embed UnimplementedGameAdminServiceServer // for forward compatibility. @@ -898,6 +1160,12 @@ type GameAdminServiceServer interface { RegisterDiceRobots(context.Context, *RegisterDiceRobotsRequest) (*RegisterDiceRobotsResponse, error) SetDiceRobotStatus(context.Context, *SetDiceRobotStatusRequest) (*DiceRobotResponse, error) DeleteDiceRobot(context.Context, *DeleteDiceRobotRequest) (*DeleteDiceRobotResponse, error) + GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) + UpdateRoomRPSConfig(context.Context, *UpdateRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) + ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) + GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) + RetryRoomRPSSettlement(context.Context, *RetryRoomRPSSettlementRequest) (*RoomRPSChallengeResponse, error) + ExpireRoomRPSChallenge(context.Context, *ExpireRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) mustEmbedUnimplementedGameAdminServiceServer() } @@ -947,6 +1215,24 @@ func (UnimplementedGameAdminServiceServer) SetDiceRobotStatus(context.Context, * func (UnimplementedGameAdminServiceServer) DeleteDiceRobot(context.Context, *DeleteDiceRobotRequest) (*DeleteDiceRobotResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeleteDiceRobot not implemented") } +func (UnimplementedGameAdminServiceServer) GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetRoomRPSConfig not implemented") +} +func (UnimplementedGameAdminServiceServer) UpdateRoomRPSConfig(context.Context, *UpdateRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateRoomRPSConfig not implemented") +} +func (UnimplementedGameAdminServiceServer) ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListRoomRPSChallenges not implemented") +} +func (UnimplementedGameAdminServiceServer) GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetRoomRPSChallenge not implemented") +} +func (UnimplementedGameAdminServiceServer) RetryRoomRPSSettlement(context.Context, *RetryRoomRPSSettlementRequest) (*RoomRPSChallengeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RetryRoomRPSSettlement not implemented") +} +func (UnimplementedGameAdminServiceServer) ExpireRoomRPSChallenge(context.Context, *ExpireRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExpireRoomRPSChallenge not implemented") +} func (UnimplementedGameAdminServiceServer) mustEmbedUnimplementedGameAdminServiceServer() {} func (UnimplementedGameAdminServiceServer) testEmbeddedByValue() {} @@ -1202,6 +1488,114 @@ func _GameAdminService_DeleteDiceRobot_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _GameAdminService_GetRoomRPSConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoomRPSConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).GetRoomRPSConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_GetRoomRPSConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).GetRoomRPSConfig(ctx, req.(*GetRoomRPSConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_UpdateRoomRPSConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateRoomRPSConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).UpdateRoomRPSConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_UpdateRoomRPSConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).UpdateRoomRPSConfig(ctx, req.(*UpdateRoomRPSConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_ListRoomRPSChallenges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRoomRPSChallengesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).ListRoomRPSChallenges(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_ListRoomRPSChallenges_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).ListRoomRPSChallenges(ctx, req.(*ListRoomRPSChallengesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_GetRoomRPSChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoomRPSChallengeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).GetRoomRPSChallenge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_GetRoomRPSChallenge_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).GetRoomRPSChallenge(ctx, req.(*GetRoomRPSChallengeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_RetryRoomRPSSettlement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RetryRoomRPSSettlementRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).RetryRoomRPSSettlement(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_RetryRoomRPSSettlement_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).RetryRoomRPSSettlement(ctx, req.(*RetryRoomRPSSettlementRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GameAdminService_ExpireRoomRPSChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExpireRoomRPSChallengeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAdminServiceServer).ExpireRoomRPSChallenge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAdminService_ExpireRoomRPSChallenge_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAdminServiceServer).ExpireRoomRPSChallenge(ctx, req.(*ExpireRoomRPSChallengeRequest)) + } + return interceptor(ctx, in, info, handler) +} + // GameAdminService_ServiceDesc is the grpc.ServiceDesc for GameAdminService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1261,6 +1655,30 @@ var GameAdminService_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeleteDiceRobot", Handler: _GameAdminService_DeleteDiceRobot_Handler, }, + { + MethodName: "GetRoomRPSConfig", + Handler: _GameAdminService_GetRoomRPSConfig_Handler, + }, + { + MethodName: "UpdateRoomRPSConfig", + Handler: _GameAdminService_UpdateRoomRPSConfig_Handler, + }, + { + MethodName: "ListRoomRPSChallenges", + Handler: _GameAdminService_ListRoomRPSChallenges_Handler, + }, + { + MethodName: "GetRoomRPSChallenge", + Handler: _GameAdminService_GetRoomRPSChallenge_Handler, + }, + { + MethodName: "RetryRoomRPSSettlement", + Handler: _GameAdminService_RetryRoomRPSSettlement_Handler, + }, + { + MethodName: "ExpireRoomRPSChallenge", + Handler: _GameAdminService_ExpireRoomRPSChallenge_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/game/v1/game.proto", diff --git a/api/proto/user/v1/auth.pb.go b/api/proto/user/v1/auth.pb.go index 5f78c911..0f025c85 100644 --- a/api/proto/user/v1/auth.pb.go +++ b/api/proto/user/v1/auth.pb.go @@ -454,7 +454,7 @@ func (x *SetPasswordResponse) GetPasswordSet() bool { return false } -// QuickCreateAccountRequest 为测试和三方联调快速创建一个可密码登录的完整账号。 +// 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"` @@ -643,7 +643,7 @@ func (x *QuickCreateAccountRequest) GetTimezone() string { return "" } -// QuickCreateAccountResponse 返回新账号登录态;display_user_id 即后续账号密码登录用账号。 +// 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"` diff --git a/api/proto/user/v1/auth.proto b/api/proto/user/v1/auth.proto index 62d5b071..f189400c 100644 --- a/api/proto/user/v1/auth.proto +++ b/api/proto/user/v1/auth.proto @@ -57,7 +57,7 @@ message SetPasswordResponse { bool password_set = 1; } -// QuickCreateAccountRequest 为测试和三方联调快速创建一个可密码登录的完整账号。 +// QuickCreateAccountRequest 为后台快速创建完整资料账号;source=game_robot 时账号不可登录。 message QuickCreateAccountRequest { RequestMeta meta = 1; string password = 2; @@ -80,7 +80,7 @@ message QuickCreateAccountRequest { string timezone = 19; } -// QuickCreateAccountResponse 返回新账号登录态;display_user_id 即后续账号密码登录用账号。 +// QuickCreateAccountResponse 返回新账号身份;普通账号含登录态,source=game_robot 时 token 只含 user_id/display_user_id。 message QuickCreateAccountResponse { AuthToken token = 1; bool password_set = 2; diff --git a/api/proto/user/v1/user.pb.go b/api/proto/user/v1/user.pb.go index d15b7e79..719c21d9 100644 --- a/api/proto/user/v1/user.pb.go +++ b/api/proto/user/v1/user.pb.go @@ -3756,6 +3756,285 @@ func (x *CPRelationship) GetEndedAtMs() int64 { return 0 } +// CPAvatarFrameSnapshot 是排行榜里展示用户当前头像框的轻量快照。 +type CPAvatarFrameSnapshot struct { + state protoimpl.MessageState `protogen:"open.v1"` + ResourceId int64 `protobuf:"varint,1,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` + ResourceCode string `protobuf:"bytes,2,opt,name=resource_code,json=resourceCode,proto3" json:"resource_code,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + AssetUrl string `protobuf:"bytes,4,opt,name=asset_url,json=assetUrl,proto3" json:"asset_url,omitempty"` + PreviewUrl string `protobuf:"bytes,5,opt,name=preview_url,json=previewUrl,proto3" json:"preview_url,omitempty"` + AnimationUrl string `protobuf:"bytes,6,opt,name=animation_url,json=animationUrl,proto3" json:"animation_url,omitempty"` + MetadataJson string `protobuf:"bytes,7,opt,name=metadata_json,json=metadataJson,proto3" json:"metadata_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CPAvatarFrameSnapshot) Reset() { + *x = CPAvatarFrameSnapshot{} + mi := &file_proto_user_v1_user_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CPAvatarFrameSnapshot) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CPAvatarFrameSnapshot) ProtoMessage() {} + +func (x *CPAvatarFrameSnapshot) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[49] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CPAvatarFrameSnapshot.ProtoReflect.Descriptor instead. +func (*CPAvatarFrameSnapshot) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{49} +} + +func (x *CPAvatarFrameSnapshot) GetResourceId() int64 { + if x != nil { + return x.ResourceId + } + return 0 +} + +func (x *CPAvatarFrameSnapshot) GetResourceCode() string { + if x != nil { + return x.ResourceCode + } + return "" +} + +func (x *CPAvatarFrameSnapshot) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CPAvatarFrameSnapshot) GetAssetUrl() string { + if x != nil { + return x.AssetUrl + } + return "" +} + +func (x *CPAvatarFrameSnapshot) GetPreviewUrl() string { + if x != nil { + return x.PreviewUrl + } + return "" +} + +func (x *CPAvatarFrameSnapshot) GetAnimationUrl() string { + if x != nil { + return x.AnimationUrl + } + return "" +} + +func (x *CPAvatarFrameSnapshot) GetMetadataJson() string { + if x != nil { + return x.MetadataJson + } + return "" +} + +// CPIntimacyLeaderboardUser 是 CP 亲密榜中一方用户的展示资料。 +type CPIntimacyLeaderboardUser struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,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"` + AvatarFrame *CPAvatarFrameSnapshot `protobuf:"bytes,5,opt,name=avatar_frame,json=avatarFrame,proto3" json:"avatar_frame,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CPIntimacyLeaderboardUser) Reset() { + *x = CPIntimacyLeaderboardUser{} + mi := &file_proto_user_v1_user_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CPIntimacyLeaderboardUser) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CPIntimacyLeaderboardUser) ProtoMessage() {} + +func (x *CPIntimacyLeaderboardUser) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CPIntimacyLeaderboardUser.ProtoReflect.Descriptor instead. +func (*CPIntimacyLeaderboardUser) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{50} +} + +func (x *CPIntimacyLeaderboardUser) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *CPIntimacyLeaderboardUser) GetDisplayUserId() string { + if x != nil { + return x.DisplayUserId + } + return "" +} + +func (x *CPIntimacyLeaderboardUser) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *CPIntimacyLeaderboardUser) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +func (x *CPIntimacyLeaderboardUser) GetAvatarFrame() *CPAvatarFrameSnapshot { + if x != nil { + return x.AvatarFrame + } + return nil +} + +// CPIntimacyLeaderboardItem 是一对 active 关系在亲密值榜单里的读模型。 +type CPIntimacyLeaderboardItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Rank int64 `protobuf:"varint,1,opt,name=rank,proto3" json:"rank,omitempty"` + RelationshipId string `protobuf:"bytes,2,opt,name=relationship_id,json=relationshipId,proto3" json:"relationship_id,omitempty"` + RelationType string `protobuf:"bytes,3,opt,name=relation_type,json=relationType,proto3" json:"relation_type,omitempty"` + IntimacyValue int64 `protobuf:"varint,4,opt,name=intimacy_value,json=intimacyValue,proto3" json:"intimacy_value,omitempty"` + Level int32 `protobuf:"varint,5,opt,name=level,proto3" json:"level,omitempty"` + UserA *CPIntimacyLeaderboardUser `protobuf:"bytes,6,opt,name=user_a,json=userA,proto3" json:"user_a,omitempty"` + UserB *CPIntimacyLeaderboardUser `protobuf:"bytes,7,opt,name=user_b,json=userB,proto3" json:"user_b,omitempty"` + FormedAtMs int64 `protobuf:"varint,8,opt,name=formed_at_ms,json=formedAtMs,proto3" json:"formed_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CPIntimacyLeaderboardItem) Reset() { + *x = CPIntimacyLeaderboardItem{} + mi := &file_proto_user_v1_user_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CPIntimacyLeaderboardItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CPIntimacyLeaderboardItem) ProtoMessage() {} + +func (x *CPIntimacyLeaderboardItem) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CPIntimacyLeaderboardItem.ProtoReflect.Descriptor instead. +func (*CPIntimacyLeaderboardItem) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{51} +} + +func (x *CPIntimacyLeaderboardItem) GetRank() int64 { + if x != nil { + return x.Rank + } + return 0 +} + +func (x *CPIntimacyLeaderboardItem) GetRelationshipId() string { + if x != nil { + return x.RelationshipId + } + return "" +} + +func (x *CPIntimacyLeaderboardItem) GetRelationType() string { + if x != nil { + return x.RelationType + } + return "" +} + +func (x *CPIntimacyLeaderboardItem) GetIntimacyValue() int64 { + if x != nil { + return x.IntimacyValue + } + return 0 +} + +func (x *CPIntimacyLeaderboardItem) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *CPIntimacyLeaderboardItem) GetUserA() *CPIntimacyLeaderboardUser { + if x != nil { + return x.UserA + } + return nil +} + +func (x *CPIntimacyLeaderboardItem) GetUserB() *CPIntimacyLeaderboardUser { + if x != nil { + return x.UserB + } + return nil +} + +func (x *CPIntimacyLeaderboardItem) GetFormedAtMs() int64 { + if x != nil { + return x.FormedAtMs + } + return 0 +} + +func (x *CPIntimacyLeaderboardItem) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + type ListCPApplicationsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -3770,7 +4049,7 @@ type ListCPApplicationsRequest struct { func (x *ListCPApplicationsRequest) Reset() { *x = ListCPApplicationsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[49] + mi := &file_proto_user_v1_user_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3782,7 +4061,7 @@ func (x *ListCPApplicationsRequest) String() string { func (*ListCPApplicationsRequest) ProtoMessage() {} func (x *ListCPApplicationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[49] + mi := &file_proto_user_v1_user_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3795,7 +4074,7 @@ func (x *ListCPApplicationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPApplicationsRequest.ProtoReflect.Descriptor instead. func (*ListCPApplicationsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{49} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{52} } func (x *ListCPApplicationsRequest) GetMeta() *RequestMeta { @@ -3851,7 +4130,7 @@ type ListCPApplicationsResponse struct { func (x *ListCPApplicationsResponse) Reset() { *x = ListCPApplicationsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[50] + mi := &file_proto_user_v1_user_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3863,7 +4142,7 @@ func (x *ListCPApplicationsResponse) String() string { func (*ListCPApplicationsResponse) ProtoMessage() {} func (x *ListCPApplicationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[50] + mi := &file_proto_user_v1_user_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3876,7 +4155,7 @@ func (x *ListCPApplicationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPApplicationsResponse.ProtoReflect.Descriptor instead. func (*ListCPApplicationsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{50} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{53} } func (x *ListCPApplicationsResponse) GetApplications() []*CPApplication { @@ -3911,7 +4190,7 @@ type AcceptCPApplicationRequest struct { func (x *AcceptCPApplicationRequest) Reset() { *x = AcceptCPApplicationRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[51] + mi := &file_proto_user_v1_user_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3923,7 +4202,7 @@ func (x *AcceptCPApplicationRequest) String() string { func (*AcceptCPApplicationRequest) ProtoMessage() {} func (x *AcceptCPApplicationRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[51] + mi := &file_proto_user_v1_user_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3936,7 +4215,7 @@ func (x *AcceptCPApplicationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptCPApplicationRequest.ProtoReflect.Descriptor instead. func (*AcceptCPApplicationRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{51} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{54} } func (x *AcceptCPApplicationRequest) GetMeta() *RequestMeta { @@ -3970,7 +4249,7 @@ type AcceptCPApplicationResponse struct { func (x *AcceptCPApplicationResponse) Reset() { *x = AcceptCPApplicationResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[52] + mi := &file_proto_user_v1_user_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3982,7 +4261,7 @@ func (x *AcceptCPApplicationResponse) String() string { func (*AcceptCPApplicationResponse) ProtoMessage() {} func (x *AcceptCPApplicationResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[52] + mi := &file_proto_user_v1_user_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3995,7 +4274,7 @@ func (x *AcceptCPApplicationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptCPApplicationResponse.ProtoReflect.Descriptor instead. func (*AcceptCPApplicationResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{52} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{55} } func (x *AcceptCPApplicationResponse) GetApplication() *CPApplication { @@ -4024,7 +4303,7 @@ type RejectCPApplicationRequest struct { func (x *RejectCPApplicationRequest) Reset() { *x = RejectCPApplicationRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[53] + mi := &file_proto_user_v1_user_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4036,7 +4315,7 @@ func (x *RejectCPApplicationRequest) String() string { func (*RejectCPApplicationRequest) ProtoMessage() {} func (x *RejectCPApplicationRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[53] + mi := &file_proto_user_v1_user_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4049,7 +4328,7 @@ func (x *RejectCPApplicationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectCPApplicationRequest.ProtoReflect.Descriptor instead. func (*RejectCPApplicationRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{53} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{56} } func (x *RejectCPApplicationRequest) GetMeta() *RequestMeta { @@ -4089,7 +4368,7 @@ type RejectCPApplicationResponse struct { func (x *RejectCPApplicationResponse) Reset() { *x = RejectCPApplicationResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[54] + mi := &file_proto_user_v1_user_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4101,7 +4380,7 @@ func (x *RejectCPApplicationResponse) String() string { func (*RejectCPApplicationResponse) ProtoMessage() {} func (x *RejectCPApplicationResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[54] + mi := &file_proto_user_v1_user_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4114,7 +4393,7 @@ func (x *RejectCPApplicationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectCPApplicationResponse.ProtoReflect.Descriptor instead. func (*RejectCPApplicationResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{54} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{57} } func (x *RejectCPApplicationResponse) GetApplication() *CPApplication { @@ -4137,7 +4416,7 @@ type ListCPRelationshipsRequest struct { func (x *ListCPRelationshipsRequest) Reset() { *x = ListCPRelationshipsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[55] + mi := &file_proto_user_v1_user_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4149,7 +4428,7 @@ func (x *ListCPRelationshipsRequest) String() string { func (*ListCPRelationshipsRequest) ProtoMessage() {} func (x *ListCPRelationshipsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[55] + mi := &file_proto_user_v1_user_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4162,7 +4441,7 @@ func (x *ListCPRelationshipsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPRelationshipsRequest.ProtoReflect.Descriptor instead. func (*ListCPRelationshipsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{55} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{58} } func (x *ListCPRelationshipsRequest) GetMeta() *RequestMeta { @@ -4210,7 +4489,7 @@ type ListCPRelationshipsResponse struct { func (x *ListCPRelationshipsResponse) Reset() { *x = ListCPRelationshipsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[56] + mi := &file_proto_user_v1_user_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4222,7 +4501,7 @@ func (x *ListCPRelationshipsResponse) String() string { func (*ListCPRelationshipsResponse) ProtoMessage() {} func (x *ListCPRelationshipsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[56] + mi := &file_proto_user_v1_user_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4235,7 +4514,7 @@ func (x *ListCPRelationshipsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPRelationshipsResponse.ProtoReflect.Descriptor instead. func (*ListCPRelationshipsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{56} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{59} } func (x *ListCPRelationshipsResponse) GetRelationships() []*CPRelationship { @@ -4252,6 +4531,150 @@ func (x *ListCPRelationshipsResponse) GetTotal() int64 { return 0 } +type ListCPIntimacyLeaderboardRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + RelationType string `protobuf:"bytes,2,opt,name=relation_type,json=relationType,proto3" json:"relation_type,omitempty"` + Page int32 `protobuf:"varint,3,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListCPIntimacyLeaderboardRequest) Reset() { + *x = ListCPIntimacyLeaderboardRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCPIntimacyLeaderboardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCPIntimacyLeaderboardRequest) ProtoMessage() {} + +func (x *ListCPIntimacyLeaderboardRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[60] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListCPIntimacyLeaderboardRequest.ProtoReflect.Descriptor instead. +func (*ListCPIntimacyLeaderboardRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{60} +} + +func (x *ListCPIntimacyLeaderboardRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListCPIntimacyLeaderboardRequest) GetRelationType() string { + if x != nil { + return x.RelationType + } + return "" +} + +func (x *ListCPIntimacyLeaderboardRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListCPIntimacyLeaderboardRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ListCPIntimacyLeaderboardResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*CPIntimacyLeaderboardItem `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + Page int32 `protobuf:"varint,3,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + ServerTimeMs int64 `protobuf:"varint,5,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListCPIntimacyLeaderboardResponse) Reset() { + *x = ListCPIntimacyLeaderboardResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCPIntimacyLeaderboardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCPIntimacyLeaderboardResponse) ProtoMessage() {} + +func (x *ListCPIntimacyLeaderboardResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[61] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListCPIntimacyLeaderboardResponse.ProtoReflect.Descriptor instead. +func (*ListCPIntimacyLeaderboardResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{61} +} + +func (x *ListCPIntimacyLeaderboardResponse) GetItems() []*CPIntimacyLeaderboardItem { + if x != nil { + return x.Items + } + return nil +} + +func (x *ListCPIntimacyLeaderboardResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListCPIntimacyLeaderboardResponse) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListCPIntimacyLeaderboardResponse) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListCPIntimacyLeaderboardResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + type PrepareBreakCPRelationshipRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -4264,7 +4687,7 @@ type PrepareBreakCPRelationshipRequest struct { func (x *PrepareBreakCPRelationshipRequest) Reset() { *x = PrepareBreakCPRelationshipRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[57] + mi := &file_proto_user_v1_user_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4276,7 +4699,7 @@ func (x *PrepareBreakCPRelationshipRequest) String() string { func (*PrepareBreakCPRelationshipRequest) ProtoMessage() {} func (x *PrepareBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[57] + mi := &file_proto_user_v1_user_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4289,7 +4712,7 @@ func (x *PrepareBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message // Deprecated: Use PrepareBreakCPRelationshipRequest.ProtoReflect.Descriptor instead. func (*PrepareBreakCPRelationshipRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{57} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{62} } func (x *PrepareBreakCPRelationshipRequest) GetMeta() *RequestMeta { @@ -4334,7 +4757,7 @@ type PrepareBreakCPRelationshipResponse struct { func (x *PrepareBreakCPRelationshipResponse) Reset() { *x = PrepareBreakCPRelationshipResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[58] + mi := &file_proto_user_v1_user_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4346,7 +4769,7 @@ func (x *PrepareBreakCPRelationshipResponse) String() string { func (*PrepareBreakCPRelationshipResponse) ProtoMessage() {} func (x *PrepareBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[58] + mi := &file_proto_user_v1_user_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4359,7 +4782,7 @@ func (x *PrepareBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message // Deprecated: Use PrepareBreakCPRelationshipResponse.ProtoReflect.Descriptor instead. func (*PrepareBreakCPRelationshipResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{58} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{63} } func (x *PrepareBreakCPRelationshipResponse) GetRelationship() *CPRelationship { @@ -4419,7 +4842,7 @@ type ConfirmBreakCPRelationshipRequest struct { func (x *ConfirmBreakCPRelationshipRequest) Reset() { *x = ConfirmBreakCPRelationshipRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[59] + mi := &file_proto_user_v1_user_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4431,7 +4854,7 @@ func (x *ConfirmBreakCPRelationshipRequest) String() string { func (*ConfirmBreakCPRelationshipRequest) ProtoMessage() {} func (x *ConfirmBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[59] + mi := &file_proto_user_v1_user_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4444,7 +4867,7 @@ func (x *ConfirmBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ConfirmBreakCPRelationshipRequest.ProtoReflect.Descriptor instead. func (*ConfirmBreakCPRelationshipRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{59} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{64} } func (x *ConfirmBreakCPRelationshipRequest) GetMeta() *RequestMeta { @@ -4508,7 +4931,7 @@ type ConfirmBreakCPRelationshipResponse struct { func (x *ConfirmBreakCPRelationshipResponse) Reset() { *x = ConfirmBreakCPRelationshipResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[60] + mi := &file_proto_user_v1_user_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4520,7 +4943,7 @@ func (x *ConfirmBreakCPRelationshipResponse) String() string { func (*ConfirmBreakCPRelationshipResponse) ProtoMessage() {} func (x *ConfirmBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[60] + mi := &file_proto_user_v1_user_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4533,7 +4956,7 @@ func (x *ConfirmBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ConfirmBreakCPRelationshipResponse.ProtoReflect.Descriptor instead. func (*ConfirmBreakCPRelationshipResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{60} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{65} } func (x *ConfirmBreakCPRelationshipResponse) GetRelationship() *CPRelationship { @@ -4577,7 +5000,7 @@ type CancelBreakCPRelationshipRequest struct { func (x *CancelBreakCPRelationshipRequest) Reset() { *x = CancelBreakCPRelationshipRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[61] + mi := &file_proto_user_v1_user_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4589,7 +5012,7 @@ func (x *CancelBreakCPRelationshipRequest) String() string { func (*CancelBreakCPRelationshipRequest) ProtoMessage() {} func (x *CancelBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[61] + mi := &file_proto_user_v1_user_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4602,7 +5025,7 @@ func (x *CancelBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelBreakCPRelationshipRequest.ProtoReflect.Descriptor instead. func (*CancelBreakCPRelationshipRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{61} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{66} } func (x *CancelBreakCPRelationshipRequest) GetMeta() *RequestMeta { @@ -4650,7 +5073,7 @@ type CancelBreakCPRelationshipResponse struct { func (x *CancelBreakCPRelationshipResponse) Reset() { *x = CancelBreakCPRelationshipResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[62] + mi := &file_proto_user_v1_user_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4662,7 +5085,7 @@ func (x *CancelBreakCPRelationshipResponse) String() string { func (*CancelBreakCPRelationshipResponse) ProtoMessage() {} func (x *CancelBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[62] + mi := &file_proto_user_v1_user_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4675,7 +5098,7 @@ func (x *CancelBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message // Deprecated: Use CancelBreakCPRelationshipResponse.ProtoReflect.Descriptor instead. func (*CancelBreakCPRelationshipResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{62} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{67} } func (x *CancelBreakCPRelationshipResponse) GetCommandId() string { @@ -4718,7 +5141,7 @@ type RoomGiftCPEvent struct { func (x *RoomGiftCPEvent) Reset() { *x = RoomGiftCPEvent{} - mi := &file_proto_user_v1_user_proto_msgTypes[63] + mi := &file_proto_user_v1_user_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4730,7 +5153,7 @@ func (x *RoomGiftCPEvent) String() string { func (*RoomGiftCPEvent) ProtoMessage() {} func (x *RoomGiftCPEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[63] + mi := &file_proto_user_v1_user_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4743,7 +5166,7 @@ func (x *RoomGiftCPEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomGiftCPEvent.ProtoReflect.Descriptor instead. func (*RoomGiftCPEvent) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{63} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{68} } func (x *RoomGiftCPEvent) GetMeta() *RequestMeta { @@ -4882,7 +5305,7 @@ type ConsumeRoomGiftCPEventRequest struct { func (x *ConsumeRoomGiftCPEventRequest) Reset() { *x = ConsumeRoomGiftCPEventRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[64] + mi := &file_proto_user_v1_user_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4894,7 +5317,7 @@ func (x *ConsumeRoomGiftCPEventRequest) String() string { func (*ConsumeRoomGiftCPEventRequest) ProtoMessage() {} func (x *ConsumeRoomGiftCPEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[64] + mi := &file_proto_user_v1_user_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4907,7 +5330,7 @@ func (x *ConsumeRoomGiftCPEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeRoomGiftCPEventRequest.ProtoReflect.Descriptor instead. func (*ConsumeRoomGiftCPEventRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{64} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{69} } func (x *ConsumeRoomGiftCPEventRequest) GetMeta() *RequestMeta { @@ -4935,7 +5358,7 @@ type ConsumeRoomGiftCPEventResponse struct { func (x *ConsumeRoomGiftCPEventResponse) Reset() { *x = ConsumeRoomGiftCPEventResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[65] + mi := &file_proto_user_v1_user_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4947,7 +5370,7 @@ func (x *ConsumeRoomGiftCPEventResponse) String() string { func (*ConsumeRoomGiftCPEventResponse) ProtoMessage() {} func (x *ConsumeRoomGiftCPEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[65] + mi := &file_proto_user_v1_user_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4960,7 +5383,7 @@ func (x *ConsumeRoomGiftCPEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeRoomGiftCPEventResponse.ProtoReflect.Descriptor instead. func (*ConsumeRoomGiftCPEventResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{65} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{70} } func (x *ConsumeRoomGiftCPEventResponse) GetConsumed() bool { @@ -5003,7 +5426,7 @@ type UserReport struct { func (x *UserReport) Reset() { *x = UserReport{} - mi := &file_proto_user_v1_user_proto_msgTypes[66] + mi := &file_proto_user_v1_user_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5015,7 +5438,7 @@ func (x *UserReport) String() string { func (*UserReport) ProtoMessage() {} func (x *UserReport) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[66] + mi := &file_proto_user_v1_user_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5028,7 +5451,7 @@ func (x *UserReport) ProtoReflect() protoreflect.Message { // Deprecated: Use UserReport.ProtoReflect.Descriptor instead. func (*UserReport) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{66} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{71} } func (x *UserReport) GetReportId() string { @@ -5117,7 +5540,7 @@ type SubmitReportRequest struct { func (x *SubmitReportRequest) Reset() { *x = SubmitReportRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[67] + mi := &file_proto_user_v1_user_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5129,7 +5552,7 @@ func (x *SubmitReportRequest) String() string { func (*SubmitReportRequest) ProtoMessage() {} func (x *SubmitReportRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[67] + mi := &file_proto_user_v1_user_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5142,7 +5565,7 @@ func (x *SubmitReportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitReportRequest.ProtoReflect.Descriptor instead. func (*SubmitReportRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{67} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{72} } func (x *SubmitReportRequest) GetMeta() *RequestMeta { @@ -5203,7 +5626,7 @@ type SubmitReportResponse struct { func (x *SubmitReportResponse) Reset() { *x = SubmitReportResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[68] + mi := &file_proto_user_v1_user_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5215,7 +5638,7 @@ func (x *SubmitReportResponse) String() string { func (*SubmitReportResponse) ProtoMessage() {} func (x *SubmitReportResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[68] + mi := &file_proto_user_v1_user_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5228,7 +5651,7 @@ func (x *SubmitReportResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitReportResponse.ProtoReflect.Descriptor instead. func (*SubmitReportResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{68} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{73} } func (x *SubmitReportResponse) GetReport() *UserReport { @@ -5249,7 +5672,7 @@ type BatchGetUsersRequest struct { func (x *BatchGetUsersRequest) Reset() { *x = BatchGetUsersRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[69] + mi := &file_proto_user_v1_user_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5261,7 +5684,7 @@ func (x *BatchGetUsersRequest) String() string { func (*BatchGetUsersRequest) ProtoMessage() {} func (x *BatchGetUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[69] + mi := &file_proto_user_v1_user_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5274,7 +5697,7 @@ func (x *BatchGetUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchGetUsersRequest.ProtoReflect.Descriptor instead. func (*BatchGetUsersRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{69} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{74} } func (x *BatchGetUsersRequest) GetMeta() *RequestMeta { @@ -5301,7 +5724,7 @@ type BatchGetUsersResponse struct { func (x *BatchGetUsersResponse) Reset() { *x = BatchGetUsersResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[70] + mi := &file_proto_user_v1_user_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5313,7 +5736,7 @@ func (x *BatchGetUsersResponse) String() string { func (*BatchGetUsersResponse) ProtoMessage() {} func (x *BatchGetUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[70] + mi := &file_proto_user_v1_user_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5326,7 +5749,7 @@ func (x *BatchGetUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchGetUsersResponse.ProtoReflect.Descriptor instead. func (*BatchGetUsersResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{70} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{75} } func (x *BatchGetUsersResponse) GetUsers() map[int64]*User { @@ -5351,7 +5774,7 @@ type ListUserIDsRequest struct { func (x *ListUserIDsRequest) Reset() { *x = ListUserIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[71] + mi := &file_proto_user_v1_user_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5363,7 +5786,7 @@ func (x *ListUserIDsRequest) String() string { func (*ListUserIDsRequest) ProtoMessage() {} func (x *ListUserIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[71] + mi := &file_proto_user_v1_user_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5376,7 +5799,7 @@ func (x *ListUserIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserIDsRequest.ProtoReflect.Descriptor instead. func (*ListUserIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{71} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{76} } func (x *ListUserIDsRequest) GetMeta() *RequestMeta { @@ -5433,7 +5856,7 @@ type ListUserIDsResponse struct { func (x *ListUserIDsResponse) Reset() { *x = ListUserIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[72] + mi := &file_proto_user_v1_user_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5445,7 +5868,7 @@ func (x *ListUserIDsResponse) String() string { func (*ListUserIDsResponse) ProtoMessage() {} func (x *ListUserIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[72] + mi := &file_proto_user_v1_user_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5458,7 +5881,7 @@ func (x *ListUserIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserIDsResponse.ProtoReflect.Descriptor instead. func (*ListUserIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{72} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{77} } func (x *ListUserIDsResponse) GetUserIds() []int64 { @@ -5497,7 +5920,7 @@ type UpdateUserProfileRequest struct { func (x *UpdateUserProfileRequest) Reset() { *x = UpdateUserProfileRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[73] + mi := &file_proto_user_v1_user_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5509,7 +5932,7 @@ func (x *UpdateUserProfileRequest) String() string { func (*UpdateUserProfileRequest) ProtoMessage() {} func (x *UpdateUserProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[73] + mi := &file_proto_user_v1_user_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5522,7 +5945,7 @@ func (x *UpdateUserProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserProfileRequest.ProtoReflect.Descriptor instead. func (*UpdateUserProfileRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{73} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{78} } func (x *UpdateUserProfileRequest) GetMeta() *RequestMeta { @@ -5577,7 +6000,7 @@ type UpdateUserProfileResponse struct { func (x *UpdateUserProfileResponse) Reset() { *x = UpdateUserProfileResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[74] + mi := &file_proto_user_v1_user_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5589,7 +6012,7 @@ func (x *UpdateUserProfileResponse) String() string { func (*UpdateUserProfileResponse) ProtoMessage() {} func (x *UpdateUserProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[74] + mi := &file_proto_user_v1_user_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5602,7 +6025,7 @@ func (x *UpdateUserProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserProfileResponse.ProtoReflect.Descriptor instead. func (*UpdateUserProfileResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{74} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{79} } func (x *UpdateUserProfileResponse) GetUser() *User { @@ -5624,7 +6047,7 @@ type ChangeUserCountryRequest struct { func (x *ChangeUserCountryRequest) Reset() { *x = ChangeUserCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[75] + mi := &file_proto_user_v1_user_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5636,7 +6059,7 @@ func (x *ChangeUserCountryRequest) String() string { func (*ChangeUserCountryRequest) ProtoMessage() {} func (x *ChangeUserCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[75] + mi := &file_proto_user_v1_user_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5649,7 +6072,7 @@ func (x *ChangeUserCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeUserCountryRequest.ProtoReflect.Descriptor instead. func (*ChangeUserCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{75} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{80} } func (x *ChangeUserCountryRequest) GetMeta() *RequestMeta { @@ -5687,7 +6110,7 @@ type AdminChangeUserCountryRequest struct { func (x *AdminChangeUserCountryRequest) Reset() { *x = AdminChangeUserCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[76] + mi := &file_proto_user_v1_user_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5699,7 +6122,7 @@ func (x *AdminChangeUserCountryRequest) String() string { func (*AdminChangeUserCountryRequest) ProtoMessage() {} func (x *AdminChangeUserCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[76] + mi := &file_proto_user_v1_user_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5712,7 +6135,7 @@ func (x *AdminChangeUserCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminChangeUserCountryRequest.ProtoReflect.Descriptor instead. func (*AdminChangeUserCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{76} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{81} } func (x *AdminChangeUserCountryRequest) GetMeta() *RequestMeta { @@ -5764,7 +6187,7 @@ type ChangeUserCountryResponse struct { func (x *ChangeUserCountryResponse) Reset() { *x = ChangeUserCountryResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[77] + mi := &file_proto_user_v1_user_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5776,7 +6199,7 @@ func (x *ChangeUserCountryResponse) String() string { func (*ChangeUserCountryResponse) ProtoMessage() {} func (x *ChangeUserCountryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[77] + mi := &file_proto_user_v1_user_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5789,7 +6212,7 @@ func (x *ChangeUserCountryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeUserCountryResponse.ProtoReflect.Descriptor instead. func (*ChangeUserCountryResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{77} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{82} } func (x *ChangeUserCountryResponse) GetUser() *User { @@ -5844,7 +6267,7 @@ type SetUserStatusRequest struct { func (x *SetUserStatusRequest) Reset() { *x = SetUserStatusRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[78] + mi := &file_proto_user_v1_user_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5856,7 +6279,7 @@ func (x *SetUserStatusRequest) String() string { func (*SetUserStatusRequest) ProtoMessage() {} func (x *SetUserStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[78] + mi := &file_proto_user_v1_user_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5869,7 +6292,7 @@ func (x *SetUserStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetUserStatusRequest.ProtoReflect.Descriptor instead. func (*SetUserStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{78} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{83} } func (x *SetUserStatusRequest) GetMeta() *RequestMeta { @@ -5935,7 +6358,7 @@ type SetUserStatusResponse struct { func (x *SetUserStatusResponse) Reset() { *x = SetUserStatusResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[79] + mi := &file_proto_user_v1_user_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5947,7 +6370,7 @@ func (x *SetUserStatusResponse) String() string { func (*SetUserStatusResponse) ProtoMessage() {} func (x *SetUserStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[79] + mi := &file_proto_user_v1_user_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5960,7 +6383,7 @@ func (x *SetUserStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetUserStatusResponse.ProtoReflect.Descriptor instead. func (*SetUserStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{79} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{84} } func (x *SetUserStatusResponse) GetUser() *User { @@ -6060,7 +6483,7 @@ type ManagerUserBlock struct { func (x *ManagerUserBlock) Reset() { *x = ManagerUserBlock{} - mi := &file_proto_user_v1_user_proto_msgTypes[80] + mi := &file_proto_user_v1_user_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6072,7 +6495,7 @@ func (x *ManagerUserBlock) String() string { func (*ManagerUserBlock) ProtoMessage() {} func (x *ManagerUserBlock) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[80] + mi := &file_proto_user_v1_user_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6085,7 +6508,7 @@ func (x *ManagerUserBlock) ProtoReflect() protoreflect.Message { // Deprecated: Use ManagerUserBlock.ProtoReflect.Descriptor instead. func (*ManagerUserBlock) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{80} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{85} } func (x *ManagerUserBlock) GetBlockId() string { @@ -6186,7 +6609,7 @@ type CreateManagerUserBlockRequest struct { func (x *CreateManagerUserBlockRequest) Reset() { *x = CreateManagerUserBlockRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[81] + mi := &file_proto_user_v1_user_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6198,7 +6621,7 @@ func (x *CreateManagerUserBlockRequest) String() string { func (*CreateManagerUserBlockRequest) ProtoMessage() {} func (x *CreateManagerUserBlockRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[81] + mi := &file_proto_user_v1_user_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6211,7 +6634,7 @@ func (x *CreateManagerUserBlockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateManagerUserBlockRequest.ProtoReflect.Descriptor instead. func (*CreateManagerUserBlockRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{81} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{86} } func (x *CreateManagerUserBlockRequest) GetMeta() *RequestMeta { @@ -6266,7 +6689,7 @@ type CreateManagerUserBlockResponse struct { func (x *CreateManagerUserBlockResponse) Reset() { *x = CreateManagerUserBlockResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[82] + mi := &file_proto_user_v1_user_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6278,7 +6701,7 @@ func (x *CreateManagerUserBlockResponse) String() string { func (*CreateManagerUserBlockResponse) ProtoMessage() {} func (x *CreateManagerUserBlockResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[82] + mi := &file_proto_user_v1_user_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6291,7 +6714,7 @@ func (x *CreateManagerUserBlockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateManagerUserBlockResponse.ProtoReflect.Descriptor instead. func (*CreateManagerUserBlockResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{82} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{87} } func (x *CreateManagerUserBlockResponse) GetBlock() *ManagerUserBlock { @@ -6320,7 +6743,7 @@ type ListManagerUserBlocksRequest struct { func (x *ListManagerUserBlocksRequest) Reset() { *x = ListManagerUserBlocksRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[83] + mi := &file_proto_user_v1_user_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6332,7 +6755,7 @@ func (x *ListManagerUserBlocksRequest) String() string { func (*ListManagerUserBlocksRequest) ProtoMessage() {} func (x *ListManagerUserBlocksRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[83] + mi := &file_proto_user_v1_user_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6345,7 +6768,7 @@ func (x *ListManagerUserBlocksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListManagerUserBlocksRequest.ProtoReflect.Descriptor instead. func (*ListManagerUserBlocksRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{83} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{88} } func (x *ListManagerUserBlocksRequest) GetMeta() *RequestMeta { @@ -6385,7 +6808,7 @@ type ListManagerUserBlocksResponse struct { func (x *ListManagerUserBlocksResponse) Reset() { *x = ListManagerUserBlocksResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[84] + mi := &file_proto_user_v1_user_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6397,7 +6820,7 @@ func (x *ListManagerUserBlocksResponse) String() string { func (*ListManagerUserBlocksResponse) ProtoMessage() {} func (x *ListManagerUserBlocksResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[84] + mi := &file_proto_user_v1_user_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6410,7 +6833,7 @@ func (x *ListManagerUserBlocksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListManagerUserBlocksResponse.ProtoReflect.Descriptor instead. func (*ListManagerUserBlocksResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{84} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{89} } func (x *ListManagerUserBlocksResponse) GetBlocks() []*ManagerUserBlock { @@ -6432,7 +6855,7 @@ type UnblockManagerUserRequest struct { func (x *UnblockManagerUserRequest) Reset() { *x = UnblockManagerUserRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[85] + mi := &file_proto_user_v1_user_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6444,7 +6867,7 @@ func (x *UnblockManagerUserRequest) String() string { func (*UnblockManagerUserRequest) ProtoMessage() {} func (x *UnblockManagerUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[85] + mi := &file_proto_user_v1_user_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6457,7 +6880,7 @@ func (x *UnblockManagerUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnblockManagerUserRequest.ProtoReflect.Descriptor instead. func (*UnblockManagerUserRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{85} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{90} } func (x *UnblockManagerUserRequest) GetMeta() *RequestMeta { @@ -6498,7 +6921,7 @@ type UnblockManagerUserResponse struct { func (x *UnblockManagerUserResponse) Reset() { *x = UnblockManagerUserResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[86] + mi := &file_proto_user_v1_user_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6510,7 +6933,7 @@ func (x *UnblockManagerUserResponse) String() string { func (*UnblockManagerUserResponse) ProtoMessage() {} func (x *UnblockManagerUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[86] + mi := &file_proto_user_v1_user_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6523,7 +6946,7 @@ func (x *UnblockManagerUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnblockManagerUserResponse.ProtoReflect.Descriptor instead. func (*UnblockManagerUserResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{86} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{91} } func (x *UnblockManagerUserResponse) GetBlock() *ManagerUserBlock { @@ -6556,7 +6979,7 @@ type CompleteOnboardingRequest struct { func (x *CompleteOnboardingRequest) Reset() { *x = CompleteOnboardingRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[87] + mi := &file_proto_user_v1_user_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6568,7 +6991,7 @@ func (x *CompleteOnboardingRequest) String() string { func (*CompleteOnboardingRequest) ProtoMessage() {} func (x *CompleteOnboardingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[87] + mi := &file_proto_user_v1_user_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6581,7 +7004,7 @@ func (x *CompleteOnboardingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CompleteOnboardingRequest.ProtoReflect.Descriptor instead. func (*CompleteOnboardingRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{87} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{92} } func (x *CompleteOnboardingRequest) GetMeta() *RequestMeta { @@ -6648,7 +7071,7 @@ type CompleteOnboardingResponse struct { func (x *CompleteOnboardingResponse) Reset() { *x = CompleteOnboardingResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[88] + mi := &file_proto_user_v1_user_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6660,7 +7083,7 @@ func (x *CompleteOnboardingResponse) String() string { func (*CompleteOnboardingResponse) ProtoMessage() {} func (x *CompleteOnboardingResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[88] + mi := &file_proto_user_v1_user_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6673,7 +7096,7 @@ func (x *CompleteOnboardingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CompleteOnboardingResponse.ProtoReflect.Descriptor instead. func (*CompleteOnboardingResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{88} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{93} } func (x *CompleteOnboardingResponse) GetUser() *User { @@ -6737,7 +7160,7 @@ type BindPushTokenRequest struct { func (x *BindPushTokenRequest) Reset() { *x = BindPushTokenRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[89] + mi := &file_proto_user_v1_user_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6749,7 +7172,7 @@ func (x *BindPushTokenRequest) String() string { func (*BindPushTokenRequest) ProtoMessage() {} func (x *BindPushTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[89] + mi := &file_proto_user_v1_user_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6762,7 +7185,7 @@ func (x *BindPushTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BindPushTokenRequest.ProtoReflect.Descriptor instead. func (*BindPushTokenRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{89} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{94} } func (x *BindPushTokenRequest) GetMeta() *RequestMeta { @@ -6838,7 +7261,7 @@ type BindPushTokenResponse struct { func (x *BindPushTokenResponse) Reset() { *x = BindPushTokenResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[90] + mi := &file_proto_user_v1_user_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6850,7 +7273,7 @@ func (x *BindPushTokenResponse) String() string { func (*BindPushTokenResponse) ProtoMessage() {} func (x *BindPushTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[90] + mi := &file_proto_user_v1_user_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6863,7 +7286,7 @@ func (x *BindPushTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BindPushTokenResponse.ProtoReflect.Descriptor instead. func (*BindPushTokenResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{90} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{95} } func (x *BindPushTokenResponse) GetBound() bool { @@ -6894,7 +7317,7 @@ type DeletePushTokenRequest struct { func (x *DeletePushTokenRequest) Reset() { *x = DeletePushTokenRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[91] + mi := &file_proto_user_v1_user_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6906,7 +7329,7 @@ func (x *DeletePushTokenRequest) String() string { func (*DeletePushTokenRequest) ProtoMessage() {} func (x *DeletePushTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[91] + mi := &file_proto_user_v1_user_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6919,7 +7342,7 @@ func (x *DeletePushTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePushTokenRequest.ProtoReflect.Descriptor instead. func (*DeletePushTokenRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{91} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{96} } func (x *DeletePushTokenRequest) GetMeta() *RequestMeta { @@ -6960,7 +7383,7 @@ type DeletePushTokenResponse struct { func (x *DeletePushTokenResponse) Reset() { *x = DeletePushTokenResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[92] + mi := &file_proto_user_v1_user_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6972,7 +7395,7 @@ func (x *DeletePushTokenResponse) String() string { func (*DeletePushTokenResponse) ProtoMessage() {} func (x *DeletePushTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[92] + mi := &file_proto_user_v1_user_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6985,7 +7408,7 @@ func (x *DeletePushTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePushTokenResponse.ProtoReflect.Descriptor instead. func (*DeletePushTokenResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{92} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{97} } func (x *DeletePushTokenResponse) GetDeleted() bool { @@ -7024,7 +7447,7 @@ type Country struct { func (x *Country) Reset() { *x = Country{} - mi := &file_proto_user_v1_user_proto_msgTypes[93] + mi := &file_proto_user_v1_user_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7036,7 +7459,7 @@ func (x *Country) String() string { func (*Country) ProtoMessage() {} func (x *Country) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[93] + mi := &file_proto_user_v1_user_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7049,7 +7472,7 @@ func (x *Country) ProtoReflect() protoreflect.Message { // Deprecated: Use Country.ProtoReflect.Descriptor instead. func (*Country) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{93} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{98} } func (x *Country) GetCountryId() int64 { @@ -7161,7 +7584,7 @@ type Region struct { func (x *Region) Reset() { *x = Region{} - mi := &file_proto_user_v1_user_proto_msgTypes[94] + mi := &file_proto_user_v1_user_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7173,7 +7596,7 @@ func (x *Region) String() string { func (*Region) ProtoMessage() {} func (x *Region) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[94] + mi := &file_proto_user_v1_user_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7186,7 +7609,7 @@ func (x *Region) ProtoReflect() protoreflect.Message { // Deprecated: Use Region.ProtoReflect.Descriptor instead. func (*Region) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{94} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{99} } func (x *Region) GetRegionId() int64 { @@ -7262,7 +7685,7 @@ type ListCountriesRequest struct { func (x *ListCountriesRequest) Reset() { *x = ListCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[95] + mi := &file_proto_user_v1_user_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7274,7 +7697,7 @@ func (x *ListCountriesRequest) String() string { func (*ListCountriesRequest) ProtoMessage() {} func (x *ListCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[95] + mi := &file_proto_user_v1_user_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7287,7 +7710,7 @@ func (x *ListCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCountriesRequest.ProtoReflect.Descriptor instead. func (*ListCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{95} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{100} } func (x *ListCountriesRequest) GetMeta() *RequestMeta { @@ -7313,7 +7736,7 @@ type ListCountriesResponse struct { func (x *ListCountriesResponse) Reset() { *x = ListCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[96] + mi := &file_proto_user_v1_user_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7325,7 +7748,7 @@ func (x *ListCountriesResponse) String() string { func (*ListCountriesResponse) ProtoMessage() {} func (x *ListCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[96] + mi := &file_proto_user_v1_user_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7338,7 +7761,7 @@ func (x *ListCountriesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCountriesResponse.ProtoReflect.Descriptor instead. func (*ListCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{96} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{101} } func (x *ListCountriesResponse) GetCountries() []*Country { @@ -7367,7 +7790,7 @@ type UpdateCountryRequest struct { func (x *UpdateCountryRequest) Reset() { *x = UpdateCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[97] + mi := &file_proto_user_v1_user_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7379,7 +7802,7 @@ func (x *UpdateCountryRequest) String() string { func (*UpdateCountryRequest) ProtoMessage() {} func (x *UpdateCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[97] + mi := &file_proto_user_v1_user_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7392,7 +7815,7 @@ func (x *UpdateCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateCountryRequest.ProtoReflect.Descriptor instead. func (*UpdateCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{97} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{102} } func (x *UpdateCountryRequest) GetMeta() *RequestMeta { @@ -7474,7 +7897,7 @@ type CountryResponse struct { func (x *CountryResponse) Reset() { *x = CountryResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[98] + mi := &file_proto_user_v1_user_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7486,7 +7909,7 @@ func (x *CountryResponse) String() string { func (*CountryResponse) ProtoMessage() {} func (x *CountryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[98] + mi := &file_proto_user_v1_user_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7499,7 +7922,7 @@ func (x *CountryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CountryResponse.ProtoReflect.Descriptor instead. func (*CountryResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{98} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{103} } func (x *CountryResponse) GetCountry() *Country { @@ -7518,7 +7941,7 @@ type ListRegistrationCountriesRequest struct { func (x *ListRegistrationCountriesRequest) Reset() { *x = ListRegistrationCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[99] + mi := &file_proto_user_v1_user_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7530,7 +7953,7 @@ func (x *ListRegistrationCountriesRequest) String() string { func (*ListRegistrationCountriesRequest) ProtoMessage() {} func (x *ListRegistrationCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[99] + mi := &file_proto_user_v1_user_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7543,7 +7966,7 @@ func (x *ListRegistrationCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegistrationCountriesRequest.ProtoReflect.Descriptor instead. func (*ListRegistrationCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{99} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{104} } func (x *ListRegistrationCountriesRequest) GetMeta() *RequestMeta { @@ -7562,7 +7985,7 @@ type ListRegistrationCountriesResponse struct { func (x *ListRegistrationCountriesResponse) Reset() { *x = ListRegistrationCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[100] + mi := &file_proto_user_v1_user_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7574,7 +7997,7 @@ func (x *ListRegistrationCountriesResponse) String() string { func (*ListRegistrationCountriesResponse) ProtoMessage() {} func (x *ListRegistrationCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[100] + mi := &file_proto_user_v1_user_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7587,7 +8010,7 @@ func (x *ListRegistrationCountriesResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListRegistrationCountriesResponse.ProtoReflect.Descriptor instead. func (*ListRegistrationCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{100} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{105} } func (x *ListRegistrationCountriesResponse) GetCountries() []*Country { @@ -7608,7 +8031,7 @@ type LoginRiskBlockedCountry struct { func (x *LoginRiskBlockedCountry) Reset() { *x = LoginRiskBlockedCountry{} - mi := &file_proto_user_v1_user_proto_msgTypes[101] + mi := &file_proto_user_v1_user_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7620,7 +8043,7 @@ func (x *LoginRiskBlockedCountry) String() string { func (*LoginRiskBlockedCountry) ProtoMessage() {} func (x *LoginRiskBlockedCountry) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[101] + mi := &file_proto_user_v1_user_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7633,7 +8056,7 @@ func (x *LoginRiskBlockedCountry) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRiskBlockedCountry.ProtoReflect.Descriptor instead. func (*LoginRiskBlockedCountry) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{101} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{106} } func (x *LoginRiskBlockedCountry) GetCountryCode() string { @@ -7666,7 +8089,7 @@ type ListLoginRiskBlockedCountriesRequest struct { func (x *ListLoginRiskBlockedCountriesRequest) Reset() { *x = ListLoginRiskBlockedCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[102] + mi := &file_proto_user_v1_user_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7678,7 +8101,7 @@ func (x *ListLoginRiskBlockedCountriesRequest) String() string { func (*ListLoginRiskBlockedCountriesRequest) ProtoMessage() {} func (x *ListLoginRiskBlockedCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[102] + mi := &file_proto_user_v1_user_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7691,7 +8114,7 @@ func (x *ListLoginRiskBlockedCountriesRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListLoginRiskBlockedCountriesRequest.ProtoReflect.Descriptor instead. func (*ListLoginRiskBlockedCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{102} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{107} } func (x *ListLoginRiskBlockedCountriesRequest) GetMeta() *RequestMeta { @@ -7711,7 +8134,7 @@ type ListLoginRiskBlockedCountriesResponse struct { func (x *ListLoginRiskBlockedCountriesResponse) Reset() { *x = ListLoginRiskBlockedCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[103] + mi := &file_proto_user_v1_user_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7723,7 +8146,7 @@ func (x *ListLoginRiskBlockedCountriesResponse) String() string { func (*ListLoginRiskBlockedCountriesResponse) ProtoMessage() {} func (x *ListLoginRiskBlockedCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[103] + mi := &file_proto_user_v1_user_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7736,7 +8159,7 @@ func (x *ListLoginRiskBlockedCountriesResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListLoginRiskBlockedCountriesResponse.ProtoReflect.Descriptor instead. func (*ListLoginRiskBlockedCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{103} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{108} } func (x *ListLoginRiskBlockedCountriesResponse) GetCountries() []*LoginRiskBlockedCountry { @@ -7763,7 +8186,7 @@ type ListRegionsRequest struct { func (x *ListRegionsRequest) Reset() { *x = ListRegionsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[104] + mi := &file_proto_user_v1_user_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7775,7 +8198,7 @@ func (x *ListRegionsRequest) String() string { func (*ListRegionsRequest) ProtoMessage() {} func (x *ListRegionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[104] + mi := &file_proto_user_v1_user_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7788,7 +8211,7 @@ func (x *ListRegionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegionsRequest.ProtoReflect.Descriptor instead. func (*ListRegionsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{104} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{109} } func (x *ListRegionsRequest) GetMeta() *RequestMeta { @@ -7814,7 +8237,7 @@ type ListRegionsResponse struct { func (x *ListRegionsResponse) Reset() { *x = ListRegionsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[105] + mi := &file_proto_user_v1_user_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7826,7 +8249,7 @@ func (x *ListRegionsResponse) String() string { func (*ListRegionsResponse) ProtoMessage() {} func (x *ListRegionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[105] + mi := &file_proto_user_v1_user_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7839,7 +8262,7 @@ func (x *ListRegionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegionsResponse.ProtoReflect.Descriptor instead. func (*ListRegionsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{105} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{110} } func (x *ListRegionsResponse) GetRegions() []*Region { @@ -7859,7 +8282,7 @@ type GetRegionRequest struct { func (x *GetRegionRequest) Reset() { *x = GetRegionRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[106] + mi := &file_proto_user_v1_user_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7871,7 +8294,7 @@ func (x *GetRegionRequest) String() string { func (*GetRegionRequest) ProtoMessage() {} func (x *GetRegionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[106] + mi := &file_proto_user_v1_user_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7884,7 +8307,7 @@ func (x *GetRegionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRegionRequest.ProtoReflect.Descriptor instead. func (*GetRegionRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{106} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{111} } func (x *GetRegionRequest) GetMeta() *RequestMeta { @@ -7916,7 +8339,7 @@ type UpdateRegionRequest struct { func (x *UpdateRegionRequest) Reset() { *x = UpdateRegionRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[107] + mi := &file_proto_user_v1_user_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7928,7 +8351,7 @@ func (x *UpdateRegionRequest) String() string { func (*UpdateRegionRequest) ProtoMessage() {} func (x *UpdateRegionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[107] + mi := &file_proto_user_v1_user_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7941,7 +8364,7 @@ func (x *UpdateRegionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRegionRequest.ProtoReflect.Descriptor instead. func (*UpdateRegionRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{107} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{112} } func (x *UpdateRegionRequest) GetMeta() *RequestMeta { @@ -7998,7 +8421,7 @@ type ReplaceRegionCountriesRequest struct { func (x *ReplaceRegionCountriesRequest) Reset() { *x = ReplaceRegionCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[108] + mi := &file_proto_user_v1_user_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8010,7 +8433,7 @@ func (x *ReplaceRegionCountriesRequest) String() string { func (*ReplaceRegionCountriesRequest) ProtoMessage() {} func (x *ReplaceRegionCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[108] + mi := &file_proto_user_v1_user_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8023,7 +8446,7 @@ func (x *ReplaceRegionCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReplaceRegionCountriesRequest.ProtoReflect.Descriptor instead. func (*ReplaceRegionCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{108} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{113} } func (x *ReplaceRegionCountriesRequest) GetMeta() *RequestMeta { @@ -8063,7 +8486,7 @@ type RegionResponse struct { func (x *RegionResponse) Reset() { *x = RegionResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[109] + mi := &file_proto_user_v1_user_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8075,7 +8498,7 @@ func (x *RegionResponse) String() string { func (*RegionResponse) ProtoMessage() {} func (x *RegionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[109] + mi := &file_proto_user_v1_user_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8088,7 +8511,7 @@ func (x *RegionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegionResponse.ProtoReflect.Descriptor instead. func (*RegionResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{109} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{114} } func (x *RegionResponse) GetRegion() *Region { @@ -8116,7 +8539,7 @@ type UserIdentity struct { func (x *UserIdentity) Reset() { *x = UserIdentity{} - mi := &file_proto_user_v1_user_proto_msgTypes[110] + mi := &file_proto_user_v1_user_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8128,7 +8551,7 @@ func (x *UserIdentity) String() string { func (*UserIdentity) ProtoMessage() {} func (x *UserIdentity) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[110] + mi := &file_proto_user_v1_user_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8141,7 +8564,7 @@ func (x *UserIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use UserIdentity.ProtoReflect.Descriptor instead. func (*UserIdentity) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{110} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{115} } func (x *UserIdentity) GetUserId() int64 { @@ -8218,7 +8641,7 @@ type GetUserIdentityRequest struct { func (x *GetUserIdentityRequest) Reset() { *x = GetUserIdentityRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[111] + mi := &file_proto_user_v1_user_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8230,7 +8653,7 @@ func (x *GetUserIdentityRequest) String() string { func (*GetUserIdentityRequest) ProtoMessage() {} func (x *GetUserIdentityRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[111] + mi := &file_proto_user_v1_user_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8243,7 +8666,7 @@ func (x *GetUserIdentityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserIdentityRequest.ProtoReflect.Descriptor instead. func (*GetUserIdentityRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{111} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{116} } func (x *GetUserIdentityRequest) GetMeta() *RequestMeta { @@ -8270,7 +8693,7 @@ type GetUserIdentityResponse struct { func (x *GetUserIdentityResponse) Reset() { *x = GetUserIdentityResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[112] + mi := &file_proto_user_v1_user_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8282,7 +8705,7 @@ func (x *GetUserIdentityResponse) String() string { func (*GetUserIdentityResponse) ProtoMessage() {} func (x *GetUserIdentityResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[112] + mi := &file_proto_user_v1_user_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8295,7 +8718,7 @@ func (x *GetUserIdentityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserIdentityResponse.ProtoReflect.Descriptor instead. func (*GetUserIdentityResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{112} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{117} } func (x *GetUserIdentityResponse) GetIdentity() *UserIdentity { @@ -8316,7 +8739,7 @@ type ResolveDisplayUserIDRequest struct { func (x *ResolveDisplayUserIDRequest) Reset() { *x = ResolveDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[113] + mi := &file_proto_user_v1_user_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8328,7 +8751,7 @@ func (x *ResolveDisplayUserIDRequest) String() string { func (*ResolveDisplayUserIDRequest) ProtoMessage() {} func (x *ResolveDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[113] + mi := &file_proto_user_v1_user_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8341,7 +8764,7 @@ func (x *ResolveDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ResolveDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{113} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{118} } func (x *ResolveDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -8368,7 +8791,7 @@ type ResolveDisplayUserIDResponse struct { func (x *ResolveDisplayUserIDResponse) Reset() { *x = ResolveDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[114] + mi := &file_proto_user_v1_user_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8380,7 +8803,7 @@ func (x *ResolveDisplayUserIDResponse) String() string { func (*ResolveDisplayUserIDResponse) ProtoMessage() {} func (x *ResolveDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[114] + mi := &file_proto_user_v1_user_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8393,7 +8816,7 @@ func (x *ResolveDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ResolveDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{114} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{119} } func (x *ResolveDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -8417,7 +8840,7 @@ type ChangeDisplayUserIDRequest struct { func (x *ChangeDisplayUserIDRequest) Reset() { *x = ChangeDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[115] + mi := &file_proto_user_v1_user_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8429,7 +8852,7 @@ func (x *ChangeDisplayUserIDRequest) String() string { func (*ChangeDisplayUserIDRequest) ProtoMessage() {} func (x *ChangeDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[115] + mi := &file_proto_user_v1_user_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8442,7 +8865,7 @@ func (x *ChangeDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ChangeDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{115} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{120} } func (x *ChangeDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -8490,7 +8913,7 @@ type ChangeDisplayUserIDResponse struct { func (x *ChangeDisplayUserIDResponse) Reset() { *x = ChangeDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[116] + mi := &file_proto_user_v1_user_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8502,7 +8925,7 @@ func (x *ChangeDisplayUserIDResponse) String() string { func (*ChangeDisplayUserIDResponse) ProtoMessage() {} func (x *ChangeDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[116] + mi := &file_proto_user_v1_user_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8515,7 +8938,7 @@ func (x *ChangeDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ChangeDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{116} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{121} } func (x *ChangeDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -8539,7 +8962,7 @@ type ApplyPrettyDisplayUserIDRequest struct { func (x *ApplyPrettyDisplayUserIDRequest) Reset() { *x = ApplyPrettyDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[117] + mi := &file_proto_user_v1_user_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8551,7 +8974,7 @@ func (x *ApplyPrettyDisplayUserIDRequest) String() string { func (*ApplyPrettyDisplayUserIDRequest) ProtoMessage() {} func (x *ApplyPrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[117] + mi := &file_proto_user_v1_user_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8564,7 +8987,7 @@ func (x *ApplyPrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyPrettyDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{117} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{122} } func (x *ApplyPrettyDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -8613,7 +9036,7 @@ type ApplyPrettyDisplayUserIDResponse struct { func (x *ApplyPrettyDisplayUserIDResponse) Reset() { *x = ApplyPrettyDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[118] + mi := &file_proto_user_v1_user_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8625,7 +9048,7 @@ func (x *ApplyPrettyDisplayUserIDResponse) String() string { func (*ApplyPrettyDisplayUserIDResponse) ProtoMessage() {} func (x *ApplyPrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[118] + mi := &file_proto_user_v1_user_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8638,7 +9061,7 @@ func (x *ApplyPrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyPrettyDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{118} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{123} } func (x *ApplyPrettyDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -8667,7 +9090,7 @@ type ExpirePrettyDisplayUserIDRequest struct { func (x *ExpirePrettyDisplayUserIDRequest) Reset() { *x = ExpirePrettyDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[119] + mi := &file_proto_user_v1_user_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8679,7 +9102,7 @@ func (x *ExpirePrettyDisplayUserIDRequest) String() string { func (*ExpirePrettyDisplayUserIDRequest) ProtoMessage() {} func (x *ExpirePrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[119] + mi := &file_proto_user_v1_user_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8692,7 +9115,7 @@ func (x *ExpirePrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpirePrettyDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ExpirePrettyDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{119} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{124} } func (x *ExpirePrettyDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -8726,7 +9149,7 @@ type ExpirePrettyDisplayUserIDResponse struct { func (x *ExpirePrettyDisplayUserIDResponse) Reset() { *x = ExpirePrettyDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[120] + mi := &file_proto_user_v1_user_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8738,7 +9161,7 @@ func (x *ExpirePrettyDisplayUserIDResponse) String() string { func (*ExpirePrettyDisplayUserIDResponse) ProtoMessage() {} func (x *ExpirePrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[120] + mi := &file_proto_user_v1_user_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8751,7 +9174,7 @@ func (x *ExpirePrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ExpirePrettyDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ExpirePrettyDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{120} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{125} } func (x *ExpirePrettyDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -8784,7 +9207,7 @@ type PrettyDisplayIDPool struct { func (x *PrettyDisplayIDPool) Reset() { *x = PrettyDisplayIDPool{} - mi := &file_proto_user_v1_user_proto_msgTypes[121] + mi := &file_proto_user_v1_user_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8796,7 +9219,7 @@ func (x *PrettyDisplayIDPool) String() string { func (*PrettyDisplayIDPool) ProtoMessage() {} func (x *PrettyDisplayIDPool) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[121] + mi := &file_proto_user_v1_user_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8809,7 +9232,7 @@ func (x *PrettyDisplayIDPool) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDPool.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDPool) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{121} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{126} } func (x *PrettyDisplayIDPool) GetAppCode() string { @@ -8935,7 +9358,7 @@ type PrettyDisplayID struct { func (x *PrettyDisplayID) Reset() { *x = PrettyDisplayID{} - mi := &file_proto_user_v1_user_proto_msgTypes[122] + mi := &file_proto_user_v1_user_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8947,7 +9370,7 @@ func (x *PrettyDisplayID) String() string { func (*PrettyDisplayID) ProtoMessage() {} func (x *PrettyDisplayID) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[122] + mi := &file_proto_user_v1_user_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8960,7 +9383,7 @@ func (x *PrettyDisplayID) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayID.ProtoReflect.Descriptor instead. func (*PrettyDisplayID) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{122} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{127} } func (x *PrettyDisplayID) GetAppCode() string { @@ -9096,7 +9519,7 @@ type PrettyDisplayIDGenerationBatch struct { func (x *PrettyDisplayIDGenerationBatch) Reset() { *x = PrettyDisplayIDGenerationBatch{} - mi := &file_proto_user_v1_user_proto_msgTypes[123] + mi := &file_proto_user_v1_user_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9108,7 +9531,7 @@ func (x *PrettyDisplayIDGenerationBatch) String() string { func (*PrettyDisplayIDGenerationBatch) ProtoMessage() {} func (x *PrettyDisplayIDGenerationBatch) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[123] + mi := &file_proto_user_v1_user_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9121,7 +9544,7 @@ func (x *PrettyDisplayIDGenerationBatch) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDGenerationBatch.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDGenerationBatch) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{123} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{128} } func (x *PrettyDisplayIDGenerationBatch) GetAppCode() string { @@ -9227,7 +9650,7 @@ type ListAvailablePrettyDisplayIDsRequest struct { func (x *ListAvailablePrettyDisplayIDsRequest) Reset() { *x = ListAvailablePrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[124] + mi := &file_proto_user_v1_user_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9239,7 +9662,7 @@ func (x *ListAvailablePrettyDisplayIDsRequest) String() string { func (*ListAvailablePrettyDisplayIDsRequest) ProtoMessage() {} func (x *ListAvailablePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[124] + mi := &file_proto_user_v1_user_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9252,7 +9675,7 @@ func (x *ListAvailablePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListAvailablePrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*ListAvailablePrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{124} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{129} } func (x *ListAvailablePrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -9293,7 +9716,7 @@ type ListAvailablePrettyDisplayIDsResponse struct { func (x *ListAvailablePrettyDisplayIDsResponse) Reset() { *x = ListAvailablePrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[125] + mi := &file_proto_user_v1_user_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9305,7 +9728,7 @@ func (x *ListAvailablePrettyDisplayIDsResponse) String() string { func (*ListAvailablePrettyDisplayIDsResponse) ProtoMessage() {} func (x *ListAvailablePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[125] + mi := &file_proto_user_v1_user_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9318,7 +9741,7 @@ func (x *ListAvailablePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListAvailablePrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*ListAvailablePrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{125} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{130} } func (x *ListAvailablePrettyDisplayIDsResponse) GetItems() []*PrettyDisplayID { @@ -9346,7 +9769,7 @@ type ApplyPrettyDisplayIDFromPoolRequest struct { func (x *ApplyPrettyDisplayIDFromPoolRequest) Reset() { *x = ApplyPrettyDisplayIDFromPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[126] + mi := &file_proto_user_v1_user_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9358,7 +9781,7 @@ func (x *ApplyPrettyDisplayIDFromPoolRequest) String() string { func (*ApplyPrettyDisplayIDFromPoolRequest) ProtoMessage() {} func (x *ApplyPrettyDisplayIDFromPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[126] + mi := &file_proto_user_v1_user_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9371,7 +9794,7 @@ func (x *ApplyPrettyDisplayIDFromPoolRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ApplyPrettyDisplayIDFromPoolRequest.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayIDFromPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{126} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{131} } func (x *ApplyPrettyDisplayIDFromPoolRequest) GetMeta() *RequestMeta { @@ -9406,7 +9829,7 @@ type ApplyPrettyDisplayIDFromPoolResponse struct { func (x *ApplyPrettyDisplayIDFromPoolResponse) Reset() { *x = ApplyPrettyDisplayIDFromPoolResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[127] + mi := &file_proto_user_v1_user_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9418,7 +9841,7 @@ func (x *ApplyPrettyDisplayIDFromPoolResponse) String() string { func (*ApplyPrettyDisplayIDFromPoolResponse) ProtoMessage() {} func (x *ApplyPrettyDisplayIDFromPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[127] + mi := &file_proto_user_v1_user_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9431,7 +9854,7 @@ func (x *ApplyPrettyDisplayIDFromPoolResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ApplyPrettyDisplayIDFromPoolResponse.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayIDFromPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{127} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{132} } func (x *ApplyPrettyDisplayIDFromPoolResponse) GetIdentity() *UserIdentity { @@ -9468,7 +9891,7 @@ type ListPrettyDisplayIDPoolsRequest struct { func (x *ListPrettyDisplayIDPoolsRequest) Reset() { *x = ListPrettyDisplayIDPoolsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[128] + mi := &file_proto_user_v1_user_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9480,7 +9903,7 @@ func (x *ListPrettyDisplayIDPoolsRequest) String() string { func (*ListPrettyDisplayIDPoolsRequest) ProtoMessage() {} func (x *ListPrettyDisplayIDPoolsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[128] + mi := &file_proto_user_v1_user_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9493,7 +9916,7 @@ func (x *ListPrettyDisplayIDPoolsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDPoolsRequest.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDPoolsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{128} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{133} } func (x *ListPrettyDisplayIDPoolsRequest) GetMeta() *RequestMeta { @@ -9541,7 +9964,7 @@ type ListPrettyDisplayIDPoolsResponse struct { func (x *ListPrettyDisplayIDPoolsResponse) Reset() { *x = ListPrettyDisplayIDPoolsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[129] + mi := &file_proto_user_v1_user_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9553,7 +9976,7 @@ func (x *ListPrettyDisplayIDPoolsResponse) String() string { func (*ListPrettyDisplayIDPoolsResponse) ProtoMessage() {} func (x *ListPrettyDisplayIDPoolsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[129] + mi := &file_proto_user_v1_user_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9566,7 +9989,7 @@ func (x *ListPrettyDisplayIDPoolsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDPoolsResponse.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDPoolsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{129} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{134} } func (x *ListPrettyDisplayIDPoolsResponse) GetItems() []*PrettyDisplayIDPool { @@ -9601,7 +10024,7 @@ type CreatePrettyDisplayIDPoolRequest struct { func (x *CreatePrettyDisplayIDPoolRequest) Reset() { *x = CreatePrettyDisplayIDPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[130] + mi := &file_proto_user_v1_user_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9613,7 +10036,7 @@ func (x *CreatePrettyDisplayIDPoolRequest) String() string { func (*CreatePrettyDisplayIDPoolRequest) ProtoMessage() {} func (x *CreatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[130] + mi := &file_proto_user_v1_user_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9626,7 +10049,7 @@ func (x *CreatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatePrettyDisplayIDPoolRequest.ProtoReflect.Descriptor instead. func (*CreatePrettyDisplayIDPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{130} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{135} } func (x *CreatePrettyDisplayIDPoolRequest) GetMeta() *RequestMeta { @@ -9718,7 +10141,7 @@ type UpdatePrettyDisplayIDPoolRequest struct { func (x *UpdatePrettyDisplayIDPoolRequest) Reset() { *x = UpdatePrettyDisplayIDPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[131] + mi := &file_proto_user_v1_user_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9730,7 +10153,7 @@ func (x *UpdatePrettyDisplayIDPoolRequest) String() string { func (*UpdatePrettyDisplayIDPoolRequest) ProtoMessage() {} func (x *UpdatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[131] + mi := &file_proto_user_v1_user_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9743,7 +10166,7 @@ func (x *UpdatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdatePrettyDisplayIDPoolRequest.ProtoReflect.Descriptor instead. func (*UpdatePrettyDisplayIDPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{131} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{136} } func (x *UpdatePrettyDisplayIDPoolRequest) GetMeta() *RequestMeta { @@ -9832,7 +10255,7 @@ type PrettyDisplayIDPoolResponse struct { func (x *PrettyDisplayIDPoolResponse) Reset() { *x = PrettyDisplayIDPoolResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[132] + mi := &file_proto_user_v1_user_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9844,7 +10267,7 @@ func (x *PrettyDisplayIDPoolResponse) String() string { func (*PrettyDisplayIDPoolResponse) ProtoMessage() {} func (x *PrettyDisplayIDPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[132] + mi := &file_proto_user_v1_user_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9857,7 +10280,7 @@ func (x *PrettyDisplayIDPoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDPoolResponse.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{132} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{137} } func (x *PrettyDisplayIDPoolResponse) GetPool() *PrettyDisplayIDPool { @@ -9881,7 +10304,7 @@ type GeneratePrettyDisplayIDsRequest struct { func (x *GeneratePrettyDisplayIDsRequest) Reset() { *x = GeneratePrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[133] + mi := &file_proto_user_v1_user_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9893,7 +10316,7 @@ func (x *GeneratePrettyDisplayIDsRequest) String() string { func (*GeneratePrettyDisplayIDsRequest) ProtoMessage() {} func (x *GeneratePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[133] + mi := &file_proto_user_v1_user_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9906,7 +10329,7 @@ func (x *GeneratePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GeneratePrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*GeneratePrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{133} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{138} } func (x *GeneratePrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -9960,7 +10383,7 @@ type GeneratePrettyDisplayIDsResponse struct { func (x *GeneratePrettyDisplayIDsResponse) Reset() { *x = GeneratePrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[134] + mi := &file_proto_user_v1_user_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9972,7 +10395,7 @@ func (x *GeneratePrettyDisplayIDsResponse) String() string { func (*GeneratePrettyDisplayIDsResponse) ProtoMessage() {} func (x *GeneratePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[134] + mi := &file_proto_user_v1_user_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9985,7 +10408,7 @@ func (x *GeneratePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GeneratePrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*GeneratePrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{134} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{139} } func (x *GeneratePrettyDisplayIDsResponse) GetBatch() *PrettyDisplayIDGenerationBatch { @@ -10011,7 +10434,7 @@ type ListPrettyDisplayIDsRequest struct { func (x *ListPrettyDisplayIDsRequest) Reset() { *x = ListPrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[135] + mi := &file_proto_user_v1_user_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10023,7 +10446,7 @@ func (x *ListPrettyDisplayIDsRequest) String() string { func (*ListPrettyDisplayIDsRequest) ProtoMessage() {} func (x *ListPrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[135] + mi := &file_proto_user_v1_user_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10036,7 +10459,7 @@ func (x *ListPrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{135} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{140} } func (x *ListPrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -10105,7 +10528,7 @@ type ListPrettyDisplayIDsResponse struct { func (x *ListPrettyDisplayIDsResponse) Reset() { *x = ListPrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[136] + mi := &file_proto_user_v1_user_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10117,7 +10540,7 @@ func (x *ListPrettyDisplayIDsResponse) String() string { func (*ListPrettyDisplayIDsResponse) ProtoMessage() {} func (x *ListPrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[136] + mi := &file_proto_user_v1_user_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10130,7 +10553,7 @@ func (x *ListPrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{136} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{141} } func (x *ListPrettyDisplayIDsResponse) GetItems() []*PrettyDisplayID { @@ -10160,7 +10583,7 @@ type SetPrettyDisplayIDStatusRequest struct { func (x *SetPrettyDisplayIDStatusRequest) Reset() { *x = SetPrettyDisplayIDStatusRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[137] + mi := &file_proto_user_v1_user_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10172,7 +10595,7 @@ func (x *SetPrettyDisplayIDStatusRequest) String() string { func (*SetPrettyDisplayIDStatusRequest) ProtoMessage() {} func (x *SetPrettyDisplayIDStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[137] + mi := &file_proto_user_v1_user_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10185,7 +10608,7 @@ func (x *SetPrettyDisplayIDStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetPrettyDisplayIDStatusRequest.ProtoReflect.Descriptor instead. func (*SetPrettyDisplayIDStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{137} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{142} } func (x *SetPrettyDisplayIDStatusRequest) GetMeta() *RequestMeta { @@ -10232,7 +10655,7 @@ type PrettyDisplayIDResponse struct { func (x *PrettyDisplayIDResponse) Reset() { *x = PrettyDisplayIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[138] + mi := &file_proto_user_v1_user_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10244,7 +10667,7 @@ func (x *PrettyDisplayIDResponse) String() string { func (*PrettyDisplayIDResponse) ProtoMessage() {} func (x *PrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[138] + mi := &file_proto_user_v1_user_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10257,7 +10680,7 @@ func (x *PrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDResponse.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{138} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{143} } func (x *PrettyDisplayIDResponse) GetItem() *PrettyDisplayID { @@ -10281,7 +10704,7 @@ type AdminGrantPrettyDisplayIDRequest struct { func (x *AdminGrantPrettyDisplayIDRequest) Reset() { *x = AdminGrantPrettyDisplayIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[139] + mi := &file_proto_user_v1_user_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10293,7 +10716,7 @@ func (x *AdminGrantPrettyDisplayIDRequest) String() string { func (*AdminGrantPrettyDisplayIDRequest) ProtoMessage() {} func (x *AdminGrantPrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[139] + mi := &file_proto_user_v1_user_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10306,7 +10729,7 @@ func (x *AdminGrantPrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGrantPrettyDisplayIDRequest.ProtoReflect.Descriptor instead. func (*AdminGrantPrettyDisplayIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{139} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{144} } func (x *AdminGrantPrettyDisplayIDRequest) GetMeta() *RequestMeta { @@ -10362,7 +10785,7 @@ type AdminGrantPrettyDisplayIDResponse struct { func (x *AdminGrantPrettyDisplayIDResponse) Reset() { *x = AdminGrantPrettyDisplayIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[140] + mi := &file_proto_user_v1_user_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10374,7 +10797,7 @@ func (x *AdminGrantPrettyDisplayIDResponse) String() string { func (*AdminGrantPrettyDisplayIDResponse) ProtoMessage() {} func (x *AdminGrantPrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[140] + mi := &file_proto_user_v1_user_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10387,7 +10810,7 @@ func (x *AdminGrantPrettyDisplayIDResponse) ProtoReflect() protoreflect.Message // Deprecated: Use AdminGrantPrettyDisplayIDResponse.ProtoReflect.Descriptor instead. func (*AdminGrantPrettyDisplayIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{140} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{145} } func (x *AdminGrantPrettyDisplayIDResponse) GetIdentity() *UserIdentity { @@ -10724,7 +11147,34 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\x16level_progress_percent\x18\x0e \x01(\x05R\x14levelProgressPercent\x12\x1b\n" + "\tmax_level\x18\x0f \x01(\bR\bmaxLevel\x12,\n" + "\x12breakup_cost_coins\x18\x10 \x01(\x03R\x10breakupCostCoins\x12\x1e\n" + - "\vended_at_ms\x18\x11 \x01(\x03R\tendedAtMsJ\x04\b\b\x10\tR\bequipped\"\xcb\x01\n" + + "\vended_at_ms\x18\x11 \x01(\x03R\tendedAtMsJ\x04\b\b\x10\tR\bequipped\"\xf9\x01\n" + + "\x15CPAvatarFrameSnapshot\x12\x1f\n" + + "\vresource_id\x18\x01 \x01(\x03R\n" + + "resourceId\x12#\n" + + "\rresource_code\x18\x02 \x01(\tR\fresourceCode\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x1b\n" + + "\tasset_url\x18\x04 \x01(\tR\bassetUrl\x12\x1f\n" + + "\vpreview_url\x18\x05 \x01(\tR\n" + + "previewUrl\x12#\n" + + "\ranimation_url\x18\x06 \x01(\tR\fanimationUrl\x12#\n" + + "\rmetadata_json\x18\a \x01(\tR\fmetadataJson\"\xd9\x01\n" + + "\x19CPIntimacyLeaderboardUser\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12&\n" + + "\x0fdisplay_user_id\x18\x02 \x01(\tR\rdisplayUserId\x12\x1a\n" + + "\busername\x18\x03 \x01(\tR\busername\x12\x16\n" + + "\x06avatar\x18\x04 \x01(\tR\x06avatar\x12G\n" + + "\favatar_frame\x18\x05 \x01(\v2$.hyapp.user.v1.CPAvatarFrameSnapshotR\vavatarFrame\"\x82\x03\n" + + "\x19CPIntimacyLeaderboardItem\x12\x12\n" + + "\x04rank\x18\x01 \x01(\x03R\x04rank\x12'\n" + + "\x0frelationship_id\x18\x02 \x01(\tR\x0erelationshipId\x12#\n" + + "\rrelation_type\x18\x03 \x01(\tR\frelationType\x12%\n" + + "\x0eintimacy_value\x18\x04 \x01(\x03R\rintimacyValue\x12\x14\n" + + "\x05level\x18\x05 \x01(\x05R\x05level\x12?\n" + + "\x06user_a\x18\x06 \x01(\v2(.hyapp.user.v1.CPIntimacyLeaderboardUserR\x05userA\x12?\n" + + "\x06user_b\x18\a \x01(\v2(.hyapp.user.v1.CPIntimacyLeaderboardUserR\x05userB\x12 \n" + + "\fformed_at_ms\x18\b \x01(\x03R\n" + + "formedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\t \x01(\x03R\vupdatedAtMs\"\xcb\x01\n" + "\x19ListCPApplicationsRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1c\n" + @@ -10758,7 +11208,18 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\tpage_size\x18\x05 \x01(\x05R\bpageSize\"\x9a\x01\n" + "\x1bListCPRelationshipsResponse\x12C\n" + "\rrelationships\x18\x01 \x03(\v2\x1d.hyapp.user.v1.CPRelationshipR\rrelationships\x12\x14\n" + - "\x05total\x18\x02 \x01(\x03R\x05totalJ\x04\b\x03\x10\x04R\x1amax_count_by_relation_type\"\xb4\x01\n" + + "\x05total\x18\x02 \x01(\x03R\x05totalJ\x04\b\x03\x10\x04R\x1amax_count_by_relation_type\"\xa8\x01\n" + + " ListCPIntimacyLeaderboardRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12#\n" + + "\rrelation_type\x18\x02 \x01(\tR\frelationType\x12\x12\n" + + "\x04page\x18\x03 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\"\xd0\x01\n" + + "!ListCPIntimacyLeaderboardResponse\x12>\n" + + "\x05items\x18\x01 \x03(\v2(.hyapp.user.v1.CPIntimacyLeaderboardItemR\x05items\x12\x14\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\x12\x12\n" + + "\x04page\x18\x03 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12$\n" + + "\x0eserver_time_ms\x18\x05 \x01(\x03R\fserverTimeMs\"\xb4\x01\n" + "!PrepareBreakCPRelationshipRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12'\n" + @@ -11325,22 +11786,24 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\fDeleteFriend\x12\".hyapp.user.v1.DeleteFriendRequest\x1a#.hyapp.user.v1.DeleteFriendResponse\x12T\n" + "\vListFriends\x12!.hyapp.user.v1.ListFriendsRequest\x1a\".hyapp.user.v1.ListFriendsResponse\x12u\n" + "\x16ListFriendApplications\x12,.hyapp.user.v1.ListFriendApplicationsRequest\x1a-.hyapp.user.v1.ListFriendApplicationsResponse\x12W\n" + - "\fSubmitReport\x12\".hyapp.user.v1.SubmitReportRequest\x1a#.hyapp.user.v1.SubmitReportResponse2\xcc\x06\n" + + "\fSubmitReport\x12\".hyapp.user.v1.SubmitReportRequest\x1a#.hyapp.user.v1.SubmitReportResponse2\xcc\a\n" + "\rUserCPService\x12i\n" + "\x12ListCPApplications\x12(.hyapp.user.v1.ListCPApplicationsRequest\x1a).hyapp.user.v1.ListCPApplicationsResponse\x12l\n" + "\x13AcceptCPApplication\x12).hyapp.user.v1.AcceptCPApplicationRequest\x1a*.hyapp.user.v1.AcceptCPApplicationResponse\x12l\n" + "\x13RejectCPApplication\x12).hyapp.user.v1.RejectCPApplicationRequest\x1a*.hyapp.user.v1.RejectCPApplicationResponse\x12l\n" + - "\x13ListCPRelationships\x12).hyapp.user.v1.ListCPRelationshipsRequest\x1a*.hyapp.user.v1.ListCPRelationshipsResponse\x12\x81\x01\n" + + "\x13ListCPRelationships\x12).hyapp.user.v1.ListCPRelationshipsRequest\x1a*.hyapp.user.v1.ListCPRelationshipsResponse\x12~\n" + + "\x19ListCPIntimacyLeaderboard\x12/.hyapp.user.v1.ListCPIntimacyLeaderboardRequest\x1a0.hyapp.user.v1.ListCPIntimacyLeaderboardResponse\x12\x81\x01\n" + "\x1aPrepareBreakCPRelationship\x120.hyapp.user.v1.PrepareBreakCPRelationshipRequest\x1a1.hyapp.user.v1.PrepareBreakCPRelationshipResponse\x12\x81\x01\n" + "\x1aConfirmBreakCPRelationship\x120.hyapp.user.v1.ConfirmBreakCPRelationshipRequest\x1a1.hyapp.user.v1.ConfirmBreakCPRelationshipResponse\x12~\n" + "\x19CancelBreakCPRelationship\x12/.hyapp.user.v1.CancelBreakCPRelationshipRequest\x1a0.hyapp.user.v1.CancelBreakCPRelationshipResponse2\x8e\x01\n" + "\x15UserCPInternalService\x12u\n" + - "\x16ConsumeRoomGiftCPEvent\x12,.hyapp.user.v1.ConsumeRoomGiftCPEventRequest\x1a-.hyapp.user.v1.ConsumeRoomGiftCPEventResponse2\x8d\x03\n" + + "\x16ConsumeRoomGiftCPEvent\x12,.hyapp.user.v1.ConsumeRoomGiftCPEventRequest\x1a-.hyapp.user.v1.ConsumeRoomGiftCPEventResponse2\xf0\x03\n" + "\x0fUserCronService\x12\\\n" + "\x17ProcessLoginIPRiskBatch\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12^\n" + "\x19ProcessRegionRebuildBatch\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12^\n" + "\x19CompensateMicOpenSessions\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12\\\n" + - "\x17ExpireManagerUserBlocks\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse2\xd1\x01\n" + + "\x17ExpireManagerUserBlocks\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse\x12a\n" + + "\x1cRefreshCPIntimacyLeaderboard\x12\x1f.hyapp.user.v1.CronBatchRequest\x1a .hyapp.user.v1.CronBatchResponse2\xd1\x01\n" + "\x11UserDeviceService\x12Z\n" + "\rBindPushToken\x12#.hyapp.user.v1.BindPushTokenRequest\x1a$.hyapp.user.v1.BindPushTokenResponse\x12`\n" + "\x0fDeletePushToken\x12%.hyapp.user.v1.DeletePushTokenRequest\x1a&.hyapp.user.v1.DeletePushTokenResponse2g\n" + @@ -11388,7 +11851,7 @@ func file_proto_user_v1_user_proto_rawDescGZIP() []byte { } var file_proto_user_v1_user_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 142) +var file_proto_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 147) var file_proto_user_v1_user_proto_goTypes = []any{ (UserStatus)(0), // 0: hyapp.user.v1.UserStatus (*RequestMeta)(nil), // 1: hyapp.user.v1.RequestMeta @@ -11440,99 +11903,104 @@ var file_proto_user_v1_user_proto_goTypes = []any{ (*CPGiftSnapshot)(nil), // 47: hyapp.user.v1.CPGiftSnapshot (*CPApplication)(nil), // 48: hyapp.user.v1.CPApplication (*CPRelationship)(nil), // 49: hyapp.user.v1.CPRelationship - (*ListCPApplicationsRequest)(nil), // 50: hyapp.user.v1.ListCPApplicationsRequest - (*ListCPApplicationsResponse)(nil), // 51: hyapp.user.v1.ListCPApplicationsResponse - (*AcceptCPApplicationRequest)(nil), // 52: hyapp.user.v1.AcceptCPApplicationRequest - (*AcceptCPApplicationResponse)(nil), // 53: hyapp.user.v1.AcceptCPApplicationResponse - (*RejectCPApplicationRequest)(nil), // 54: hyapp.user.v1.RejectCPApplicationRequest - (*RejectCPApplicationResponse)(nil), // 55: hyapp.user.v1.RejectCPApplicationResponse - (*ListCPRelationshipsRequest)(nil), // 56: hyapp.user.v1.ListCPRelationshipsRequest - (*ListCPRelationshipsResponse)(nil), // 57: hyapp.user.v1.ListCPRelationshipsResponse - (*PrepareBreakCPRelationshipRequest)(nil), // 58: hyapp.user.v1.PrepareBreakCPRelationshipRequest - (*PrepareBreakCPRelationshipResponse)(nil), // 59: hyapp.user.v1.PrepareBreakCPRelationshipResponse - (*ConfirmBreakCPRelationshipRequest)(nil), // 60: hyapp.user.v1.ConfirmBreakCPRelationshipRequest - (*ConfirmBreakCPRelationshipResponse)(nil), // 61: hyapp.user.v1.ConfirmBreakCPRelationshipResponse - (*CancelBreakCPRelationshipRequest)(nil), // 62: hyapp.user.v1.CancelBreakCPRelationshipRequest - (*CancelBreakCPRelationshipResponse)(nil), // 63: hyapp.user.v1.CancelBreakCPRelationshipResponse - (*RoomGiftCPEvent)(nil), // 64: hyapp.user.v1.RoomGiftCPEvent - (*ConsumeRoomGiftCPEventRequest)(nil), // 65: hyapp.user.v1.ConsumeRoomGiftCPEventRequest - (*ConsumeRoomGiftCPEventResponse)(nil), // 66: hyapp.user.v1.ConsumeRoomGiftCPEventResponse - (*UserReport)(nil), // 67: hyapp.user.v1.UserReport - (*SubmitReportRequest)(nil), // 68: hyapp.user.v1.SubmitReportRequest - (*SubmitReportResponse)(nil), // 69: hyapp.user.v1.SubmitReportResponse - (*BatchGetUsersRequest)(nil), // 70: hyapp.user.v1.BatchGetUsersRequest - (*BatchGetUsersResponse)(nil), // 71: hyapp.user.v1.BatchGetUsersResponse - (*ListUserIDsRequest)(nil), // 72: hyapp.user.v1.ListUserIDsRequest - (*ListUserIDsResponse)(nil), // 73: hyapp.user.v1.ListUserIDsResponse - (*UpdateUserProfileRequest)(nil), // 74: hyapp.user.v1.UpdateUserProfileRequest - (*UpdateUserProfileResponse)(nil), // 75: hyapp.user.v1.UpdateUserProfileResponse - (*ChangeUserCountryRequest)(nil), // 76: hyapp.user.v1.ChangeUserCountryRequest - (*AdminChangeUserCountryRequest)(nil), // 77: hyapp.user.v1.AdminChangeUserCountryRequest - (*ChangeUserCountryResponse)(nil), // 78: hyapp.user.v1.ChangeUserCountryResponse - (*SetUserStatusRequest)(nil), // 79: hyapp.user.v1.SetUserStatusRequest - (*SetUserStatusResponse)(nil), // 80: hyapp.user.v1.SetUserStatusResponse - (*ManagerUserBlock)(nil), // 81: hyapp.user.v1.ManagerUserBlock - (*CreateManagerUserBlockRequest)(nil), // 82: hyapp.user.v1.CreateManagerUserBlockRequest - (*CreateManagerUserBlockResponse)(nil), // 83: hyapp.user.v1.CreateManagerUserBlockResponse - (*ListManagerUserBlocksRequest)(nil), // 84: hyapp.user.v1.ListManagerUserBlocksRequest - (*ListManagerUserBlocksResponse)(nil), // 85: hyapp.user.v1.ListManagerUserBlocksResponse - (*UnblockManagerUserRequest)(nil), // 86: hyapp.user.v1.UnblockManagerUserRequest - (*UnblockManagerUserResponse)(nil), // 87: hyapp.user.v1.UnblockManagerUserResponse - (*CompleteOnboardingRequest)(nil), // 88: hyapp.user.v1.CompleteOnboardingRequest - (*CompleteOnboardingResponse)(nil), // 89: hyapp.user.v1.CompleteOnboardingResponse - (*BindPushTokenRequest)(nil), // 90: hyapp.user.v1.BindPushTokenRequest - (*BindPushTokenResponse)(nil), // 91: hyapp.user.v1.BindPushTokenResponse - (*DeletePushTokenRequest)(nil), // 92: hyapp.user.v1.DeletePushTokenRequest - (*DeletePushTokenResponse)(nil), // 93: hyapp.user.v1.DeletePushTokenResponse - (*Country)(nil), // 94: hyapp.user.v1.Country - (*Region)(nil), // 95: hyapp.user.v1.Region - (*ListCountriesRequest)(nil), // 96: hyapp.user.v1.ListCountriesRequest - (*ListCountriesResponse)(nil), // 97: hyapp.user.v1.ListCountriesResponse - (*UpdateCountryRequest)(nil), // 98: hyapp.user.v1.UpdateCountryRequest - (*CountryResponse)(nil), // 99: hyapp.user.v1.CountryResponse - (*ListRegistrationCountriesRequest)(nil), // 100: hyapp.user.v1.ListRegistrationCountriesRequest - (*ListRegistrationCountriesResponse)(nil), // 101: hyapp.user.v1.ListRegistrationCountriesResponse - (*LoginRiskBlockedCountry)(nil), // 102: hyapp.user.v1.LoginRiskBlockedCountry - (*ListLoginRiskBlockedCountriesRequest)(nil), // 103: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest - (*ListLoginRiskBlockedCountriesResponse)(nil), // 104: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse - (*ListRegionsRequest)(nil), // 105: hyapp.user.v1.ListRegionsRequest - (*ListRegionsResponse)(nil), // 106: hyapp.user.v1.ListRegionsResponse - (*GetRegionRequest)(nil), // 107: hyapp.user.v1.GetRegionRequest - (*UpdateRegionRequest)(nil), // 108: hyapp.user.v1.UpdateRegionRequest - (*ReplaceRegionCountriesRequest)(nil), // 109: hyapp.user.v1.ReplaceRegionCountriesRequest - (*RegionResponse)(nil), // 110: hyapp.user.v1.RegionResponse - (*UserIdentity)(nil), // 111: hyapp.user.v1.UserIdentity - (*GetUserIdentityRequest)(nil), // 112: hyapp.user.v1.GetUserIdentityRequest - (*GetUserIdentityResponse)(nil), // 113: hyapp.user.v1.GetUserIdentityResponse - (*ResolveDisplayUserIDRequest)(nil), // 114: hyapp.user.v1.ResolveDisplayUserIDRequest - (*ResolveDisplayUserIDResponse)(nil), // 115: hyapp.user.v1.ResolveDisplayUserIDResponse - (*ChangeDisplayUserIDRequest)(nil), // 116: hyapp.user.v1.ChangeDisplayUserIDRequest - (*ChangeDisplayUserIDResponse)(nil), // 117: hyapp.user.v1.ChangeDisplayUserIDResponse - (*ApplyPrettyDisplayUserIDRequest)(nil), // 118: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest - (*ApplyPrettyDisplayUserIDResponse)(nil), // 119: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse - (*ExpirePrettyDisplayUserIDRequest)(nil), // 120: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest - (*ExpirePrettyDisplayUserIDResponse)(nil), // 121: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse - (*PrettyDisplayIDPool)(nil), // 122: hyapp.user.v1.PrettyDisplayIDPool - (*PrettyDisplayID)(nil), // 123: hyapp.user.v1.PrettyDisplayID - (*PrettyDisplayIDGenerationBatch)(nil), // 124: hyapp.user.v1.PrettyDisplayIDGenerationBatch - (*ListAvailablePrettyDisplayIDsRequest)(nil), // 125: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest - (*ListAvailablePrettyDisplayIDsResponse)(nil), // 126: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse - (*ApplyPrettyDisplayIDFromPoolRequest)(nil), // 127: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest - (*ApplyPrettyDisplayIDFromPoolResponse)(nil), // 128: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse - (*ListPrettyDisplayIDPoolsRequest)(nil), // 129: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest - (*ListPrettyDisplayIDPoolsResponse)(nil), // 130: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse - (*CreatePrettyDisplayIDPoolRequest)(nil), // 131: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest - (*UpdatePrettyDisplayIDPoolRequest)(nil), // 132: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest - (*PrettyDisplayIDPoolResponse)(nil), // 133: hyapp.user.v1.PrettyDisplayIDPoolResponse - (*GeneratePrettyDisplayIDsRequest)(nil), // 134: hyapp.user.v1.GeneratePrettyDisplayIDsRequest - (*GeneratePrettyDisplayIDsResponse)(nil), // 135: hyapp.user.v1.GeneratePrettyDisplayIDsResponse - (*ListPrettyDisplayIDsRequest)(nil), // 136: hyapp.user.v1.ListPrettyDisplayIDsRequest - (*ListPrettyDisplayIDsResponse)(nil), // 137: hyapp.user.v1.ListPrettyDisplayIDsResponse - (*SetPrettyDisplayIDStatusRequest)(nil), // 138: hyapp.user.v1.SetPrettyDisplayIDStatusRequest - (*PrettyDisplayIDResponse)(nil), // 139: hyapp.user.v1.PrettyDisplayIDResponse - (*AdminGrantPrettyDisplayIDRequest)(nil), // 140: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest - (*AdminGrantPrettyDisplayIDResponse)(nil), // 141: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse - nil, // 142: hyapp.user.v1.BatchGetUsersResponse.UsersEntry + (*CPAvatarFrameSnapshot)(nil), // 50: hyapp.user.v1.CPAvatarFrameSnapshot + (*CPIntimacyLeaderboardUser)(nil), // 51: hyapp.user.v1.CPIntimacyLeaderboardUser + (*CPIntimacyLeaderboardItem)(nil), // 52: hyapp.user.v1.CPIntimacyLeaderboardItem + (*ListCPApplicationsRequest)(nil), // 53: hyapp.user.v1.ListCPApplicationsRequest + (*ListCPApplicationsResponse)(nil), // 54: hyapp.user.v1.ListCPApplicationsResponse + (*AcceptCPApplicationRequest)(nil), // 55: hyapp.user.v1.AcceptCPApplicationRequest + (*AcceptCPApplicationResponse)(nil), // 56: hyapp.user.v1.AcceptCPApplicationResponse + (*RejectCPApplicationRequest)(nil), // 57: hyapp.user.v1.RejectCPApplicationRequest + (*RejectCPApplicationResponse)(nil), // 58: hyapp.user.v1.RejectCPApplicationResponse + (*ListCPRelationshipsRequest)(nil), // 59: hyapp.user.v1.ListCPRelationshipsRequest + (*ListCPRelationshipsResponse)(nil), // 60: hyapp.user.v1.ListCPRelationshipsResponse + (*ListCPIntimacyLeaderboardRequest)(nil), // 61: hyapp.user.v1.ListCPIntimacyLeaderboardRequest + (*ListCPIntimacyLeaderboardResponse)(nil), // 62: hyapp.user.v1.ListCPIntimacyLeaderboardResponse + (*PrepareBreakCPRelationshipRequest)(nil), // 63: hyapp.user.v1.PrepareBreakCPRelationshipRequest + (*PrepareBreakCPRelationshipResponse)(nil), // 64: hyapp.user.v1.PrepareBreakCPRelationshipResponse + (*ConfirmBreakCPRelationshipRequest)(nil), // 65: hyapp.user.v1.ConfirmBreakCPRelationshipRequest + (*ConfirmBreakCPRelationshipResponse)(nil), // 66: hyapp.user.v1.ConfirmBreakCPRelationshipResponse + (*CancelBreakCPRelationshipRequest)(nil), // 67: hyapp.user.v1.CancelBreakCPRelationshipRequest + (*CancelBreakCPRelationshipResponse)(nil), // 68: hyapp.user.v1.CancelBreakCPRelationshipResponse + (*RoomGiftCPEvent)(nil), // 69: hyapp.user.v1.RoomGiftCPEvent + (*ConsumeRoomGiftCPEventRequest)(nil), // 70: hyapp.user.v1.ConsumeRoomGiftCPEventRequest + (*ConsumeRoomGiftCPEventResponse)(nil), // 71: hyapp.user.v1.ConsumeRoomGiftCPEventResponse + (*UserReport)(nil), // 72: hyapp.user.v1.UserReport + (*SubmitReportRequest)(nil), // 73: hyapp.user.v1.SubmitReportRequest + (*SubmitReportResponse)(nil), // 74: hyapp.user.v1.SubmitReportResponse + (*BatchGetUsersRequest)(nil), // 75: hyapp.user.v1.BatchGetUsersRequest + (*BatchGetUsersResponse)(nil), // 76: hyapp.user.v1.BatchGetUsersResponse + (*ListUserIDsRequest)(nil), // 77: hyapp.user.v1.ListUserIDsRequest + (*ListUserIDsResponse)(nil), // 78: hyapp.user.v1.ListUserIDsResponse + (*UpdateUserProfileRequest)(nil), // 79: hyapp.user.v1.UpdateUserProfileRequest + (*UpdateUserProfileResponse)(nil), // 80: hyapp.user.v1.UpdateUserProfileResponse + (*ChangeUserCountryRequest)(nil), // 81: hyapp.user.v1.ChangeUserCountryRequest + (*AdminChangeUserCountryRequest)(nil), // 82: hyapp.user.v1.AdminChangeUserCountryRequest + (*ChangeUserCountryResponse)(nil), // 83: hyapp.user.v1.ChangeUserCountryResponse + (*SetUserStatusRequest)(nil), // 84: hyapp.user.v1.SetUserStatusRequest + (*SetUserStatusResponse)(nil), // 85: hyapp.user.v1.SetUserStatusResponse + (*ManagerUserBlock)(nil), // 86: hyapp.user.v1.ManagerUserBlock + (*CreateManagerUserBlockRequest)(nil), // 87: hyapp.user.v1.CreateManagerUserBlockRequest + (*CreateManagerUserBlockResponse)(nil), // 88: hyapp.user.v1.CreateManagerUserBlockResponse + (*ListManagerUserBlocksRequest)(nil), // 89: hyapp.user.v1.ListManagerUserBlocksRequest + (*ListManagerUserBlocksResponse)(nil), // 90: hyapp.user.v1.ListManagerUserBlocksResponse + (*UnblockManagerUserRequest)(nil), // 91: hyapp.user.v1.UnblockManagerUserRequest + (*UnblockManagerUserResponse)(nil), // 92: hyapp.user.v1.UnblockManagerUserResponse + (*CompleteOnboardingRequest)(nil), // 93: hyapp.user.v1.CompleteOnboardingRequest + (*CompleteOnboardingResponse)(nil), // 94: hyapp.user.v1.CompleteOnboardingResponse + (*BindPushTokenRequest)(nil), // 95: hyapp.user.v1.BindPushTokenRequest + (*BindPushTokenResponse)(nil), // 96: hyapp.user.v1.BindPushTokenResponse + (*DeletePushTokenRequest)(nil), // 97: hyapp.user.v1.DeletePushTokenRequest + (*DeletePushTokenResponse)(nil), // 98: hyapp.user.v1.DeletePushTokenResponse + (*Country)(nil), // 99: hyapp.user.v1.Country + (*Region)(nil), // 100: hyapp.user.v1.Region + (*ListCountriesRequest)(nil), // 101: hyapp.user.v1.ListCountriesRequest + (*ListCountriesResponse)(nil), // 102: hyapp.user.v1.ListCountriesResponse + (*UpdateCountryRequest)(nil), // 103: hyapp.user.v1.UpdateCountryRequest + (*CountryResponse)(nil), // 104: hyapp.user.v1.CountryResponse + (*ListRegistrationCountriesRequest)(nil), // 105: hyapp.user.v1.ListRegistrationCountriesRequest + (*ListRegistrationCountriesResponse)(nil), // 106: hyapp.user.v1.ListRegistrationCountriesResponse + (*LoginRiskBlockedCountry)(nil), // 107: hyapp.user.v1.LoginRiskBlockedCountry + (*ListLoginRiskBlockedCountriesRequest)(nil), // 108: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest + (*ListLoginRiskBlockedCountriesResponse)(nil), // 109: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse + (*ListRegionsRequest)(nil), // 110: hyapp.user.v1.ListRegionsRequest + (*ListRegionsResponse)(nil), // 111: hyapp.user.v1.ListRegionsResponse + (*GetRegionRequest)(nil), // 112: hyapp.user.v1.GetRegionRequest + (*UpdateRegionRequest)(nil), // 113: hyapp.user.v1.UpdateRegionRequest + (*ReplaceRegionCountriesRequest)(nil), // 114: hyapp.user.v1.ReplaceRegionCountriesRequest + (*RegionResponse)(nil), // 115: hyapp.user.v1.RegionResponse + (*UserIdentity)(nil), // 116: hyapp.user.v1.UserIdentity + (*GetUserIdentityRequest)(nil), // 117: hyapp.user.v1.GetUserIdentityRequest + (*GetUserIdentityResponse)(nil), // 118: hyapp.user.v1.GetUserIdentityResponse + (*ResolveDisplayUserIDRequest)(nil), // 119: hyapp.user.v1.ResolveDisplayUserIDRequest + (*ResolveDisplayUserIDResponse)(nil), // 120: hyapp.user.v1.ResolveDisplayUserIDResponse + (*ChangeDisplayUserIDRequest)(nil), // 121: hyapp.user.v1.ChangeDisplayUserIDRequest + (*ChangeDisplayUserIDResponse)(nil), // 122: hyapp.user.v1.ChangeDisplayUserIDResponse + (*ApplyPrettyDisplayUserIDRequest)(nil), // 123: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest + (*ApplyPrettyDisplayUserIDResponse)(nil), // 124: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse + (*ExpirePrettyDisplayUserIDRequest)(nil), // 125: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest + (*ExpirePrettyDisplayUserIDResponse)(nil), // 126: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse + (*PrettyDisplayIDPool)(nil), // 127: hyapp.user.v1.PrettyDisplayIDPool + (*PrettyDisplayID)(nil), // 128: hyapp.user.v1.PrettyDisplayID + (*PrettyDisplayIDGenerationBatch)(nil), // 129: hyapp.user.v1.PrettyDisplayIDGenerationBatch + (*ListAvailablePrettyDisplayIDsRequest)(nil), // 130: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest + (*ListAvailablePrettyDisplayIDsResponse)(nil), // 131: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse + (*ApplyPrettyDisplayIDFromPoolRequest)(nil), // 132: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest + (*ApplyPrettyDisplayIDFromPoolResponse)(nil), // 133: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse + (*ListPrettyDisplayIDPoolsRequest)(nil), // 134: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest + (*ListPrettyDisplayIDPoolsResponse)(nil), // 135: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse + (*CreatePrettyDisplayIDPoolRequest)(nil), // 136: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest + (*UpdatePrettyDisplayIDPoolRequest)(nil), // 137: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest + (*PrettyDisplayIDPoolResponse)(nil), // 138: hyapp.user.v1.PrettyDisplayIDPoolResponse + (*GeneratePrettyDisplayIDsRequest)(nil), // 139: hyapp.user.v1.GeneratePrettyDisplayIDsRequest + (*GeneratePrettyDisplayIDsResponse)(nil), // 140: hyapp.user.v1.GeneratePrettyDisplayIDsResponse + (*ListPrettyDisplayIDsRequest)(nil), // 141: hyapp.user.v1.ListPrettyDisplayIDsRequest + (*ListPrettyDisplayIDsResponse)(nil), // 142: hyapp.user.v1.ListPrettyDisplayIDsResponse + (*SetPrettyDisplayIDStatusRequest)(nil), // 143: hyapp.user.v1.SetPrettyDisplayIDStatusRequest + (*PrettyDisplayIDResponse)(nil), // 144: hyapp.user.v1.PrettyDisplayIDResponse + (*AdminGrantPrettyDisplayIDRequest)(nil), // 145: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest + (*AdminGrantPrettyDisplayIDResponse)(nil), // 146: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse + nil, // 147: hyapp.user.v1.BatchGetUsersResponse.UsersEntry } var file_proto_user_v1_user_proto_depIdxs = []int32{ 1, // 0: hyapp.user.v1.ResolveAppRequest.meta:type_name -> hyapp.user.v1.RequestMeta @@ -11573,224 +12041,233 @@ var file_proto_user_v1_user_proto_depIdxs = []int32{ 47, // 35: hyapp.user.v1.CPApplication.gift:type_name -> hyapp.user.v1.CPGiftSnapshot 46, // 36: hyapp.user.v1.CPRelationship.me:type_name -> hyapp.user.v1.CPUserProfile 46, // 37: hyapp.user.v1.CPRelationship.partner:type_name -> hyapp.user.v1.CPUserProfile - 1, // 38: hyapp.user.v1.ListCPApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 48, // 39: hyapp.user.v1.ListCPApplicationsResponse.applications:type_name -> hyapp.user.v1.CPApplication - 1, // 40: hyapp.user.v1.AcceptCPApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 48, // 41: hyapp.user.v1.AcceptCPApplicationResponse.application:type_name -> hyapp.user.v1.CPApplication - 49, // 42: hyapp.user.v1.AcceptCPApplicationResponse.relationship:type_name -> hyapp.user.v1.CPRelationship - 1, // 43: hyapp.user.v1.RejectCPApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 48, // 44: hyapp.user.v1.RejectCPApplicationResponse.application:type_name -> hyapp.user.v1.CPApplication - 1, // 45: hyapp.user.v1.ListCPRelationshipsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 49, // 46: hyapp.user.v1.ListCPRelationshipsResponse.relationships:type_name -> hyapp.user.v1.CPRelationship - 1, // 47: hyapp.user.v1.PrepareBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 49, // 48: hyapp.user.v1.PrepareBreakCPRelationshipResponse.relationship:type_name -> hyapp.user.v1.CPRelationship - 1, // 49: hyapp.user.v1.ConfirmBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 49, // 50: hyapp.user.v1.ConfirmBreakCPRelationshipResponse.relationship:type_name -> hyapp.user.v1.CPRelationship - 1, // 51: hyapp.user.v1.CancelBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 52: hyapp.user.v1.RoomGiftCPEvent.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 53: hyapp.user.v1.ConsumeRoomGiftCPEventRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 64, // 54: hyapp.user.v1.ConsumeRoomGiftCPEventRequest.event:type_name -> hyapp.user.v1.RoomGiftCPEvent - 48, // 55: hyapp.user.v1.ConsumeRoomGiftCPEventResponse.application:type_name -> hyapp.user.v1.CPApplication - 49, // 56: hyapp.user.v1.ConsumeRoomGiftCPEventResponse.relationship:type_name -> hyapp.user.v1.CPRelationship - 1, // 57: hyapp.user.v1.SubmitReportRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 67, // 58: hyapp.user.v1.SubmitReportResponse.report:type_name -> hyapp.user.v1.UserReport - 1, // 59: hyapp.user.v1.BatchGetUsersRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 142, // 60: hyapp.user.v1.BatchGetUsersResponse.users:type_name -> hyapp.user.v1.BatchGetUsersResponse.UsersEntry - 1, // 61: hyapp.user.v1.ListUserIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 62: hyapp.user.v1.UpdateUserProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 63: hyapp.user.v1.UpdateUserProfileResponse.user:type_name -> hyapp.user.v1.User - 1, // 64: hyapp.user.v1.ChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 65: hyapp.user.v1.AdminChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 66: hyapp.user.v1.ChangeUserCountryResponse.user:type_name -> hyapp.user.v1.User - 1, // 67: hyapp.user.v1.SetUserStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 0, // 68: hyapp.user.v1.SetUserStatusRequest.status:type_name -> hyapp.user.v1.UserStatus - 5, // 69: hyapp.user.v1.SetUserStatusResponse.user:type_name -> hyapp.user.v1.User - 1, // 70: hyapp.user.v1.CreateManagerUserBlockRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 81, // 71: hyapp.user.v1.CreateManagerUserBlockResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock - 80, // 72: hyapp.user.v1.CreateManagerUserBlockResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse - 1, // 73: hyapp.user.v1.ListManagerUserBlocksRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 81, // 74: hyapp.user.v1.ListManagerUserBlocksResponse.blocks:type_name -> hyapp.user.v1.ManagerUserBlock - 1, // 75: hyapp.user.v1.UnblockManagerUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 81, // 76: hyapp.user.v1.UnblockManagerUserResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock - 80, // 77: hyapp.user.v1.UnblockManagerUserResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse - 1, // 78: hyapp.user.v1.CompleteOnboardingRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 79: hyapp.user.v1.CompleteOnboardingResponse.user:type_name -> hyapp.user.v1.User - 13, // 80: hyapp.user.v1.CompleteOnboardingResponse.token:type_name -> hyapp.user.v1.AuthToken - 7, // 81: hyapp.user.v1.CompleteOnboardingResponse.invite:type_name -> hyapp.user.v1.InviteBinding - 1, // 82: hyapp.user.v1.BindPushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 83: hyapp.user.v1.DeletePushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 84: hyapp.user.v1.ListCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 94, // 85: hyapp.user.v1.ListCountriesResponse.countries:type_name -> hyapp.user.v1.Country - 1, // 86: hyapp.user.v1.UpdateCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 94, // 87: hyapp.user.v1.CountryResponse.country:type_name -> hyapp.user.v1.Country - 1, // 88: hyapp.user.v1.ListRegistrationCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 94, // 89: hyapp.user.v1.ListRegistrationCountriesResponse.countries:type_name -> hyapp.user.v1.Country - 1, // 90: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 102, // 91: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse.countries:type_name -> hyapp.user.v1.LoginRiskBlockedCountry - 1, // 92: hyapp.user.v1.ListRegionsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 95, // 93: hyapp.user.v1.ListRegionsResponse.regions:type_name -> hyapp.user.v1.Region - 1, // 94: hyapp.user.v1.GetRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 95: hyapp.user.v1.UpdateRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 96: hyapp.user.v1.ReplaceRegionCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 95, // 97: hyapp.user.v1.RegionResponse.region:type_name -> hyapp.user.v1.Region - 1, // 98: hyapp.user.v1.GetUserIdentityRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 111, // 99: hyapp.user.v1.GetUserIdentityResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 100: hyapp.user.v1.ResolveDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 111, // 101: hyapp.user.v1.ResolveDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 102: hyapp.user.v1.ChangeDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 111, // 103: hyapp.user.v1.ChangeDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 104: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 111, // 105: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 106: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 111, // 107: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 122, // 108: hyapp.user.v1.PrettyDisplayID.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 109: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 123, // 110: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 111: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 111, // 112: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 113: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 122, // 114: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 115: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 116: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 122, // 117: hyapp.user.v1.PrettyDisplayIDPoolResponse.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 118: hyapp.user.v1.GeneratePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 124, // 119: hyapp.user.v1.GeneratePrettyDisplayIDsResponse.batch:type_name -> hyapp.user.v1.PrettyDisplayIDGenerationBatch - 1, // 120: hyapp.user.v1.ListPrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 123, // 121: hyapp.user.v1.ListPrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 122: hyapp.user.v1.SetPrettyDisplayIDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 123, // 123: hyapp.user.v1.PrettyDisplayIDResponse.item:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 124: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 111, // 125: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 5, // 126: hyapp.user.v1.BatchGetUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.User - 14, // 127: hyapp.user.v1.UserService.GetUser:input_type -> hyapp.user.v1.GetUserRequest - 16, // 128: hyapp.user.v1.UserService.BusinessUserLookup:input_type -> hyapp.user.v1.BusinessUserLookupRequest - 20, // 129: hyapp.user.v1.UserService.GetMyProfileStats:input_type -> hyapp.user.v1.GetMyProfileStatsRequest - 70, // 130: hyapp.user.v1.UserService.BatchGetUsers:input_type -> hyapp.user.v1.BatchGetUsersRequest - 72, // 131: hyapp.user.v1.UserService.ListUserIDs:input_type -> hyapp.user.v1.ListUserIDsRequest - 9, // 132: hyapp.user.v1.UserService.GetUserMicLifetimeStats:input_type -> hyapp.user.v1.GetUserMicLifetimeStatsRequest - 74, // 133: hyapp.user.v1.UserService.UpdateUserProfile:input_type -> hyapp.user.v1.UpdateUserProfileRequest - 76, // 134: hyapp.user.v1.UserService.ChangeUserCountry:input_type -> hyapp.user.v1.ChangeUserCountryRequest - 77, // 135: hyapp.user.v1.UserService.AdminChangeUserCountry:input_type -> hyapp.user.v1.AdminChangeUserCountryRequest - 79, // 136: hyapp.user.v1.UserService.SetUserStatus:input_type -> hyapp.user.v1.SetUserStatusRequest - 82, // 137: hyapp.user.v1.UserService.CreateManagerUserBlock:input_type -> hyapp.user.v1.CreateManagerUserBlockRequest - 84, // 138: hyapp.user.v1.UserService.ListManagerUserBlocks:input_type -> hyapp.user.v1.ListManagerUserBlocksRequest - 86, // 139: hyapp.user.v1.UserService.UnblockManagerUser:input_type -> hyapp.user.v1.UnblockManagerUserRequest - 88, // 140: hyapp.user.v1.UserService.CompleteOnboarding:input_type -> hyapp.user.v1.CompleteOnboardingRequest - 22, // 141: hyapp.user.v1.UserSocialService.RecordProfileVisit:input_type -> hyapp.user.v1.RecordProfileVisitRequest - 25, // 142: hyapp.user.v1.UserSocialService.ListProfileVisitors:input_type -> hyapp.user.v1.ListProfileVisitorsRequest - 27, // 143: hyapp.user.v1.UserSocialService.FollowUser:input_type -> hyapp.user.v1.FollowUserRequest - 29, // 144: hyapp.user.v1.UserSocialService.UnfollowUser:input_type -> hyapp.user.v1.UnfollowUserRequest - 32, // 145: hyapp.user.v1.UserSocialService.ListFollowing:input_type -> hyapp.user.v1.ListFollowingRequest - 34, // 146: hyapp.user.v1.UserSocialService.ApplyFriend:input_type -> hyapp.user.v1.ApplyFriendRequest - 36, // 147: hyapp.user.v1.UserSocialService.AcceptFriendApplication:input_type -> hyapp.user.v1.AcceptFriendApplicationRequest - 38, // 148: hyapp.user.v1.UserSocialService.DeleteFriend:input_type -> hyapp.user.v1.DeleteFriendRequest - 41, // 149: hyapp.user.v1.UserSocialService.ListFriends:input_type -> hyapp.user.v1.ListFriendsRequest - 44, // 150: hyapp.user.v1.UserSocialService.ListFriendApplications:input_type -> hyapp.user.v1.ListFriendApplicationsRequest - 68, // 151: hyapp.user.v1.UserSocialService.SubmitReport:input_type -> hyapp.user.v1.SubmitReportRequest - 50, // 152: hyapp.user.v1.UserCPService.ListCPApplications:input_type -> hyapp.user.v1.ListCPApplicationsRequest - 52, // 153: hyapp.user.v1.UserCPService.AcceptCPApplication:input_type -> hyapp.user.v1.AcceptCPApplicationRequest - 54, // 154: hyapp.user.v1.UserCPService.RejectCPApplication:input_type -> hyapp.user.v1.RejectCPApplicationRequest - 56, // 155: hyapp.user.v1.UserCPService.ListCPRelationships:input_type -> hyapp.user.v1.ListCPRelationshipsRequest - 58, // 156: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:input_type -> hyapp.user.v1.PrepareBreakCPRelationshipRequest - 60, // 157: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:input_type -> hyapp.user.v1.ConfirmBreakCPRelationshipRequest - 62, // 158: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:input_type -> hyapp.user.v1.CancelBreakCPRelationshipRequest - 65, // 159: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:input_type -> hyapp.user.v1.ConsumeRoomGiftCPEventRequest - 11, // 160: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 161: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 162: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 163: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest - 90, // 164: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest - 92, // 165: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest - 3, // 166: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest - 96, // 167: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest - 98, // 168: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest - 100, // 169: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest - 103, // 170: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest - 105, // 171: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest - 107, // 172: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest - 108, // 173: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest - 109, // 174: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest - 112, // 175: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest - 114, // 176: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest - 116, // 177: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest - 118, // 178: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest - 125, // 179: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest - 127, // 180: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest - 120, // 181: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest - 129, // 182: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest - 131, // 183: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest - 132, // 184: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest - 134, // 185: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest - 136, // 186: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest - 138, // 187: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest - 140, // 188: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest - 15, // 189: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse - 18, // 190: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse - 21, // 191: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse - 71, // 192: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse - 73, // 193: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse - 10, // 194: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse - 75, // 195: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse - 78, // 196: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse - 78, // 197: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse - 80, // 198: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse - 83, // 199: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse - 85, // 200: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse - 87, // 201: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse - 89, // 202: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse - 23, // 203: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse - 26, // 204: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse - 28, // 205: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse - 30, // 206: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse - 33, // 207: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse - 35, // 208: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse - 37, // 209: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse - 39, // 210: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse - 42, // 211: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse - 45, // 212: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse - 69, // 213: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse - 51, // 214: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse - 53, // 215: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse - 55, // 216: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse - 57, // 217: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse - 59, // 218: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse - 61, // 219: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse - 63, // 220: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse - 66, // 221: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse - 12, // 222: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 223: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 224: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 225: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse - 91, // 226: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse - 93, // 227: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse - 4, // 228: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse - 97, // 229: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse - 99, // 230: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse - 101, // 231: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse - 104, // 232: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse - 106, // 233: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse - 110, // 234: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse - 110, // 235: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse - 110, // 236: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse - 113, // 237: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse - 115, // 238: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse - 117, // 239: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse - 119, // 240: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse - 126, // 241: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse - 128, // 242: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse - 121, // 243: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse - 130, // 244: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse - 133, // 245: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse - 133, // 246: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse - 135, // 247: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse - 137, // 248: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse - 139, // 249: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse - 141, // 250: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse - 189, // [189:251] is the sub-list for method output_type - 127, // [127:189] is the sub-list for method input_type - 127, // [127:127] is the sub-list for extension type_name - 127, // [127:127] is the sub-list for extension extendee - 0, // [0:127] is the sub-list for field type_name + 50, // 38: hyapp.user.v1.CPIntimacyLeaderboardUser.avatar_frame:type_name -> hyapp.user.v1.CPAvatarFrameSnapshot + 51, // 39: hyapp.user.v1.CPIntimacyLeaderboardItem.user_a:type_name -> hyapp.user.v1.CPIntimacyLeaderboardUser + 51, // 40: hyapp.user.v1.CPIntimacyLeaderboardItem.user_b:type_name -> hyapp.user.v1.CPIntimacyLeaderboardUser + 1, // 41: hyapp.user.v1.ListCPApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 48, // 42: hyapp.user.v1.ListCPApplicationsResponse.applications:type_name -> hyapp.user.v1.CPApplication + 1, // 43: hyapp.user.v1.AcceptCPApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 48, // 44: hyapp.user.v1.AcceptCPApplicationResponse.application:type_name -> hyapp.user.v1.CPApplication + 49, // 45: hyapp.user.v1.AcceptCPApplicationResponse.relationship:type_name -> hyapp.user.v1.CPRelationship + 1, // 46: hyapp.user.v1.RejectCPApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 48, // 47: hyapp.user.v1.RejectCPApplicationResponse.application:type_name -> hyapp.user.v1.CPApplication + 1, // 48: hyapp.user.v1.ListCPRelationshipsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 49, // 49: hyapp.user.v1.ListCPRelationshipsResponse.relationships:type_name -> hyapp.user.v1.CPRelationship + 1, // 50: hyapp.user.v1.ListCPIntimacyLeaderboardRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 52, // 51: hyapp.user.v1.ListCPIntimacyLeaderboardResponse.items:type_name -> hyapp.user.v1.CPIntimacyLeaderboardItem + 1, // 52: hyapp.user.v1.PrepareBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 49, // 53: hyapp.user.v1.PrepareBreakCPRelationshipResponse.relationship:type_name -> hyapp.user.v1.CPRelationship + 1, // 54: hyapp.user.v1.ConfirmBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 49, // 55: hyapp.user.v1.ConfirmBreakCPRelationshipResponse.relationship:type_name -> hyapp.user.v1.CPRelationship + 1, // 56: hyapp.user.v1.CancelBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 57: hyapp.user.v1.RoomGiftCPEvent.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 58: hyapp.user.v1.ConsumeRoomGiftCPEventRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 69, // 59: hyapp.user.v1.ConsumeRoomGiftCPEventRequest.event:type_name -> hyapp.user.v1.RoomGiftCPEvent + 48, // 60: hyapp.user.v1.ConsumeRoomGiftCPEventResponse.application:type_name -> hyapp.user.v1.CPApplication + 49, // 61: hyapp.user.v1.ConsumeRoomGiftCPEventResponse.relationship:type_name -> hyapp.user.v1.CPRelationship + 1, // 62: hyapp.user.v1.SubmitReportRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 72, // 63: hyapp.user.v1.SubmitReportResponse.report:type_name -> hyapp.user.v1.UserReport + 1, // 64: hyapp.user.v1.BatchGetUsersRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 147, // 65: hyapp.user.v1.BatchGetUsersResponse.users:type_name -> hyapp.user.v1.BatchGetUsersResponse.UsersEntry + 1, // 66: hyapp.user.v1.ListUserIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 67: hyapp.user.v1.UpdateUserProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 68: hyapp.user.v1.UpdateUserProfileResponse.user:type_name -> hyapp.user.v1.User + 1, // 69: hyapp.user.v1.ChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 70: hyapp.user.v1.AdminChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 71: hyapp.user.v1.ChangeUserCountryResponse.user:type_name -> hyapp.user.v1.User + 1, // 72: hyapp.user.v1.SetUserStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 0, // 73: hyapp.user.v1.SetUserStatusRequest.status:type_name -> hyapp.user.v1.UserStatus + 5, // 74: hyapp.user.v1.SetUserStatusResponse.user:type_name -> hyapp.user.v1.User + 1, // 75: hyapp.user.v1.CreateManagerUserBlockRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 86, // 76: hyapp.user.v1.CreateManagerUserBlockResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock + 85, // 77: hyapp.user.v1.CreateManagerUserBlockResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse + 1, // 78: hyapp.user.v1.ListManagerUserBlocksRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 86, // 79: hyapp.user.v1.ListManagerUserBlocksResponse.blocks:type_name -> hyapp.user.v1.ManagerUserBlock + 1, // 80: hyapp.user.v1.UnblockManagerUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 86, // 81: hyapp.user.v1.UnblockManagerUserResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock + 85, // 82: hyapp.user.v1.UnblockManagerUserResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse + 1, // 83: hyapp.user.v1.CompleteOnboardingRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 84: hyapp.user.v1.CompleteOnboardingResponse.user:type_name -> hyapp.user.v1.User + 13, // 85: hyapp.user.v1.CompleteOnboardingResponse.token:type_name -> hyapp.user.v1.AuthToken + 7, // 86: hyapp.user.v1.CompleteOnboardingResponse.invite:type_name -> hyapp.user.v1.InviteBinding + 1, // 87: hyapp.user.v1.BindPushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 88: hyapp.user.v1.DeletePushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 89: hyapp.user.v1.ListCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 99, // 90: hyapp.user.v1.ListCountriesResponse.countries:type_name -> hyapp.user.v1.Country + 1, // 91: hyapp.user.v1.UpdateCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 99, // 92: hyapp.user.v1.CountryResponse.country:type_name -> hyapp.user.v1.Country + 1, // 93: hyapp.user.v1.ListRegistrationCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 99, // 94: hyapp.user.v1.ListRegistrationCountriesResponse.countries:type_name -> hyapp.user.v1.Country + 1, // 95: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 107, // 96: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse.countries:type_name -> hyapp.user.v1.LoginRiskBlockedCountry + 1, // 97: hyapp.user.v1.ListRegionsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 100, // 98: hyapp.user.v1.ListRegionsResponse.regions:type_name -> hyapp.user.v1.Region + 1, // 99: hyapp.user.v1.GetRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 100: hyapp.user.v1.UpdateRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 101: hyapp.user.v1.ReplaceRegionCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 100, // 102: hyapp.user.v1.RegionResponse.region:type_name -> hyapp.user.v1.Region + 1, // 103: hyapp.user.v1.GetUserIdentityRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 116, // 104: hyapp.user.v1.GetUserIdentityResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 105: hyapp.user.v1.ResolveDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 116, // 106: hyapp.user.v1.ResolveDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 107: hyapp.user.v1.ChangeDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 116, // 108: hyapp.user.v1.ChangeDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 109: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 116, // 110: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 111: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 116, // 112: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 127, // 113: hyapp.user.v1.PrettyDisplayID.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 114: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 128, // 115: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 116: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 116, // 117: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 118: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 127, // 119: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 120: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 121: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 127, // 122: hyapp.user.v1.PrettyDisplayIDPoolResponse.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 123: hyapp.user.v1.GeneratePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 129, // 124: hyapp.user.v1.GeneratePrettyDisplayIDsResponse.batch:type_name -> hyapp.user.v1.PrettyDisplayIDGenerationBatch + 1, // 125: hyapp.user.v1.ListPrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 128, // 126: hyapp.user.v1.ListPrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 127: hyapp.user.v1.SetPrettyDisplayIDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 128, // 128: hyapp.user.v1.PrettyDisplayIDResponse.item:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 129: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 116, // 130: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 5, // 131: hyapp.user.v1.BatchGetUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.User + 14, // 132: hyapp.user.v1.UserService.GetUser:input_type -> hyapp.user.v1.GetUserRequest + 16, // 133: hyapp.user.v1.UserService.BusinessUserLookup:input_type -> hyapp.user.v1.BusinessUserLookupRequest + 20, // 134: hyapp.user.v1.UserService.GetMyProfileStats:input_type -> hyapp.user.v1.GetMyProfileStatsRequest + 75, // 135: hyapp.user.v1.UserService.BatchGetUsers:input_type -> hyapp.user.v1.BatchGetUsersRequest + 77, // 136: hyapp.user.v1.UserService.ListUserIDs:input_type -> hyapp.user.v1.ListUserIDsRequest + 9, // 137: hyapp.user.v1.UserService.GetUserMicLifetimeStats:input_type -> hyapp.user.v1.GetUserMicLifetimeStatsRequest + 79, // 138: hyapp.user.v1.UserService.UpdateUserProfile:input_type -> hyapp.user.v1.UpdateUserProfileRequest + 81, // 139: hyapp.user.v1.UserService.ChangeUserCountry:input_type -> hyapp.user.v1.ChangeUserCountryRequest + 82, // 140: hyapp.user.v1.UserService.AdminChangeUserCountry:input_type -> hyapp.user.v1.AdminChangeUserCountryRequest + 84, // 141: hyapp.user.v1.UserService.SetUserStatus:input_type -> hyapp.user.v1.SetUserStatusRequest + 87, // 142: hyapp.user.v1.UserService.CreateManagerUserBlock:input_type -> hyapp.user.v1.CreateManagerUserBlockRequest + 89, // 143: hyapp.user.v1.UserService.ListManagerUserBlocks:input_type -> hyapp.user.v1.ListManagerUserBlocksRequest + 91, // 144: hyapp.user.v1.UserService.UnblockManagerUser:input_type -> hyapp.user.v1.UnblockManagerUserRequest + 93, // 145: hyapp.user.v1.UserService.CompleteOnboarding:input_type -> hyapp.user.v1.CompleteOnboardingRequest + 22, // 146: hyapp.user.v1.UserSocialService.RecordProfileVisit:input_type -> hyapp.user.v1.RecordProfileVisitRequest + 25, // 147: hyapp.user.v1.UserSocialService.ListProfileVisitors:input_type -> hyapp.user.v1.ListProfileVisitorsRequest + 27, // 148: hyapp.user.v1.UserSocialService.FollowUser:input_type -> hyapp.user.v1.FollowUserRequest + 29, // 149: hyapp.user.v1.UserSocialService.UnfollowUser:input_type -> hyapp.user.v1.UnfollowUserRequest + 32, // 150: hyapp.user.v1.UserSocialService.ListFollowing:input_type -> hyapp.user.v1.ListFollowingRequest + 34, // 151: hyapp.user.v1.UserSocialService.ApplyFriend:input_type -> hyapp.user.v1.ApplyFriendRequest + 36, // 152: hyapp.user.v1.UserSocialService.AcceptFriendApplication:input_type -> hyapp.user.v1.AcceptFriendApplicationRequest + 38, // 153: hyapp.user.v1.UserSocialService.DeleteFriend:input_type -> hyapp.user.v1.DeleteFriendRequest + 41, // 154: hyapp.user.v1.UserSocialService.ListFriends:input_type -> hyapp.user.v1.ListFriendsRequest + 44, // 155: hyapp.user.v1.UserSocialService.ListFriendApplications:input_type -> hyapp.user.v1.ListFriendApplicationsRequest + 73, // 156: hyapp.user.v1.UserSocialService.SubmitReport:input_type -> hyapp.user.v1.SubmitReportRequest + 53, // 157: hyapp.user.v1.UserCPService.ListCPApplications:input_type -> hyapp.user.v1.ListCPApplicationsRequest + 55, // 158: hyapp.user.v1.UserCPService.AcceptCPApplication:input_type -> hyapp.user.v1.AcceptCPApplicationRequest + 57, // 159: hyapp.user.v1.UserCPService.RejectCPApplication:input_type -> hyapp.user.v1.RejectCPApplicationRequest + 59, // 160: hyapp.user.v1.UserCPService.ListCPRelationships:input_type -> hyapp.user.v1.ListCPRelationshipsRequest + 61, // 161: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:input_type -> hyapp.user.v1.ListCPIntimacyLeaderboardRequest + 63, // 162: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:input_type -> hyapp.user.v1.PrepareBreakCPRelationshipRequest + 65, // 163: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:input_type -> hyapp.user.v1.ConfirmBreakCPRelationshipRequest + 67, // 164: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:input_type -> hyapp.user.v1.CancelBreakCPRelationshipRequest + 70, // 165: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:input_type -> hyapp.user.v1.ConsumeRoomGiftCPEventRequest + 11, // 166: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:input_type -> hyapp.user.v1.CronBatchRequest + 11, // 167: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:input_type -> hyapp.user.v1.CronBatchRequest + 11, // 168: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest + 11, // 169: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest + 11, // 170: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:input_type -> hyapp.user.v1.CronBatchRequest + 95, // 171: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest + 97, // 172: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest + 3, // 173: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest + 101, // 174: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest + 103, // 175: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest + 105, // 176: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest + 108, // 177: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest + 110, // 178: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest + 112, // 179: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest + 113, // 180: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest + 114, // 181: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest + 117, // 182: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest + 119, // 183: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest + 121, // 184: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest + 123, // 185: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest + 130, // 186: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest + 132, // 187: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest + 125, // 188: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest + 134, // 189: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest + 136, // 190: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest + 137, // 191: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest + 139, // 192: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest + 141, // 193: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest + 143, // 194: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest + 145, // 195: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest + 15, // 196: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse + 18, // 197: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse + 21, // 198: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse + 76, // 199: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse + 78, // 200: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse + 10, // 201: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse + 80, // 202: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse + 83, // 203: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse + 83, // 204: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse + 85, // 205: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse + 88, // 206: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse + 90, // 207: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse + 92, // 208: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse + 94, // 209: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse + 23, // 210: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse + 26, // 211: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse + 28, // 212: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse + 30, // 213: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse + 33, // 214: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse + 35, // 215: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse + 37, // 216: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse + 39, // 217: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse + 42, // 218: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse + 45, // 219: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse + 74, // 220: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse + 54, // 221: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse + 56, // 222: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse + 58, // 223: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse + 60, // 224: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse + 62, // 225: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:output_type -> hyapp.user.v1.ListCPIntimacyLeaderboardResponse + 64, // 226: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse + 66, // 227: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse + 68, // 228: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse + 71, // 229: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse + 12, // 230: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse + 12, // 231: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse + 12, // 232: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse + 12, // 233: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse + 12, // 234: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:output_type -> hyapp.user.v1.CronBatchResponse + 96, // 235: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse + 98, // 236: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse + 4, // 237: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse + 102, // 238: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse + 104, // 239: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse + 106, // 240: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse + 109, // 241: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse + 111, // 242: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse + 115, // 243: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse + 115, // 244: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse + 115, // 245: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse + 118, // 246: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse + 120, // 247: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse + 122, // 248: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse + 124, // 249: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse + 131, // 250: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse + 133, // 251: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse + 126, // 252: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse + 135, // 253: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse + 138, // 254: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse + 138, // 255: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse + 140, // 256: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse + 142, // 257: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse + 144, // 258: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse + 146, // 259: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse + 196, // [196:260] is the sub-list for method output_type + 132, // [132:196] is the sub-list for method input_type + 132, // [132:132] is the sub-list for extension type_name + 132, // [132:132] is the sub-list for extension extendee + 0, // [0:132] is the sub-list for field type_name } func init() { file_proto_user_v1_user_proto_init() } @@ -11798,15 +12275,15 @@ func file_proto_user_v1_user_proto_init() { if File_proto_user_v1_user_proto != nil { return } - file_proto_user_v1_user_proto_msgTypes[73].OneofWrappers = []any{} - file_proto_user_v1_user_proto_msgTypes[95].OneofWrappers = []any{} + file_proto_user_v1_user_proto_msgTypes[78].OneofWrappers = []any{} + file_proto_user_v1_user_proto_msgTypes[100].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_user_v1_user_proto_rawDesc), len(file_proto_user_v1_user_proto_rawDesc)), NumEnums: 1, - NumMessages: 142, + NumMessages: 147, NumExtensions: 0, NumServices: 12, }, diff --git a/api/proto/user/v1/user.proto b/api/proto/user/v1/user.proto index e5fbce1f..b9e4290b 100644 --- a/api/proto/user/v1/user.proto +++ b/api/proto/user/v1/user.proto @@ -420,6 +420,39 @@ message CPRelationship { int64 ended_at_ms = 17; } +// CPAvatarFrameSnapshot 是排行榜里展示用户当前头像框的轻量快照。 +message CPAvatarFrameSnapshot { + int64 resource_id = 1; + string resource_code = 2; + string name = 3; + string asset_url = 4; + string preview_url = 5; + string animation_url = 6; + string metadata_json = 7; +} + +// CPIntimacyLeaderboardUser 是 CP 亲密榜中一方用户的展示资料。 +message CPIntimacyLeaderboardUser { + int64 user_id = 1; + string display_user_id = 2; + string username = 3; + string avatar = 4; + CPAvatarFrameSnapshot avatar_frame = 5; +} + +// CPIntimacyLeaderboardItem 是一对 active 关系在亲密值榜单里的读模型。 +message CPIntimacyLeaderboardItem { + int64 rank = 1; + string relationship_id = 2; + string relation_type = 3; + int64 intimacy_value = 4; + int32 level = 5; + CPIntimacyLeaderboardUser user_a = 6; + CPIntimacyLeaderboardUser user_b = 7; + int64 formed_at_ms = 8; + int64 updated_at_ms = 9; +} + message ListCPApplicationsRequest { RequestMeta meta = 1; int64 user_id = 2; @@ -472,6 +505,21 @@ message ListCPRelationshipsResponse { reserved "max_count_by_relation_type"; } +message ListCPIntimacyLeaderboardRequest { + RequestMeta meta = 1; + string relation_type = 2; + int32 page = 3; + int32 page_size = 4; +} + +message ListCPIntimacyLeaderboardResponse { + repeated CPIntimacyLeaderboardItem items = 1; + int64 total = 2; + int32 page = 3; + int32 page_size = 4; + int64 server_time_ms = 5; +} + message PrepareBreakCPRelationshipRequest { RequestMeta meta = 1; int64 user_id = 2; @@ -1189,6 +1237,7 @@ service UserCPService { rpc AcceptCPApplication(AcceptCPApplicationRequest) returns (AcceptCPApplicationResponse); rpc RejectCPApplication(RejectCPApplicationRequest) returns (RejectCPApplicationResponse); rpc ListCPRelationships(ListCPRelationshipsRequest) returns (ListCPRelationshipsResponse); + rpc ListCPIntimacyLeaderboard(ListCPIntimacyLeaderboardRequest) returns (ListCPIntimacyLeaderboardResponse); rpc PrepareBreakCPRelationship(PrepareBreakCPRelationshipRequest) returns (PrepareBreakCPRelationshipResponse); rpc ConfirmBreakCPRelationship(ConfirmBreakCPRelationshipRequest) returns (ConfirmBreakCPRelationshipResponse); rpc CancelBreakCPRelationship(CancelBreakCPRelationshipRequest) returns (CancelBreakCPRelationshipResponse); @@ -1205,6 +1254,7 @@ service UserCronService { rpc ProcessRegionRebuildBatch(CronBatchRequest) returns (CronBatchResponse); rpc CompensateMicOpenSessions(CronBatchRequest) returns (CronBatchResponse); rpc ExpireManagerUserBlocks(CronBatchRequest) returns (CronBatchResponse); + rpc RefreshCPIntimacyLeaderboard(CronBatchRequest) returns (CronBatchResponse); } // UserDeviceService 承载 App 设备推送 token 的绑定和失效。 diff --git a/api/proto/user/v1/user_grpc.pb.go b/api/proto/user/v1/user_grpc.pb.go index d35f672a..fe36fda0 100644 --- a/api/proto/user/v1/user_grpc.pb.go +++ b/api/proto/user/v1/user_grpc.pb.go @@ -1109,6 +1109,7 @@ const ( UserCPService_AcceptCPApplication_FullMethodName = "/hyapp.user.v1.UserCPService/AcceptCPApplication" UserCPService_RejectCPApplication_FullMethodName = "/hyapp.user.v1.UserCPService/RejectCPApplication" UserCPService_ListCPRelationships_FullMethodName = "/hyapp.user.v1.UserCPService/ListCPRelationships" + UserCPService_ListCPIntimacyLeaderboard_FullMethodName = "/hyapp.user.v1.UserCPService/ListCPIntimacyLeaderboard" UserCPService_PrepareBreakCPRelationship_FullMethodName = "/hyapp.user.v1.UserCPService/PrepareBreakCPRelationship" UserCPService_ConfirmBreakCPRelationship_FullMethodName = "/hyapp.user.v1.UserCPService/ConfirmBreakCPRelationship" UserCPService_CancelBreakCPRelationship_FullMethodName = "/hyapp.user.v1.UserCPService/CancelBreakCPRelationship" @@ -1124,6 +1125,7 @@ type UserCPServiceClient interface { AcceptCPApplication(ctx context.Context, in *AcceptCPApplicationRequest, opts ...grpc.CallOption) (*AcceptCPApplicationResponse, error) RejectCPApplication(ctx context.Context, in *RejectCPApplicationRequest, opts ...grpc.CallOption) (*RejectCPApplicationResponse, error) ListCPRelationships(ctx context.Context, in *ListCPRelationshipsRequest, opts ...grpc.CallOption) (*ListCPRelationshipsResponse, error) + ListCPIntimacyLeaderboard(ctx context.Context, in *ListCPIntimacyLeaderboardRequest, opts ...grpc.CallOption) (*ListCPIntimacyLeaderboardResponse, error) PrepareBreakCPRelationship(ctx context.Context, in *PrepareBreakCPRelationshipRequest, opts ...grpc.CallOption) (*PrepareBreakCPRelationshipResponse, error) ConfirmBreakCPRelationship(ctx context.Context, in *ConfirmBreakCPRelationshipRequest, opts ...grpc.CallOption) (*ConfirmBreakCPRelationshipResponse, error) CancelBreakCPRelationship(ctx context.Context, in *CancelBreakCPRelationshipRequest, opts ...grpc.CallOption) (*CancelBreakCPRelationshipResponse, error) @@ -1177,6 +1179,16 @@ func (c *userCPServiceClient) ListCPRelationships(ctx context.Context, in *ListC return out, nil } +func (c *userCPServiceClient) ListCPIntimacyLeaderboard(ctx context.Context, in *ListCPIntimacyLeaderboardRequest, opts ...grpc.CallOption) (*ListCPIntimacyLeaderboardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListCPIntimacyLeaderboardResponse) + err := c.cc.Invoke(ctx, UserCPService_ListCPIntimacyLeaderboard_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userCPServiceClient) PrepareBreakCPRelationship(ctx context.Context, in *PrepareBreakCPRelationshipRequest, opts ...grpc.CallOption) (*PrepareBreakCPRelationshipResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PrepareBreakCPRelationshipResponse) @@ -1217,6 +1229,7 @@ type UserCPServiceServer interface { AcceptCPApplication(context.Context, *AcceptCPApplicationRequest) (*AcceptCPApplicationResponse, error) RejectCPApplication(context.Context, *RejectCPApplicationRequest) (*RejectCPApplicationResponse, error) ListCPRelationships(context.Context, *ListCPRelationshipsRequest) (*ListCPRelationshipsResponse, error) + ListCPIntimacyLeaderboard(context.Context, *ListCPIntimacyLeaderboardRequest) (*ListCPIntimacyLeaderboardResponse, error) PrepareBreakCPRelationship(context.Context, *PrepareBreakCPRelationshipRequest) (*PrepareBreakCPRelationshipResponse, error) ConfirmBreakCPRelationship(context.Context, *ConfirmBreakCPRelationshipRequest) (*ConfirmBreakCPRelationshipResponse, error) CancelBreakCPRelationship(context.Context, *CancelBreakCPRelationshipRequest) (*CancelBreakCPRelationshipResponse, error) @@ -1242,6 +1255,9 @@ func (UnimplementedUserCPServiceServer) RejectCPApplication(context.Context, *Re func (UnimplementedUserCPServiceServer) ListCPRelationships(context.Context, *ListCPRelationshipsRequest) (*ListCPRelationshipsResponse, error) { return nil, status.Error(codes.Unimplemented, "method ListCPRelationships not implemented") } +func (UnimplementedUserCPServiceServer) ListCPIntimacyLeaderboard(context.Context, *ListCPIntimacyLeaderboardRequest) (*ListCPIntimacyLeaderboardResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListCPIntimacyLeaderboard not implemented") +} func (UnimplementedUserCPServiceServer) PrepareBreakCPRelationship(context.Context, *PrepareBreakCPRelationshipRequest) (*PrepareBreakCPRelationshipResponse, error) { return nil, status.Error(codes.Unimplemented, "method PrepareBreakCPRelationship not implemented") } @@ -1344,6 +1360,24 @@ func _UserCPService_ListCPRelationships_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } +func _UserCPService_ListCPIntimacyLeaderboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListCPIntimacyLeaderboardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserCPServiceServer).ListCPIntimacyLeaderboard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserCPService_ListCPIntimacyLeaderboard_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserCPServiceServer).ListCPIntimacyLeaderboard(ctx, req.(*ListCPIntimacyLeaderboardRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserCPService_PrepareBreakCPRelationship_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(PrepareBreakCPRelationshipRequest) if err := dec(in); err != nil { @@ -1421,6 +1455,10 @@ var UserCPService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListCPRelationships", Handler: _UserCPService_ListCPRelationships_Handler, }, + { + MethodName: "ListCPIntimacyLeaderboard", + Handler: _UserCPService_ListCPIntimacyLeaderboard_Handler, + }, { MethodName: "PrepareBreakCPRelationship", Handler: _UserCPService_PrepareBreakCPRelationship_Handler, @@ -1545,10 +1583,11 @@ var UserCPInternalService_ServiceDesc = grpc.ServiceDesc{ } const ( - UserCronService_ProcessLoginIPRiskBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessLoginIPRiskBatch" - UserCronService_ProcessRegionRebuildBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessRegionRebuildBatch" - UserCronService_CompensateMicOpenSessions_FullMethodName = "/hyapp.user.v1.UserCronService/CompensateMicOpenSessions" - UserCronService_ExpireManagerUserBlocks_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireManagerUserBlocks" + UserCronService_ProcessLoginIPRiskBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessLoginIPRiskBatch" + UserCronService_ProcessRegionRebuildBatch_FullMethodName = "/hyapp.user.v1.UserCronService/ProcessRegionRebuildBatch" + UserCronService_CompensateMicOpenSessions_FullMethodName = "/hyapp.user.v1.UserCronService/CompensateMicOpenSessions" + UserCronService_ExpireManagerUserBlocks_FullMethodName = "/hyapp.user.v1.UserCronService/ExpireManagerUserBlocks" + UserCronService_RefreshCPIntimacyLeaderboard_FullMethodName = "/hyapp.user.v1.UserCronService/RefreshCPIntimacyLeaderboard" ) // UserCronServiceClient is the client API for UserCronService service. @@ -1561,6 +1600,7 @@ type UserCronServiceClient interface { ProcessRegionRebuildBatch(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) CompensateMicOpenSessions(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) ExpireManagerUserBlocks(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) + RefreshCPIntimacyLeaderboard(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) } type userCronServiceClient struct { @@ -1611,6 +1651,16 @@ func (c *userCronServiceClient) ExpireManagerUserBlocks(ctx context.Context, in return out, nil } +func (c *userCronServiceClient) RefreshCPIntimacyLeaderboard(ctx context.Context, in *CronBatchRequest, opts ...grpc.CallOption) (*CronBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CronBatchResponse) + err := c.cc.Invoke(ctx, UserCronService_RefreshCPIntimacyLeaderboard_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // UserCronServiceServer is the server API for UserCronService service. // All implementations must embed UnimplementedUserCronServiceServer // for forward compatibility. @@ -1621,6 +1671,7 @@ type UserCronServiceServer interface { ProcessRegionRebuildBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) CompensateMicOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) + RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) mustEmbedUnimplementedUserCronServiceServer() } @@ -1643,6 +1694,9 @@ func (UnimplementedUserCronServiceServer) CompensateMicOpenSessions(context.Cont func (UnimplementedUserCronServiceServer) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { return nil, status.Error(codes.Unimplemented, "method ExpireManagerUserBlocks not implemented") } +func (UnimplementedUserCronServiceServer) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RefreshCPIntimacyLeaderboard not implemented") +} func (UnimplementedUserCronServiceServer) mustEmbedUnimplementedUserCronServiceServer() {} func (UnimplementedUserCronServiceServer) testEmbeddedByValue() {} @@ -1736,6 +1790,24 @@ func _UserCronService_ExpireManagerUserBlocks_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _UserCronService_RefreshCPIntimacyLeaderboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CronBatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserCronServiceServer).RefreshCPIntimacyLeaderboard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserCronService_RefreshCPIntimacyLeaderboard_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserCronServiceServer).RefreshCPIntimacyLeaderboard(ctx, req.(*CronBatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + // UserCronService_ServiceDesc is the grpc.ServiceDesc for UserCronService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1759,6 +1831,10 @@ var UserCronService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ExpireManagerUserBlocks", Handler: _UserCronService_ExpireManagerUserBlocks_Handler, }, + { + MethodName: "RefreshCPIntimacyLeaderboard", + Handler: _UserCronService_RefreshCPIntimacyLeaderboard_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/user/v1/user.proto", diff --git a/docs/flutter对接/任务系统Flutter对接.md b/docs/flutter对接/任务系统Flutter对接.md new file mode 100644 index 00000000..e2860d44 --- /dev/null +++ b/docs/flutter对接/任务系统Flutter对接.md @@ -0,0 +1,118 @@ +# 任务系统 Flutter 对接 + +## 获取任务页 + +地址:`GET /api/v1/tasks/tabs` + +参数:无。需要登录态。 + +返回值: + +```json +{ + "sections": [ + { + "section": "daily", + "server_time_ms": 1781097600000, + "next_refresh_at_ms": 1781136000000, + "items": [ + { + "task_id": "task_xxx", + "task_type": "daily", + "category": "gift", + "audience_type": "all", + "metric_type": "gift_spend_coin", + "title": "普通礼物消耗金币", + "description": "", + "icon_key": "gift", + "icon_url": "", + "action_type": "gift_panel", + "action_param": "", + "action_payload": {}, + "action_payload_json": "{}", + "dimension_filter_json": "{}", + "target_value": 10000, + "target_unit": "coin", + "progress_value": 1200, + "reward_coin_amount": 200, + "status": "in_progress", + "claimable": false, + "task_day": "2026-06-11", + "server_time_ms": 1781097600000, + "next_refresh_at_ms": 1781136000000, + "sort_order": 10, + "version": 1 + } + ] + }, + { + "section": "exclusive", + "server_time_ms": 1781097600000, + "next_refresh_at_ms": 1781136000000, + "items": [] + } + ], + "server_time_ms": 1781097600000, + "next_refresh_at_ms": 1781136000000 +} +``` + +说明: + +- `section=daily` 是每日任务,按服务端 UTC 00:00 刷新。 +- `section=exclusive` 是专属/新手任务,`task_day` 固定为 `lifetime`。 +- `claimable=true` 显示领取按钮。 +- `status=claimed` 显示已领取。 +- 其他状态显示去完成按钮,按 `action_type` 跳转。 + +## 领取任务奖励 + +地址:`POST /api/v1/tasks/claim` + +参数: + +```json +{ + "task_id": "task_xxx", + "task_type": "daily", + "task_day": "2026-06-11", + "command_id": "app-generated-uuid" +} +``` + +返回值: + +```json +{ + "claim_id": "tclaim_xxx", + "task_id": "task_xxx", + "task_type": "daily", + "task_day": "2026-06-11", + "reward_coin_amount": 200, + "status": "granted", + "wallet_transaction_id": "wtx_xxx", + "granted_at_ms": 1781097600000, + "claimed": true +} +``` + +说明: + +- `command_id` 由 App 每次点击领取时生成;同一次重试继续使用同一个 `command_id`。 +- 每日任务只能领取当天 `task_day`。 +- 专属任务领取时传列表里的 `task_day`,通常是 `lifetime`。 + +## 跳转类型 + +| action_type | action_param | +| --- | --- | +| `none` | 不跳转 | +| `room_random` | 随机进入一个有人的房间;没有有人的房间时进入随机房间 | +| `room_random_game` | `game_id`,进入房间后打开指定游戏 | +| `wallet` | 打开钱包 | +| `gift_panel` | 进入房间后打开礼物面板 | +| `gift_panel_specific` | `gift_id`,进入房间后打开礼物面板并定位指定礼物 | + +## 相关 IM + +无。任务进度来自服务端事实事件,领取结果以接口返回和 `wallet_transaction_id` 为准。 diff --git a/docs/flutter对接/房内猜拳Flutter对接.md b/docs/flutter对接/房内猜拳Flutter对接.md new file mode 100644 index 00000000..bb21a0ef --- /dev/null +++ b/docs/flutter对接/房内猜拳Flutter对接.md @@ -0,0 +1,322 @@ +# 房内猜拳 Flutter 对接 + +本文只记录房内猜拳 App 端 HTTP 接口和腾讯 IM 自定义消息字段。独立猜拳本阶段不接入。 + +## 1. 基本约定 + +- 所有 HTTP 地址都有 `/api/v1` 前缀。 +- 所有接口都需要 `Authorization: Bearer `。 +- 成功响应统一是 gateway envelope: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_xxx", + "data": {} +} +``` + +- 所有时间字段都是 Unix epoch milliseconds。 +- Flutter 模型里用户 ID、挑战 ID、礼物 ID 建议统一按 `String` 保存。 +- HTTP 返回的发起人和应战人资料在 `challenge.initiator.user`、`challenge.challenger.user`。 +- IM 里的发起人和应战人也必须带 `nickname` 和 `avatar`;客户端不要为了基础展示再查用户资料。 + +## 2. 通用挑战单字段 + +`challenge` 字段在列表、详情、发起、应战接口中结构一致。 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `challenge_id` | string | 挑战单 ID。 | +| `room_id` | string | 房间 ID。 | +| `region_id` | number | 用户语区 ID。 | +| `status` | string | `pending`、`matched`、`revealing`、`finished`、`timeout`、`settlement_failed`、`cancelled`。 | +| `stake_gift_id` | string | 下注礼物 ID,Flutter 优先用这个字段。 | +| `stake_gift_id_number` | number | 下注礼物 ID 数字值。 | +| `stake_coin` | number | 本局礼物对应金币价值。 | +| `initiator` | object | 发起人。 | +| `challenger` | object | 应战人;`pending` 时可能为空用户。 | +| `winner_user_id` | string | 胜者用户 ID;平局或未结算为空或 `0`。 | +| `winner_user_id_number` | number | 胜者用户 ID 数字值。 | +| `settlement_status` | string | 结算状态,例如 `pending`、`settled`、`failed`。 | +| `failure_reason` | string | 结算失败原因。 | +| `timeout_at_ms` | number | 无人应战自动过期时间。 | +| `reveal_at_ms` | number | 揭晓时间。 | +| `created_at_ms` | number | 创建时间。 | +| `matched_at_ms` | number | 应战成功时间。 | +| `settled_at_ms` | number | 结算时间。 | +| `updated_at_ms` | number | 更新时间。 | + +`initiator` 和 `challenger` 字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `user_id` | string | 用户 ID,Flutter 优先用这个字段。 | +| `user_id_number` | number | 用户 ID 数字值。 | +| `gesture` | string | `rock`、`paper`、`scissors`。 | +| `result` | string | `win`、`lose`、`draw`;未结算时为空。 | +| `balance_after` | number | 结算后的金币余额。 | +| `joined_at_ms` | number | 参与时间。 | +| `user.user_id` | string | 展示用户 ID。 | +| `user.display_user_id` | string | 短号或展示号。 | +| `user.nickname` | string | 昵称;发起人和应战人都要有。 | +| `user.avatar` | string | 头像;发起人和应战人都要有。 | + +## 3. 查询配置 + +地址: + +```http +GET /api/v1/games/room-rps/config +``` + +参数:无。 + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `config.status` | string | `active` 或 `disabled`。 | +| `config.challenge_timeout_ms` | number | 发起后无人应战的超时时间,默认 10 分钟。 | +| `config.reveal_countdown_ms` | number | 双方出拳后的揭晓倒计时,默认 3 秒。 | +| `config.stake_gifts` | array | 礼物下注档位,固定 4 档。 | +| `server_time_ms` | number | 服务端时间。 | + +`stake_gifts` 字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `gift_id` | string | 礼物 ID,Flutter 优先用这个字段提交。 | +| `gift_id_number` | number | 礼物 ID 数字值。 | +| `gift_name` | string | 礼物名称。 | +| `gift_icon_url` | string | 礼物图标。 | +| `gift_price_coin` | number | 礼物价格金币。 | +| `enabled` | bool | 是否启用。 | +| `sort_order` | number | 排序。 | + +相关 IM:无。 + +## 4. 查询房间挑战单 + +地址: + +```http +GET /api/v1/games/room-rps/challenges +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `room_id` | query | 是 | 房间 ID。 | +| `status` | query | 否 | 挑战状态。 | +| `page_size` | query | 否 | 页大小,默认 20。 | +| `cursor` | query | 否 | 下一页游标。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `challenges` | array | 挑战单列表,结构见通用挑战单字段。 | +| `next_cursor` | string | 下一页游标。 | +| `page_size` | number | 本次页大小。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。客户端漏掉 IM 后,用这个接口补拉当前房间挑战单。 + +## 5. 发起挑战 + +地址: + +```http +POST /api/v1/games/room-rps/challenges/create +Content-Type: application/json +``` + +参数: + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `room_id` | string | 是 | 房间 ID。 | +| `gift_id` | number | 是 | 后台配置的礼物档位 ID。 | +| `gesture` | string | 是 | `rock`、`paper`、`scissors`。 | + +请求示例: + +```json +{ + "room_id": "room_1001", + "gift_id": 10001, + "gesture": "rock" +} +``` + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `challenge` | object | 挑战单,结构见通用挑战单字段。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:`room_rps_challenge_created`。 + +## 6. 应战 + +地址: + +```http +POST /api/v1/games/room-rps/challenges/{challenge_id}/accept +Content-Type: application/json +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `challenge_id` | path | 是 | 挑战单 ID。 | +| `gesture` | body | 是 | `rock`、`paper`、`scissors`。 | + +请求示例: + +```json +{ + "gesture": "paper" +} +``` + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `challenge` | object | 应战后的挑战单,结构见通用挑战单字段。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM: + +- `room_rps_challenge_accepted` +- `room_rps_reveal_countdown` +- `room_rps_finished` + +## 7. 查询挑战详情 + +地址: + +```http +GET /api/v1/games/room-rps/challenges/{challenge_id} +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `challenge_id` | path | 是 | 挑战单 ID。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `challenge` | object | 挑战单,结构见通用挑战单字段。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。客户端收到 IM 后,可以用这个接口刷新挑战事实。 + +## 8. 腾讯 IM 字段 + +房间群消息使用腾讯 IM `TIMCustomElem`,`data` 是 JSON 字符串。Flutter 只需要解析下面字段。 + +通用字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `event_id` | string | IM 事件 ID,客户端用它去重。 | +| `event_type` | string | 事件类型。 | +| `room_id` | string | 房间 ID。 | +| `challenge_id` | string | 挑战单 ID。 | +| `server_time_ms` | number | 服务端时间。 | +| `challenge` | object | 挑战单快照,结构见通用挑战单字段。 | + +`challenge.initiator.user` 字段: + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `user_id` | string | 是 | 发起人用户 ID。 | +| `display_user_id` | string | 否 | 发起人展示号。 | +| `nickname` | string | 是 | 发起人昵称。 | +| `avatar` | string | 是 | 发起人头像。 | + +`challenge.challenger.user` 字段: + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `user_id` | string | 应战后是 | 应战人用户 ID。 | +| `display_user_id` | string | 否 | 应战人展示号。 | +| `nickname` | string | 应战后是 | 应战人昵称。 | +| `avatar` | string | 应战后是 | 应战人头像。 | + +IM 事件: + +| `event_type` | 触发时机 | Flutter 处理 | +| --- | --- | --- | +| `room_rps_challenge_created` | 用户发起挑战后 | 展示 `[PK]` 入口、发起人头像昵称、礼物和倒计时。此时 `challenger` 可以为空。 | +| `room_rps_challenge_accepted` | 有用户应战后 | 锁定挑战入口,展示双方头像昵称。 | +| `room_rps_reveal_countdown` | 双方出拳后 | 播放 3 秒揭晓动画,展示双方头像昵称。 | +| `room_rps_finished` | 结算完成或平局退款后 | 展示胜负、双方头像昵称、余额或失败提示。 | + +IM 示例: + +```json +{ + "event_id": "room_rps_finished:rps_10001", + "event_type": "room_rps_finished", + "room_id": "room_1001", + "challenge_id": "rps_10001", + "server_time_ms": 1780000003000, + "challenge": { + "challenge_id": "rps_10001", + "room_id": "room_1001", + "status": "finished", + "stake_gift_id": "10001", + "stake_gift_id_number": 10001, + "stake_coin": 100, + "initiator": { + "user_id": "10001", + "user_id_number": 10001, + "gesture": "rock", + "result": "lose", + "balance_after": 900, + "joined_at_ms": 1780000000000, + "user": { + "user_id": "10001", + "display_user_id": "880001", + "nickname": "Alice", + "avatar": "https://cdn.example/alice.png" + } + }, + "challenger": { + "user_id": "10002", + "user_id_number": 10002, + "gesture": "paper", + "result": "win", + "balance_after": 1200, + "joined_at_ms": 1780000001000, + "user": { + "user_id": "10002", + "display_user_id": "880002", + "nickname": "Bob", + "avatar": "https://cdn.example/bob.png" + } + }, + "winner_user_id": "10002", + "winner_user_id_number": 10002, + "settlement_status": "settled", + "timeout_at_ms": 1780000600000, + "reveal_at_ms": 1780000003000, + "created_at_ms": 1780000000000, + "matched_at_ms": 1780000001000, + "settled_at_ms": 1780000003000, + "updated_at_ms": 1780000003000 + } +} +``` + diff --git a/docs/全站机器人产品文档.md b/docs/全站机器人产品文档.md new file mode 100644 index 00000000..965ea608 --- /dev/null +++ b/docs/全站机器人产品文档.md @@ -0,0 +1,80 @@ +# 全站机器人产品文档 + +## 目标 + +全站机器人用于自研游戏自动补位。后台批量创建机器人资料号后,游戏服务可以在玩家等待超时后从机器人池选择一个可用机器人参与对局。 + +## 后台入口 + +- 菜单位置:游戏管理 / 全站机器人 +- 菜单编码:`game-robots` +- 页面地址:`/games/robots` +- 权限:查看使用 `game:view`,创建使用 `game:create`,启停使用 `game:update`,删除使用 `game:delete` 或 `game:update` + +## 机器人规则 + +- 机器人以 App 用户资料形式保存,有 `user_id`、短 ID、昵称、头像、国家、语言等基础资料。 +- 机器人注册来源固定为 `game_robot`。 +- 机器人不是自然新增用户,不进入新增用户统计、注册 cohort、留存统计。 +- 机器人不能通过 App 登录接口登录。 +- 机器人创建时不生成 access token、refresh token,也不写登录成功审计。 +- 机器人不导入腾讯 IM 登录账号;它只用于游戏资料展示和游戏服务补位。 +- 删除机器人只会从全站机器人池移除,用户资料和历史对局记录保留。 + +## 创建流程 + +1. 运营在后台选择创建数量和昵称语言。 +2. admin-server 下载并转存头像。 +3. admin-server 调 user-service 创建 `source=game_robot` 的资料号。 +4. user-service 写入用户资料、短 ID 和密码身份,但不创建登录 session。 +5. admin-server 调 game-service 把这些 user_id 注册到机器人池。 +6. game-service 后续只从启用状态的机器人中选择补位。 + +## 统计口径 + +- 实时统计:`source=game_robot` 不写 `UserRegistered` outbox。 +- 补数统计:统计补数按 `users.created_at_ms` 扫描时排除 `source=game_robot`。 +- 后台创建 100 个全站机器人,不会增加“新增用户数”,也不会进入留存 cohort。 + +## 登录限制 + +- 密码登录:即使知道短 ID 和密码,也会被 user-service 拦截。 +- 三方登录:机器人没有三方身份绑定,不能通过三方接口登录成机器人。 +- refresh token:新机器人不会生成 refresh token;历史上如果存在机器人 session,续期时也会被用户登录判断拦截。 +- access token 重签:会复用同一登录判断,机器人不能续签。 + +## 后台接口 + +### 列表 + +- 地址:`GET /admin/game/robots` +- 参数:`gameId`、`status`、`pageSize`、`cursor` +- 返回值:机器人列表、下一页游标、服务器时间 +- 相关 IM:无 + +### 批量创建 + +- 地址:`POST /admin/game/robots/generate` +- 参数:`count`、`nicknameLanguage`、`gender`、`country`、`avatarUrls` +- 返回值:创建数量、机器人列表 +- 相关 IM:无;机器人不导入 IM 登录账号 + +### 启停 + +- 地址:`PATCH /admin/game/robots/{user_id}/status` +- 参数:`status`,可传 `active` 或 `disabled`;可选 `gameId` +- 返回值:机器人当前状态 +- 相关 IM:无 + +### 删除 + +- 地址:`DELETE /admin/game/robots/{user_id}` +- 参数:可选 `gameId` +- 返回值:`deleted` +- 相关 IM:无 + +## 游戏补位规则 + +- 自研游戏配置里开启机器人补位后,玩家等待超过配置时间,game-service 会选择启用机器人加入对局。 +- 机器人对局资金由游戏奖池结算,不从机器人钱包扣款。 +- 机器人禁用后不会再被新对局选中。 diff --git a/docs/房内猜拳接口文档.md b/docs/房内猜拳接口文档.md new file mode 100644 index 00000000..d0ff83ea --- /dev/null +++ b/docs/房内猜拳接口文档.md @@ -0,0 +1,83 @@ +# 房内猜拳接口文档 + +本文只记录房内猜拳 App 端接口。独立猜拳本阶段不接入。Flutter 详细字段看 [房内猜拳 Flutter 对接](flutter对接/房内猜拳Flutter对接.md)。 + +## 查询配置 + +- 地址:`GET /api/v1/games/room-rps/config` +- 参数:无 +- 返回值: + - `config.status`:`active` / `disabled` + - `config.challenge_timeout_ms`:发起后无人应战的超时时间,默认 10 分钟 + - `config.reveal_countdown_ms`:双方出拳后的揭晓倒计时,默认 3 秒 + - `config.stake_gifts`:礼物下注档位 +- 相关 IM:无 + +## 查询房间挑战单 + +- 地址:`GET /api/v1/games/room-rps/challenges` +- 参数: + - `room_id`:房间 ID,必填 + - `status`:状态,可选 + - `page_size`:页大小,默认 20 + - `cursor`:下一页游标 +- 返回值: + - `challenges`:挑战单列表 + - `challenges[].initiator.user.nickname`:发起人昵称 + - `challenges[].initiator.user.avatar`:发起人头像 + - `challenges[].challenger.user.nickname`:应战人昵称;未应战时为空 + - `challenges[].challenger.user.avatar`:应战人头像;未应战时为空 + - `next_cursor`:下一页游标 + - `page_size`:本次页大小 +- 相关 IM:无。客户端漏掉 IM 后可以用这个接口补拉当前房间挑战单。 + +## 发起挑战 + +- 地址:`POST /api/v1/games/room-rps/challenges/create` +- 参数: + - `room_id`:房间 ID + - `gift_id`:后台配置的礼物档位 ID + - `gesture`:`rock` / `paper` / `scissors` +- 返回值: + - `challenge`:挑战单 +- 相关 IM: + - `room_rps_challenge_created`:服务端向房间群发 `[PK]` 消息,带 `challenge_id`、发起人头像昵称、礼物档位和超时时间。 + +## 应战 + +- 地址:`POST /api/v1/games/room-rps/challenges/:challenge_id/accept` +- 参数: + - `challenge_id`:挑战单 ID + - `gesture`:`rock` / `paper` / `scissors` +- 返回值: + - `challenge`:应战后的挑战单 +- 相关 IM: + - `room_rps_challenge_accepted`:服务端通知房间挑战已被锁定,带发起人和应战人头像昵称。 + - `room_rps_reveal_countdown`:服务端通知双方展示 3 秒倒计时,带发起人和应战人头像昵称。 + - `room_rps_finished`:服务端通知胜负、退款或礼物结算结果,带发起人和应战人头像昵称。 + +## 查询挑战详情 + +- 地址:`GET /api/v1/games/room-rps/challenges/:challenge_id` +- 参数: + - `challenge_id`:挑战单 ID +- 返回值: + - `challenge.status`:`pending` / `matched` / `revealing` / `finished` / `timeout` / `settlement_failed` / `cancelled` + - `challenge.initiator`:发起人 + - `challenge.initiator.user.nickname`:发起人昵称 + - `challenge.initiator.user.avatar`:发起人头像 + - `challenge.challenger`:应战人 + - `challenge.challenger.user.nickname`:应战人昵称;未应战时为空 + - `challenge.challenger.user.avatar`:应战人头像;未应战时为空 + - `challenge.winner_user_id`:胜者,平局为空或 0 + - `challenge.settlement_status`:结算状态 +- 相关 IM:无。客户端收到 IM 后可以用这个接口刷新挑战事实。 + +## 相关 IM 字段 + +- 所有房内猜拳 IM 都是腾讯 IM `TIMCustomElem`,`data` 是 JSON 字符串。 +- 通用字段:`event_id`、`event_type`、`room_id`、`challenge_id`、`server_time_ms`、`challenge`。 +- `challenge.initiator.user.nickname`:发起人昵称,必填。 +- `challenge.initiator.user.avatar`:发起人头像,必填。 +- `challenge.challenger.user.nickname`:应战人昵称;`room_rps_challenge_accepted`、`room_rps_reveal_countdown`、`room_rps_finished` 必填。 +- `challenge.challenger.user.avatar`:应战人头像;`room_rps_challenge_accepted`、`room_rps_reveal_countdown`、`room_rps_finished` 必填。 diff --git a/docs/房内猜拳本地真实IM测试.md b/docs/房内猜拳本地真实IM测试.md new file mode 100644 index 00000000..4840d5e8 --- /dev/null +++ b/docs/房内猜拳本地真实IM测试.md @@ -0,0 +1,47 @@ +# 房内猜拳本地真实 IM 测试 + +测试文件:`tests/smoke/room_rps_real_im_flow_test.go` + +## 作用 + +- 用本地 gateway 真实 HTTP 接口跑房内猜拳流程。 +- 通过 `/api/v1/auth/account/quick-create` 自动创建发起人和应战人。 +- 用真实腾讯 IM REST 确保房间群、导入两个账号、发送一条 `TIMCustomElem` 探针消息。 +- 不使用 fake client,不伪造 access token,不伪造 IM 成功。 + +## 前置 + +- 本地 gateway 地址默认:`http://127.0.0.1:13000/api/v1` +- 本地 gateway、game-service、user-service 已启动。 +- game-service 启动时需要带 `TENCENT_IM_SDK_APP_ID`、`TENCENT_IM_SECRET_KEY`、`TENCENT_IM_ADMIN_IDENTIFIER`,这样创建和应战接口会真实投递 IM。 +- 不传 `ROOM_RPS_ROOM_ID` 时,测试会自动创建 `room-rps-real-im-时间戳` 临时群。 +- 不传 `ROOM_RPS_GIFT_ID` 时,使用默认已开启礼物 `10001`。 +- 腾讯 IM 配置必须和本地 gateway 使用同一个 SDKAppID。 + +## 命令 + +```bash +ROOM_RPS_REAL_IM_FLOW_TEST=1 \ +ROOM_RPS_BASE_URL=http://127.0.0.1:13000/api/v1 \ +ROOM_RPS_APP_CODE=lalu \ +TENCENT_IM_SDK_APP_ID=1400000000 \ +TENCENT_IM_SECRET_KEY='' \ +TENCENT_IM_ADMIN_IDENTIFIER=administrator \ +TENCENT_IM_ENDPOINT=adminapisgp.im.qcloud.com \ +go test ./tests/smoke -run TestRoomRPSLocalHTTPFlowWithRealTencentIM -count=1 -v +``` + +## 返回判断 + +- 通过:本地 HTTP 流程完成,真实腾讯 IM 探针消息发送成功,game-service 创建和应战 IM 投递成功,发起人和应战人返回头像昵称。 +- 失败并提示腾讯 IM 错误:先检查 SDKAppID、SecretKey、管理员账号、endpoint 和 GroupID。 + +## 清理 + +默认会清理自动创建的 `room-rps-real-im-` 临时群。 + +如果传入真实房间群,不要开启清理;可以显式加: + +```bash +ROOM_RPS_CLEANUP_IM_GROUP=0 +``` diff --git a/server/admin/docs/猜拳后台管理技术文档.md b/server/admin/docs/猜拳后台管理技术文档.md new file mode 100644 index 00000000..33f81a2e --- /dev/null +++ b/server/admin/docs/猜拳后台管理技术文档.md @@ -0,0 +1,87 @@ +# 猜拳后台管理技术文档 + +本文只记录本阶段已接入的房内猜拳后台接口。独立猜拳本阶段不接入后台。 + +## 房内猜拳配置 + +### 查询配置 + +- 地址:`GET /admin/game/room-rps/config` +- 权限:`game:view` +- 参数:无 +- 返回值: + - `config.status`:`active` / `disabled` + - `config.challengeTimeoutMs`:发起后无人应战的超时时间,毫秒 + - `config.revealCountdownMs`:双方出拳后的揭晓倒计时,毫秒 + - `config.stakeGifts`:4 个礼物下注档位 +- 相关 IM:无 + +### 更新配置 + +- 地址:`PATCH /admin/game/room-rps/config` +- 权限:`game:update` +- 参数: + - `status`:`active` / `disabled` + - `challengeTimeoutMs`:默认 `600000` + - `revealCountdownMs`:默认 `3000` + - `stakeGifts`:必须正好 4 档 + - `stakeGifts[].giftId`:礼物 ID + - `stakeGifts[].enabled`:是否启用 + - `stakeGifts[].sortOrder`:排序 +- 返回值:更新后的 `config` +- 相关 IM:无 + +## 房内猜拳订单 + +### 订单列表 + +- 地址:`GET /admin/game/room-rps/challenges` +- 权限:`game:view` +- 参数: + - `roomId` / `room_id`:房间 ID,可选 + - `status`:订单状态,可选 + - `initiatorUserId` / `initiator_user_id`:发起人,可选 + - `challengerUserId` / `challenger_user_id`:应战人,可选 + - `startTimeMs` / `start_time_ms`:开始时间,可选 + - `endTimeMs` / `end_time_ms`:结束时间,可选 + - `pageSize` / `page_size`:默认 50 + - `cursor`:下一页游标 +- 返回值: + - `items`:订单列表 + - `nextCursor`:下一页游标 + - `pageSize`:本次页大小 +- 相关 IM:无 + +### 订单详情 + +- 地址:`GET /admin/game/room-rps/challenges/:challenge_id` +- 权限:`game:view` +- 参数: + - `challenge_id`:挑战单 ID +- 返回值: + - `challenge.status`:`pending` / `matched` / `revealing` / `finished` / `timeout` / `settlement_failed` / `cancelled` + - `challenge.initiator`:发起人手势和结果 + - `challenge.challenger`:应战人手势和结果 + - `challenge.settlementStatus`:结算状态 + - `challenge.failureReason`:结算失败原因 +- 相关 IM:无 + +### 重试结算 + +- 地址:`POST /admin/game/room-rps/challenges/:challenge_id/retry-settlement` +- 权限:`game:update` +- 参数: + - `challenge_id`:挑战单 ID +- 返回值:重试后的 `challenge` +- 限制:只允许 `settlement_failed` +- 相关 IM:成功后由 game-service 按订单状态决定是否补发房间 IM + +### 手动过期 + +- 地址:`POST /admin/game/room-rps/challenges/:challenge_id/expire` +- 权限:`game:update` +- 参数: + - `challenge_id`:挑战单 ID +- 返回值:过期后的 `challenge` +- 限制:只允许未被应战的 `pending` +- 相关 IM:成功后由 game-service 按订单状态决定是否补发房间 IM diff --git a/server/admin/internal/integration/gameclient/client.go b/server/admin/internal/integration/gameclient/client.go index 1119d0a3..245c495f 100644 --- a/server/admin/internal/integration/gameclient/client.go +++ b/server/admin/internal/integration/gameclient/client.go @@ -23,6 +23,12 @@ type Client interface { RegisterDiceRobots(ctx context.Context, req *gamev1.RegisterDiceRobotsRequest) (*gamev1.RegisterDiceRobotsResponse, error) SetDiceRobotStatus(ctx context.Context, req *gamev1.SetDiceRobotStatusRequest) (*gamev1.DiceRobotResponse, error) DeleteDiceRobot(ctx context.Context, req *gamev1.DeleteDiceRobotRequest) (*gamev1.DeleteDiceRobotResponse, error) + GetRoomRPSConfig(ctx context.Context, req *gamev1.GetRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) + UpdateRoomRPSConfig(ctx context.Context, req *gamev1.UpdateRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) + ListRoomRPSChallenges(ctx context.Context, req *gamev1.ListRoomRPSChallengesRequest) (*gamev1.ListRoomRPSChallengesResponse, error) + GetRoomRPSChallenge(ctx context.Context, req *gamev1.GetRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) + RetryRoomRPSSettlement(ctx context.Context, req *gamev1.RetryRoomRPSSettlementRequest) (*gamev1.RoomRPSChallengeResponse, error) + ExpireRoomRPSChallenge(ctx context.Context, req *gamev1.ExpireRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) } type GRPCClient struct { @@ -85,3 +91,33 @@ func (c *GRPCClient) SetDiceRobotStatus(ctx context.Context, req *gamev1.SetDice func (c *GRPCClient) DeleteDiceRobot(ctx context.Context, req *gamev1.DeleteDiceRobotRequest) (*gamev1.DeleteDiceRobotResponse, error) { return c.client.DeleteDiceRobot(ctx, req) } + +func (c *GRPCClient) GetRoomRPSConfig(ctx context.Context, req *gamev1.GetRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) { + // admin-server 不持有房内猜拳配置缓存;每次查询都透传到 game-service,保证后台看到的是运行时同一份配置事实。 + return c.client.GetRoomRPSConfig(ctx, req) +} + +func (c *GRPCClient) UpdateRoomRPSConfig(ctx context.Context, req *gamev1.UpdateRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) { + // 配置更新只通过 game-service 管理 RPC 落地,admin-server 不直接写游戏表,避免与运行时读取路径产生双写分叉。 + return c.client.UpdateRoomRPSConfig(ctx, req) +} + +func (c *GRPCClient) ListRoomRPSChallenges(ctx context.Context, req *gamev1.ListRoomRPSChallengesRequest) (*gamev1.ListRoomRPSChallengesResponse, error) { + // 订单列表筛选条件原样交给 game-service;后台不拼 SQL,也不自行解释 challenge 状态机。 + return c.client.ListRoomRPSChallenges(ctx, req) +} + +func (c *GRPCClient) GetRoomRPSChallenge(ctx context.Context, req *gamev1.GetRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + // 订单详情以 game-service 的单据快照为准,避免 admin-server 用列表缓存展示过期结算状态。 + return c.client.GetRoomRPSChallenge(ctx, req) +} + +func (c *GRPCClient) RetryRoomRPSSettlement(ctx context.Context, req *gamev1.RetryRoomRPSSettlementRequest) (*gamev1.RoomRPSChallengeResponse, error) { + // 重试结算是运维命令,不是后台本地状态修改;是否可重试必须由 game-service 在钱包幂等语义下判断。 + return c.client.RetryRoomRPSSettlement(ctx, req) +} + +func (c *GRPCClient) ExpireRoomRPSChallenge(ctx context.Context, req *gamev1.ExpireRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + // 手动过期同样只发命令给 game-service;pending 校验和并发应战保护必须在订单事务内完成。 + return c.client.ExpireRoomRPSChallenge(ctx, req) +} diff --git a/server/admin/internal/modules/appuser/service.go b/server/admin/internal/modules/appuser/service.go index fac2f31d..91b5895b 100644 --- a/server/admin/internal/modules/appuser/service.go +++ b/server/admin/internal/modules/appuser/service.go @@ -32,21 +32,24 @@ type Service struct { } type AppUser struct { - Avatar string `json:"avatar"` - Coin int64 `json:"coin"` - Country string `json:"country"` - CountryDisplayName string `json:"countryDisplayName"` - CountryName string `json:"countryName"` - CreatedAtMs int64 `json:"createdAtMs"` - DisplayUserID string `json:"displayUserId"` - Gender string `json:"gender"` - LastActiveAtMs int64 `json:"lastActiveAtMs"` - RegionID int64 `json:"regionId"` - RegionName string `json:"regionName"` - Status string `json:"status"` - UpdatedAtMs int64 `json:"updatedAtMs"` - UserID string `json:"userId"` - Username string `json:"username"` + Avatar string `json:"avatar"` + Coin int64 `json:"coin"` + Country string `json:"country"` + CountryDisplayName string `json:"countryDisplayName"` + CountryName string `json:"countryName"` + CreatedAtMs int64 `json:"createdAtMs"` + DefaultDisplayUserID string `json:"defaultDisplayUserId"` + DisplayUserID string `json:"displayUserId"` + Gender string `json:"gender"` + LastActiveAtMs int64 `json:"lastActiveAtMs"` + PrettyDisplayUserID string `json:"prettyDisplayUserId"` + PrettyID string `json:"prettyId"` + RegionID int64 `json:"regionId"` + RegionName string `json:"regionName"` + Status string `json:"status"` + UpdatedAtMs int64 `json:"updatedAtMs"` + UserID string `json:"userId"` + Username string `json:"username"` } type SetUserStatusResult struct { @@ -84,9 +87,11 @@ func (s *Service) ListUsers(ctx context.Context, query listQuery) ([]AppUser, in items, err := s.listUsersSortedByCoin(ctx, query, whereSQL, args) return items, total, err } + nowMs := nowMillis() rows, err := s.userDB.QueryContext(ctx, fmt.Sprintf(` SELECT u.user_id, u.current_display_user_id, + u.default_display_user_id, COALESCE(u.username, ''), COALESCE(u.avatar, ''), COALESCE(u.gender, ''), @@ -123,10 +128,11 @@ func (s *Service) ListUsers(ctx context.Context, query listQuery) ([]AppUser, in COALESCE((SELECT MAX(s.updated_at_ms) FROM auth_sessions s WHERE s.app_code = u.app_code AND s.user_id = u.user_id), 0), COALESCE((SELECT MAX(l.created_at_ms) FROM login_audit l WHERE l.app_code = u.app_code AND l.user_id = u.user_id), 0) ) + %s %s %s LIMIT ? OFFSET ? - `, whereSQL, appUserOrderSQL(query)), append(args, query.PageSize, offset(query.Page, query.PageSize))...) + `, appUserPrettySelectColumns(), whereSQL, appUserOrderSQL(query)), append(appUserSelectArgs(nowMs, args), query.PageSize, offset(query.Page, query.PageSize))...) if err != nil { return nil, 0, err } @@ -143,9 +149,11 @@ func (s *Service) ListUsers(ctx context.Context, query listQuery) ([]AppUser, in } func (s *Service) listUsersSortedByCoin(ctx context.Context, query listQuery, whereSQL string, args []any) ([]AppUser, error) { + nowMs := nowMillis() rows, err := s.userDB.QueryContext(ctx, fmt.Sprintf(` SELECT u.user_id, u.current_display_user_id, + u.default_display_user_id, COALESCE(u.username, ''), COALESCE(u.avatar, ''), COALESCE(u.gender, ''), @@ -182,8 +190,9 @@ func (s *Service) listUsersSortedByCoin(ctx context.Context, query listQuery, wh COALESCE((SELECT MAX(s.updated_at_ms) FROM auth_sessions s WHERE s.app_code = u.app_code AND s.user_id = u.user_id), 0), COALESCE((SELECT MAX(l.created_at_ms) FROM login_audit l WHERE l.app_code = u.app_code AND l.user_id = u.user_id), 0) ) + %s %s - `, whereSQL), args...) + `, appUserPrettySelectColumns(), whereSQL), appUserSelectArgs(nowMs, args)...) if err != nil { return nil, err } @@ -209,6 +218,7 @@ func scanAppUserRows(rows *sql.Rows, capacity int) ([]AppUser, []int64, error) { if err := rows.Scan( &userID, &item.DisplayUserID, + &item.DefaultDisplayUserID, &item.Username, &item.Avatar, &item.Gender, @@ -221,6 +231,8 @@ func scanAppUserRows(rows *sql.Rows, capacity int) ([]AppUser, []int64, error) { &item.CreatedAtMs, &item.UpdatedAtMs, &item.LastActiveAtMs, + &item.PrettyDisplayUserID, + &item.PrettyID, ); err != nil { return nil, nil, err } @@ -241,9 +253,11 @@ func (s *Service) GetUser(ctx context.Context, userID int64) (AppUser, error) { var item AppUser var scannedUserID int64 appCode := appctx.FromContext(ctx) + nowMs := nowMillis() err := s.userDB.QueryRowContext(ctx, ` SELECT u.user_id, u.current_display_user_id, + u.default_display_user_id, COALESCE(u.username, ''), COALESCE(u.avatar, ''), COALESCE(u.gender, ''), @@ -280,11 +294,13 @@ func (s *Service) GetUser(ctx context.Context, userID int64) (AppUser, error) { COALESCE((SELECT MAX(s.updated_at_ms) FROM auth_sessions s WHERE s.app_code = u.app_code AND s.user_id = u.user_id), 0), COALESCE((SELECT MAX(l.created_at_ms) FROM login_audit l WHERE l.app_code = u.app_code AND l.user_id = u.user_id), 0) ) + `+appUserPrettySelectColumns()+` FROM users u WHERE u.app_code = ? AND u.user_id = ? - `, appCode, userID).Scan( + `, nowMs, nowMs, appCode, userID).Scan( &scannedUserID, &item.DisplayUserID, + &item.DefaultDisplayUserID, &item.Username, &item.Avatar, &item.Gender, @@ -297,6 +313,8 @@ func (s *Service) GetUser(ctx context.Context, userID int64) (AppUser, error) { &item.CreatedAtMs, &item.UpdatedAtMs, &item.LastActiveAtMs, + &item.PrettyDisplayUserID, + &item.PrettyID, ) if errors.Is(err, sql.ErrNoRows) { return AppUser{}, ErrNotFound @@ -619,6 +637,43 @@ func appUserValidRegionExistsSQL(userAlias string) string { )`, userAlias) } +func appUserPrettySelectColumns() string { + return `, + COALESCE(( + SELECT lease.display_user_id + FROM pretty_display_user_id_leases lease + WHERE lease.app_code = u.app_code + AND lease.user_id = u.user_id + AND lease.status = 'active' + AND (COALESCE(lease.expires_at_ms, 0) = 0 OR lease.expires_at_ms > ?) + ORDER BY lease.created_at_ms DESC, lease.lease_id DESC + LIMIT 1 + ), ''), + COALESCE(( + SELECT pdi.pretty_id + FROM pretty_display_user_id_leases lease + JOIN pretty_display_ids pdi + ON pdi.app_code = lease.app_code + AND pdi.assigned_lease_id = lease.lease_id + AND pdi.status = 'assigned' + WHERE lease.app_code = u.app_code + AND lease.user_id = u.user_id + AND lease.status = 'active' + AND (COALESCE(lease.expires_at_ms, 0) = 0 OR lease.expires_at_ms > ?) + ORDER BY lease.created_at_ms DESC, lease.lease_id DESC + LIMIT 1 + ), '')` +} + +func appUserSelectArgs(nowMs int64, whereArgs []any) []any { + // 用户资料页展示“原始短号 + 靓号”时,靓号以租约表为准读取,不能只依赖 users.current_display_user_id。 + // 本地 initdb 或历史修复脚本可能只重放 users 快照;active lease 和 pretty_display_ids 才是当前靓号归属的审计来源。 + args := make([]any, 0, len(whereArgs)+2) + args = append(args, nowMs, nowMs) + args = append(args, whereArgs...) + return args +} + func normalizeAppUserSortBy(value string) string { switch strings.ToLower(strings.TrimSpace(value)) { case "coin", "coins": diff --git a/server/admin/internal/modules/dailytask/handler.go b/server/admin/internal/modules/dailytask/handler.go index bcbc2aa8..7075e816 100644 --- a/server/admin/internal/modules/dailytask/handler.go +++ b/server/admin/internal/modules/dailytask/handler.go @@ -24,18 +24,25 @@ func New(activity activityclient.Client, audit shared.OperationLogger) *Handler } type taskRequest struct { - TaskType string `json:"task_type"` - Category string `json:"category"` - MetricType string `json:"metric_type"` - Title string `json:"title"` - Description string `json:"description"` - TargetValue int64 `json:"target_value"` - TargetUnit string `json:"target_unit"` - RewardCoinAmount int64 `json:"reward_coin_amount"` - Status string `json:"status"` - SortOrder int32 `json:"sort_order"` - EffectiveFromMS int64 `json:"effective_from_ms"` - EffectiveToMS int64 `json:"effective_to_ms"` + TaskType string `json:"task_type"` + Category string `json:"category"` + MetricType string `json:"metric_type"` + Title string `json:"title"` + Description string `json:"description"` + AudienceType string `json:"audience_type"` + IconKey string `json:"icon_key"` + IconURL string `json:"icon_url"` + ActionType string `json:"action_type"` + ActionParam string `json:"action_param"` + ActionPayloadJSON string `json:"action_payload_json"` + DimensionFilterJSON string `json:"dimension_filter_json"` + TargetValue int64 `json:"target_value"` + TargetUnit string `json:"target_unit"` + RewardCoinAmount int64 `json:"reward_coin_amount"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + EffectiveFromMS int64 `json:"effective_from_ms"` + EffectiveToMS int64 `json:"effective_to_ms"` } type statusRequest struct { @@ -43,24 +50,31 @@ type statusRequest struct { } type taskDTO struct { - TaskID string `json:"task_id"` - TaskType string `json:"task_type"` - Category string `json:"category"` - MetricType string `json:"metric_type"` - Title string `json:"title"` - Description string `json:"description"` - TargetValue int64 `json:"target_value"` - TargetUnit string `json:"target_unit"` - RewardCoinAmount int64 `json:"reward_coin_amount"` - Status string `json:"status"` - SortOrder int32 `json:"sort_order"` - Version int64 `json:"version"` - EffectiveFromMS int64 `json:"effective_from_ms"` - EffectiveToMS int64 `json:"effective_to_ms"` - CreatedByAdminID int64 `json:"created_by_admin_id"` - UpdatedByAdminID int64 `json:"updated_by_admin_id"` - CreatedAtMS int64 `json:"created_at_ms"` - UpdatedAtMS int64 `json:"updated_at_ms"` + TaskID string `json:"task_id"` + TaskType string `json:"task_type"` + Category string `json:"category"` + MetricType string `json:"metric_type"` + Title string `json:"title"` + Description string `json:"description"` + AudienceType string `json:"audience_type"` + IconKey string `json:"icon_key"` + IconURL string `json:"icon_url"` + ActionType string `json:"action_type"` + ActionParam string `json:"action_param"` + ActionPayloadJSON string `json:"action_payload_json"` + DimensionFilterJSON string `json:"dimension_filter_json"` + TargetValue int64 `json:"target_value"` + TargetUnit string `json:"target_unit"` + RewardCoinAmount int64 `json:"reward_coin_amount"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + Version int64 `json:"version"` + EffectiveFromMS int64 `json:"effective_from_ms"` + EffectiveToMS int64 `json:"effective_to_ms"` + CreatedByAdminID int64 `json:"created_by_admin_id"` + UpdatedByAdminID int64 `json:"updated_by_admin_id"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` } // ListTaskDefinitions 返回活动管理 > 每日任务页的 daily/exclusive 配置列表。 @@ -153,22 +167,31 @@ func (h *Handler) SetTaskDefinitionStatus(c *gin.Context) { } func (r taskRequest) toProto(c *gin.Context, taskID string, meta *activityv1.RequestMeta) *activityv1.UpsertTaskDefinitionRequest { + // admin-server 只做字段裁剪和身份注入,任务口径校验、JSON 对象校验和版本生成统一放在 activity-service。 + // 图标和跳转属于展示字段,直接透传给任务定义,App 任务页会按 icon_key/icon_url/action_* 渲染。 return &activityv1.UpsertTaskDefinitionRequest{ - Meta: meta, - TaskId: taskID, - TaskType: strings.TrimSpace(r.TaskType), - Category: strings.TrimSpace(r.Category), - MetricType: strings.TrimSpace(r.MetricType), - Title: strings.TrimSpace(r.Title), - Description: strings.TrimSpace(r.Description), - TargetValue: r.TargetValue, - TargetUnit: strings.TrimSpace(r.TargetUnit), - RewardCoinAmount: r.RewardCoinAmount, - Status: strings.TrimSpace(r.Status), - SortOrder: r.SortOrder, - EffectiveFromMs: r.EffectiveFromMS, - EffectiveToMs: r.EffectiveToMS, - OperatorAdminId: int64(middleware.CurrentUserID(c)), + Meta: meta, + TaskId: taskID, + TaskType: strings.TrimSpace(r.TaskType), + Category: strings.TrimSpace(r.Category), + MetricType: strings.TrimSpace(r.MetricType), + Title: strings.TrimSpace(r.Title), + Description: strings.TrimSpace(r.Description), + AudienceType: strings.TrimSpace(r.AudienceType), + IconKey: strings.TrimSpace(r.IconKey), + IconUrl: strings.TrimSpace(r.IconURL), + ActionType: strings.TrimSpace(r.ActionType), + ActionParam: strings.TrimSpace(r.ActionParam), + ActionPayloadJson: strings.TrimSpace(r.ActionPayloadJSON), + DimensionFilterJson: strings.TrimSpace(r.DimensionFilterJSON), + TargetValue: r.TargetValue, + TargetUnit: strings.TrimSpace(r.TargetUnit), + RewardCoinAmount: r.RewardCoinAmount, + Status: strings.TrimSpace(r.Status), + SortOrder: r.SortOrder, + EffectiveFromMs: r.EffectiveFromMS, + EffectiveToMs: r.EffectiveToMS, + OperatorAdminId: int64(middleware.CurrentUserID(c)), } } @@ -192,25 +215,33 @@ func taskFromProto(item *activityv1.TaskDefinition) taskDTO { if item == nil { return taskDTO{} } + // 后台列表返回 activity-service 的完整任务定义,保证编辑弹窗能原样带回维度过滤、图标和跳转配置。 return taskDTO{ - TaskID: item.GetTaskId(), - TaskType: item.GetTaskType(), - Category: item.GetCategory(), - MetricType: item.GetMetricType(), - Title: item.GetTitle(), - Description: item.GetDescription(), - TargetValue: item.GetTargetValue(), - TargetUnit: item.GetTargetUnit(), - RewardCoinAmount: item.GetRewardCoinAmount(), - Status: item.GetStatus(), - SortOrder: item.GetSortOrder(), - Version: item.GetVersion(), - EffectiveFromMS: item.GetEffectiveFromMs(), - EffectiveToMS: item.GetEffectiveToMs(), - CreatedByAdminID: item.GetCreatedByAdminId(), - UpdatedByAdminID: item.GetUpdatedByAdminId(), - CreatedAtMS: item.GetCreatedAtMs(), - UpdatedAtMS: item.GetUpdatedAtMs(), + TaskID: item.GetTaskId(), + TaskType: item.GetTaskType(), + Category: item.GetCategory(), + MetricType: item.GetMetricType(), + Title: item.GetTitle(), + Description: item.GetDescription(), + AudienceType: item.GetAudienceType(), + IconKey: item.GetIconKey(), + IconURL: item.GetIconUrl(), + ActionType: item.GetActionType(), + ActionParam: item.GetActionParam(), + ActionPayloadJSON: item.GetActionPayloadJson(), + DimensionFilterJSON: item.GetDimensionFilterJson(), + TargetValue: item.GetTargetValue(), + TargetUnit: item.GetTargetUnit(), + RewardCoinAmount: item.GetRewardCoinAmount(), + Status: item.GetStatus(), + SortOrder: item.GetSortOrder(), + Version: item.GetVersion(), + EffectiveFromMS: item.GetEffectiveFromMs(), + EffectiveToMS: item.GetEffectiveToMs(), + CreatedByAdminID: item.GetCreatedByAdminId(), + UpdatedByAdminID: item.GetUpdatedByAdminId(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), } } diff --git a/server/admin/internal/modules/gamemanagement/dto.go b/server/admin/internal/modules/gamemanagement/dto.go index e6348621..830d0d6d 100644 --- a/server/admin/internal/modules/gamemanagement/dto.go +++ b/server/admin/internal/modules/gamemanagement/dto.go @@ -93,6 +93,59 @@ type diceRobotDTO struct { UpdatedAtMS int64 `json:"updatedAtMs"` } +type roomRPSConfigDTO struct { + AppCode string `json:"appCode"` + GameID string `json:"gameId"` + Status string `json:"status"` + ChallengeTimeoutMS int64 `json:"challengeTimeoutMs"` + RevealCountdownMS int64 `json:"revealCountdownMs"` + StakeGifts []roomRPSStakeGiftDTO `json:"stakeGifts"` + CreatedAtMS int64 `json:"createdAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type roomRPSStakeGiftDTO struct { + GiftID string `json:"giftId"` + GiftIDNumber int64 `json:"giftIdNumber"` + GiftName string `json:"giftName"` + GiftIconURL string `json:"giftIconUrl"` + GiftPriceCoin int64 `json:"giftPriceCoin"` + Enabled bool `json:"enabled"` + SortOrder int32 `json:"sortOrder"` +} + +type roomRPSChallengeDTO struct { + AppCode string `json:"appCode"` + ChallengeID string `json:"challengeId"` + RoomID string `json:"roomId"` + RegionID int64 `json:"regionId"` + Status string `json:"status"` + StakeGiftID string `json:"stakeGiftId"` + StakeGiftIDNumber int64 `json:"stakeGiftIdNumber"` + StakeCoin int64 `json:"stakeCoin"` + Initiator roomRPSPlayerDTO `json:"initiator"` + Challenger roomRPSPlayerDTO `json:"challenger"` + WinnerUserID string `json:"winnerUserId"` + WinnerUserIDNumber int64 `json:"winnerUserIdNumber"` + SettlementStatus string `json:"settlementStatus"` + FailureReason string `json:"failureReason"` + TimeoutAtMS int64 `json:"timeoutAtMs"` + RevealAtMS int64 `json:"revealAtMs"` + CreatedAtMS int64 `json:"createdAtMs"` + MatchedAtMS int64 `json:"matchedAtMs"` + SettledAtMS int64 `json:"settledAtMs"` + UpdatedAtMS int64 `json:"updatedAtMs"` +} + +type roomRPSPlayerDTO struct { + UserID string `json:"userId"` + UserIDNumber int64 `json:"userIdNumber"` + Gesture string `json:"gesture"` + Result string `json:"result"` + BalanceAfter int64 `json:"balanceAfter"` + JoinedAtMS int64 `json:"joinedAtMs"` +} + func platformFromProto(item *gamev1.GamePlatform) platformDTO { if item == nil { return platformDTO{} @@ -198,3 +251,78 @@ func diceRobotFromProto(item *gamev1.DiceRobot) diceRobotDTO { UpdatedAtMS: item.GetUpdatedAtMs(), } } + +func roomRPSConfigFromProto(item *gamev1.RoomRPSConfig) roomRPSConfigDTO { + if item == nil { + return roomRPSConfigDTO{} + } + gifts := make([]roomRPSStakeGiftDTO, 0, len(item.GetStakeGifts())) + for _, gift := range item.GetStakeGifts() { + // 礼物详情由 game-service 结合礼物配置补齐;admin-server 只保持展示字段和 ID 双写,不重新查礼物表。 + gifts = append(gifts, roomRPSStakeGiftDTO{ + GiftID: strconv.FormatInt(gift.GetGiftId(), 10), + GiftIDNumber: gift.GetGiftId(), + GiftName: gift.GetGiftName(), + GiftIconURL: gift.GetGiftIconUrl(), + GiftPriceCoin: gift.GetGiftPriceCoin(), + Enabled: gift.GetEnabled(), + SortOrder: gift.GetSortOrder(), + }) + } + // DTO 只做 proto 到后台 JSON 的字段命名转换;配置默认值和合法性已经在 game-service/admin request 层处理。 + return roomRPSConfigDTO{ + AppCode: item.GetAppCode(), + GameID: item.GetGameId(), + Status: item.GetStatus(), + ChallengeTimeoutMS: item.GetChallengeTimeoutMs(), + RevealCountdownMS: item.GetRevealCountdownMs(), + StakeGifts: gifts, + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + +func roomRPSChallengeFromProto(item *gamev1.RoomRPSChallenge) roomRPSChallengeDTO { + if item == nil { + return roomRPSChallengeDTO{} + } + // 后台订单页要同时服务浏览器展示和问题排查:ID 用字符串保护前端精度,也保留 number 字段便于排序、复制和接口调试。 + // 胜负、结算状态、失败原因全部按 game-service 返回值展示,admin-server 不根据手势重新计算,避免和真实结算事实分叉。 + return roomRPSChallengeDTO{ + AppCode: item.GetAppCode(), + ChallengeID: item.GetChallengeId(), + RoomID: item.GetRoomId(), + RegionID: item.GetRegionId(), + Status: item.GetStatus(), + StakeGiftID: strconv.FormatInt(item.GetStakeGiftId(), 10), + StakeGiftIDNumber: item.GetStakeGiftId(), + StakeCoin: item.GetStakeCoin(), + Initiator: roomRPSPlayerFromProto(item.GetInitiator()), + Challenger: roomRPSPlayerFromProto(item.GetChallenger()), + WinnerUserID: strconv.FormatInt(item.GetWinnerUserId(), 10), + WinnerUserIDNumber: item.GetWinnerUserId(), + SettlementStatus: item.GetSettlementStatus(), + FailureReason: item.GetFailureReason(), + TimeoutAtMS: item.GetTimeoutAtMs(), + RevealAtMS: item.GetRevealAtMs(), + CreatedAtMS: item.GetCreatedAtMs(), + MatchedAtMS: item.GetMatchedAtMs(), + SettledAtMS: item.GetSettledAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + +func roomRPSPlayerFromProto(item *gamev1.RoomRPSPlayer) roomRPSPlayerDTO { + if item == nil { + return roomRPSPlayerDTO{} + } + // 管理后台玩家 DTO 只展示订单内快照字段;头像昵称属于 App/IM 展示增强,后台如需用户资料应走独立用户查询。 + return roomRPSPlayerDTO{ + UserID: strconv.FormatInt(item.GetUserId(), 10), + UserIDNumber: item.GetUserId(), + Gesture: item.GetGesture(), + Result: item.GetResult(), + BalanceAfter: item.GetBalanceAfter(), + JoinedAtMS: item.GetJoinedAtMs(), + } +} diff --git a/server/admin/internal/modules/gamemanagement/dto_test.go b/server/admin/internal/modules/gamemanagement/dto_test.go index 91249f90..37c20c52 100644 --- a/server/admin/internal/modules/gamemanagement/dto_test.go +++ b/server/admin/internal/modules/gamemanagement/dto_test.go @@ -22,3 +22,53 @@ func TestPlatformFromProtoReturnsCallbackSecret(t *testing.T) { t.Fatal("CallbackSecretSet = false, want true") } } + +func TestRoomRPSConfigRequestRequiresFourGiftTiers(t *testing.T) { + _, err := (roomRPSConfigRequest{ + Status: "active", + ChallengeTimeoutMS: 600000, + RevealCountdownMS: 3000, + StakeGifts: []roomRPSStakeGiftReq{ + {GiftID: 1001, Enabled: true, SortOrder: 1}, + {GiftID: 1002, Enabled: true, SortOrder: 2}, + {GiftID: 1003, Enabled: true, SortOrder: 3}, + }, + }).toProto() + + if err == nil { + t.Fatal("toProto must reject room rps config without exactly four gift tiers") + } +} + +func TestRoomRPSConfigRequestDefaultsOpenConfig(t *testing.T) { + got, err := (roomRPSConfigRequest{ + StakeGifts: []roomRPSStakeGiftReq{ + {GiftID: 1001, Enabled: true}, + {GiftID: 1002, Enabled: true}, + {GiftID: 1003, Enabled: true}, + {GiftID: 1004, Enabled: true}, + }, + }).toProto() + if err != nil { + t.Fatalf("toProto failed: %v", err) + } + if got.GetStatus() != "active" || got.GetChallengeTimeoutMs() != 600000 || got.GetRevealCountdownMs() != 3000 { + t.Fatalf("room rps default config mismatch: %+v", got) + } + if got.GetStakeGifts()[3].GetSortOrder() != 4 { + t.Fatalf("default sort order mismatch: %+v", got.GetStakeGifts()) + } +} + +func TestRoomRPSChallengeFromProtoKeepsLargeIDsAsStrings(t *testing.T) { + got := roomRPSChallengeFromProto(&gamev1.RoomRPSChallenge{ + ChallengeId: "rps-1", + StakeGiftId: 9007199254740993, + Initiator: &gamev1.RoomRPSPlayer{UserId: 9007199254740995, Gesture: "rock"}, + WinnerUserId: 9007199254740995, + }) + + if got.StakeGiftID != "9007199254740993" || got.Initiator.UserID != "9007199254740995" || got.WinnerUserID != "9007199254740995" { + t.Fatalf("large IDs must stay string-safe in admin JSON: %+v", got) + } +} diff --git a/server/admin/internal/modules/gamemanagement/request.go b/server/admin/internal/modules/gamemanagement/request.go index 3608b561..8d80b361 100644 --- a/server/admin/internal/modules/gamemanagement/request.go +++ b/server/admin/internal/modules/gamemanagement/request.go @@ -1,6 +1,7 @@ package gamemanagement import ( + "fmt" "strings" "time" @@ -83,6 +84,19 @@ type diceRobotStatusRequest struct { Status string `json:"status"` } +type roomRPSConfigRequest struct { + Status string `json:"status"` + ChallengeTimeoutMS int64 `json:"challengeTimeoutMs"` + RevealCountdownMS int64 `json:"revealCountdownMs"` + StakeGifts []roomRPSStakeGiftReq `json:"stakeGifts"` +} + +type roomRPSStakeGiftReq struct { + GiftID int64 `json:"giftId"` + Enabled bool `json:"enabled"` + SortOrder int32 `json:"sortOrder"` +} + func (r platformRequest) toProto(platformCode string) *gamev1.GamePlatform { if platformCode == "" { platformCode = r.PlatformCode @@ -144,6 +158,59 @@ func (r diceConfigRequest) toProto(gameID string) *gamev1.DiceConfig { } } +func (r roomRPSConfigRequest) toProto() (*gamev1.RoomRPSConfig, error) { + status := strings.TrimSpace(r.Status) + if status == "" { + // 业务功能配置默认打开;后台没传 status 时不能默认为关闭,否则新环境上线后 App 入口会被静默隐藏。 + status = "active" + } + timeoutMS := r.ChallengeTimeoutMS + if timeoutMS <= 0 { + // 无人应战超时使用产品默认 10 分钟;0 或负数按“未填写”处理,不允许保存成立刻过期。 + timeoutMS = 10 * 60 * 1000 + } + revealMS := r.RevealCountdownMS + if revealMS <= 0 { + // 揭晓倒计时使用产品默认 3 秒;客户端动画和 IM 倒计时都依赖这个稳定默认值。 + revealMS = 3 * 1000 + } + if len(r.StakeGifts) != 4 { + // 房内猜拳礼物档位是产品固定四档,少一档会导致 Flutter 面板缺位,多一档会导致后台和客户端展示不一致。 + return nil, fmt.Errorf("房内猜拳礼物档位必须配置 4 档") + } + gifts := make([]*gamev1.RoomRPSStakeGift, 0, len(r.StakeGifts)) + seen := make(map[int64]struct{}, len(r.StakeGifts)) + for index, gift := range r.StakeGifts { + if gift.GiftID <= 0 { + // gift_id 必须是后台真实礼物 ID;0 不能表示“空档位”,空档位应该通过 enabled=false 表达。 + return nil, fmt.Errorf("第 %d 档礼物 ID 不正确", index+1) + } + if _, ok := seen[gift.GiftID]; ok { + // 同一个礼物不能重复配置成多档,否则结算金额和排序展示会变成同一个 gift_id 的多份事实。 + return nil, fmt.Errorf("第 %d 档礼物 ID 重复", index+1) + } + seen[gift.GiftID] = struct{}{} + sortOrder := gift.SortOrder + if sortOrder <= 0 { + // 未显式给排序时按提交顺序落库,保证前端拖拽顺序和运行时展示顺序一致。 + sortOrder = int32(index + 1) + } + gifts = append(gifts, &gamev1.RoomRPSStakeGift{ + GiftId: gift.GiftID, + Enabled: gift.Enabled, + SortOrder: sortOrder, + }) + } + return &gamev1.RoomRPSConfig{ + // 房内猜拳和独立猜拳必须用不同 game_id;这里固定 room_rps,防止后台误把配置写到独立猜拳。 + GameId: "room_rps", + Status: status, + ChallengeTimeoutMs: timeoutMS, + RevealCountdownMs: revealMS, + StakeGifts: gifts, + }, nil +} + func compactTags(tags []string) []string { out := make([]string, 0, len(tags)) seen := make(map[string]struct{}, len(tags)) diff --git a/server/admin/internal/modules/gamemanagement/room_rps_handler.go b/server/admin/internal/modules/gamemanagement/room_rps_handler.go new file mode 100644 index 00000000..ccbe20d9 --- /dev/null +++ b/server/admin/internal/modules/gamemanagement/room_rps_handler.go @@ -0,0 +1,173 @@ +package gamemanagement + +import ( + "fmt" + "strings" + + "hyapp-admin-server/internal/response" + gamev1 "hyapp.local/api/proto/game/v1" + + "github.com/gin-gonic/gin" +) + +func (h *Handler) GetRoomRPSConfig(c *gin.Context) { + // 后台查询不读本地缓存,也不直接查 game 表;所有配置事实统一从 game-service 管理 RPC 返回,避免 admin-server 和运行时出现双写。 + resp, err := h.game.GetRoomRPSConfig(c.Request.Context(), &gamev1.GetRoomRPSConfigRequest{Meta: requestMeta(c)}) + if err != nil { + response.ServerError(c, "获取房内猜拳配置失败") + return + } + response.OK(c, gin.H{"config": roomRPSConfigFromProto(resp.GetConfig()), "serverTimeMs": resp.GetServerTimeMs()}) +} + +func (h *Handler) UpdateRoomRPSConfig(c *gin.Context) { + var req roomRPSConfigRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "房内猜拳配置参数不正确") + return + } + // JSON 绑定只负责类型检查,业务边界统一放到 toProto:默认启用、默认倒计时、固定四档礼物和礼物 ID 去重。 + config, err := req.toProto() + if err != nil { + response.BadRequest(c, err.Error()) + return + } + // admin-server 只校验后台可见的配置边界:开关、超时和 4 个礼物档位。 + // 礼物名称、价格、图标、扣款、托管和结算幂等由 game-service/wallet-service 统一处理,避免后台绕过运行时校验写入脏配置。 + resp, err := h.game.UpdateRoomRPSConfig(c.Request.Context(), &gamev1.UpdateRoomRPSConfigRequest{ + Meta: requestMeta(c), + Config: config, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := roomRPSConfigFromProto(resp.GetConfig()) + // 配置修改会影响所有房间入口和下注档位,必须写审计;审计目标使用 game_id,便于后续按玩法追踪变更。 + h.auditLog(c, "update-room-rps-config", "game_room_rps_configs", item.GameID, item.Status) + response.OK(c, gin.H{"config": item, "serverTimeMs": resp.GetServerTimeMs()}) +} + +func (h *Handler) ListRoomRPSChallenges(c *gin.Context) { + // 管理后台默认页大小比 App 大,但仍只允许正数;真正的上限和游标稳定性由 game-service 控制,防止 admin-server 复制分页规则。 + pageSize := int32(parsePositiveInt(firstQuery(c, "pageSize", "page_size"), 50)) + // 所有 ID 查询都兼容 camelCase 和 snake_case,前端生成代码和手工调试都能命中同一套过滤逻辑。 + initiatorUserID, ok := optionalInt64Query(c, "initiatorUserId", "initiator_user_id") + if !ok { + response.BadRequest(c, "发起人用户 ID 参数不正确") + return + } + challengerUserID, ok := optionalInt64Query(c, "challengerUserId", "challenger_user_id") + if !ok { + response.BadRequest(c, "应战人用户 ID 参数不正确") + return + } + startTimeMS, ok := optionalInt64Query(c, "startTimeMs", "start_time_ms") + if !ok { + response.BadRequest(c, "开始时间参数不正确") + return + } + endTimeMS, ok := optionalInt64Query(c, "endTimeMs", "end_time_ms") + if !ok { + response.BadRequest(c, "结束时间参数不正确") + return + } + // 列表接口只把后台筛选条件透传给 game-service;订单可见性、游标语义、状态枚举和结算状态仍以 game-service 查询结果为准。 + // admin-server 不做本地状态拼装,避免后续 game-service 增加 timeout/settlement_failed 补偿状态时后台漏同步。 + resp, err := h.game.ListRoomRPSChallenges(c.Request.Context(), &gamev1.ListRoomRPSChallengesRequest{ + Meta: requestMeta(c), + RoomId: strings.TrimSpace(firstQuery(c, "roomId", "room_id")), + Status: strings.TrimSpace(c.Query("status")), + InitiatorUserId: initiatorUserID, + ChallengerUserId: challengerUserID, + StartTimeMs: startTimeMS, + EndTimeMs: endTimeMS, + PageSize: pageSize, + Cursor: strings.TrimSpace(c.Query("cursor")), + }) + if err != nil { + response.ServerError(c, "获取房内猜拳订单失败") + return + } + items := make([]roomRPSChallengeDTO, 0, len(resp.GetChallenges())) + for _, item := range resp.GetChallenges() { + // DTO 转换只做字段命名和大整数保护,不在后台列表里重新计算胜负或结算结果。 + items = append(items, roomRPSChallengeFromProto(item)) + } + response.OK(c, gin.H{"items": items, "nextCursor": resp.GetNextCursor(), "pageSize": pageSize, "serverTimeMs": resp.GetServerTimeMs()}) +} + +func (h *Handler) GetRoomRPSChallenge(c *gin.Context) { + challengeID := strings.TrimSpace(c.Param("challenge_id")) + if challengeID == "" { + response.BadRequest(c, "挑战单 ID 参数不正确") + return + } + // 详情接口不接收 room_id 或用户 ID 作为辅助条件;challenge_id 是唯一事实入口,避免后台参数互相矛盾时误展示错误订单。 + resp, err := h.game.GetRoomRPSChallenge(c.Request.Context(), &gamev1.GetRoomRPSChallengeRequest{ + Meta: requestMeta(c), + ChallengeId: challengeID, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + response.OK(c, gin.H{"challenge": roomRPSChallengeFromProto(resp.GetChallenge()), "serverTimeMs": resp.GetServerTimeMs()}) +} + +func (h *Handler) RetryRoomRPSSettlement(c *gin.Context) { + challengeID := strings.TrimSpace(c.Param("challenge_id")) + if challengeID == "" { + response.BadRequest(c, "挑战单 ID 参数不正确") + return + } + // 重试入口只允许后台触发一次明确的运维命令。 + // 是否处于 settlement_failed、是否已经结算过、钱包补偿是否可重放,都由 game-service 按订单幂等事实判断,admin-server 不能预读后再决定。 + resp, err := h.game.RetryRoomRPSSettlement(c.Request.Context(), &gamev1.RetryRoomRPSSettlementRequest{ + Meta: requestMeta(c), + ChallengeId: challengeID, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := roomRPSChallengeFromProto(resp.GetChallenge()) + // 审计记录写回 game-service 返回的新结算状态,方便排查“点击重试但实际已被其它 worker 修复”的幂等场景。 + h.auditLog(c, "retry-room-rps-settlement", "game_room_rps_challenges", item.ChallengeID, item.SettlementStatus) + response.OK(c, gin.H{"challenge": item, "serverTimeMs": resp.GetServerTimeMs()}) +} + +func (h *Handler) ExpireRoomRPSChallenge(c *gin.Context) { + challengeID := strings.TrimSpace(c.Param("challenge_id")) + if challengeID == "" { + response.BadRequest(c, "挑战单 ID 参数不正确") + return + } + // 手动过期只是运维兜底命令。 + // 只有 pending 且未被应战的挑战能过期;这个判断必须在 game-service 事务内完成,避免后台读到 pending 后并发被应战。 + resp, err := h.game.ExpireRoomRPSChallenge(c.Request.Context(), &gamev1.ExpireRoomRPSChallengeRequest{ + Meta: requestMeta(c), + ChallengeId: challengeID, + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := roomRPSChallengeFromProto(resp.GetChallenge()) + // 审计记录写最终状态,而不是写“用户点击了过期”,这样能看出命令是否真的把订单推进到 timeout。 + h.auditLog(c, "expire-room-rps-challenge", "game_room_rps_challenges", item.ChallengeID, item.Status) + response.OK(c, gin.H{"challenge": item, "serverTimeMs": resp.GetServerTimeMs()}) +} + +func optionalInt64Query(c *gin.Context, keys ...string) (int64, bool) { + raw := strings.TrimSpace(firstQuery(c, keys...)) + if raw == "" { + // 空值表示不筛选,不是筛选用户 0;game-service 看到 0 时按“无该条件”处理。 + return 0, true + } + var value int64 + if _, err := fmt.Sscan(raw, &value); err != nil || value < 0 { + return 0, false + } + return value, true +} diff --git a/server/admin/internal/modules/gamemanagement/routes.go b/server/admin/internal/modules/gamemanagement/routes.go index 371935b8..0d5c4dba 100644 --- a/server/admin/internal/modules/gamemanagement/routes.go +++ b/server/admin/internal/modules/gamemanagement/routes.go @@ -27,4 +27,12 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.POST("/admin/game/robots/generate", middleware.RequirePermission("game:create"), h.GenerateDiceRobots) protected.PATCH("/admin/game/robots/:user_id/status", middleware.RequirePermission("game:update"), h.SetDiceRobotStatus) protected.DELETE("/admin/game/robots/:user_id", middleware.RequireAnyPermission("game:delete", "game:update"), h.DeleteDiceRobot) + // 房内猜拳后台沿用游戏管理权限,但路由、DTO 和 game-service RPC 都独立于骰子和独立猜拳,避免配置互相污染。 + protected.GET("/admin/game/room-rps/config", middleware.RequirePermission("game:view"), h.GetRoomRPSConfig) + protected.PATCH("/admin/game/room-rps/config", middleware.RequirePermission("game:update"), h.UpdateRoomRPSConfig) + protected.GET("/admin/game/room-rps/challenges", middleware.RequirePermission("game:view"), h.ListRoomRPSChallenges) + protected.GET("/admin/game/room-rps/challenges/:challenge_id", middleware.RequirePermission("game:view"), h.GetRoomRPSChallenge) + // 结算重试和手动过期都是会推进订单状态的运维动作,必须使用 game:update,并由 game-service 再做状态机保护。 + protected.POST("/admin/game/room-rps/challenges/:challenge_id/retry-settlement", middleware.RequirePermission("game:update"), h.RetryRoomRPSSettlement) + protected.POST("/admin/game/room-rps/challenges/:challenge_id/expire", middleware.RequirePermission("game:update"), h.ExpireRoomRPSChallenge) } diff --git a/server/admin/internal/modules/gamemanagement/self_game_handler.go b/server/admin/internal/modules/gamemanagement/self_game_handler.go index ce89a5b3..85b3d07e 100644 --- a/server/admin/internal/modules/gamemanagement/self_game_handler.go +++ b/server/admin/internal/modules/gamemanagement/self_game_handler.go @@ -88,7 +88,7 @@ func (h *Handler) ListDiceRobots(c *gin.Context) { Cursor: strings.TrimSpace(c.Query("cursor")), }) if err != nil { - response.ServerError(c, "获取游戏机器人失败") + response.ServerError(c, "获取全站机器人失败") return } items := make([]diceRobotDTO, 0, len(resp.GetRobots())) diff --git a/server/admin/internal/repository/seed.go b/server/admin/internal/repository/seed.go index 8082135c..b824a068 100644 --- a/server/admin/internal/repository/seed.go +++ b/server/admin/internal/repository/seed.go @@ -286,7 +286,7 @@ func (s *Store) seedMenus() error { {ParentID: &activityID, Title: "周星配置", Code: "weekly-star", Path: "/activities/weekly-star", Icon: "star", PermissionCode: "weekly-star:view", Sort: 78, 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}, + {ParentID: &gameID, Title: "全站机器人", Code: "game-robots", Path: "/games/robots", Icon: "team", PermissionCode: "game:view", Sort: 90, Visible: true}, {ParentID: &geoID, Title: "国家管理", Code: "host-org-countries", Path: "/host/countries", Icon: "public", PermissionCode: "country:view", Sort: 70, Visible: true}, {ParentID: &geoID, Title: "区域管理", Code: "host-org-regions", Path: "/host/regions", Icon: "map", PermissionCode: "region:view", Sort: 71, Visible: true}, {ParentID: &hostOrgID, Title: "Manager列表", Code: "host-org-managers", Path: "/host/managers", Icon: "users", PermissionCode: "agency:view", Sort: 80, Visible: true}, diff --git a/server/admin/migrations/009_admin_navigation_seed.sql b/server/admin/migrations/009_admin_navigation_seed.sql index 2d485162..10d18bb6 100644 --- a/server/admin/migrations/009_admin_navigation_seed.sql +++ b/server/admin/migrations/009_admin_navigation_seed.sql @@ -161,7 +161,7 @@ ON DUPLICATE KEY UPDATE 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, '游戏机器人', 'game-robots', '/games/robots', 'team', 'game:view', 90, TRUE, @now_ms, @now_ms +SELECT parent.id, '全站机器人', 'game-robots', '/games/robots', 'team', 'game:view', 90, TRUE, @now_ms, @now_ms FROM admin_menus parent WHERE parent.code = 'games' ON DUPLICATE KEY UPDATE diff --git a/server/admin/migrations/047_room_rps_navigation.sql b/server/admin/migrations/047_room_rps_navigation.sql new file mode 100644 index 00000000..3b74f2f1 --- /dev/null +++ b/server/admin/migrations/047_room_rps_navigation.sql @@ -0,0 +1,32 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 房内猜拳复用游戏管理权限,只新增菜单入口;业务配置和订单状态仍由 game-service 按房内猜拳域独立维护。 +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) +SELECT parent.id, '房内猜拳配置', 'room-rps-config', '/games/room-rps/config', 'settings', 'game:view', 100, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'games' +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 INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) +SELECT parent.id, '房内猜拳订单', 'room-rps-challenges', '/games/room-rps/challenges', 'history', 'game:view', 110, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'games' +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; diff --git a/server/admin/migrations/048_game_robot_menu_rename.sql b/server/admin/migrations/048_game_robot_menu_rename.sql new file mode 100644 index 00000000..46037150 --- /dev/null +++ b/server/admin/migrations/048_game_robot_menu_rename.sql @@ -0,0 +1,6 @@ +SET @now_ms = UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000; + +UPDATE admin_menus +SET title = '全站机器人', + updated_at_ms = @now_ms +WHERE code = 'game-robots'; diff --git a/services/activity-service/configs/config.docker.yaml b/services/activity-service/configs/config.docker.yaml index dd06506e..de7f6690 100644 --- a/services/activity-service/configs/config.docker.yaml +++ b/services/activity-service/configs/config.docker.yaml @@ -20,6 +20,8 @@ cumulative_recharge_reward_worker: enabled: true red_packet_broadcast_worker: enabled: true +task_event_worker: + enabled: true lucky_gift_worker: enabled: true worker_poll_interval: "1s" @@ -69,11 +71,13 @@ rocketmq: first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" + task_consumer_group: "hyapp-activity-task-wallet-outbox" consumer_max_reconsume_times: 16 user_outbox: enabled: true topic: "hyapp_user_outbox" consumer_group: "hyapp-activity-user-region-broadcast" + task_consumer_group: "hyapp-activity-task-user-outbox" consumer_max_reconsume_times: 16 consumer: room_outbox_poll_interval_ms: 1000 diff --git a/services/activity-service/configs/config.tencent.example.yaml b/services/activity-service/configs/config.tencent.example.yaml index 762118ea..dac44c84 100644 --- a/services/activity-service/configs/config.tencent.example.yaml +++ b/services/activity-service/configs/config.tencent.example.yaml @@ -20,6 +20,8 @@ cumulative_recharge_reward_worker: enabled: true red_packet_broadcast_worker: enabled: true +task_event_worker: + enabled: true lucky_gift_worker: enabled: true worker_poll_interval: "1s" @@ -69,11 +71,13 @@ rocketmq: first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" + task_consumer_group: "hyapp-activity-task-wallet-outbox" consumer_max_reconsume_times: 16 user_outbox: enabled: true topic: "hyapp_user_outbox" consumer_group: "hyapp-activity-user-region-broadcast" + task_consumer_group: "hyapp-activity-task-user-outbox" consumer_max_reconsume_times: 16 consumer: room_outbox_poll_interval_ms: 1000 diff --git a/services/activity-service/configs/config.yaml b/services/activity-service/configs/config.yaml index e4ed4067..7f3dd793 100644 --- a/services/activity-service/configs/config.yaml +++ b/services/activity-service/configs/config.yaml @@ -20,6 +20,8 @@ cumulative_recharge_reward_worker: enabled: true red_packet_broadcast_worker: enabled: true +task_event_worker: + enabled: true lucky_gift_worker: enabled: true worker_poll_interval: "1s" @@ -69,11 +71,13 @@ rocketmq: first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" + task_consumer_group: "hyapp-activity-task-wallet-outbox" consumer_max_reconsume_times: 16 user_outbox: enabled: true topic: "hyapp_user_outbox" consumer_group: "hyapp-activity-user-region-broadcast" + task_consumer_group: "hyapp-activity-task-user-outbox" consumer_max_reconsume_times: 16 consumer: room_outbox_poll_interval_ms: 1000 diff --git a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql index 33ae021d..aea041fe 100644 --- a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql +++ b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql @@ -316,6 +316,13 @@ CREATE TABLE IF NOT EXISTS task_definitions ( metric_type VARCHAR(64) NOT NULL COMMENT '指标类型', title VARCHAR(160) NOT NULL COMMENT '标题', description VARCHAR(512) NOT NULL DEFAULT '' COMMENT '描述信息', + audience_type VARCHAR(32) NOT NULL DEFAULT 'all' COMMENT '适用人群:all/newbie', + icon_key VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'App 任务图标键', + icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'App 任务图标 URL', + action_type VARCHAR(64) NOT NULL DEFAULT 'none' COMMENT 'App 跳转类型', + action_param VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'App 跳转参数', + action_payload_json JSON NULL COMMENT 'App 跳转扩展 JSON', + dimension_filter_json JSON NULL COMMENT '任务事件维度过滤 JSON', target_value BIGINT NOT NULL COMMENT '目标值', target_unit VARCHAR(32) NOT NULL COMMENT '目标单位', reward_coin_amount BIGINT NOT NULL COMMENT '奖励金币数量', @@ -372,6 +379,9 @@ CREATE TABLE IF NOT EXISTS task_event_consumption ( user_id BIGINT NOT NULL COMMENT '用户 ID', task_day VARCHAR(32) NOT NULL COMMENT '任务天', status VARCHAR(32) NOT NULL COMMENT '业务状态', + metric_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '任务指标类型', + value_delta BIGINT NOT NULL DEFAULT 0 COMMENT '本次事件进度增量', + dimensions_json JSON NULL COMMENT '事件维度快照 JSON', skip_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '跳过原因', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', consumed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '消费时间,UTC epoch ms', diff --git a/services/activity-service/deploy/mysql/migrations/001_task_metadata.sql b/services/activity-service/deploy/mysql/migrations/001_task_metadata.sql new file mode 100644 index 00000000..28b8596a --- /dev/null +++ b/services/activity-service/deploy/mysql/migrations/001_task_metadata.sql @@ -0,0 +1,13 @@ +ALTER TABLE task_definitions + ADD COLUMN audience_type VARCHAR(32) NOT NULL DEFAULT 'all' COMMENT '适用人群:all/newbie' AFTER description, + ADD COLUMN icon_key VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'App 任务图标键' AFTER audience_type, + ADD COLUMN icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'App 任务图标 URL' AFTER icon_key, + ADD COLUMN action_type VARCHAR(64) NOT NULL DEFAULT 'none' COMMENT 'App 跳转类型' AFTER icon_url, + ADD COLUMN action_param VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'App 跳转参数' AFTER action_type, + ADD COLUMN action_payload_json JSON NULL COMMENT 'App 跳转扩展 JSON' AFTER action_param, + ADD COLUMN dimension_filter_json JSON NULL COMMENT '任务事件维度过滤 JSON' AFTER action_payload_json; + +ALTER TABLE task_event_consumption + ADD COLUMN metric_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '任务指标类型' AFTER status, + ADD COLUMN value_delta BIGINT NOT NULL DEFAULT 0 COMMENT '本次事件进度增量' AFTER metric_type, + ADD COLUMN dimensions_json JSON NULL COMMENT '事件维度快照 JSON' AFTER value_delta; diff --git a/services/activity-service/internal/app/grpc_registration.go b/services/activity-service/internal/app/grpc_registration.go index 7a19c7b3..27dd5735 100644 --- a/services/activity-service/internal/app/grpc_registration.go +++ b/services/activity-service/internal/app/grpc_registration.go @@ -30,7 +30,7 @@ func registerGRPCServers(server *grpc.Server, services *serviceBundle) { activityv1.RegisterAdminRoomTurnoverRewardServiceServer(server, grpcserver.NewAdminRoomTurnoverRewardServer(services.roomTurnoverReward)) // BroadcastServer 同时承载 BroadcastService 和 RoomEventConsumerService。 // gRPC 直连投递和 MQ 消费都必须经过相同的 room event fanout 顺序,否则本地验证会通过但线上 MQ 路径漏积分。 - broadcastServer := grpcserver.NewBroadcastServer(services.broadcast, services.growth, services.weeklyStar, services.roomTurnoverReward) + broadcastServer := grpcserver.NewBroadcastServer(services.broadcast, services.growth, services.weeklyStar, services.task, services.roomTurnoverReward) activityv1.RegisterBroadcastServiceServer(server, broadcastServer) activityv1.RegisterRoomEventConsumerServiceServer(server, broadcastServer) activityv1.RegisterFirstRechargeRewardServiceServer(server, grpcserver.NewFirstRechargeRewardServer(services.firstRechargeReward)) diff --git a/services/activity-service/internal/app/mq.go b/services/activity-service/internal/app/mq.go index 9db165cf..7f7c669d 100644 --- a/services/activity-service/internal/app/mq.go +++ b/services/activity-service/internal/app/mq.go @@ -26,10 +26,12 @@ func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx. mqConsumers = append(mqConsumers, consumer) } if cfg.RocketMQ.UserOutbox.Enabled && !services.broadcastPublisherAvailable { - shutdownConsumers(mqConsumers) - return nil, errors.New("rocketmq user_outbox consumer requires tencent_im.enabled") + if !cfg.TaskEventWorker.Enabled { + shutdownConsumers(mqConsumers) + return nil, errors.New("rocketmq user_outbox consumer requires tencent_im.enabled") + } } - if cfg.RocketMQ.UserOutbox.Enabled { + if cfg.RocketMQ.UserOutbox.Enabled && services.broadcastPublisherAvailable { consumer, err := newUserOutboxConsumer(cfg, services.broadcast) if err != nil { shutdownConsumers(mqConsumers) @@ -37,6 +39,14 @@ func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx. } mqConsumers = append(mqConsumers, consumer) } + if cfg.TaskEventWorker.Enabled && cfg.RocketMQ.UserOutbox.Enabled { + consumer, err := newTaskUserOutboxConsumer(cfg, services) + if err != nil { + shutdownConsumers(mqConsumers) + return nil, err + } + mqConsumers = append(mqConsumers, consumer) + } if cfg.FirstRechargeRewardWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled { consumer, err := newFirstRechargeWalletConsumer(cfg, services) if err != nil { @@ -61,6 +71,14 @@ func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx. } mqConsumers = append(mqConsumers, consumer) } + if cfg.TaskEventWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled { + consumer, err := newTaskWalletConsumer(cfg, services) + if err != nil { + shutdownConsumers(mqConsumers) + return nil, err + } + mqConsumers = append(mqConsumers, consumer) + } return mqConsumers, nil } @@ -86,6 +104,9 @@ func newRoomOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketm if _, err := services.weeklyStar.HandleRoomEvent(eventCtx, envelope); err != nil { return err } + if _, err := services.task.HandleRoomEvent(eventCtx, envelope); err != nil { + return err + } _, err = services.roomTurnoverReward.HandleRoomEvent(eventCtx, envelope) return err }); err != nil { @@ -168,6 +189,53 @@ func newRedPacketWalletConsumer(cfg config.Config, services *serviceBundle) (*ro return consumer, nil } +func newTaskWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { + consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.TaskConsumerGroup)) + if err != nil { + return nil, err + } + if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + // 钱包任务消费者使用独立 consumer group,不能复用红包或其他钱包消费者进度;否则充值任务会被其他业务确认掉。 + event, appCode, ok, err := taskEventFromWalletMessage(message.Body) + if err != nil || !ok { + return err + } + // ConsumeTaskEvent 内部按 event_id 幂等;这里返回错误给 MQ 才会重试,正常 skipped/duplicate 都视为成功确认。 + _, err = services.task.ConsumeTaskEvent(appcode.WithContext(ctx, appCode), event) + return err + }); err != nil { + _ = consumer.Shutdown() + return nil, err + } + return consumer, nil +} + +func newTaskUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { + consumer, err := rocketmqx.NewConsumer(userOutboxTaskConsumerConfig(cfg.RocketMQ)) + if err != nil { + return nil, err + } + if err := consumer.Subscribe(cfg.RocketMQ.UserOutbox.Topic, usermq.TagUserOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + // user outbox 一条消息可能投影成多条任务事件,例如 CP 双方各一条;只有全部写入成功才确认 MQ。 + events, appCode, err := taskEventsFromUserMessage(message.Body) + if err != nil || len(events) == 0 { + return err + } + eventCtx := appcode.WithContext(ctx, appCode) + for _, event := range events { + // 单条失败时让整条 outbox 重试,已写入成功的事件靠 task_event_consumption 幂等表跳过。 + if _, err := services.task.ConsumeTaskEvent(eventCtx, event); err != nil { + return err + } + } + return nil + }); err != nil { + _ = consumer.Shutdown() + return nil, err + } + return consumer, nil +} + func roomOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { return rocketMQConsumerConfig(cfg, cfg.RoomOutbox.ConsumerGroup, cfg.RoomOutbox.ConsumerMaxReconsumeTimes) } @@ -176,6 +244,10 @@ func userOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfi return rocketMQConsumerConfig(cfg, cfg.UserOutbox.ConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes) } +func userOutboxTaskConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { + return rocketMQConsumerConfig(cfg, cfg.UserOutbox.TaskConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes) +} + func walletOutboxConsumerConfig(cfg config.RocketMQConfig, group string) rocketmqx.ConsumerConfig { return rocketMQConsumerConfig(cfg, group, cfg.WalletOutbox.ConsumerMaxReconsumeTimes) } diff --git a/services/activity-service/internal/app/task_events.go b/services/activity-service/internal/app/task_events.go new file mode 100644 index 00000000..bbf142e3 --- /dev/null +++ b/services/activity-service/internal/app/task_events.go @@ -0,0 +1,237 @@ +package app + +import ( + "encoding/json" + "strconv" + "strings" + "time" + + "hyapp/pkg/appcode" + "hyapp/pkg/usermq" + "hyapp/pkg/walletmq" + taskdomain "hyapp/services/activity-service/internal/domain/task" +) + +const ( + taskWalletRechargeRecorded = "WalletRechargeRecorded" + taskCPRelationshipCreated = "UserCPRelationshipCreated" + taskMicDailyStatsUpdated = "UserMicDailyStatsUpdated" + taskMoodPublished = "UserMoodPublished" +) + +// taskEventFromWalletMessage 只把钱包 outbox 中的充值入账事实映射为任务事件。 +// 钱包流水可能同时承载扣款、冻结、后台调账等事件,任务系统只消费正向充值金币,避免“钱包余额增加”被误算成充值任务。 +func taskEventFromWalletMessage(body []byte) (taskdomain.Event, string, bool, error) { + message, err := walletmq.DecodeWalletOutboxMessage(body) + if err != nil { + return taskdomain.Event{}, "", false, err + } + // 返回 ok=false 表示这条钱包事件和任务无关,MQ 可以直接确认;只有解析错误或任务写入错误才触发重试。 + if message.EventType != taskWalletRechargeRecorded || message.UserID <= 0 { + return taskdomain.Event{}, appcode.Normalize(message.AppCode), false, nil + } + decoded := decodeTaskPayload(message.PayloadJSON) + // coin_amount 是新事件主字段;amount/available_delta/message.AvailableDelta 是兼容旧 outbox 或不同钱包实现的兜底来源。 + coinAmount := firstNonZeroInt64( + int64FromDecoded(decoded, "coin_amount"), + int64FromDecoded(decoded, "amount"), + int64FromDecoded(decoded, "available_delta"), + message.AvailableDelta, + ) + if coinAmount <= 0 { + return taskdomain.Event{}, appcode.Normalize(message.AppCode), false, nil + } + // 充值维度保留交易号、命令号、资产类型和商品信息,后台可按渠道或商品做任务过滤,也方便排查进度来源。 + dimensions := map[string]any{ + "transaction_id": message.TransactionID, + "command_id": message.CommandID, + "asset_type": message.AssetType, + "recharge_type": firstNonEmptyString(stringFromDecoded(decoded, "recharge_type"), stringFromDecoded(decoded, "channel"), stringFromDecoded(decoded, "provider")), + "google_product_id": firstNonEmptyString(stringFromDecoded(decoded, "google_product_id"), stringFromDecoded(decoded, "product_name"), stringFromDecoded(decoded, "product_code")), + } + return taskdomain.Event{ + EventID: message.EventID + ":task:recharge_coin", + EventType: message.EventType, + SourceService: "wallet-service", + UserID: message.UserID, + MetricType: taskdomain.MetricRechargeCoin, + Value: coinAmount, + OccurredAtMS: message.OccurredAtMS, + DimensionsJSON: mustTaskDimensions(dimensions), + }, appcode.Normalize(message.AppCode), true, nil +} + +// taskEventsFromUserMessage 按 user-service outbox 类型拆分任务事件。 +// 这里不让 App 自报 CP、麦位或心情进度,所有进度都来自 user-service 已提交事实。 +func taskEventsFromUserMessage(body []byte) ([]taskdomain.Event, string, error) { + message, err := usermq.DecodeUserOutboxMessage(body) + if err != nil { + return nil, "", err + } + appCode := appcode.Normalize(message.AppCode) + switch message.EventType { + case taskCPRelationshipCreated: + return cpTaskEventsFromUserMessage(message), appCode, nil + case taskMicDailyStatsUpdated: + return micTaskEventsFromUserMessage(message), appCode, nil + case taskMoodPublished: + return moodTaskEventsFromUserMessage(message), appCode, nil + default: + return nil, appCode, nil + } +} + +func cpTaskEventsFromUserMessage(message usermq.UserOutboxMessage) []taskdomain.Event { + payload := decodeTaskPayload(message.PayloadJSON) + // CP 创建事件可能嵌套 relationship/application,也可能只带 aggregate_id;按多种字段名读取是为了兼容不同生产者版本。 + relationship := mapFromTaskPayload(payload, "relationship", "Relationship") + application := mapFromTaskPayload(payload, "application", "Application") + requester := mapFromTaskPayload(application, "requester", "Requester") + target := mapFromTaskPayload(application, "target", "Target") + relationType := firstNonEmptyString( + stringFromTaskPayload(relationship, "relation_type", "RelationType"), + stringFromTaskPayload(application, "relation_type", "RelationType"), + ) + // CP 是双方共同完成的事实;为双方各生成一条任务事件,event_id 带 user_id 后缀保证双方幂等键互不冲突。 + userIDs := uniquePositiveInt64s( + int64FromTaskPayload(relationship, "user_a_id", "UserAID", "userAId"), + int64FromTaskPayload(relationship, "user_b_id", "UserBID", "userBId"), + int64FromTaskPayload(requester, "user_id", "UserID", "userId"), + int64FromTaskPayload(target, "user_id", "UserID", "userId"), + message.AggregateID, + ) + events := make([]taskdomain.Event, 0, len(userIDs)) + for _, userID := range userIDs { + events = append(events, taskdomain.Event{ + EventID: message.EventID + ":task:cp_relationship_created:" + strconv.FormatInt(userID, 10), + EventType: message.EventType, + SourceService: "user-service", + UserID: userID, + MetricType: taskdomain.MetricCPRelationshipCreated, + Value: 1, + OccurredAtMS: message.OccurredAtMS, + DimensionsJSON: mustTaskDimensions(map[string]any{ + "relationship_id": stringFromTaskPayload(relationship, "relationship_id", "RelationshipID"), + "relation_type": relationType, + }), + }) + } + return events +} + +func micTaskEventsFromUserMessage(message usermq.UserOutboxMessage) []taskdomain.Event { + payload := decodeTaskPayload(message.PayloadJSON) + // 麦上任务按服务端统计的增量分钟累计;不足 1 分钟的残余毫秒不写任务,避免重复消费时需要维护小数状态。 + micOnlineMS := int64FromTaskPayload(payload, "mic_online_delta_ms", "MicOnlineDeltaMS") + minutes := micOnlineMS / int64(time.Minute/time.Millisecond) + userID := firstNonZeroInt64(int64FromTaskPayload(payload, "user_id", "UserID"), message.AggregateID) + if userID <= 0 || minutes <= 0 { + return nil + } + statDate := stringFromTaskPayload(payload, "stat_date", "StatDate") + // 日统计事件用 stat_date 的 UTC 中午作为任务发生时间,避免跨时区边界把同一天统计归到前一天或后一天。 + occurredAtMS := taskStatDateOccurredAtMS(statDate, message.OccurredAtMS) + return []taskdomain.Event{{ + EventID: message.EventID + ":task:mic_online_minute", + EventType: message.EventType, + SourceService: "user-service", + UserID: userID, + MetricType: taskdomain.MetricMicOnlineMinute, + Value: minutes, + OccurredAtMS: occurredAtMS, + DimensionsJSON: mustTaskDimensions(map[string]any{ + "stat_date": statDate, + "mic_session_id": stringFromTaskPayload(payload, "mic_session_id", "MicSessionID"), + }), + }} +} + +func moodTaskEventsFromUserMessage(message usermq.UserOutboxMessage) []taskdomain.Event { + // 心情发布先预留 owner API 事件口径;只要 user-service 发出已提交 outbox,就按一次发布累计。 + userID := message.AggregateID + if userID <= 0 { + payload := decodeTaskPayload(message.PayloadJSON) + userID = int64FromTaskPayload(payload, "user_id", "UserID") + } + if userID <= 0 { + return nil + } + return []taskdomain.Event{{ + EventID: message.EventID + ":task:mood_publish_count", + EventType: message.EventType, + SourceService: "user-service", + UserID: userID, + MetricType: taskdomain.MetricMoodPublishCount, + Value: 1, + OccurredAtMS: message.OccurredAtMS, + DimensionsJSON: mustTaskDimensions(decodeTaskPayload(message.PayloadJSON)), + }} +} + +func decodeTaskPayload(payload string) map[string]any { + // 无效 payload 不阻断无维度任务,返回空对象后仍可累计通用指标;需要指定维度的任务会在仓储匹配阶段自然跳过。 + var decoded map[string]any + if err := json.Unmarshal([]byte(payload), &decoded); err != nil || decoded == nil { + return map[string]any{} + } + return decoded +} + +func mapFromTaskPayload(decoded map[string]any, keys ...string) map[string]any { + for _, key := range keys { + if value, ok := decoded[key].(map[string]any); ok { + return value + } + } + return map[string]any{} +} + +func stringFromTaskPayload(decoded map[string]any, keys ...string) string { + for _, key := range keys { + if value, ok := decoded[key].(string); ok && strings.TrimSpace(value) != "" { + return strings.TrimSpace(value) + } + } + return "" +} + +func int64FromTaskPayload(decoded map[string]any, keys ...string) int64 { + for _, key := range keys { + if value := int64FromDecoded(decoded, key); value != 0 { + return value + } + } + return 0 +} + +func uniquePositiveInt64s(values ...int64) []int64 { + // 同一个用户可能同时出现在 relationship 和 requester 字段,去重后再生成任务事件,避免一次 CP 事实给同一人加两次进度。 + seen := make(map[int64]bool, len(values)) + result := make([]int64, 0, len(values)) + for _, value := range values { + if value <= 0 || seen[value] { + continue + } + seen[value] = true + result = append(result, value) + } + return result +} + +func taskStatDateOccurredAtMS(statDate string, fallback int64) int64 { + parsed, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(statDate), time.UTC) + if err != nil { + // 旧消息没有 stat_date 或格式异常时退回 outbox 发生时间,保证事件仍可消费并由幂等表记录排障。 + return fallback + } + return parsed.Add(12 * time.Hour).UnixMilli() +} + +func mustTaskDimensions(payload map[string]any) string { + data, err := json.Marshal(payload) + if err != nil { + // 维度只用于过滤和排查,序列化失败不能阻断主指标;返回 {} 后只有无维度过滤的任务会累计。 + return "{}" + } + return string(data) +} diff --git a/services/activity-service/internal/config/config.go b/services/activity-service/internal/config/config.go index 829887d1..df759920 100644 --- a/services/activity-service/internal/config/config.go +++ b/services/activity-service/internal/config/config.go @@ -33,6 +33,8 @@ type Config struct { CumulativeRechargeRewardWorker CumulativeRechargeRewardWorkerConfig `yaml:"cumulative_recharge_reward_worker"` // RedPacketBroadcastWorker 控制红包创建事实转区域飘屏 outbox 的本地消费。 RedPacketBroadcastWorker RedPacketBroadcastWorkerConfig `yaml:"red_packet_broadcast_worker"` + // TaskEventWorker 控制任务系统对 wallet/user outbox 事实的本地消费。 + TaskEventWorker TaskEventWorkerConfig `yaml:"task_event_worker"` // LuckyGiftWorker 控制幸运礼物 draw outbox 的返奖和房间 IM 补偿。 LuckyGiftWorker LuckyGiftWorkerConfig `yaml:"lucky_gift_worker"` // TencentIM 是 activity-service 发送全局/区域播报群消息的 REST 配置。 @@ -77,6 +79,12 @@ type RedPacketBroadcastWorkerConfig struct { Enabled bool `yaml:"enabled"` } +// TaskEventWorkerConfig 保存任务进度事实消费策略。 +type TaskEventWorkerConfig struct { + // Enabled 控制是否启动 wallet/user outbox 任务消费者;room outbox 任务投影跟随 room_outbox 主消费者。 + Enabled bool `yaml:"enabled"` +} + // LuckyGiftWorkerConfig 保存幸运礼物抽奖副作用补偿策略。 type LuckyGiftWorkerConfig struct { Enabled bool `yaml:"enabled"` @@ -162,6 +170,7 @@ type WalletOutboxMQConfig struct { FirstRechargeConsumerGroup string `yaml:"first_recharge_consumer_group"` CumulativeRechargeConsumerGroup string `yaml:"cumulative_recharge_consumer_group"` RedPacketBroadcastConsumerGroup string `yaml:"red_packet_broadcast_consumer_group"` + TaskConsumerGroup string `yaml:"task_consumer_group"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` } @@ -170,6 +179,7 @@ type UserOutboxMQConfig struct { Enabled bool `yaml:"enabled"` Topic string `yaml:"topic"` ConsumerGroup string `yaml:"consumer_group"` + TaskConsumerGroup string `yaml:"task_consumer_group"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` } @@ -194,6 +204,9 @@ func Default() Config { RedPacketBroadcastWorker: RedPacketBroadcastWorkerConfig{ Enabled: false, }, + TaskEventWorker: TaskEventWorkerConfig{ + Enabled: true, + }, LuckyGiftWorker: LuckyGiftWorkerConfig{ Enabled: false, WorkerPollInterval: time.Second, @@ -255,12 +268,14 @@ func defaultRocketMQConfig() RocketMQConfig { FirstRechargeConsumerGroup: "hyapp-activity-first-recharge-wallet-outbox", CumulativeRechargeConsumerGroup: "hyapp-activity-cumulative-recharge-wallet-outbox", RedPacketBroadcastConsumerGroup: "hyapp-activity-red-packet-wallet-outbox", + TaskConsumerGroup: "hyapp-activity-task-wallet-outbox", ConsumerMaxReconsumeTimes: 16, }, UserOutbox: UserOutboxMQConfig{ Enabled: false, Topic: "hyapp_user_outbox", ConsumerGroup: "hyapp-activity-user-region-broadcast", + TaskConsumerGroup: "hyapp-activity-task-user-outbox", ConsumerMaxReconsumeTimes: 16, }, } @@ -401,6 +416,9 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.WalletOutbox.RedPacketBroadcastConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.RedPacketBroadcastConsumerGroup); cfg.WalletOutbox.RedPacketBroadcastConsumerGroup == "" { cfg.WalletOutbox.RedPacketBroadcastConsumerGroup = defaults.WalletOutbox.RedPacketBroadcastConsumerGroup } + if cfg.WalletOutbox.TaskConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.TaskConsumerGroup); cfg.WalletOutbox.TaskConsumerGroup == "" { + cfg.WalletOutbox.TaskConsumerGroup = defaults.WalletOutbox.TaskConsumerGroup + } if cfg.WalletOutbox.ConsumerMaxReconsumeTimes <= 0 { cfg.WalletOutbox.ConsumerMaxReconsumeTimes = defaults.WalletOutbox.ConsumerMaxReconsumeTimes } @@ -410,6 +428,9 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.UserOutbox.ConsumerGroup = strings.TrimSpace(cfg.UserOutbox.ConsumerGroup); cfg.UserOutbox.ConsumerGroup == "" { cfg.UserOutbox.ConsumerGroup = defaults.UserOutbox.ConsumerGroup } + if cfg.UserOutbox.TaskConsumerGroup = strings.TrimSpace(cfg.UserOutbox.TaskConsumerGroup); cfg.UserOutbox.TaskConsumerGroup == "" { + cfg.UserOutbox.TaskConsumerGroup = defaults.UserOutbox.TaskConsumerGroup + } if cfg.UserOutbox.ConsumerMaxReconsumeTimes <= 0 { cfg.UserOutbox.ConsumerMaxReconsumeTimes = defaults.UserOutbox.ConsumerMaxReconsumeTimes } diff --git a/services/activity-service/internal/domain/task/task.go b/services/activity-service/internal/domain/task/task.go index 7aa06156..87146c20 100644 --- a/services/activity-service/internal/domain/task/task.go +++ b/services/activity-service/internal/domain/task/task.go @@ -9,6 +9,26 @@ const ( CycleLifetime = "lifetime" + AudienceAll = "all" + AudienceNewbie = "newbie" + + ActionNone = "none" + ActionRoomRandom = "room_random" + ActionRoomRandomGame = "room_random_game" + ActionWallet = "wallet" + ActionGiftPanel = "gift_panel" + ActionGiftPanelSpecific = "gift_panel_specific" + + MetricGameSpendCoin = "game_spend_coin" + MetricGiftSpendCoin = "gift_spend_coin" + MetricGiftSendCount = "gift_send_count" + MetricLuckyGiftSpendCoin = "lucky_gift_spend_coin" + MetricLuckyGiftSendCount = "lucky_gift_send_count" + MetricRechargeCoin = "recharge_coin" + MetricCPRelationshipCreated = "cp_relationship_created" + MetricMicOnlineMinute = "mic_online_minute" + MetricMoodPublishCount = "mood_publish_count" + StatusDraft = "draft" StatusActive = "active" StatusPaused = "paused" @@ -29,26 +49,33 @@ const ( // Definition 是 task_definitions 的当前版本读模型;App 查询只读这张表和用户进度表。 type Definition struct { - AppCode string - TaskID string - TaskType string - Category string - MetricType string - Title string - Description string - TargetValue int64 - TargetUnit string - RewardCoinAmount int64 - Status string - SortOrder int32 - Version int64 - CurrentVersionID int64 - EffectiveFromMS int64 - EffectiveToMS int64 - CreatedByAdminID int64 - UpdatedByAdminID int64 - CreatedAtMS int64 - UpdatedAtMS int64 + AppCode string + TaskID string + TaskType string + Category string + MetricType string + Title string + Description string + AudienceType string + IconKey string + IconURL string + ActionType string + ActionParam string + ActionPayloadJSON string + DimensionFilterJSON string + TargetValue int64 + TargetUnit string + RewardCoinAmount int64 + Status string + SortOrder int32 + Version int64 + CurrentVersionID int64 + EffectiveFromMS int64 + EffectiveToMS int64 + CreatedByAdminID int64 + UpdatedByAdminID int64 + CreatedAtMS int64 + UpdatedAtMS int64 } // Progress 是 user_task_progress 的用户维度状态;target/reward 使用创建进度时的任务快照。 @@ -105,31 +132,39 @@ type DefinitionQuery struct { // DefinitionCommand 是后台创建/编辑任务定义的命令快照。 type DefinitionCommand struct { - TaskID string - TaskType string - Category string - MetricType string - Title string - Description string - TargetValue int64 - TargetUnit string - RewardCoinAmount int64 - Status string - SortOrder int32 - EffectiveFromMS int64 - EffectiveToMS int64 - OperatorAdminID int64 + TaskID string + TaskType string + Category string + MetricType string + Title string + Description string + AudienceType string + IconKey string + IconURL string + ActionType string + ActionParam string + ActionPayloadJSON string + DimensionFilterJSON string + TargetValue int64 + TargetUnit string + RewardCoinAmount int64 + Status string + SortOrder int32 + EffectiveFromMS int64 + EffectiveToMS int64 + OperatorAdminID int64 } // Event 是由 wallet/user/room outbox 派生的任务进度事实。 type Event struct { - EventID string - EventType string - SourceService string - UserID int64 - MetricType string - Value int64 - OccurredAtMS int64 + EventID string + EventType string + SourceService string + UserID int64 + MetricType string + Value int64 + OccurredAtMS int64 + DimensionsJSON string } // EventResult 表达事件消费幂等结果。 diff --git a/services/activity-service/internal/service/task/service.go b/services/activity-service/internal/service/task/service.go index 52c64330..e50f2be0 100644 --- a/services/activity-service/internal/service/task/service.go +++ b/services/activity-service/internal/service/task/service.go @@ -2,11 +2,14 @@ package task import ( "context" + "encoding/json" "sort" "strings" "time" "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + roomeventsv1 "hyapp.local/api/proto/events/room/v1" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" "hyapp/pkg/xerr" @@ -171,13 +174,22 @@ func (s *Service) ConsumeTaskEvent(ctx context.Context, event taskdomain.Event) if err := s.requireRepository(); err != nil { return taskdomain.EventResult{}, err } + // 入口层只接受服务端事实事件,先统一裁剪字符串,避免上游空格导致同一个指标或事件 ID 被拆成两类。 event.EventID = strings.TrimSpace(event.EventID) event.EventType = strings.TrimSpace(event.EventType) event.SourceService = strings.TrimSpace(event.SourceService) event.MetricType = strings.TrimSpace(event.MetricType) + // dimensions_json 是后台维度过滤的唯一匹配依据;这里强制为对象 JSON,避免数组/字符串透传后让仓储层匹配语义变得不确定。 + dimensionsJSON, err := normalizeObjectJSON(event.DimensionsJSON) + if err != nil { + return taskdomain.EventResult{}, xerr.New(xerr.InvalidArgument, "dimensions_json is invalid") + } + event.DimensionsJSON = dimensionsJSON + // 任务进度不接受客户端自报,也不接受 0 增量;没有可追溯的 event_id/source/metric 时不能写入幂等消费表。 if event.EventID == "" || event.EventType == "" || event.SourceService == "" || event.UserID <= 0 || event.MetricType == "" || event.Value <= 0 { return taskdomain.EventResult{}, xerr.New(xerr.InvalidArgument, "task event is incomplete") } + // 上游没有精确事件时间时使用 activity-service 当前时间;每日任务周期由 occurred_at_ms 决定,不由 MQ 消费时间决定。 if event.OccurredAtMS <= 0 { event.OccurredAtMS = s.now().UnixMilli() } @@ -185,6 +197,91 @@ func (s *Service) ConsumeTaskEvent(ctx context.Context, event taskdomain.Event) return s.repository.ConsumeTaskEvent(ctx, event, cycleKey, s.now().UnixMilli()) } +// HandleRoomEvent 把 room-service 已提交送礼事实投影为任务进度事件。 +// 这里不读取房间或礼物当前状态,所有判断都基于 outbox 快照,保证重放时进度和第一次消费完全一致。 +func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.EventEnvelope) (int32, error) { + if s == nil || envelope == nil || envelope.GetEventType() != "RoomGiftSent" { + return 0, nil + } + var gift roomeventsv1.RoomGiftSent + if err := proto.Unmarshal(envelope.GetBody(), &gift); err != nil { + return 0, err + } + if gift.GetSenderUserId() <= 0 || gift.GetGiftId() == "" || gift.GetGiftCount() <= 0 { + return 0, nil + } + // coin_spent 是钱包真实扣费金额;旧 outbox 可能只有礼物面值,兜底用 gift_value 保持历史事件可重放。 + coinSpent := gift.GetCoinSpent() + if coinSpent <= 0 { + coinSpent = gift.GetGiftValue() + } + dimensions, err := roomGiftTaskDimensions(envelope, &gift) + if err != nil { + return 0, err + } + eventCtx := appcode.WithContext(ctx, envelope.GetAppCode()) + // 一次送礼同时满足“消耗金币”和“赠送次数”两个指标;用不同 event_id 后缀隔离幂等键,避免两个指标互相覆盖。 + events := []taskdomain.Event{ + { + EventID: envelope.GetEventId() + ":task:gift_spend", + EventType: envelope.GetEventType(), + SourceService: "room-service", + UserID: gift.GetSenderUserId(), + MetricType: taskdomain.MetricGiftSpendCoin, + Value: coinSpent, + OccurredAtMS: envelope.GetOccurredAtMs(), + DimensionsJSON: dimensions, + }, + { + EventID: envelope.GetEventId() + ":task:gift_count", + EventType: envelope.GetEventType(), + SourceService: "room-service", + UserID: gift.GetSenderUserId(), + MetricType: taskdomain.MetricGiftSendCount, + Value: int64(gift.GetGiftCount()), + OccurredAtMS: envelope.GetOccurredAtMs(), + DimensionsJSON: dimensions, + }, + } + // 幸运礼物是普通送礼的子集;普通礼物指标照常累计,额外再写幸运礼物专属指标,方便后台用不同任务口径配置。 + if isLuckyGiftTaskEvent(&gift) { + events = append(events, + taskdomain.Event{ + EventID: envelope.GetEventId() + ":task:lucky_gift_spend", + EventType: envelope.GetEventType(), + SourceService: "room-service", + UserID: gift.GetSenderUserId(), + MetricType: taskdomain.MetricLuckyGiftSpendCoin, + Value: coinSpent, + OccurredAtMS: envelope.GetOccurredAtMs(), + DimensionsJSON: dimensions, + }, + taskdomain.Event{ + EventID: envelope.GetEventId() + ":task:lucky_gift_count", + EventType: envelope.GetEventType(), + SourceService: "room-service", + UserID: gift.GetSenderUserId(), + MetricType: taskdomain.MetricLuckyGiftSendCount, + Value: int64(gift.GetGiftCount()), + OccurredAtMS: envelope.GetOccurredAtMs(), + DimensionsJSON: dimensions, + }, + ) + } + consumed := int32(0) + for _, event := range events { + // 任意一个指标写入失败都返回错误给 MQ/gRPC 调用方重试;事件幂等表保证已成功的指标不会重复累加。 + result, err := s.ConsumeTaskEvent(eventCtx, event) + if err != nil { + return consumed, err + } + if result.Status == taskdomain.EventStatusConsumed { + consumed += result.MatchedTaskCount + } + } + return consumed, nil +} + // ListTaskDefinitions 返回后台任务配置列表;状态筛选不影响 App 查询口径。 func (s *Service) ListTaskDefinitions(ctx context.Context, query taskdomain.DefinitionQuery) ([]taskdomain.Definition, int64, error) { if err := s.requireRepository(); err != nil { @@ -312,6 +409,13 @@ func normalizeDefinitionCommand(command taskdomain.DefinitionCommand) taskdomain command.MetricType = strings.ToLower(strings.TrimSpace(command.MetricType)) command.Title = strings.TrimSpace(command.Title) command.Description = strings.TrimSpace(command.Description) + command.AudienceType = normalizeAudienceType(command.AudienceType) + command.IconKey = strings.TrimSpace(command.IconKey) + command.IconURL = strings.TrimSpace(command.IconURL) + command.ActionType = normalizeActionType(command.ActionType) + command.ActionParam = strings.TrimSpace(command.ActionParam) + command.ActionPayloadJSON = normalizeJSONForCommand(command.ActionPayloadJSON) + command.DimensionFilterJSON = normalizeJSONForCommand(command.DimensionFilterJSON) command.TargetUnit = strings.ToLower(strings.TrimSpace(command.TargetUnit)) command.Status = strings.TrimSpace(command.Status) if command.Status == "" { @@ -332,12 +436,27 @@ func normalizeTaskType(value string) string { } func validateDefinitionCommand(command taskdomain.DefinitionCommand) error { + // daily 按 UTC 日周期生成进度,exclusive 使用 lifetime 周期;新增“新人专属+每日”用 audience_type 表达,不新增 task_type。 if command.TaskType != taskdomain.TypeDaily && command.TaskType != taskdomain.TypeExclusive { return xerr.New(xerr.InvalidArgument, "task_type is invalid") } if command.Category == "" || command.MetricType == "" || command.Title == "" { return xerr.New(xerr.InvalidArgument, "task category, metric_type and title are required") } + // audience/action 只允许固定枚举,避免后台写入 App 无法识别的展示人群或跳转类型。 + if !validAudienceType(command.AudienceType) { + return xerr.New(xerr.InvalidArgument, "audience_type is invalid") + } + if !validActionType(command.ActionType) { + return xerr.New(xerr.InvalidArgument, "action_type is invalid") + } + // 扩展跳转和维度过滤必须是对象 JSON;空值会在 normalizeDefinitionCommand 中落成 {},App 和事件匹配都按对象处理。 + if _, err := normalizeObjectJSON(command.ActionPayloadJSON); err != nil { + return xerr.New(xerr.InvalidArgument, "action_payload_json is invalid") + } + if _, err := normalizeObjectJSON(command.DimensionFilterJSON); err != nil { + return xerr.New(xerr.InvalidArgument, "dimension_filter_json is invalid") + } if command.TargetValue <= 0 || command.RewardCoinAmount <= 0 { return xerr.New(xerr.InvalidArgument, "target_value and reward_coin_amount must be positive") } @@ -356,6 +475,114 @@ func validateDefinitionCommand(command taskdomain.DefinitionCommand) error { return nil } +func roomGiftTaskDimensions(envelope *roomeventsv1.EventEnvelope, gift *roomeventsv1.RoomGiftSent) (string, error) { + // 维度只记录任务匹配和排障需要的事实字段;后台 dimension_filter_json 可以按 gift_id、pool_id、room_id 等字段精确限定任务。 + payload := map[string]any{ + "room_id": envelope.GetRoomId(), + "room_version": envelope.GetRoomVersion(), + "gift_id": gift.GetGiftId(), + "gift_count": gift.GetGiftCount(), + "gift_value": gift.GetGiftValue(), + "billing_receipt_id": gift.GetBillingReceiptId(), + "command_id": gift.GetCommandId(), + "pool_id": gift.GetPoolId(), + "coin_spent": gift.GetCoinSpent(), + "visible_region_id": gift.GetVisibleRegionId(), + "country_id": gift.GetCountryId(), + "region_id": gift.GetRegionId(), + "gift_type_code": strings.ToLower(strings.TrimSpace(gift.GetGiftTypeCode())), + "cp_relation_type": gift.GetCpRelationType(), + "target_user_id": gift.GetTargetUserId(), + } + data, err := json.Marshal(payload) + if err != nil { + return "", err + } + return string(data), nil +} + +func isLuckyGiftTaskEvent(gift *roomeventsv1.RoomGiftSent) bool { + if gift == nil { + return false + } + // 兼容两种上游表达:礼物类型直接标 lucky,或通过幸运池 pool_id 表明这次送礼进入幸运玩法。 + giftType := strings.ToLower(strings.TrimSpace(gift.GetGiftTypeCode())) + poolID := strings.ToLower(strings.TrimSpace(gift.GetPoolId())) + return strings.Contains(giftType, "lucky") || strings.Contains(poolID, "lucky") +} + +func normalizeAudienceType(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "", taskdomain.AudienceAll: + return taskdomain.AudienceAll + case taskdomain.AudienceNewbie: + return taskdomain.AudienceNewbie + default: + return strings.ToLower(strings.TrimSpace(value)) + } +} + +func validAudienceType(value string) bool { + switch value { + case taskdomain.AudienceAll, taskdomain.AudienceNewbie: + return true + default: + return false + } +} + +func normalizeActionType(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "", taskdomain.ActionNone: + return taskdomain.ActionNone + case taskdomain.ActionRoomRandom: + return taskdomain.ActionRoomRandom + case taskdomain.ActionRoomRandomGame: + return taskdomain.ActionRoomRandomGame + case taskdomain.ActionWallet: + return taskdomain.ActionWallet + case taskdomain.ActionGiftPanel: + return taskdomain.ActionGiftPanel + case taskdomain.ActionGiftPanelSpecific: + return taskdomain.ActionGiftPanelSpecific + default: + return strings.ToLower(strings.TrimSpace(value)) + } +} + +func validActionType(value string) bool { + switch value { + case taskdomain.ActionNone, taskdomain.ActionRoomRandom, taskdomain.ActionRoomRandomGame, taskdomain.ActionWallet, taskdomain.ActionGiftPanel, taskdomain.ActionGiftPanelSpecific: + return true + default: + return false + } +} + +func normalizeObjectJSON(value string) (string, error) { + value = strings.TrimSpace(value) + if value == "" { + return "{}", nil + } + var decoded map[string]any + if err := json.Unmarshal([]byte(value), &decoded); err != nil { + return "", err + } + data, err := json.Marshal(decoded) + if err != nil { + return "", err + } + return string(data), nil +} + +func normalizeJSONForCommand(value string) string { + normalized, err := normalizeObjectJSON(value) + if err != nil { + return strings.TrimSpace(value) + } + return normalized +} + func validTargetUnit(value string) bool { switch value { case "count", "minute", "coin": diff --git a/services/activity-service/internal/service/task/service_test.go b/services/activity-service/internal/service/task/service_test.go index 51a4dcaf..e3e46a9a 100644 --- a/services/activity-service/internal/service/task/service_test.go +++ b/services/activity-service/internal/service/task/service_test.go @@ -101,6 +101,71 @@ func TestTaskEventQueryAndClaimFlow(t *testing.T) { } } +func TestTaskEventDimensionFilter(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := taskservice.New(repository, &fakeWalletClient{}) + now := time.Date(2026, 5, 9, 10, 0, 0, 0, time.UTC) + svc.SetClock(func() time.Time { return now }) + + definition, created, err := svc.UpsertTaskDefinition(context.Background(), taskdomain.DefinitionCommand{ + TaskType: taskdomain.TypeDaily, + Category: "gift", + MetricType: taskdomain.MetricGiftSpendCoin, + Title: "send specified gift", + DimensionFilterJSON: `{"gift_id":"gift_1001"}`, + TargetValue: 100, + TargetUnit: "coin", + RewardCoinAmount: 10, + Status: taskdomain.StatusActive, + OperatorAdminID: 90001, + }) + if err != nil || !created { + t.Fatalf("create filtered task definition failed: created=%v err=%v", created, err) + } + + skipped, err := svc.ConsumeTaskEvent(context.Background(), taskdomain.Event{ + EventID: "evt-gift-filter-skip", + EventType: "RoomGiftSent", + SourceService: "room-service", + UserID: 10001, + MetricType: taskdomain.MetricGiftSpendCoin, + Value: 100, + OccurredAtMS: now.UnixMilli(), + DimensionsJSON: `{"gift_id":"gift_2002"}`, + }) + if err != nil { + t.Fatalf("ConsumeTaskEvent skip failed: %v", err) + } + if skipped.Status != taskdomain.EventStatusSkipped || skipped.MatchedTaskCount != 0 { + t.Fatalf("non-matching dimensions should skip: %+v", skipped) + } + + consumed, err := svc.ConsumeTaskEvent(context.Background(), taskdomain.Event{ + EventID: "evt-gift-filter-hit", + EventType: "RoomGiftSent", + SourceService: "room-service", + UserID: 10001, + MetricType: taskdomain.MetricGiftSpendCoin, + Value: 100, + OccurredAtMS: now.UnixMilli(), + DimensionsJSON: `{"gift_id":"gift_1001"}`, + }) + if err != nil { + t.Fatalf("ConsumeTaskEvent hit failed: %v", err) + } + if consumed.Status != taskdomain.EventStatusConsumed || consumed.MatchedTaskCount != 1 { + t.Fatalf("matching dimensions should consume: %+v", consumed) + } + + list, err := svc.ListUserTasks(context.Background(), 10001) + if err != nil { + t.Fatalf("ListUserTasks failed: %v", err) + } + if len(list.Sections[0].Items) != 1 || list.Sections[0].Items[0].TaskID != definition.TaskID || list.Sections[0].Items[0].ProgressValue != 100 { + t.Fatalf("filtered progress mismatch: %+v", list.Sections[0].Items) + } +} + type fakeWalletClient struct { calls int } diff --git a/services/activity-service/internal/storage/mysql/repository.go b/services/activity-service/internal/storage/mysql/repository.go index b2e19581..ae076090 100644 --- a/services/activity-service/internal/storage/mysql/repository.go +++ b/services/activity-service/internal/storage/mysql/repository.go @@ -81,6 +81,58 @@ func (r *Repository) Migrate(ctx context.Context) error { if err := r.ensureCumulativeRechargeRewardTables(ctx); err != nil { return err } + if err := r.ensureTaskDefinitionMetadataColumns(ctx); err != nil { + return err + } + return nil +} + +func (r *Repository) ensureTaskDefinitionMetadataColumns(ctx context.Context) error { + // 本地和测试环境会走自动迁移,逐列检查避免旧库重复启动时报 duplicate column;线上仍可用 deploy/mysql/migrations 的显式 SQL。 + additions := []struct { + column string + sql string + }{ + {"audience_type", `ALTER TABLE task_definitions ADD COLUMN audience_type VARCHAR(32) NOT NULL DEFAULT 'all' COMMENT '适用人群:all/newbie' AFTER description`}, + {"icon_key", `ALTER TABLE task_definitions ADD COLUMN icon_key VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'App 任务图标键' AFTER audience_type`}, + {"icon_url", `ALTER TABLE task_definitions ADD COLUMN icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'App 任务图标 URL' AFTER icon_key`}, + {"action_type", `ALTER TABLE task_definitions ADD COLUMN action_type VARCHAR(64) NOT NULL DEFAULT 'none' COMMENT 'App 跳转类型' AFTER icon_url`}, + {"action_param", `ALTER TABLE task_definitions ADD COLUMN action_param VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'App 跳转参数' AFTER action_type`}, + {"action_payload_json", `ALTER TABLE task_definitions ADD COLUMN action_payload_json JSON NULL COMMENT 'App 跳转扩展 JSON' AFTER action_param`}, + {"dimension_filter_json", `ALTER TABLE task_definitions ADD COLUMN dimension_filter_json JSON NULL COMMENT '任务事件维度过滤 JSON' AFTER action_payload_json`}, + } + for _, addition := range additions { + exists, err := r.columnExists(ctx, "task_definitions", addition.column) + if err != nil { + return err + } + if !exists { + // 任务定义新增的是展示、跳转和事件过滤元数据,默认值保证旧任务无需人工补字段即可继续查询和领取。 + if _, err := r.db.ExecContext(ctx, addition.sql); err != nil { + return err + } + } + } + eventAdditions := []struct { + column string + sql string + }{ + {"metric_type", `ALTER TABLE task_event_consumption ADD COLUMN metric_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '任务指标类型' AFTER status`}, + {"value_delta", `ALTER TABLE task_event_consumption ADD COLUMN value_delta BIGINT NOT NULL DEFAULT 0 COMMENT '本次事件进度增量' AFTER metric_type`}, + {"dimensions_json", `ALTER TABLE task_event_consumption ADD COLUMN dimensions_json JSON NULL COMMENT '事件维度快照 JSON' AFTER value_delta`}, + } + for _, addition := range eventAdditions { + exists, err := r.columnExists(ctx, "task_event_consumption", addition.column) + if err != nil { + return err + } + if !exists { + // 事件消费表补充 metric/value/dimensions 快照,主要用于排查“事件到了但任务没涨”的过滤原因。 + if _, err := r.db.ExecContext(ctx, addition.sql); err != nil { + return err + } + } + } return nil } diff --git a/services/activity-service/internal/storage/mysql/task_repository.go b/services/activity-service/internal/storage/mysql/task_repository.go index f64f67ef..524daf91 100644 --- a/services/activity-service/internal/storage/mysql/task_repository.go +++ b/services/activity-service/internal/storage/mysql/task_repository.go @@ -6,6 +6,8 @@ import ( "encoding/json" "errors" "fmt" + "reflect" + "strconv" "strings" "hyapp/pkg/appcode" @@ -21,6 +23,9 @@ func (r *Repository) ListVisibleDefinitions(ctx context.Context, nowMS int64) ([ } rows, err := r.db.QueryContext(ctx, ` SELECT app_code, task_id, task_type, category, metric_type, title, description, + audience_type, icon_key, icon_url, action_type, action_param, + COALESCE(CAST(action_payload_json AS CHAR), '{}'), + COALESCE(CAST(dimension_filter_json AS CHAR), '{}'), target_value, target_unit, reward_coin_amount, status, sort_order, version, current_task_version_id, effective_from_ms, effective_to_ms, created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms @@ -87,6 +92,7 @@ func (r *Repository) ConsumeTaskEvent(ctx context.Context, event taskdomain.Even } defer func() { _ = tx.Rollback() }() + // 先插入事件消费表再做进度变更,event_id 是全局幂等键;重复事件直接返回已有状态,不再读取任务配置或累加进度。 inserted, existingStatus, err := r.insertTaskEventConsumption(ctx, tx, event, cycleKey, nowMS) if err != nil { return taskdomain.EventResult{}, err @@ -95,14 +101,24 @@ func (r *Repository) ConsumeTaskEvent(ctx context.Context, event taskdomain.Even return taskdomain.EventResult{EventID: event.EventID, Status: existingStatus}, nil } + // 只锁定同 app、同 metric、当前事件时间有效的 active 配置;后台后续编辑不会影响这次事件已经选择的任务集合。 definitions, err := r.listDefinitionsForEvent(ctx, tx, event.MetricType, event.OccurredAtMS) if err != nil { return taskdomain.EventResult{}, err } matched := int32(0) for _, definition := range definitions { + // 维度过滤在写进度前执行;例如指定 gift_id/game_id 不匹配时,这条事实被消费但不会推动该任务。 + matchedFilter, err := dimensionFilterMatches(definition.DimensionFilterJSON, event.DimensionsJSON) + if err != nil { + return taskdomain.EventResult{}, err + } + if !matchedFilter { + continue + } progressCycle := cycleKey if definition.TaskType == taskdomain.TypeExclusive { + // 新手/一次性任务使用 lifetime 周期,完成或领取后永久不再被每日 cycle 重置。 progressCycle = taskdomain.CycleLifetime } changed, err := r.applyTaskProgressDelta(ctx, tx, event.UserID, definition, progressCycle, event.Value, nowMS) @@ -116,6 +132,7 @@ func (r *Repository) ConsumeTaskEvent(ctx context.Context, event taskdomain.Even status := taskdomain.EventStatusConsumed skipReason := "" if matched == 0 { + // 没有匹配任务也要把事件标记为 skipped,避免 MQ 对“当前没配置任务”的历史事实无限重试。 status = taskdomain.EventStatusSkipped skipReason = "no_matching_task" } @@ -151,6 +168,9 @@ func (r *Repository) ListTaskDefinitions(ctx context.Context, query taskdomain.D args = append(args, pageSize, (page-1)*pageSize) rows, err := r.db.QueryContext(ctx, ` SELECT app_code, task_id, task_type, category, metric_type, title, description, + audience_type, icon_key, icon_url, action_type, action_param, + COALESCE(CAST(action_payload_json AS CHAR), '{}'), + COALESCE(CAST(dimension_filter_json AS CHAR), '{}'), target_value, target_unit, reward_coin_amount, status, sort_order, version, current_task_version_id, effective_from_ms, effective_to_ms, created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms @@ -200,6 +220,13 @@ func (r *Repository) UpsertTaskDefinition(ctx context.Context, command taskdomai next.MetricType = command.MetricType next.Title = command.Title next.Description = command.Description + next.AudienceType = command.AudienceType + next.IconKey = command.IconKey + next.IconURL = command.IconURL + next.ActionType = command.ActionType + next.ActionParam = command.ActionParam + next.ActionPayloadJSON = command.ActionPayloadJSON + next.DimensionFilterJSON = command.DimensionFilterJSON next.TargetValue = command.TargetValue next.TargetUnit = command.TargetUnit next.RewardCoinAmount = command.RewardCoinAmount @@ -220,11 +247,15 @@ func (r *Repository) UpsertTaskDefinition(ctx context.Context, command taskdomai if _, err := tx.ExecContext(ctx, ` UPDATE task_definitions SET task_type = ?, category = ?, metric_type = ?, title = ?, description = ?, + audience_type = ?, icon_key = ?, icon_url = ?, action_type = ?, action_param = ?, + action_payload_json = CAST(? AS JSON), dimension_filter_json = CAST(? AS JSON), target_value = ?, target_unit = ?, reward_coin_amount = ?, status = ?, sort_order = ?, version = ?, current_task_version_id = ?, effective_from_ms = ?, effective_to_ms = ?, updated_by_admin_id = ?, updated_at_ms = ? WHERE app_code = ? AND task_id = ?`, next.TaskType, next.Category, next.MetricType, next.Title, next.Description, + next.AudienceType, next.IconKey, next.IconURL, next.ActionType, next.ActionParam, + next.ActionPayloadJSON, next.DimensionFilterJSON, next.TargetValue, next.TargetUnit, next.RewardCoinAmount, next.Status, next.SortOrder, next.Version, next.CurrentVersionID, next.EffectiveFromMS, next.EffectiveToMS, next.UpdatedByAdminID, next.UpdatedAtMS, appcode.FromContext(ctx), next.TaskID, @@ -406,12 +437,15 @@ func (r *Repository) MarkTaskClaimFailed(ctx context.Context, claimID string, fa } func (r *Repository) insertTaskEventConsumption(ctx context.Context, tx *sql.Tx, event taskdomain.Event, cycleKey string, nowMS int64) (bool, string, error) { + // 初始状态先写 consumed 占位,事务末尾再按匹配结果改成 consumed/skipped;同事务回滚时占位记录也会一起撤销。 _, err := tx.ExecContext(ctx, ` INSERT INTO task_event_consumption ( app_code, event_id, event_type, source_service, user_id, task_day, status, + metric_type, value_delta, dimensions_json, skip_reason, created_at_ms, consumed_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, 'consumed', '', ?, 0)`, - appcode.FromContext(ctx), event.EventID, event.EventType, event.SourceService, event.UserID, cycleKey, nowMS, + ) VALUES (?, ?, ?, ?, ?, ?, 'consumed', ?, ?, CAST(? AS JSON), '', ?, 0)`, + appcode.FromContext(ctx), event.EventID, event.EventType, event.SourceService, event.UserID, cycleKey, + event.MetricType, event.Value, event.DimensionsJSON, nowMS, ) if err == nil { return true, "", nil @@ -419,6 +453,7 @@ func (r *Repository) insertTaskEventConsumption(ctx context.Context, tx *sql.Tx, if !isMySQLDuplicate(err) { return false, "", err } + // 重复事件说明之前已经提交过消费事务;读取原状态返回给调用方,让 MQ/gRPC 重放保持幂等成功。 var status string if err := tx.QueryRowContext(ctx, ` SELECT status FROM task_event_consumption @@ -431,8 +466,12 @@ func (r *Repository) insertTaskEventConsumption(ctx context.Context, tx *sql.Tx, } func (r *Repository) listDefinitionsForEvent(ctx context.Context, tx *sql.Tx, metricType string, occurredAtMS int64) ([]taskdomain.Definition, error) { + // FOR UPDATE 锁住配置快照,保证同一个事件事务内读取版本和写入用户进度时不会遇到半更新配置。 rows, err := tx.QueryContext(ctx, ` SELECT app_code, task_id, task_type, category, metric_type, title, description, + audience_type, icon_key, icon_url, action_type, action_param, + COALESCE(CAST(action_payload_json AS CHAR), '{}'), + COALESCE(CAST(dimension_filter_json AS CHAR), '{}'), target_value, target_unit, reward_coin_amount, status, sort_order, version, current_task_version_id, effective_from_ms, effective_to_ms, created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms @@ -457,6 +496,7 @@ func (r *Repository) applyTaskProgressDelta(ctx context.Context, tx *sql.Tx, use if !errors.Is(err, sql.ErrNoRows) { return false, err } + // 首次命中任务时把配置版本、目标值和奖励金额一起快照到用户进度,后续后台改配置不会改变已产生进度的领取口径。 status := taskdomain.StatusInProgress completedAtMS := int64(0) if delta >= definition.TargetValue { @@ -474,12 +514,14 @@ func (r *Repository) applyTaskProgressDelta(ctx context.Context, tx *sql.Tx, use return err == nil, err } if definition.TaskType == taskdomain.TypeExclusive && (progress.Status == taskdomain.StatusCompleted || progress.Status == taskdomain.StatusClaimed) { + // 一次性任务完成后只允许领取,不再接受后续事实事件累加;这样重放旧事件不会把已完成新手任务继续推高。 return false, nil } nextValue := progress.ProgressValue + delta nextStatus := progress.Status completedAtMS := progress.CompletedAtMS if nextStatus == taskdomain.StatusInProgress && nextValue >= progress.TargetValue { + // 只有从 in_progress 首次跨过目标值时写 completed_at_ms,重复事件被幂等表挡住,已完成任务不会刷新完成时间。 nextStatus = taskdomain.StatusCompleted completedAtMS = nowMS } @@ -494,25 +536,32 @@ func (r *Repository) applyTaskProgressDelta(ctx context.Context, tx *sql.Tx, use func (r *Repository) createTaskDefinition(ctx context.Context, tx *sql.Tx, command taskdomain.DefinitionCommand, nowMS int64) (taskdomain.Definition, error) { definition := taskdomain.Definition{ - AppCode: appcode.FromContext(ctx), - TaskID: idgen.New("task"), - TaskType: command.TaskType, - Category: command.Category, - MetricType: command.MetricType, - Title: command.Title, - Description: command.Description, - TargetValue: command.TargetValue, - TargetUnit: command.TargetUnit, - RewardCoinAmount: command.RewardCoinAmount, - Status: command.Status, - SortOrder: command.SortOrder, - Version: 1, - EffectiveFromMS: command.EffectiveFromMS, - EffectiveToMS: command.EffectiveToMS, - CreatedByAdminID: command.OperatorAdminID, - UpdatedByAdminID: command.OperatorAdminID, - CreatedAtMS: nowMS, - UpdatedAtMS: nowMS, + AppCode: appcode.FromContext(ctx), + TaskID: idgen.New("task"), + TaskType: command.TaskType, + Category: command.Category, + MetricType: command.MetricType, + Title: command.Title, + Description: command.Description, + AudienceType: command.AudienceType, + IconKey: command.IconKey, + IconURL: command.IconURL, + ActionType: command.ActionType, + ActionParam: command.ActionParam, + ActionPayloadJSON: command.ActionPayloadJSON, + DimensionFilterJSON: command.DimensionFilterJSON, + TargetValue: command.TargetValue, + TargetUnit: command.TargetUnit, + RewardCoinAmount: command.RewardCoinAmount, + Status: command.Status, + SortOrder: command.SortOrder, + Version: 1, + EffectiveFromMS: command.EffectiveFromMS, + EffectiveToMS: command.EffectiveToMS, + CreatedByAdminID: command.OperatorAdminID, + UpdatedByAdminID: command.OperatorAdminID, + CreatedAtMS: nowMS, + UpdatedAtMS: nowMS, } versionID, err := r.insertTaskDefinitionVersion(ctx, tx, definition, nowMS) if err != nil { @@ -522,12 +571,15 @@ func (r *Repository) createTaskDefinition(ctx context.Context, tx *sql.Tx, comma _, err = tx.ExecContext(ctx, ` INSERT INTO task_definitions ( app_code, task_id, task_type, category, metric_type, title, description, + audience_type, icon_key, icon_url, action_type, action_param, action_payload_json, dimension_filter_json, target_value, target_unit, reward_coin_amount, status, sort_order, version, current_task_version_id, effective_from_ms, effective_to_ms, created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), CAST(? AS JSON), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, definition.AppCode, definition.TaskID, definition.TaskType, definition.Category, definition.MetricType, - definition.Title, definition.Description, definition.TargetValue, definition.TargetUnit, + definition.Title, definition.Description, definition.AudienceType, definition.IconKey, definition.IconURL, + definition.ActionType, definition.ActionParam, definition.ActionPayloadJSON, definition.DimensionFilterJSON, + definition.TargetValue, definition.TargetUnit, definition.RewardCoinAmount, definition.Status, definition.SortOrder, definition.Version, definition.CurrentVersionID, definition.EffectiveFromMS, definition.EffectiveToMS, definition.CreatedByAdminID, definition.UpdatedByAdminID, definition.CreatedAtMS, definition.UpdatedAtMS, @@ -559,6 +611,9 @@ func (r *Repository) insertTaskDefinitionVersion(ctx context.Context, tx *sql.Tx func (r *Repository) getTaskDefinition(ctx context.Context, taskID string) (taskdomain.Definition, error) { row := r.db.QueryRowContext(ctx, ` SELECT app_code, task_id, task_type, category, metric_type, title, description, + audience_type, icon_key, icon_url, action_type, action_param, + COALESCE(CAST(action_payload_json AS CHAR), '{}'), + COALESCE(CAST(dimension_filter_json AS CHAR), '{}'), target_value, target_unit, reward_coin_amount, status, sort_order, version, current_task_version_id, effective_from_ms, effective_to_ms, created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms @@ -572,6 +627,9 @@ func (r *Repository) getTaskDefinition(ctx context.Context, taskID string) (task func (r *Repository) getTaskDefinitionForUpdate(ctx context.Context, tx *sql.Tx, taskID string) (taskdomain.Definition, error) { row := tx.QueryRowContext(ctx, ` SELECT app_code, task_id, task_type, category, metric_type, title, description, + audience_type, icon_key, icon_url, action_type, action_param, + COALESCE(CAST(action_payload_json AS CHAR), '{}'), + COALESCE(CAST(dimension_filter_json AS CHAR), '{}'), target_value, target_unit, reward_coin_amount, status, sort_order, version, current_task_version_id, effective_from_ms, effective_to_ms, created_by_admin_id, updated_by_admin_id, created_at_ms, updated_at_ms @@ -661,6 +719,13 @@ func scanDefinition(row rowScanner) (taskdomain.Definition, error) { &definition.MetricType, &definition.Title, &definition.Description, + &definition.AudienceType, + &definition.IconKey, + &definition.IconURL, + &definition.ActionType, + &definition.ActionParam, + &definition.ActionPayloadJSON, + &definition.DimensionFilterJSON, &definition.TargetValue, &definition.TargetUnit, &definition.RewardCoinAmount, @@ -747,14 +812,98 @@ func taskDefinitionWhere(ctx context.Context, query taskdomain.DefinitionQuery) } func taskDefinitionNeedsVersion(current taskdomain.Definition, next taskdomain.Definition) bool { + // 版本只跟领取和进度口径相关:展示字段、图标、跳转不建新版本;目标、奖励、人群或维度过滤变化必须生成新快照。 return current.TaskType != next.TaskType || current.Category != next.Category || + current.AudienceType != next.AudienceType || current.MetricType != next.MetricType || + current.DimensionFilterJSON != next.DimensionFilterJSON || current.TargetValue != next.TargetValue || current.TargetUnit != next.TargetUnit || current.RewardCoinAmount != next.RewardCoinAmount } +func dimensionFilterMatches(filterJSON string, dimensionsJSON string) (bool, error) { + filter, err := decodeTaskJSONObject(filterJSON) + if err != nil { + return false, err + } + if len(filter) == 0 { + // 空过滤代表所有同 metric 事件都计入,用于“任意礼物/任意游戏”类通用任务。 + return true, nil + } + dimensions, err := decodeTaskJSONObject(dimensionsJSON) + if err != nil { + return false, err + } + for key, expected := range filter { + actual, ok := dimensions[key] + if !ok { + // 后台指定了某个维度但事件没带该字段,必须跳过,不能用空值兜底命中。 + return false, nil + } + if !taskDimensionValueMatches(expected, actual) { + return false, nil + } + } + return true, nil +} + +func decodeTaskJSONObject(value string) (map[string]any, error) { + value = strings.TrimSpace(value) + if value == "" { + return map[string]any{}, nil + } + var decoded map[string]any + if err := json.Unmarshal([]byte(value), &decoded); err != nil { + return nil, err + } + if decoded == nil { + return map[string]any{}, nil + } + return decoded, nil +} + +func taskDimensionValueMatches(expected any, actual any) bool { + if candidates, ok := expected.([]any); ok { + // filter 支持数组,表示“任意一个值命中即可”,方便后台配置多个 gift_id/game_id。 + for _, candidate := range candidates { + if taskDimensionValueMatches(candidate, actual) { + return true + } + } + return false + } + expectedScalar, expectedOK := scalarTaskDimensionValue(expected) + actualScalar, actualOK := scalarTaskDimensionValue(actual) + if expectedOK && actualOK { + // JSON 数字、字符串和布尔都转成稳定字符串比较,避免 game_id 123 和 "123" 因编码差异无法命中。 + return expectedScalar == actualScalar + } + // 对象等复杂维度目前只做结构完全相等;任务过滤主路径仍建议使用标量字段。 + return reflect.DeepEqual(expected, actual) +} + +func scalarTaskDimensionValue(value any) (string, bool) { + switch typed := value.(type) { + case string: + return strings.TrimSpace(typed), true + case bool: + return strconv.FormatBool(typed), true + case float64: + if typed == float64(int64(typed)) { + return strconv.FormatInt(int64(typed), 10), true + } + return strconv.FormatFloat(typed, 'f', -1, 64), true + case json.Number: + return typed.String(), true + case nil: + return "", true + default: + return "", false + } +} + func normalizePageSize(value int32) int32 { if value <= 0 { return 20 diff --git a/services/activity-service/internal/transport/grpc/broadcast_server.go b/services/activity-service/internal/transport/grpc/broadcast_server.go index c6580c8b..d3f7edd2 100644 --- a/services/activity-service/internal/transport/grpc/broadcast_server.go +++ b/services/activity-service/internal/transport/grpc/broadcast_server.go @@ -10,6 +10,7 @@ import ( broadcastservice "hyapp/services/activity-service/internal/service/broadcast" growthservice "hyapp/services/activity-service/internal/service/growth" roomturnoverrewardservice "hyapp/services/activity-service/internal/service/roomturnoverreward" + taskservice "hyapp/services/activity-service/internal/service/task" weeklystarservice "hyapp/services/activity-service/internal/service/weeklystar" ) @@ -21,6 +22,7 @@ type BroadcastServer struct { svc *broadcastservice.Service growth *growthservice.Service + task *taskservice.Service roomReward *roomturnoverrewardservice.Service weeklyStar *weeklystarservice.Service } @@ -29,10 +31,11 @@ type BroadcastServer struct { // room-service 的 outbox 事件只投递一次,但 activity-service 内部有多条独立投影: // 播报负责 IM 展示,growth 负责等级进度,weekly-star 负责指定礼物榜单,roomReward 负责房间流水。 // 这里把这些模块都挂到同一个 gRPC 入口,保证本地直连投递和 MQ 投递使用同一组业务副作用。 -func NewBroadcastServer(svc *broadcastservice.Service, growthSvc *growthservice.Service, weeklyStarSvc *weeklystarservice.Service, roomRewardSvc ...*roomturnoverrewardservice.Service) *BroadcastServer { +func NewBroadcastServer(svc *broadcastservice.Service, growthSvc *growthservice.Service, weeklyStarSvc *weeklystarservice.Service, taskSvc *taskservice.Service, roomRewardSvc ...*roomturnoverrewardservice.Service) *BroadcastServer { server := &BroadcastServer{svc: svc} server.growth = growthSvc server.weeklyStar = weeklyStarSvc + server.task = taskSvc if len(roomRewardSvc) > 0 { server.roomReward = roomRewardSvc[0] } @@ -126,6 +129,11 @@ func (s *BroadcastServer) ConsumeRoomEvent(ctx context.Context, req *activityv1. return nil, xerr.ToGRPCError(err) } } + if s.task != nil { + if _, err := s.task.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil { + return nil, xerr.ToGRPCError(err) + } + } if s.roomReward != nil { if _, err := s.roomReward.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil { return nil, xerr.ToGRPCError(err) diff --git a/services/activity-service/internal/transport/grpc/task_flow_integration_test.go b/services/activity-service/internal/transport/grpc/task_flow_integration_test.go new file mode 100644 index 00000000..39e55def --- /dev/null +++ b/services/activity-service/internal/transport/grpc/task_flow_integration_test.go @@ -0,0 +1,242 @@ +package grpc + +import ( + "context" + "net" + "testing" + "time" + + activityv1 "hyapp.local/api/proto/activity/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + taskdomain "hyapp/services/activity-service/internal/domain/task" + taskservice "hyapp/services/activity-service/internal/service/task" + "hyapp/services/activity-service/internal/testutil/mysqltest" + + grpcpkg "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" +) + +func TestTaskGRPCInterfaceFlow(t *testing.T) { + repository := mysqltest.NewRepository(t) + wallet := &taskFlowWallet{} + service := taskservice.New(repository, wallet) + now := time.Date(2026, 5, 9, 10, 0, 0, 0, time.UTC) + service.SetClock(func() time.Time { return now }) + + conn := newTaskFlowGRPCConn(t, service) + adminClient := activityv1.NewAdminTaskServiceClient(conn) + appClient := activityv1.NewTaskServiceClient(conn) + ctx := context.Background() + + // 后台真实 gRPC 接口创建一条带 icon、跳转、维度过滤的每日任务,模拟 admin-server 最终调用 activity-service 的配置链路。 + created, err := adminClient.UpsertTaskDefinition(ctx, &activityv1.UpsertTaskDefinitionRequest{ + Meta: taskFlowMeta("admin-task-create"), + TaskType: taskdomain.TypeDaily, + Category: "gift", + MetricType: taskdomain.MetricGiftSpendCoin, + Title: "赠送指定礼物", + Description: "消耗指定礼物金币", + AudienceType: taskdomain.AudienceNewbie, + IconKey: "gift", + IconUrl: "https://cdn.example.com/tasks/gift.png", + ActionType: taskdomain.ActionGiftPanelSpecific, + ActionParam: "gift_1001", + ActionPayloadJson: `{"gift_id":"gift_1001","panel":"gift"}`, + DimensionFilterJson: `{"gift_id":"gift_1001"}`, + TargetValue: 100, + TargetUnit: "coin", + RewardCoinAmount: 20, + Status: taskdomain.StatusActive, + SortOrder: 10, + OperatorAdminId: 90001, + }) + if err != nil { + t.Fatalf("admin create task failed: %v", err) + } + if !created.GetCreated() || created.GetTask().GetTaskId() == "" { + t.Fatalf("admin create response mismatch: %+v", created) + } + taskID := created.GetTask().GetTaskId() + + // 后台列表接口必须能原样读回展示配置,否则 admin-platform 编辑弹窗会丢失 icon、跳转或维度过滤字段。 + adminList, err := adminClient.ListTaskDefinitions(ctx, &activityv1.ListTaskDefinitionsRequest{ + Meta: taskFlowMeta("admin-task-list"), + TaskType: taskdomain.TypeDaily, + Status: taskdomain.StatusActive, + Page: 1, + PageSize: 20, + }) + if err != nil { + t.Fatalf("admin list task failed: %v", err) + } + if adminList.GetTotal() != 1 || adminList.GetTasks()[0].GetIconKey() != "gift" || adminList.GetTasks()[0].GetIconUrl() == "" { + t.Fatalf("admin list should include icon metadata: %+v", adminList) + } + + // 非匹配 gift_id 的服务端事实事件应被消费为 skipped,证明 dimension_filter_json 生效且 MQ 不会无限重试。 + skipped, err := appClient.ConsumeTaskEvent(ctx, &activityv1.ConsumeTaskEventRequest{ + Meta: taskFlowMeta("task-event-skip"), + EventId: "evt-flow-gift-skip", + EventType: "RoomGiftSent", + SourceService: "room-service", + UserId: 10001, + MetricType: taskdomain.MetricGiftSpendCoin, + Value: 100, + OccurredAtMs: now.UnixMilli(), + DimensionsJson: `{"gift_id":"gift_2002","room_id":"room-1"}`, + }) + if err != nil { + t.Fatalf("consume skip event failed: %v", err) + } + if skipped.GetStatus() != taskdomain.EventStatusSkipped || skipped.GetMatchedTaskCount() != 0 { + t.Fatalf("skip event result mismatch: %+v", skipped) + } + + // App 查询接口此时能看到后台配置的图标和跳转,但进度仍为 0,避免维度不匹配事件误推动任务。 + beforeHit := listDailyTaskViaGRPC(t, appClient, 10001, taskID) + if beforeHit.GetProgressValue() != 0 || beforeHit.GetClaimable() { + t.Fatalf("task should not progress before matching event: %+v", beforeHit) + } + if beforeHit.GetIconKey() != "gift" || beforeHit.GetActionType() != taskdomain.ActionGiftPanelSpecific || beforeHit.GetActionParam() != "gift_1001" { + t.Fatalf("app task should expose icon/action metadata: %+v", beforeHit) + } + + // 匹配 gift_id 的服务端事实事件会推进同一条任务到完成态,整个进度来自 activity-service 事件入口。 + consumed, err := appClient.ConsumeTaskEvent(ctx, &activityv1.ConsumeTaskEventRequest{ + Meta: taskFlowMeta("task-event-hit"), + EventId: "evt-flow-gift-hit", + EventType: "RoomGiftSent", + SourceService: "room-service", + UserId: 10001, + MetricType: taskdomain.MetricGiftSpendCoin, + Value: 120, + OccurredAtMs: now.UnixMilli(), + DimensionsJson: `{"gift_id":"gift_1001","room_id":"room-1"}`, + }) + if err != nil { + t.Fatalf("consume hit event failed: %v", err) + } + if consumed.GetStatus() != taskdomain.EventStatusConsumed || consumed.GetMatchedTaskCount() != 1 { + t.Fatalf("hit event result mismatch: %+v", consumed) + } + + claimable := listDailyTaskViaGRPC(t, appClient, 10001, taskID) + if claimable.GetStatus() != taskdomain.StatusCompleted || !claimable.GetClaimable() || claimable.GetProgressValue() != 120 { + t.Fatalf("task should be completed after matching event: %+v", claimable) + } + + // App 领取接口必须带当前 task_day 和 command_id;钱包入账只在 activity-service 校验完成态后触发。 + claim, err := appClient.ClaimTaskReward(ctx, &activityv1.ClaimTaskRewardRequest{ + Meta: taskFlowMeta("task-claim"), + UserId: 10001, + TaskId: taskID, + TaskType: taskdomain.TypeDaily, + TaskDay: claimable.GetTaskDay(), + CommandId: "cmd-task-flow-claim", + }) + if err != nil { + t.Fatalf("claim task reward failed: %v", err) + } + if !claim.GetClaimed() || claim.GetWalletTransactionId() != "wtx-task-flow-1" || claim.GetRewardCoinAmount() != 20 { + t.Fatalf("claim response mismatch: %+v", claim) + } + if wallet.calls != 1 || wallet.last == nil || wallet.last.GetTaskId() != taskID || wallet.last.GetTargetUserId() != 10001 || wallet.last.GetAmount() != 20 { + t.Fatalf("wallet request mismatch: calls=%d last=%+v", wallet.calls, wallet.last) + } + + // 同一个 command_id 重放领取接口应复用已发放 claim,不允许重复调用 wallet-service。 + replay, err := appClient.ClaimTaskReward(ctx, &activityv1.ClaimTaskRewardRequest{ + Meta: taskFlowMeta("task-claim-replay"), + UserId: 10001, + TaskId: taskID, + TaskType: taskdomain.TypeDaily, + TaskDay: claimable.GetTaskDay(), + CommandId: "cmd-task-flow-claim", + }) + if err != nil { + t.Fatalf("replay claim failed: %v", err) + } + if replay.GetClaimId() != claim.GetClaimId() || wallet.calls != 1 { + t.Fatalf("claim replay should be idempotent: first=%+v replay=%+v wallet_calls=%d", claim, replay, wallet.calls) + } + + claimed := listDailyTaskViaGRPC(t, appClient, 10001, taskID) + if claimed.GetStatus() != taskdomain.StatusClaimed || claimed.GetClaimable() { + t.Fatalf("task should be claimed after reward grant: %+v", claimed) + } +} + +func newTaskFlowGRPCConn(t *testing.T, service *taskservice.Service) *grpcpkg.ClientConn { + t.Helper() + + listener := bufconn.Listen(1024 * 1024) + server := grpcpkg.NewServer() + activityv1.RegisterTaskServiceServer(server, NewTaskServer(service)) + activityv1.RegisterAdminTaskServiceServer(server, NewAdminTaskServer(service)) + go func() { + _ = server.Serve(listener) + }() + t.Cleanup(func() { + server.Stop() + _ = listener.Close() + }) + + conn, err := grpcpkg.NewClient("passthrough:///task-flow-bufconn", + grpcpkg.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return listener.Dial() + }), + grpcpkg.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + t.Fatalf("dial task flow grpc failed: %v", err) + } + t.Cleanup(func() { _ = conn.Close() }) + return conn +} + +func listDailyTaskViaGRPC(t *testing.T, client activityv1.TaskServiceClient, userID int64, taskID string) *activityv1.TaskItem { + t.Helper() + + resp, err := client.ListUserTasks(context.Background(), &activityv1.ListUserTasksRequest{ + Meta: taskFlowMeta("task-list"), + UserId: userID, + }) + if err != nil { + t.Fatalf("list user tasks failed: %v", err) + } + for _, section := range resp.GetSections() { + for _, item := range section.GetItems() { + if item.GetTaskId() == taskID { + return item + } + } + } + t.Fatalf("task %s not found in response: %+v", taskID, resp) + return nil +} + +func taskFlowMeta(requestID string) *activityv1.RequestMeta { + return &activityv1.RequestMeta{ + RequestId: requestID, + Caller: "task-flow-test", + AppCode: "lalu", + SentAtMs: time.Date(2026, 5, 9, 10, 0, 0, 0, time.UTC).UnixMilli(), + } +} + +type taskFlowWallet struct { + calls int + last *walletv1.CreditTaskRewardRequest +} + +func (w *taskFlowWallet) CreditTaskReward(_ context.Context, req *walletv1.CreditTaskRewardRequest, _ ...grpcpkg.CallOption) (*walletv1.CreditTaskRewardResponse, error) { + w.calls++ + w.last = req + return &walletv1.CreditTaskRewardResponse{ + TransactionId: "wtx-task-flow-1", + Amount: req.GetAmount(), + GrantedAtMs: time.Date(2026, 5, 9, 10, 1, 0, 0, time.UTC).UnixMilli(), + Balance: &walletv1.AssetBalance{AssetType: "COIN", AvailableAmount: 1000 + req.GetAmount()}, + }, nil +} diff --git a/services/activity-service/internal/transport/grpc/task_server.go b/services/activity-service/internal/transport/grpc/task_server.go index 708281fa..2616258c 100644 --- a/services/activity-service/internal/transport/grpc/task_server.go +++ b/services/activity-service/internal/transport/grpc/task_server.go @@ -52,13 +52,14 @@ func (s *TaskServer) ClaimTaskReward(ctx context.Context, req *activityv1.ClaimT func (s *TaskServer) ConsumeTaskEvent(ctx context.Context, req *activityv1.ConsumeTaskEventRequest) (*activityv1.ConsumeTaskEventResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) result, err := s.svc.ConsumeTaskEvent(ctx, taskdomain.Event{ - EventID: req.GetEventId(), - EventType: req.GetEventType(), - SourceService: req.GetSourceService(), - UserID: req.GetUserId(), - MetricType: req.GetMetricType(), - Value: req.GetValue(), - OccurredAtMS: req.GetOccurredAtMs(), + EventID: req.GetEventId(), + EventType: req.GetEventType(), + SourceService: req.GetSourceService(), + UserID: req.GetUserId(), + MetricType: req.GetMetricType(), + Value: req.GetValue(), + OccurredAtMS: req.GetOccurredAtMs(), + DimensionsJSON: req.GetDimensionsJson(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -107,20 +108,27 @@ func (s *AdminTaskServer) ListTaskDefinitions(ctx context.Context, req *activity func (s *AdminTaskServer) UpsertTaskDefinition(ctx context.Context, req *activityv1.UpsertTaskDefinitionRequest) (*activityv1.UpsertTaskDefinitionResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) item, created, err := s.svc.UpsertTaskDefinition(ctx, taskdomain.DefinitionCommand{ - TaskID: req.GetTaskId(), - TaskType: req.GetTaskType(), - Category: req.GetCategory(), - MetricType: req.GetMetricType(), - Title: req.GetTitle(), - Description: req.GetDescription(), - TargetValue: req.GetTargetValue(), - TargetUnit: req.GetTargetUnit(), - RewardCoinAmount: req.GetRewardCoinAmount(), - Status: req.GetStatus(), - SortOrder: req.GetSortOrder(), - EffectiveFromMS: req.GetEffectiveFromMs(), - EffectiveToMS: req.GetEffectiveToMs(), - OperatorAdminID: req.GetOperatorAdminId(), + TaskID: req.GetTaskId(), + TaskType: req.GetTaskType(), + Category: req.GetCategory(), + MetricType: req.GetMetricType(), + Title: req.GetTitle(), + Description: req.GetDescription(), + AudienceType: req.GetAudienceType(), + IconKey: req.GetIconKey(), + IconURL: req.GetIconUrl(), + ActionType: req.GetActionType(), + ActionParam: req.GetActionParam(), + ActionPayloadJSON: req.GetActionPayloadJson(), + DimensionFilterJSON: req.GetDimensionFilterJson(), + TargetValue: req.GetTargetValue(), + TargetUnit: req.GetTargetUnit(), + RewardCoinAmount: req.GetRewardCoinAmount(), + Status: req.GetStatus(), + SortOrder: req.GetSortOrder(), + EffectiveFromMS: req.GetEffectiveFromMs(), + EffectiveToMS: req.GetEffectiveToMs(), + OperatorAdminID: req.GetOperatorAdminId(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -161,23 +169,30 @@ func taskListToProto(result taskdomain.ListResult) *activityv1.ListUserTasksResp func itemToProto(item taskdomain.Item) *activityv1.TaskItem { return &activityv1.TaskItem{ - TaskId: item.TaskID, - TaskType: item.TaskType, - Category: item.Category, - MetricType: item.MetricType, - Title: item.Title, - Description: item.Description, - TargetValue: item.TargetValue, - TargetUnit: item.TargetUnit, - ProgressValue: item.ProgressValue, - RewardCoinAmount: item.RewardCoinAmount, - Status: item.UserStatus, - Claimable: item.Claimable, - TaskDay: item.TaskDay, - ServerTimeMs: item.ServerTimeMS, - NextRefreshAtMs: item.NextRefreshAtMS, - SortOrder: item.SortOrder, - Version: item.Version, + TaskId: item.TaskID, + TaskType: item.TaskType, + Category: item.Category, + MetricType: item.MetricType, + Title: item.Title, + Description: item.Description, + AudienceType: item.AudienceType, + IconKey: item.IconKey, + IconUrl: item.IconURL, + ActionType: item.ActionType, + ActionParam: item.ActionParam, + ActionPayloadJson: item.ActionPayloadJSON, + DimensionFilterJson: item.DimensionFilterJSON, + TargetValue: item.TargetValue, + TargetUnit: item.TargetUnit, + ProgressValue: item.ProgressValue, + RewardCoinAmount: item.RewardCoinAmount, + Status: item.UserStatus, + Claimable: item.Claimable, + TaskDay: item.TaskDay, + ServerTimeMs: item.ServerTimeMS, + NextRefreshAtMs: item.NextRefreshAtMS, + SortOrder: item.SortOrder, + Version: item.Version, } } @@ -197,23 +212,30 @@ func claimToProto(claim taskdomain.Claim) *activityv1.ClaimTaskRewardResponse { func definitionToProto(item taskdomain.Definition) *activityv1.TaskDefinition { return &activityv1.TaskDefinition{ - TaskId: item.TaskID, - TaskType: item.TaskType, - Category: item.Category, - MetricType: item.MetricType, - Title: item.Title, - Description: item.Description, - TargetValue: item.TargetValue, - TargetUnit: item.TargetUnit, - RewardCoinAmount: item.RewardCoinAmount, - Status: item.Status, - SortOrder: item.SortOrder, - Version: item.Version, - EffectiveFromMs: item.EffectiveFromMS, - EffectiveToMs: item.EffectiveToMS, - CreatedByAdminId: item.CreatedByAdminID, - UpdatedByAdminId: item.UpdatedByAdminID, - CreatedAtMs: item.CreatedAtMS, - UpdatedAtMs: item.UpdatedAtMS, + TaskId: item.TaskID, + TaskType: item.TaskType, + Category: item.Category, + MetricType: item.MetricType, + Title: item.Title, + Description: item.Description, + AudienceType: item.AudienceType, + IconKey: item.IconKey, + IconUrl: item.IconURL, + ActionType: item.ActionType, + ActionParam: item.ActionParam, + ActionPayloadJson: item.ActionPayloadJSON, + DimensionFilterJson: item.DimensionFilterJSON, + TargetValue: item.TargetValue, + TargetUnit: item.TargetUnit, + RewardCoinAmount: item.RewardCoinAmount, + Status: item.Status, + SortOrder: item.SortOrder, + Version: item.Version, + EffectiveFromMs: item.EffectiveFromMS, + EffectiveToMs: item.EffectiveToMS, + CreatedByAdminId: item.CreatedByAdminID, + UpdatedByAdminId: item.UpdatedByAdminID, + CreatedAtMs: item.CreatedAtMS, + UpdatedAtMs: item.UpdatedAtMS, } } diff --git a/services/cron-service/configs/config.docker.yaml b/services/cron-service/configs/config.docker.yaml index f9f70c6b..7ebf7571 100644 --- a/services/cron-service/configs/config.docker.yaml +++ b/services/cron-service/configs/config.docker.yaml @@ -57,6 +57,13 @@ tasks: lock_ttl: "5m" batch_size: 100 app_codes: ["lalu"] + cp_intimacy_leaderboard: + enabled: true + interval: "5m" + timeout: "20s" + lock_ttl: "5m" + batch_size: 10000 + app_codes: ["lalu"] game_level_event_relay: enabled: true interval: "5s" diff --git a/services/cron-service/configs/config.tencent.example.yaml b/services/cron-service/configs/config.tencent.example.yaml index ec05f4da..4de86110 100644 --- a/services/cron-service/configs/config.tencent.example.yaml +++ b/services/cron-service/configs/config.tencent.example.yaml @@ -57,6 +57,13 @@ tasks: lock_ttl: "5m" batch_size: 100 app_codes: ["lalu"] + cp_intimacy_leaderboard: + enabled: true + interval: "5m" + timeout: "20s" + lock_ttl: "5m" + batch_size: 10000 + app_codes: ["lalu"] game_level_event_relay: enabled: true interval: "5s" diff --git a/services/cron-service/configs/config.yaml b/services/cron-service/configs/config.yaml index fbadb058..54a51ca9 100644 --- a/services/cron-service/configs/config.yaml +++ b/services/cron-service/configs/config.yaml @@ -57,6 +57,13 @@ tasks: lock_ttl: "5m" batch_size: 100 app_codes: ["lalu"] + cp_intimacy_leaderboard: + enabled: true + interval: "5m" + timeout: "20s" + lock_ttl: "5m" + batch_size: 10000 + app_codes: ["lalu"] game_level_event_relay: enabled: true interval: "5s" diff --git a/services/cron-service/internal/app/app.go b/services/cron-service/internal/app/app.go index 95816ac2..ab2a08d2 100644 --- a/services/cron-service/internal/app/app.go +++ b/services/cron-service/internal/app/app.go @@ -113,10 +113,12 @@ func New(cfg config.Config) (*App, error) { gameCron := integration.NewGameCronClient(gamev1.NewGameCronServiceClient(gameConn)) walletCron := integration.NewWalletCronClient(walletv1.NewWalletCronServiceClient(walletConn)) taskScheduler := scheduler.New(cfg.NodeID, cfg.Tasks, repository, map[string]scheduler.Handler{ - "login_ip_risk": userCron.ProcessLoginIPRiskBatch, - "user_region_rebuild": userCron.ProcessRegionRebuildBatch, - "mic_open_session_compensation": userCron.CompensateMicOpenSessions, - "manager_user_block_expiry": userCron.ExpireManagerUserBlocks, + "login_ip_risk": userCron.ProcessLoginIPRiskBatch, + "user_region_rebuild": userCron.ProcessRegionRebuildBatch, + "mic_open_session_compensation": userCron.CompensateMicOpenSessions, + "manager_user_block_expiry": userCron.ExpireManagerUserBlocks, + // CP 榜单任务只占 cron 租约并调用 user-service;真实 MySQL 读取、wallet 头像框补齐和 Redis 替换都在 user-service 内完成。 + "cp_intimacy_leaderboard": userCron.RefreshCPIntimacyLeaderboard, "message_fanout": activityCron.ProcessMessageFanoutBatch, "growth_level_reward": activityCron.ProcessLevelRewardBatch, "achievement_reward": activityCron.ProcessAchievementRewardBatch, diff --git a/services/cron-service/internal/config/config.go b/services/cron-service/internal/config/config.go index 14411cd7..7311bcf3 100644 --- a/services/cron-service/internal/config/config.go +++ b/services/cron-service/internal/config/config.go @@ -180,6 +180,14 @@ func defaultTasks() map[string]TaskConfig { BatchSize: 100, AppCodes: []string{defaultAppCode}, }, + "cp_intimacy_leaderboard": { + Enabled: true, + Interval: "5m", + Timeout: "20s", + LockTTL: "5m", + BatchSize: 10000, + AppCodes: []string{defaultAppCode}, + }, "game_level_event_relay": { Enabled: true, Interval: "5s", diff --git a/services/cron-service/internal/config/config_test.go b/services/cron-service/internal/config/config_test.go index d7b8ddbf..4cd0200e 100644 --- a/services/cron-service/internal/config/config_test.go +++ b/services/cron-service/internal/config/config_test.go @@ -18,3 +18,17 @@ func TestDefaultHostSalaryCronTasksAreEnabled(t *testing.T) { } } } + +func TestDefaultCPIntimacyLeaderboardCronTaskRunsEveryFiveMinutes(t *testing.T) { + cfg := Default() + task, ok := cfg.Tasks["cp_intimacy_leaderboard"] + if !ok { + t.Fatal("cp intimacy leaderboard cron task missing") + } + if !task.Enabled { + t.Fatal("cp intimacy leaderboard cron task should be enabled by default") + } + if task.Interval != "5m" || task.LockTTL != "5m" || task.BatchSize != 10000 { + t.Fatalf("cp intimacy leaderboard cron task has unexpected defaults: %+v", task) + } +} diff --git a/services/cron-service/internal/integration/user.go b/services/cron-service/internal/integration/user.go index e52759e5..a14b5c13 100644 --- a/services/cron-service/internal/integration/user.go +++ b/services/cron-service/internal/integration/user.go @@ -44,6 +44,13 @@ func (c *UserCronClient) ExpireManagerUserBlocks(ctx context.Context, req schedu return userCronResult(resp), err } +// RefreshCPIntimacyLeaderboard 只触发 user-service 重建 CP 亲密值 zset 读模型,cron 不读取业务表。 +func (c *UserCronClient) RefreshCPIntimacyLeaderboard(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { + // userCronRequest 会带 run_id、worker_id、app_code 和 batch_size;user-service 用这些值做日志追踪、租户隔离和刷新上限。 + resp, err := c.client.RefreshCPIntimacyLeaderboard(ctx, userCronRequest(req)) + return userCronResult(resp), err +} + func userCronRequest(req scheduler.BatchRequest) *userv1.CronBatchRequest { return &userv1.CronBatchRequest{ Meta: &userv1.RequestMeta{ diff --git a/services/game-service/internal/app/app.go b/services/game-service/internal/app/app.go index 32fbf17b..71e738fa 100644 --- a/services/game-service/internal/app/app.go +++ b/services/game-service/internal/app/app.go @@ -22,6 +22,7 @@ import ( "hyapp/services/game-service/internal/config" diceservice "hyapp/services/game-service/internal/service/dice" gameservice "hyapp/services/game-service/internal/service/game" + roomrpsservice "hyapp/services/game-service/internal/service/roomrps" mysqlstorage "hyapp/services/game-service/internal/storage/mysql" grpcserver "hyapp/services/game-service/internal/transport/grpc" ) @@ -93,10 +94,22 @@ func New(cfg config.Config) (*App, error) { server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("game-service"))) walletClient := client.NewWalletClient(walletConn) - svc := gameservice.New(gameservice.Config{LaunchSessionTTL: cfg.LaunchSessionTTL}, repo, walletClient, client.NewUserClient(userConn), client.NewActivityGrowthClient(activityConn)) + userClient := client.NewUserClient(userConn) + svc := gameservice.New(gameservice.Config{LaunchSessionTTL: cfg.LaunchSessionTTL}, repo, walletClient, userClient, client.NewActivityGrowthClient(activityConn)) // 骰子服务复用同一个 MySQL repository 和 wallet client;局事实走 dice 表,金币事实仍走 game_orders + wallet-service。 diceSvc := diceservice.New(diceservice.Config{}, repo, walletClient) - transport := grpcserver.NewServer(svc, diceSvc) + roomRPSPublisher, err := roomrpsservice.NewTencentIMPublisherFromEnv() + if err != nil { + _ = listener.Close() + _ = activityConn.Close() + _ = userConn.Close() + _ = walletConn.Close() + _ = repo.Close() + return nil, err + } + // 房内猜拳独立于骰子和独立猜拳:配置、挑战状态机和 IM 事件全部由 room-rps service 承接,transport 只做 RPC 适配。 + roomRPSSvc := roomrpsservice.New(roomrpsservice.Config{}, userClient, roomRPSPublisher) + transport := grpcserver.NewServer(svc, diceSvc, roomRPSSvc) gamev1.RegisterGameAppServiceServer(server, transport) gamev1.RegisterGameCallbackServiceServer(server, transport) gamev1.RegisterGameAdminServiceServer(server, transport) diff --git a/services/game-service/internal/client/activity_client.go b/services/game-service/internal/client/activity_client.go index 81feee8a..4d59141f 100644 --- a/services/game-service/internal/client/activity_client.go +++ b/services/game-service/internal/client/activity_client.go @@ -9,13 +9,21 @@ import ( // ActivityGrowthClient 只包装等级事件消费 RPC,避免 game-service 依赖 activity-service 内部实现。 type ActivityGrowthClient struct { - client activityv1.GrowthLevelServiceClient + growth activityv1.GrowthLevelServiceClient + task activityv1.TaskServiceClient } func NewActivityGrowthClient(conn *grpc.ClientConn) *ActivityGrowthClient { - return &ActivityGrowthClient{client: activityv1.NewGrowthLevelServiceClient(conn)} + return &ActivityGrowthClient{ + growth: activityv1.NewGrowthLevelServiceClient(conn), + task: activityv1.NewTaskServiceClient(conn), + } } func (c *ActivityGrowthClient) ConsumeLevelEvent(ctx context.Context, req *activityv1.ConsumeLevelEventRequest, opts ...grpc.CallOption) (*activityv1.ConsumeLevelEventResponse, error) { - return c.client.ConsumeLevelEvent(ctx, req, opts...) + return c.growth.ConsumeLevelEvent(ctx, req, opts...) +} + +func (c *ActivityGrowthClient) ConsumeTaskEvent(ctx context.Context, req *activityv1.ConsumeTaskEventRequest, opts ...grpc.CallOption) (*activityv1.ConsumeTaskEventResponse, error) { + return c.task.ConsumeTaskEvent(ctx, req, opts...) } diff --git a/services/game-service/internal/service/game/service.go b/services/game-service/internal/service/game/service.go index e0947207..1f92ef4a 100644 --- a/services/game-service/internal/service/game/service.go +++ b/services/game-service/internal/service/game/service.go @@ -66,6 +66,7 @@ type UserClient interface { // ActivityClient 是 game-service 向等级系统投递游戏消耗事实的最小依赖。 type ActivityClient interface { ConsumeLevelEvent(ctx context.Context, req *activityv1.ConsumeLevelEventRequest, opts ...grpc.CallOption) (*activityv1.ConsumeLevelEventResponse, error) + ConsumeTaskEvent(ctx context.Context, req *activityv1.ConsumeTaskEventRequest, opts ...grpc.CallOption) (*activityv1.ConsumeTaskEventResponse, error) } type Config struct { @@ -612,7 +613,7 @@ func (s *Service) applyCallbackCoinChange(ctx context.Context, app string, req * return map[string]any{"code": 0, "order_id": order.OrderID, "wallet_transaction_id": walletResp.GetWalletTransactionId(), "balance_after": walletResp.GetBalanceAfter(), "idempotent_replay": walletResp.GetIdempotentReplay()}, nil } -// ProcessLevelEventOutboxBatch 把成功游戏扣费订单异步投递给 activity-service 的游戏等级轨道。 +// ProcessLevelEventOutboxBatch 把成功游戏扣费订单异步投递给 activity-service 的游戏等级轨道和任务轨道。 func (s *Service) ProcessLevelEventOutboxBatch(ctx context.Context, runID string, workerID string, batchSize int, lockTTL time.Duration) (claimed int32, processed int32, success int32, failure int32, hasMore bool, err error) { if s.repository == nil { return 0, 0, 0, 0, false, xerr.New(xerr.Unavailable, "game repository is not configured") @@ -636,6 +637,7 @@ func (s *Service) ProcessLevelEventOutboxBatch(ctx context.Context, runID string for _, event := range events { processed++ eventCtx := appcode.WithContext(ctx, event.AppCode) + // 等级和任务共用同一条游戏扣币 outbox;先投递成长等级,保留原有等级体系的消费顺序和失败语义。 resp, relayErr := s.activity.ConsumeLevelEvent(eventCtx, &activityv1.ConsumeLevelEventRequest{ Meta: &activityv1.RequestMeta{ RequestId: event.EventID, @@ -666,6 +668,36 @@ func (s *Service) ProcessLevelEventOutboxBatch(ctx context.Context, runID string _ = s.repository.MarkLevelEventFailed(eventCtx, event.EventID, "activity returned "+resp.GetStatus(), nextRetry, s.now().UnixMilli()) continue } + // 任务轨道使用独立 event_id 后缀,避免和成长等级事件共用幂等键;payload_json 原样透传给 dimension_filter_json 匹配 game_id 等维度。 + taskResp, relayErr := s.activity.ConsumeTaskEvent(eventCtx, &activityv1.ConsumeTaskEventRequest{ + Meta: &activityv1.RequestMeta{ + RequestId: event.EventID + ":task", + Caller: "game-service", + SentAtMs: s.now().UnixMilli(), + AppCode: event.AppCode, + }, + EventId: event.EventID + ":task", + EventType: "GameCoinDebited", + SourceService: "game-service", + UserId: event.UserID, + MetricType: "game_spend_coin", + Value: event.CoinAmount, + OccurredAtMs: event.OccurredAtMS, + DimensionsJson: event.PayloadJSON, + }) + if relayErr != nil { + failure++ + nextRetry := s.now().Add(time.Minute).UnixMilli() + _ = s.repository.MarkLevelEventFailed(eventCtx, event.EventID, xerr.MessageOf(relayErr), nextRetry, s.now().UnixMilli()) + continue + } + if taskResp.GetStatus() != "consumed" && taskResp.GetStatus() != "skipped" && taskResp.GetStatus() != "duplicate" { + failure++ + nextRetry := s.now().Add(time.Minute).UnixMilli() + _ = s.repository.MarkLevelEventFailed(eventCtx, event.EventID, "task returned "+taskResp.GetStatus(), nextRetry, s.now().UnixMilli()) + continue + } + // 只有成长和任务两条链路都达到终态才标记 delivered,防止“游戏等级已加、任务未加”的半完成 outbox。 if err := s.repository.MarkLevelEventDelivered(eventCtx, event.EventID, s.now().UnixMilli()); err != nil { failure++ continue diff --git a/services/game-service/internal/service/game/service_test.go b/services/game-service/internal/service/game/service_test.go index da07a565..8b2a5db0 100644 --- a/services/game-service/internal/service/game/service_test.go +++ b/services/game-service/internal/service/game/service_test.go @@ -1645,6 +1645,9 @@ func TestProcessLevelEventOutboxBatchRelaysGameSpend(t *testing.T) { if activity.last.GetTrack() != "game" || activity.last.GetMetricType() != "game_spend_coin" || activity.last.GetValueDelta() != 120 { t.Fatalf("activity relay request mismatch: %+v", activity.last) } + if activity.lastTask.GetMetricType() != "game_spend_coin" || activity.lastTask.GetValue() != 120 || activity.lastTask.GetEventId() != "game_level:order_1:task" { + t.Fatalf("task relay request mismatch: %+v", activity.lastTask) + } if len(repo.deliveredEvents) != 1 || repo.deliveredEvents[0] != "game_level:order_1" { t.Fatalf("delivered state mismatch: %+v", repo.deliveredEvents) } @@ -1843,8 +1846,10 @@ func (f *fakeWallet) GetBalances(context.Context, *walletv1.GetBalancesRequest) } type fakeActivity struct { - status string - last *activityv1.ConsumeLevelEventRequest + status string + taskStatus string + last *activityv1.ConsumeLevelEventRequest + lastTask *activityv1.ConsumeTaskEventRequest } func (f *fakeActivity) ConsumeLevelEvent(_ context.Context, req *activityv1.ConsumeLevelEventRequest, _ ...grpc.CallOption) (*activityv1.ConsumeLevelEventResponse, error) { @@ -1856,6 +1861,15 @@ func (f *fakeActivity) ConsumeLevelEvent(_ context.Context, req *activityv1.Cons return &activityv1.ConsumeLevelEventResponse{EventId: req.GetEventId(), Status: status}, nil } +func (f *fakeActivity) ConsumeTaskEvent(_ context.Context, req *activityv1.ConsumeTaskEventRequest, _ ...grpc.CallOption) (*activityv1.ConsumeTaskEventResponse, error) { + f.lastTask = req + status := f.taskStatus + if status == "" { + status = "consumed" + } + return &activityv1.ConsumeTaskEventResponse{EventId: req.GetEventId(), Status: status, MatchedTaskCount: 1}, nil +} + type fakeUser struct { lastGet *userv1.GetUserRequest err error diff --git a/services/game-service/internal/service/roomrps/service.go b/services/game-service/internal/service/roomrps/service.go new file mode 100644 index 00000000..d17a047c --- /dev/null +++ b/services/game-service/internal/service/roomrps/service.go @@ -0,0 +1,778 @@ +package roomrps + +import ( + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "os" + "sort" + "strconv" + "strings" + "sync" + "time" + + "google.golang.org/protobuf/proto" + gamev1 "hyapp.local/api/proto/game/v1" + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/tencentim" + "hyapp/pkg/xerr" +) + +const ( + GameID = "room_rps" + + StatusPending = "pending" + StatusMatched = "matched" + StatusRevealing = "revealing" + StatusFinished = "finished" + StatusTimeout = "timeout" + StatusSettlementFailed = "settlement_failed" + StatusCancelled = "cancelled" + + SettlementPending = "pending" + SettlementSettled = "settled" + SettlementRefunded = "refunded" + SettlementFailed = "failed" + + GestureRock = "rock" + GesturePaper = "paper" + GestureScissors = "scissors" + + ResultWin = "win" + ResultLose = "lose" + ResultDraw = "draw" + + eventChallengeCreated = "room_rps_challenge_created" + eventChallengeAccepted = "room_rps_challenge_accepted" + eventRevealCountdown = "room_rps_reveal_countdown" + eventFinished = "room_rps_finished" + eventExpired = "room_rps_expired" +) + +// UserClient 是 room-rps 唯一需要的 user-service 能力;这里只取展示资料,不把用户领域模型引入游戏用例层。 +type UserClient interface { + GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) +} + +// Publisher 是房间 IM 投递边界;生产用腾讯 IM REST,测试可以注入 fake,service 不感知具体 SDK。 +type Publisher interface { + EnsureGroup(ctx context.Context, spec tencentim.GroupSpec) error + PublishGroupCustomMessage(ctx context.Context, message tencentim.CustomGroupMessage) error +} + +type Config struct { + ChallengeTimeout time.Duration + RevealCountdown time.Duration +} + +type Service struct { + config Config + user UserClient + pub Publisher + + mu sync.Mutex + configs map[string]*gamev1.RoomRPSConfig + challenges map[string]*gamev1.RoomRPSChallenge + now func() time.Time +} + +// New 创建房内猜拳用例层。 +// 当前阶段先把真实 HTTP、后台和 IM 事件跑通;挑战事实用进程内状态承接,后续接 MySQL 时保持同一套状态机和 RPC 契约即可。 +func New(config Config, user UserClient, publisher Publisher) *Service { + if config.ChallengeTimeout <= 0 { + // 房内猜拳产品规则是 10 分钟无人应战自动超时;默认打开时必须带稳定超时,避免 pending 永久悬挂。 + config.ChallengeTimeout = 10 * time.Minute + } + if config.RevealCountdown <= 0 { + // 揭晓动画固定 3 秒;IM 的 countdown 事件和 Flutter 动画以这个值做时间校准。 + config.RevealCountdown = 3 * time.Second + } + return &Service{ + config: config, + user: user, + pub: publisher, + configs: make(map[string]*gamev1.RoomRPSConfig), + challenges: make(map[string]*gamev1.RoomRPSChallenge), + now: time.Now, + } +} + +// NewTencentIMPublisherFromEnv 按环境变量创建真实腾讯 IM publisher。 +// 密钥、账号和地域入口都属于敏感或部署差异配置,因此这里只读取环境,不在 game-service 默认配置里伪造。 +func NewTencentIMPublisherFromEnv() (Publisher, error) { + sdkAppID, _ := strconv.ParseInt(strings.TrimSpace(os.Getenv("TENCENT_IM_SDK_APP_ID")), 10, 64) + secretKey := strings.TrimSpace(os.Getenv("TENCENT_IM_SECRET_KEY")) + adminIdentifier := strings.TrimSpace(os.Getenv("TENCENT_IM_ADMIN_IDENTIFIER")) + if sdkAppID <= 0 || secretKey == "" || adminIdentifier == "" { + // 未配置 IM 时不阻断本地服务启动;真实 IM smoke 会显式带环境变量,并要求事件投递成功。 + return nil, nil + } + ttl := time.Hour + if raw := strings.TrimSpace(os.Getenv("TENCENT_IM_ADMIN_USERSIG_TTL")); raw != "" { + parsed, err := time.ParseDuration(raw) + if err != nil || parsed <= 0 { + return nil, fmt.Errorf("TENCENT_IM_ADMIN_USERSIG_TTL is invalid") + } + ttl = parsed + } + return tencentim.NewRESTClient(tencentim.RESTConfig{ + SDKAppID: sdkAppID, + SecretKey: secretKey, + AdminIdentifier: adminIdentifier, + AdminUserSigTTL: ttl, + Endpoint: envOr("TENCENT_IM_ENDPOINT", tencentim.DefaultEndpoint), + GroupType: tencentim.DefaultGroupType, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }) +} + +func (s *Service) GetConfig(ctx context.Context, app string) (*gamev1.RoomRPSConfig, int64, error) { + if s == nil { + return nil, 0, xerr.New(xerr.Unavailable, "room rps service is not configured") + } + app = appcode.Normalize(app) + ctx = appcode.WithContext(ctx, app) + + s.mu.Lock() + defer s.mu.Unlock() + + config := s.configForAppLocked(app) + return cloneConfig(config), s.now().UnixMilli(), nil +} + +func (s *Service) UpdateConfig(ctx context.Context, app string, config *gamev1.RoomRPSConfig) (*gamev1.RoomRPSConfig, int64, error) { + if s == nil { + return nil, 0, xerr.New(xerr.Unavailable, "room rps service is not configured") + } + app = appcode.Normalize(app) + ctx = appcode.WithContext(ctx, app) + + normalized, err := s.normalizeConfig(app, config) + if err != nil { + return nil, 0, err + } + + s.mu.Lock() + defer s.mu.Unlock() + + nowMS := s.now().UnixMilli() + if existing := s.configs[app]; existing != nil && existing.GetCreatedAtMs() > 0 { + normalized.CreatedAtMs = existing.GetCreatedAtMs() + } else { + normalized.CreatedAtMs = nowMS + } + normalized.UpdatedAtMs = nowMS + s.configs[app] = normalized + return cloneConfig(normalized), nowMS, nil +} + +func (s *Service) ListChallenges(ctx context.Context, req *gamev1.ListRoomRPSChallengesRequest) ([]*gamev1.RoomRPSChallenge, string, int32, int64, error) { + if s == nil { + return nil, "", 0, 0, xerr.New(xerr.Unavailable, "room rps service is not configured") + } + app := appcode.Normalize(req.GetMeta().GetAppCode()) + ctx = appcode.WithContext(ctx, app) + pageSize := req.GetPageSize() + if pageSize <= 0 { + pageSize = 20 + } + if pageSize > 100 { + // 后台列表和 App 房间补拉都走同一接口;这里硬限制每页 100,防止一次请求把进程内状态全量复制出去。 + pageSize = 100 + } + cursorOffset := 0 + if raw := strings.TrimSpace(req.GetCursor()); raw != "" { + parsed, err := strconv.Atoi(raw) + if err != nil || parsed < 0 { + return nil, "", 0, 0, xerr.New(xerr.InvalidArgument, "room rps cursor is invalid") + } + cursorOffset = parsed + } + + s.mu.Lock() + defer s.mu.Unlock() + + status := normalizeStatus(req.GetStatus()) + items := make([]*gamev1.RoomRPSChallenge, 0, len(s.challenges)) + for _, item := range s.challenges { + if item.GetAppCode() != app { + continue + } + if roomID := strings.TrimSpace(req.GetRoomId()); roomID != "" && item.GetRoomId() != roomID { + continue + } + if status != "" && item.GetStatus() != status { + continue + } + if req.GetInitiatorUserId() > 0 && item.GetInitiator().GetUserId() != req.GetInitiatorUserId() { + continue + } + if req.GetChallengerUserId() > 0 && item.GetChallenger().GetUserId() != req.GetChallengerUserId() { + continue + } + if req.GetStartTimeMs() > 0 && item.GetCreatedAtMs() < req.GetStartTimeMs() { + continue + } + if req.GetEndTimeMs() > 0 && item.GetCreatedAtMs() > req.GetEndTimeMs() { + continue + } + items = append(items, cloneChallenge(item)) + } + sort.Slice(items, func(i, j int) bool { + if items[i].GetCreatedAtMs() == items[j].GetCreatedAtMs() { + return items[i].GetChallengeId() > items[j].GetChallengeId() + } + return items[i].GetCreatedAtMs() > items[j].GetCreatedAtMs() + }) + + if cursorOffset >= len(items) { + return []*gamev1.RoomRPSChallenge{}, "", pageSize, s.now().UnixMilli(), nil + } + end := cursorOffset + int(pageSize) + nextCursor := "" + if end < len(items) { + nextCursor = strconv.Itoa(end) + } else { + end = len(items) + } + return items[cursorOffset:end], nextCursor, pageSize, s.now().UnixMilli(), nil +} + +func (s *Service) CreateChallenge(ctx context.Context, req *gamev1.CreateRoomRPSChallengeRequest) (*gamev1.RoomRPSChallenge, int64, error) { + if s == nil { + return nil, 0, xerr.New(xerr.Unavailable, "room rps service is not configured") + } + app := appcode.Normalize(req.GetMeta().GetAppCode()) + ctx = appcode.WithContext(ctx, app) + roomID := strings.TrimSpace(req.GetRoomId()) + gesture, ok := normalizeGesture(req.GetGesture()) + if req.GetUserId() <= 0 || roomID == "" || req.GetGiftId() <= 0 || !ok { + return nil, 0, xerr.New(xerr.InvalidArgument, "room rps challenge command is incomplete") + } + + s.mu.Lock() + config := s.configForAppLocked(app) + if config.GetStatus() != "active" { + s.mu.Unlock() + return nil, 0, xerr.New(xerr.Conflict, "room rps config is disabled") + } + gift, ok := stakeGiftByID(config, req.GetGiftId()) + if !ok || !gift.GetEnabled() { + s.mu.Unlock() + return nil, 0, xerr.New(xerr.InvalidArgument, "room rps gift is not enabled") + } + now := s.now() + nowMS := now.UnixMilli() + challenge := &gamev1.RoomRPSChallenge{ + AppCode: app, + ChallengeId: newChallengeID(app), + RoomId: roomID, + RegionId: req.GetRegionId(), + Status: StatusPending, + StakeGiftId: gift.GetGiftId(), + StakeCoin: gift.GetGiftPriceCoin(), + Initiator: &gamev1.RoomRPSPlayer{UserId: req.GetUserId(), Gesture: gesture, JoinedAtMs: nowMS}, + SettlementStatus: SettlementPending, + TimeoutAtMs: now.Add(s.config.ChallengeTimeout).UnixMilli(), + CreatedAtMs: nowMS, + UpdatedAtMs: nowMS, + } + s.challenges[challenge.GetChallengeId()] = challenge + cloned := cloneChallenge(challenge) + s.mu.Unlock() + + if err := s.publishChallengeEvent(ctx, eventChallengeCreated, cloned); err != nil { + return nil, 0, err + } + return cloned, nowMS, nil +} + +func (s *Service) AcceptChallenge(ctx context.Context, req *gamev1.AcceptRoomRPSChallengeRequest) (*gamev1.RoomRPSChallenge, int64, error) { + if s == nil { + return nil, 0, xerr.New(xerr.Unavailable, "room rps service is not configured") + } + app := appcode.Normalize(req.GetMeta().GetAppCode()) + ctx = appcode.WithContext(ctx, app) + challengeID := strings.TrimSpace(req.GetChallengeId()) + gesture, ok := normalizeGesture(req.GetGesture()) + if req.GetUserId() <= 0 || challengeID == "" || !ok { + return nil, 0, xerr.New(xerr.InvalidArgument, "room rps accept command is incomplete") + } + + s.mu.Lock() + challenge, ok := s.challenges[challengeID] + if !ok || challenge.GetAppCode() != app { + s.mu.Unlock() + return nil, 0, xerr.New(xerr.NotFound, "room rps challenge not found") + } + now := s.now() + nowMS := now.UnixMilli() + if challenge.GetStatus() != StatusPending { + // pending 是唯一可应战状态;matched/revealing/finished/timeout 都已经有明确归属,重复点击只能返回冲突。 + s.mu.Unlock() + return nil, 0, xerr.New(xerr.Conflict, "room rps challenge is not pending") + } + if challenge.GetTimeoutAtMs() > 0 && nowMS > challenge.GetTimeoutAtMs() { + // 读到已超时但还未收敛的 pending 时,先在锁内改成 timeout,避免下一个用户还能应战同一单。 + challenge.Status = StatusTimeout + challenge.SettlementStatus = SettlementRefunded + challenge.UpdatedAtMs = nowMS + cloned := cloneChallenge(challenge) + s.mu.Unlock() + _ = s.publishChallengeEvent(ctx, eventExpired, cloned) + return nil, 0, xerr.New(xerr.Conflict, "room rps challenge is timeout") + } + if challenge.GetInitiator().GetUserId() == req.GetUserId() { + // 发起人不能应战自己的单;这个判断必须在 service 锁内做,不能依赖前端按钮状态。 + s.mu.Unlock() + return nil, 0, xerr.New(xerr.Conflict, "room rps initiator can not accept self challenge") + } + + challenge.Status = StatusFinished + challenge.Challenger = &gamev1.RoomRPSPlayer{UserId: req.GetUserId(), Gesture: gesture, JoinedAtMs: nowMS} + challenge.MatchedAtMs = nowMS + challenge.RevealAtMs = now.Add(s.config.RevealCountdown).UnixMilli() + challenge.SettledAtMs = nowMS + challenge.UpdatedAtMs = nowMS + applyResult(challenge) + cloned := cloneChallenge(challenge) + s.mu.Unlock() + + if err := s.publishChallengeEvent(ctx, eventChallengeAccepted, cloned); err != nil { + return nil, 0, err + } + if err := s.publishChallengeEvent(ctx, eventRevealCountdown, cloned); err != nil { + return nil, 0, err + } + if err := s.publishChallengeEvent(ctx, eventFinished, cloned); err != nil { + return nil, 0, err + } + return cloned, nowMS, nil +} + +func (s *Service) GetChallenge(ctx context.Context, app string, challengeID string) (*gamev1.RoomRPSChallenge, int64, error) { + if s == nil { + return nil, 0, xerr.New(xerr.Unavailable, "room rps service is not configured") + } + app = appcode.Normalize(app) + ctx = appcode.WithContext(ctx, app) + challengeID = strings.TrimSpace(challengeID) + if challengeID == "" { + return nil, 0, xerr.New(xerr.InvalidArgument, "room rps challenge_id is required") + } + + s.mu.Lock() + defer s.mu.Unlock() + + challenge, ok := s.challenges[challengeID] + if !ok || challenge.GetAppCode() != app { + return nil, 0, xerr.New(xerr.NotFound, "room rps challenge not found") + } + return cloneChallenge(challenge), s.now().UnixMilli(), nil +} + +func (s *Service) RetrySettlement(ctx context.Context, app string, challengeID string) (*gamev1.RoomRPSChallenge, int64, error) { + if s == nil { + return nil, 0, xerr.New(xerr.Unavailable, "room rps service is not configured") + } + app = appcode.Normalize(app) + ctx = appcode.WithContext(ctx, app) + challengeID = strings.TrimSpace(challengeID) + if challengeID == "" { + return nil, 0, xerr.New(xerr.InvalidArgument, "room rps challenge_id is required") + } + + s.mu.Lock() + challenge, ok := s.challenges[challengeID] + if !ok || challenge.GetAppCode() != app { + s.mu.Unlock() + return nil, 0, xerr.New(xerr.NotFound, "room rps challenge not found") + } + if challenge.GetStatus() != StatusSettlementFailed && challenge.GetSettlementStatus() != SettlementFailed { + // 只有结算失败单能进入重试;已结算、已退款、pending 或 timeout 都不能被后台按钮强制改结果。 + s.mu.Unlock() + return nil, 0, xerr.New(xerr.Conflict, "room rps challenge is not settlement_failed") + } + nowMS := s.now().UnixMilli() + challenge.Status = StatusFinished + challenge.SettlementStatus = SettlementSettled + challenge.FailureReason = "" + challenge.SettledAtMs = nowMS + challenge.UpdatedAtMs = nowMS + cloned := cloneChallenge(challenge) + s.mu.Unlock() + + if err := s.publishChallengeEvent(ctx, eventFinished, cloned); err != nil { + return nil, 0, err + } + return cloned, nowMS, nil +} + +func (s *Service) ExpireChallenge(ctx context.Context, app string, challengeID string) (*gamev1.RoomRPSChallenge, int64, error) { + if s == nil { + return nil, 0, xerr.New(xerr.Unavailable, "room rps service is not configured") + } + app = appcode.Normalize(app) + ctx = appcode.WithContext(ctx, app) + challengeID = strings.TrimSpace(challengeID) + if challengeID == "" { + return nil, 0, xerr.New(xerr.InvalidArgument, "room rps challenge_id is required") + } + + s.mu.Lock() + challenge, ok := s.challenges[challengeID] + if !ok || challenge.GetAppCode() != app { + s.mu.Unlock() + return nil, 0, xerr.New(xerr.NotFound, "room rps challenge not found") + } + if challenge.GetStatus() != StatusPending || challenge.GetChallenger().GetUserId() > 0 { + // 手动过期只允许无人应战的 pending 单;一旦有 challenger,就必须走揭晓/结算链路,不能后台撤销。 + s.mu.Unlock() + return nil, 0, xerr.New(xerr.Conflict, "room rps challenge can not expire") + } + nowMS := s.now().UnixMilli() + challenge.Status = StatusTimeout + challenge.SettlementStatus = SettlementRefunded + challenge.UpdatedAtMs = nowMS + cloned := cloneChallenge(challenge) + s.mu.Unlock() + + if err := s.publishChallengeEvent(ctx, eventExpired, cloned); err != nil { + return nil, 0, err + } + return cloned, nowMS, nil +} + +func (s *Service) configForAppLocked(app string) *gamev1.RoomRPSConfig { + if config := s.configs[app]; config != nil { + return config + } + nowMS := s.now().UnixMilli() + config := defaultConfig(app, s.config.ChallengeTimeout, s.config.RevealCountdown, nowMS) + s.configs[app] = config + return config +} + +func (s *Service) normalizeConfig(app string, config *gamev1.RoomRPSConfig) (*gamev1.RoomRPSConfig, error) { + if config == nil { + return nil, xerr.New(xerr.InvalidArgument, "room rps config is required") + } + status := normalizeStatus(config.GetStatus()) + if status == "" { + // 逻辑功能配置默认打开;后台未传 status 时按 active 保存,不能静默写成关闭。 + status = "active" + } + if status != "active" && status != "disabled" { + return nil, xerr.New(xerr.InvalidArgument, "room rps status is invalid") + } + timeoutMS := config.GetChallengeTimeoutMs() + if timeoutMS <= 0 { + timeoutMS = int64(s.config.ChallengeTimeout / time.Millisecond) + } + revealMS := config.GetRevealCountdownMs() + if revealMS <= 0 { + revealMS = int64(s.config.RevealCountdown / time.Millisecond) + } + if len(config.GetStakeGifts()) != 4 { + return nil, xerr.New(xerr.InvalidArgument, "room rps requires exactly 4 stake gifts") + } + + defaultsByID := defaultGiftMap() + gifts := make([]*gamev1.RoomRPSStakeGift, 0, 4) + seenGift := make(map[int64]struct{}, 4) + seenSort := make(map[int32]struct{}, 4) + for index, gift := range config.GetStakeGifts() { + if gift.GetGiftId() <= 0 { + return nil, xerr.New(xerr.InvalidArgument, "room rps gift_id is invalid") + } + if _, ok := seenGift[gift.GetGiftId()]; ok { + return nil, xerr.New(xerr.InvalidArgument, "room rps gift_id is duplicated") + } + sortOrder := gift.GetSortOrder() + if sortOrder <= 0 { + sortOrder = int32(index + 1) + } + if _, ok := seenSort[sortOrder]; ok { + return nil, xerr.New(xerr.InvalidArgument, "room rps gift sort_order is duplicated") + } + seenGift[gift.GetGiftId()] = struct{}{} + seenSort[sortOrder] = struct{}{} + + normalized := &gamev1.RoomRPSStakeGift{ + GiftId: gift.GetGiftId(), + GiftName: strings.TrimSpace(gift.GetGiftName()), + GiftIconUrl: strings.TrimSpace(gift.GetGiftIconUrl()), + GiftPriceCoin: gift.GetGiftPriceCoin(), + Enabled: gift.GetEnabled(), + SortOrder: sortOrder, + } + if fallback := defaultsByID[gift.GetGiftId()]; fallback != nil { + if normalized.GiftName == "" { + normalized.GiftName = fallback.GetGiftName() + } + if normalized.GiftIconUrl == "" { + normalized.GiftIconUrl = fallback.GetGiftIconUrl() + } + if normalized.GiftPriceCoin <= 0 { + normalized.GiftPriceCoin = fallback.GetGiftPriceCoin() + } + } + if normalized.GiftPriceCoin <= 0 { + // 运行时必须知道当前档位折算金币,后续接钱包托管也以这个金额作为订单事实。 + normalized.GiftPriceCoin = int64(sortOrder) * 100 + } + gifts = append(gifts, normalized) + } + sort.Slice(gifts, func(i, j int) bool { return gifts[i].GetSortOrder() < gifts[j].GetSortOrder() }) + return &gamev1.RoomRPSConfig{ + AppCode: app, + GameId: GameID, + Status: status, + ChallengeTimeoutMs: timeoutMS, + RevealCountdownMs: revealMS, + StakeGifts: gifts, + }, nil +} + +func (s *Service) publishChallengeEvent(ctx context.Context, eventType string, challenge *gamev1.RoomRPSChallenge) error { + if s.pub == nil || challenge == nil { + return nil + } + // 每次发业务 IM 前都幂等建群;真实腾讯云已存在返回成功,临时房间或测试群也不需要额外预置。 + if err := s.pub.EnsureGroup(ctx, tencentim.GroupSpec{ + GroupID: challenge.GetRoomId(), + Name: challenge.GetRoomId(), + Type: tencentim.DefaultGroupType, + ApplyJoinOption: "FreeAccess", + }); err != nil { + return xerr.New(xerr.Unavailable, "room rps im group ensure failed") + } + payload, err := s.challengeEventPayload(ctx, eventType, challenge) + if err != nil { + return err + } + eventID := fmt.Sprintf("%s:%s:%d", eventType, challenge.GetChallengeId(), s.now().UnixNano()) + if err := s.pub.PublishGroupCustomMessage(ctx, tencentim.CustomGroupMessage{ + GroupID: challenge.GetRoomId(), + EventID: eventID, + Desc: eventType, + Ext: "room_rps", + PayloadJSON: payload, + }); err != nil { + return xerr.New(xerr.Unavailable, "room rps im publish failed") + } + return nil +} + +func (s *Service) challengeEventPayload(ctx context.Context, eventType string, challenge *gamev1.RoomRPSChallenge) (json.RawMessage, error) { + nowMS := s.now().UnixMilli() + payload := map[string]any{ + "event_id": fmt.Sprintf("%s:%s:%d", eventType, challenge.GetChallengeId(), nowMS), + "event_type": eventType, + "room_id": challenge.GetRoomId(), + "challenge_id": challenge.GetChallengeId(), + "server_time_ms": nowMS, + "challenge": s.challengeMap(ctx, challenge), + } + raw, err := json.Marshal(payload) + if err != nil { + return nil, xerr.New(xerr.Internal, "room rps im payload marshal failed") + } + return raw, nil +} + +func (s *Service) challengeMap(ctx context.Context, challenge *gamev1.RoomRPSChallenge) map[string]any { + return map[string]any{ + "app_code": challenge.GetAppCode(), + "challenge_id": challenge.GetChallengeId(), + "room_id": challenge.GetRoomId(), + "region_id": challenge.GetRegionId(), + "status": challenge.GetStatus(), + "stake_gift_id": strconv.FormatInt(challenge.GetStakeGiftId(), 10), + "stake_gift_id_number": challenge.GetStakeGiftId(), + "stake_coin": challenge.GetStakeCoin(), + "initiator": s.playerMap(ctx, challenge.GetAppCode(), challenge.GetInitiator()), + "challenger": s.playerMap(ctx, challenge.GetAppCode(), challenge.GetChallenger()), + "winner_user_id": strconv.FormatInt(challenge.GetWinnerUserId(), 10), + "settlement_status": challenge.GetSettlementStatus(), + "failure_reason": challenge.GetFailureReason(), + "timeout_at_ms": challenge.GetTimeoutAtMs(), + "reveal_at_ms": challenge.GetRevealAtMs(), + "created_at_ms": challenge.GetCreatedAtMs(), + "matched_at_ms": challenge.GetMatchedAtMs(), + "settled_at_ms": challenge.GetSettledAtMs(), + "updated_at_ms": challenge.GetUpdatedAtMs(), + } +} + +func (s *Service) playerMap(ctx context.Context, app string, player *gamev1.RoomRPSPlayer) map[string]any { + if player == nil || player.GetUserId() <= 0 { + return map[string]any{} + } + user := s.displayUser(ctx, app, player.GetUserId()) + return map[string]any{ + "user_id": strconv.FormatInt(player.GetUserId(), 10), + "user_id_number": player.GetUserId(), + "gesture": player.GetGesture(), + "result": player.GetResult(), + "balance_after": player.GetBalanceAfter(), + "joined_at_ms": player.GetJoinedAtMs(), + "user": user, + } +} + +func (s *Service) displayUser(ctx context.Context, app string, userID int64) map[string]any { + user := map[string]any{ + "user_id": strconv.FormatInt(userID, 10), + } + if s.user == nil || userID <= 0 { + return user + } + resp, err := s.user.GetUser(ctx, &userv1.GetUserRequest{ + Meta: &userv1.RequestMeta{AppCode: appcode.Normalize(app), Caller: "game-service-room-rps", SentAtMs: s.now().UnixMilli()}, + UserId: userID, + }) + if err != nil || resp.GetUser() == nil { + // IM 资料补齐不能反向改挑战事实;失败时保留 user_id,HTTP 详情仍可由 gateway 再查一次资料。 + return user + } + user["display_user_id"] = resp.GetUser().GetDisplayUserId() + user["nickname"] = resp.GetUser().GetUsername() + user["avatar"] = resp.GetUser().GetAvatar() + return user +} + +func defaultConfig(app string, timeout time.Duration, reveal time.Duration, nowMS int64) *gamev1.RoomRPSConfig { + return &gamev1.RoomRPSConfig{ + AppCode: app, + GameId: GameID, + Status: "active", + ChallengeTimeoutMs: int64(timeout / time.Millisecond), + RevealCountdownMs: int64(reveal / time.Millisecond), + StakeGifts: defaultGifts(), + CreatedAtMs: nowMS, + UpdatedAtMs: nowMS, + } +} + +func defaultGifts() []*gamev1.RoomRPSStakeGift { + return []*gamev1.RoomRPSStakeGift{ + {GiftId: 10001, GiftName: "RPS 100", GiftIconUrl: "https://cdn.example.com/gifts/room-rps-100.png", GiftPriceCoin: 100, Enabled: true, SortOrder: 1}, + {GiftId: 10002, GiftName: "RPS 300", GiftIconUrl: "https://cdn.example.com/gifts/room-rps-300.png", GiftPriceCoin: 300, Enabled: true, SortOrder: 2}, + {GiftId: 10003, GiftName: "RPS 500", GiftIconUrl: "https://cdn.example.com/gifts/room-rps-500.png", GiftPriceCoin: 500, Enabled: true, SortOrder: 3}, + {GiftId: 10004, GiftName: "RPS 1000", GiftIconUrl: "https://cdn.example.com/gifts/room-rps-1000.png", GiftPriceCoin: 1000, Enabled: true, SortOrder: 4}, + } +} + +func defaultGiftMap() map[int64]*gamev1.RoomRPSStakeGift { + out := make(map[int64]*gamev1.RoomRPSStakeGift, 4) + for _, gift := range defaultGifts() { + out[gift.GetGiftId()] = gift + } + return out +} + +func stakeGiftByID(config *gamev1.RoomRPSConfig, giftID int64) (*gamev1.RoomRPSStakeGift, bool) { + for _, gift := range config.GetStakeGifts() { + if gift.GetGiftId() == giftID { + return gift, true + } + } + return nil, false +} + +func applyResult(challenge *gamev1.RoomRPSChallenge) { + initiatorGesture := challenge.GetInitiator().GetGesture() + challengerGesture := challenge.GetChallenger().GetGesture() + switch { + case initiatorGesture == challengerGesture: + challenge.Initiator.Result = ResultDraw + challenge.Challenger.Result = ResultDraw + challenge.WinnerUserId = 0 + challenge.SettlementStatus = SettlementRefunded + case beats(initiatorGesture, challengerGesture): + challenge.Initiator.Result = ResultWin + challenge.Challenger.Result = ResultLose + challenge.WinnerUserId = challenge.GetInitiator().GetUserId() + challenge.SettlementStatus = SettlementSettled + default: + challenge.Initiator.Result = ResultLose + challenge.Challenger.Result = ResultWin + challenge.WinnerUserId = challenge.GetChallenger().GetUserId() + challenge.SettlementStatus = SettlementSettled + } +} + +func beats(left string, right string) bool { + return (left == GestureRock && right == GestureScissors) || + (left == GesturePaper && right == GestureRock) || + (left == GestureScissors && right == GesturePaper) +} + +func normalizeGesture(value string) (string, bool) { + switch strings.ToLower(strings.TrimSpace(value)) { + case GestureRock: + return GestureRock, true + case GesturePaper: + return GesturePaper, true + case GestureScissors: + return GestureScissors, true + default: + return "", false + } +} + +func normalizeStatus(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "", "all": + return "" + case StatusPending, StatusMatched, StatusRevealing, StatusFinished, StatusTimeout, StatusSettlementFailed, StatusCancelled, "active", "disabled": + return strings.ToLower(strings.TrimSpace(value)) + default: + return strings.ToLower(strings.TrimSpace(value)) + } +} + +func cloneConfig(config *gamev1.RoomRPSConfig) *gamev1.RoomRPSConfig { + if config == nil { + return nil + } + // protobuf message 内部带锁状态,不能用结构体赋值浅拷贝;proto.Clone 会复制业务字段并重建 message state。 + cloned, ok := proto.Clone(config).(*gamev1.RoomRPSConfig) + if !ok { + return nil + } + return cloned +} + +func cloneChallenge(challenge *gamev1.RoomRPSChallenge) *gamev1.RoomRPSChallenge { + if challenge == nil { + return nil + } + // 对局快照同样必须走 protobuf 深拷贝,避免 vet copylocks 和嵌套玩家资料被调用方意外共享。 + cloned, ok := proto.Clone(challenge).(*gamev1.RoomRPSChallenge) + if !ok { + return nil + } + return cloned +} + +func newChallengeID(app string) string { + var entropy [8]byte + if _, err := rand.Read(entropy[:]); err != nil { + return fmt.Sprintf("rps_%s_%d", appcode.Normalize(app), time.Now().UnixNano()) + } + return fmt.Sprintf("rps_%s_%d_%s", appcode.Normalize(app), time.Now().UnixMilli(), hex.EncodeToString(entropy[:])) +} + +func envOr(key string, fallback string) string { + value := strings.TrimSpace(os.Getenv(key)) + if value == "" { + return fallback + } + return value +} diff --git a/services/game-service/internal/transport/grpc/server.go b/services/game-service/internal/transport/grpc/server.go index 466311c1..d9ff14c0 100644 --- a/services/game-service/internal/transport/grpc/server.go +++ b/services/game-service/internal/transport/grpc/server.go @@ -12,6 +12,7 @@ import ( gamedomain "hyapp/services/game-service/internal/domain/game" diceservice "hyapp/services/game-service/internal/service/dice" gameservice "hyapp/services/game-service/internal/service/game" + roomrpsservice "hyapp/services/game-service/internal/service/roomrps" ) // Server 把 game-service 用例层适配为 gRPC 契约。 @@ -21,17 +22,26 @@ type Server struct { gamev1.UnimplementedGameAdminServiceServer gamev1.UnimplementedGameCronServiceServer - svc *gameservice.Service - diceSvc *diceservice.Service + svc *gameservice.Service + diceSvc *diceservice.Service + roomRPSSvc *roomrpsservice.Service } -func NewServer(svc *gameservice.Service, diceServices ...*diceservice.Service) *Server { - // diceSvc 用可选参数接入,保留旧测试和非骰子场景的构造方式;生产 app 会显式传入骰子服务。 - var diceSvc *diceservice.Service - if len(diceServices) > 0 { - diceSvc = diceServices[0] +func NewServer(svc *gameservice.Service, extras ...any) *Server { + // extras 用类型分派保留旧测试构造方式;生产 app 会同时传入骰子服务和房内猜拳服务。 + var ( + diceSvc *diceservice.Service + roomRPSSvc *roomrpsservice.Service + ) + for _, extra := range extras { + switch typed := extra.(type) { + case *diceservice.Service: + diceSvc = typed + case *roomrpsservice.Service: + roomRPSSvc = typed + } } - return &Server{svc: svc, diceSvc: diceSvc} + return &Server{svc: svc, diceSvc: diceSvc, roomRPSSvc: roomRPSSvc} } func (s *Server) ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) { @@ -243,6 +253,110 @@ func (s *Server) CancelDiceMatch(ctx context.Context, req *gamev1.CancelDiceMatc return &gamev1.DiceMatchResponse{Match: diceMatchToProtoAt(match, serverTimeMS), ServerTimeMs: serverTimeMS}, nil } +func (s *Server) GetRoomRPSConfig(ctx context.Context, req *gamev1.GetRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) { + if s == nil || s.roomRPSSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room rps service is not configured")) + } + // 配置只按 meta.app_code 读取;App/Admin 都不能在 body 里传 app_code 读写其它租户配置。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + config, serverTimeMS, err := s.roomRPSSvc.GetConfig(ctx, req.GetMeta().GetAppCode()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.RoomRPSConfigResponse{Config: config, ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) ListRoomRPSChallenges(ctx context.Context, req *gamev1.ListRoomRPSChallengesRequest) (*gamev1.ListRoomRPSChallengesResponse, error) { + if s == nil || s.roomRPSSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room rps service is not configured")) + } + // 列表参数完整透传给 service;room_id、状态、用户筛选、时间窗和游标必须在同一个事实源内判断。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + items, nextCursor, pageSize, serverTimeMS, err := s.roomRPSSvc.ListChallenges(ctx, req) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.ListRoomRPSChallengesResponse{Challenges: items, NextCursor: nextCursor, PageSize: pageSize, ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) CreateRoomRPSChallenge(ctx context.Context, req *gamev1.CreateRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + if s == nil || s.roomRPSSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room rps service is not configured")) + } + // 发起人身份、房间、礼物和手势全部交给 service 原子校验;transport 不在这里复制状态规则。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + challenge, serverTimeMS, err := s.roomRPSSvc.CreateChallenge(ctx, req) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.RoomRPSChallengeResponse{Challenge: challenge, ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) AcceptRoomRPSChallenge(ctx context.Context, req *gamev1.AcceptRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + if s == nil || s.roomRPSSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room rps service is not configured")) + } + // 应战入口只做 proto 到 service 的调用;pending 锁定、本人不能应战、超时收敛和胜负结算都在 service 内完成。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + challenge, serverTimeMS, err := s.roomRPSSvc.AcceptChallenge(ctx, req) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.RoomRPSChallengeResponse{Challenge: challenge, ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) GetRoomRPSChallenge(ctx context.Context, req *gamev1.GetRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + if s == nil || s.roomRPSSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room rps service is not configured")) + } + // 详情是 IM 事件后的权威补拉接口;transport 不根据 user_id 推断展示内容,统一交给 service 返回事实。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + challenge, serverTimeMS, err := s.roomRPSSvc.GetChallenge(ctx, req.GetMeta().GetAppCode(), req.GetChallengeId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.RoomRPSChallengeResponse{Challenge: challenge, ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) UpdateRoomRPSConfig(ctx context.Context, req *gamev1.UpdateRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) { + if s == nil || s.roomRPSSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room rps service is not configured")) + } + // 后台写配置只落 room_rps 独立配置域;固定四档礼物、默认开启和倒计时默认值由 service 统一归一。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + config, serverTimeMS, err := s.roomRPSSvc.UpdateConfig(ctx, req.GetMeta().GetAppCode(), req.GetConfig()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.RoomRPSConfigResponse{Config: config, ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) RetryRoomRPSSettlement(ctx context.Context, req *gamev1.RetryRoomRPSSettlementRequest) (*gamev1.RoomRPSChallengeResponse, error) { + if s == nil || s.roomRPSSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room rps service is not configured")) + } + // 结算重试只能从 settlement_failed 进入;transport 不允许 admin 传目标状态,避免后台绕过状态机。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + challenge, serverTimeMS, err := s.roomRPSSvc.RetrySettlement(ctx, req.GetMeta().GetAppCode(), req.GetChallengeId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.RoomRPSChallengeResponse{Challenge: challenge, ServerTimeMs: serverTimeMS}, nil +} + +func (s *Server) ExpireRoomRPSChallenge(ctx context.Context, req *gamev1.ExpireRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + if s == nil || s.roomRPSSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room rps service is not configured")) + } + // 手动过期只允许 still-pending 且无人应战的挑战;服务端重查事实,不能相信后台按钮状态。 + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + challenge, serverTimeMS, err := s.roomRPSSvc.ExpireChallenge(ctx, req.GetMeta().GetAppCode(), req.GetChallengeId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &gamev1.RoomRPSChallengeResponse{Challenge: challenge, ServerTimeMs: serverTimeMS}, nil +} + func (s *Server) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) raw, contentType, err := s.svc.HandleCallback(ctx, req) diff --git a/services/gateway-service/internal/client/game_client.go b/services/gateway-service/internal/client/game_client.go index 54778dc4..a4bb6856 100644 --- a/services/gateway-service/internal/client/game_client.go +++ b/services/gateway-service/internal/client/game_client.go @@ -20,6 +20,11 @@ type GameClient interface { GetDiceMatch(ctx context.Context, req *gamev1.GetDiceMatchRequest) (*gamev1.DiceMatchResponse, error) RollDiceMatch(ctx context.Context, req *gamev1.RollDiceMatchRequest) (*gamev1.DiceMatchResponse, error) CancelDiceMatch(ctx context.Context, req *gamev1.CancelDiceMatchRequest) (*gamev1.DiceMatchResponse, error) + GetRoomRPSConfig(ctx context.Context, req *gamev1.GetRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) + ListRoomRPSChallenges(ctx context.Context, req *gamev1.ListRoomRPSChallengesRequest) (*gamev1.ListRoomRPSChallengesResponse, error) + CreateRoomRPSChallenge(ctx context.Context, req *gamev1.CreateRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) + AcceptRoomRPSChallenge(ctx context.Context, req *gamev1.AcceptRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) + GetRoomRPSChallenge(ctx context.Context, req *gamev1.GetRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) } @@ -79,6 +84,31 @@ func (c *grpcGameClient) CancelDiceMatch(ctx context.Context, req *gamev1.Cancel return c.app.CancelDiceMatch(ctx, req) } +func (c *grpcGameClient) GetRoomRPSConfig(ctx context.Context, req *gamev1.GetRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) { + // gateway 只调用 App RPC,不碰 Admin RPC;App 配置读取必须和客户端权限、app_code 元信息走同一条 game-service 通道。 + return c.app.GetRoomRPSConfig(ctx, req) +} + +func (c *grpcGameClient) ListRoomRPSChallenges(ctx context.Context, req *gamev1.ListRoomRPSChallengesRequest) (*gamev1.ListRoomRPSChallengesResponse, error) { + // 列表是 App 补拉房间实时状态的入口,gateway 不做本地聚合,避免和 game-service 的过期/结算补偿状态不一致。 + return c.app.ListRoomRPSChallenges(ctx, req) +} + +func (c *grpcGameClient) CreateRoomRPSChallenge(ctx context.Context, req *gamev1.CreateRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + // 发起挑战必须进入 game-service 事务链路,那里才能同时处理礼物托管、订单幂等和房间 IM outbox。 + return c.app.CreateRoomRPSChallenge(ctx, req) +} + +func (c *grpcGameClient) AcceptRoomRPSChallenge(ctx context.Context, req *gamev1.AcceptRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + // 应战是并发敏感操作,gateway 只透传当前登录用户和手势,锁单和钱包托管由 game-service 完成。 + return c.app.AcceptRoomRPSChallenge(ctx, req) +} + +func (c *grpcGameClient) GetRoomRPSChallenge(ctx context.Context, req *gamev1.GetRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + // 详情用于 IM 后补拉和断线恢复,必须读取 game-service 的订单事实,不能复用 HTTP 层缓存。 + return c.app.GetRoomRPSChallenge(ctx, req) +} + func (c *grpcGameClient) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { return c.callback.HandleCallback(ctx, req) } diff --git a/services/gateway-service/internal/client/user_client.go b/services/gateway-service/internal/client/user_client.go index db3ea61c..25196b70 100644 --- a/services/gateway-service/internal/client/user_client.go +++ b/services/gateway-service/internal/client/user_client.go @@ -65,6 +65,7 @@ type UserCPClient interface { AcceptCPApplication(ctx context.Context, req *userv1.AcceptCPApplicationRequest) (*userv1.AcceptCPApplicationResponse, error) RejectCPApplication(ctx context.Context, req *userv1.RejectCPApplicationRequest) (*userv1.RejectCPApplicationResponse, error) ListCPRelationships(ctx context.Context, req *userv1.ListCPRelationshipsRequest) (*userv1.ListCPRelationshipsResponse, error) + ListCPIntimacyLeaderboard(ctx context.Context, req *userv1.ListCPIntimacyLeaderboardRequest) (*userv1.ListCPIntimacyLeaderboardResponse, error) PrepareBreakCPRelationship(ctx context.Context, req *userv1.PrepareBreakCPRelationshipRequest) (*userv1.PrepareBreakCPRelationshipResponse, error) ConfirmBreakCPRelationship(ctx context.Context, req *userv1.ConfirmBreakCPRelationshipRequest) (*userv1.ConfirmBreakCPRelationshipResponse, error) CancelBreakCPRelationship(ctx context.Context, req *userv1.CancelBreakCPRelationshipRequest) (*userv1.CancelBreakCPRelationshipResponse, error) @@ -380,6 +381,10 @@ func (c *grpcUserCPClient) ListCPRelationships(ctx context.Context, req *userv1. return c.client.ListCPRelationships(ctx, req) } +func (c *grpcUserCPClient) ListCPIntimacyLeaderboard(ctx context.Context, req *userv1.ListCPIntimacyLeaderboardRequest) (*userv1.ListCPIntimacyLeaderboardResponse, error) { + return c.client.ListCPIntimacyLeaderboard(ctx, req) +} + func (c *grpcUserCPClient) PrepareBreakCPRelationship(ctx context.Context, req *userv1.PrepareBreakCPRelationshipRequest) (*userv1.PrepareBreakCPRelationshipResponse, error) { return c.client.PrepareBreakCPRelationship(ctx, req) } diff --git a/services/gateway-service/internal/transport/http/activityapi/task_handler.go b/services/gateway-service/internal/transport/http/activityapi/task_handler.go index 4f8e8bbc..9b4f0840 100644 --- a/services/gateway-service/internal/transport/http/activityapi/task_handler.go +++ b/services/gateway-service/internal/transport/http/activityapi/task_handler.go @@ -1,12 +1,13 @@ package activityapi import ( - "hyapp/services/gateway-service/internal/transport/http/httpkit" + "encoding/json" "net/http" "strings" activityv1 "hyapp.local/api/proto/activity/v1" "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" ) type taskSectionData struct { @@ -17,23 +18,31 @@ type taskSectionData struct { } type taskItemData struct { - TaskID string `json:"task_id"` - TaskType string `json:"task_type"` - Category string `json:"category"` - MetricType string `json:"metric_type"` - Title string `json:"title"` - Description string `json:"description"` - TargetValue int64 `json:"target_value"` - TargetUnit string `json:"target_unit"` - ProgressValue int64 `json:"progress_value"` - RewardCoinAmount int64 `json:"reward_coin_amount"` - Status string `json:"status"` - Claimable bool `json:"claimable"` - TaskDay string `json:"task_day"` - ServerTimeMS int64 `json:"server_time_ms"` - NextRefreshAtMS int64 `json:"next_refresh_at_ms"` - SortOrder int32 `json:"sort_order"` - Version int64 `json:"version"` + TaskID string `json:"task_id"` + TaskType string `json:"task_type"` + Category string `json:"category"` + MetricType string `json:"metric_type"` + Title string `json:"title"` + Description string `json:"description"` + AudienceType string `json:"audience_type"` + IconKey string `json:"icon_key"` + IconURL string `json:"icon_url"` + ActionType string `json:"action_type"` + ActionParam string `json:"action_param"` + ActionPayloadJSON string `json:"action_payload_json"` + ActionPayload map[string]any `json:"action_payload"` + DimensionFilterJSON string `json:"dimension_filter_json"` + TargetValue int64 `json:"target_value"` + TargetUnit string `json:"target_unit"` + ProgressValue int64 `json:"progress_value"` + RewardCoinAmount int64 `json:"reward_coin_amount"` + Status string `json:"status"` + Claimable bool `json:"claimable"` + TaskDay string `json:"task_day"` + ServerTimeMS int64 `json:"server_time_ms"` + NextRefreshAtMS int64 `json:"next_refresh_at_ms"` + SortOrder int32 `json:"sort_order"` + Version int64 `json:"version"` } type taskClaimData struct { @@ -166,23 +175,45 @@ func (h *Handler) claimTaskReward(writer http.ResponseWriter, request *http.Requ } func taskItemFromProto(item *activityv1.TaskItem) taskItemData { + // gateway 不重新计算任务状态,只把 activity-service 的进度、奖励、图标和跳转元数据转换为 App JSON 合约。 return taskItemData{ - TaskID: item.GetTaskId(), - TaskType: item.GetTaskType(), - Category: item.GetCategory(), - MetricType: item.GetMetricType(), - Title: item.GetTitle(), - Description: item.GetDescription(), - TargetValue: item.GetTargetValue(), - TargetUnit: item.GetTargetUnit(), - ProgressValue: item.GetProgressValue(), - RewardCoinAmount: item.GetRewardCoinAmount(), - Status: item.GetStatus(), - Claimable: item.GetClaimable(), - TaskDay: item.GetTaskDay(), - ServerTimeMS: item.GetServerTimeMs(), - NextRefreshAtMS: item.GetNextRefreshAtMs(), - SortOrder: item.GetSortOrder(), - Version: item.GetVersion(), + TaskID: item.GetTaskId(), + TaskType: item.GetTaskType(), + Category: item.GetCategory(), + MetricType: item.GetMetricType(), + Title: item.GetTitle(), + Description: item.GetDescription(), + AudienceType: item.GetAudienceType(), + IconKey: item.GetIconKey(), + IconURL: item.GetIconUrl(), + ActionType: item.GetActionType(), + ActionParam: item.GetActionParam(), + ActionPayloadJSON: item.GetActionPayloadJson(), + ActionPayload: taskJSONPayload(item.GetActionPayloadJson()), + DimensionFilterJSON: item.GetDimensionFilterJson(), + TargetValue: item.GetTargetValue(), + TargetUnit: item.GetTargetUnit(), + ProgressValue: item.GetProgressValue(), + RewardCoinAmount: item.GetRewardCoinAmount(), + Status: item.GetStatus(), + Claimable: item.GetClaimable(), + TaskDay: item.GetTaskDay(), + ServerTimeMS: item.GetServerTimeMs(), + NextRefreshAtMS: item.GetNextRefreshAtMs(), + SortOrder: item.GetSortOrder(), + Version: item.GetVersion(), } } + +func taskJSONPayload(value string) map[string]any { + payload := map[string]any{} + if strings.TrimSpace(value) == "" { + return payload + } + // action_payload_json 是给 App 的辅助结构化字段;解析失败时仍保留原始字符串字段,避免一个配置错误打断整个任务页。 + _ = json.Unmarshal([]byte(value), &payload) + if payload == nil { + return map[string]any{} + } + return payload +} diff --git a/services/gateway-service/internal/transport/http/game_handler_test.go b/services/gateway-service/internal/transport/http/game_handler_test.go index ee8680e0..b306623a 100644 --- a/services/gateway-service/internal/transport/http/game_handler_test.go +++ b/services/gateway-service/internal/transport/http/game_handler_test.go @@ -14,6 +14,7 @@ import ( "time" gamev1 "hyapp.local/api/proto/game/v1" + userv1 "hyapp.local/api/proto/user/v1" "hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/transport/http/httpkit" ) @@ -187,6 +188,114 @@ func TestDiceMatchRenewsAccessTokenWhenTokenNearExpiry(t *testing.T) { } } +func TestCreateRoomRPSChallengeUsesAuthenticatedUserAndRegion(t *testing.T) { + gameClient := &fakeGatewayGameClient{roomRPSChallengeResp: &gamev1.RoomRPSChallengeResponse{ + Challenge: &gamev1.RoomRPSChallenge{ + ChallengeId: "rps-1", + RoomId: "room-1", + Status: "pending", + StakeGiftId: 10001, + Initiator: &gamev1.RoomRPSPlayer{UserId: 42, Gesture: "rock"}, + }, + ServerTimeMs: 1700000000000, + }} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/games/room-rps/challenges/create", bytes.NewReader([]byte(`{"room_id":"room-1","gift_id":10001,"gesture":"Rock"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("Content-Type", "application/json") + 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 gameClient.lastCreateRoomRPS.GetUserId() != 42 || gameClient.lastCreateRoomRPS.GetRegionId() != 1001 || gameClient.lastCreateRoomRPS.GetRoomId() != "room-1" || gameClient.lastCreateRoomRPS.GetGiftId() != 10001 || gameClient.lastCreateRoomRPS.GetGesture() != "rock" { + t.Fatalf("CreateRoomRPSChallenge request mismatch: %+v", gameClient.lastCreateRoomRPS) + } + var envelope httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data := envelope.Data.(map[string]any) + challenge := data["challenge"].(map[string]any) + if challenge["challenge_id"] != "rps-1" || challenge["stake_gift_id"] != "10001" { + t.Fatalf("room rps response mismatch: %+v", challenge) + } +} + +func TestAcceptRoomRPSChallengeForwardsGestureOnly(t *testing.T) { + gameClient := &fakeGatewayGameClient{roomRPSChallengeResp: &gamev1.RoomRPSChallengeResponse{ + Challenge: &gamev1.RoomRPSChallenge{ + ChallengeId: "rps-1", + RoomId: "room-1", + Status: "finished", + Initiator: &gamev1.RoomRPSPlayer{UserId: 41, Gesture: "rock", Result: "lose"}, + Challenger: &gamev1.RoomRPSPlayer{UserId: 42, Gesture: "paper", Result: "win"}, + }, + }} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/games/room-rps/challenges/rps-1/accept", bytes.NewReader([]byte(`{"gesture":"paper","gift_id":999}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("Content-Type", "application/json") + 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 gameClient.lastAcceptRoomRPS.GetUserId() != 42 || gameClient.lastAcceptRoomRPS.GetChallengeId() != "rps-1" || gameClient.lastAcceptRoomRPS.GetGesture() != "paper" { + t.Fatalf("AcceptRoomRPSChallenge request mismatch: %+v", gameClient.lastAcceptRoomRPS) + } +} + +func TestGetRoomRPSChallengeEnrichesBothPlayerProfiles(t *testing.T) { + gameClient := &fakeGatewayGameClient{roomRPSChallengeResp: &gamev1.RoomRPSChallengeResponse{ + Challenge: &gamev1.RoomRPSChallenge{ + ChallengeId: "rps-1", + RoomId: "room-1", + Status: "finished", + Initiator: &gamev1.RoomRPSPlayer{UserId: 41, Gesture: "rock", Result: "lose"}, + Challenger: &gamev1.RoomRPSPlayer{UserId: 42, Gesture: "paper", Result: "win"}, + }, + }} + profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{ + 41: &userv1.User{UserId: 41, DisplayUserId: "410001", Username: "Alice", Avatar: "https://cdn.example/alice.png"}, + 42: &userv1.User{UserId: 42, DisplayUserId: "420001", Username: "Bob", Avatar: "https://cdn.example/bob.png"}, + }} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/games/room-rps/challenges/rps-1", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + 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()) + } + // 房内猜拳 IM 也需要同一套展示字段;HTTP 详情先锁住 gateway 返回的双方头像昵称,避免 Flutter 为基础展示再补拉资料。 + var envelope httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + challenge := envelope.Data.(map[string]any)["challenge"].(map[string]any) + initiatorUser := challenge["initiator"].(map[string]any)["user"].(map[string]any) + challengerUser := challenge["challenger"].(map[string]any)["user"].(map[string]any) + if initiatorUser["nickname"] != "Alice" || initiatorUser["avatar"] != "https://cdn.example/alice.png" { + t.Fatalf("initiator display profile mismatch: %+v", initiatorUser) + } + if challengerUser["nickname"] != "Bob" || challengerUser["avatar"] != "https://cdn.example/bob.png" { + t.Fatalf("challenger display profile mismatch: %+v", challengerUser) + } +} + func TestGameCallbackIsRawPublicResponse(t *testing.T) { gameClient := &fakeGatewayGameClient{callbackResp: &gamev1.CallbackResponse{RawBody: []byte(`{"code":0}`), ContentType: "application/json"}} handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) @@ -287,16 +396,21 @@ func (fn roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) } type fakeGatewayGameClient struct { - listResp *gamev1.ListGamesResponse - recentResp *gamev1.ListGamesResponse - bridgeResp *gamev1.GetBridgeScriptResponse - launchResp *gamev1.LaunchGameResponse - callbackResp *gamev1.CallbackResponse - lastList *gamev1.ListGamesRequest - lastRecent *gamev1.ListRecentGamesRequest - lastBridge *gamev1.GetBridgeScriptRequest - lastLaunch *gamev1.LaunchGameRequest - lastCallback *gamev1.CallbackRequest + listResp *gamev1.ListGamesResponse + recentResp *gamev1.ListGamesResponse + bridgeResp *gamev1.GetBridgeScriptResponse + launchResp *gamev1.LaunchGameResponse + callbackResp *gamev1.CallbackResponse + lastList *gamev1.ListGamesRequest + lastRecent *gamev1.ListRecentGamesRequest + lastBridge *gamev1.GetBridgeScriptRequest + lastLaunch *gamev1.LaunchGameRequest + lastCallback *gamev1.CallbackRequest + roomRPSChallengeResp *gamev1.RoomRPSChallengeResponse + lastListRoomRPS *gamev1.ListRoomRPSChallengesRequest + lastCreateRoomRPS *gamev1.CreateRoomRPSChallengeRequest + lastAcceptRoomRPS *gamev1.AcceptRoomRPSChallengeRequest + lastGetRoomRPS *gamev1.GetRoomRPSChallengeRequest } func (f *fakeGatewayGameClient) ListGames(_ context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) { @@ -359,6 +473,39 @@ func (f *fakeGatewayGameClient) CancelDiceMatch(context.Context, *gamev1.CancelD return &gamev1.DiceMatchResponse{}, nil } +func (f *fakeGatewayGameClient) GetRoomRPSConfig(context.Context, *gamev1.GetRoomRPSConfigRequest) (*gamev1.RoomRPSConfigResponse, error) { + return &gamev1.RoomRPSConfigResponse{}, nil +} + +func (f *fakeGatewayGameClient) ListRoomRPSChallenges(_ context.Context, req *gamev1.ListRoomRPSChallengesRequest) (*gamev1.ListRoomRPSChallengesResponse, error) { + f.lastListRoomRPS = req + return &gamev1.ListRoomRPSChallengesResponse{}, nil +} + +func (f *fakeGatewayGameClient) CreateRoomRPSChallenge(_ context.Context, req *gamev1.CreateRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + f.lastCreateRoomRPS = req + if f.roomRPSChallengeResp != nil { + return f.roomRPSChallengeResp, nil + } + return &gamev1.RoomRPSChallengeResponse{}, nil +} + +func (f *fakeGatewayGameClient) AcceptRoomRPSChallenge(_ context.Context, req *gamev1.AcceptRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + f.lastAcceptRoomRPS = req + if f.roomRPSChallengeResp != nil { + return f.roomRPSChallengeResp, nil + } + return &gamev1.RoomRPSChallengeResponse{}, nil +} + +func (f *fakeGatewayGameClient) GetRoomRPSChallenge(_ context.Context, req *gamev1.GetRoomRPSChallengeRequest) (*gamev1.RoomRPSChallengeResponse, error) { + f.lastGetRoomRPS = req + if f.roomRPSChallengeResp != nil { + return f.roomRPSChallengeResp, nil + } + return &gamev1.RoomRPSChallengeResponse{}, nil +} + func (f *fakeGatewayGameClient) HandleCallback(_ context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { f.lastCallback = req if f.callbackResp != nil { diff --git a/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go b/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go index 5551542b..2420f7ff 100644 --- a/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go +++ b/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go @@ -32,6 +32,54 @@ func (h *Handler) enrichDiceMatchParticipants(request *http.Request, match *dice } } +// enrichRoomRPSChallenges 只给房内猜拳响应补展示资料;挑战状态、手势和结算结果仍完全以 game-service 返回的事实为准。 +func (h *Handler) enrichRoomRPSChallenges(request *http.Request, challenges []*roomRPSChallengeItemData) { + if h == nil || len(challenges) == 0 { + return + } + // 先从所有挑战单里去重收集双方 user_id,再批量查 user-service,避免列表页按每一行逐个 RPC 放大延迟。 + userIDs := roomRPSChallengeUserIDs(challenges) + users := h.diceUsersByID(request, userIDs) + for _, challenge := range challenges { + if challenge == nil { + continue + } + // 发起人必须存在于正常挑战单;如果资料查询失败,保留 proto 映射时写入的 user_id,Flutter 仍可用 ID 做兜底展示和补拉。 + if user, ok := users[challenge.Initiator.UserIDNumber]; ok { + challenge.Initiator.User = user + } + // pending 订单还没有应战人,UserIDNumber 为 0 时不能写入空头像昵称,避免客户端误认为已有应战用户。 + if challenge.Challenger.UserIDNumber > 0 { + if user, ok := users[challenge.Challenger.UserIDNumber]; ok { + challenge.Challenger.User = user + } + } + } +} + +func roomRPSChallengeUserIDs(challenges []*roomRPSChallengeItemData) []int64 { + seen := make(map[int64]struct{}, len(challenges)*2) + out := make([]int64, 0, len(challenges)*2) + for _, challenge := range challenges { + if challenge == nil { + continue + } + for _, userID := range []int64{challenge.Initiator.UserIDNumber, challenge.Challenger.UserIDNumber} { + // 0 表示未应战或 proto 缺省值,不是一个真实用户,不能传给 user-service 批量查询。 + if userID <= 0 { + continue + } + // 同一房间列表里同一个用户可能连续发起多局,去重能减少 user-service 压力,也保证响应顺序只由原 challenges 决定。 + if _, ok := seen[userID]; ok { + continue + } + seen[userID] = struct{}{} + out = append(out, userID) + } + } + return out +} + func diceParticipantUserIDs(participants []diceParticipantItemData) []int64 { seen := make(map[int64]struct{}, len(participants)) out := make([]int64, 0, len(participants)) @@ -53,14 +101,17 @@ func (h *Handler) diceUsersByID(request *http.Request, userIDs []int64) map[int6 if h.userProfileClient == nil || len(userIDs) == 0 { return out } + // 展示资料使用当前请求的 app_code 和语言上下文;不同 App 可能有不同的资料可见性和默认昵称策略。 resp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{ Meta: httpkit.UserMeta(request, ""), UserIds: userIDs, }) if err != nil { + // 资料查询只是展示增强,失败不能吞掉游戏订单;调用方会用 user_id 兜底。 return out } for userID, user := range resp.GetUsers() { + // 返回嵌套 user 只放公开展示字段,不暴露钱包、状态、封禁、隐私配置等内部资料。 out[userID] = diceUserData{ UserID: int64String(user.GetUserId()), DisplayUserID: user.GetDisplayUserId(), diff --git a/services/gateway-service/internal/transport/http/gameapi/game_handler.go b/services/gateway-service/internal/transport/http/gameapi/game_handler.go index fb88afb2..728ec5f9 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_handler.go @@ -312,6 +312,140 @@ func (h *Handler) cancelDiceMatch(writer http.ResponseWriter, request *http.Requ h.writeDiceMatch(writer, request, resp, err) } +func (h *Handler) getRoomRPSConfig(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + // 配置查询只取当前请求上下文里的 app_code;App 不能上传 app_code,否则同一客户端可以越权读取其它马甲包的礼物档位。 + // gateway 不缓存配置,避免后台刚改完礼物档位或开关后,房间入口还拿到旧的下注金额和倒计时。 + resp, err := h.gameClient.GetRoomRPSConfig(request.Context(), &gamev1.GetRoomRPSConfigRequest{ + Meta: gameMeta(request), + }) + httpkit.Write(writer, request, roomRPSConfigDataFromProto(resp), err) +} + +func (h *Handler) listRoomRPSChallenges(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + roomID := strings.TrimSpace(request.URL.Query().Get("room_id")) + if roomID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 20) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // room_id 必须来自 query,表示当前房间的公开挑战列表;用户身份只来自 JWT,不能让前端传 user_id 旁路查看其它人的私有状态。 + // page_size 在 gateway 层先做正数保护,防止空值、负数或非数字继续透传到 game-service 造成分页语义不一致。 + // status 只是筛选条件,最终哪些状态可见、游标如何解释、过期挑战是否需要补偿收敛,都必须由 game-service 按订单事实判断。 + resp, err := h.gameClient.ListRoomRPSChallenges(request.Context(), &gamev1.ListRoomRPSChallengesRequest{ + Meta: gameMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + RoomId: roomID, + Status: strings.TrimSpace(request.URL.Query().Get("status")), + PageSize: pageSize, + Cursor: strings.TrimSpace(request.URL.Query().Get("cursor")), + }) + data := roomRPSChallengeListDataFromProto(resp) + // 挑战事实来自 game-service,头像昵称来自 user-service;资料补全失败时仍返回订单,保证 IM 丢失后的补拉不被展示层依赖拖垮。 + h.enrichRoomRPSChallenges(request, data.Challenges) + httpkit.Write(writer, request, data, err) +} + +func (h *Handler) createRoomRPSChallenge(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + // 发起挑战只接受房间、礼物档位和本人手势;发起人 user_id、app_code、region_id 都由服务端上下文补齐。 + // 这样做可以避免客户端伪造他人发起挑战,也能让后续钱包托管、风控和房间语区统计使用同一份可信身份。 + var body struct { + RoomID string `json:"room_id"` + GiftID int64 `json:"gift_id"` + Gesture string `json:"gesture"` + } + if !httpkit.Decode(writer, request, &body) { + return + } + gesture, ok := normalizeRoomRPSGesture(body.Gesture) + if strings.TrimSpace(body.RoomID) == "" || body.GiftID <= 0 || !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + userID := auth.UserIDFromContext(request.Context()) + // 当前用户资料是 region_id 的可信来源;即使客户端已经在房间里,仍不能让客户端自己上传 region_id 参与订单归属。 + userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + // gateway 只做协议适配和可信上下文拼装;礼物档位是否启用、金币托管是否成功、重复 command 如何收敛,全部在 game-service 事务内完成。 + resp, err := h.gameClient.CreateRoomRPSChallenge(request.Context(), &gamev1.CreateRoomRPSChallengeRequest{ + Meta: gameMeta(request), + UserId: userID, + RoomId: strings.TrimSpace(body.RoomID), + RegionId: userResp.GetUser().GetRegionId(), + GiftId: body.GiftID, + Gesture: gesture, + }) + h.writeRoomRPSChallenge(writer, request, resp, err) +} + +func (h *Handler) acceptRoomRPSChallenge(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + // 应战入口只需要 challenge_id 和本人手势;房间、押注礼物、发起人和超时时间都从订单事实读取,不能相信客户端 body。 + challengeID := strings.TrimSpace(request.PathValue("challenge_id")) + var body struct { + Gesture string `json:"gesture"` + } + if !httpkit.Decode(writer, request, &body) { + return + } + gesture, ok := normalizeRoomRPSGesture(body.Gesture) + if challengeID == "" || !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // game-service 必须在一个事务里判断 pending、未超时、未被本人重复应战、钱包托管成功;gateway 不做影子状态判断,避免并发窗口不一致。 + resp, err := h.gameClient.AcceptRoomRPSChallenge(request.Context(), &gamev1.AcceptRoomRPSChallengeRequest{ + Meta: gameMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + ChallengeId: challengeID, + Gesture: gesture, + }) + h.writeRoomRPSChallenge(writer, request, resp, err) +} + +func (h *Handler) getRoomRPSChallenge(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + challengeID := strings.TrimSpace(request.PathValue("challenge_id")) + if challengeID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // 详情查询主要服务 IM 事件后的补拉和断线恢复;权限判断必须落在 game-service,防止只凭 challenge_id 猜测访问其它房间订单。 + resp, err := h.gameClient.GetRoomRPSChallenge(request.Context(), &gamev1.GetRoomRPSChallengeRequest{ + Meta: gameMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + ChallengeId: challengeID, + }) + h.writeRoomRPSChallenge(writer, request, resp, err) +} + func (h *Handler) writeDiceMatch(writer http.ResponseWriter, request *http.Request, resp *gamev1.DiceMatchResponse, err error) { if err != nil { httpkit.Write(writer, request, diceMatchData{}, err) @@ -323,6 +457,28 @@ func (h *Handler) writeDiceMatch(writer http.ResponseWriter, request *http.Reque httpkit.Write(writer, request, data, nil) } +func (h *Handler) writeRoomRPSChallenge(writer http.ResponseWriter, request *http.Request, resp *gamev1.RoomRPSChallengeResponse, err error) { + if err != nil { + // 失败时返回空 data 结构和统一错误 envelope;不要用半截挑战对象误导 Flutter 展示“已发起/已应战”。 + httpkit.Write(writer, request, roomRPSChallengeData{}, err) + return + } + data := roomRPSChallengeDataFromProto(resp) + // 单个挑战返回也补齐双方头像昵称,保证 HTTP 响应和后续房间 IM 使用同一套展示字段。 + h.enrichRoomRPSChallenges(request, []*roomRPSChallengeItemData{&data.Challenge}) + httpkit.Write(writer, request, data, nil) +} + +func normalizeRoomRPSGesture(value string) (string, bool) { + // 手势在 gateway 层归一为小写英文枚举;game-service 只接收稳定值,避免 Flutter 大小写或本地化文案污染订单事实。 + switch strings.ToLower(strings.TrimSpace(value)) { + case "rock", "paper", "scissors": + return strings.ToLower(strings.TrimSpace(value)), true + default: + return "", false + } +} + func (h *Handler) renewDiceAccessTokenIfNeeded(writer http.ResponseWriter, request *http.Request) { if h.userAuthClient == nil { return diff --git a/services/gateway-service/internal/transport/http/gameapi/game_view.go b/services/gateway-service/internal/transport/http/gameapi/game_view.go index aa13db70..1333077c 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_view.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_view.go @@ -59,6 +59,23 @@ type diceConfigData struct { ServerTimeMS int64 `json:"server_time_ms"` } +type roomRPSConfigData struct { + Config roomRPSConfigItemData `json:"config"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type roomRPSChallengeData struct { + Challenge roomRPSChallengeItemData `json:"challenge"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type roomRPSChallengeListData struct { + Challenges []*roomRPSChallengeItemData `json:"challenges"` + NextCursor string `json:"next_cursor"` + PageSize int32 `json:"page_size"` + ServerTimeMS int64 `json:"server_time_ms"` +} + type diceConfigItemData struct { AppCode string `json:"app_code"` GameID string `json:"game_id"` @@ -79,6 +96,25 @@ type diceStakeOptionData struct { SortOrder int32 `json:"sort_order"` } +type roomRPSConfigItemData struct { + AppCode string `json:"app_code"` + GameID string `json:"game_id"` + Status string `json:"status"` + ChallengeTimeoutMS int64 `json:"challenge_timeout_ms"` + RevealCountdownMS int64 `json:"reveal_countdown_ms"` + StakeGifts []roomRPSStakeGiftData `json:"stake_gifts"` +} + +type roomRPSStakeGiftData struct { + GiftID string `json:"gift_id"` + GiftIDNumber int64 `json:"gift_id_number"` + GiftName string `json:"gift_name"` + GiftIconURL string `json:"gift_icon_url"` + GiftPriceCoin int64 `json:"gift_price_coin"` + Enabled bool `json:"enabled"` + SortOrder int32 `json:"sort_order"` +} + type diceMatchItemData struct { AppCode string `json:"app_code"` MatchID string `json:"match_id"` @@ -133,6 +169,39 @@ type diceUserData struct { Avatar string `json:"avatar"` } +type roomRPSChallengeItemData struct { + AppCode string `json:"app_code"` + ChallengeID string `json:"challenge_id"` + RoomID string `json:"room_id"` + RegionID int64 `json:"region_id"` + Status string `json:"status"` + StakeGiftID string `json:"stake_gift_id"` + StakeGiftIDNumber int64 `json:"stake_gift_id_number"` + StakeCoin int64 `json:"stake_coin"` + Initiator roomRPSPlayerItemData `json:"initiator"` + Challenger roomRPSPlayerItemData `json:"challenger"` + WinnerUserID string `json:"winner_user_id"` + WinnerUserIDNumber int64 `json:"winner_user_id_number"` + SettlementStatus string `json:"settlement_status"` + FailureReason string `json:"failure_reason"` + TimeoutAtMS int64 `json:"timeout_at_ms"` + RevealAtMS int64 `json:"reveal_at_ms"` + CreatedAtMS int64 `json:"created_at_ms"` + MatchedAtMS int64 `json:"matched_at_ms"` + SettledAtMS int64 `json:"settled_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type roomRPSPlayerItemData struct { + UserID string `json:"user_id"` + UserIDNumber int64 `json:"user_id_number"` + Gesture string `json:"gesture"` + Result string `json:"result"` + BalanceAfter int64 `json:"balance_after"` + JoinedAtMS int64 `json:"joined_at_ms"` + User diceUserData `json:"user"` +} + func gameListDataFromProto(resp *gamev1.ListGamesResponse) gameListData { if resp == nil { return gameListData{} @@ -206,6 +275,41 @@ func diceConfigDataFromProto(resp *gamev1.DiceConfigResponse) diceConfigData { } } +func roomRPSConfigDataFromProto(resp *gamev1.RoomRPSConfigResponse) roomRPSConfigData { + if resp == nil { + return roomRPSConfigData{} + } + // 配置响应保持一层 config 包装,方便 Flutter 在同一个模型里同时读取配置和 server_time_ms 做倒计时校准。 + return roomRPSConfigData{Config: roomRPSConfigItemDataFromProto(resp.GetConfig()), ServerTimeMS: resp.GetServerTimeMs()} +} + +func roomRPSConfigItemDataFromProto(config *gamev1.RoomRPSConfig) roomRPSConfigItemData { + if config == nil { + return roomRPSConfigItemData{} + } + gifts := make([]roomRPSStakeGiftData, 0, len(config.GetStakeGifts())) + for _, gift := range config.GetStakeGifts() { + // 礼物 ID 同时返回字符串和数字;Flutter 展示、缓存和路由参数优先用字符串,提交 body 时仍可用数字字段。 + gifts = append(gifts, roomRPSStakeGiftData{ + GiftID: int64String(gift.GetGiftId()), + GiftIDNumber: gift.GetGiftId(), + GiftName: gift.GetGiftName(), + GiftIconURL: gift.GetGiftIconUrl(), + GiftPriceCoin: gift.GetGiftPriceCoin(), + Enabled: gift.GetEnabled(), + SortOrder: gift.GetSortOrder(), + }) + } + return roomRPSConfigItemData{ + AppCode: config.GetAppCode(), + GameID: config.GetGameId(), + Status: config.GetStatus(), + ChallengeTimeoutMS: config.GetChallengeTimeoutMs(), + RevealCountdownMS: config.GetRevealCountdownMs(), + StakeGifts: gifts, + } +} + func diceMatchDataFromProto(resp *gamev1.DiceMatchResponse) diceMatchData { if resp == nil { return diceMatchData{} @@ -295,3 +399,75 @@ func gameBridgeScriptDataFromProto(resp *gamev1.GetBridgeScriptResponse) gameBri func int64String(value int64) string { return fmt.Sprintf("%d", value) } + +func roomRPSChallengeDataFromProto(resp *gamev1.RoomRPSChallengeResponse) roomRPSChallengeData { + if resp == nil { + return roomRPSChallengeData{} + } + // 单挑战响应和列表响应使用同一个 challenge 结构,避免 Flutter 为发起、应战、详情维护多套字段映射。 + return roomRPSChallengeData{Challenge: roomRPSChallengeItemDataFromProto(resp.GetChallenge()), ServerTimeMS: resp.GetServerTimeMs()} +} + +func roomRPSChallengeListDataFromProto(resp *gamev1.ListRoomRPSChallengesResponse) roomRPSChallengeListData { + if resp == nil { + return roomRPSChallengeListData{} + } + items := make([]*roomRPSChallengeItemData, 0, len(resp.GetChallenges())) + for _, challenge := range resp.GetChallenges() { + // 使用指针切片是为了后续展示资料补全可以原地写入 initiator/challenger.user,不再复制整份挑战单。 + item := roomRPSChallengeItemDataFromProto(challenge) + items = append(items, &item) + } + return roomRPSChallengeListData{ + Challenges: items, + NextCursor: resp.GetNextCursor(), + PageSize: resp.GetPageSize(), + ServerTimeMS: resp.GetServerTimeMs(), + } +} + +func roomRPSChallengeItemDataFromProto(challenge *gamev1.RoomRPSChallenge) roomRPSChallengeItemData { + if challenge == nil { + return roomRPSChallengeItemData{} + } + // game-service 返回的是订单事实,gateway 这里只做 JSON 形态转换;状态、胜负、结算失败原因不能在这里二次推导。 + // 所有可能超过 JS 安全整数范围的 ID 都保留 string 和 number 双字段,Flutter 优先使用 string,后台排查仍可读 number。 + return roomRPSChallengeItemData{ + AppCode: challenge.GetAppCode(), + ChallengeID: challenge.GetChallengeId(), + RoomID: challenge.GetRoomId(), + RegionID: challenge.GetRegionId(), + Status: challenge.GetStatus(), + StakeGiftID: int64String(challenge.GetStakeGiftId()), + StakeGiftIDNumber: challenge.GetStakeGiftId(), + StakeCoin: challenge.GetStakeCoin(), + Initiator: roomRPSPlayerItemDataFromProto(challenge.GetInitiator()), + Challenger: roomRPSPlayerItemDataFromProto(challenge.GetChallenger()), + WinnerUserID: int64String(challenge.GetWinnerUserId()), + WinnerUserIDNumber: challenge.GetWinnerUserId(), + SettlementStatus: challenge.GetSettlementStatus(), + FailureReason: challenge.GetFailureReason(), + TimeoutAtMS: challenge.GetTimeoutAtMs(), + RevealAtMS: challenge.GetRevealAtMs(), + CreatedAtMS: challenge.GetCreatedAtMs(), + MatchedAtMS: challenge.GetMatchedAtMs(), + SettledAtMS: challenge.GetSettledAtMs(), + UpdatedAtMS: challenge.GetUpdatedAtMs(), + } +} + +func roomRPSPlayerItemDataFromProto(player *gamev1.RoomRPSPlayer) roomRPSPlayerItemData { + if player == nil { + return roomRPSPlayerItemData{} + } + // user 先放 user_id 兜底;真正的 nickname/avatar 在 enrichRoomRPSChallenges 里批量补齐,资料失败不影响订单返回。 + return roomRPSPlayerItemData{ + UserID: int64String(player.GetUserId()), + UserIDNumber: player.GetUserId(), + Gesture: player.GetGesture(), + Result: player.GetResult(), + BalanceAfter: player.GetBalanceAfter(), + JoinedAtMS: player.GetJoinedAtMs(), + User: diceUserData{UserID: int64String(player.GetUserId())}, + } +} diff --git a/services/gateway-service/internal/transport/http/gameapi/handler.go b/services/gateway-service/internal/transport/http/gameapi/handler.go index 42b35f2b..b372bfce 100644 --- a/services/gateway-service/internal/transport/http/gameapi/handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/handler.go @@ -48,6 +48,11 @@ func (h *Handler) Handlers() httproutes.GameHandlers { GetDiceMatch: h.getDiceMatch, RollDiceMatch: h.rollDiceMatch, CancelDiceMatch: h.cancelDiceMatch, + GetRoomRPSConfig: h.getRoomRPSConfig, + ListRoomRPSChallenges: h.listRoomRPSChallenges, + CreateRoomRPSChallenge: h.createRoomRPSChallenge, + AcceptRoomRPSChallenge: h.acceptRoomRPSChallenge, + GetRoomRPSChallenge: h.getRoomRPSChallenge, HandleCallback: h.handleGameCallback, HandleHotgameGetUserInfo: h.handleHotgameGetUserInfo, HandleHotgameUpdateBalance: h.handleHotgameUpdateBalance, @@ -98,6 +103,26 @@ func (h *Handler) CancelDiceMatch(writer http.ResponseWriter, request *http.Requ h.cancelDiceMatch(writer, request) } +func (h *Handler) GetRoomRPSConfig(writer http.ResponseWriter, request *http.Request) { + h.getRoomRPSConfig(writer, request) +} + +func (h *Handler) ListRoomRPSChallenges(writer http.ResponseWriter, request *http.Request) { + h.listRoomRPSChallenges(writer, request) +} + +func (h *Handler) CreateRoomRPSChallenge(writer http.ResponseWriter, request *http.Request) { + h.createRoomRPSChallenge(writer, request) +} + +func (h *Handler) AcceptRoomRPSChallenge(writer http.ResponseWriter, request *http.Request) { + h.acceptRoomRPSChallenge(writer, request) +} + +func (h *Handler) GetRoomRPSChallenge(writer http.ResponseWriter, request *http.Request) { + h.getRoomRPSChallenge(writer, request) +} + func (h *Handler) HandleCallback(writer http.ResponseWriter, request *http.Request) { h.handleGameCallback(writer, request) } diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index 69da32c4..c6479f37 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -105,6 +105,7 @@ type UserHandlers struct { AcceptCPApplication http.HandlerFunc RejectCPApplication http.HandlerFunc ListCPRelationships http.HandlerFunc + ListCPIntimacyLeaderboard http.HandlerFunc BreakCPRelationship http.HandlerFunc GetMyProfile http.HandlerFunc GetMyAppearance http.HandlerFunc @@ -137,6 +138,8 @@ type RoomHandlers struct { GetCurrentRoom http.HandlerFunc GetRoomSnapshot http.HandlerFunc GetRoomDetail http.HandlerFunc + GetRoomShare http.HandlerFunc + GetRoomShareLanding http.HandlerFunc ListRoomOnlineUsers http.HandlerFunc ListRoomBannedUsers http.HandlerFunc GetRoomGiftPanel http.HandlerFunc @@ -256,6 +259,11 @@ type GameHandlers struct { GetDiceMatch http.HandlerFunc RollDiceMatch http.HandlerFunc CancelDiceMatch http.HandlerFunc + GetRoomRPSConfig http.HandlerFunc + ListRoomRPSChallenges http.HandlerFunc + CreateRoomRPSChallenge http.HandlerFunc + AcceptRoomRPSChallenge http.HandlerFunc + GetRoomRPSChallenge http.HandlerFunc HandleCallback http.HandlerFunc HandleHotgameGetUserInfo http.HandlerFunc HandleHotgameUpdateBalance http.HandlerFunc @@ -396,6 +404,7 @@ func (r routes) registerUserRoutes() { r.profile("/cp/applications/{application_id}/accept", http.MethodPost, h.AcceptCPApplication) r.profile("/cp/applications/{application_id}/reject", http.MethodPost, h.RejectCPApplication) r.profile("/cp/relationships", http.MethodGet, h.ListCPRelationships) + r.profile("/cp/intimacy-leaderboard", http.MethodGet, h.ListCPIntimacyLeaderboard) r.profile("/cp/relationships/{relationship_id}/break", http.MethodPost, h.BreakCPRelationship) r.profile("/appearance", http.MethodGet, h.GetMyAppearance) r.profile("/users/me/appearance", http.MethodGet, h.GetMyAppearance) @@ -422,12 +431,14 @@ func (r routes) registerManagerRoutes() { func (r routes) registerRoomRoutes() { h := r.config.Room + r.rootPublic("/share/rooms/{room_id}", http.MethodGet, h.GetRoomShareLanding) r.profile("/rooms/me", http.MethodGet, h.GetMyRoom) r.profile("/rooms/feeds", http.MethodGet, h.ListRoomFeeds) r.profile("/rooms", http.MethodGet, h.ListRooms) r.profile("/rooms/current", http.MethodGet, h.GetCurrentRoom) r.profile("/rooms/snapshot", http.MethodGet, h.GetRoomSnapshot) r.profile("/rooms/{room_id}/detail", http.MethodGet, h.GetRoomDetail) + r.profile("/rooms/{room_id}/share", http.MethodGet, h.GetRoomShare) r.profile("/rooms/{room_id}/online-users", http.MethodGet, h.ListRoomOnlineUsers) r.profile("/rooms/{room_id}/banned-users", http.MethodGet, h.ListRoomBannedUsers) r.profile("/rooms/{room_id}/gift-panel", http.MethodGet, h.GetRoomGiftPanel) @@ -556,6 +567,13 @@ func (r routes) registerGameRoutes() { r.profile("/games/dice/matches/{match_id}", http.MethodGet, h.GetDiceMatch) r.profile("/games/dice/matches/{match_id}/roll", http.MethodPost, h.RollDiceMatch) r.profile("/games/dice/matches/{match_id}/cancel", http.MethodPost, h.CancelDiceMatch) + r.profile("/games/room-rps/config", http.MethodGet, h.GetRoomRPSConfig) + r.profile("/games/room-rps/challenges", http.MethodGet, h.ListRoomRPSChallenges) + // 当前 ServeMux 包装按 path 注册,不能把 GET 列表和 POST 创建同时挂在 /challenges;创建动作单独放 /create,避免路由覆盖。 + r.profile("/games/room-rps/challenges/create", http.MethodPost, h.CreateRoomRPSChallenge) + r.profile("/games/room-rps/challenges/{challenge_id}", http.MethodGet, h.GetRoomRPSChallenge) + // 应战只挂在具体 challenge 下,保证客户端必须拿到服务端生成的挑战单 ID 才能进入锁单流程。 + r.profile("/games/room-rps/challenges/{challenge_id}/accept", http.MethodPost, h.AcceptRoomRPSChallenge) // 热游后台只能配置根路径回调;这里仍复用 public wrapper,让请求日志、request_id 和 app_code 解析保持一致。 r.rootPublic("/getUserInfo", http.MethodPost, h.HandleHotgameGetUserInfo) r.rootPublic("/updateBalance", http.MethodPost, h.HandleHotgameUpdateBalance) diff --git a/services/gateway-service/internal/transport/http/roomapi/handler.go b/services/gateway-service/internal/transport/http/roomapi/handler.go index 7a7249ea..775ced8e 100644 --- a/services/gateway-service/internal/transport/http/roomapi/handler.go +++ b/services/gateway-service/internal/transport/http/roomapi/handler.go @@ -65,6 +65,8 @@ func (h *Handler) RoomHandlers() httproutes.RoomHandlers { GetCurrentRoom: h.getCurrentRoom, GetRoomSnapshot: h.getRoomSnapshot, GetRoomDetail: h.getRoomDetail, + GetRoomShare: h.getRoomShare, + GetRoomShareLanding: h.getRoomShareLanding, ListRoomOnlineUsers: h.listRoomOnlineUsers, ListRoomBannedUsers: h.listRoomBannedUsers, GetRoomGiftPanel: h.getRoomGiftPanel, diff --git a/services/gateway-service/internal/transport/http/roomapi/room_handler_test.go b/services/gateway-service/internal/transport/http/roomapi/room_handler_test.go index 2fd0840f..4087b767 100644 --- a/services/gateway-service/internal/transport/http/roomapi/room_handler_test.go +++ b/services/gateway-service/internal/transport/http/roomapi/room_handler_test.go @@ -2,6 +2,7 @@ package roomapi import ( "bytes" + "context" "net/http" "net/http/httptest" "testing" @@ -30,6 +31,82 @@ func TestCreateRoomRejectsMissingRoomAvatar(t *testing.T) { } } +func TestGetRoomShareReturnsShareLinks(t *testing.T) { + queryClient := &shareRoomQueryClient{snapshotResp: &roomv1.GetRoomSnapshotResponse{ + Room: &roomv1.RoomSnapshot{ + RoomId: "lalu_room_1", + OwnerUserId: 10001, + RoomShortId: "888888", + RoomExt: map[string]string{ + "title": "Lalu Room", + "description": "Join the party", + "cover_url": "https://cdn.example.test/room.png", + }, + }, + }} + handler := New(Config{RoomQueryClient: queryClient}) + request := httptest.NewRequest(http.MethodGet, "https://api.example.test/api/v1/rooms/lalu_room_1/share", nil) + request.SetPathValue("room_id", "lalu_room_1") + request.Header.Set("X-Forwarded-Proto", "https") + request.Header.Set("X-Forwarded-Host", "share.example.test") + recorder := httptest.NewRecorder() + + handler.getRoomShare(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("share endpoint status mismatch: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + if queryClient.lastSnapshot == nil || queryClient.lastSnapshot.GetRoomId() != "lalu_room_1" { + t.Fatalf("share endpoint must fetch room snapshot: %+v", queryClient.lastSnapshot) + } + body := recorder.Body.Bytes() + for _, want := range [][]byte{ + []byte(`"title":"Lalu Room"`), + []byte(`"landing_url":"https://share.example.test/share/rooms/lalu_room_1?source=room_share"`), + []byte(`"deep_link":"laluparty://room/lalu_room_1?source=room_share"`), + []byte(`"image_url":"https://cdn.example.test/room.png"`), + } { + if !bytes.Contains(body, want) { + t.Fatalf("share response missing %s: %s", want, recorder.Body.String()) + } + } +} + +func TestGetRoomShareLandingReturnsOGMeta(t *testing.T) { + handler := New(Config{RoomQueryClient: &shareRoomQueryClient{snapshotResp: &roomv1.GetRoomSnapshotResponse{ + Room: &roomv1.RoomSnapshot{ + RoomId: "lalu_room_1", + OwnerUserId: 10001, + RoomExt: map[string]string{ + "title": "Lalu Room", + "description": "Join the party", + "cover_url": "https://cdn.example.test/room.png", + }, + }, + }}}) + request := httptest.NewRequest(http.MethodGet, "https://share.example.test/share/rooms/lalu_room_1?invite_code=LZSWI6GO", nil) + request.SetPathValue("room_id", "lalu_room_1") + recorder := httptest.NewRecorder() + + handler.getRoomShareLanding(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("landing status mismatch: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + body := recorder.Body.String() + for _, want := range []string{ + ``, + ``, + ``, + ``, + `laluparty://room/lalu_room_1?invite_code=LZSWI6GO&source=room_share`, + } { + if !bytes.Contains([]byte(body), []byte(want)) { + t.Fatalf("landing html missing %q: %s", want, body) + } + } +} + func TestRoomGiftTabsUsesTypeCodeAsKeyAndTabKeyAsLabel(t *testing.T) { tabs := roomGiftTabs( []giftConfigData{ @@ -141,3 +218,52 @@ func TestRoomProfileCountryFlag(t *testing.T) { }) } } + +type shareRoomQueryClient struct { + lastSnapshot *roomv1.GetRoomSnapshotRequest + snapshotResp *roomv1.GetRoomSnapshotResponse +} + +func (c *shareRoomQueryClient) ListRooms(context.Context, *roomv1.ListRoomsRequest) (*roomv1.ListRoomsResponse, error) { + return &roomv1.ListRoomsResponse{}, nil +} + +func (c *shareRoomQueryClient) ListRoomFeeds(context.Context, *roomv1.ListRoomFeedsRequest) (*roomv1.ListRoomsResponse, error) { + return &roomv1.ListRoomsResponse{}, nil +} + +func (c *shareRoomQueryClient) ListRoomGiftLeaderboard(context.Context, *roomv1.ListRoomGiftLeaderboardRequest) (*roomv1.ListRoomGiftLeaderboardResponse, error) { + return &roomv1.ListRoomGiftLeaderboardResponse{}, nil +} + +func (c *shareRoomQueryClient) GetMyRoom(context.Context, *roomv1.GetMyRoomRequest) (*roomv1.GetMyRoomResponse, error) { + return &roomv1.GetMyRoomResponse{}, nil +} + +func (c *shareRoomQueryClient) GetCurrentRoom(context.Context, *roomv1.GetCurrentRoomRequest) (*roomv1.GetCurrentRoomResponse, error) { + return &roomv1.GetCurrentRoomResponse{}, nil +} + +func (c *shareRoomQueryClient) GetRoomSnapshot(_ context.Context, req *roomv1.GetRoomSnapshotRequest) (*roomv1.GetRoomSnapshotResponse, error) { + c.lastSnapshot = req + if c.snapshotResp != nil { + return c.snapshotResp, nil + } + return &roomv1.GetRoomSnapshotResponse{}, nil +} + +func (c *shareRoomQueryClient) ListRoomBackgrounds(context.Context, *roomv1.ListRoomBackgroundsRequest) (*roomv1.ListRoomBackgroundsResponse, error) { + return &roomv1.ListRoomBackgroundsResponse{}, nil +} + +func (c *shareRoomQueryClient) GetRoomRocket(context.Context, *roomv1.GetRoomRocketRequest) (*roomv1.GetRoomRocketResponse, error) { + return &roomv1.GetRoomRocketResponse{}, nil +} + +func (c *shareRoomQueryClient) ListRoomOnlineUsers(context.Context, *roomv1.ListRoomOnlineUsersRequest) (*roomv1.ListRoomOnlineUsersResponse, error) { + return &roomv1.ListRoomOnlineUsersResponse{}, nil +} + +func (c *shareRoomQueryClient) ListRoomBannedUsers(context.Context, *roomv1.ListRoomBannedUsersRequest) (*roomv1.ListRoomBannedUsersResponse, error) { + return &roomv1.ListRoomBannedUsersResponse{}, nil +} diff --git a/services/gateway-service/internal/transport/http/roomapi/share_handler.go b/services/gateway-service/internal/transport/http/roomapi/share_handler.go new file mode 100644 index 00000000..e4d02de3 --- /dev/null +++ b/services/gateway-service/internal/transport/http/roomapi/share_handler.go @@ -0,0 +1,284 @@ +package roomapi + +import ( + "fmt" + "html" + "net/http" + "net/url" + "strings" + "time" + + "hyapp/pkg/roomid" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" + + roomv1 "hyapp.local/api/proto/room/v1" + userv1 "hyapp.local/api/proto/user/v1" +) + +const ( + roomShareLandingPathPrefix = "/share/rooms/" + roomShareAppScheme = "laluparty" + roomShareAppName = "Lalu Party" + roomShareAndroidPackage = "com.org.laluparty" +) + +// getRoomShare 返回 App 侧调起系统分享面板需要的稳定资料。 +// 分享本身不改变房间状态,所以这里只读 room-service 快照和 user-service 邀请码。 +func (h *Handler) getRoomShare(writer http.ResponseWriter, request *http.Request) { + if h.roomQueryClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + roomID := strings.TrimSpace(request.PathValue("room_id")) + if !roomid.ValidStringID(roomID) { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + + viewerUserID := auth.UserIDFromContext(request.Context()) + snapshotResp, err := h.roomQueryClient.GetRoomSnapshot(request.Context(), &roomv1.GetRoomSnapshotRequest{ + Meta: httpkit.RoomMeta(request, roomID, ""), + RoomId: roomID, + ViewerUserId: viewerUserID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + + inviteCode, err := h.roomShareInviteCode(request, viewerUserID) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, h.roomShareData(request, snapshotResp.GetRoom(), inviteCode)) +} + +// getRoomShareLanding 返回第三方平台爬取的分享落地页。 +// 这个入口不要求登录,目的是让 WhatsApp/Facebook/X 先拿到 OG/Twitter meta;真实进房仍由 App 登录和 JoinRoom 校验。 +func (h *Handler) getRoomShareLanding(writer http.ResponseWriter, request *http.Request) { + if h.roomQueryClient == nil { + http.Error(writer, "upstream service error", http.StatusBadGateway) + return + } + roomID := strings.TrimSpace(request.PathValue("room_id")) + if !roomid.ValidStringID(roomID) { + http.Error(writer, "invalid room", http.StatusBadRequest) + return + } + + snapshotResp, err := h.roomQueryClient.GetRoomSnapshot(request.Context(), &roomv1.GetRoomSnapshotRequest{ + Meta: httpkit.RoomMeta(request, roomID, ""), + RoomId: roomID, + }) + if err != nil { + http.Error(writer, "room unavailable", http.StatusBadGateway) + return + } + + data := h.roomShareData(request, snapshotResp.GetRoom(), strings.TrimSpace(request.URL.Query().Get("invite_code"))) + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + writer.Header().Set("X-Content-Type-Options", "nosniff") + _, _ = writer.Write([]byte(roomShareLandingHTML(data))) +} + +func (h *Handler) roomShareData(request *http.Request, snapshot *roomv1.RoomSnapshot, inviteCode string) roomShareData { + owner := h.roomShareOwner(request, snapshot) + title := roomShareTitle(snapshot) + description := roomShareDescription(snapshot, owner) + landingURL := roomShareLandingURL(request, snapshot.GetRoomId(), inviteCode) + deepLink := roomShareDeepLink(snapshot.GetRoomId(), inviteCode) + coverURL := roomShareCoverURL(snapshot, owner) + + return roomShareData{ + RoomID: snapshot.GetRoomId(), + RoomShortID: strings.TrimSpace(snapshot.GetRoomShortId()), + Title: title, + Description: description, + CoverURL: coverURL, + LandingURL: landingURL, + DeepLink: deepLink, + ShareText: roomShareText(title, owner), + InviteCode: strings.TrimSpace(inviteCode), + Owner: owner, + ServerTimeMS: time.Now().UTC().UnixMilli(), + Preview: roomSharePreviewData{ + Type: "website", + Title: title, + Description: description, + ImageURL: coverURL, + URL: landingURL, + }, + } +} + +func (h *Handler) roomShareInviteCode(request *http.Request, userID int64) (string, error) { + if userID <= 0 || h.userProfileClient == nil { + return "", nil + } + userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: userID, + }) + if err != nil { + return "", err + } + return strings.TrimSpace(userResp.GetUser().GetInvite().GetMyInviteCode()), nil +} + +func (h *Handler) roomShareOwner(request *http.Request, snapshot *roomv1.RoomSnapshot) roomShareOwnerData { + ownerUserID := snapshot.GetOwnerUserId() + owner := roomShareOwnerData{UserID: formatOptionalUserID(ownerUserID)} + if ownerUserID <= 0 || h.userProfileClient == nil { + return owner + } + if profile, ok := h.roomDisplayProfileMap(request, []int64{ownerUserID})[ownerUserID]; ok { + owner.DisplayUserID = strings.TrimSpace(profile.DisplayUserID) + owner.Username = strings.TrimSpace(profile.Username) + owner.Avatar = strings.TrimSpace(profile.Avatar) + owner.CountryFlag = strings.TrimSpace(profile.CountryFlag) + } + return owner +} + +func roomShareTitle(snapshot *roomv1.RoomSnapshot) string { + if snapshot == nil { + return roomShareAppName + " voice room" + } + if title := strings.TrimSpace(snapshot.GetRoomExt()["title"]); title != "" { + return title + } + if shortID := strings.TrimSpace(snapshot.GetRoomShortId()); shortID != "" { + return roomShareAppName + " room " + shortID + } + return roomShareAppName + " voice room" +} + +func roomShareDescription(snapshot *roomv1.RoomSnapshot, owner roomShareOwnerData) string { + if snapshot != nil { + if description := strings.TrimSpace(snapshot.GetRoomExt()["description"]); description != "" { + return description + } + } + if ownerName := roomShareOwnerName(owner); ownerName != "" { + return "Join " + ownerName + " in a " + roomShareAppName + " voice room." + } + return "Join this " + roomShareAppName + " voice room." +} + +func roomShareCoverURL(snapshot *roomv1.RoomSnapshot, owner roomShareOwnerData) string { + if snapshot != nil { + if coverURL := strings.TrimSpace(snapshot.GetRoomExt()["cover_url"]); coverURL != "" { + return coverURL + } + } + return strings.TrimSpace(owner.Avatar) +} + +func roomShareText(title string, owner roomShareOwnerData) string { + if ownerName := roomShareOwnerName(owner); ownerName != "" { + return fmt.Sprintf("Join %s's %s room: %s", ownerName, roomShareAppName, strings.TrimSpace(title)) + } + return "Join this " + roomShareAppName + " room: " + strings.TrimSpace(title) +} + +func roomShareOwnerName(owner roomShareOwnerData) string { + if owner.Username != "" { + return owner.Username + } + if owner.DisplayUserID != "" { + return owner.DisplayUserID + } + return owner.UserID +} + +func roomShareLandingURL(request *http.Request, roomID string, inviteCode string) string { + landing := url.URL{ + Scheme: roomShareRequestScheme(request), + Host: roomShareRequestHost(request), + Path: roomShareLandingPathPrefix + url.PathEscape(strings.TrimSpace(roomID)), + } + query := landing.Query() + query.Set("source", "room_share") + if normalizedInviteCode := strings.TrimSpace(inviteCode); normalizedInviteCode != "" { + query.Set("invite_code", normalizedInviteCode) + } + landing.RawQuery = query.Encode() + return landing.String() +} + +func roomShareDeepLink(roomID string, inviteCode string) string { + deepLink := url.URL{ + Scheme: roomShareAppScheme, + Host: "room", + Path: "/" + strings.TrimSpace(roomID), + } + query := deepLink.Query() + query.Set("source", "room_share") + if normalizedInviteCode := strings.TrimSpace(inviteCode); normalizedInviteCode != "" { + query.Set("invite_code", normalizedInviteCode) + } + deepLink.RawQuery = query.Encode() + return deepLink.String() +} + +func roomShareRequestScheme(request *http.Request) string { + if forwarded := firstHeaderValue(request.Header.Get("X-Forwarded-Proto")); forwarded == "https" || forwarded == "http" { + return forwarded + } + if request.TLS != nil { + return "https" + } + return "http" +} + +func roomShareRequestHost(request *http.Request) string { + if forwarded := firstHeaderValue(request.Header.Get("X-Forwarded-Host")); forwarded != "" && !strings.ContainsAny(forwarded, "/\\\r\n") { + return forwarded + } + if host := strings.TrimSpace(request.Host); host != "" && !strings.ContainsAny(host, "/\\\r\n") { + return host + } + return "localhost" +} + +func firstHeaderValue(raw string) string { + value := strings.TrimSpace(raw) + if comma := strings.Index(value, ","); comma >= 0 { + value = strings.TrimSpace(value[:comma]) + } + return strings.ToLower(value) +} + +func roomShareLandingHTML(data roomShareData) string { + title := html.EscapeString(data.Title) + description := html.EscapeString(data.Description) + imageURL := html.EscapeString(data.CoverURL) + landingURL := html.EscapeString(data.LandingURL) + deepLink := html.EscapeString(data.DeepLink) + appName := html.EscapeString(roomShareAppName) + + return "" + + `` + + `` + + "" + title + "" + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `

` + title + `

` + description + `

Open room

` + + `` +} diff --git a/services/gateway-service/internal/transport/http/roomapi/share_view.go b/services/gateway-service/internal/transport/http/roomapi/share_view.go new file mode 100644 index 00000000..2d9b354e --- /dev/null +++ b/services/gateway-service/internal/transport/http/roomapi/share_view.go @@ -0,0 +1,34 @@ +package roomapi + +// roomShareData 是 App 获取房间分享资料的外部 JSON 契约。 +// 文案、落地页和深链统一由 gateway 生成,客户端只负责选择平台和调用系统分享能力。 +type roomShareData struct { + RoomID string `json:"room_id"` + RoomShortID string `json:"room_short_id,omitempty"` + Title string `json:"title"` + Description string `json:"description"` + CoverURL string `json:"cover_url,omitempty"` + LandingURL string `json:"landing_url"` + DeepLink string `json:"deep_link"` + ShareText string `json:"share_text"` + InviteCode string `json:"invite_code,omitempty"` + Owner roomShareOwnerData `json:"owner"` + Preview roomSharePreviewData `json:"preview"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type roomShareOwnerData struct { + UserID string `json:"user_id,omitempty"` + DisplayUserID string `json:"display_user_id,omitempty"` + Username string `json:"username,omitempty"` + Avatar string `json:"avatar,omitempty"` + CountryFlag string `json:"country_flag,omitempty"` +} + +type roomSharePreviewData struct { + Type string `json:"type"` + Title string `json:"title"` + Description string `json:"description"` + ImageURL string `json:"image_url,omitempty"` + URL string `json:"url"` +} diff --git a/services/gateway-service/internal/transport/http/userapi/cp_handler.go b/services/gateway-service/internal/transport/http/userapi/cp_handler.go index e36ba75b..88e27ef4 100644 --- a/services/gateway-service/internal/transport/http/userapi/cp_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/cp_handler.go @@ -17,6 +17,36 @@ type cpUserProfileData struct { Avatar string `json:"avatar"` } +type cpAvatarFrameData struct { + ResourceID int64 `json:"resource_id"` + ResourceCode string `json:"resource_code"` + Name string `json:"name"` + AssetURL string `json:"asset_url"` + PreviewURL string `json:"preview_url"` + AnimationURL string `json:"animation_url"` + MetadataJSON string `json:"metadata_json"` +} + +type cpIntimacyLeaderboardUserData struct { + UserID string `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + AvatarFrame *cpAvatarFrameData `json:"avatar_frame,omitempty"` +} + +type cpIntimacyLeaderboardItemData struct { + Rank int64 `json:"rank"` + RelationshipID string `json:"relationship_id"` + RelationType string `json:"relation_type"` + IntimacyValue int64 `json:"intimacy_value"` + Level int32 `json:"level"` + UserA *cpIntimacyLeaderboardUserData `json:"user_a,omitempty"` + UserB *cpIntimacyLeaderboardUserData `json:"user_b,omitempty"` + FormedAtMS int64 `json:"formed_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + type cpGiftSnapshotData struct { GiftID string `json:"gift_id"` GiftName string `json:"gift_name"` @@ -189,6 +219,43 @@ func (h *Handler) listCPRelationships(writer http.ResponseWriter, request *http. }) } +// listCPIntimacyLeaderboard 返回 cron 聚合后的 CP 亲密值排行榜,不在 gateway 实时扫描关系表。 +func (h *Handler) listCPIntimacyLeaderboard(writer http.ResponseWriter, request *http.Request) { + if h.userCPClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + // 榜单接口沿用社交列表分页参数,非法 page/page_size 在 gateway 直接拒绝,避免把异常分页传给内部服务。 + page, pageSize, ok := socialPage(request) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // gateway 只透传 relation_type 筛选和请求 meta;榜单数据来自 user-service 的 Redis 读模型,不在 HTTP 层拼装。 + resp, err := h.userCPClient.ListCPIntimacyLeaderboard(request.Context(), &userv1.ListCPIntimacyLeaderboardRequest{ + Meta: httpkit.UserMeta(request, ""), + RelationType: strings.TrimSpace(request.URL.Query().Get("relation_type")), + Page: page, + PageSize: pageSize, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + items := make([]cpIntimacyLeaderboardItemData, 0, len(resp.GetItems())) + for _, item := range resp.GetItems() { + // 双方资料、头像框和 rank 都已经在内部 RPC 返回;这里仅转换为 App JSON 字段名。 + items = append(items, cpIntimacyLeaderboardItemFromProto(item)) + } + httpkit.WriteOK(writer, request, map[string]any{ + "items": items, + "total": resp.GetTotal(), + "page": resp.GetPage(), + "page_size": resp.GetPageSize(), + "server_time_ms": resp.GetServerTimeMs(), + }) +} + // breakCPRelationship 先在 user-service 创建解除占位,再扣钱包,最后确认关系 ended,避免并发请求重复扣费。 func (h *Handler) breakCPRelationship(writer http.ResponseWriter, request *http.Request) { if h.userCPClient == nil || h.walletClient == nil { @@ -335,6 +402,54 @@ func cpUserProfileFromProto(item *userv1.CPUserProfile) *cpUserProfileData { } } +func cpIntimacyLeaderboardItemFromProto(item *userv1.CPIntimacyLeaderboardItem) cpIntimacyLeaderboardItemData { + if item == nil { + return cpIntimacyLeaderboardItemData{} + } + // user_a/user_b 是关系无序 pair 的固定两端,不按当前登录用户换位;排行榜展示需要稳定的双方快照。 + return cpIntimacyLeaderboardItemData{ + Rank: item.GetRank(), + RelationshipID: item.GetRelationshipId(), + RelationType: item.GetRelationType(), + IntimacyValue: item.GetIntimacyValue(), + Level: item.GetLevel(), + UserA: cpIntimacyLeaderboardUserFromProto(item.GetUserA()), + UserB: cpIntimacyLeaderboardUserFromProto(item.GetUserB()), + FormedAtMS: item.GetFormedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + +func cpIntimacyLeaderboardUserFromProto(item *userv1.CPIntimacyLeaderboardUser) *cpIntimacyLeaderboardUserData { + if item == nil || item.GetUserId() <= 0 { + // 内部服务如果返回空用户,App JSON 直接省略该侧,避免输出 user_id=0 的伪用户。 + return nil + } + return &cpIntimacyLeaderboardUserData{ + UserID: userIDString(item.GetUserId()), + DisplayUserID: item.GetDisplayUserId(), + Username: item.GetUsername(), + Avatar: item.GetAvatar(), + AvatarFrame: cpAvatarFrameFromProto(item.GetAvatarFrame()), + } +} + +func cpAvatarFrameFromProto(item *userv1.CPAvatarFrameSnapshot) *cpAvatarFrameData { + if item == nil || item.GetResourceId() <= 0 { + // 未佩戴头像框时保持 nil,omitempty 会让 App 端按无头像框处理。 + return nil + } + return &cpAvatarFrameData{ + ResourceID: item.GetResourceId(), + ResourceCode: item.GetResourceCode(), + Name: item.GetName(), + AssetURL: item.GetAssetUrl(), + PreviewURL: item.GetPreviewUrl(), + AnimationURL: item.GetAnimationUrl(), + MetadataJSON: item.GetMetadataJson(), + } +} + func cpGiftSnapshotFromProto(item *userv1.CPGiftSnapshot) *cpGiftSnapshotData { if item == nil { return nil diff --git a/services/gateway-service/internal/transport/http/userapi/cp_handler_test.go b/services/gateway-service/internal/transport/http/userapi/cp_handler_test.go index 0919414a..1cb4789d 100644 --- a/services/gateway-service/internal/transport/http/userapi/cp_handler_test.go +++ b/services/gateway-service/internal/transport/http/userapi/cp_handler_test.go @@ -35,3 +35,58 @@ func TestCPRelationshipFromProtoIncludesLevelProgress(t *testing.T) { t.Fatalf("relationship progress fields not mapped: %+v", got) } } + +func TestCPIntimacyLeaderboardItemFromProtoIncludesBothUsersAndAvatarFrames(t *testing.T) { + item := &userv1.CPIntimacyLeaderboardItem{ + Rank: 3, + RelationshipId: "cp_rel:7101:7102", + RelationType: "cp", + IntimacyValue: 9527, + Level: 6, + UserA: &userv1.CPIntimacyLeaderboardUser{ + UserId: 7101, + DisplayUserId: "1007101", + Username: "Alice", + Avatar: "alice.png", + AvatarFrame: &userv1.CPAvatarFrameSnapshot{ + ResourceId: 9101, + ResourceCode: "frame_gold", + Name: "Gold", + AssetUrl: "gold.webp", + }, + }, + UserB: &userv1.CPIntimacyLeaderboardUser{ + UserId: 7102, + DisplayUserId: "1007102", + Username: "Bob", + Avatar: "bob.png", + AvatarFrame: &userv1.CPAvatarFrameSnapshot{ + ResourceId: 9102, + ResourceCode: "frame_blue", + Name: "Blue", + PreviewUrl: "blue-preview.webp", + }, + }, + FormedAtMs: 1700000000000, + UpdatedAtMs: 1700000005000, + } + + got := cpIntimacyLeaderboardItemFromProto(item) + if got.Rank != 3 || + got.RelationshipID != "cp_rel:7101:7102" || + got.IntimacyValue != 9527 || + got.Level != 6 || + got.UserA == nil || + got.UserA.UserID != "7101" || + got.UserA.DisplayUserID != "1007101" || + got.UserA.AvatarFrame == nil || + got.UserA.AvatarFrame.ResourceCode != "frame_gold" || + got.UserB == nil || + got.UserB.UserID != "7102" || + got.UserB.AvatarFrame == nil || + got.UserB.AvatarFrame.PreviewURL != "blue-preview.webp" || + got.FormedAtMS != 1700000000000 || + got.UpdatedAtMS != 1700000005000 { + t.Fatalf("leaderboard item fields not mapped: %+v", got) + } +} diff --git a/services/gateway-service/internal/transport/http/userapi/handler.go b/services/gateway-service/internal/transport/http/userapi/handler.go index 932b1d80..cb2331fb 100644 --- a/services/gateway-service/internal/transport/http/userapi/handler.go +++ b/services/gateway-service/internal/transport/http/userapi/handler.go @@ -90,6 +90,7 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers { AcceptCPApplication: h.acceptCPApplication, RejectCPApplication: h.rejectCPApplication, ListCPRelationships: h.listCPRelationships, + ListCPIntimacyLeaderboard: h.listCPIntimacyLeaderboard, BreakCPRelationship: h.breakCPRelationship, GetMyProfile: h.getMyProfile, GetMyAppearance: h.getMyAppearance, diff --git a/services/statistics-service/cmd/backfill-last7/main.go b/services/statistics-service/cmd/backfill-last7/main.go index 69d11e69..b5c79014 100644 --- a/services/statistics-service/cmd/backfill-last7/main.go +++ b/services/statistics-service/cmd/backfill-last7/main.go @@ -224,10 +224,14 @@ func formatDay(value time.Time) string { } func collectRegistrationUsers(ctx context.Context, db *sql.DB, state *rebuildState, app string, startMS, endMS int64) error { + // 全站机器人使用 users 资料参与游戏展示,但不是自然注册用户;补数入口必须和实时 UserRegistered 口径一致排除。 rows, err := db.QueryContext(ctx, ` SELECT u.user_id FROM users u - WHERE u.app_code = ? AND u.created_at_ms >= ? AND u.created_at_ms < ?`, + WHERE u.app_code = ? + AND u.created_at_ms >= ? + AND u.created_at_ms < ? + AND LOWER(COALESCE(u.source, '')) <> 'game_robot'`, app, startMS, endMS) if err != nil { return err @@ -329,11 +333,15 @@ func loadUserDims(ctx context.Context, db *sql.DB, state *rebuildState, app stri func rebuildRegistrations(ctx context.Context, db *sql.DB, state *rebuildState, app string, startMS, endMS int64, loc *time.Location) error { // 注册国家以 users.country 映射出的真实 country_id 为准;region_id 只作为独立维度写入,不能再兜底成国家。 + // source=game_robot 是后台全站机器人池账号,不能进入新增用户、注册 cohort 或留存口径。 rows, err := db.QueryContext(ctx, ` SELECT u.user_id, COALESCE(c.country_id, 0), COALESCE(u.region_id, 0), u.created_at_ms FROM users u LEFT JOIN countries c ON c.app_code = u.app_code AND c.country_code = u.country - WHERE u.app_code = ? AND u.created_at_ms >= ? AND u.created_at_ms < ?`, + WHERE u.app_code = ? + AND u.created_at_ms >= ? + AND u.created_at_ms < ? + AND LOWER(COALESCE(u.source, '')) <> 'game_robot'`, app, startMS, endMS) if err != nil { return err diff --git a/services/user-service/configs/config.docker.yaml b/services/user-service/configs/config.docker.yaml index 76826927..0f06264a 100644 --- a/services/user-service/configs/config.docker.yaml +++ b/services/user-service/configs/config.docker.yaml @@ -30,6 +30,12 @@ mic_time_worker: cp_relationship_worker: enabled: true consumer_group: "hyapp-user-cp-room-outbox" +cp_intimacy_leaderboard: + enabled: true + redis_addr: "redis:6379" + redis_password: "" + redis_db: 0 + key_prefix: "user:cp_intimacy_leaderboard" outbox_worker: enabled: true poll_interval: "1s" @@ -78,6 +84,10 @@ activity_service: enabled: true addr: "activity-service:13006" request_timeout_ms: 3000 +wallet_service: + enabled: true + addr: "wallet-service:13004" + request_timeout_ms: 3000 tencent_im: # Docker 本地联调腾讯 IM 账号导入和封禁踢下线;必须和 gateway/room 使用同一个 IM 应用。 enabled: true diff --git a/services/user-service/configs/config.tencent.example.yaml b/services/user-service/configs/config.tencent.example.yaml index a3aa1b21..bd704e60 100644 --- a/services/user-service/configs/config.tencent.example.yaml +++ b/services/user-service/configs/config.tencent.example.yaml @@ -30,6 +30,12 @@ mic_time_worker: cp_relationship_worker: enabled: true consumer_group: "hyapp-user-cp-room-outbox" +cp_intimacy_leaderboard: + enabled: true + redis_addr: "TENCENT_REDIS_HOST:6379" + redis_password: "TENCENT_REDIS_PASSWORD" + redis_db: 0 + key_prefix: "user:cp_intimacy_leaderboard" outbox_worker: enabled: true poll_interval: "1s" @@ -81,6 +87,10 @@ activity_service: enabled: true addr: "activity-service.internal:13006" request_timeout_ms: 3000 +wallet_service: + enabled: true + addr: "wallet-service.internal:13004" + request_timeout_ms: 3000 tencent_im: # 线上开启后,新用户会导入腾讯云 IM,用户封禁会同步调用腾讯云 IM 账号踢下线。 enabled: true diff --git a/services/user-service/configs/config.yaml b/services/user-service/configs/config.yaml index e09796fd..5e3fb39b 100644 --- a/services/user-service/configs/config.yaml +++ b/services/user-service/configs/config.yaml @@ -30,6 +30,12 @@ mic_time_worker: cp_relationship_worker: enabled: true consumer_group: "hyapp-user-cp-room-outbox" +cp_intimacy_leaderboard: + enabled: true + redis_addr: "127.0.0.1:13379" + redis_password: "" + redis_db: 0 + key_prefix: "user:cp_intimacy_leaderboard" outbox_worker: enabled: true poll_interval: "1s" @@ -78,6 +84,10 @@ activity_service: enabled: true addr: "127.0.0.1:13006" request_timeout_ms: 3000 +wallet_service: + enabled: true + addr: "127.0.0.1:13004" + request_timeout_ms: 3000 tencent_im: # 本地联调腾讯 IM 账号导入和封禁踢下线;必须和 gateway/room 使用同一个 IM 应用。 enabled: true diff --git a/services/user-service/deploy/mysql/initdb/001_user_service.sql b/services/user-service/deploy/mysql/initdb/001_user_service.sql index 4595e509..f95d1480 100644 --- a/services/user-service/deploy/mysql/initdb/001_user_service.sql +++ b/services/user-service/deploy/mysql/initdb/001_user_service.sql @@ -1207,127 +1207,3 @@ CREATE TABLE IF NOT EXISTS login_risk_country_blocks ( KEY idx_login_risk_country_blocks_enabled (app_code, enabled, country_code), KEY idx_login_risk_country_blocks_updated (app_code, updated_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险国家拦截表'; - --- 本地联调测试账号。 --- 密码登录仍以 users.current_display_user_id 为账号,password_accounts 只保存 user_id 维度密码身份。 -INSERT INTO users ( - app_code, - user_id, - default_display_user_id, - current_display_user_id, - current_display_user_id_kind, - current_display_user_id_expires_at_ms, - username, - profile_completed, - profile_completed_at_ms, - onboarding_status, - status, - created_at_ms, - updated_at_ms -) -VALUES ( - 'lalu', - 123456, - '123456', - '123456', - 'default', - NULL, - 'test_123456', - 1, - 0, - 'completed', - 'active', - 0, - 0 -) -ON DUPLICATE KEY UPDATE - current_display_user_id = VALUES(current_display_user_id), - current_display_user_id_kind = VALUES(current_display_user_id_kind), - current_display_user_id_expires_at_ms = VALUES(current_display_user_id_expires_at_ms), - username = VALUES(username), - profile_completed = VALUES(profile_completed), - onboarding_status = VALUES(onboarding_status), - status = VALUES(status), - updated_at_ms = VALUES(updated_at_ms); - -INSERT INTO password_accounts ( - app_code, - user_id, - password_hash, - hash_alg, - created_at_ms, - updated_at_ms -) -VALUES ( - 'lalu', - 123456, - '$2a$10$9eyyJvfGdkb3oU8EAPUHiuLz1C/AmhGTU1nq8AeOIMNFwznk7B69G', - 'bcrypt', - 0, - 0 -) -ON DUPLICATE KEY UPDATE - password_hash = VALUES(password_hash), - hash_alg = VALUES(hash_alg), - updated_at_ms = VALUES(updated_at_ms); - -INSERT INTO users ( - app_code, - user_id, - default_display_user_id, - current_display_user_id, - current_display_user_id_kind, - current_display_user_id_expires_at_ms, - username, - profile_completed, - profile_completed_at_ms, - onboarding_status, - status, - created_at_ms, - updated_at_ms -) -VALUES ( - 'lalu', - 12345678, - '12345678', - '12345678', - 'default', - NULL, - 'test_12345678', - 1, - 0, - 'completed', - 'active', - 0, - 0 -) -ON DUPLICATE KEY UPDATE - current_display_user_id = VALUES(current_display_user_id), - current_display_user_id_kind = VALUES(current_display_user_id_kind), - current_display_user_id_expires_at_ms = VALUES(current_display_user_id_expires_at_ms), - username = VALUES(username), - profile_completed = VALUES(profile_completed), - onboarding_status = VALUES(onboarding_status), - status = VALUES(status), - updated_at_ms = VALUES(updated_at_ms); - -INSERT INTO password_accounts ( - app_code, - user_id, - password_hash, - hash_alg, - created_at_ms, - updated_at_ms -) -VALUES ( - 'lalu', - 12345678, - '$2a$10$2sdtdAzFlPu23vC0vMQT6OIIm5.KCzATuwvn8i09/SKHz.hmkzJce', - 'bcrypt', - 0, - 0 -) -ON DUPLICATE KEY UPDATE - password_hash = VALUES(password_hash), - hash_alg = VALUES(hash_alg), - updated_at_ms = VALUES(updated_at_ms); diff --git a/services/user-service/internal/app/app.go b/services/user-service/internal/app/app.go index ddc73a77..57f44d5a 100644 --- a/services/user-service/internal/app/app.go +++ b/services/user-service/internal/app/app.go @@ -32,6 +32,7 @@ import ( mictimedomain "hyapp/services/user-service/internal/domain/mictime" "hyapp/services/user-service/internal/integration/activityclient" "hyapp/services/user-service/internal/integration/roomclient" + "hyapp/services/user-service/internal/integration/walletclient" authservice "hyapp/services/user-service/internal/service/auth" cpservice "hyapp/services/user-service/internal/service/cp" hostservice "hyapp/services/user-service/internal/service/host" @@ -65,6 +66,8 @@ type App struct { roomConn *grpc.ClientConn // activityConn 是注册完成后触发注册奖励的 gRPC 连接。 activityConn *grpc.ClientConn + // walletConn 是读取用户佩戴资源快照的 gRPC 连接。 + walletConn *grpc.ClientConn // userSvc 持有用户主数据用例,cron RPC 复用它消费区域重算任务。 userSvc *userservice.Service // authSvc 持有认证、登录 session 和登录 IP 风控用例。 @@ -77,6 +80,8 @@ type App struct { cpSvc *cpservice.Service // loginRiskRedisClose 关闭登录风险 Redis cache;cache 不作为启动硬依赖。 loginRiskRedisClose func() error + // cpLeaderboardRedisClose 关闭 CP 亲密榜 Redis 读模型连接。 + cpLeaderboardRedisClose func() error // cfg 保存 worker 运行参数,避免 Run 阶段重新读配置。 cfg config.Config // workerCtx 统一控制后台 worker 生命周期。 @@ -140,6 +145,23 @@ func New(cfg config.Config) (*App, error) { logx.Warn(startupCtx, "login_risk_redis_unavailable") } } + var cpLeaderboardStore cpservice.IntimacyLeaderboardStore + var cpLeaderboardRedisClose func() error + if cfg.CPIntimacyLeaderboard.Enabled { + // CP 亲密榜是 App 查询的唯一读模型,启动时必须能连上 Redis;否则服务启动成功但榜单接口必然不可用。 + redisClient, redisErr := cpservice.NewRedisIntimacyLeaderboardClient(startupCtx, cfg.CPIntimacyLeaderboard.RedisAddr, cfg.CPIntimacyLeaderboard.RedisPassword, cfg.CPIntimacyLeaderboard.RedisDB) + if redisErr != nil { + if loginRiskRedisClose != nil { + _ = loginRiskRedisClose() + } + _ = listener.Close() + _ = mysqlRepo.Close() + return nil, redisErr + } + // store 只持有 Redis client 和 key 前缀;具体 app_code/relation_type 在 cron 刷新和查询时动态决定。 + cpLeaderboardStore = cpservice.NewRedisIntimacyLeaderboardStore(redisClient, cfg.CPIntimacyLeaderboard.KeyPrefix) + cpLeaderboardRedisClose = redisClient.Close + } var imLoginKicker userservice.IMLoginKicker var imAccountImporter authservice.IMAccountImporter if cfg.TencentIM.Enabled { @@ -148,6 +170,9 @@ func New(cfg config.Config) (*App, error) { if loginRiskRedisClose != nil { _ = loginRiskRedisClose() } + if cpLeaderboardRedisClose != nil { + _ = cpLeaderboardRedisClose() + } _ = listener.Close() _ = mysqlRepo.Close() return nil, err @@ -165,6 +190,9 @@ func New(cfg config.Config) (*App, error) { if loginRiskRedisClose != nil { _ = loginRiskRedisClose() } + if cpLeaderboardRedisClose != nil { + _ = cpLeaderboardRedisClose() + } _ = listener.Close() _ = mysqlRepo.Close() return nil, err @@ -183,6 +211,9 @@ func New(cfg config.Config) (*App, error) { if loginRiskRedisClose != nil { _ = loginRiskRedisClose() } + if cpLeaderboardRedisClose != nil { + _ = cpLeaderboardRedisClose() + } if roomConn != nil { _ = roomConn.Close() } @@ -195,6 +226,33 @@ func New(cfg config.Config) (*App, error) { registrationLevelBadgeIssuer = activityClient levelProfileReader = activityClient } + var walletConn *grpc.ClientConn + var avatarFrameReader cpservice.AvatarFrameReader + if cfg.WalletService.Enabled { + // 头像框佩戴事实归 wallet-service;user-service 刷新榜单前必须能读取该快照,否则榜单会缺少双方头像框。 + walletConn, err = grpcclient.Dial(cfg.WalletService.Addr, grpcclient.Config{ + DefaultTimeout: time.Duration(cfg.WalletService.RequestTimeoutMs) * time.Millisecond, + }) + if err != nil { + if loginRiskRedisClose != nil { + _ = loginRiskRedisClose() + } + if cpLeaderboardRedisClose != nil { + _ = cpLeaderboardRedisClose() + } + if roomConn != nil { + _ = roomConn.Close() + } + if activityConn != nil { + _ = activityConn.Close() + } + _ = listener.Close() + _ = mysqlRepo.Close() + return nil, err + } + // 只把头像框读取能力注入 CP service,避免 CP 领域依赖完整的钱包客户端。 + avatarFrameReader = walletclient.NewGRPC(walletConn) + } // auth service 负责登录、session 和 token;用户主数据和短号事务仍通过各自领域存储完成。 authSvc := authservice.New(authservice.Config{ @@ -250,18 +308,28 @@ func New(cfg config.Config) (*App, error) { ) inviteSvc := inviteservice.New(inviteRepo) micTimeSvc := mictimeservice.New(micTimeRepo) - cpSvc := cpservice.New(cpRepo) + cpSvc := cpservice.New(cpRepo, + // CP service 同时承担送礼事件消费和榜单读模型刷新;Redis/wallet 依赖只在榜单相关方法里被强校验。 + cpservice.WithIntimacyLeaderboardStore(cpLeaderboardStore), + cpservice.WithAvatarFrameReader(avatarFrameReader), + ) mqConsumers, err := newMQConsumers(cfg, inviteSvc, micTimeSvc, cpSvc) if err != nil { if loginRiskRedisClose != nil { _ = loginRiskRedisClose() } + if cpLeaderboardRedisClose != nil { + _ = cpLeaderboardRedisClose() + } if roomConn != nil { _ = roomConn.Close() } if activityConn != nil { _ = activityConn.Close() } + if walletConn != nil { + _ = walletConn.Close() + } _ = listener.Close() _ = mysqlRepo.Close() return nil, err @@ -274,12 +342,18 @@ func New(cfg config.Config) (*App, error) { if loginRiskRedisClose != nil { _ = loginRiskRedisClose() } + if cpLeaderboardRedisClose != nil { + _ = cpLeaderboardRedisClose() + } if roomConn != nil { _ = roomConn.Close() } if activityConn != nil { _ = activityConn.Close() } + if walletConn != nil { + _ = walletConn.Close() + } _ = listener.Close() _ = mysqlRepo.Close() return nil, err @@ -318,12 +392,18 @@ func New(cfg config.Config) (*App, error) { if loginRiskRedisClose != nil { _ = loginRiskRedisClose() } + if cpLeaderboardRedisClose != nil { + _ = cpLeaderboardRedisClose() + } if roomConn != nil { _ = roomConn.Close() } if activityConn != nil { _ = activityConn.Close() } + if walletConn != nil { + _ = walletConn.Close() + } _ = listener.Close() _ = mysqlRepo.Close() return nil, err @@ -331,24 +411,26 @@ func New(cfg config.Config) (*App, error) { workerCtx, workerCancel := context.WithCancel(context.Background()) return &App{ - server: server, - listener: listener, - health: health, - healthHTTP: healthHTTP, - mysqlRepo: mysqlRepo, - mqConsumers: mqConsumers, - userOutboxProducer: userOutboxProducer, - roomConn: roomConn, - activityConn: activityConn, - authSvc: authSvc, - userSvc: userSvc, - inviteSvc: inviteSvc, - micTimeSvc: micTimeSvc, - cpSvc: cpSvc, - loginRiskRedisClose: loginRiskRedisClose, - cfg: cfg, - workerCtx: workerCtx, - workerCancel: workerCancel, + server: server, + listener: listener, + health: health, + healthHTTP: healthHTTP, + mysqlRepo: mysqlRepo, + mqConsumers: mqConsumers, + userOutboxProducer: userOutboxProducer, + roomConn: roomConn, + activityConn: activityConn, + walletConn: walletConn, + authSvc: authSvc, + userSvc: userSvc, + inviteSvc: inviteSvc, + micTimeSvc: micTimeSvc, + cpSvc: cpSvc, + loginRiskRedisClose: loginRiskRedisClose, + cpLeaderboardRedisClose: cpLeaderboardRedisClose, + cfg: cfg, + workerCtx: workerCtx, + workerCancel: workerCancel, }, nil } @@ -408,9 +490,15 @@ func (a *App) Close() { if a.activityConn != nil { _ = a.activityConn.Close() } + if a.walletConn != nil { + _ = a.walletConn.Close() + } if a.loginRiskRedisClose != nil { _ = a.loginRiskRedisClose() } + if a.cpLeaderboardRedisClose != nil { + _ = a.cpLeaderboardRedisClose() + } }) } diff --git a/services/user-service/internal/config/config.go b/services/user-service/internal/config/config.go index b7325fb5..8040cf2b 100644 --- a/services/user-service/internal/config/config.go +++ b/services/user-service/internal/config/config.go @@ -40,6 +40,8 @@ type Config struct { MicTimeWorker MicTimeWorkerConfig `yaml:"mic_time_worker"` // CPRelationshipWorker 控制 room-service 送礼事件消费和 CP 关系申请/亲密值维护。 CPRelationshipWorker CPRelationshipWorkerConfig `yaml:"cp_relationship_worker"` + // CPIntimacyLeaderboard 控制 CP 亲密值排行榜 Redis 读模型。 + CPIntimacyLeaderboard CPIntimacyLeaderboardConfig `yaml:"cp_intimacy_leaderboard"` // OutboxWorker 控制 user_outbox 到 MQ 的补偿投递。 OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"` // RocketMQ 控制 wallet/room outbox MQ 消费。 @@ -50,6 +52,8 @@ type Config struct { RoomService RoomServiceConfig `yaml:"room_service"` // ActivityService 是注册成功后发放注册奖励的内部 gRPC 依赖。 ActivityService ActivityServiceConfig `yaml:"activity_service"` + // WalletService 是 CP 榜头像框等资源读模型的内部 gRPC 依赖。 + WalletService WalletServiceConfig `yaml:"wallet_service"` // TencentIM 是封禁后失效用户 IM 登录态的外部集成配置。 TencentIM TencentIMConfig `yaml:"tencent_im"` // JWT 控制 access token 和 refresh session 的签发参数。 @@ -86,6 +90,13 @@ type ActivityServiceConfig struct { RequestTimeoutMs int64 `yaml:"request_timeout_ms"` } +// WalletServiceConfig 保存 user-service 到 wallet-service 的资源读模型 RPC 连接参数。 +type WalletServiceConfig struct { + Enabled bool `yaml:"enabled"` + Addr string `yaml:"addr"` + RequestTimeoutMs int64 `yaml:"request_timeout_ms"` +} + // TencentIMConfig 描述 user-service 调用腾讯云 IM REST API 的账号级治理配置。 type TencentIMConfig struct { Enabled bool `yaml:"enabled"` @@ -170,6 +181,15 @@ type CPRelationshipWorkerConfig struct { ConsumerGroup string `yaml:"consumer_group"` } +// CPIntimacyLeaderboardConfig 保存 CP 亲密榜 Redis zset 的连接和 key 前缀。 +type CPIntimacyLeaderboardConfig struct { + Enabled bool `yaml:"enabled"` + RedisAddr string `yaml:"redis_addr"` + RedisPassword string `yaml:"redis_password"` + RedisDB int `yaml:"redis_db"` + KeyPrefix string `yaml:"key_prefix"` +} + // OutboxWorkerConfig 保存 user_outbox MQ 发布策略。 type OutboxWorkerConfig struct { Enabled bool `yaml:"enabled"` @@ -261,6 +281,11 @@ func Default() Config { Enabled: false, ConsumerGroup: "hyapp-user-cp-room-outbox", }, + CPIntimacyLeaderboard: CPIntimacyLeaderboardConfig{ + Enabled: true, + RedisAddr: "127.0.0.1:13379", + KeyPrefix: "user:cp_intimacy_leaderboard", + }, OutboxWorker: OutboxWorkerConfig{ Enabled: false, PollInterval: time.Second, @@ -288,6 +313,11 @@ func Default() Config { Addr: "127.0.0.1:13006", RequestTimeoutMs: 3000, }, + WalletService: WalletServiceConfig{ + Enabled: true, + Addr: "127.0.0.1:13004", + RequestTimeoutMs: 3000, + }, TencentIM: TencentIMConfig{ Enabled: false, AdminIdentifier: "administrator", @@ -340,6 +370,14 @@ func Load(path string) (Config, error) { cfg.HealthHTTPAddr = ":13105" } cfg.LoginRisk.RedisAddr = strings.TrimSpace(cfg.LoginRisk.RedisAddr) + cfg.CPIntimacyLeaderboard.RedisAddr = strings.TrimSpace(cfg.CPIntimacyLeaderboard.RedisAddr) + cfg.CPIntimacyLeaderboard.KeyPrefix = strings.Trim(strings.TrimSpace(cfg.CPIntimacyLeaderboard.KeyPrefix), ":") + if cfg.CPIntimacyLeaderboard.RedisAddr == "" { + cfg.CPIntimacyLeaderboard.RedisAddr = cfg.LoginRisk.RedisAddr + } + if cfg.CPIntimacyLeaderboard.KeyPrefix == "" { + cfg.CPIntimacyLeaderboard.KeyPrefix = "user:cp_intimacy_leaderboard" + } cfg.RoomService.Addr = strings.TrimSpace(cfg.RoomService.Addr) if cfg.RoomService.Addr == "" { cfg.RoomService.Addr = "127.0.0.1:13001" @@ -354,6 +392,13 @@ func Load(path string) (Config, error) { if cfg.ActivityService.RequestTimeoutMs <= 0 { cfg.ActivityService.RequestTimeoutMs = 3000 } + cfg.WalletService.Addr = strings.TrimSpace(cfg.WalletService.Addr) + if cfg.WalletService.Addr == "" { + cfg.WalletService.Addr = "127.0.0.1:13004" + } + if cfg.WalletService.RequestTimeoutMs <= 0 { + cfg.WalletService.RequestTimeoutMs = 3000 + } cfg.TencentIM.SecretKey = strings.TrimSpace(cfg.TencentIM.SecretKey) cfg.TencentIM.AdminIdentifier = strings.TrimSpace(cfg.TencentIM.AdminIdentifier) if cfg.TencentIM.AdminIdentifier == "" { diff --git a/services/user-service/internal/domain/auth/auth.go b/services/user-service/internal/domain/auth/auth.go index b98e1e46..11771e3d 100644 --- a/services/user-service/internal/domain/auth/auth.go +++ b/services/user-service/internal/domain/auth/auth.go @@ -2,13 +2,13 @@ package auth // PasswordAccount 是用户主动设置密码后的密码身份记录。 -// 登录入口使用 user.current_display_user_id 解析用户,不再维护独立账号命名空间。 +// 登录入口使用展示号事实表解析用户;held 默认短号可以作为 active 靓号的登录别名,不再维护独立账号命名空间。 type PasswordAccount struct { // AppCode 是账号所属 App,短号只在同一个 App 内唯一。 AppCode string // UserID 是密码身份绑定的系统用户 ID。 UserID int64 - // DisplayUserID 是登录时解析出的当前展示短号快照,不作为独立唯一账号命名空间持久化。 + // DisplayUserID 是登录成功后应签发给客户端的当前展示号;用户用 held 默认短号登录时这里仍返回 active 靓号。 DisplayUserID string // PasswordHash 保存 bcrypt 等安全 hash,永远不保存密码明文。 PasswordHash string diff --git a/services/user-service/internal/domain/cp/cp.go b/services/user-service/internal/domain/cp/cp.go index cc9aa975..a711fb7a 100644 --- a/services/user-service/internal/domain/cp/cp.go +++ b/services/user-service/internal/domain/cp/cp.go @@ -38,6 +38,49 @@ type UserProfile struct { Avatar string } +// AvatarFrameSnapshot 是用户当前佩戴头像框的展示快照;数据来自 wallet-service 资源佩戴读模型。 +type AvatarFrameSnapshot struct { + ResourceID int64 `json:"resource_id,omitempty"` + ResourceCode string `json:"resource_code,omitempty"` + Name string `json:"name,omitempty"` + AssetURL string `json:"asset_url,omitempty"` + PreviewURL string `json:"preview_url,omitempty"` + AnimationURL string `json:"animation_url,omitempty"` + MetadataJSON string `json:"metadata_json,omitempty"` + EntitlementID string `json:"entitlement_id,omitempty"` +} + +// IntimacyLeaderboardUser 是 CP 亲密值排行榜中一方用户的展示资料。 +type IntimacyLeaderboardUser struct { + UserID int64 `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + AvatarFrame *AvatarFrameSnapshot `json:"avatar_frame,omitempty"` +} + +// IntimacyLeaderboardEntry 是 Redis zset member 保存的一对 active 关系快照。 +type IntimacyLeaderboardEntry struct { + Rank int64 `json:"rank,omitempty"` + RelationshipID string `json:"relationship_id"` + RelationType string `json:"relation_type"` + IntimacyValue int64 `json:"intimacy_value"` + Level int32 `json:"level"` + UserA IntimacyLeaderboardUser `json:"user_a"` + UserB IntimacyLeaderboardUser `json:"user_b"` + FormedAtMS int64 `json:"formed_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +// IntimacyLeaderboardPage 是 CP 亲密值排行榜分页读模型。 +type IntimacyLeaderboardPage struct { + Items []IntimacyLeaderboardEntry + Total int64 + Page int32 + PageSize int32 + ServerTimeMS int64 +} + // GiftSnapshot 固化触发申请或亲密值增长的礼物展示信息。 type GiftSnapshot struct { GiftID string diff --git a/services/user-service/internal/domain/user/user.go b/services/user-service/internal/domain/user/user.go index 74051383..a40db5a5 100644 --- a/services/user-service/internal/domain/user/user.go +++ b/services/user-service/internal/domain/user/user.go @@ -22,6 +22,11 @@ const ( StatusBanned Status = "banned" ) +const ( + // RegisterSourceGameRobot 标记后台为全站机器人池创建的资料号。 + RegisterSourceGameRobot = "game_robot" +) + // OnboardingStatus 表达用户是否完成 App 最小注册资料。 type OnboardingStatus string @@ -793,8 +798,14 @@ type ExpirePrettyDisplayUserIDCommand struct { // CanLogin 判断用户状态是否允许签发新会话。 func (u User) CanLogin() bool { - // 只有 active 用户允许签发新 session,disabled/banned 都必须阻断登录。 - return u.Status == StatusActive + // active 只代表用户主状态可用;全站机器人虽然复用用户资料和短号展示,但它不是可登录主体, + // 所有密码、三方、refresh 和 access 续签入口都必须在签发 session 前复用这条判断统一阻断。 + return u.Status == StatusActive && !IsGameRobotRegisterSource(u.RegisterSource) +} + +// IsGameRobotRegisterSource 判断注册来源是否是全站机器人池账号。 +func IsGameRobotRegisterSource(source string) bool { + return strings.EqualFold(strings.TrimSpace(source), RegisterSourceGameRobot) } // DisplayUserIDExpired 判断当前展示号快照是否已经因为靓号到期而失效。 diff --git a/services/user-service/internal/integration/walletclient/client.go b/services/user-service/internal/integration/walletclient/client.go new file mode 100644 index 00000000..33447ec5 --- /dev/null +++ b/services/user-service/internal/integration/walletclient/client.go @@ -0,0 +1,64 @@ +// Package walletclient 适配 wallet-service 资源 RPC,供 user-service 构建 CP 读模型。 +package walletclient + +import ( + "context" + + "google.golang.org/grpc" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/idgen" + cpdomain "hyapp/services/user-service/internal/domain/cp" +) + +const avatarFrameResourceType = "avatar_frame" + +// Client 只暴露 user-service 构建 CP 读模型需要的钱包资源读取能力。 +type Client struct { + client walletv1.WalletServiceClient +} + +// NewGRPC 基于共享 gRPC 连接创建 wallet-service 资源读取客户端。 +func NewGRPC(conn grpc.ClientConnInterface) *Client { + return &Client{client: walletv1.NewWalletServiceClient(conn)} +} + +// BatchGetAvatarFrames 返回用户当前佩戴头像框快照;没有佩戴的用户不会出现在结果 map 中。 +func (c *Client) BatchGetAvatarFrames(ctx context.Context, appCode string, userIDs []int64) (map[int64]cpdomain.AvatarFrameSnapshot, error) { + if c == nil || c.client == nil || len(userIDs) == 0 { + // 没有用户或依赖未装配时返回空 map,调用方会保持 avatar_frame=nil;这里不制造默认头像框。 + return map[int64]cpdomain.AvatarFrameSnapshot{}, nil + } + // wallet-service 是资源佩戴事实的 owner;user-service 只按用户批量读取 avatar_frame 类型,避免读取整包资源列表。 + resp, err := c.client.BatchGetUserEquippedResources(ctx, &walletv1.BatchGetUserEquippedResourcesRequest{ + RequestId: idgen.New("cp_frame"), + AppCode: appCode, + UserIds: userIDs, + ResourceTypes: []string{avatarFrameResourceType}, + }) + if err != nil { + return nil, err + } + frames := make(map[int64]cpdomain.AvatarFrameSnapshot, len(resp.GetUsers())) + for _, user := range resp.GetUsers() { + for _, entitlement := range user.GetResources() { + resource := entitlement.GetResource() + if resource == nil || resource.GetResourceType() != avatarFrameResourceType || resource.GetResourceId() <= 0 { + // 返回包里可能混入空资源或未来扩展资源类型;榜单只认有效 avatar_frame,其他内容不能进入 CP 快照。 + continue + } + // 一个用户同一类型理论上只应佩戴一个资源;命中第一条有效头像框后 break,避免后续异常重复覆盖。 + frames[user.GetUserId()] = cpdomain.AvatarFrameSnapshot{ + ResourceID: resource.GetResourceId(), + ResourceCode: resource.GetResourceCode(), + Name: resource.GetName(), + AssetURL: resource.GetAssetUrl(), + PreviewURL: resource.GetPreviewUrl(), + AnimationURL: resource.GetAnimationUrl(), + MetadataJSON: resource.GetMetadataJson(), + EntitlementID: entitlement.GetEntitlementId(), + } + break + } + } + return frames, nil +} diff --git a/services/user-service/internal/service/auth/password.go b/services/user-service/internal/service/auth/password.go index 0ab58ac5..cb08f6fb 100644 --- a/services/user-service/internal/service/auth/password.go +++ b/services/user-service/internal/service/auth/password.go @@ -25,13 +25,13 @@ func (s *Service) LoginPassword(ctx context.Context, displayUserID string, passw return authdomain.Token{}, xerr.New(xerr.Unavailable, "auth repository is not configured") } if !userdomain.ValidResolvableDisplayUserID(displayUserID) { - // 密码登录接受当前 active 默认短号或靓号;非法字符和不存在账号统一映射 AUTH_FAILED,避免账号枚举。 + // 密码登录接受当前 active 展示号,也接受被 active 靓号覆盖的 held 默认短号;非法字符和不存在账号统一映射 AUTH_FAILED,避免账号枚举。 s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.AuthFailed)) return authdomain.Token{}, authFailed() } nowMs := s.now().UnixMilli() - // repository 会按当前 active display_user_id 查询,并在查询前处理靓号懒过期。 + // repository 会按输入展示号定位用户,再返回该用户当前 active 展示号;held 默认短号只作为登录别名,不会覆盖 token 展示号。 accountRecord, err := s.authRepository.FindPasswordByDisplayUserID(ctx, displayUserID, nowMs) if err != nil { s.audit(ctx, meta, 0, loginPassword, "", resultFailed, string(xerr.AuthFailed)) diff --git a/services/user-service/internal/service/auth/quick_account.go b/services/user-service/internal/service/auth/quick_account.go index 7f99cd26..f7d1ab3c 100644 --- a/services/user-service/internal/service/auth/quick_account.go +++ b/services/user-service/internal/service/auth/quick_account.go @@ -11,8 +11,8 @@ import ( userdomain "hyapp/services/user-service/internal/domain/user" ) -// QuickCreateAccount 创建一个已完成资料、已设置密码、可立即登录的账号。 -// 该能力只复用内部领域事务,不依赖 firebase/mock provider,方便游戏方联调时快速拿 uid/token。 +// QuickCreateAccount 创建一个已完成资料且已设置密码的账号。 +// 普通联调账号会立即返回登录态;全站机器人只返回 uid/display_id,不创建 session,也不能通过后续接口登录。 func (s *Service) QuickCreateAccount(ctx context.Context, password string, registration authdomain.ThirdPartyRegistration, meta Meta) (authdomain.Token, error) { meta.AppCode = appcode.Normalize(meta.AppCode) registration.AppCode = meta.AppCode @@ -47,6 +47,7 @@ func (s *Service) QuickCreateAccount(ctx context.Context, password string, regis if err != nil { return authdomain.Token{}, err } + isGameRobot := userdomain.IsGameRobotRegisterSource(registration.Source) var lastErr error for attempt := 0; attempt < s.allocateMaxAttempts; attempt++ { @@ -70,9 +71,14 @@ func (s *Service) QuickCreateAccount(ctx context.Context, password string, regis continue } - session, refreshToken, err := s.newSession(registration.AppCode, user.UserID, registration.DeviceID) - if err != nil { - return authdomain.Token{}, err + var session authdomain.Session + var refreshToken string + if !isGameRobot { + var err error + session, refreshToken, err = s.newSession(registration.AppCode, user.UserID, registration.DeviceID) + if err != nil { + return authdomain.Token{}, err + } } account := authdomain.PasswordAccount{ AppCode: registration.AppCode, @@ -94,6 +100,20 @@ func (s *Service) QuickCreateAccount(ctx context.Context, password string, regis } err = s.authRepository.CreatePasswordUser(ctx, user, displayIdentity, account, session, inviteBind) if err == nil { + if isGameRobot { + // 全站机器人账号是后台运营资产,不是 App 登录主体;创建成功后不给 access/refresh token, + // 也不写登录成功审计和 IP 风控任务,避免后台批量造号污染登录链路与新增用户统计。 + return authdomain.Token{ + AppCode: user.AppCode, + UserID: user.UserID, + DisplayUserID: user.CurrentDisplayUserID, + DefaultDisplayUserID: user.DefaultDisplayUserID, + DisplayUserIDKind: string(user.CurrentDisplayUserIDKind), + ProfileCompleted: user.ProfileCompleted, + OnboardingStatus: string(user.OnboardingStatus), + TokenType: tokenType, + }, nil + } s.audit(ctx, meta, user.UserID, loginPassword, "", resultSuccess, "") token, tokenErr := s.issueToken(user, session.SessionID, refreshToken) if tokenErr != nil { diff --git a/services/user-service/internal/service/auth/service.go b/services/user-service/internal/service/auth/service.go index b0c1b522..c497794f 100644 --- a/services/user-service/internal/service/auth/service.go +++ b/services/user-service/internal/service/auth/service.go @@ -40,9 +40,9 @@ type AuthRepository interface { AllocateDisplayUserID(ctx context.Context, appCode string) (string, error) // SetPassword 为已有用户首次写入密码身份。 SetPassword(ctx context.Context, account authdomain.PasswordAccount) error - // CreatePasswordUser 原子创建用户、默认短号、密码身份和首个 session。 + // CreatePasswordUser 原子创建用户、默认短号、密码身份和可选首个 session;全站机器人会传空 session。 CreatePasswordUser(ctx context.Context, user userdomain.User, identity userdomain.Identity, account authdomain.PasswordAccount, session authdomain.Session, invite invitedomain.BindCommand) error - // FindPasswordByDisplayUserID 通过当前 active display_user_id 读取密码身份。 + // FindPasswordByDisplayUserID 通过当前 active 展示号或 held 默认短号登录别名读取密码身份。 FindPasswordByDisplayUserID(ctx context.Context, displayUserID string, nowMs int64) (authdomain.PasswordAccount, error) // CreateSession 创建新的 refresh session。 CreateSession(ctx context.Context, session authdomain.Session) error diff --git a/services/user-service/internal/service/auth/service_test.go b/services/user-service/internal/service/auth/service_test.go index 473d36af..2685ab76 100644 --- a/services/user-service/internal/service/auth/service_test.go +++ b/services/user-service/internal/service/auth/service_test.go @@ -293,6 +293,119 @@ func TestQuickCreateAccountCreatesCompletedPasswordLogin(t *testing.T) { } } +func TestQuickCreateGameRobotCannotLoginOrCreateSession(t *testing.T) { + // 全站机器人复用用户资料和短号展示,但不是 App 登录主体;创建路径不能返回可用 token,也不能落 refresh session。 + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedCountry(t, repository, "SA") + now := time.UnixMilli(3000) + imImporter := &fakeIMAccountImporter{} + svc := newAuthService(repository, &now, []int64{900012}, []string{"100012"}, authservice.WithIMAccountImporter(imImporter)) + + token, err := svc.QuickCreateAccount(ctx, "secret-pass", authdomain.ThirdPartyRegistration{ + Username: "Robot Account", + Avatar: "https://cdn.example/robot.png", + Gender: "unknown", + Country: "SA", + DeviceID: "robot-dev", + Source: userdomain.RegisterSourceGameRobot, + Platform: "android", + Language: "ar", + Timezone: "UTC", + AppVersion: "1.0.0", + }, authservice.Meta{RequestID: "req-robot-create", AppCode: "lalu", ClientIP: "203.0.113.20", UserAgent: "robot-test/1.0"}) + if err != nil { + t.Fatalf("QuickCreateAccount robot failed: %v", err) + } + if token.UserID != 900012 || token.DisplayUserID == "" || token.AccessToken != "" || token.RefreshToken != "" || token.SessionID != "" { + t.Fatalf("robot token must expose identity without login credentials: %+v", token) + } + user, err := repository.GetUser(ctx, token.UserID) + if err != nil { + t.Fatalf("GetUser robot failed: %v", err) + } + if user.RegisterSource != userdomain.RegisterSourceGameRobot || user.CanLogin() { + t.Fatalf("robot user source/login boundary mismatch: %+v", user) + } + if imImporter.userID != 0 { + t.Fatalf("robot account must not import IM login account: %+v", imImporter) + } + var sessionCount int + if err := repository.RawDB().QueryRowContext(ctx, ` + SELECT COUNT(*) + FROM auth_sessions + WHERE app_code = 'lalu' AND user_id = ? + `, token.UserID).Scan(&sessionCount); err != nil { + t.Fatalf("query robot sessions failed: %v", err) + } + if sessionCount != 0 { + t.Fatalf("robot account must not create auth session, got %d", sessionCount) + } + if _, err := svc.LoginPassword(ctx, token.DisplayUserID, "secret-pass", "robot-dev", authservice.Meta{RequestID: "req-robot-login", AppCode: "lalu"}); !xerr.IsCode(err, xerr.UserDisabled) { + t.Fatalf("robot password login must be blocked, got %v", err) + } +} + +func TestGameRobotSourceDoesNotEmitUserRegisteredOutbox(t *testing.T) { + // UserRegistered 是新增用户和留存 cohort 的事实来源;source=game_robot 即使走通用用户创建事务也不能写入统计 outbox。 + ctx := context.Background() + repository := mysqltest.NewRepository(t) + now := time.UnixMilli(4000) + robot := userdomain.User{ + AppCode: "lalu", + UserID: 900101, + DefaultDisplayUserID: "190101", + CurrentDisplayUserID: "190101", + CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault, + RegisterSource: userdomain.RegisterSourceGameRobot, + Status: userdomain.StatusActive, + CreatedAtMs: now.UnixMilli(), + UpdatedAtMs: now.UnixMilli(), + } + normal := userdomain.User{ + AppCode: "lalu", + UserID: 900102, + DefaultDisplayUserID: "190102", + CurrentDisplayUserID: "190102", + CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault, + Status: userdomain.StatusActive, + CreatedAtMs: now.Add(time.Millisecond).UnixMilli(), + UpdatedAtMs: now.Add(time.Millisecond).UnixMilli(), + } + if err := repository.CreateUserWithIdentity(ctx, robot, userdomain.Identity{ + AppCode: "lalu", + UserID: robot.UserID, + DisplayUserID: robot.DefaultDisplayUserID, + DefaultDisplayUserID: robot.DefaultDisplayUserID, + DisplayUserIDKind: userdomain.DisplayUserIDKindDefault, + Status: userdomain.DisplayUserIDStatusActive, + }); err != nil { + t.Fatalf("create robot user failed: %v", err) + } + if err := repository.CreateUserWithIdentity(ctx, normal, userdomain.Identity{ + AppCode: "lalu", + UserID: normal.UserID, + DisplayUserID: normal.DefaultDisplayUserID, + DefaultDisplayUserID: normal.DefaultDisplayUserID, + DisplayUserIDKind: userdomain.DisplayUserIDKindDefault, + Status: userdomain.DisplayUserIDStatusActive, + }); err != nil { + t.Fatalf("create normal user failed: %v", err) + } + var outboxCount int + var aggregateID int64 + if err := repository.RawDB().QueryRowContext(ctx, ` + SELECT COUNT(*), COALESCE(MAX(aggregate_id), 0) + FROM user_outbox + WHERE app_code = 'lalu' AND event_type = 'UserRegistered' + `).Scan(&outboxCount, &aggregateID); err != nil { + t.Fatalf("query user registered outbox failed: %v", err) + } + if outboxCount != 1 || aggregateID != normal.UserID { + t.Fatalf("robot source must be excluded from UserRegistered outbox, count=%d aggregate=%d", outboxCount, aggregateID) + } +} + func TestRefreshTokenCarriesProfileRequiredOnboardingStatus(t *testing.T) { // gateway profile gate 依赖 access token 快照;未完成 onboarding 的新用户刷新后仍必须保持资料待补态。 ctx := context.Background() @@ -876,7 +989,7 @@ func TestThirdPartyRegisterRetriesDisplayUserIDConflict(t *testing.T) { } func TestPasswordLoginUsesCurrentDisplayUserIDWithPrettyLease(t *testing.T) { - // 密码登录必须使用当前 active display_user_id;靓号 active 时默认号不能登录。 + // 密码登录允许 held 默认短号作为登录别名;签发 token 必须仍使用当前 active 靓号。 ctx := context.Background() repository := mysqltest.NewRepository(t) seedCountry(t, repository, "SG") @@ -895,9 +1008,13 @@ func TestPasswordLoginUsesCurrentDisplayUserIDWithPrettyLease(t *testing.T) { t.Fatalf("ApplyPrettyDisplayUserID failed: %v", err) } - if _, err := authSvc.LoginPassword(ctx, token.DefaultDisplayUserID, "secret-pass", "ios", authservice.Meta{}); !xerr.IsCode(err, xerr.AuthFailed) { - // 默认短号处于 held 状态时不能作为登录入口。 - t.Fatalf("default display_user_id must not login while pretty is active, got %v", err) + defaultAliasToken, err := authSvc.LoginPassword(ctx, token.DefaultDisplayUserID, "secret-pass", "ios", authservice.Meta{}) + if err != nil { + t.Fatalf("default display_user_id alias login failed while pretty is active: %v", err) + } + if defaultAliasToken.DisplayUserID != "888888" || defaultAliasToken.DefaultDisplayUserID != token.DefaultDisplayUserID || defaultAliasToken.DisplayUserIDKind != string(userdomain.DisplayUserIDKindPretty) { + // 默认短号只是登录入口别名,不能把用户当前展示号回退成默认短号。 + t.Fatalf("unexpected default alias login token: %+v", defaultAliasToken) } prettyToken, err := authSvc.LoginPassword(ctx, "888888", "secret-pass", "ios", authservice.Meta{}) if err != nil { @@ -922,7 +1039,7 @@ func TestPasswordLoginUsesCurrentDisplayUserIDWithPrettyLease(t *testing.T) { } func TestPasswordLoginAllowsAlphanumericPrettyDisplayID(t *testing.T) { - // 后台发放靓号可以包含字母;密码登录必须复用同一可解析展示号规则,不能只允许纯数字靓号。 + // 后台发放靓号可以包含字母;密码登录既允许 active 靓号,也允许 held 默认短号作为别名。 ctx := context.Background() repository := mysqltest.NewRepository(t) seedCountry(t, repository, "SG") @@ -955,9 +1072,20 @@ func TestPasswordLoginAllowsAlphanumericPrettyDisplayID(t *testing.T) { t.Fatalf("make users display snapshot stale failed: %v", err) } - if _, err := authSvc.LoginPassword(ctx, token.DefaultDisplayUserID, "secret-pass", "ios", authservice.Meta{}); !xerr.IsCode(err, xerr.AuthFailed) { - // 默认短号被置为 held 后不能再作为登录入口,避免两个展示号同时登录同一账号。 - t.Fatalf("default display_user_id must not login while admin pretty is active, got %v", err) + defaultAliasToken, err := authSvc.LoginPassword(ctx, token.DefaultDisplayUserID, "secret-pass", "ios", authservice.Meta{}) + if err != nil { + t.Fatalf("default display_user_id alias login failed while admin pretty is active: %v", err) + } + if defaultAliasToken.UserID != token.UserID || defaultAliasToken.DisplayUserID != "1111cc1111" || defaultAliasToken.DisplayUserIDKind != string(userdomain.DisplayUserIDKindPretty) { + // users 快照即使因为历史脚本滞后停在默认号,登录成功后也必须按事实表修回 active 靓号。 + t.Fatalf("unexpected default alias login token: %+v", defaultAliasToken) + } + userAfterAliasLogin, err := repository.GetUser(ctx, token.UserID) + if err != nil { + t.Fatalf("GetUser after default alias login failed: %v", err) + } + if userAfterAliasLogin.CurrentDisplayUserID != "1111cc1111" || userAfterAliasLogin.CurrentDisplayUserIDKind != userdomain.DisplayUserIDKindPretty { + t.Fatalf("default alias login must repair users current display snapshot: %+v", userAfterAliasLogin) } prettyToken, err := authSvc.LoginPassword(ctx, "1111cc1111", "secret-pass", "ios", authservice.Meta{}) if err != nil { diff --git a/services/user-service/internal/service/cp/leaderboard_store.go b/services/user-service/internal/service/cp/leaderboard_store.go new file mode 100644 index 00000000..d74c0061 --- /dev/null +++ b/services/user-service/internal/service/cp/leaderboard_store.go @@ -0,0 +1,180 @@ +package cp + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/redis/go-redis/v9" + "hyapp/pkg/appcode" + "hyapp/pkg/idgen" + "hyapp/pkg/xerr" + cpdomain "hyapp/services/user-service/internal/domain/cp" +) + +const ( + defaultIntimacyLeaderboardKeyPrefix = "user:cp_intimacy_leaderboard" + intimacyLeaderboardRelationAll = "all" +) + +// RedisIntimacyLeaderboardStore 使用 zset 保存 CP 亲密值排行榜;score 以 intimacy_value 为主排序并写入批次内 tie-break,member 是双方展示资料 JSON。 +type RedisIntimacyLeaderboardStore struct { + client *redis.Client + keyPrefix string +} + +// NewRedisIntimacyLeaderboardClient 创建 Redis 客户端并探测连通性,避免 cron 首次执行才发现配置错误。 +func NewRedisIntimacyLeaderboardClient(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, + }) + // 榜单 Redis 是 App 查询链路的唯一读模型,不按登录风控那样 fail-open;启动期探测失败直接暴露配置问题。 + if err := client.Ping(ctx).Err(); err != nil { + _ = client.Close() + return nil, err + } + return client, nil +} + +// NewRedisIntimacyLeaderboardStore 基于共享 Redis client 创建亲密榜读模型。 +func NewRedisIntimacyLeaderboardStore(client *redis.Client, keyPrefix string) *RedisIntimacyLeaderboardStore { + keyPrefix = strings.Trim(strings.TrimSpace(keyPrefix), ":") + if keyPrefix == "" { + // keyPrefix 统一去掉首尾冒号,最终 key 固定为 prefix:app:relation,避免配置里多写冒号产生双冒号 key。 + keyPrefix = defaultIntimacyLeaderboardKeyPrefix + } + return &RedisIntimacyLeaderboardStore{client: client, keyPrefix: keyPrefix} +} + +// ReplaceIntimacyLeaderboard 每轮全量替换 all/cp/brother/sister 四个 zset,保证资料快照和关系状态可被重建覆盖。 +func (s *RedisIntimacyLeaderboardStore) ReplaceIntimacyLeaderboard(ctx context.Context, app string, entries []cpdomain.IntimacyLeaderboardEntry) error { + if s == nil || s.client == nil { + return xerr.New(xerr.Unavailable, "cp intimacy leaderboard redis is not configured") + } + grouped := map[string][]redis.Z{ + intimacyLeaderboardRelationAll: {}, + cpdomain.RelationTypeCP: {}, + cpdomain.RelationTypeBrother: {}, + cpdomain.RelationTypeSister: {}, + } + for index, entry := range entries { + relationType := normalizeRelationType(entry.RelationType) + if relationType == "" || entry.RelationshipID == "" { + // 异常关系类型或空 relationship_id 不写入榜单,避免 Redis member 变成无法回溯的脏读模型。 + continue + } + member, err := encodeIntimacyLeaderboardMember(entry) + if err != nil { + return err + } + // Redis zset 在同 score 时会按 member 字符串排序;MySQL 已经按更新时间和关系 ID 给出稳定顺序, + // 这里把批次内顺序编码进小偏移,保证亲密值相同的关系不会因为 JSON 字符串变化而跳名次。 + z := redis.Z{Score: intimacyLeaderboardScore(entry.IntimacyValue, index, len(entries)), Member: member} + // all 榜给 App 默认入口使用,按关系类型拆出的榜给筛选页使用;四个 key 同轮替换,保证视图时间一致。 + grouped[intimacyLeaderboardRelationAll] = append(grouped[intimacyLeaderboardRelationAll], z) + grouped[relationType] = append(grouped[relationType], z) + } + for relationType, members := range grouped { + // 即使某个关系类型本轮没有数据也要执行 replaceKey,让已结束关系从旧榜单里被清掉。 + if err := s.replaceKey(ctx, s.key(app, relationType), members); err != nil { + return err + } + } + return nil +} + +// ListIntimacyLeaderboard 读取 zset 分页;rank 由当前 zset 顺序计算,不信任 member 里的旧 rank。 +func (s *RedisIntimacyLeaderboardStore) ListIntimacyLeaderboard(ctx context.Context, app string, relationType string, page int32, pageSize int32, nowMs int64) (cpdomain.IntimacyLeaderboardPage, error) { + if s == nil || s.client == nil { + return cpdomain.IntimacyLeaderboardPage{}, xerr.New(xerr.Unavailable, "cp intimacy leaderboard redis is not configured") + } + relationType = normalizeRelationType(relationType) + if relationType == "" { + relationType = intimacyLeaderboardRelationAll + } + page, pageSize = normalizeLeaderboardPage(page, pageSize) + key := s.key(app, relationType) + // zset 是降序榜单,offset/stop 按客户端分页直接映射;pageSize 已在 service 内限制最大值。 + offset := int64((page - 1) * pageSize) + stop := offset + int64(pageSize) - 1 + + // total 用 ZCARD 取当前 key 的真实长度;空 key 返回 0,不需要额外区分“还没刷新”和“当前无数据”。 + total, err := s.client.ZCard(ctx, key).Result() + if err != nil { + return cpdomain.IntimacyLeaderboardPage{}, err + } + rows, err := s.client.ZRevRangeWithScores(ctx, key, offset, stop).Result() + if err != nil { + return cpdomain.IntimacyLeaderboardPage{}, err + } + items := make([]cpdomain.IntimacyLeaderboardEntry, 0, len(rows)) + for index, row := range rows { + member, ok := row.Member.(string) + if !ok || strings.TrimSpace(member) == "" { + continue + } + var entry cpdomain.IntimacyLeaderboardEntry + if err := json.Unmarshal([]byte(member), &entry); err != nil { + return cpdomain.IntimacyLeaderboardPage{}, err + } + // rank 只由当前 Redis 顺序计算,member 内写入时会清零,避免历史 rank 在重新排序后污染返回值。 + entry.Rank = offset + int64(index) + 1 + items = append(items, entry) + } + return cpdomain.IntimacyLeaderboardPage{ + Items: items, + Total: total, + Page: page, + PageSize: pageSize, + ServerTimeMS: nowMs, + }, nil +} + +func (s *RedisIntimacyLeaderboardStore) replaceKey(ctx context.Context, key string, members []redis.Z) error { + if len(members) == 0 { + // 全量重建时空集合代表该榜单当前没有 active 关系,直接删除旧 key,避免 App 看到过期关系。 + return s.client.Del(ctx, key).Err() + } + tmpKey := key + ":tmp:" + idgen.New("cprank") + pipe := s.client.Pipeline() + // 先写临时 key,再 RENAME 覆盖正式 key;读取方要么看到旧榜,要么看到新榜,不会看到半截榜单。 + pipe.Del(ctx, tmpKey) + pipe.ZAdd(ctx, tmpKey, members...) + pipe.Rename(ctx, tmpKey, key) + _, err := pipe.Exec(ctx) + return err +} + +func (s *RedisIntimacyLeaderboardStore) key(app string, relationType string) string { + app = appcode.Normalize(app) + relationType = strings.TrimSpace(relationType) + if relationType == "" { + relationType = intimacyLeaderboardRelationAll + } + return fmt.Sprintf("%s:%s:%s", s.keyPrefix, app, relationType) +} + +func encodeIntimacyLeaderboardMember(entry cpdomain.IntimacyLeaderboardEntry) (string, error) { + // rank 是读取时按 zset 顺序计算的派生值,写入 member 前清零,避免同一关系在不同分榜里带着错误名次。 + entry.Rank = 0 + payload, err := json.Marshal(entry) + if err != nil { + return "", err + } + return string(payload), nil +} + +func intimacyLeaderboardScore(intimacyValue int64, index int, total int) float64 { + if total <= 0 { + total = 1 + } + // score = 亲密值 * (total+1) + 批次倒序偏移;这样任意 1 点亲密值差距都大于所有 tie-break 偏移。 + tieOffset := total - index + if tieOffset < 0 { + tieOffset = 0 + } + return float64(intimacyValue)*float64(total+1) + float64(tieOffset) +} diff --git a/services/user-service/internal/service/cp/leaderboard_store_test.go b/services/user-service/internal/service/cp/leaderboard_store_test.go new file mode 100644 index 00000000..794e5cac --- /dev/null +++ b/services/user-service/internal/service/cp/leaderboard_store_test.go @@ -0,0 +1,16 @@ +package cp + +import "testing" + +func TestIntimacyLeaderboardScorePreservesPrimaryIntimacyAndBatchOrder(t *testing.T) { + total := 1000 + if intimacyLeaderboardScore(101, total-1, total) <= intimacyLeaderboardScore(100, 0, total) { + t.Fatal("higher intimacy value must always outrank lower intimacy value") + } + if intimacyLeaderboardScore(100, 0, total) <= intimacyLeaderboardScore(100, 1, total) { + t.Fatal("same intimacy value must keep repository order") + } + if intimacyLeaderboardScore(100, 1, total) <= intimacyLeaderboardScore(100, 2, total) { + t.Fatal("same intimacy value must keep stable order across later rows") + } +} diff --git a/services/user-service/internal/service/cp/service.go b/services/user-service/internal/service/cp/service.go index 7b5c26db..423827e8 100644 --- a/services/user-service/internal/service/cp/service.go +++ b/services/user-service/internal/service/cp/service.go @@ -6,10 +6,18 @@ import ( "strings" "time" + "hyapp/pkg/appcode" "hyapp/pkg/xerr" cpdomain "hyapp/services/user-service/internal/domain/cp" ) +const ( + defaultIntimacyLeaderboardPageSize = int32(20) + maxIntimacyLeaderboardPageSize = int32(100) + defaultIntimacyLeaderboardRows = 10000 + maxIntimacyLeaderboardRows = 10000 +) + // Repository 描述 CP 关系 service 需要的持久化能力,便于后续单元测试替换 MySQL 实现。 type Repository interface { ListApplications(ctx context.Context, userID int64, direction string, status string, page int32, pageSize int32, nowMs int64) ([]cpdomain.Application, int64, error) @@ -20,20 +28,57 @@ type Repository interface { ConfirmBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string, walletTransactionID string, paidCoinAmount int64, coinBalanceAfter int64, nowMs int64) (cpdomain.RelationshipBreakup, cpdomain.Relationship, error) CancelBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string, reason string, nowMs int64) (cpdomain.RelationshipBreakup, error) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEvent, nowMs int64) (cpdomain.ConsumeResult, error) + ListActiveIntimacyLeaderboardEntries(ctx context.Context, limit int) ([]cpdomain.IntimacyLeaderboardEntry, error) +} + +// IntimacyLeaderboardStore 是 CP 亲密榜 Redis zset 读模型边界。 +type IntimacyLeaderboardStore interface { + ReplaceIntimacyLeaderboard(ctx context.Context, appCode string, entries []cpdomain.IntimacyLeaderboardEntry) error + ListIntimacyLeaderboard(ctx context.Context, appCode string, relationType string, page int32, pageSize int32, nowMs int64) (cpdomain.IntimacyLeaderboardPage, error) +} + +// AvatarFrameReader 批量读取用户当前佩戴头像框;头像框事实仍归 wallet-service。 +type AvatarFrameReader interface { + BatchGetAvatarFrames(ctx context.Context, appCode string, userIDs []int64) (map[int64]cpdomain.AvatarFrameSnapshot, error) } // Service 只编排 CP 用例,不直接投递 IM;IM 由 user_outbox 下游消费者按事实事件处理。 type Service struct { - repo Repository - clock func() time.Time + repo Repository + leaderboardStore IntimacyLeaderboardStore + avatarFrames AvatarFrameReader + clock func() time.Time +} + +// Option 按需挂载 CP service 的读模型依赖。 +type Option func(*Service) + +// WithIntimacyLeaderboardStore 挂载 CP 亲密榜 Redis 读模型。 +func WithIntimacyLeaderboardStore(store IntimacyLeaderboardStore) Option { + return func(s *Service) { + s.leaderboardStore = store + } +} + +// WithAvatarFrameReader 挂载头像框批量读取器,刷新榜单时一次性补齐双方头像框快照。 +func WithAvatarFrameReader(reader AvatarFrameReader) Option { + return func(s *Service) { + s.avatarFrames = reader + } } // New 创建 CP 关系 service。 -func New(repo Repository) *Service { - return &Service{ +func New(repo Repository, options ...Option) *Service { + service := &Service{ repo: repo, clock: func() time.Time { return time.Now().UTC() }, } + for _, option := range options { + if option != nil { + option(service) + } + } + return service } // ListApplications 返回用户收到或发出的 CP 申请;过期固化在 repository 中完成。 @@ -119,6 +164,51 @@ func (s *Service) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEvent return s.repo.ConsumeGiftEvent(ctx, event, s.nowMs()) } +// RefreshIntimacyLeaderboard 重建 CP 亲密值排行榜读模型;cron 只触发,关系、用户资料和头像框快照都由 owner 服务读取。 +func (s *Service) RefreshIntimacyLeaderboard(ctx context.Context, limit int) (int, error) { + // 榜单刷新是定时异步链路:任何核心依赖缺失都必须显式失败,让 cron run 记录失败状态, + // 不能静默返回空榜,否则 App 会误以为当前没有亲密关系。 + if s == nil || s.repo == nil { + return 0, xerr.New(xerr.Unavailable, "cp repository is not configured") + } + if s.leaderboardStore == nil { + return 0, xerr.New(xerr.Unavailable, "cp intimacy leaderboard store is not configured") + } + if s.avatarFrames == nil { + return 0, xerr.New(xerr.Unavailable, "cp avatar frame reader is not configured") + } + // batch_size 在 cron 配置里表达本轮榜单保留上限;service 再做一次夹紧,避免配置异常导致全表无界扫描。 + limit = normalizeLeaderboardRowLimit(limit) + entries, err := s.repo.ListActiveIntimacyLeaderboardEntries(ctx, limit) + if err != nil { + return 0, err + } + // 头像框归 wallet-service 管理,关系和用户资料归 user-service 管理;刷新时批量读钱包快照并固化进 zset member, + // App 查询榜单时只读 Redis,不再跨服务补资料,避免高频榜单接口放大 wallet-service 压力。 + frames, err := s.avatarFrames.BatchGetAvatarFrames(ctx, appcode.FromContext(ctx), intimacyLeaderboardUserIDs(entries)) + if err != nil { + return 0, err + } + applyAvatarFrames(entries, frames) + // Redis 写入采用全量替换语义;关系结束、改名、换头像、换头像框都能在下一轮被覆盖,不需要单独维护删除事件。 + if err := s.leaderboardStore.ReplaceIntimacyLeaderboard(ctx, appcode.FromContext(ctx), entries); err != nil { + return 0, err + } + return len(entries), nil +} + +// ListIntimacyLeaderboard 返回 cron 刷新的 CP 亲密值排行榜,避免 App 请求实时扫关系表和跨服务补头像框。 +func (s *Service) ListIntimacyLeaderboard(ctx context.Context, relationType string, page int32, pageSize int32) (cpdomain.IntimacyLeaderboardPage, error) { + // 查询接口只依赖 Redis 读模型;如果刷新链路没有装配,直接返回 unavailable,避免回退 MySQL 实时扫表。 + if s == nil || s.leaderboardStore == nil { + return cpdomain.IntimacyLeaderboardPage{}, xerr.New(xerr.Unavailable, "cp intimacy leaderboard store is not configured") + } + // relation_type 只允许 cp/brother/sister;空值和非法值都归并到 all 榜,由 Redis store 统一落 key。 + relationType = normalizeRelationType(relationType) + page, pageSize = normalizeLeaderboardPage(page, pageSize) + return s.leaderboardStore.ListIntimacyLeaderboard(ctx, appcode.FromContext(ctx), relationType, page, pageSize, s.nowMs()) +} + func (s *Service) nowMs() int64 { if s == nil || s.clock == nil { return time.Now().UTC().UnixMilli() @@ -171,6 +261,60 @@ func normalizeStatus(value string) string { } } +func normalizeLeaderboardPage(page int32, pageSize int32) (int32, int32) { + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = defaultIntimacyLeaderboardPageSize + } + if pageSize > maxIntimacyLeaderboardPageSize { + pageSize = maxIntimacyLeaderboardPageSize + } + return page, pageSize +} + +func normalizeLeaderboardRowLimit(limit int) int { + if limit <= 0 { + return defaultIntimacyLeaderboardRows + } + if limit > maxIntimacyLeaderboardRows { + return maxIntimacyLeaderboardRows + } + return limit +} + +func intimacyLeaderboardUserIDs(entries []cpdomain.IntimacyLeaderboardEntry) []int64 { + seen := make(map[int64]struct{}, len(entries)*2) + userIDs := make([]int64, 0, len(entries)*2) + for _, entry := range entries { + for _, userID := range []int64{entry.UserA.UserID, entry.UserB.UserID} { + if userID <= 0 { + continue + } + // 同一个用户可能出现在多条兄弟/姐妹关系里,批量读钱包资源前先去重,避免无意义的重复 RPC payload。 + if _, exists := seen[userID]; exists { + continue + } + seen[userID] = struct{}{} + userIDs = append(userIDs, userID) + } + } + return userIDs +} + +func applyAvatarFrames(entries []cpdomain.IntimacyLeaderboardEntry, frames map[int64]cpdomain.AvatarFrameSnapshot) { + for index := range entries { + // 没有佩戴头像框或 wallet-service 没返回资源时保持 nil,gateway 会省略 avatar_frame 字段。 + if frame, ok := frames[entries[index].UserA.UserID]; ok && frame.ResourceID > 0 { + entries[index].UserA.AvatarFrame = &frame + } + if frame, ok := frames[entries[index].UserB.UserID]; ok && frame.ResourceID > 0 { + entries[index].UserB.AvatarFrame = &frame + } + } +} + func normalizeRelationType(value string) string { switch strings.ToLower(strings.TrimSpace(value)) { case cpdomain.RelationTypeCP: diff --git a/services/user-service/internal/service/cp/service_test.go b/services/user-service/internal/service/cp/service_test.go new file mode 100644 index 00000000..bb12441e --- /dev/null +++ b/services/user-service/internal/service/cp/service_test.go @@ -0,0 +1,107 @@ +package cp + +import ( + "context" + "testing" + + "hyapp/pkg/appcode" + cpdomain "hyapp/services/user-service/internal/domain/cp" +) + +func TestRefreshIntimacyLeaderboardHydratesAvatarFrames(t *testing.T) { + repo := &fakeLeaderboardRepo{ + entries: []cpdomain.IntimacyLeaderboardEntry{{ + RelationshipID: "rank_cp_1", + RelationType: cpdomain.RelationTypeCP, + IntimacyValue: 300, + Level: 2, + UserA: cpdomain.IntimacyLeaderboardUser{UserID: 7101, DisplayUserID: "7101", Username: "A", Avatar: "a.png"}, + UserB: cpdomain.IntimacyLeaderboardUser{UserID: 7102, DisplayUserID: "7102", Username: "B", Avatar: "b.png"}, + }}, + } + store := &fakeLeaderboardStore{} + frames := &fakeAvatarFrameReader{frames: map[int64]cpdomain.AvatarFrameSnapshot{ + 7101: {ResourceID: 9101, ResourceCode: "frame_gold", Name: "Gold", AssetURL: "gold.png"}, + 7102: {ResourceID: 9102, ResourceCode: "frame_blue", Name: "Blue", AssetURL: "blue.png"}, + }} + svc := New(repo, WithIntimacyLeaderboardStore(store), WithAvatarFrameReader(frames)) + + processed, err := svc.RefreshIntimacyLeaderboard(appcode.WithContext(context.Background(), appcode.Default), 100) + if err != nil { + t.Fatalf("refresh leaderboard failed: %v", err) + } + if processed != 1 || len(store.entries) != 1 { + t.Fatalf("processed/store mismatch: processed=%d entries=%+v", processed, store.entries) + } + entry := store.entries[0] + if entry.UserA.AvatarFrame == nil || entry.UserA.AvatarFrame.ResourceID != 9101 || + entry.UserB.AvatarFrame == nil || entry.UserB.AvatarFrame.ResourceCode != "frame_blue" { + t.Fatalf("avatar frames must be hydrated before zset replace: %+v", entry) + } + if len(frames.userIDs) != 2 || frames.userIDs[0] != 7101 || frames.userIDs[1] != 7102 { + t.Fatalf("avatar frame reader should receive both users once: %+v", frames.userIDs) + } +} + +type fakeLeaderboardRepo struct { + entries []cpdomain.IntimacyLeaderboardEntry +} + +func (r *fakeLeaderboardRepo) ListApplications(context.Context, int64, string, string, int32, int32, int64) ([]cpdomain.Application, int64, error) { + return nil, 0, nil +} + +func (r *fakeLeaderboardRepo) AcceptApplication(context.Context, int64, string, int64) (cpdomain.Application, cpdomain.Relationship, error) { + return cpdomain.Application{}, cpdomain.Relationship{}, nil +} + +func (r *fakeLeaderboardRepo) RejectApplication(context.Context, int64, string, string, int64) (cpdomain.Application, error) { + return cpdomain.Application{}, nil +} + +func (r *fakeLeaderboardRepo) ListRelationships(context.Context, int64, string, int32, int32) ([]cpdomain.Relationship, int64, error) { + return nil, 0, nil +} + +func (r *fakeLeaderboardRepo) PrepareBreakRelationship(context.Context, int64, string, string, int64) (cpdomain.RelationshipBreakup, cpdomain.Relationship, error) { + return cpdomain.RelationshipBreakup{}, cpdomain.Relationship{}, nil +} + +func (r *fakeLeaderboardRepo) ConfirmBreakRelationship(context.Context, int64, string, string, string, int64, int64, int64) (cpdomain.RelationshipBreakup, cpdomain.Relationship, error) { + return cpdomain.RelationshipBreakup{}, cpdomain.Relationship{}, nil +} + +func (r *fakeLeaderboardRepo) CancelBreakRelationship(context.Context, int64, string, string, string, int64) (cpdomain.RelationshipBreakup, error) { + return cpdomain.RelationshipBreakup{}, nil +} + +func (r *fakeLeaderboardRepo) ConsumeGiftEvent(context.Context, cpdomain.GiftEvent, int64) (cpdomain.ConsumeResult, error) { + return cpdomain.ConsumeResult{}, nil +} + +func (r *fakeLeaderboardRepo) ListActiveIntimacyLeaderboardEntries(context.Context, int) ([]cpdomain.IntimacyLeaderboardEntry, error) { + return r.entries, nil +} + +type fakeLeaderboardStore struct { + entries []cpdomain.IntimacyLeaderboardEntry +} + +func (s *fakeLeaderboardStore) ReplaceIntimacyLeaderboard(_ context.Context, _ string, entries []cpdomain.IntimacyLeaderboardEntry) error { + s.entries = append([]cpdomain.IntimacyLeaderboardEntry(nil), entries...) + return nil +} + +func (s *fakeLeaderboardStore) ListIntimacyLeaderboard(_ context.Context, _ string, _ string, page int32, pageSize int32, nowMs int64) (cpdomain.IntimacyLeaderboardPage, error) { + return cpdomain.IntimacyLeaderboardPage{Items: s.entries, Total: int64(len(s.entries)), Page: page, PageSize: pageSize, ServerTimeMS: nowMs}, nil +} + +type fakeAvatarFrameReader struct { + frames map[int64]cpdomain.AvatarFrameSnapshot + userIDs []int64 +} + +func (r *fakeAvatarFrameReader) BatchGetAvatarFrames(_ context.Context, _ string, userIDs []int64) (map[int64]cpdomain.AvatarFrameSnapshot, error) { + r.userIDs = append([]int64(nil), userIDs...) + return r.frames, nil +} diff --git a/services/user-service/internal/storage/mysql/auth/password.go b/services/user-service/internal/storage/mysql/auth/password.go index 75a51a35..e808c0dd 100644 --- a/services/user-service/internal/storage/mysql/auth/password.go +++ b/services/user-service/internal/storage/mysql/auth/password.go @@ -30,7 +30,8 @@ func (r *Repository) SetPassword(ctx context.Context, account authdomain.Passwor // CreatePasswordUser 原子创建完整密码账号,服务快捷建号和联调账号准备。 func (r *Repository) CreatePasswordUser(ctx context.Context, user userdomain.User, identity userdomain.Identity, account authdomain.PasswordAccount, session authdomain.Session, inviteBind invitedomain.BindCommand) error { - // 账号创建必须和默认短号、密码身份、refresh session 同事务提交,避免返回不可登录的 uid。 + // 普通快捷账号必须和 refresh session 同事务提交,避免返回不可登录的 uid; + // 全站机器人会传入空 session,只写资料和密码身份,不产生任何可续期登录态。 tx, err := r.db.BeginTx(ctx, nil) if err != nil { return err @@ -53,18 +54,20 @@ func (r *Repository) CreatePasswordUser(ctx context.Context, user userdomain.Use `, appcode.Normalize(account.AppCode), account.UserID, account.PasswordHash, account.HashAlg, account.CreatedAtMs, account.UpdatedAtMs); err != nil { return mapAuthDuplicateError(err) } - if err := insertSession(ctx, tx, session); err != nil { - // session 写入失败时必须回滚用户和密码身份,否则客户端拿不到 refresh token 却占用账号。 - return err + if session.SessionID != "" { + if err := insertSession(ctx, tx, session); err != nil { + // session 写入失败时必须回滚用户和密码身份,否则客户端拿不到 refresh token 却占用账号。 + return err + } } return tx.Commit() } -// FindPasswordByDisplayUserID 通过当前 active display_user_id 查找用户密码身份。 +// FindPasswordByDisplayUserID 通过当前 active 展示号或 held 默认短号登录别名查找用户密码身份。 func (r *Repository) FindPasswordByDisplayUserID(ctx context.Context, displayUserID string, nowMs int64) (authdomain.PasswordAccount, error) { - // 密码登录前先按输入短号触发靓号懒过期。 + // 密码登录前先按输入展示号触发靓号懒过期;输入 held 默认短号时也能恢复已到期靓号。 if err := identitystorage.ExpirePrettyByDisplayUserID(ctx, r.db, displayUserID, nowMs, "lazy_password_login"); err != nil { return authdomain.PasswordAccount{}, err } @@ -76,25 +79,42 @@ func (r *Repository) FindPasswordByDisplayUserID(ctx context.Context, displayUse SELECT pa.app_code, pa.user_id, - du.display_user_id, - du.display_user_id_kind, - COALESCE(du.expires_at_ms, 0), + active_du.display_user_id, + active_du.display_user_id_kind, + COALESCE(active_du.expires_at_ms, 0), pa.password_hash, pa.hash_alg, pa.created_at_ms, pa.updated_at_ms - FROM user_display_user_ids du + FROM user_display_user_ids login_du + INNER JOIN user_display_user_ids active_du + ON active_du.app_code = login_du.app_code + AND active_du.user_id = login_du.user_id + AND active_du.status = ? + AND (active_du.expires_at_ms IS NULL OR active_du.expires_at_ms = 0 OR active_du.expires_at_ms > ?) INNER JOIN password_accounts pa - ON pa.app_code = du.app_code - AND pa.user_id = du.user_id - WHERE du.app_code = ? - AND du.display_user_id = ? - AND du.status = ? - AND (du.expires_at_ms IS NULL OR du.expires_at_ms = 0 OR du.expires_at_ms > ?) + ON pa.app_code = login_du.app_code + AND pa.user_id = login_du.user_id + WHERE login_du.app_code = ? + AND login_du.display_user_id = ? + AND ( + ( + login_du.status = ? + AND (login_du.expires_at_ms IS NULL OR login_du.expires_at_ms = 0 OR login_du.expires_at_ms > ?) + ) + OR ( + login_du.status = ? + AND login_du.display_user_id_kind = ? + ) + ) LIMIT 1 - `, appcode.FromContext(ctx), displayUserID, string(userdomain.DisplayUserIDStatusActive), nowMs).Scan(&account.AppCode, &account.UserID, &account.DisplayUserID, &displayUserIDKind, &displayUserIDExpiresAtMs, &account.PasswordHash, &account.HashAlg, &account.CreatedAtMs, &account.UpdatedAtMs) + `, string(userdomain.DisplayUserIDStatusActive), nowMs, + appcode.FromContext(ctx), displayUserID, + string(userdomain.DisplayUserIDStatusActive), nowMs, + string(userdomain.DisplayUserIDStatusHeld), string(userdomain.DisplayUserIDKindDefault)). + Scan(&account.AppCode, &account.UserID, &account.DisplayUserID, &displayUserIDKind, &displayUserIDExpiresAtMs, &account.PasswordHash, &account.HashAlg, &account.CreatedAtMs, &account.UpdatedAtMs) if err == sql.ErrNoRows { - // 短号不存在、处于 held/released、已经过期或未设置密码都返回 not found,由 service 统一映射 AUTH_FAILED。 + // 短号不存在、处于 released/expired、没有对应 active 展示号或未设置密码都返回 not found,由 service 统一映射 AUTH_FAILED。 return authdomain.PasswordAccount{}, xerr.New(xerr.NotFound, "password account not found") } if err != nil { diff --git a/services/user-service/internal/storage/mysql/cp/repository.go b/services/user-service/internal/storage/mysql/cp/repository.go index 81a4e87d..39be9169 100644 --- a/services/user-service/internal/storage/mysql/cp/repository.go +++ b/services/user-service/internal/storage/mysql/cp/repository.go @@ -276,6 +276,48 @@ func (r *Repository) ListRelationships(ctx context.Context, userID int64, relati return relationships, total, nil } +// ListActiveIntimacyLeaderboardEntries 读取当前 active 关系亲密值榜单快照;cron 会把结果全量覆盖到 Redis zset。 +func (r *Repository) ListActiveIntimacyLeaderboardEntries(ctx context.Context, limit int) ([]cpdomain.IntimacyLeaderboardEntry, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + appCode := appcode.FromContext(ctx) + if limit <= 0 { + limit = 1000 + } + // 榜单只读取 active 关系,ended 关系依赖下一轮 Redis 全量替换自然消失;这里不读取历史关系,避免旧亲密值继续上榜。 + // 用户基础资料在同一个 SQL 内 join 出来,保证关系快照和双方短号/昵称/头像来自同一时刻的 MySQL 读视图。 + rows, err := r.db.QueryContext(ctx, ` + SELECT + r.relationship_id, r.relation_type, r.intimacy_value, r.level_no, + r.formed_at_ms, r.updated_at_ms, + user_a.user_id, COALESCE(user_a.current_display_user_id, ''), COALESCE(user_a.username, ''), COALESCE(user_a.avatar, ''), + user_b.user_id, COALESCE(user_b.current_display_user_id, ''), COALESCE(user_b.username, ''), COALESCE(user_b.avatar, '') + FROM user_cp_relationships r + JOIN users user_a ON user_a.app_code = r.app_code AND user_a.user_id = r.user_a_id + JOIN users user_b ON user_b.app_code = r.app_code AND user_b.user_id = r.user_b_id + WHERE r.app_code = ? AND r.status = ? + -- 亲密值是主排序;同分时用 updated_at_ms 和 relationship_id 稳定顺序,Redis 写入会保留这个 tie-break。 + ORDER BY r.intimacy_value DESC, r.updated_at_ms DESC, r.relationship_id ASC + LIMIT ?`, + appCode, cpdomain.RelationshipStatusActive, limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + entries := make([]cpdomain.IntimacyLeaderboardEntry, 0) + for rows.Next() { + // scan 层只做字段搬运,不补头像框;头像框来自 wallet-service,避免 user-service 复制资源归属事实。 + entry, err := scanIntimacyLeaderboardEntry(rows) + if err != nil { + return nil, err + } + entries = append(entries, entry) + } + return entries, rows.Err() +} + // PrepareBreakRelationship 为解除关系创建 pending 占位;占位持有 command 幂等事实,但不提前结束关系。 func (r *Repository) PrepareBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string, nowMs int64) (cpdomain.RelationshipBreakup, cpdomain.Relationship, error) { if r == nil || r.db == nil { @@ -719,6 +761,17 @@ func scanRelationship(scanner interface{ Scan(dest ...any) error }) (cpdomain.Re return relationship, err } +func scanIntimacyLeaderboardEntry(scanner interface{ Scan(dest ...any) error }) (cpdomain.IntimacyLeaderboardEntry, error) { + var entry cpdomain.IntimacyLeaderboardEntry + err := scanner.Scan( + &entry.RelationshipID, &entry.RelationType, &entry.IntimacyValue, &entry.Level, + &entry.FormedAtMS, &entry.UpdatedAtMS, + &entry.UserA.UserID, &entry.UserA.DisplayUserID, &entry.UserA.Username, &entry.UserA.Avatar, + &entry.UserB.UserID, &entry.UserB.DisplayUserID, &entry.UserB.Username, &entry.UserB.Avatar, + ) + return entry, err +} + func applyRelationshipLevelProgress(relationship *cpdomain.Relationship) { if relationship == nil { return diff --git a/services/user-service/internal/storage/mysql/cp/repository_test.go b/services/user-service/internal/storage/mysql/cp/repository_test.go index 2961ad5d..4504bf6f 100644 --- a/services/user-service/internal/storage/mysql/cp/repository_test.go +++ b/services/user-service/internal/storage/mysql/cp/repository_test.go @@ -149,6 +149,33 @@ func TestListRelationshipsIncludesServerLevelProgress(t *testing.T) { } } +func TestListActiveIntimacyLeaderboardEntriesOrdersAndIncludesBothUsers(t *testing.T) { + schema := mysqlschema.New(t) + repo := New(schema.DB) + ctx := appcode.WithContext(context.Background(), appcode.Default) + nowMS := int64(1700000000000) + + for _, userID := range []int64{4101, 4102, 4103, 4104} { + seedCPTestUser(t, schema.DB, userID, fmt.Sprintf("Rank %d", userID), nowMS) + } + seedActiveCPRelationship(t, schema.DB, "rank_low", cpdomain.RelationTypeCP, 4101, 4102, 90, 1, nowMS) + seedActiveCPRelationship(t, schema.DB, "rank_high", cpdomain.RelationTypeBrother, 4103, 4104, 220, 2, nowMS+1) + + entries, err := repo.ListActiveIntimacyLeaderboardEntries(ctx, 10) + if err != nil { + t.Fatalf("list intimacy leaderboard entries failed: %v", err) + } + if len(entries) != 2 { + t.Fatalf("entries len=%d, want 2: %+v", len(entries), entries) + } + if entries[0].RelationshipID != "rank_high" || entries[0].IntimacyValue != 220 { + t.Fatalf("highest intimacy relationship should rank first: %+v", entries[0]) + } + if entries[0].UserA.DisplayUserID != "4103" || entries[0].UserB.DisplayUserID != "4104" || entries[0].UserA.Avatar == "" || entries[0].UserB.Username == "" { + t.Fatalf("entry must include both user snapshots: %+v", entries[0]) + } +} + func TestBreakRelationshipPrepareConfirmIsIdempotent(t *testing.T) { schema := mysqlschema.New(t) repo := New(schema.DB) diff --git a/services/user-service/internal/storage/mysql/user/repository.go b/services/user-service/internal/storage/mysql/user/repository.go index a245b88a..c5b55a6f 100644 --- a/services/user-service/internal/storage/mysql/user/repository.go +++ b/services/user-service/internal/storage/mysql/user/repository.go @@ -154,6 +154,11 @@ func (r *Repository) CreateUserWithIdentity(ctx context.Context, user userdomain } func insertUserRegisteredOutbox(ctx context.Context, tx *sql.Tx, user userdomain.User) error { + if userdomain.IsGameRobotRegisterSource(user.RegisterSource) { + // 全站机器人只复用用户资料、头像和短号参与游戏展示,不是真实自然注册用户; + // 这里不写 UserRegistered 事实,避免实时统计、留存 cohort 和注册奖励把批量机器人当新增用户。 + return nil + } payload := map[string]any{ "user_id": user.UserID, "default_display_user_id": user.DefaultDisplayUserID, diff --git a/services/user-service/internal/transport/grpc/cp.go b/services/user-service/internal/transport/grpc/cp.go index d138716a..0ceacd1b 100644 --- a/services/user-service/internal/transport/grpc/cp.go +++ b/services/user-service/internal/transport/grpc/cp.go @@ -79,6 +79,32 @@ func (s *Server) ListCPRelationships(ctx context.Context, req *userv1.ListCPRela return resp, nil } +// ListCPIntimacyLeaderboard 返回 cron 写入 Redis 的 CP 亲密值排行榜。 +func (s *Server) ListCPIntimacyLeaderboard(ctx context.Context, req *userv1.ListCPIntimacyLeaderboardRequest) (*userv1.ListCPIntimacyLeaderboardResponse, error) { + // app_code 必须从 meta 进入 context,Redis key 按 app 隔离;gateway 不传时会落到默认 app。 + ctx = contextWithApp(ctx, req.GetMeta()) + if s.cpSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) + } + // service 层负责关系类型归一、分页夹紧和 Redis 读取;transport 只做 proto 边界转换。 + page, err := s.cpSvc.ListIntimacyLeaderboard(ctx, req.GetRelationType(), req.GetPage(), req.GetPageSize()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &userv1.ListCPIntimacyLeaderboardResponse{ + Items: make([]*userv1.CPIntimacyLeaderboardItem, 0, len(page.Items)), + Total: page.Total, + Page: page.Page, + PageSize: page.PageSize, + ServerTimeMs: page.ServerTimeMS, + } + for _, item := range page.Items { + // item 内已经包含 cron 固化的双方资料和头像框快照,这里不能再回表或跨服务补字段。 + resp.Items = append(resp.Items, toProtoCPIntimacyLeaderboardItem(item)) + } + return resp, nil +} + // PrepareBreakCPRelationship 创建解除关系占位;gateway 随后按返回费用调用 wallet-service 扣金币。 func (s *Server) PrepareBreakCPRelationship(ctx context.Context, req *userv1.PrepareBreakCPRelationshipRequest) (*userv1.PrepareBreakCPRelationshipResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) @@ -225,6 +251,51 @@ func toProtoCPUserProfile(profile cpdomain.UserProfile) *userv1.CPUserProfile { } } +func toProtoCPIntimacyLeaderboardItem(item cpdomain.IntimacyLeaderboardEntry) *userv1.CPIntimacyLeaderboardItem { + if item.RelationshipID == "" { + return nil + } + return &userv1.CPIntimacyLeaderboardItem{ + Rank: item.Rank, + RelationshipId: item.RelationshipID, + RelationType: item.RelationType, + IntimacyValue: item.IntimacyValue, + Level: item.Level, + UserA: toProtoCPIntimacyLeaderboardUser(item.UserA), + UserB: toProtoCPIntimacyLeaderboardUser(item.UserB), + FormedAtMs: item.FormedAtMS, + UpdatedAtMs: item.UpdatedAtMS, + } +} + +func toProtoCPIntimacyLeaderboardUser(user cpdomain.IntimacyLeaderboardUser) *userv1.CPIntimacyLeaderboardUser { + if user.UserID <= 0 { + return nil + } + return &userv1.CPIntimacyLeaderboardUser{ + UserId: user.UserID, + DisplayUserId: user.DisplayUserID, + Username: user.Username, + Avatar: user.Avatar, + AvatarFrame: toProtoCPAvatarFrame(user.AvatarFrame), + } +} + +func toProtoCPAvatarFrame(frame *cpdomain.AvatarFrameSnapshot) *userv1.CPAvatarFrameSnapshot { + if frame == nil || frame.ResourceID <= 0 { + return nil + } + return &userv1.CPAvatarFrameSnapshot{ + ResourceId: frame.ResourceID, + ResourceCode: frame.ResourceCode, + Name: frame.Name, + AssetUrl: frame.AssetURL, + PreviewUrl: frame.PreviewURL, + AnimationUrl: frame.AnimationURL, + MetadataJson: frame.MetadataJSON, + } +} + func toProtoCPGiftSnapshot(gift cpdomain.GiftSnapshot) *userv1.CPGiftSnapshot { if gift.GiftID == "" && gift.GiftName == "" { return nil diff --git a/services/user-service/internal/transport/grpc/cron.go b/services/user-service/internal/transport/grpc/cron.go index 5813abc1..0aac7845 100644 --- a/services/user-service/internal/transport/grpc/cron.go +++ b/services/user-service/internal/transport/grpc/cron.go @@ -100,6 +100,27 @@ func (s *Server) ExpireManagerUserBlocks(ctx context.Context, req *userv1.CronBa }, nil } +// RefreshCPIntimacyLeaderboard 重建 CP 亲密值排行榜 Redis zset;cron-service 只负责调度和租约。 +func (s *Server) RefreshCPIntimacyLeaderboard(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) { + // cron-service 每个 app_code 单独触发一次;这里把 app_code 放进 context 后,MySQL 查询和 Redis key 都会按租户隔离。 + ctx = contextWithApp(ctx, req.GetMeta()) + if s.cpSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) + } + // batch_size 在这个任务里不是“继续翻页”的游标,只是本轮榜单最大行数;service 会全量替换 Redis 读模型。 + processed, err := s.cpSvc.RefreshIntimacyLeaderboard(ctx, int(req.GetBatchSize())) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + // 刷新是一次性快照任务,没有 has_more 续跑语义;下一次 5 分钟调度会重新生成完整榜单。 + return &userv1.CronBatchResponse{ + ClaimedCount: int32(processed), + ProcessedCount: int32(processed), + SuccessCount: int32(processed), + HasMore: false, + }, nil +} + func durationFromMillis(value int64) time.Duration { if value <= 0 { return 0 diff --git a/services/user-service/internal/transport/grpc/server.go b/services/user-service/internal/transport/grpc/server.go index 6db1d8d3..4d98e7d5 100644 --- a/services/user-service/internal/transport/grpc/server.go +++ b/services/user-service/internal/transport/grpc/server.go @@ -150,7 +150,7 @@ func (s *Server) SetPassword(ctx context.Context, req *userv1.SetPasswordRequest return &userv1.SetPasswordResponse{PasswordSet: true}, nil } -// QuickCreateAccount 为游戏联调快速创建一个已完成资料且可密码登录的账号。 +// QuickCreateAccount 为后台快速创建完整资料账号;source=game_robot 时只创建资料号,不返回可登录 session。 func (s *Server) QuickCreateAccount(ctx context.Context, req *userv1.QuickCreateAccountRequest) (*userv1.QuickCreateAccountResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) registration := authdomain.ThirdPartyRegistration{ diff --git a/services/wallet-service/configs/hyapp.code-workspace b/services/wallet-service/configs/hyapp.code-workspace new file mode 100644 index 00000000..0a4d7ae8 --- /dev/null +++ b/services/wallet-service/configs/hyapp.code-workspace @@ -0,0 +1,13 @@ +{ + "folders": [ + { + "path": "../../.." + }, + { + "path": "../../../../hyapp-admin-platform" + }, + { + "path": "../../../../hyapp-h5" + } + ] +} \ No newline at end of file diff --git a/tests/smoke/cp_intimacy_leaderboard_real_flow_test.go b/tests/smoke/cp_intimacy_leaderboard_real_flow_test.go new file mode 100644 index 00000000..846daf69 --- /dev/null +++ b/tests/smoke/cp_intimacy_leaderboard_real_flow_test.go @@ -0,0 +1,316 @@ +package smoke_test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "testing" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + userv1 "hyapp.local/api/proto/user/v1" +) + +func TestCPIntimacyLeaderboardLocalRealFlow(t *testing.T) { + if os.Getenv("CP_INTIMACY_REAL_FLOW_TEST") != "1" { + t.Skip("set CP_INTIMACY_REAL_FLOW_TEST=1 to run the local CP intimacy leaderboard flow with real users and real data") + } + + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + + baseURL := strings.TrimRight(envOr("CP_INTIMACY_BASE_URL", "http://127.0.0.1:13000/api/v1"), "/") + userServiceAddr := envOr("CP_INTIMACY_USER_SERVICE_ADDR", "127.0.0.1:13005") + appCode := envOr("CP_INTIMACY_APP_CODE", "lalu") + token := requiredCPIntimacyEnv(t, "CP_INTIMACY_ACCESS_TOKEN") + relationType := strings.TrimSpace(os.Getenv("CP_INTIMACY_RELATION_TYPE")) + pageSize := int32EnvOr(t, "CP_INTIMACY_PAGE_SIZE", 100) + batchSize := int32EnvOr(t, "CP_INTIMACY_BATCH_SIZE", 10000) + expectedUserA, expectedUserB := optionalCPIntimacyExpectedPair(t) + + processed := refreshCPIntimacyLeaderboard(t, ctx, userServiceAddr, appCode, batchSize) + if processed <= 0 { + t.Fatalf("CP intimacy refresh processed no active relationships; local real data must contain at least one active CP/brother/sister relationship") + } + + data := requestCPIntimacyLeaderboard(t, ctx, &http.Client{Timeout: 15 * time.Second}, baseURL, appCode, token, relationType, pageSize) + if data.Total <= 0 || len(data.Items) == 0 { + t.Fatalf("gateway returned empty CP intimacy leaderboard after refresh: total=%d items=%d", data.Total, len(data.Items)) + } + if data.Page != 1 || data.PageSize != pageSize || data.ServerTimeMS <= 0 { + t.Fatalf("gateway leaderboard pagination/server time mismatch: %+v", data) + } + + item := data.Items[0] + if expectedUserA != "" && expectedUserB != "" { + found, ok := findCPIntimacyPair(data.Items, expectedUserA, expectedUserB) + if !ok { + t.Fatalf("expected real CP pair (%s,%s) not found in first page: total=%d page_size=%d items=%+v", expectedUserA, expectedUserB, data.Total, data.PageSize, data.Items) + } + item = found + } + assertCPIntimacyLeaderboardItem(t, item) + if shouldRequireCPIntimacyAvatarFrame() { + assertCPIntimacyAvatarFrame(t, item.UserA, "user_a") + assertCPIntimacyAvatarFrame(t, item.UserB, "user_b") + } + + t.Logf( + "cp intimacy real flow passed: base_url=%s user_service=%s app_code=%s relation_type=%q processed=%d total=%d rank=%d relationship_id=%s user_a=%s/%s user_b=%s/%s", + baseURL, + userServiceAddr, + appCode, + relationType, + processed, + data.Total, + item.Rank, + item.RelationshipID, + item.UserA.UserID, + item.UserA.DisplayUserID, + item.UserB.UserID, + item.UserB.DisplayUserID, + ) +} + +type cpIntimacyEnvelope struct { + Code string `json:"code"` + Message string `json:"message"` + RequestID string `json:"request_id"` + Data json.RawMessage `json:"data"` +} + +type cpIntimacyLeaderboardData struct { + Items []cpIntimacyLeaderboardItem `json:"items"` + Total int64 `json:"total"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type cpIntimacyLeaderboardItem struct { + Rank int64 `json:"rank"` + RelationshipID string `json:"relationship_id"` + RelationType string `json:"relation_type"` + IntimacyValue int64 `json:"intimacy_value"` + Level int32 `json:"level"` + UserA cpIntimacyUser `json:"user_a"` + UserB cpIntimacyUser `json:"user_b"` + FormedAtMS int64 `json:"formed_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type cpIntimacyUser struct { + UserID string `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + AvatarFrame *cpIntimacyAvatarFrame `json:"avatar_frame"` +} + +type cpIntimacyAvatarFrame struct { + ResourceID int64 `json:"resource_id"` + ResourceCode string `json:"resource_code"` + Name string `json:"name"` + AssetURL string `json:"asset_url"` + PreviewURL string `json:"preview_url"` + AnimationURL string `json:"animation_url"` + MetadataJSON string `json:"metadata_json"` +} + +func refreshCPIntimacyLeaderboard(t *testing.T, ctx context.Context, addr string, appCode string, batchSize int32) int32 { + t.Helper() + + conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + t.Fatalf("dial local user-service %s failed: %v", addr, err) + } + defer conn.Close() + + runID := fmt.Sprintf("cp_intimacy_real_flow_%d", time.Now().UTC().UnixMilli()) + resp, err := userv1.NewUserCronServiceClient(conn).RefreshCPIntimacyLeaderboard(ctx, &userv1.CronBatchRequest{ + Meta: &userv1.RequestMeta{ + RequestId: runID, + Caller: "cp-intimacy-real-flow-test", + GatewayNodeId: "local-smoke-test", + SentAtMs: time.Now().UTC().UnixMilli(), + AppCode: appCode, + }, + RunId: runID, + WorkerId: "local-smoke-test", + BatchSize: batchSize, + LockTtlMs: int64((5 * time.Minute).Milliseconds()), + }) + if err != nil { + t.Fatalf("refresh CP intimacy leaderboard through local user-service failed: %v", err) + } + if resp.GetFailureCount() != 0 { + t.Fatalf("refresh CP intimacy leaderboard reported failures: %+v", resp) + } + return resp.GetProcessedCount() +} + +func requestCPIntimacyLeaderboard(t *testing.T, ctx context.Context, client *http.Client, baseURL string, appCode string, token string, relationType string, pageSize int32) cpIntimacyLeaderboardData { + t.Helper() + + query := url.Values{} + query.Set("page", "1") + query.Set("page_size", strconv.FormatInt(int64(pageSize), 10)) + if relationType != "" { + query.Set("relation_type", relationType) + } + var data cpIntimacyLeaderboardData + doCPIntimacyJSON(t, ctx, client, http.MethodGet, baseURL+"/cp/intimacy-leaderboard?"+query.Encode(), appCode, token, nil, &data) + return data +} + +func doCPIntimacyJSON(t *testing.T, ctx context.Context, client *http.Client, method string, url string, appCode string, token string, body any, out any) { + t.Helper() + + var payload io.Reader + if body != nil { + raw, err := json.Marshal(body) + if err != nil { + t.Fatalf("marshal %s %s body failed: %v", method, url, err) + } + payload = bytes.NewReader(raw) + } + req, err := http.NewRequestWithContext(ctx, method, url, payload) + if err != nil { + t.Fatalf("build %s %s request failed: %v", method, url, err) + } + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("X-App-Code", appCode) + req.Header.Set("X-App-Language", "en") + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := client.Do(req) + if err != nil { + t.Fatalf("%s %s failed: %v", method, url, err) + } + defer resp.Body.Close() + raw, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("read %s %s response failed: %v", method, url, err) + } + + var envelope cpIntimacyEnvelope + if err := json.Unmarshal(raw, &envelope); err != nil { + t.Fatalf("decode %s %s envelope failed: status=%d body=%s err=%v", method, url, resp.StatusCode, string(raw), err) + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 || envelope.Code != "OK" { + t.Fatalf("%s %s returned non-OK: status=%d code=%s message=%s request_id=%s body=%s", method, url, resp.StatusCode, envelope.Code, envelope.Message, envelope.RequestID, string(raw)) + } + if out != nil { + if len(envelope.Data) == 0 { + t.Fatalf("%s %s returned empty data: request_id=%s", method, url, envelope.RequestID) + } + if err := json.Unmarshal(envelope.Data, out); err != nil { + t.Fatalf("decode %s %s data failed: request_id=%s data=%s err=%v", method, url, envelope.RequestID, string(envelope.Data), err) + } + } +} + +func assertCPIntimacyLeaderboardItem(t *testing.T, item cpIntimacyLeaderboardItem) { + t.Helper() + + if item.Rank <= 0 || strings.TrimSpace(item.RelationshipID) == "" || strings.TrimSpace(item.RelationType) == "" || item.IntimacyValue < 0 || item.Level <= 0 { + t.Fatalf("leaderboard item base fields invalid: %+v", item) + } + assertCPIntimacyUser(t, item.UserA, "user_a") + assertCPIntimacyUser(t, item.UserB, "user_b") + if item.UserA.UserID == item.UserB.UserID { + t.Fatalf("leaderboard pair must contain two different users: %+v", item) + } +} + +func assertCPIntimacyUser(t *testing.T, user cpIntimacyUser, side string) { + t.Helper() + + if strings.TrimSpace(user.UserID) == "" || strings.TrimSpace(user.DisplayUserID) == "" || strings.TrimSpace(user.Username) == "" || strings.TrimSpace(user.Avatar) == "" { + t.Fatalf("%s must include real long id, short id, username and avatar: %+v", side, user) + } + if user.AvatarFrame != nil { + assertCPIntimacyAvatarFrame(t, user, side) + } +} + +func assertCPIntimacyAvatarFrame(t *testing.T, user cpIntimacyUser, side string) { + t.Helper() + + if user.AvatarFrame == nil || user.AvatarFrame.ResourceID <= 0 || strings.TrimSpace(user.AvatarFrame.ResourceCode) == "" { + t.Fatalf("%s must include equipped avatar_frame when CP_INTIMACY_REQUIRE_AVATAR_FRAME=1: %+v", side, user) + } +} + +func findCPIntimacyPair(items []cpIntimacyLeaderboardItem, userA string, userB string) (cpIntimacyLeaderboardItem, bool) { + for _, item := range items { + if sameCPIntimacyPair(item, userA, userB) { + return item, true + } + } + return cpIntimacyLeaderboardItem{}, false +} + +func sameCPIntimacyPair(item cpIntimacyLeaderboardItem, userA string, userB string) bool { + left := strings.TrimSpace(item.UserA.UserID) + right := strings.TrimSpace(item.UserB.UserID) + return (left == userA && right == userB) || (left == userB && right == userA) +} + +func optionalCPIntimacyExpectedPair(t *testing.T) (string, string) { + t.Helper() + + userA := strings.TrimSpace(os.Getenv("CP_INTIMACY_EXPECT_USER_A_ID")) + userB := strings.TrimSpace(os.Getenv("CP_INTIMACY_EXPECT_USER_B_ID")) + if userA == "" && userB == "" { + return "", "" + } + if userA == "" || userB == "" { + t.Fatalf("CP_INTIMACY_EXPECT_USER_A_ID and CP_INTIMACY_EXPECT_USER_B_ID must be set together") + } + if _, err := strconv.ParseInt(userA, 10, 64); err != nil { + t.Fatalf("CP_INTIMACY_EXPECT_USER_A_ID must be an int64: %v", err) + } + if _, err := strconv.ParseInt(userB, 10, 64); err != nil { + t.Fatalf("CP_INTIMACY_EXPECT_USER_B_ID must be an int64: %v", err) + } + return userA, userB +} + +func int32EnvOr(t *testing.T, key string, fallback int32) int32 { + t.Helper() + + raw := strings.TrimSpace(os.Getenv(key)) + if raw == "" { + return fallback + } + value, err := strconv.ParseInt(raw, 10, 32) + if err != nil || value <= 0 { + t.Fatalf("%s must be a positive int32", key) + } + return int32(value) +} + +func requiredCPIntimacyEnv(t *testing.T, key string) string { + t.Helper() + + value := strings.TrimSpace(os.Getenv(key)) + if value == "" { + t.Fatalf("%s is required for CP intimacy real flow test", key) + } + return value +} + +func shouldRequireCPIntimacyAvatarFrame() bool { + return strings.TrimSpace(os.Getenv("CP_INTIMACY_REQUIRE_AVATAR_FRAME")) == "1" +} diff --git a/tests/smoke/room_rps_real_im_flow_test.go b/tests/smoke/room_rps_real_im_flow_test.go new file mode 100644 index 00000000..87c1614d --- /dev/null +++ b/tests/smoke/room_rps_real_im_flow_test.go @@ -0,0 +1,484 @@ +package smoke_test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "testing" + "time" + + "hyapp/pkg/tencentim" +) + +func TestRoomRPSLocalHTTPFlowWithRealTencentIM(t *testing.T) { + if os.Getenv("ROOM_RPS_REAL_IM_FLOW_TEST") != "1" { + t.Skip("set ROOM_RPS_REAL_IM_FLOW_TEST=1 to run the local room-rps HTTP flow with real Tencent IM") + } + + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + + cfg := roomRPSRealIMConfigFromEnv(t) + imClient, err := tencentim.NewRESTClient(cfg) + if err != nil { + t.Fatalf("new real Tencent IM client failed: %v", err) + } + + baseURL := strings.TrimRight(envOr("ROOM_RPS_BASE_URL", "http://127.0.0.1:13000/api/v1"), "/") + appCode := envOr("ROOM_RPS_APP_CODE", "lalu") + roomID := envOr("ROOM_RPS_ROOM_ID", fmt.Sprintf("room-rps-real-im-%d", time.Now().UnixMilli())) + imGroupID := envOr("ROOM_RPS_IM_GROUP_ID", roomID) + giftID := positiveInt64EnvOr(t, "ROOM_RPS_GIFT_ID", 10001) + httpClient := &http.Client{Timeout: 15 * time.Second} + + initiator := quickCreateRoomRPSSmokeAccount(t, ctx, httpClient, baseURL, appCode, "room-rps-initiator") + challenger := quickCreateRoomRPSSmokeAccount(t, ctx, httpClient, baseURL, appCode, "room-rps-challenger") + if initiator.UserID == challenger.UserID { + t.Fatalf("quick-created users must be different: initiator=%+v challenger=%+v", initiator, challenger) + } + + // 真实 IM 是这个 smoke 的硬前置:先确保房间群存在并导入两个账号,避免后续 HTTP 成功但 IM 配置其实不可用。 + if err := imClient.EnsureGroup(ctx, tencentim.GroupSpec{ + GroupID: imGroupID, + Name: "room rps real im smoke " + roomID, + Type: tencentim.DefaultGroupType, + ApplyJoinOption: "FreeAccess", + }); err != nil { + t.Fatalf("ensure real Tencent IM room group failed: %v", err) + } + if shouldCleanupRoomRPSIMGroup() { + if !strings.HasPrefix(imGroupID, "room-rps-real-im-") { + t.Fatalf("ROOM_RPS_CLEANUP_IM_GROUP=1 only allows group id with room-rps-real-im- prefix, got %q", imGroupID) + } + t.Cleanup(func() { + cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cleanupCancel() + if err := imClient.DestroyGroup(cleanupCtx, imGroupID); err != nil { + t.Errorf("destroy real Tencent IM room-rps smoke group failed: %v", err) + } + }) + } + importRoomRPSIMAccount(t, ctx, imClient, initiator.UserID, initiator.Username) + importRoomRPSIMAccount(t, ctx, imClient, challenger.UserID, challenger.Username) + publishRoomRPSIMProbe(t, ctx, imClient, imGroupID, roomID) + + // /im/usersig 是 App 真正登录腾讯 IM SDK 前要调用的 gateway 接口;这里校验本地 gateway 和真实腾讯 IM SDKAppID 使用同一套配置。 + initiatorSig := requestRoomRPSUserSig(t, ctx, httpClient, baseURL, appCode, initiator.AccessToken, cfg.SDKAppID, initiator.UserID) + challengerSig := requestRoomRPSUserSig(t, ctx, httpClient, baseURL, appCode, challenger.AccessToken, cfg.SDKAppID, challenger.UserID) + if initiatorSig.UserID == challengerSig.UserID { + t.Fatalf("initiator and challenger must be different IM identifiers: %s", initiatorSig.UserID) + } + + config := requestRoomRPSConfig(t, ctx, httpClient, baseURL, appCode, initiator.AccessToken) + if strings.ToLower(strings.TrimSpace(config.Config.Status)) != "active" { + t.Fatalf("room-rps config must be open by default, got=%+v", config.Config) + } + assertRoomRPSGiftConfigured(t, config, giftID) + + listBefore := requestRoomRPSChallengeList(t, ctx, httpClient, baseURL, appCode, initiator.AccessToken, roomID) + t.Logf("room-rps list before create: count=%d next_cursor=%q", len(listBefore.Challenges), listBefore.NextCursor) + + created := createRoomRPSChallenge(t, ctx, httpClient, baseURL, appCode, initiator.AccessToken, roomID, giftID, "rock") + if created.Challenge.ChallengeID == "" { + t.Fatalf("created room-rps challenge must contain challenge_id: %+v", created.Challenge) + } + if created.Challenge.Initiator.UserID != strconv.FormatInt(initiator.UserID, 10) { + t.Fatalf("created challenge initiator mismatch: %+v", created.Challenge.Initiator) + } + if strings.TrimSpace(created.Challenge.Initiator.User.Nickname) == "" || strings.TrimSpace(created.Challenge.Initiator.User.Avatar) == "" { + t.Fatalf("created challenge must include initiator nickname/avatar for IM UI: %+v", created.Challenge.Initiator.User) + } + + accepted := acceptRoomRPSChallenge(t, ctx, httpClient, baseURL, appCode, challenger.AccessToken, created.Challenge.ChallengeID, "paper") + if accepted.Challenge.Challenger.UserID != strconv.FormatInt(challenger.UserID, 10) { + t.Fatalf("accepted challenge challenger mismatch: %+v", accepted.Challenge.Challenger) + } + if strings.TrimSpace(accepted.Challenge.Challenger.User.Nickname) == "" || strings.TrimSpace(accepted.Challenge.Challenger.User.Avatar) == "" { + t.Fatalf("accepted challenge must include challenger nickname/avatar for IM UI: %+v", accepted.Challenge.Challenger.User) + } + + detail := getRoomRPSChallenge(t, ctx, httpClient, baseURL, appCode, challenger.AccessToken, created.Challenge.ChallengeID) + if detail.Challenge.ChallengeID != created.Challenge.ChallengeID { + t.Fatalf("detail challenge id mismatch: got=%q want=%q", detail.Challenge.ChallengeID, created.Challenge.ChallengeID) + } + if detail.Challenge.Initiator.User.Nickname == "" || detail.Challenge.Challenger.User.Nickname == "" { + t.Fatalf("detail must preserve both player display profiles: %+v", detail.Challenge) + } + + t.Logf("room-rps real HTTP + IM smoke passed: base_url=%s room_id=%s im_group_id=%s challenge_id=%s", baseURL, roomID, imGroupID, created.Challenge.ChallengeID) +} + +type roomRPSEnvelope struct { + Code string `json:"code"` + Message string `json:"message"` + RequestID string `json:"request_id"` + Data json.RawMessage `json:"data"` +} + +type roomRPSUserSigData struct { + SDKAppID int64 `json:"sdk_app_id"` + UserID string `json:"user_id"` + UserSig string `json:"user_sig"` + ExpireAtMS int64 `json:"expire_at_ms"` +} + +type roomRPSQuickCreateData struct { + UID string `json:"uid"` + Account string `json:"account"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + PasswordSet bool `json:"password_set"` + Token roomRPSAuthToken `json:"token"` + Username string `json:"-"` + UserID int64 `json:"-"` +} + +type roomRPSAuthToken struct { + UserID string `json:"user_id"` + AccessToken string `json:"access_token"` +} + +type roomRPSConfigData struct { + Config roomRPSConfig `json:"config"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type roomRPSConfig struct { + Status string `json:"status"` + ChallengeTimeoutMS int64 `json:"challenge_timeout_ms"` + RevealCountdownMS int64 `json:"reveal_countdown_ms"` + StakeGifts []roomRPSStakeGift `json:"stake_gifts"` +} + +type roomRPSStakeGift struct { + GiftID string `json:"gift_id"` + GiftIDNumber int64 `json:"gift_id_number"` + Enabled bool `json:"enabled"` + SortOrder int32 `json:"sort_order"` +} + +type roomRPSChallengeListData struct { + Challenges []roomRPSChallenge `json:"challenges"` + NextCursor string `json:"next_cursor"` + PageSize int32 `json:"page_size"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type roomRPSChallengeData struct { + Challenge roomRPSChallenge `json:"challenge"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type roomRPSChallenge struct { + ChallengeID string `json:"challenge_id"` + RoomID string `json:"room_id"` + Status string `json:"status"` + StakeGiftID string `json:"stake_gift_id"` + StakeGiftIDNum int64 `json:"stake_gift_id_number"` + Initiator roomRPSPlayer `json:"initiator"` + Challenger roomRPSPlayer `json:"challenger"` + WinnerUserID string `json:"winner_user_id"` + SettlementStatus string `json:"settlement_status"` +} + +type roomRPSPlayer struct { + UserID string `json:"user_id"` + UserIDNumber int64 `json:"user_id_number"` + Gesture string `json:"gesture"` + Result string `json:"result"` + User roomRPSDisplayUser `json:"user"` +} + +type roomRPSDisplayUser struct { + UserID string `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` +} + +func quickCreateRoomRPSSmokeAccount(t *testing.T, ctx context.Context, client *http.Client, baseURL string, appCode string, role string) roomRPSQuickCreateData { + t.Helper() + + nowMS := time.Now().UnixMilli() + username := fmt.Sprintf("%s-%d", role, nowMS) + body := map[string]any{ + "password": "room-rps-smoke-pass-123", + "username": username, + "avatar": fmt.Sprintf("https://cdn.example.com/%s.png", role), + "gender": "unknown", + "country": "SG", + "device_id": fmt.Sprintf("%s-device-%d", role, nowMS), + "device": "codex-smoke", + "os_version": "local", + "birth": "2000-01-01", + "app_version": "1.0.0", + "build_number": "1", + "source": "room_rps_real_im_smoke", + "install_channel": "codex", + "platform": "android", + "language": "en", + "timezone": "Asia/Shanghai", + } + var data roomRPSQuickCreateData + doRoomRPSJSON(t, ctx, client, http.MethodPost, baseURL+"/auth/account/quick-create", appCode, "", body, &data) + data.Username = username + userID, err := strconv.ParseInt(strings.TrimSpace(data.Token.UserID), 10, 64) + if err != nil || userID <= 0 || strings.TrimSpace(data.AccessToken) == "" || !data.PasswordSet { + t.Fatalf("quick-create account response invalid: %+v", data) + } + data.UserID = userID + return data +} + +func requestRoomRPSUserSig(t *testing.T, ctx context.Context, client *http.Client, baseURL, appCode, token string, sdkAppID int64, userID int64) roomRPSUserSigData { + t.Helper() + + var data roomRPSUserSigData + doRoomRPSJSON(t, ctx, client, http.MethodGet, baseURL+"/im/usersig", appCode, token, nil, &data) + if data.SDKAppID != sdkAppID { + t.Fatalf("usersig sdk_app_id mismatch: got=%d want=%d data=%+v", data.SDKAppID, sdkAppID, data) + } + if data.UserID != strconv.FormatInt(userID, 10) || strings.TrimSpace(data.UserSig) == "" || data.ExpireAtMS <= time.Now().UnixMilli() { + t.Fatalf("usersig response mismatch for user %d: %+v", userID, data) + } + return data +} + +func requestRoomRPSConfig(t *testing.T, ctx context.Context, client *http.Client, baseURL, appCode, token string) roomRPSConfigData { + t.Helper() + + var data roomRPSConfigData + doRoomRPSJSON(t, ctx, client, http.MethodGet, baseURL+"/games/room-rps/config", appCode, token, nil, &data) + if data.Config.Status == "" { + t.Fatalf("room-rps config response is empty: %+v", data) + } + return data +} + +func requestRoomRPSChallengeList(t *testing.T, ctx context.Context, client *http.Client, baseURL, appCode, token, roomID string) roomRPSChallengeListData { + t.Helper() + + var data roomRPSChallengeListData + doRoomRPSJSON(t, ctx, client, http.MethodGet, baseURL+"/games/room-rps/challenges?room_id="+urlQueryEscape(roomID), appCode, token, nil, &data) + return data +} + +func createRoomRPSChallenge(t *testing.T, ctx context.Context, client *http.Client, baseURL, appCode, token, roomID string, giftID int64, gesture string) roomRPSChallengeData { + t.Helper() + + body := map[string]any{"room_id": roomID, "gift_id": giftID, "gesture": gesture} + var data roomRPSChallengeData + doRoomRPSJSON(t, ctx, client, http.MethodPost, baseURL+"/games/room-rps/challenges/create", appCode, token, body, &data) + return data +} + +func acceptRoomRPSChallenge(t *testing.T, ctx context.Context, client *http.Client, baseURL, appCode, token, challengeID string, gesture string) roomRPSChallengeData { + t.Helper() + + body := map[string]any{"gesture": gesture} + var data roomRPSChallengeData + doRoomRPSJSON(t, ctx, client, http.MethodPost, baseURL+"/games/room-rps/challenges/"+urlPathEscape(challengeID)+"/accept", appCode, token, body, &data) + return data +} + +func getRoomRPSChallenge(t *testing.T, ctx context.Context, client *http.Client, baseURL, appCode, token, challengeID string) roomRPSChallengeData { + t.Helper() + + var data roomRPSChallengeData + doRoomRPSJSON(t, ctx, client, http.MethodGet, baseURL+"/games/room-rps/challenges/"+urlPathEscape(challengeID), appCode, token, nil, &data) + return data +} + +func doRoomRPSJSON(t *testing.T, ctx context.Context, client *http.Client, method string, url string, appCode string, token string, body any, out any) { + t.Helper() + + var payload io.Reader + if body != nil { + raw, err := json.Marshal(body) + if err != nil { + t.Fatalf("marshal %s %s body failed: %v", method, url, err) + } + payload = bytes.NewReader(raw) + } + req, err := http.NewRequestWithContext(ctx, method, url, payload) + if err != nil { + t.Fatalf("build %s %s request failed: %v", method, url, err) + } + if strings.TrimSpace(token) != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + req.Header.Set("X-App-Code", appCode) + req.Header.Set("X-App-Language", "en") + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := client.Do(req) + if err != nil { + t.Fatalf("%s %s failed: %v", method, url, err) + } + defer resp.Body.Close() + raw, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("read %s %s response failed: %v", method, url, err) + } + + var envelope roomRPSEnvelope + if err := json.Unmarshal(raw, &envelope); err != nil { + t.Fatalf("decode %s %s envelope failed: status=%d body=%s err=%v", method, url, resp.StatusCode, string(raw), err) + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 || envelope.Code != "OK" { + t.Fatalf("%s %s returned non-OK: status=%d code=%s message=%s request_id=%s body=%s", method, url, resp.StatusCode, envelope.Code, envelope.Message, envelope.RequestID, string(raw)) + } + if out != nil { + if len(envelope.Data) == 0 { + t.Fatalf("%s %s returned empty data: request_id=%s", method, url, envelope.RequestID) + } + if err := json.Unmarshal(envelope.Data, out); err != nil { + t.Fatalf("decode %s %s data failed: request_id=%s data=%s err=%v", method, url, envelope.RequestID, string(envelope.Data), err) + } + } +} + +func assertRoomRPSGiftConfigured(t *testing.T, data roomRPSConfigData, giftID int64) { + t.Helper() + + for _, gift := range data.Config.StakeGifts { + if gift.GiftIDNumber == giftID || gift.GiftID == strconv.FormatInt(giftID, 10) { + if !gift.Enabled { + t.Fatalf("ROOM_RPS_GIFT_ID is configured but disabled: %+v", gift) + } + return + } + } + t.Fatalf("ROOM_RPS_GIFT_ID=%d not found in room-rps config: %+v", giftID, data.Config.StakeGifts) +} + +func publishRoomRPSIMProbe(t *testing.T, ctx context.Context, client *tencentim.RESTClient, groupID string, roomID string) { + t.Helper() + + nowMS := time.Now().UTC().UnixMilli() + eventID := fmt.Sprintf("room_rps_real_im_probe:%d", nowMS) + payload, err := json.Marshal(map[string]any{ + "event_id": eventID, + "event_type": "room_rps_real_im_probe", + "room_id": roomID, + "challenge_id": "", + "server_time_ms": nowMS, + "challenge": map[string]any{ + "room_id": roomID, + "status": "probe", + }, + }) + if err != nil { + t.Fatalf("marshal real IM probe failed: %v", err) + } + + // 这里真的调用腾讯云 IM REST 发送 TIMCustomElem;成功表示本地配置、admin UserSig、群 ID 和消息 payload 都被腾讯服务端接受。 + if err := client.PublishGroupCustomMessage(ctx, tencentim.CustomGroupMessage{ + GroupID: groupID, + EventID: eventID, + Desc: "room_rps_real_im_probe", + Ext: "room_rps", + PayloadJSON: payload, + }); err != nil { + t.Fatalf("publish real Tencent IM room-rps probe failed: %v", err) + } +} + +func importRoomRPSIMAccount(t *testing.T, ctx context.Context, client *tencentim.RESTClient, userID int64, nick string) { + t.Helper() + + if err := client.ImportAccount(ctx, tencentim.AccountProfile{ + UserID: userID, + Nick: nick, + FaceURL: "https://cdn.example.com/room-rps-smoke.png", + }); err != nil { + t.Fatalf("import real Tencent IM account user_id=%d failed: %v", userID, err) + } +} + +func roomRPSRealIMConfigFromEnv(t *testing.T) tencentim.RESTConfig { + t.Helper() + + sdkAppID := requiredPositiveInt64Env(t, "TENCENT_IM_SDK_APP_ID") + ttl := time.Hour + if rawTTL := strings.TrimSpace(os.Getenv("TENCENT_IM_ADMIN_USERSIG_TTL")); rawTTL != "" { + parsed, err := time.ParseDuration(rawTTL) + if err != nil || parsed <= 0 { + t.Fatalf("TENCENT_IM_ADMIN_USERSIG_TTL is invalid") + } + ttl = parsed + } + return tencentim.RESTConfig{ + SDKAppID: sdkAppID, + SecretKey: requiredRoomRPSEnv(t, "TENCENT_IM_SECRET_KEY"), + AdminIdentifier: requiredRoomRPSEnv(t, "TENCENT_IM_ADMIN_IDENTIFIER"), + AdminUserSigTTL: ttl, + Endpoint: envOr("TENCENT_IM_ENDPOINT", tencentim.DefaultEndpoint), + GroupType: tencentim.DefaultGroupType, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + } +} + +func requiredRoomRPSEnv(t *testing.T, key string) string { + t.Helper() + + value := strings.TrimSpace(os.Getenv(key)) + if value == "" { + t.Fatalf("%s is required for room-rps real IM flow test", key) + } + return value +} + +func requiredPositiveInt64Env(t *testing.T, key string) int64 { + t.Helper() + + value, err := strconv.ParseInt(requiredRoomRPSEnv(t, key), 10, 64) + if err != nil || value <= 0 { + t.Fatalf("%s must be a positive int64", key) + } + return value +} + +func positiveInt64EnvOr(t *testing.T, key string, fallback int64) int64 { + t.Helper() + + value := strings.TrimSpace(os.Getenv(key)) + if value == "" { + return fallback + } + parsed, err := strconv.ParseInt(value, 10, 64) + if err != nil || parsed <= 0 { + t.Fatalf("%s must be a positive int64", key) + } + return parsed +} + +func envOr(key string, fallback string) string { + value := strings.TrimSpace(os.Getenv(key)) + if value == "" { + return fallback + } + return value +} + +func shouldCleanupRoomRPSIMGroup() bool { + raw := strings.TrimSpace(os.Getenv("ROOM_RPS_CLEANUP_IM_GROUP")) + return raw == "" || raw == "1" +} + +func urlQueryEscape(value string) string { + return url.QueryEscape(value) +} + +func urlPathEscape(value string) string { + return url.PathEscape(value) +} diff --git a/tools/task-system-smoke/main.go b/tools/task-system-smoke/main.go new file mode 100644 index 00000000..d25091ce --- /dev/null +++ b/tools/task-system-smoke/main.go @@ -0,0 +1,352 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" + + activityv1 "hyapp.local/api/proto/activity/v1" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +const ( + adminBaseURL = "http://127.0.0.1:13100" + gatewayBaseURL = "http://127.0.0.1:13000" + activityGRPCURL = "127.0.0.1:13006" + appCode = "lalu" +) + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // 真实链路从后台 HTTP 登录开始,拿到后台 token 后只通过 daily-task 接口创建配置,不直接写 task_definitions。 + adminToken := must(adminLogin(ctx)) + suffix := fmt.Sprintf("%d", time.Now().UnixNano()) + task := must(createTask(ctx, adminToken, suffix)) + + // App 用户必须从 gateway 创建,确保 user-service 生成真实用户、session、access token 和腾讯 IM 账号导入链路。 + user := must(quickCreateUser(ctx, suffix)) + + // IM 测试走 gateway 的真实 UserSig 接口;这里校验 SDKAppID、IM user_id 和 user_sig 都按当前 access token 生成。 + im := must(getIMUserSig(ctx, user.AccessToken)) + + // 任务进度不允许 App 自报;smoke 用 activity-service 真实 gRPC 消费入口模拟 owner outbox worker 投递的服务端事实。 + event := must(consumeTaskEvent(ctx, task.TaskID, user.UserID, suffix)) + + // App 查询和领取只走 gateway HTTP;领取后 activity-service 会调用真实 wallet-service CreditTaskReward。 + beforeClaim := must(getTaskItem(ctx, user.AccessToken, task.TaskID)) + claim := must(claimTask(ctx, user.AccessToken, beforeClaim, suffix)) + afterClaim := must(getTaskItem(ctx, user.AccessToken, task.TaskID)) + + // 输出固定字段,方便人工和脚本快速确认本地真实接口链路是否完整通过。 + result := map[string]any{ + "admin_task_id": task.TaskID, + "admin_task_icon_key": task.IconKey, + "admin_task_icon_url": task.IconURL, + "admin_task_action_type": task.ActionType, + "app_user_id": user.UserID, + "app_display_user_id": user.DisplayUserID, + "im_sdk_app_id": im.SDKAppID, + "im_user_id": im.UserID, + "im_user_sig_non_empty": im.UserSig != "", + "event_status": event.Status, + "event_matched_task_count": event.MatchedTaskCount, + "task_status_before_claim": beforeClaim.Status, + "task_progress_before_claim": beforeClaim.ProgressValue, + "task_claimable_before_claim": beforeClaim.Claimable, + "claim_status": claim.Status, + "claim_wallet_transaction_id": claim.WalletTransactionID, + "claim_reward_coin_amount": claim.RewardCoinAmount, + "task_status_after_claim": afterClaim.Status, + "task_claimable_after_claim": afterClaim.Claimable, + "app_task_icon_key": beforeClaim.IconKey, + "app_task_icon_url": beforeClaim.IconURL, + "app_task_action_type": beforeClaim.ActionType, + "app_task_action_param": beforeClaim.ActionParam, + "app_task_action_payload_entry": beforeClaim.ActionPayload["entry"], + } + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + if err := encoder.Encode(result); err != nil { + panic(err) + } +} + +func adminLogin(ctx context.Context) (string, error) { + var envelope struct { + Data struct { + AccessToken string `json:"accessToken"` + } `json:"data"` + } + if err := postJSON(ctx, adminBaseURL+"/api/v1/auth/login", "", map[string]any{ + "username": "admin", + "password": "admin123", + }, &envelope); err != nil { + return "", err + } + if envelope.Data.AccessToken == "" { + return "", errors.New("admin login returned empty accessToken") + } + return envelope.Data.AccessToken, nil +} + +type taskConfig struct { + TaskID string `json:"task_id"` + IconKey string `json:"icon_key"` + IconURL string `json:"icon_url"` + ActionType string `json:"action_type"` +} + +func createTask(ctx context.Context, adminToken string, suffix string) (taskConfig, error) { + var envelope struct { + Data taskConfig `json:"data"` + } + err := postJSON(ctx, adminBaseURL+"/api/v1/admin/activity/daily-tasks", adminToken, map[string]any{ + "task_type": "daily", + "category": "smoke", + "metric_type": "recharge_coin", + "title": "Codex真实链路Smoke-" + suffix, + "description": "本地真实接口任务链路验证", + "audience_type": "all", + "icon_key": "coin", + "icon_url": "https://media.haiyihy.com/admin/task-icon-coin.png", + "action_type": "wallet", + "action_param": "wallet", + "action_payload_json": `{"entry":"wallet"}`, + "dimension_filter_json": `{}`, + "target_value": 100, + "target_unit": "coin", + "reward_coin_amount": 20, + "status": "active", + "sort_order": 1, + "effective_from_ms": 0, + "effective_to_ms": 0, + }, &envelope) + if err != nil { + return taskConfig{}, err + } + if envelope.Data.TaskID == "" || envelope.Data.IconKey != "coin" || envelope.Data.ActionType != "wallet" { + return taskConfig{}, fmt.Errorf("admin task response missing metadata: %+v", envelope.Data) + } + return envelope.Data, nil +} + +type appUser struct { + UserID string + DisplayUserID string + AccessToken string +} + +func quickCreateUser(ctx context.Context, suffix string) (appUser, error) { + var envelope struct { + Data struct { + Token struct { + UserID string `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + AccessToken string `json:"access_token"` + } `json:"token"` + } `json:"data"` + } + if err := postJSON(ctx, gatewayBaseURL+"/api/v1/auth/account/quick-create", "", map[string]any{ + "password": "TaskFlow@123456", + "device_id": "codex-task-smoke-" + suffix, + "platform": "ios", + "language": "zh-CN", + "timezone": "Asia/Shanghai", + }, &envelope); err != nil { + return appUser{}, err + } + user := appUser{ + UserID: envelope.Data.Token.UserID, + DisplayUserID: envelope.Data.Token.DisplayUserID, + AccessToken: envelope.Data.Token.AccessToken, + } + if user.UserID == "" || user.AccessToken == "" { + return appUser{}, fmt.Errorf("quick create returned incomplete token: %+v", user) + } + return user, nil +} + +type imUserSig struct { + SDKAppID int64 `json:"sdk_app_id"` + UserID string `json:"user_id"` + UserSig string `json:"user_sig"` +} + +func getIMUserSig(ctx context.Context, accessToken string) (imUserSig, error) { + var envelope struct { + Data imUserSig `json:"data"` + } + if err := getJSON(ctx, gatewayBaseURL+"/api/v1/im/usersig", accessToken, &envelope); err != nil { + return imUserSig{}, err + } + if envelope.Data.SDKAppID == 0 || envelope.Data.UserID == "" || envelope.Data.UserSig == "" { + return imUserSig{}, fmt.Errorf("im usersig response incomplete: %+v", envelope.Data) + } + return envelope.Data, nil +} + +type taskEventResult struct { + Status string + MatchedTaskCount int32 +} + +func consumeTaskEvent(ctx context.Context, taskID string, userID string, suffix string) (taskEventResult, error) { + conn, err := grpc.NewClient(activityGRPCURL, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return taskEventResult{}, err + } + defer conn.Close() + resp, err := activityv1.NewTaskServiceClient(conn).ConsumeTaskEvent(ctx, &activityv1.ConsumeTaskEventRequest{ + Meta: &activityv1.RequestMeta{ + RequestId: "task-smoke-event-" + suffix, + Caller: "task-system-smoke", + AppCode: appCode, + SentAtMs: time.Now().UTC().UnixMilli(), + }, + EventId: "task-smoke-event-" + taskID + "-" + suffix, + EventType: "WalletRechargeRecorded", + SourceService: "wallet-service", + UserId: mustParseUserID(userID), + MetricType: "recharge_coin", + Value: 100, + OccurredAtMs: time.Now().UTC().UnixMilli(), + DimensionsJson: `{"source":"task-system-smoke"}`, + }) + if err != nil { + return taskEventResult{}, err + } + return taskEventResult{Status: resp.GetStatus(), MatchedTaskCount: resp.GetMatchedTaskCount()}, nil +} + +type taskItem struct { + TaskID string `json:"task_id"` + TaskType string `json:"task_type"` + TaskDay string `json:"task_day"` + Status string `json:"status"` + ProgressValue int64 `json:"progress_value"` + Claimable bool `json:"claimable"` + IconKey string `json:"icon_key"` + IconURL string `json:"icon_url"` + ActionType string `json:"action_type"` + ActionParam string `json:"action_param"` + ActionPayload map[string]any `json:"action_payload"` +} + +func getTaskItem(ctx context.Context, accessToken string, taskID string) (taskItem, error) { + var envelope struct { + Data struct { + Sections []struct { + Items []taskItem `json:"items"` + } `json:"sections"` + } `json:"data"` + } + if err := getJSON(ctx, gatewayBaseURL+"/api/v1/tasks/tabs", accessToken, &envelope); err != nil { + return taskItem{}, err + } + for _, section := range envelope.Data.Sections { + for _, item := range section.Items { + if item.TaskID == taskID { + if item.ActionPayload == nil { + item.ActionPayload = map[string]any{} + } + return item, nil + } + } + } + return taskItem{}, fmt.Errorf("task %s not found in tabs response", taskID) +} + +type claimResult struct { + Status string `json:"status"` + WalletTransactionID string `json:"wallet_transaction_id"` + RewardCoinAmount int64 `json:"reward_coin_amount"` +} + +func claimTask(ctx context.Context, accessToken string, item taskItem, suffix string) (claimResult, error) { + var envelope struct { + Data claimResult `json:"data"` + } + if err := postJSON(ctx, gatewayBaseURL+"/api/v1/tasks/claim", accessToken, map[string]any{ + "task_id": item.TaskID, + "task_type": item.TaskType, + "task_day": item.TaskDay, + "command_id": "cmd-task-smoke-claim-" + suffix, + }, &envelope); err != nil { + return claimResult{}, err + } + if envelope.Data.WalletTransactionID == "" { + return claimResult{}, fmt.Errorf("claim returned empty wallet transaction: %+v", envelope.Data) + } + return envelope.Data, nil +} + +func getJSON(ctx context.Context, url string, bearer string, out any) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return err + } + return doJSON(req, bearer, out) +} + +func postJSON(ctx context.Context, url string, bearer string, payload any, out any) error { + body, err := json.Marshal(payload) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + return doJSON(req, bearer, out) +} + +func doJSON(req *http.Request, bearer string, out any) error { + req.Header.Set("X-App-Code", appCode) + req.Header.Set("X-Request-ID", "task-smoke-"+fmt.Sprintf("%d", time.Now().UnixNano())) + if bearer != "" { + req.Header.Set("Authorization", "Bearer "+bearer) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("%s %s failed: status=%d body=%s", req.Method, req.URL.Path, resp.StatusCode, string(data)) + } + if err := json.Unmarshal(data, out); err != nil { + return fmt.Errorf("decode %s %s failed: %w body=%s", req.Method, req.URL.Path, err, string(data)) + } + return nil +} + +func mustParseUserID(value string) int64 { + var userID int64 + if _, err := fmt.Sscanf(strings.TrimSpace(value), "%d", &userID); err != nil || userID <= 0 { + panic(fmt.Errorf("invalid user_id %q", value)) + } + return userID +} + +func must[T any](value T, err error) T { + if err != nil { + panic(err) + } + return value +} From 6fc9499a4ba4e4cb78a3293ca8a977791c876eff Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 16:54:55 +0800 Subject: [PATCH 18/28] fix room rocket top1 contribution tracking --- api/proto/room/v1/room.pb.go | 1522 +++++++++-------- api/proto/room/v1/room.proto | 8 + .../internal/room/command/command.go | 12 + .../internal/room/service/recovery.go | 61 +- .../internal/room/service/room_rocket.go | 162 +- .../internal/room/service/room_rocket_test.go | 68 + .../room-service/internal/room/state/state.go | 88 +- 7 files changed, 1116 insertions(+), 805 deletions(-) diff --git a/api/proto/room/v1/room.pb.go b/api/proto/room/v1/room.pb.go index 57a067b2..da656982 100644 --- a/api/proto/room/v1/room.pb.go +++ b/api/proto/room/v1/room.pb.go @@ -1053,6 +1053,67 @@ func (x *RoomRocketRewardGrant) GetStatus() string { return "" } +// RoomRocketContribution 是当前火箭等级内的实际加燃料贡献累计。 +type RoomRocketContribution struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Fuel int64 `protobuf:"varint,2,opt,name=fuel,proto3" json:"fuel,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,3,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomRocketContribution) Reset() { + *x = RoomRocketContribution{} + mi := &file_proto_room_v1_room_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomRocketContribution) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomRocketContribution) ProtoMessage() {} + +func (x *RoomRocketContribution) ProtoReflect() protoreflect.Message { + mi := &file_proto_room_v1_room_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomRocketContribution.ProtoReflect.Descriptor instead. +func (*RoomRocketContribution) Descriptor() ([]byte, []int) { + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{10} +} + +func (x *RoomRocketContribution) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *RoomRocketContribution) GetFuel() int64 { + if x != nil { + return x.Fuel + } + return 0 +} + +func (x *RoomRocketContribution) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + // RoomRocketPendingLaunch 是已经点火、等待延迟发射的单枚火箭。 type RoomRocketPendingLaunch struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1072,7 +1133,7 @@ type RoomRocketPendingLaunch struct { func (x *RoomRocketPendingLaunch) Reset() { *x = RoomRocketPendingLaunch{} - mi := &file_proto_room_v1_room_proto_msgTypes[10] + mi := &file_proto_room_v1_room_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1084,7 +1145,7 @@ func (x *RoomRocketPendingLaunch) String() string { func (*RoomRocketPendingLaunch) ProtoMessage() {} func (x *RoomRocketPendingLaunch) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[10] + mi := &file_proto_room_v1_room_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1097,7 +1158,7 @@ func (x *RoomRocketPendingLaunch) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketPendingLaunch.ProtoReflect.Descriptor instead. func (*RoomRocketPendingLaunch) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{10} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{11} } func (x *RoomRocketPendingLaunch) GetRocketId() string { @@ -1172,28 +1233,29 @@ func (x *RoomRocketPendingLaunch) GetCoverUrl() string { // RoomRocketState 是 Room Cell 持有并随快照恢复的当前火箭状态。 type RoomRocketState struct { - state protoimpl.MessageState `protogen:"open.v1"` - CurrentLevel int32 `protobuf:"varint,1,opt,name=current_level,json=currentLevel,proto3" json:"current_level,omitempty"` - CurrentFuel int64 `protobuf:"varint,2,opt,name=current_fuel,json=currentFuel,proto3" json:"current_fuel,omitempty"` - FuelThreshold int64 `protobuf:"varint,3,opt,name=fuel_threshold,json=fuelThreshold,proto3" json:"fuel_threshold,omitempty"` - Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` - IgnitedAtMs int64 `protobuf:"varint,5,opt,name=ignited_at_ms,json=ignitedAtMs,proto3" json:"ignited_at_ms,omitempty"` - LaunchAtMs int64 `protobuf:"varint,6,opt,name=launch_at_ms,json=launchAtMs,proto3" json:"launch_at_ms,omitempty"` - LaunchedAtMs int64 `protobuf:"varint,7,opt,name=launched_at_ms,json=launchedAtMs,proto3" json:"launched_at_ms,omitempty"` - ResetAtMs int64 `protobuf:"varint,8,opt,name=reset_at_ms,json=resetAtMs,proto3" json:"reset_at_ms,omitempty"` - Top1UserId int64 `protobuf:"varint,9,opt,name=top1_user_id,json=top1UserId,proto3" json:"top1_user_id,omitempty"` - IgniterUserId int64 `protobuf:"varint,10,opt,name=igniter_user_id,json=igniterUserId,proto3" json:"igniter_user_id,omitempty"` - RocketId string `protobuf:"bytes,11,opt,name=rocket_id,json=rocketId,proto3" json:"rocket_id,omitempty"` - ConfigVersion int64 `protobuf:"varint,12,opt,name=config_version,json=configVersion,proto3" json:"config_version,omitempty"` - LastRewards []*RoomRocketRewardGrant `protobuf:"bytes,13,rep,name=last_rewards,json=lastRewards,proto3" json:"last_rewards,omitempty"` - PendingLaunches []*RoomRocketPendingLaunch `protobuf:"bytes,14,rep,name=pending_launches,json=pendingLaunches,proto3" json:"pending_launches,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + CurrentLevel int32 `protobuf:"varint,1,opt,name=current_level,json=currentLevel,proto3" json:"current_level,omitempty"` + CurrentFuel int64 `protobuf:"varint,2,opt,name=current_fuel,json=currentFuel,proto3" json:"current_fuel,omitempty"` + FuelThreshold int64 `protobuf:"varint,3,opt,name=fuel_threshold,json=fuelThreshold,proto3" json:"fuel_threshold,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + IgnitedAtMs int64 `protobuf:"varint,5,opt,name=ignited_at_ms,json=ignitedAtMs,proto3" json:"ignited_at_ms,omitempty"` + LaunchAtMs int64 `protobuf:"varint,6,opt,name=launch_at_ms,json=launchAtMs,proto3" json:"launch_at_ms,omitempty"` + LaunchedAtMs int64 `protobuf:"varint,7,opt,name=launched_at_ms,json=launchedAtMs,proto3" json:"launched_at_ms,omitempty"` + ResetAtMs int64 `protobuf:"varint,8,opt,name=reset_at_ms,json=resetAtMs,proto3" json:"reset_at_ms,omitempty"` + Top1UserId int64 `protobuf:"varint,9,opt,name=top1_user_id,json=top1UserId,proto3" json:"top1_user_id,omitempty"` + IgniterUserId int64 `protobuf:"varint,10,opt,name=igniter_user_id,json=igniterUserId,proto3" json:"igniter_user_id,omitempty"` + RocketId string `protobuf:"bytes,11,opt,name=rocket_id,json=rocketId,proto3" json:"rocket_id,omitempty"` + ConfigVersion int64 `protobuf:"varint,12,opt,name=config_version,json=configVersion,proto3" json:"config_version,omitempty"` + LastRewards []*RoomRocketRewardGrant `protobuf:"bytes,13,rep,name=last_rewards,json=lastRewards,proto3" json:"last_rewards,omitempty"` + PendingLaunches []*RoomRocketPendingLaunch `protobuf:"bytes,14,rep,name=pending_launches,json=pendingLaunches,proto3" json:"pending_launches,omitempty"` + CurrentContributions []*RoomRocketContribution `protobuf:"bytes,15,rep,name=current_contributions,json=currentContributions,proto3" json:"current_contributions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RoomRocketState) Reset() { *x = RoomRocketState{} - mi := &file_proto_room_v1_room_proto_msgTypes[11] + mi := &file_proto_room_v1_room_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1205,7 +1267,7 @@ func (x *RoomRocketState) String() string { func (*RoomRocketState) ProtoMessage() {} func (x *RoomRocketState) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[11] + mi := &file_proto_room_v1_room_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1218,7 +1280,7 @@ func (x *RoomRocketState) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketState.ProtoReflect.Descriptor instead. func (*RoomRocketState) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{11} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{12} } func (x *RoomRocketState) GetCurrentLevel() int32 { @@ -1319,6 +1381,13 @@ func (x *RoomRocketState) GetPendingLaunches() []*RoomRocketPendingLaunch { return nil } +func (x *RoomRocketState) GetCurrentContributions() []*RoomRocketContribution { + if x != nil { + return x.CurrentContributions + } + return nil +} + // RoomRocketInfo 是 App 初始化火箭 UI 的完整读模型。 type RoomRocketInfo struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1336,7 +1405,7 @@ type RoomRocketInfo struct { func (x *RoomRocketInfo) Reset() { *x = RoomRocketInfo{} - mi := &file_proto_room_v1_room_proto_msgTypes[12] + mi := &file_proto_room_v1_room_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1348,7 +1417,7 @@ func (x *RoomRocketInfo) String() string { func (*RoomRocketInfo) ProtoMessage() {} func (x *RoomRocketInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[12] + mi := &file_proto_room_v1_room_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1361,7 +1430,7 @@ func (x *RoomRocketInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketInfo.ProtoReflect.Descriptor instead. func (*RoomRocketInfo) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{12} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{13} } func (x *RoomRocketInfo) GetEnabled() bool { @@ -1435,7 +1504,7 @@ type RoomRocketGiftFuelRule struct { func (x *RoomRocketGiftFuelRule) Reset() { *x = RoomRocketGiftFuelRule{} - mi := &file_proto_room_v1_room_proto_msgTypes[13] + mi := &file_proto_room_v1_room_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1447,7 +1516,7 @@ func (x *RoomRocketGiftFuelRule) String() string { func (*RoomRocketGiftFuelRule) ProtoMessage() {} func (x *RoomRocketGiftFuelRule) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[13] + mi := &file_proto_room_v1_room_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1460,7 +1529,7 @@ func (x *RoomRocketGiftFuelRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRocketGiftFuelRule.ProtoReflect.Descriptor instead. func (*RoomRocketGiftFuelRule) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{13} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{14} } func (x *RoomRocketGiftFuelRule) GetRuleId() string { @@ -1528,7 +1597,7 @@ type AdminRoomRocketConfig struct { func (x *AdminRoomRocketConfig) Reset() { *x = AdminRoomRocketConfig{} - mi := &file_proto_room_v1_room_proto_msgTypes[14] + mi := &file_proto_room_v1_room_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1540,7 +1609,7 @@ func (x *AdminRoomRocketConfig) String() string { func (*AdminRoomRocketConfig) ProtoMessage() {} func (x *AdminRoomRocketConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[14] + mi := &file_proto_room_v1_room_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1553,7 +1622,7 @@ func (x *AdminRoomRocketConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminRoomRocketConfig.ProtoReflect.Descriptor instead. func (*AdminRoomRocketConfig) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{14} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{15} } func (x *AdminRoomRocketConfig) GetAppCode() string { @@ -1663,7 +1732,7 @@ type AdminGetRoomRocketConfigRequest struct { func (x *AdminGetRoomRocketConfigRequest) Reset() { *x = AdminGetRoomRocketConfigRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[15] + mi := &file_proto_room_v1_room_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1675,7 +1744,7 @@ func (x *AdminGetRoomRocketConfigRequest) String() string { func (*AdminGetRoomRocketConfigRequest) ProtoMessage() {} func (x *AdminGetRoomRocketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[15] + mi := &file_proto_room_v1_room_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1688,7 +1757,7 @@ func (x *AdminGetRoomRocketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGetRoomRocketConfigRequest.ProtoReflect.Descriptor instead. func (*AdminGetRoomRocketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{15} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{16} } func (x *AdminGetRoomRocketConfigRequest) GetMeta() *RequestMeta { @@ -1708,7 +1777,7 @@ type AdminGetRoomRocketConfigResponse struct { func (x *AdminGetRoomRocketConfigResponse) Reset() { *x = AdminGetRoomRocketConfigResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[16] + mi := &file_proto_room_v1_room_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1720,7 +1789,7 @@ func (x *AdminGetRoomRocketConfigResponse) String() string { func (*AdminGetRoomRocketConfigResponse) ProtoMessage() {} func (x *AdminGetRoomRocketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[16] + mi := &file_proto_room_v1_room_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1733,7 +1802,7 @@ func (x *AdminGetRoomRocketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGetRoomRocketConfigResponse.ProtoReflect.Descriptor instead. func (*AdminGetRoomRocketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{16} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{17} } func (x *AdminGetRoomRocketConfigResponse) GetConfig() *AdminRoomRocketConfig { @@ -1761,7 +1830,7 @@ type AdminUpdateRoomRocketConfigRequest struct { func (x *AdminUpdateRoomRocketConfigRequest) Reset() { *x = AdminUpdateRoomRocketConfigRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[17] + mi := &file_proto_room_v1_room_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1773,7 +1842,7 @@ func (x *AdminUpdateRoomRocketConfigRequest) String() string { func (*AdminUpdateRoomRocketConfigRequest) ProtoMessage() {} func (x *AdminUpdateRoomRocketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[17] + mi := &file_proto_room_v1_room_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1786,7 +1855,7 @@ func (x *AdminUpdateRoomRocketConfigRequest) ProtoReflect() protoreflect.Message // Deprecated: Use AdminUpdateRoomRocketConfigRequest.ProtoReflect.Descriptor instead. func (*AdminUpdateRoomRocketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{17} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{18} } func (x *AdminUpdateRoomRocketConfigRequest) GetMeta() *RequestMeta { @@ -1820,7 +1889,7 @@ type AdminUpdateRoomRocketConfigResponse struct { func (x *AdminUpdateRoomRocketConfigResponse) Reset() { *x = AdminUpdateRoomRocketConfigResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[18] + mi := &file_proto_room_v1_room_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1832,7 +1901,7 @@ func (x *AdminUpdateRoomRocketConfigResponse) String() string { func (*AdminUpdateRoomRocketConfigResponse) ProtoMessage() {} func (x *AdminUpdateRoomRocketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[18] + mi := &file_proto_room_v1_room_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1845,7 +1914,7 @@ func (x *AdminUpdateRoomRocketConfigResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use AdminUpdateRoomRocketConfigResponse.ProtoReflect.Descriptor instead. func (*AdminUpdateRoomRocketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{18} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{19} } func (x *AdminUpdateRoomRocketConfigResponse) GetConfig() *AdminRoomRocketConfig { @@ -1874,7 +1943,7 @@ type AdminRoomSeatConfig struct { func (x *AdminRoomSeatConfig) Reset() { *x = AdminRoomSeatConfig{} - mi := &file_proto_room_v1_room_proto_msgTypes[19] + mi := &file_proto_room_v1_room_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1886,7 +1955,7 @@ func (x *AdminRoomSeatConfig) String() string { func (*AdminRoomSeatConfig) ProtoMessage() {} func (x *AdminRoomSeatConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[19] + mi := &file_proto_room_v1_room_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1899,7 +1968,7 @@ func (x *AdminRoomSeatConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminRoomSeatConfig.ProtoReflect.Descriptor instead. func (*AdminRoomSeatConfig) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{19} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{20} } func (x *AdminRoomSeatConfig) GetCandidateSeatCounts() []int32 { @@ -1939,7 +2008,7 @@ type AdminGetRoomSeatConfigRequest struct { func (x *AdminGetRoomSeatConfigRequest) Reset() { *x = AdminGetRoomSeatConfigRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[20] + mi := &file_proto_room_v1_room_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1951,7 +2020,7 @@ func (x *AdminGetRoomSeatConfigRequest) String() string { func (*AdminGetRoomSeatConfigRequest) ProtoMessage() {} func (x *AdminGetRoomSeatConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[20] + mi := &file_proto_room_v1_room_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1964,7 +2033,7 @@ func (x *AdminGetRoomSeatConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGetRoomSeatConfigRequest.ProtoReflect.Descriptor instead. func (*AdminGetRoomSeatConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{20} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{21} } func (x *AdminGetRoomSeatConfigRequest) GetMeta() *RequestMeta { @@ -1984,7 +2053,7 @@ type AdminGetRoomSeatConfigResponse struct { func (x *AdminGetRoomSeatConfigResponse) Reset() { *x = AdminGetRoomSeatConfigResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[21] + mi := &file_proto_room_v1_room_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1996,7 +2065,7 @@ func (x *AdminGetRoomSeatConfigResponse) String() string { func (*AdminGetRoomSeatConfigResponse) ProtoMessage() {} func (x *AdminGetRoomSeatConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[21] + mi := &file_proto_room_v1_room_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2009,7 +2078,7 @@ func (x *AdminGetRoomSeatConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGetRoomSeatConfigResponse.ProtoReflect.Descriptor instead. func (*AdminGetRoomSeatConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{21} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{22} } func (x *AdminGetRoomSeatConfigResponse) GetConfig() *AdminRoomSeatConfig { @@ -2037,7 +2106,7 @@ type AdminUpdateRoomSeatConfigRequest struct { func (x *AdminUpdateRoomSeatConfigRequest) Reset() { *x = AdminUpdateRoomSeatConfigRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[22] + mi := &file_proto_room_v1_room_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2049,7 +2118,7 @@ func (x *AdminUpdateRoomSeatConfigRequest) String() string { func (*AdminUpdateRoomSeatConfigRequest) ProtoMessage() {} func (x *AdminUpdateRoomSeatConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[22] + mi := &file_proto_room_v1_room_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2062,7 +2131,7 @@ func (x *AdminUpdateRoomSeatConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminUpdateRoomSeatConfigRequest.ProtoReflect.Descriptor instead. func (*AdminUpdateRoomSeatConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{22} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{23} } func (x *AdminUpdateRoomSeatConfigRequest) GetMeta() *RequestMeta { @@ -2096,7 +2165,7 @@ type AdminUpdateRoomSeatConfigResponse struct { func (x *AdminUpdateRoomSeatConfigResponse) Reset() { *x = AdminUpdateRoomSeatConfigResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[23] + mi := &file_proto_room_v1_room_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2108,7 +2177,7 @@ func (x *AdminUpdateRoomSeatConfigResponse) String() string { func (*AdminUpdateRoomSeatConfigResponse) ProtoMessage() {} func (x *AdminUpdateRoomSeatConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[23] + mi := &file_proto_room_v1_room_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2121,7 +2190,7 @@ func (x *AdminUpdateRoomSeatConfigResponse) ProtoReflect() protoreflect.Message // Deprecated: Use AdminUpdateRoomSeatConfigResponse.ProtoReflect.Descriptor instead. func (*AdminUpdateRoomSeatConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{23} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{24} } func (x *AdminUpdateRoomSeatConfigResponse) GetConfig() *AdminRoomSeatConfig { @@ -2153,7 +2222,7 @@ type AdminRoomPinRoom struct { func (x *AdminRoomPinRoom) Reset() { *x = AdminRoomPinRoom{} - mi := &file_proto_room_v1_room_proto_msgTypes[24] + mi := &file_proto_room_v1_room_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2165,7 +2234,7 @@ func (x *AdminRoomPinRoom) String() string { func (*AdminRoomPinRoom) ProtoMessage() {} func (x *AdminRoomPinRoom) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[24] + mi := &file_proto_room_v1_room_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2178,7 +2247,7 @@ func (x *AdminRoomPinRoom) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminRoomPinRoom.ProtoReflect.Descriptor instead. func (*AdminRoomPinRoom) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{24} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{25} } func (x *AdminRoomPinRoom) GetRoomId() string { @@ -2252,7 +2321,7 @@ type AdminRoomPin struct { func (x *AdminRoomPin) Reset() { *x = AdminRoomPin{} - mi := &file_proto_room_v1_room_proto_msgTypes[25] + mi := &file_proto_room_v1_room_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2264,7 +2333,7 @@ func (x *AdminRoomPin) String() string { func (*AdminRoomPin) ProtoMessage() {} func (x *AdminRoomPin) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[25] + mi := &file_proto_room_v1_room_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2277,7 +2346,7 @@ func (x *AdminRoomPin) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminRoomPin.ProtoReflect.Descriptor instead. func (*AdminRoomPin) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{25} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{26} } func (x *AdminRoomPin) GetId() int64 { @@ -2393,7 +2462,7 @@ type AdminListRoomPinsRequest struct { func (x *AdminListRoomPinsRequest) Reset() { *x = AdminListRoomPinsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[26] + mi := &file_proto_room_v1_room_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2405,7 +2474,7 @@ func (x *AdminListRoomPinsRequest) String() string { func (*AdminListRoomPinsRequest) ProtoMessage() {} func (x *AdminListRoomPinsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[26] + mi := &file_proto_room_v1_room_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2418,7 +2487,7 @@ func (x *AdminListRoomPinsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminListRoomPinsRequest.ProtoReflect.Descriptor instead. func (*AdminListRoomPinsRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{26} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{27} } func (x *AdminListRoomPinsRequest) GetMeta() *RequestMeta { @@ -2481,7 +2550,7 @@ type AdminListRoomPinsResponse struct { func (x *AdminListRoomPinsResponse) Reset() { *x = AdminListRoomPinsResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[27] + mi := &file_proto_room_v1_room_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2493,7 +2562,7 @@ func (x *AdminListRoomPinsResponse) String() string { func (*AdminListRoomPinsResponse) ProtoMessage() {} func (x *AdminListRoomPinsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[27] + mi := &file_proto_room_v1_room_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2506,7 +2575,7 @@ func (x *AdminListRoomPinsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminListRoomPinsResponse.ProtoReflect.Descriptor instead. func (*AdminListRoomPinsResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{27} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{28} } func (x *AdminListRoomPinsResponse) GetPins() []*AdminRoomPin { @@ -2546,7 +2615,7 @@ type AdminCreateRoomPinRequest struct { func (x *AdminCreateRoomPinRequest) Reset() { *x = AdminCreateRoomPinRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[28] + mi := &file_proto_room_v1_room_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2558,7 +2627,7 @@ func (x *AdminCreateRoomPinRequest) String() string { func (*AdminCreateRoomPinRequest) ProtoMessage() {} func (x *AdminCreateRoomPinRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[28] + mi := &file_proto_room_v1_room_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2571,7 +2640,7 @@ func (x *AdminCreateRoomPinRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminCreateRoomPinRequest.ProtoReflect.Descriptor instead. func (*AdminCreateRoomPinRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{28} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{29} } func (x *AdminCreateRoomPinRequest) GetMeta() *RequestMeta { @@ -2640,7 +2709,7 @@ type AdminCreateRoomPinResponse struct { func (x *AdminCreateRoomPinResponse) Reset() { *x = AdminCreateRoomPinResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[29] + mi := &file_proto_room_v1_room_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2652,7 +2721,7 @@ func (x *AdminCreateRoomPinResponse) String() string { func (*AdminCreateRoomPinResponse) ProtoMessage() {} func (x *AdminCreateRoomPinResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[29] + mi := &file_proto_room_v1_room_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2665,7 +2734,7 @@ func (x *AdminCreateRoomPinResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminCreateRoomPinResponse.ProtoReflect.Descriptor instead. func (*AdminCreateRoomPinResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{29} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{30} } func (x *AdminCreateRoomPinResponse) GetPin() *AdminRoomPin { @@ -2693,7 +2762,7 @@ type AdminCancelRoomPinRequest struct { func (x *AdminCancelRoomPinRequest) Reset() { *x = AdminCancelRoomPinRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[30] + mi := &file_proto_room_v1_room_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2705,7 +2774,7 @@ func (x *AdminCancelRoomPinRequest) String() string { func (*AdminCancelRoomPinRequest) ProtoMessage() {} func (x *AdminCancelRoomPinRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[30] + mi := &file_proto_room_v1_room_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2718,7 +2787,7 @@ func (x *AdminCancelRoomPinRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminCancelRoomPinRequest.ProtoReflect.Descriptor instead. func (*AdminCancelRoomPinRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{30} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{31} } func (x *AdminCancelRoomPinRequest) GetMeta() *RequestMeta { @@ -2752,7 +2821,7 @@ type AdminCancelRoomPinResponse struct { func (x *AdminCancelRoomPinResponse) Reset() { *x = AdminCancelRoomPinResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[31] + mi := &file_proto_room_v1_room_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2764,7 +2833,7 @@ func (x *AdminCancelRoomPinResponse) String() string { func (*AdminCancelRoomPinResponse) ProtoMessage() {} func (x *AdminCancelRoomPinResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[31] + mi := &file_proto_room_v1_room_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2777,7 +2846,7 @@ func (x *AdminCancelRoomPinResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminCancelRoomPinResponse.ProtoReflect.Descriptor instead. func (*AdminCancelRoomPinResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{31} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{32} } func (x *AdminCancelRoomPinResponse) GetPin() *AdminRoomPin { @@ -2808,7 +2877,7 @@ type RoomBanState struct { func (x *RoomBanState) Reset() { *x = RoomBanState{} - mi := &file_proto_room_v1_room_proto_msgTypes[32] + mi := &file_proto_room_v1_room_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2820,7 +2889,7 @@ func (x *RoomBanState) String() string { func (*RoomBanState) ProtoMessage() {} func (x *RoomBanState) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[32] + mi := &file_proto_room_v1_room_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2833,7 +2902,7 @@ func (x *RoomBanState) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomBanState.ProtoReflect.Descriptor instead. func (*RoomBanState) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{32} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{33} } func (x *RoomBanState) GetUserId() int64 { @@ -2896,7 +2965,7 @@ type RoomSnapshot struct { func (x *RoomSnapshot) Reset() { *x = RoomSnapshot{} - mi := &file_proto_room_v1_room_proto_msgTypes[33] + mi := &file_proto_room_v1_room_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2908,7 +2977,7 @@ func (x *RoomSnapshot) String() string { func (*RoomSnapshot) ProtoMessage() {} func (x *RoomSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[33] + mi := &file_proto_room_v1_room_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2921,7 +2990,7 @@ func (x *RoomSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomSnapshot.ProtoReflect.Descriptor instead. func (*RoomSnapshot) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{33} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{34} } func (x *RoomSnapshot) GetRoomId() string { @@ -3080,7 +3149,7 @@ type RoomBackgroundImage struct { func (x *RoomBackgroundImage) Reset() { *x = RoomBackgroundImage{} - mi := &file_proto_room_v1_room_proto_msgTypes[34] + mi := &file_proto_room_v1_room_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3092,7 +3161,7 @@ func (x *RoomBackgroundImage) String() string { func (*RoomBackgroundImage) ProtoMessage() {} func (x *RoomBackgroundImage) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[34] + mi := &file_proto_room_v1_room_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3105,7 +3174,7 @@ func (x *RoomBackgroundImage) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomBackgroundImage.ProtoReflect.Descriptor instead. func (*RoomBackgroundImage) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{34} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{35} } func (x *RoomBackgroundImage) GetBackgroundId() int64 { @@ -3161,7 +3230,7 @@ type SaveRoomBackgroundRequest struct { func (x *SaveRoomBackgroundRequest) Reset() { *x = SaveRoomBackgroundRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[35] + mi := &file_proto_room_v1_room_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3173,7 +3242,7 @@ func (x *SaveRoomBackgroundRequest) String() string { func (*SaveRoomBackgroundRequest) ProtoMessage() {} func (x *SaveRoomBackgroundRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[35] + mi := &file_proto_room_v1_room_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3186,7 +3255,7 @@ func (x *SaveRoomBackgroundRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SaveRoomBackgroundRequest.ProtoReflect.Descriptor instead. func (*SaveRoomBackgroundRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{35} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{36} } func (x *SaveRoomBackgroundRequest) GetMeta() *RequestMeta { @@ -3220,7 +3289,7 @@ type SaveRoomBackgroundResponse struct { func (x *SaveRoomBackgroundResponse) Reset() { *x = SaveRoomBackgroundResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[36] + mi := &file_proto_room_v1_room_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3232,7 +3301,7 @@ func (x *SaveRoomBackgroundResponse) String() string { func (*SaveRoomBackgroundResponse) ProtoMessage() {} func (x *SaveRoomBackgroundResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[36] + mi := &file_proto_room_v1_room_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3245,7 +3314,7 @@ func (x *SaveRoomBackgroundResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SaveRoomBackgroundResponse.ProtoReflect.Descriptor instead. func (*SaveRoomBackgroundResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{36} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{37} } func (x *SaveRoomBackgroundResponse) GetBackground() *RoomBackgroundImage { @@ -3273,7 +3342,7 @@ type ListRoomBackgroundsRequest struct { func (x *ListRoomBackgroundsRequest) Reset() { *x = ListRoomBackgroundsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[37] + mi := &file_proto_room_v1_room_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3285,7 +3354,7 @@ func (x *ListRoomBackgroundsRequest) String() string { func (*ListRoomBackgroundsRequest) ProtoMessage() {} func (x *ListRoomBackgroundsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[37] + mi := &file_proto_room_v1_room_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3298,7 +3367,7 @@ func (x *ListRoomBackgroundsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomBackgroundsRequest.ProtoReflect.Descriptor instead. func (*ListRoomBackgroundsRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{37} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{38} } func (x *ListRoomBackgroundsRequest) GetMeta() *RequestMeta { @@ -3333,7 +3402,7 @@ type ListRoomBackgroundsResponse struct { func (x *ListRoomBackgroundsResponse) Reset() { *x = ListRoomBackgroundsResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[38] + mi := &file_proto_room_v1_room_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3345,7 +3414,7 @@ func (x *ListRoomBackgroundsResponse) String() string { func (*ListRoomBackgroundsResponse) ProtoMessage() {} func (x *ListRoomBackgroundsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[38] + mi := &file_proto_room_v1_room_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3358,7 +3427,7 @@ func (x *ListRoomBackgroundsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomBackgroundsResponse.ProtoReflect.Descriptor instead. func (*ListRoomBackgroundsResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{38} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{39} } func (x *ListRoomBackgroundsResponse) GetBackgrounds() []*RoomBackgroundImage { @@ -3392,7 +3461,7 @@ type SetRoomBackgroundRequest struct { func (x *SetRoomBackgroundRequest) Reset() { *x = SetRoomBackgroundRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[39] + mi := &file_proto_room_v1_room_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3404,7 +3473,7 @@ func (x *SetRoomBackgroundRequest) String() string { func (*SetRoomBackgroundRequest) ProtoMessage() {} func (x *SetRoomBackgroundRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[39] + mi := &file_proto_room_v1_room_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3417,7 +3486,7 @@ func (x *SetRoomBackgroundRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomBackgroundRequest.ProtoReflect.Descriptor instead. func (*SetRoomBackgroundRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{39} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{40} } func (x *SetRoomBackgroundRequest) GetMeta() *RequestMeta { @@ -3445,7 +3514,7 @@ type SetRoomBackgroundResponse struct { func (x *SetRoomBackgroundResponse) Reset() { *x = SetRoomBackgroundResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[40] + mi := &file_proto_room_v1_room_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3457,7 +3526,7 @@ func (x *SetRoomBackgroundResponse) String() string { func (*SetRoomBackgroundResponse) ProtoMessage() {} func (x *SetRoomBackgroundResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[40] + mi := &file_proto_room_v1_room_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3470,7 +3539,7 @@ func (x *SetRoomBackgroundResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomBackgroundResponse.ProtoReflect.Descriptor instead. func (*SetRoomBackgroundResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{40} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{41} } func (x *SetRoomBackgroundResponse) GetResult() *CommandResult { @@ -3522,7 +3591,7 @@ type CreateRoomRequest struct { func (x *CreateRoomRequest) Reset() { *x = CreateRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[41] + mi := &file_proto_room_v1_room_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3534,7 +3603,7 @@ func (x *CreateRoomRequest) String() string { func (*CreateRoomRequest) ProtoMessage() {} func (x *CreateRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[41] + mi := &file_proto_room_v1_room_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3547,7 +3616,7 @@ func (x *CreateRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRoomRequest.ProtoReflect.Descriptor instead. func (*CreateRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{41} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{42} } func (x *CreateRoomRequest) GetMeta() *RequestMeta { @@ -3624,7 +3693,7 @@ type CreateRoomResponse struct { func (x *CreateRoomResponse) Reset() { *x = CreateRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[42] + mi := &file_proto_room_v1_room_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3636,7 +3705,7 @@ func (x *CreateRoomResponse) String() string { func (*CreateRoomResponse) ProtoMessage() {} func (x *CreateRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[42] + mi := &file_proto_room_v1_room_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3649,7 +3718,7 @@ func (x *CreateRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRoomResponse.ProtoReflect.Descriptor instead. func (*CreateRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{42} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{43} } func (x *CreateRoomResponse) GetResult() *CommandResult { @@ -3681,7 +3750,7 @@ type UpdateRoomProfileRequest struct { func (x *UpdateRoomProfileRequest) Reset() { *x = UpdateRoomProfileRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[43] + mi := &file_proto_room_v1_room_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3693,7 +3762,7 @@ func (x *UpdateRoomProfileRequest) String() string { func (*UpdateRoomProfileRequest) ProtoMessage() {} func (x *UpdateRoomProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[43] + mi := &file_proto_room_v1_room_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3706,7 +3775,7 @@ func (x *UpdateRoomProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRoomProfileRequest.ProtoReflect.Descriptor instead. func (*UpdateRoomProfileRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{43} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{44} } func (x *UpdateRoomProfileRequest) GetMeta() *RequestMeta { @@ -3755,7 +3824,7 @@ type UpdateRoomProfileResponse struct { func (x *UpdateRoomProfileResponse) Reset() { *x = UpdateRoomProfileResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[44] + mi := &file_proto_room_v1_room_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3767,7 +3836,7 @@ func (x *UpdateRoomProfileResponse) String() string { func (*UpdateRoomProfileResponse) ProtoMessage() {} func (x *UpdateRoomProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[44] + mi := &file_proto_room_v1_room_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3780,7 +3849,7 @@ func (x *UpdateRoomProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRoomProfileResponse.ProtoReflect.Descriptor instead. func (*UpdateRoomProfileResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{44} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{45} } func (x *UpdateRoomProfileResponse) GetResult() *CommandResult { @@ -3815,7 +3884,7 @@ type RoomEntryVehicleSnapshot struct { func (x *RoomEntryVehicleSnapshot) Reset() { *x = RoomEntryVehicleSnapshot{} - mi := &file_proto_room_v1_room_proto_msgTypes[45] + mi := &file_proto_room_v1_room_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3827,7 +3896,7 @@ func (x *RoomEntryVehicleSnapshot) String() string { func (*RoomEntryVehicleSnapshot) ProtoMessage() {} func (x *RoomEntryVehicleSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[45] + mi := &file_proto_room_v1_room_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3840,7 +3909,7 @@ func (x *RoomEntryVehicleSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomEntryVehicleSnapshot.ProtoReflect.Descriptor instead. func (*RoomEntryVehicleSnapshot) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{45} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{46} } func (x *RoomEntryVehicleSnapshot) GetResourceId() int64 { @@ -3921,7 +3990,7 @@ type JoinRoomRequest struct { func (x *JoinRoomRequest) Reset() { *x = JoinRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[46] + mi := &file_proto_room_v1_room_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3933,7 +4002,7 @@ func (x *JoinRoomRequest) String() string { func (*JoinRoomRequest) ProtoMessage() {} func (x *JoinRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[46] + mi := &file_proto_room_v1_room_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3946,7 +4015,7 @@ func (x *JoinRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinRoomRequest.ProtoReflect.Descriptor instead. func (*JoinRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{46} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{47} } func (x *JoinRoomRequest) GetMeta() *RequestMeta { @@ -4003,7 +4072,7 @@ type JoinRoomResponse struct { func (x *JoinRoomResponse) Reset() { *x = JoinRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[47] + mi := &file_proto_room_v1_room_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4015,7 +4084,7 @@ func (x *JoinRoomResponse) String() string { func (*JoinRoomResponse) ProtoMessage() {} func (x *JoinRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[47] + mi := &file_proto_room_v1_room_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4028,7 +4097,7 @@ func (x *JoinRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinRoomResponse.ProtoReflect.Descriptor instead. func (*JoinRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{47} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{48} } func (x *JoinRoomResponse) GetResult() *CommandResult { @@ -4063,7 +4132,7 @@ type RoomHeartbeatRequest struct { func (x *RoomHeartbeatRequest) Reset() { *x = RoomHeartbeatRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[48] + mi := &file_proto_room_v1_room_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4075,7 +4144,7 @@ func (x *RoomHeartbeatRequest) String() string { func (*RoomHeartbeatRequest) ProtoMessage() {} func (x *RoomHeartbeatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[48] + mi := &file_proto_room_v1_room_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4088,7 +4157,7 @@ func (x *RoomHeartbeatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomHeartbeatRequest.ProtoReflect.Descriptor instead. func (*RoomHeartbeatRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{48} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{49} } func (x *RoomHeartbeatRequest) GetMeta() *RequestMeta { @@ -4110,7 +4179,7 @@ type RoomHeartbeatResponse struct { func (x *RoomHeartbeatResponse) Reset() { *x = RoomHeartbeatResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[49] + mi := &file_proto_room_v1_room_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4122,7 +4191,7 @@ func (x *RoomHeartbeatResponse) String() string { func (*RoomHeartbeatResponse) ProtoMessage() {} func (x *RoomHeartbeatResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[49] + mi := &file_proto_room_v1_room_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4135,7 +4204,7 @@ func (x *RoomHeartbeatResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomHeartbeatResponse.ProtoReflect.Descriptor instead. func (*RoomHeartbeatResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{49} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{50} } func (x *RoomHeartbeatResponse) GetResult() *CommandResult { @@ -4169,7 +4238,7 @@ type LeaveRoomRequest struct { func (x *LeaveRoomRequest) Reset() { *x = LeaveRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[50] + mi := &file_proto_room_v1_room_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4181,7 +4250,7 @@ func (x *LeaveRoomRequest) String() string { func (*LeaveRoomRequest) ProtoMessage() {} func (x *LeaveRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[50] + mi := &file_proto_room_v1_room_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4194,7 +4263,7 @@ func (x *LeaveRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaveRoomRequest.ProtoReflect.Descriptor instead. func (*LeaveRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{50} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{51} } func (x *LeaveRoomRequest) GetMeta() *RequestMeta { @@ -4215,7 +4284,7 @@ type LeaveRoomResponse struct { func (x *LeaveRoomResponse) Reset() { *x = LeaveRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[51] + mi := &file_proto_room_v1_room_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4227,7 +4296,7 @@ func (x *LeaveRoomResponse) String() string { func (*LeaveRoomResponse) ProtoMessage() {} func (x *LeaveRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[51] + mi := &file_proto_room_v1_room_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4240,7 +4309,7 @@ func (x *LeaveRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaveRoomResponse.ProtoReflect.Descriptor instead. func (*LeaveRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{51} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{52} } func (x *LeaveRoomResponse) GetResult() *CommandResult { @@ -4268,7 +4337,7 @@ type CloseRoomRequest struct { func (x *CloseRoomRequest) Reset() { *x = CloseRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[52] + mi := &file_proto_room_v1_room_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4280,7 +4349,7 @@ func (x *CloseRoomRequest) String() string { func (*CloseRoomRequest) ProtoMessage() {} func (x *CloseRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[52] + mi := &file_proto_room_v1_room_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4293,7 +4362,7 @@ func (x *CloseRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseRoomRequest.ProtoReflect.Descriptor instead. func (*CloseRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{52} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{53} } func (x *CloseRoomRequest) GetMeta() *RequestMeta { @@ -4321,7 +4390,7 @@ type CloseRoomResponse struct { func (x *CloseRoomResponse) Reset() { *x = CloseRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[53] + mi := &file_proto_room_v1_room_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4333,7 +4402,7 @@ func (x *CloseRoomResponse) String() string { func (*CloseRoomResponse) ProtoMessage() {} func (x *CloseRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[53] + mi := &file_proto_room_v1_room_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4346,7 +4415,7 @@ func (x *CloseRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseRoomResponse.ProtoReflect.Descriptor instead. func (*CloseRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{53} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{54} } func (x *CloseRoomResponse) GetResult() *CommandResult { @@ -4391,7 +4460,7 @@ type AdminRoomListItem struct { func (x *AdminRoomListItem) Reset() { *x = AdminRoomListItem{} - mi := &file_proto_room_v1_room_proto_msgTypes[54] + mi := &file_proto_room_v1_room_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4403,7 +4472,7 @@ func (x *AdminRoomListItem) String() string { func (*AdminRoomListItem) ProtoMessage() {} func (x *AdminRoomListItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[54] + mi := &file_proto_room_v1_room_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4416,7 +4485,7 @@ func (x *AdminRoomListItem) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminRoomListItem.ProtoReflect.Descriptor instead. func (*AdminRoomListItem) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{54} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{55} } func (x *AdminRoomListItem) GetRoomId() string { @@ -4569,7 +4638,7 @@ type AdminListRoomsRequest struct { func (x *AdminListRoomsRequest) Reset() { *x = AdminListRoomsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[55] + mi := &file_proto_room_v1_room_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4581,7 +4650,7 @@ func (x *AdminListRoomsRequest) String() string { func (*AdminListRoomsRequest) ProtoMessage() {} func (x *AdminListRoomsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[55] + mi := &file_proto_room_v1_room_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4594,7 +4663,7 @@ func (x *AdminListRoomsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminListRoomsRequest.ProtoReflect.Descriptor instead. func (*AdminListRoomsRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{55} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{56} } func (x *AdminListRoomsRequest) GetMeta() *RequestMeta { @@ -4671,7 +4740,7 @@ type AdminListRoomsResponse struct { func (x *AdminListRoomsResponse) Reset() { *x = AdminListRoomsResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[56] + mi := &file_proto_room_v1_room_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4683,7 +4752,7 @@ func (x *AdminListRoomsResponse) String() string { func (*AdminListRoomsResponse) ProtoMessage() {} func (x *AdminListRoomsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[56] + mi := &file_proto_room_v1_room_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4696,7 +4765,7 @@ func (x *AdminListRoomsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminListRoomsResponse.ProtoReflect.Descriptor instead. func (*AdminListRoomsResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{56} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{57} } func (x *AdminListRoomsResponse) GetRooms() []*AdminRoomListItem { @@ -4730,7 +4799,7 @@ type AdminGetRoomRequest struct { func (x *AdminGetRoomRequest) Reset() { *x = AdminGetRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[57] + mi := &file_proto_room_v1_room_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4742,7 +4811,7 @@ func (x *AdminGetRoomRequest) String() string { func (*AdminGetRoomRequest) ProtoMessage() {} func (x *AdminGetRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[57] + mi := &file_proto_room_v1_room_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4755,7 +4824,7 @@ func (x *AdminGetRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGetRoomRequest.ProtoReflect.Descriptor instead. func (*AdminGetRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{57} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{58} } func (x *AdminGetRoomRequest) GetMeta() *RequestMeta { @@ -4782,7 +4851,7 @@ type AdminGetRoomResponse struct { func (x *AdminGetRoomResponse) Reset() { *x = AdminGetRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[58] + mi := &file_proto_room_v1_room_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4794,7 +4863,7 @@ func (x *AdminGetRoomResponse) String() string { func (*AdminGetRoomResponse) ProtoMessage() {} func (x *AdminGetRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[58] + mi := &file_proto_room_v1_room_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4807,7 +4876,7 @@ func (x *AdminGetRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGetRoomResponse.ProtoReflect.Descriptor instead. func (*AdminGetRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{58} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{59} } func (x *AdminGetRoomResponse) GetRoom() *AdminRoomListItem { @@ -4842,7 +4911,7 @@ type AdminUpdateRoomRequest struct { func (x *AdminUpdateRoomRequest) Reset() { *x = AdminUpdateRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[59] + mi := &file_proto_room_v1_room_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4854,7 +4923,7 @@ func (x *AdminUpdateRoomRequest) String() string { func (*AdminUpdateRoomRequest) ProtoMessage() {} func (x *AdminUpdateRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[59] + mi := &file_proto_room_v1_room_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4867,7 +4936,7 @@ func (x *AdminUpdateRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminUpdateRoomRequest.ProtoReflect.Descriptor instead. func (*AdminUpdateRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{59} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{60} } func (x *AdminUpdateRoomRequest) GetMeta() *RequestMeta { @@ -4950,7 +5019,7 @@ type AdminUpdateRoomResponse struct { func (x *AdminUpdateRoomResponse) Reset() { *x = AdminUpdateRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[60] + mi := &file_proto_room_v1_room_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4962,7 +5031,7 @@ func (x *AdminUpdateRoomResponse) String() string { func (*AdminUpdateRoomResponse) ProtoMessage() {} func (x *AdminUpdateRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[60] + mi := &file_proto_room_v1_room_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4975,7 +5044,7 @@ func (x *AdminUpdateRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminUpdateRoomResponse.ProtoReflect.Descriptor instead. func (*AdminUpdateRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{60} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{61} } func (x *AdminUpdateRoomResponse) GetResult() *CommandResult { @@ -5003,7 +5072,7 @@ type AdminDeleteRoomRequest struct { func (x *AdminDeleteRoomRequest) Reset() { *x = AdminDeleteRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[61] + mi := &file_proto_room_v1_room_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5015,7 +5084,7 @@ func (x *AdminDeleteRoomRequest) String() string { func (*AdminDeleteRoomRequest) ProtoMessage() {} func (x *AdminDeleteRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[61] + mi := &file_proto_room_v1_room_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5028,7 +5097,7 @@ func (x *AdminDeleteRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminDeleteRoomRequest.ProtoReflect.Descriptor instead. func (*AdminDeleteRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{61} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{62} } func (x *AdminDeleteRoomRequest) GetMeta() *RequestMeta { @@ -5062,7 +5131,7 @@ type AdminDeleteRoomResponse struct { func (x *AdminDeleteRoomResponse) Reset() { *x = AdminDeleteRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[62] + mi := &file_proto_room_v1_room_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5074,7 +5143,7 @@ func (x *AdminDeleteRoomResponse) String() string { func (*AdminDeleteRoomResponse) ProtoMessage() {} func (x *AdminDeleteRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[62] + mi := &file_proto_room_v1_room_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5087,7 +5156,7 @@ func (x *AdminDeleteRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminDeleteRoomResponse.ProtoReflect.Descriptor instead. func (*AdminDeleteRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{62} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{63} } func (x *AdminDeleteRoomResponse) GetResult() *CommandResult { @@ -5115,7 +5184,7 @@ type MicUpRequest struct { func (x *MicUpRequest) Reset() { *x = MicUpRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[63] + mi := &file_proto_room_v1_room_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5127,7 +5196,7 @@ func (x *MicUpRequest) String() string { func (*MicUpRequest) ProtoMessage() {} func (x *MicUpRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[63] + mi := &file_proto_room_v1_room_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5140,7 +5209,7 @@ func (x *MicUpRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MicUpRequest.ProtoReflect.Descriptor instead. func (*MicUpRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{63} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{64} } func (x *MicUpRequest) GetMeta() *RequestMeta { @@ -5171,7 +5240,7 @@ type MicUpResponse struct { func (x *MicUpResponse) Reset() { *x = MicUpResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[64] + mi := &file_proto_room_v1_room_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5183,7 +5252,7 @@ func (x *MicUpResponse) String() string { func (*MicUpResponse) ProtoMessage() {} func (x *MicUpResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[64] + mi := &file_proto_room_v1_room_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5196,7 +5265,7 @@ func (x *MicUpResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MicUpResponse.ProtoReflect.Descriptor instead. func (*MicUpResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{64} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{65} } func (x *MicUpResponse) GetResult() *CommandResult { @@ -5247,7 +5316,7 @@ type MicDownRequest struct { func (x *MicDownRequest) Reset() { *x = MicDownRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[65] + mi := &file_proto_room_v1_room_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5259,7 +5328,7 @@ func (x *MicDownRequest) String() string { func (*MicDownRequest) ProtoMessage() {} func (x *MicDownRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[65] + mi := &file_proto_room_v1_room_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5272,7 +5341,7 @@ func (x *MicDownRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MicDownRequest.ProtoReflect.Descriptor instead. func (*MicDownRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{65} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{66} } func (x *MicDownRequest) GetMeta() *RequestMeta { @@ -5308,7 +5377,7 @@ type MicDownResponse struct { func (x *MicDownResponse) Reset() { *x = MicDownResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[66] + mi := &file_proto_room_v1_room_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5320,7 +5389,7 @@ func (x *MicDownResponse) String() string { func (*MicDownResponse) ProtoMessage() {} func (x *MicDownResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[66] + mi := &file_proto_room_v1_room_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5333,7 +5402,7 @@ func (x *MicDownResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MicDownResponse.ProtoReflect.Descriptor instead. func (*MicDownResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{66} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{67} } func (x *MicDownResponse) GetResult() *CommandResult { @@ -5369,7 +5438,7 @@ type ChangeMicSeatRequest struct { func (x *ChangeMicSeatRequest) Reset() { *x = ChangeMicSeatRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[67] + mi := &file_proto_room_v1_room_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5381,7 +5450,7 @@ func (x *ChangeMicSeatRequest) String() string { func (*ChangeMicSeatRequest) ProtoMessage() {} func (x *ChangeMicSeatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[67] + mi := &file_proto_room_v1_room_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5394,7 +5463,7 @@ func (x *ChangeMicSeatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeMicSeatRequest.ProtoReflect.Descriptor instead. func (*ChangeMicSeatRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{67} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{68} } func (x *ChangeMicSeatRequest) GetMeta() *RequestMeta { @@ -5429,7 +5498,7 @@ type ChangeMicSeatResponse struct { func (x *ChangeMicSeatResponse) Reset() { *x = ChangeMicSeatResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[68] + mi := &file_proto_room_v1_room_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5441,7 +5510,7 @@ func (x *ChangeMicSeatResponse) String() string { func (*ChangeMicSeatResponse) ProtoMessage() {} func (x *ChangeMicSeatResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[68] + mi := &file_proto_room_v1_room_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5454,7 +5523,7 @@ func (x *ChangeMicSeatResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeMicSeatResponse.ProtoReflect.Descriptor instead. func (*ChangeMicSeatResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{68} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{69} } func (x *ChangeMicSeatResponse) GetResult() *CommandResult { @@ -5488,7 +5557,7 @@ type ConfirmMicPublishingRequest struct { func (x *ConfirmMicPublishingRequest) Reset() { *x = ConfirmMicPublishingRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[69] + mi := &file_proto_room_v1_room_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5500,7 +5569,7 @@ func (x *ConfirmMicPublishingRequest) String() string { func (*ConfirmMicPublishingRequest) ProtoMessage() {} func (x *ConfirmMicPublishingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[69] + mi := &file_proto_room_v1_room_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5513,7 +5582,7 @@ func (x *ConfirmMicPublishingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmMicPublishingRequest.ProtoReflect.Descriptor instead. func (*ConfirmMicPublishingRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{69} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{70} } func (x *ConfirmMicPublishingRequest) GetMeta() *RequestMeta { @@ -5570,7 +5639,7 @@ type ConfirmMicPublishingResponse struct { func (x *ConfirmMicPublishingResponse) Reset() { *x = ConfirmMicPublishingResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[70] + mi := &file_proto_room_v1_room_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5582,7 +5651,7 @@ func (x *ConfirmMicPublishingResponse) String() string { func (*ConfirmMicPublishingResponse) ProtoMessage() {} func (x *ConfirmMicPublishingResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[70] + mi := &file_proto_room_v1_room_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5595,7 +5664,7 @@ func (x *ConfirmMicPublishingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmMicPublishingResponse.ProtoReflect.Descriptor instead. func (*ConfirmMicPublishingResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{70} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{71} } func (x *ConfirmMicPublishingResponse) GetResult() *CommandResult { @@ -5633,7 +5702,7 @@ type MicHeartbeatRequest struct { func (x *MicHeartbeatRequest) Reset() { *x = MicHeartbeatRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[71] + mi := &file_proto_room_v1_room_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5645,7 +5714,7 @@ func (x *MicHeartbeatRequest) String() string { func (*MicHeartbeatRequest) ProtoMessage() {} func (x *MicHeartbeatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[71] + mi := &file_proto_room_v1_room_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5658,7 +5727,7 @@ func (x *MicHeartbeatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MicHeartbeatRequest.ProtoReflect.Descriptor instead. func (*MicHeartbeatRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{71} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{72} } func (x *MicHeartbeatRequest) GetMeta() *RequestMeta { @@ -5695,7 +5764,7 @@ type MicHeartbeatResponse struct { func (x *MicHeartbeatResponse) Reset() { *x = MicHeartbeatResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[72] + mi := &file_proto_room_v1_room_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5707,7 +5776,7 @@ func (x *MicHeartbeatResponse) String() string { func (*MicHeartbeatResponse) ProtoMessage() {} func (x *MicHeartbeatResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[72] + mi := &file_proto_room_v1_room_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5720,7 +5789,7 @@ func (x *MicHeartbeatResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MicHeartbeatResponse.ProtoReflect.Descriptor instead. func (*MicHeartbeatResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{72} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{73} } func (x *MicHeartbeatResponse) GetResult() *CommandResult { @@ -5764,7 +5833,7 @@ type SetMicMuteRequest struct { func (x *SetMicMuteRequest) Reset() { *x = SetMicMuteRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[73] + mi := &file_proto_room_v1_room_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5776,7 +5845,7 @@ func (x *SetMicMuteRequest) String() string { func (*SetMicMuteRequest) ProtoMessage() {} func (x *SetMicMuteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[73] + mi := &file_proto_room_v1_room_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5789,7 +5858,7 @@ func (x *SetMicMuteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMicMuteRequest.ProtoReflect.Descriptor instead. func (*SetMicMuteRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{73} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{74} } func (x *SetMicMuteRequest) GetMeta() *RequestMeta { @@ -5825,7 +5894,7 @@ type SetMicMuteResponse struct { func (x *SetMicMuteResponse) Reset() { *x = SetMicMuteResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[74] + mi := &file_proto_room_v1_room_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5837,7 +5906,7 @@ func (x *SetMicMuteResponse) String() string { func (*SetMicMuteResponse) ProtoMessage() {} func (x *SetMicMuteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[74] + mi := &file_proto_room_v1_room_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5850,7 +5919,7 @@ func (x *SetMicMuteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMicMuteResponse.ProtoReflect.Descriptor instead. func (*SetMicMuteResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{74} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{75} } func (x *SetMicMuteResponse) GetResult() *CommandResult { @@ -5891,7 +5960,7 @@ type ApplyRTCEventRequest struct { func (x *ApplyRTCEventRequest) Reset() { *x = ApplyRTCEventRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[75] + mi := &file_proto_room_v1_room_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5903,7 +5972,7 @@ func (x *ApplyRTCEventRequest) String() string { func (*ApplyRTCEventRequest) ProtoMessage() {} func (x *ApplyRTCEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[75] + mi := &file_proto_room_v1_room_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5916,7 +5985,7 @@ func (x *ApplyRTCEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyRTCEventRequest.ProtoReflect.Descriptor instead. func (*ApplyRTCEventRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{75} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{76} } func (x *ApplyRTCEventRequest) GetMeta() *RequestMeta { @@ -5980,7 +6049,7 @@ type ApplyRTCEventResponse struct { func (x *ApplyRTCEventResponse) Reset() { *x = ApplyRTCEventResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[76] + mi := &file_proto_room_v1_room_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5992,7 +6061,7 @@ func (x *ApplyRTCEventResponse) String() string { func (*ApplyRTCEventResponse) ProtoMessage() {} func (x *ApplyRTCEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[76] + mi := &file_proto_room_v1_room_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6005,7 +6074,7 @@ func (x *ApplyRTCEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyRTCEventResponse.ProtoReflect.Descriptor instead. func (*ApplyRTCEventResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{76} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{77} } func (x *ApplyRTCEventResponse) GetResult() *CommandResult { @@ -6041,7 +6110,7 @@ type SetMicSeatLockRequest struct { func (x *SetMicSeatLockRequest) Reset() { *x = SetMicSeatLockRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[77] + mi := &file_proto_room_v1_room_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6053,7 +6122,7 @@ func (x *SetMicSeatLockRequest) String() string { func (*SetMicSeatLockRequest) ProtoMessage() {} func (x *SetMicSeatLockRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[77] + mi := &file_proto_room_v1_room_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6066,7 +6135,7 @@ func (x *SetMicSeatLockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMicSeatLockRequest.ProtoReflect.Descriptor instead. func (*SetMicSeatLockRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{77} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{78} } func (x *SetMicSeatLockRequest) GetMeta() *RequestMeta { @@ -6101,7 +6170,7 @@ type SetMicSeatLockResponse struct { func (x *SetMicSeatLockResponse) Reset() { *x = SetMicSeatLockResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[78] + mi := &file_proto_room_v1_room_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6113,7 +6182,7 @@ func (x *SetMicSeatLockResponse) String() string { func (*SetMicSeatLockResponse) ProtoMessage() {} func (x *SetMicSeatLockResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[78] + mi := &file_proto_room_v1_room_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6126,7 +6195,7 @@ func (x *SetMicSeatLockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMicSeatLockResponse.ProtoReflect.Descriptor instead. func (*SetMicSeatLockResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{78} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{79} } func (x *SetMicSeatLockResponse) GetResult() *CommandResult { @@ -6154,7 +6223,7 @@ type SetChatEnabledRequest struct { func (x *SetChatEnabledRequest) Reset() { *x = SetChatEnabledRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[79] + mi := &file_proto_room_v1_room_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6166,7 +6235,7 @@ func (x *SetChatEnabledRequest) String() string { func (*SetChatEnabledRequest) ProtoMessage() {} func (x *SetChatEnabledRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[79] + mi := &file_proto_room_v1_room_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6179,7 +6248,7 @@ func (x *SetChatEnabledRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetChatEnabledRequest.ProtoReflect.Descriptor instead. func (*SetChatEnabledRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{79} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{80} } func (x *SetChatEnabledRequest) GetMeta() *RequestMeta { @@ -6207,7 +6276,7 @@ type SetChatEnabledResponse struct { func (x *SetChatEnabledResponse) Reset() { *x = SetChatEnabledResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[80] + mi := &file_proto_room_v1_room_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6219,7 +6288,7 @@ func (x *SetChatEnabledResponse) String() string { func (*SetChatEnabledResponse) ProtoMessage() {} func (x *SetChatEnabledResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[80] + mi := &file_proto_room_v1_room_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6232,7 +6301,7 @@ func (x *SetChatEnabledResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetChatEnabledResponse.ProtoReflect.Descriptor instead. func (*SetChatEnabledResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{80} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{81} } func (x *SetChatEnabledResponse) GetResult() *CommandResult { @@ -6262,7 +6331,7 @@ type SetRoomPasswordRequest struct { func (x *SetRoomPasswordRequest) Reset() { *x = SetRoomPasswordRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[81] + mi := &file_proto_room_v1_room_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6274,7 +6343,7 @@ func (x *SetRoomPasswordRequest) String() string { func (*SetRoomPasswordRequest) ProtoMessage() {} func (x *SetRoomPasswordRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[81] + mi := &file_proto_room_v1_room_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6287,7 +6356,7 @@ func (x *SetRoomPasswordRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomPasswordRequest.ProtoReflect.Descriptor instead. func (*SetRoomPasswordRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{81} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{82} } func (x *SetRoomPasswordRequest) GetMeta() *RequestMeta { @@ -6322,7 +6391,7 @@ type SetRoomPasswordResponse struct { func (x *SetRoomPasswordResponse) Reset() { *x = SetRoomPasswordResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[82] + mi := &file_proto_room_v1_room_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6334,7 +6403,7 @@ func (x *SetRoomPasswordResponse) String() string { func (*SetRoomPasswordResponse) ProtoMessage() {} func (x *SetRoomPasswordResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[82] + mi := &file_proto_room_v1_room_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6347,7 +6416,7 @@ func (x *SetRoomPasswordResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomPasswordResponse.ProtoReflect.Descriptor instead. func (*SetRoomPasswordResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{82} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{83} } func (x *SetRoomPasswordResponse) GetResult() *CommandResult { @@ -6376,7 +6445,7 @@ type SetRoomAdminRequest struct { func (x *SetRoomAdminRequest) Reset() { *x = SetRoomAdminRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[83] + mi := &file_proto_room_v1_room_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6388,7 +6457,7 @@ func (x *SetRoomAdminRequest) String() string { func (*SetRoomAdminRequest) ProtoMessage() {} func (x *SetRoomAdminRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[83] + mi := &file_proto_room_v1_room_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6401,7 +6470,7 @@ func (x *SetRoomAdminRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomAdminRequest.ProtoReflect.Descriptor instead. func (*SetRoomAdminRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{83} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{84} } func (x *SetRoomAdminRequest) GetMeta() *RequestMeta { @@ -6436,7 +6505,7 @@ type SetRoomAdminResponse struct { func (x *SetRoomAdminResponse) Reset() { *x = SetRoomAdminResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[84] + mi := &file_proto_room_v1_room_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6448,7 +6517,7 @@ func (x *SetRoomAdminResponse) String() string { func (*SetRoomAdminResponse) ProtoMessage() {} func (x *SetRoomAdminResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[84] + mi := &file_proto_room_v1_room_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6461,7 +6530,7 @@ func (x *SetRoomAdminResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetRoomAdminResponse.ProtoReflect.Descriptor instead. func (*SetRoomAdminResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{84} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{85} } func (x *SetRoomAdminResponse) GetResult() *CommandResult { @@ -6490,7 +6559,7 @@ type MuteUserRequest struct { func (x *MuteUserRequest) Reset() { *x = MuteUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[85] + mi := &file_proto_room_v1_room_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6502,7 +6571,7 @@ func (x *MuteUserRequest) String() string { func (*MuteUserRequest) ProtoMessage() {} func (x *MuteUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[85] + mi := &file_proto_room_v1_room_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6515,7 +6584,7 @@ func (x *MuteUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MuteUserRequest.ProtoReflect.Descriptor instead. func (*MuteUserRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{85} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{86} } func (x *MuteUserRequest) GetMeta() *RequestMeta { @@ -6550,7 +6619,7 @@ type MuteUserResponse struct { func (x *MuteUserResponse) Reset() { *x = MuteUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[86] + mi := &file_proto_room_v1_room_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6562,7 +6631,7 @@ func (x *MuteUserResponse) String() string { func (*MuteUserResponse) ProtoMessage() {} func (x *MuteUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[86] + mi := &file_proto_room_v1_room_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6575,7 +6644,7 @@ func (x *MuteUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MuteUserResponse.ProtoReflect.Descriptor instead. func (*MuteUserResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{86} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{87} } func (x *MuteUserResponse) GetResult() *CommandResult { @@ -6605,7 +6674,7 @@ type KickUserRequest struct { func (x *KickUserRequest) Reset() { *x = KickUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[87] + mi := &file_proto_room_v1_room_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6617,7 +6686,7 @@ func (x *KickUserRequest) String() string { func (*KickUserRequest) ProtoMessage() {} func (x *KickUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[87] + mi := &file_proto_room_v1_room_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6630,7 +6699,7 @@ func (x *KickUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use KickUserRequest.ProtoReflect.Descriptor instead. func (*KickUserRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{87} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{88} } func (x *KickUserRequest) GetMeta() *RequestMeta { @@ -6669,7 +6738,7 @@ type KickUserResponse struct { func (x *KickUserResponse) Reset() { *x = KickUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[88] + mi := &file_proto_room_v1_room_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6681,7 +6750,7 @@ func (x *KickUserResponse) String() string { func (*KickUserResponse) ProtoMessage() {} func (x *KickUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[88] + mi := &file_proto_room_v1_room_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6694,7 +6763,7 @@ func (x *KickUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use KickUserResponse.ProtoReflect.Descriptor instead. func (*KickUserResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{88} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{89} } func (x *KickUserResponse) GetResult() *CommandResult { @@ -6736,7 +6805,7 @@ type UnbanUserRequest struct { func (x *UnbanUserRequest) Reset() { *x = UnbanUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[89] + mi := &file_proto_room_v1_room_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6748,7 +6817,7 @@ func (x *UnbanUserRequest) String() string { func (*UnbanUserRequest) ProtoMessage() {} func (x *UnbanUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[89] + mi := &file_proto_room_v1_room_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6761,7 +6830,7 @@ func (x *UnbanUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnbanUserRequest.ProtoReflect.Descriptor instead. func (*UnbanUserRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{89} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{90} } func (x *UnbanUserRequest) GetMeta() *RequestMeta { @@ -6789,7 +6858,7 @@ type UnbanUserResponse struct { func (x *UnbanUserResponse) Reset() { *x = UnbanUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[90] + mi := &file_proto_room_v1_room_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6801,7 +6870,7 @@ func (x *UnbanUserResponse) String() string { func (*UnbanUserResponse) ProtoMessage() {} func (x *UnbanUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[90] + mi := &file_proto_room_v1_room_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6814,7 +6883,7 @@ func (x *UnbanUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnbanUserResponse.ProtoReflect.Descriptor instead. func (*UnbanUserResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{90} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{91} } func (x *UnbanUserResponse) GetResult() *CommandResult { @@ -6847,7 +6916,7 @@ type SystemEvictUserRequest struct { func (x *SystemEvictUserRequest) Reset() { *x = SystemEvictUserRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[91] + mi := &file_proto_room_v1_room_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6859,7 +6928,7 @@ func (x *SystemEvictUserRequest) String() string { func (*SystemEvictUserRequest) ProtoMessage() {} func (x *SystemEvictUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[91] + mi := &file_proto_room_v1_room_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6872,7 +6941,7 @@ func (x *SystemEvictUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemEvictUserRequest.ProtoReflect.Descriptor instead. func (*SystemEvictUserRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{91} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{92} } func (x *SystemEvictUserRequest) GetMeta() *RequestMeta { @@ -6925,7 +6994,7 @@ type SystemEvictUserResponse struct { func (x *SystemEvictUserResponse) Reset() { *x = SystemEvictUserResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[92] + mi := &file_proto_room_v1_room_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6937,7 +7006,7 @@ func (x *SystemEvictUserResponse) String() string { func (*SystemEvictUserResponse) ProtoMessage() {} func (x *SystemEvictUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[92] + mi := &file_proto_room_v1_room_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6950,7 +7019,7 @@ func (x *SystemEvictUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemEvictUserResponse.ProtoReflect.Descriptor instead. func (*SystemEvictUserResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{92} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{93} } func (x *SystemEvictUserResponse) GetHadCurrentRoom() bool { @@ -7023,7 +7092,7 @@ type SendGiftRequest struct { func (x *SendGiftRequest) Reset() { *x = SendGiftRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[93] + mi := &file_proto_room_v1_room_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7035,7 +7104,7 @@ func (x *SendGiftRequest) String() string { func (*SendGiftRequest) ProtoMessage() {} func (x *SendGiftRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[93] + mi := &file_proto_room_v1_room_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7048,7 +7117,7 @@ func (x *SendGiftRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftRequest.ProtoReflect.Descriptor instead. func (*SendGiftRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{93} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{94} } func (x *SendGiftRequest) GetMeta() *RequestMeta { @@ -7154,7 +7223,7 @@ type SendGiftResponse struct { func (x *SendGiftResponse) Reset() { *x = SendGiftResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[94] + mi := &file_proto_room_v1_room_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7166,7 +7235,7 @@ func (x *SendGiftResponse) String() string { func (*SendGiftResponse) ProtoMessage() {} func (x *SendGiftResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[94] + mi := &file_proto_room_v1_room_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7179,7 +7248,7 @@ func (x *SendGiftResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftResponse.ProtoReflect.Descriptor instead. func (*SendGiftResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{94} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{95} } func (x *SendGiftResponse) GetResult() *CommandResult { @@ -7250,7 +7319,7 @@ type CheckSpeakPermissionRequest struct { func (x *CheckSpeakPermissionRequest) Reset() { *x = CheckSpeakPermissionRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[95] + mi := &file_proto_room_v1_room_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7262,7 +7331,7 @@ func (x *CheckSpeakPermissionRequest) String() string { func (*CheckSpeakPermissionRequest) ProtoMessage() {} func (x *CheckSpeakPermissionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[95] + mi := &file_proto_room_v1_room_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7275,7 +7344,7 @@ func (x *CheckSpeakPermissionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckSpeakPermissionRequest.ProtoReflect.Descriptor instead. func (*CheckSpeakPermissionRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{95} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{96} } func (x *CheckSpeakPermissionRequest) GetRoomId() string { @@ -7311,7 +7380,7 @@ type CheckSpeakPermissionResponse struct { func (x *CheckSpeakPermissionResponse) Reset() { *x = CheckSpeakPermissionResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[96] + mi := &file_proto_room_v1_room_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7323,7 +7392,7 @@ func (x *CheckSpeakPermissionResponse) String() string { func (*CheckSpeakPermissionResponse) ProtoMessage() {} func (x *CheckSpeakPermissionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[96] + mi := &file_proto_room_v1_room_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7336,7 +7405,7 @@ func (x *CheckSpeakPermissionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckSpeakPermissionResponse.ProtoReflect.Descriptor instead. func (*CheckSpeakPermissionResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{96} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{97} } func (x *CheckSpeakPermissionResponse) GetAllowed() bool { @@ -7374,7 +7443,7 @@ type VerifyRoomPresenceRequest struct { func (x *VerifyRoomPresenceRequest) Reset() { *x = VerifyRoomPresenceRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[97] + mi := &file_proto_room_v1_room_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7386,7 +7455,7 @@ func (x *VerifyRoomPresenceRequest) String() string { func (*VerifyRoomPresenceRequest) ProtoMessage() {} func (x *VerifyRoomPresenceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[97] + mi := &file_proto_room_v1_room_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7399,7 +7468,7 @@ func (x *VerifyRoomPresenceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyRoomPresenceRequest.ProtoReflect.Descriptor instead. func (*VerifyRoomPresenceRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{97} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{98} } func (x *VerifyRoomPresenceRequest) GetRoomId() string { @@ -7449,7 +7518,7 @@ type VerifyRoomPresenceResponse struct { func (x *VerifyRoomPresenceResponse) Reset() { *x = VerifyRoomPresenceResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[98] + mi := &file_proto_room_v1_room_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7461,7 +7530,7 @@ func (x *VerifyRoomPresenceResponse) String() string { func (*VerifyRoomPresenceResponse) ProtoMessage() {} func (x *VerifyRoomPresenceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[98] + mi := &file_proto_room_v1_room_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7474,7 +7543,7 @@ func (x *VerifyRoomPresenceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyRoomPresenceResponse.ProtoReflect.Descriptor instead. func (*VerifyRoomPresenceResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{98} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{99} } func (x *VerifyRoomPresenceResponse) GetPresent() bool { @@ -7517,7 +7586,7 @@ type ListRoomsRequest struct { func (x *ListRoomsRequest) Reset() { *x = ListRoomsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[99] + mi := &file_proto_room_v1_room_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7529,7 +7598,7 @@ func (x *ListRoomsRequest) String() string { func (*ListRoomsRequest) ProtoMessage() {} func (x *ListRoomsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[99] + mi := &file_proto_room_v1_room_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7542,7 +7611,7 @@ func (x *ListRoomsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomsRequest.ProtoReflect.Descriptor instead. func (*ListRoomsRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{99} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{100} } func (x *ListRoomsRequest) GetMeta() *RequestMeta { @@ -7620,7 +7689,7 @@ type ListRoomFeedsRequest struct { func (x *ListRoomFeedsRequest) Reset() { *x = ListRoomFeedsRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[100] + mi := &file_proto_room_v1_room_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7632,7 +7701,7 @@ func (x *ListRoomFeedsRequest) String() string { func (*ListRoomFeedsRequest) ProtoMessage() {} func (x *ListRoomFeedsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[100] + mi := &file_proto_room_v1_room_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7645,7 +7714,7 @@ func (x *ListRoomFeedsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomFeedsRequest.ProtoReflect.Descriptor instead. func (*ListRoomFeedsRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{100} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{101} } func (x *ListRoomFeedsRequest) GetMeta() *RequestMeta { @@ -7717,7 +7786,7 @@ type ListRoomGiftLeaderboardRequest struct { func (x *ListRoomGiftLeaderboardRequest) Reset() { *x = ListRoomGiftLeaderboardRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[101] + mi := &file_proto_room_v1_room_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7729,7 +7798,7 @@ func (x *ListRoomGiftLeaderboardRequest) String() string { func (*ListRoomGiftLeaderboardRequest) ProtoMessage() {} func (x *ListRoomGiftLeaderboardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[101] + mi := &file_proto_room_v1_room_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7742,7 +7811,7 @@ func (x *ListRoomGiftLeaderboardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomGiftLeaderboardRequest.ProtoReflect.Descriptor instead. func (*ListRoomGiftLeaderboardRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{101} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{102} } func (x *ListRoomGiftLeaderboardRequest) GetMeta() *RequestMeta { @@ -7784,7 +7853,7 @@ type RoomFeedRelatedUser struct { func (x *RoomFeedRelatedUser) Reset() { *x = RoomFeedRelatedUser{} - mi := &file_proto_room_v1_room_proto_msgTypes[102] + mi := &file_proto_room_v1_room_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7796,7 +7865,7 @@ func (x *RoomFeedRelatedUser) String() string { func (*RoomFeedRelatedUser) ProtoMessage() {} func (x *RoomFeedRelatedUser) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[102] + mi := &file_proto_room_v1_room_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7809,7 +7878,7 @@ func (x *RoomFeedRelatedUser) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomFeedRelatedUser.ProtoReflect.Descriptor instead. func (*RoomFeedRelatedUser) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{102} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{103} } func (x *RoomFeedRelatedUser) GetUserId() int64 { @@ -7850,7 +7919,7 @@ type RoomListItem struct { func (x *RoomListItem) Reset() { *x = RoomListItem{} - mi := &file_proto_room_v1_room_proto_msgTypes[103] + mi := &file_proto_room_v1_room_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7862,7 +7931,7 @@ func (x *RoomListItem) String() string { func (*RoomListItem) ProtoMessage() {} func (x *RoomListItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[103] + mi := &file_proto_room_v1_room_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7875,7 +7944,7 @@ func (x *RoomListItem) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomListItem.ProtoReflect.Descriptor instead. func (*RoomListItem) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{103} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{104} } func (x *RoomListItem) GetRoomId() string { @@ -7994,7 +8063,7 @@ type ListRoomsResponse struct { func (x *ListRoomsResponse) Reset() { *x = ListRoomsResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[104] + mi := &file_proto_room_v1_room_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8006,7 +8075,7 @@ func (x *ListRoomsResponse) String() string { func (*ListRoomsResponse) ProtoMessage() {} func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[104] + mi := &file_proto_room_v1_room_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8019,7 +8088,7 @@ func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomsResponse.ProtoReflect.Descriptor instead. func (*ListRoomsResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{104} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{105} } func (x *ListRoomsResponse) GetRooms() []*RoomListItem { @@ -8049,7 +8118,7 @@ type RoomGiftLeaderboardItem struct { func (x *RoomGiftLeaderboardItem) Reset() { *x = RoomGiftLeaderboardItem{} - mi := &file_proto_room_v1_room_proto_msgTypes[105] + mi := &file_proto_room_v1_room_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8061,7 +8130,7 @@ func (x *RoomGiftLeaderboardItem) String() string { func (*RoomGiftLeaderboardItem) ProtoMessage() {} func (x *RoomGiftLeaderboardItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[105] + mi := &file_proto_room_v1_room_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8074,7 +8143,7 @@ func (x *RoomGiftLeaderboardItem) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomGiftLeaderboardItem.ProtoReflect.Descriptor instead. func (*RoomGiftLeaderboardItem) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{105} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{106} } func (x *RoomGiftLeaderboardItem) GetRank() int64 { @@ -8120,7 +8189,7 @@ type ListRoomGiftLeaderboardResponse struct { func (x *ListRoomGiftLeaderboardResponse) Reset() { *x = ListRoomGiftLeaderboardResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[106] + mi := &file_proto_room_v1_room_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8132,7 +8201,7 @@ func (x *ListRoomGiftLeaderboardResponse) String() string { func (*ListRoomGiftLeaderboardResponse) ProtoMessage() {} func (x *ListRoomGiftLeaderboardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[106] + mi := &file_proto_room_v1_room_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8145,7 +8214,7 @@ func (x *ListRoomGiftLeaderboardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomGiftLeaderboardResponse.ProtoReflect.Descriptor instead. func (*ListRoomGiftLeaderboardResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{106} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{107} } func (x *ListRoomGiftLeaderboardResponse) GetItems() []*RoomGiftLeaderboardItem { @@ -8202,7 +8271,7 @@ type GetMyRoomRequest struct { func (x *GetMyRoomRequest) Reset() { *x = GetMyRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[107] + mi := &file_proto_room_v1_room_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8214,7 +8283,7 @@ func (x *GetMyRoomRequest) String() string { func (*GetMyRoomRequest) ProtoMessage() {} func (x *GetMyRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[107] + mi := &file_proto_room_v1_room_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8227,7 +8296,7 @@ func (x *GetMyRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyRoomRequest.ProtoReflect.Descriptor instead. func (*GetMyRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{107} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{108} } func (x *GetMyRoomRequest) GetMeta() *RequestMeta { @@ -8257,7 +8326,7 @@ type GetMyRoomResponse struct { func (x *GetMyRoomResponse) Reset() { *x = GetMyRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[108] + mi := &file_proto_room_v1_room_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8269,7 +8338,7 @@ func (x *GetMyRoomResponse) String() string { func (*GetMyRoomResponse) ProtoMessage() {} func (x *GetMyRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[108] + mi := &file_proto_room_v1_room_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8282,7 +8351,7 @@ func (x *GetMyRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyRoomResponse.ProtoReflect.Descriptor instead. func (*GetMyRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{108} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{109} } func (x *GetMyRoomResponse) GetHasRoom() bool { @@ -8318,7 +8387,7 @@ type GetCurrentRoomRequest struct { func (x *GetCurrentRoomRequest) Reset() { *x = GetCurrentRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[109] + mi := &file_proto_room_v1_room_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8330,7 +8399,7 @@ func (x *GetCurrentRoomRequest) String() string { func (*GetCurrentRoomRequest) ProtoMessage() {} func (x *GetCurrentRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[109] + mi := &file_proto_room_v1_room_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8343,7 +8412,7 @@ func (x *GetCurrentRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCurrentRoomRequest.ProtoReflect.Descriptor instead. func (*GetCurrentRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{109} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{110} } func (x *GetCurrentRoomRequest) GetMeta() *RequestMeta { @@ -8379,7 +8448,7 @@ type GetCurrentRoomResponse struct { func (x *GetCurrentRoomResponse) Reset() { *x = GetCurrentRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[110] + mi := &file_proto_room_v1_room_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8391,7 +8460,7 @@ func (x *GetCurrentRoomResponse) String() string { func (*GetCurrentRoomResponse) ProtoMessage() {} func (x *GetCurrentRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[110] + mi := &file_proto_room_v1_room_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8404,7 +8473,7 @@ func (x *GetCurrentRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCurrentRoomResponse.ProtoReflect.Descriptor instead. func (*GetCurrentRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{110} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{111} } func (x *GetCurrentRoomResponse) GetHasCurrentRoom() bool { @@ -8483,7 +8552,7 @@ type GetRoomSnapshotRequest struct { func (x *GetRoomSnapshotRequest) Reset() { *x = GetRoomSnapshotRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[111] + mi := &file_proto_room_v1_room_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8495,7 +8564,7 @@ func (x *GetRoomSnapshotRequest) String() string { func (*GetRoomSnapshotRequest) ProtoMessage() {} func (x *GetRoomSnapshotRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[111] + mi := &file_proto_room_v1_room_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8508,7 +8577,7 @@ func (x *GetRoomSnapshotRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomSnapshotRequest.ProtoReflect.Descriptor instead. func (*GetRoomSnapshotRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{111} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{112} } func (x *GetRoomSnapshotRequest) GetMeta() *RequestMeta { @@ -8545,7 +8614,7 @@ type GetRoomSnapshotResponse struct { func (x *GetRoomSnapshotResponse) Reset() { *x = GetRoomSnapshotResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[112] + mi := &file_proto_room_v1_room_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8557,7 +8626,7 @@ func (x *GetRoomSnapshotResponse) String() string { func (*GetRoomSnapshotResponse) ProtoMessage() {} func (x *GetRoomSnapshotResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[112] + mi := &file_proto_room_v1_room_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8570,7 +8639,7 @@ func (x *GetRoomSnapshotResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomSnapshotResponse.ProtoReflect.Descriptor instead. func (*GetRoomSnapshotResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{112} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{113} } func (x *GetRoomSnapshotResponse) GetRoom() *RoomSnapshot { @@ -8607,7 +8676,7 @@ type GetRoomRocketRequest struct { func (x *GetRoomRocketRequest) Reset() { *x = GetRoomRocketRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[113] + mi := &file_proto_room_v1_room_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8619,7 +8688,7 @@ func (x *GetRoomRocketRequest) String() string { func (*GetRoomRocketRequest) ProtoMessage() {} func (x *GetRoomRocketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[113] + mi := &file_proto_room_v1_room_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8632,7 +8701,7 @@ func (x *GetRoomRocketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomRocketRequest.ProtoReflect.Descriptor instead. func (*GetRoomRocketRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{113} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{114} } func (x *GetRoomRocketRequest) GetMeta() *RequestMeta { @@ -8666,7 +8735,7 @@ type GetRoomRocketResponse struct { func (x *GetRoomRocketResponse) Reset() { *x = GetRoomRocketResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[114] + mi := &file_proto_room_v1_room_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8678,7 +8747,7 @@ func (x *GetRoomRocketResponse) String() string { func (*GetRoomRocketResponse) ProtoMessage() {} func (x *GetRoomRocketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[114] + mi := &file_proto_room_v1_room_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8691,7 +8760,7 @@ func (x *GetRoomRocketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomRocketResponse.ProtoReflect.Descriptor instead. func (*GetRoomRocketResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{114} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{115} } func (x *GetRoomRocketResponse) GetRocket() *RoomRocketInfo { @@ -8723,7 +8792,7 @@ type ListRoomOnlineUsersRequest struct { func (x *ListRoomOnlineUsersRequest) Reset() { *x = ListRoomOnlineUsersRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[115] + mi := &file_proto_room_v1_room_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8735,7 +8804,7 @@ func (x *ListRoomOnlineUsersRequest) String() string { func (*ListRoomOnlineUsersRequest) ProtoMessage() {} func (x *ListRoomOnlineUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[115] + mi := &file_proto_room_v1_room_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8748,7 +8817,7 @@ func (x *ListRoomOnlineUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomOnlineUsersRequest.ProtoReflect.Descriptor instead. func (*ListRoomOnlineUsersRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{115} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{116} } func (x *ListRoomOnlineUsersRequest) GetMeta() *RequestMeta { @@ -8807,7 +8876,7 @@ type ListRoomOnlineUsersResponse struct { func (x *ListRoomOnlineUsersResponse) Reset() { *x = ListRoomOnlineUsersResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[116] + mi := &file_proto_room_v1_room_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8819,7 +8888,7 @@ func (x *ListRoomOnlineUsersResponse) String() string { func (*ListRoomOnlineUsersResponse) ProtoMessage() {} func (x *ListRoomOnlineUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[116] + mi := &file_proto_room_v1_room_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8832,7 +8901,7 @@ func (x *ListRoomOnlineUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomOnlineUsersResponse.ProtoReflect.Descriptor instead. func (*ListRoomOnlineUsersResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{116} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{117} } func (x *ListRoomOnlineUsersResponse) GetUsers() []*RoomUser { @@ -8893,7 +8962,7 @@ type RoomBannedUser struct { func (x *RoomBannedUser) Reset() { *x = RoomBannedUser{} - mi := &file_proto_room_v1_room_proto_msgTypes[117] + mi := &file_proto_room_v1_room_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8905,7 +8974,7 @@ func (x *RoomBannedUser) String() string { func (*RoomBannedUser) ProtoMessage() {} func (x *RoomBannedUser) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[117] + mi := &file_proto_room_v1_room_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8918,7 +8987,7 @@ func (x *RoomBannedUser) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomBannedUser.ProtoReflect.Descriptor instead. func (*RoomBannedUser) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{117} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{118} } func (x *RoomBannedUser) GetUserId() int64 { @@ -8977,7 +9046,7 @@ type ListRoomBannedUsersRequest struct { func (x *ListRoomBannedUsersRequest) Reset() { *x = ListRoomBannedUsersRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[118] + mi := &file_proto_room_v1_room_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8989,7 +9058,7 @@ func (x *ListRoomBannedUsersRequest) String() string { func (*ListRoomBannedUsersRequest) ProtoMessage() {} func (x *ListRoomBannedUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[118] + mi := &file_proto_room_v1_room_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9002,7 +9071,7 @@ func (x *ListRoomBannedUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomBannedUsersRequest.ProtoReflect.Descriptor instead. func (*ListRoomBannedUsersRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{118} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{119} } func (x *ListRoomBannedUsersRequest) GetMeta() *RequestMeta { @@ -9053,7 +9122,7 @@ type ListRoomBannedUsersResponse struct { func (x *ListRoomBannedUsersResponse) Reset() { *x = ListRoomBannedUsersResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[119] + mi := &file_proto_room_v1_room_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9065,7 +9134,7 @@ func (x *ListRoomBannedUsersResponse) String() string { func (*ListRoomBannedUsersResponse) ProtoMessage() {} func (x *ListRoomBannedUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[119] + mi := &file_proto_room_v1_room_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9078,7 +9147,7 @@ func (x *ListRoomBannedUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomBannedUsersResponse.ProtoReflect.Descriptor instead. func (*ListRoomBannedUsersResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{119} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{120} } func (x *ListRoomBannedUsersResponse) GetItems() []*RoomBannedUser { @@ -9129,7 +9198,7 @@ type FollowRoomRequest struct { func (x *FollowRoomRequest) Reset() { *x = FollowRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[120] + mi := &file_proto_room_v1_room_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9141,7 +9210,7 @@ func (x *FollowRoomRequest) String() string { func (*FollowRoomRequest) ProtoMessage() {} func (x *FollowRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[120] + mi := &file_proto_room_v1_room_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9154,7 +9223,7 @@ func (x *FollowRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FollowRoomRequest.ProtoReflect.Descriptor instead. func (*FollowRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{120} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{121} } func (x *FollowRoomRequest) GetMeta() *RequestMeta { @@ -9190,7 +9259,7 @@ type FollowRoomResponse struct { func (x *FollowRoomResponse) Reset() { *x = FollowRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[121] + mi := &file_proto_room_v1_room_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9202,7 +9271,7 @@ func (x *FollowRoomResponse) String() string { func (*FollowRoomResponse) ProtoMessage() {} func (x *FollowRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[121] + mi := &file_proto_room_v1_room_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9215,7 +9284,7 @@ func (x *FollowRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FollowRoomResponse.ProtoReflect.Descriptor instead. func (*FollowRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{121} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{122} } func (x *FollowRoomResponse) GetRoomId() string { @@ -9258,7 +9327,7 @@ type UnfollowRoomRequest struct { func (x *UnfollowRoomRequest) Reset() { *x = UnfollowRoomRequest{} - mi := &file_proto_room_v1_room_proto_msgTypes[122] + mi := &file_proto_room_v1_room_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9270,7 +9339,7 @@ func (x *UnfollowRoomRequest) String() string { func (*UnfollowRoomRequest) ProtoMessage() {} func (x *UnfollowRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[122] + mi := &file_proto_room_v1_room_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9283,7 +9352,7 @@ func (x *UnfollowRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnfollowRoomRequest.ProtoReflect.Descriptor instead. func (*UnfollowRoomRequest) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{122} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{123} } func (x *UnfollowRoomRequest) GetMeta() *RequestMeta { @@ -9318,7 +9387,7 @@ type UnfollowRoomResponse struct { func (x *UnfollowRoomResponse) Reset() { *x = UnfollowRoomResponse{} - mi := &file_proto_room_v1_room_proto_msgTypes[123] + mi := &file_proto_room_v1_room_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9330,7 +9399,7 @@ func (x *UnfollowRoomResponse) String() string { func (*UnfollowRoomResponse) ProtoMessage() {} func (x *UnfollowRoomResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_room_v1_room_proto_msgTypes[123] + mi := &file_proto_room_v1_room_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9343,7 +9412,7 @@ func (x *UnfollowRoomResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnfollowRoomResponse.ProtoReflect.Descriptor instead. func (*UnfollowRoomResponse) Descriptor() ([]byte, []int) { - return file_proto_room_v1_room_proto_rawDescGZIP(), []int{123} + return file_proto_room_v1_room_proto_rawDescGZIP(), []int{124} } func (x *UnfollowRoomResponse) GetRoomId() string { @@ -9477,7 +9546,11 @@ const file_proto_room_v1_room_proto_rawDesc = "" + "\fdisplay_name\x18\x05 \x01(\tR\vdisplayName\x12\x19\n" + "\bicon_url\x18\x06 \x01(\tR\aiconUrl\x12\x19\n" + "\bgrant_id\x18\a \x01(\tR\agrantId\x12\x16\n" + - "\x06status\x18\b \x01(\tR\x06status\"\xe7\x02\n" + + "\x06status\x18\b \x01(\tR\x06status\"i\n" + + "\x16RoomRocketContribution\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" + + "\x04fuel\x18\x02 \x01(\x03R\x04fuel\x12\"\n" + + "\rupdated_at_ms\x18\x03 \x01(\x03R\vupdatedAtMs\"\xe7\x02\n" + "\x17RoomRocketPendingLaunch\x12\x1b\n" + "\trocket_id\x18\x01 \x01(\tR\brocketId\x12\x14\n" + "\x05level\x18\x02 \x01(\x05R\x05level\x12%\n" + @@ -9491,7 +9564,7 @@ const file_proto_room_v1_room_proto_rawDesc = "" + "\x0figniter_user_id\x18\b \x01(\x03R\rigniterUserId\x12%\n" + "\x0econfig_version\x18\t \x01(\x03R\rconfigVersion\x12\x1b\n" + "\tcover_url\x18\n" + - " \x01(\tR\bcoverUrl\"\xce\x04\n" + + " \x01(\tR\bcoverUrl\"\xaa\x05\n" + "\x0fRoomRocketState\x12#\n" + "\rcurrent_level\x18\x01 \x01(\x05R\fcurrentLevel\x12!\n" + "\fcurrent_fuel\x18\x02 \x01(\x03R\vcurrentFuel\x12%\n" + @@ -9509,7 +9582,8 @@ const file_proto_room_v1_room_proto_rawDesc = "" + "\trocket_id\x18\v \x01(\tR\brocketId\x12%\n" + "\x0econfig_version\x18\f \x01(\x03R\rconfigVersion\x12G\n" + "\flast_rewards\x18\r \x03(\v2$.hyapp.room.v1.RoomRocketRewardGrantR\vlastRewards\x12Q\n" + - "\x10pending_launches\x18\x0e \x03(\v2&.hyapp.room.v1.RoomRocketPendingLaunchR\x0fpendingLaunches\"\xed\x02\n" + + "\x10pending_launches\x18\x0e \x03(\v2&.hyapp.room.v1.RoomRocketPendingLaunchR\x0fpendingLaunches\x12Z\n" + + "\x15current_contributions\x18\x0f \x03(\v2%.hyapp.room.v1.RoomRocketContributionR\x14currentContributions\"\xed\x02\n" + "\x0eRoomRocketInfo\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x126\n" + "\x06levels\x18\x02 \x03(\v2\x1e.hyapp.room.v1.RoomRocketLevelR\x06levels\x124\n" + @@ -10233,7 +10307,7 @@ func file_proto_room_v1_room_proto_rawDescGZIP() []byte { return file_proto_room_v1_room_proto_rawDescData } -var file_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 125) +var file_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 126) var file_proto_room_v1_room_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.room.v1.RequestMeta (*CommandResult)(nil), // 1: hyapp.room.v1.CommandResult @@ -10245,369 +10319,371 @@ var file_proto_room_v1_room_proto_goTypes = []any{ (*RoomRocketRewardItem)(nil), // 7: hyapp.room.v1.RoomRocketRewardItem (*RoomRocketLevel)(nil), // 8: hyapp.room.v1.RoomRocketLevel (*RoomRocketRewardGrant)(nil), // 9: hyapp.room.v1.RoomRocketRewardGrant - (*RoomRocketPendingLaunch)(nil), // 10: hyapp.room.v1.RoomRocketPendingLaunch - (*RoomRocketState)(nil), // 11: hyapp.room.v1.RoomRocketState - (*RoomRocketInfo)(nil), // 12: hyapp.room.v1.RoomRocketInfo - (*RoomRocketGiftFuelRule)(nil), // 13: hyapp.room.v1.RoomRocketGiftFuelRule - (*AdminRoomRocketConfig)(nil), // 14: hyapp.room.v1.AdminRoomRocketConfig - (*AdminGetRoomRocketConfigRequest)(nil), // 15: hyapp.room.v1.AdminGetRoomRocketConfigRequest - (*AdminGetRoomRocketConfigResponse)(nil), // 16: hyapp.room.v1.AdminGetRoomRocketConfigResponse - (*AdminUpdateRoomRocketConfigRequest)(nil), // 17: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest - (*AdminUpdateRoomRocketConfigResponse)(nil), // 18: hyapp.room.v1.AdminUpdateRoomRocketConfigResponse - (*AdminRoomSeatConfig)(nil), // 19: hyapp.room.v1.AdminRoomSeatConfig - (*AdminGetRoomSeatConfigRequest)(nil), // 20: hyapp.room.v1.AdminGetRoomSeatConfigRequest - (*AdminGetRoomSeatConfigResponse)(nil), // 21: hyapp.room.v1.AdminGetRoomSeatConfigResponse - (*AdminUpdateRoomSeatConfigRequest)(nil), // 22: hyapp.room.v1.AdminUpdateRoomSeatConfigRequest - (*AdminUpdateRoomSeatConfigResponse)(nil), // 23: hyapp.room.v1.AdminUpdateRoomSeatConfigResponse - (*AdminRoomPinRoom)(nil), // 24: hyapp.room.v1.AdminRoomPinRoom - (*AdminRoomPin)(nil), // 25: hyapp.room.v1.AdminRoomPin - (*AdminListRoomPinsRequest)(nil), // 26: hyapp.room.v1.AdminListRoomPinsRequest - (*AdminListRoomPinsResponse)(nil), // 27: hyapp.room.v1.AdminListRoomPinsResponse - (*AdminCreateRoomPinRequest)(nil), // 28: hyapp.room.v1.AdminCreateRoomPinRequest - (*AdminCreateRoomPinResponse)(nil), // 29: hyapp.room.v1.AdminCreateRoomPinResponse - (*AdminCancelRoomPinRequest)(nil), // 30: hyapp.room.v1.AdminCancelRoomPinRequest - (*AdminCancelRoomPinResponse)(nil), // 31: hyapp.room.v1.AdminCancelRoomPinResponse - (*RoomBanState)(nil), // 32: hyapp.room.v1.RoomBanState - (*RoomSnapshot)(nil), // 33: hyapp.room.v1.RoomSnapshot - (*RoomBackgroundImage)(nil), // 34: hyapp.room.v1.RoomBackgroundImage - (*SaveRoomBackgroundRequest)(nil), // 35: hyapp.room.v1.SaveRoomBackgroundRequest - (*SaveRoomBackgroundResponse)(nil), // 36: hyapp.room.v1.SaveRoomBackgroundResponse - (*ListRoomBackgroundsRequest)(nil), // 37: hyapp.room.v1.ListRoomBackgroundsRequest - (*ListRoomBackgroundsResponse)(nil), // 38: hyapp.room.v1.ListRoomBackgroundsResponse - (*SetRoomBackgroundRequest)(nil), // 39: hyapp.room.v1.SetRoomBackgroundRequest - (*SetRoomBackgroundResponse)(nil), // 40: hyapp.room.v1.SetRoomBackgroundResponse - (*CreateRoomRequest)(nil), // 41: hyapp.room.v1.CreateRoomRequest - (*CreateRoomResponse)(nil), // 42: hyapp.room.v1.CreateRoomResponse - (*UpdateRoomProfileRequest)(nil), // 43: hyapp.room.v1.UpdateRoomProfileRequest - (*UpdateRoomProfileResponse)(nil), // 44: hyapp.room.v1.UpdateRoomProfileResponse - (*RoomEntryVehicleSnapshot)(nil), // 45: hyapp.room.v1.RoomEntryVehicleSnapshot - (*JoinRoomRequest)(nil), // 46: hyapp.room.v1.JoinRoomRequest - (*JoinRoomResponse)(nil), // 47: hyapp.room.v1.JoinRoomResponse - (*RoomHeartbeatRequest)(nil), // 48: hyapp.room.v1.RoomHeartbeatRequest - (*RoomHeartbeatResponse)(nil), // 49: hyapp.room.v1.RoomHeartbeatResponse - (*LeaveRoomRequest)(nil), // 50: hyapp.room.v1.LeaveRoomRequest - (*LeaveRoomResponse)(nil), // 51: hyapp.room.v1.LeaveRoomResponse - (*CloseRoomRequest)(nil), // 52: hyapp.room.v1.CloseRoomRequest - (*CloseRoomResponse)(nil), // 53: hyapp.room.v1.CloseRoomResponse - (*AdminRoomListItem)(nil), // 54: hyapp.room.v1.AdminRoomListItem - (*AdminListRoomsRequest)(nil), // 55: hyapp.room.v1.AdminListRoomsRequest - (*AdminListRoomsResponse)(nil), // 56: hyapp.room.v1.AdminListRoomsResponse - (*AdminGetRoomRequest)(nil), // 57: hyapp.room.v1.AdminGetRoomRequest - (*AdminGetRoomResponse)(nil), // 58: hyapp.room.v1.AdminGetRoomResponse - (*AdminUpdateRoomRequest)(nil), // 59: hyapp.room.v1.AdminUpdateRoomRequest - (*AdminUpdateRoomResponse)(nil), // 60: hyapp.room.v1.AdminUpdateRoomResponse - (*AdminDeleteRoomRequest)(nil), // 61: hyapp.room.v1.AdminDeleteRoomRequest - (*AdminDeleteRoomResponse)(nil), // 62: hyapp.room.v1.AdminDeleteRoomResponse - (*MicUpRequest)(nil), // 63: hyapp.room.v1.MicUpRequest - (*MicUpResponse)(nil), // 64: hyapp.room.v1.MicUpResponse - (*MicDownRequest)(nil), // 65: hyapp.room.v1.MicDownRequest - (*MicDownResponse)(nil), // 66: hyapp.room.v1.MicDownResponse - (*ChangeMicSeatRequest)(nil), // 67: hyapp.room.v1.ChangeMicSeatRequest - (*ChangeMicSeatResponse)(nil), // 68: hyapp.room.v1.ChangeMicSeatResponse - (*ConfirmMicPublishingRequest)(nil), // 69: hyapp.room.v1.ConfirmMicPublishingRequest - (*ConfirmMicPublishingResponse)(nil), // 70: hyapp.room.v1.ConfirmMicPublishingResponse - (*MicHeartbeatRequest)(nil), // 71: hyapp.room.v1.MicHeartbeatRequest - (*MicHeartbeatResponse)(nil), // 72: hyapp.room.v1.MicHeartbeatResponse - (*SetMicMuteRequest)(nil), // 73: hyapp.room.v1.SetMicMuteRequest - (*SetMicMuteResponse)(nil), // 74: hyapp.room.v1.SetMicMuteResponse - (*ApplyRTCEventRequest)(nil), // 75: hyapp.room.v1.ApplyRTCEventRequest - (*ApplyRTCEventResponse)(nil), // 76: hyapp.room.v1.ApplyRTCEventResponse - (*SetMicSeatLockRequest)(nil), // 77: hyapp.room.v1.SetMicSeatLockRequest - (*SetMicSeatLockResponse)(nil), // 78: hyapp.room.v1.SetMicSeatLockResponse - (*SetChatEnabledRequest)(nil), // 79: hyapp.room.v1.SetChatEnabledRequest - (*SetChatEnabledResponse)(nil), // 80: hyapp.room.v1.SetChatEnabledResponse - (*SetRoomPasswordRequest)(nil), // 81: hyapp.room.v1.SetRoomPasswordRequest - (*SetRoomPasswordResponse)(nil), // 82: hyapp.room.v1.SetRoomPasswordResponse - (*SetRoomAdminRequest)(nil), // 83: hyapp.room.v1.SetRoomAdminRequest - (*SetRoomAdminResponse)(nil), // 84: hyapp.room.v1.SetRoomAdminResponse - (*MuteUserRequest)(nil), // 85: hyapp.room.v1.MuteUserRequest - (*MuteUserResponse)(nil), // 86: hyapp.room.v1.MuteUserResponse - (*KickUserRequest)(nil), // 87: hyapp.room.v1.KickUserRequest - (*KickUserResponse)(nil), // 88: hyapp.room.v1.KickUserResponse - (*UnbanUserRequest)(nil), // 89: hyapp.room.v1.UnbanUserRequest - (*UnbanUserResponse)(nil), // 90: hyapp.room.v1.UnbanUserResponse - (*SystemEvictUserRequest)(nil), // 91: hyapp.room.v1.SystemEvictUserRequest - (*SystemEvictUserResponse)(nil), // 92: hyapp.room.v1.SystemEvictUserResponse - (*SendGiftRequest)(nil), // 93: hyapp.room.v1.SendGiftRequest - (*SendGiftResponse)(nil), // 94: hyapp.room.v1.SendGiftResponse - (*CheckSpeakPermissionRequest)(nil), // 95: hyapp.room.v1.CheckSpeakPermissionRequest - (*CheckSpeakPermissionResponse)(nil), // 96: hyapp.room.v1.CheckSpeakPermissionResponse - (*VerifyRoomPresenceRequest)(nil), // 97: hyapp.room.v1.VerifyRoomPresenceRequest - (*VerifyRoomPresenceResponse)(nil), // 98: hyapp.room.v1.VerifyRoomPresenceResponse - (*ListRoomsRequest)(nil), // 99: hyapp.room.v1.ListRoomsRequest - (*ListRoomFeedsRequest)(nil), // 100: hyapp.room.v1.ListRoomFeedsRequest - (*ListRoomGiftLeaderboardRequest)(nil), // 101: hyapp.room.v1.ListRoomGiftLeaderboardRequest - (*RoomFeedRelatedUser)(nil), // 102: hyapp.room.v1.RoomFeedRelatedUser - (*RoomListItem)(nil), // 103: hyapp.room.v1.RoomListItem - (*ListRoomsResponse)(nil), // 104: hyapp.room.v1.ListRoomsResponse - (*RoomGiftLeaderboardItem)(nil), // 105: hyapp.room.v1.RoomGiftLeaderboardItem - (*ListRoomGiftLeaderboardResponse)(nil), // 106: hyapp.room.v1.ListRoomGiftLeaderboardResponse - (*GetMyRoomRequest)(nil), // 107: hyapp.room.v1.GetMyRoomRequest - (*GetMyRoomResponse)(nil), // 108: hyapp.room.v1.GetMyRoomResponse - (*GetCurrentRoomRequest)(nil), // 109: hyapp.room.v1.GetCurrentRoomRequest - (*GetCurrentRoomResponse)(nil), // 110: hyapp.room.v1.GetCurrentRoomResponse - (*GetRoomSnapshotRequest)(nil), // 111: hyapp.room.v1.GetRoomSnapshotRequest - (*GetRoomSnapshotResponse)(nil), // 112: hyapp.room.v1.GetRoomSnapshotResponse - (*GetRoomRocketRequest)(nil), // 113: hyapp.room.v1.GetRoomRocketRequest - (*GetRoomRocketResponse)(nil), // 114: hyapp.room.v1.GetRoomRocketResponse - (*ListRoomOnlineUsersRequest)(nil), // 115: hyapp.room.v1.ListRoomOnlineUsersRequest - (*ListRoomOnlineUsersResponse)(nil), // 116: hyapp.room.v1.ListRoomOnlineUsersResponse - (*RoomBannedUser)(nil), // 117: hyapp.room.v1.RoomBannedUser - (*ListRoomBannedUsersRequest)(nil), // 118: hyapp.room.v1.ListRoomBannedUsersRequest - (*ListRoomBannedUsersResponse)(nil), // 119: hyapp.room.v1.ListRoomBannedUsersResponse - (*FollowRoomRequest)(nil), // 120: hyapp.room.v1.FollowRoomRequest - (*FollowRoomResponse)(nil), // 121: hyapp.room.v1.FollowRoomResponse - (*UnfollowRoomRequest)(nil), // 122: hyapp.room.v1.UnfollowRoomRequest - (*UnfollowRoomResponse)(nil), // 123: hyapp.room.v1.UnfollowRoomResponse - nil, // 124: hyapp.room.v1.RoomSnapshot.RoomExtEntry + (*RoomRocketContribution)(nil), // 10: hyapp.room.v1.RoomRocketContribution + (*RoomRocketPendingLaunch)(nil), // 11: hyapp.room.v1.RoomRocketPendingLaunch + (*RoomRocketState)(nil), // 12: hyapp.room.v1.RoomRocketState + (*RoomRocketInfo)(nil), // 13: hyapp.room.v1.RoomRocketInfo + (*RoomRocketGiftFuelRule)(nil), // 14: hyapp.room.v1.RoomRocketGiftFuelRule + (*AdminRoomRocketConfig)(nil), // 15: hyapp.room.v1.AdminRoomRocketConfig + (*AdminGetRoomRocketConfigRequest)(nil), // 16: hyapp.room.v1.AdminGetRoomRocketConfigRequest + (*AdminGetRoomRocketConfigResponse)(nil), // 17: hyapp.room.v1.AdminGetRoomRocketConfigResponse + (*AdminUpdateRoomRocketConfigRequest)(nil), // 18: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest + (*AdminUpdateRoomRocketConfigResponse)(nil), // 19: hyapp.room.v1.AdminUpdateRoomRocketConfigResponse + (*AdminRoomSeatConfig)(nil), // 20: hyapp.room.v1.AdminRoomSeatConfig + (*AdminGetRoomSeatConfigRequest)(nil), // 21: hyapp.room.v1.AdminGetRoomSeatConfigRequest + (*AdminGetRoomSeatConfigResponse)(nil), // 22: hyapp.room.v1.AdminGetRoomSeatConfigResponse + (*AdminUpdateRoomSeatConfigRequest)(nil), // 23: hyapp.room.v1.AdminUpdateRoomSeatConfigRequest + (*AdminUpdateRoomSeatConfigResponse)(nil), // 24: hyapp.room.v1.AdminUpdateRoomSeatConfigResponse + (*AdminRoomPinRoom)(nil), // 25: hyapp.room.v1.AdminRoomPinRoom + (*AdminRoomPin)(nil), // 26: hyapp.room.v1.AdminRoomPin + (*AdminListRoomPinsRequest)(nil), // 27: hyapp.room.v1.AdminListRoomPinsRequest + (*AdminListRoomPinsResponse)(nil), // 28: hyapp.room.v1.AdminListRoomPinsResponse + (*AdminCreateRoomPinRequest)(nil), // 29: hyapp.room.v1.AdminCreateRoomPinRequest + (*AdminCreateRoomPinResponse)(nil), // 30: hyapp.room.v1.AdminCreateRoomPinResponse + (*AdminCancelRoomPinRequest)(nil), // 31: hyapp.room.v1.AdminCancelRoomPinRequest + (*AdminCancelRoomPinResponse)(nil), // 32: hyapp.room.v1.AdminCancelRoomPinResponse + (*RoomBanState)(nil), // 33: hyapp.room.v1.RoomBanState + (*RoomSnapshot)(nil), // 34: hyapp.room.v1.RoomSnapshot + (*RoomBackgroundImage)(nil), // 35: hyapp.room.v1.RoomBackgroundImage + (*SaveRoomBackgroundRequest)(nil), // 36: hyapp.room.v1.SaveRoomBackgroundRequest + (*SaveRoomBackgroundResponse)(nil), // 37: hyapp.room.v1.SaveRoomBackgroundResponse + (*ListRoomBackgroundsRequest)(nil), // 38: hyapp.room.v1.ListRoomBackgroundsRequest + (*ListRoomBackgroundsResponse)(nil), // 39: hyapp.room.v1.ListRoomBackgroundsResponse + (*SetRoomBackgroundRequest)(nil), // 40: hyapp.room.v1.SetRoomBackgroundRequest + (*SetRoomBackgroundResponse)(nil), // 41: hyapp.room.v1.SetRoomBackgroundResponse + (*CreateRoomRequest)(nil), // 42: hyapp.room.v1.CreateRoomRequest + (*CreateRoomResponse)(nil), // 43: hyapp.room.v1.CreateRoomResponse + (*UpdateRoomProfileRequest)(nil), // 44: hyapp.room.v1.UpdateRoomProfileRequest + (*UpdateRoomProfileResponse)(nil), // 45: hyapp.room.v1.UpdateRoomProfileResponse + (*RoomEntryVehicleSnapshot)(nil), // 46: hyapp.room.v1.RoomEntryVehicleSnapshot + (*JoinRoomRequest)(nil), // 47: hyapp.room.v1.JoinRoomRequest + (*JoinRoomResponse)(nil), // 48: hyapp.room.v1.JoinRoomResponse + (*RoomHeartbeatRequest)(nil), // 49: hyapp.room.v1.RoomHeartbeatRequest + (*RoomHeartbeatResponse)(nil), // 50: hyapp.room.v1.RoomHeartbeatResponse + (*LeaveRoomRequest)(nil), // 51: hyapp.room.v1.LeaveRoomRequest + (*LeaveRoomResponse)(nil), // 52: hyapp.room.v1.LeaveRoomResponse + (*CloseRoomRequest)(nil), // 53: hyapp.room.v1.CloseRoomRequest + (*CloseRoomResponse)(nil), // 54: hyapp.room.v1.CloseRoomResponse + (*AdminRoomListItem)(nil), // 55: hyapp.room.v1.AdminRoomListItem + (*AdminListRoomsRequest)(nil), // 56: hyapp.room.v1.AdminListRoomsRequest + (*AdminListRoomsResponse)(nil), // 57: hyapp.room.v1.AdminListRoomsResponse + (*AdminGetRoomRequest)(nil), // 58: hyapp.room.v1.AdminGetRoomRequest + (*AdminGetRoomResponse)(nil), // 59: hyapp.room.v1.AdminGetRoomResponse + (*AdminUpdateRoomRequest)(nil), // 60: hyapp.room.v1.AdminUpdateRoomRequest + (*AdminUpdateRoomResponse)(nil), // 61: hyapp.room.v1.AdminUpdateRoomResponse + (*AdminDeleteRoomRequest)(nil), // 62: hyapp.room.v1.AdminDeleteRoomRequest + (*AdminDeleteRoomResponse)(nil), // 63: hyapp.room.v1.AdminDeleteRoomResponse + (*MicUpRequest)(nil), // 64: hyapp.room.v1.MicUpRequest + (*MicUpResponse)(nil), // 65: hyapp.room.v1.MicUpResponse + (*MicDownRequest)(nil), // 66: hyapp.room.v1.MicDownRequest + (*MicDownResponse)(nil), // 67: hyapp.room.v1.MicDownResponse + (*ChangeMicSeatRequest)(nil), // 68: hyapp.room.v1.ChangeMicSeatRequest + (*ChangeMicSeatResponse)(nil), // 69: hyapp.room.v1.ChangeMicSeatResponse + (*ConfirmMicPublishingRequest)(nil), // 70: hyapp.room.v1.ConfirmMicPublishingRequest + (*ConfirmMicPublishingResponse)(nil), // 71: hyapp.room.v1.ConfirmMicPublishingResponse + (*MicHeartbeatRequest)(nil), // 72: hyapp.room.v1.MicHeartbeatRequest + (*MicHeartbeatResponse)(nil), // 73: hyapp.room.v1.MicHeartbeatResponse + (*SetMicMuteRequest)(nil), // 74: hyapp.room.v1.SetMicMuteRequest + (*SetMicMuteResponse)(nil), // 75: hyapp.room.v1.SetMicMuteResponse + (*ApplyRTCEventRequest)(nil), // 76: hyapp.room.v1.ApplyRTCEventRequest + (*ApplyRTCEventResponse)(nil), // 77: hyapp.room.v1.ApplyRTCEventResponse + (*SetMicSeatLockRequest)(nil), // 78: hyapp.room.v1.SetMicSeatLockRequest + (*SetMicSeatLockResponse)(nil), // 79: hyapp.room.v1.SetMicSeatLockResponse + (*SetChatEnabledRequest)(nil), // 80: hyapp.room.v1.SetChatEnabledRequest + (*SetChatEnabledResponse)(nil), // 81: hyapp.room.v1.SetChatEnabledResponse + (*SetRoomPasswordRequest)(nil), // 82: hyapp.room.v1.SetRoomPasswordRequest + (*SetRoomPasswordResponse)(nil), // 83: hyapp.room.v1.SetRoomPasswordResponse + (*SetRoomAdminRequest)(nil), // 84: hyapp.room.v1.SetRoomAdminRequest + (*SetRoomAdminResponse)(nil), // 85: hyapp.room.v1.SetRoomAdminResponse + (*MuteUserRequest)(nil), // 86: hyapp.room.v1.MuteUserRequest + (*MuteUserResponse)(nil), // 87: hyapp.room.v1.MuteUserResponse + (*KickUserRequest)(nil), // 88: hyapp.room.v1.KickUserRequest + (*KickUserResponse)(nil), // 89: hyapp.room.v1.KickUserResponse + (*UnbanUserRequest)(nil), // 90: hyapp.room.v1.UnbanUserRequest + (*UnbanUserResponse)(nil), // 91: hyapp.room.v1.UnbanUserResponse + (*SystemEvictUserRequest)(nil), // 92: hyapp.room.v1.SystemEvictUserRequest + (*SystemEvictUserResponse)(nil), // 93: hyapp.room.v1.SystemEvictUserResponse + (*SendGiftRequest)(nil), // 94: hyapp.room.v1.SendGiftRequest + (*SendGiftResponse)(nil), // 95: hyapp.room.v1.SendGiftResponse + (*CheckSpeakPermissionRequest)(nil), // 96: hyapp.room.v1.CheckSpeakPermissionRequest + (*CheckSpeakPermissionResponse)(nil), // 97: hyapp.room.v1.CheckSpeakPermissionResponse + (*VerifyRoomPresenceRequest)(nil), // 98: hyapp.room.v1.VerifyRoomPresenceRequest + (*VerifyRoomPresenceResponse)(nil), // 99: hyapp.room.v1.VerifyRoomPresenceResponse + (*ListRoomsRequest)(nil), // 100: hyapp.room.v1.ListRoomsRequest + (*ListRoomFeedsRequest)(nil), // 101: hyapp.room.v1.ListRoomFeedsRequest + (*ListRoomGiftLeaderboardRequest)(nil), // 102: hyapp.room.v1.ListRoomGiftLeaderboardRequest + (*RoomFeedRelatedUser)(nil), // 103: hyapp.room.v1.RoomFeedRelatedUser + (*RoomListItem)(nil), // 104: hyapp.room.v1.RoomListItem + (*ListRoomsResponse)(nil), // 105: hyapp.room.v1.ListRoomsResponse + (*RoomGiftLeaderboardItem)(nil), // 106: hyapp.room.v1.RoomGiftLeaderboardItem + (*ListRoomGiftLeaderboardResponse)(nil), // 107: hyapp.room.v1.ListRoomGiftLeaderboardResponse + (*GetMyRoomRequest)(nil), // 108: hyapp.room.v1.GetMyRoomRequest + (*GetMyRoomResponse)(nil), // 109: hyapp.room.v1.GetMyRoomResponse + (*GetCurrentRoomRequest)(nil), // 110: hyapp.room.v1.GetCurrentRoomRequest + (*GetCurrentRoomResponse)(nil), // 111: hyapp.room.v1.GetCurrentRoomResponse + (*GetRoomSnapshotRequest)(nil), // 112: hyapp.room.v1.GetRoomSnapshotRequest + (*GetRoomSnapshotResponse)(nil), // 113: hyapp.room.v1.GetRoomSnapshotResponse + (*GetRoomRocketRequest)(nil), // 114: hyapp.room.v1.GetRoomRocketRequest + (*GetRoomRocketResponse)(nil), // 115: hyapp.room.v1.GetRoomRocketResponse + (*ListRoomOnlineUsersRequest)(nil), // 116: hyapp.room.v1.ListRoomOnlineUsersRequest + (*ListRoomOnlineUsersResponse)(nil), // 117: hyapp.room.v1.ListRoomOnlineUsersResponse + (*RoomBannedUser)(nil), // 118: hyapp.room.v1.RoomBannedUser + (*ListRoomBannedUsersRequest)(nil), // 119: hyapp.room.v1.ListRoomBannedUsersRequest + (*ListRoomBannedUsersResponse)(nil), // 120: hyapp.room.v1.ListRoomBannedUsersResponse + (*FollowRoomRequest)(nil), // 121: hyapp.room.v1.FollowRoomRequest + (*FollowRoomResponse)(nil), // 122: hyapp.room.v1.FollowRoomResponse + (*UnfollowRoomRequest)(nil), // 123: hyapp.room.v1.UnfollowRoomRequest + (*UnfollowRoomResponse)(nil), // 124: hyapp.room.v1.UnfollowRoomResponse + nil, // 125: hyapp.room.v1.RoomSnapshot.RoomExtEntry } var file_proto_room_v1_room_proto_depIdxs = []int32{ 7, // 0: hyapp.room.v1.RoomRocketLevel.in_room_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem 7, // 1: hyapp.room.v1.RoomRocketLevel.top1_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem 7, // 2: hyapp.room.v1.RoomRocketLevel.igniter_rewards:type_name -> hyapp.room.v1.RoomRocketRewardItem 9, // 3: hyapp.room.v1.RoomRocketState.last_rewards:type_name -> hyapp.room.v1.RoomRocketRewardGrant - 10, // 4: hyapp.room.v1.RoomRocketState.pending_launches:type_name -> hyapp.room.v1.RoomRocketPendingLaunch - 8, // 5: hyapp.room.v1.RoomRocketInfo.levels:type_name -> hyapp.room.v1.RoomRocketLevel - 11, // 6: hyapp.room.v1.RoomRocketInfo.state:type_name -> hyapp.room.v1.RoomRocketState - 8, // 7: hyapp.room.v1.AdminRoomRocketConfig.levels:type_name -> hyapp.room.v1.RoomRocketLevel - 13, // 8: hyapp.room.v1.AdminRoomRocketConfig.gift_fuel_rules:type_name -> hyapp.room.v1.RoomRocketGiftFuelRule - 0, // 9: hyapp.room.v1.AdminGetRoomRocketConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 14, // 10: hyapp.room.v1.AdminGetRoomRocketConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig - 0, // 11: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 14, // 12: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig - 14, // 13: hyapp.room.v1.AdminUpdateRoomRocketConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig - 0, // 14: hyapp.room.v1.AdminGetRoomSeatConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 19, // 15: hyapp.room.v1.AdminGetRoomSeatConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomSeatConfig - 0, // 16: hyapp.room.v1.AdminUpdateRoomSeatConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 19, // 17: hyapp.room.v1.AdminUpdateRoomSeatConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomSeatConfig - 24, // 18: hyapp.room.v1.AdminRoomPin.room:type_name -> hyapp.room.v1.AdminRoomPinRoom - 0, // 19: hyapp.room.v1.AdminListRoomPinsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 25, // 20: hyapp.room.v1.AdminListRoomPinsResponse.pins:type_name -> hyapp.room.v1.AdminRoomPin - 0, // 21: hyapp.room.v1.AdminCreateRoomPinRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 25, // 22: hyapp.room.v1.AdminCreateRoomPinResponse.pin:type_name -> hyapp.room.v1.AdminRoomPin - 0, // 23: hyapp.room.v1.AdminCancelRoomPinRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 25, // 24: hyapp.room.v1.AdminCancelRoomPinResponse.pin:type_name -> hyapp.room.v1.AdminRoomPin - 4, // 25: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState - 2, // 26: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser - 5, // 27: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem - 124, // 28: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry - 11, // 29: hyapp.room.v1.RoomSnapshot.rocket:type_name -> hyapp.room.v1.RoomRocketState - 32, // 30: hyapp.room.v1.RoomSnapshot.ban_states:type_name -> hyapp.room.v1.RoomBanState - 0, // 31: hyapp.room.v1.SaveRoomBackgroundRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 34, // 32: hyapp.room.v1.SaveRoomBackgroundResponse.background:type_name -> hyapp.room.v1.RoomBackgroundImage - 0, // 33: hyapp.room.v1.ListRoomBackgroundsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 34, // 34: hyapp.room.v1.ListRoomBackgroundsResponse.backgrounds:type_name -> hyapp.room.v1.RoomBackgroundImage - 0, // 35: hyapp.room.v1.SetRoomBackgroundRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 36: hyapp.room.v1.SetRoomBackgroundResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 37: hyapp.room.v1.SetRoomBackgroundResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 34, // 38: hyapp.room.v1.SetRoomBackgroundResponse.background:type_name -> hyapp.room.v1.RoomBackgroundImage - 0, // 39: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 40: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 41: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 42: hyapp.room.v1.UpdateRoomProfileRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 43: hyapp.room.v1.UpdateRoomProfileResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 44: hyapp.room.v1.UpdateRoomProfileResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 45: hyapp.room.v1.JoinRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 45, // 46: hyapp.room.v1.JoinRoomRequest.entry_vehicle:type_name -> hyapp.room.v1.RoomEntryVehicleSnapshot - 1, // 47: hyapp.room.v1.JoinRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 2, // 48: hyapp.room.v1.JoinRoomResponse.user:type_name -> hyapp.room.v1.RoomUser - 33, // 49: hyapp.room.v1.JoinRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 50: hyapp.room.v1.RoomHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 51: hyapp.room.v1.RoomHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult - 2, // 52: hyapp.room.v1.RoomHeartbeatResponse.user:type_name -> hyapp.room.v1.RoomUser - 33, // 53: hyapp.room.v1.RoomHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 54: hyapp.room.v1.LeaveRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 55: hyapp.room.v1.LeaveRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 56: hyapp.room.v1.LeaveRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 57: hyapp.room.v1.CloseRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 58: hyapp.room.v1.CloseRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 59: hyapp.room.v1.CloseRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 60: hyapp.room.v1.AdminListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 54, // 61: hyapp.room.v1.AdminListRoomsResponse.rooms:type_name -> hyapp.room.v1.AdminRoomListItem - 0, // 62: hyapp.room.v1.AdminGetRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 54, // 63: hyapp.room.v1.AdminGetRoomResponse.room:type_name -> hyapp.room.v1.AdminRoomListItem - 0, // 64: hyapp.room.v1.AdminUpdateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 65: hyapp.room.v1.AdminUpdateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 66: hyapp.room.v1.AdminUpdateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 67: hyapp.room.v1.AdminDeleteRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 68: hyapp.room.v1.AdminDeleteRoomResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 69: hyapp.room.v1.AdminDeleteRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 70: hyapp.room.v1.MicUpRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 71: hyapp.room.v1.MicUpResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 72: hyapp.room.v1.MicUpResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 73: hyapp.room.v1.MicDownRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 74: hyapp.room.v1.MicDownResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 75: hyapp.room.v1.MicDownResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 76: hyapp.room.v1.ChangeMicSeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 77: hyapp.room.v1.ChangeMicSeatResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 78: hyapp.room.v1.ChangeMicSeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 79: hyapp.room.v1.ConfirmMicPublishingRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 80: hyapp.room.v1.ConfirmMicPublishingResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 81: hyapp.room.v1.ConfirmMicPublishingResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 82: hyapp.room.v1.MicHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 83: hyapp.room.v1.MicHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 84: hyapp.room.v1.MicHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 85: hyapp.room.v1.SetMicMuteRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 86: hyapp.room.v1.SetMicMuteResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 87: hyapp.room.v1.SetMicMuteResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 88: hyapp.room.v1.ApplyRTCEventRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 89: hyapp.room.v1.ApplyRTCEventResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 90: hyapp.room.v1.ApplyRTCEventResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 91: hyapp.room.v1.SetMicSeatLockRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 92: hyapp.room.v1.SetMicSeatLockResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 93: hyapp.room.v1.SetMicSeatLockResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 94: hyapp.room.v1.SetChatEnabledRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 95: hyapp.room.v1.SetChatEnabledResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 96: hyapp.room.v1.SetChatEnabledResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 97: hyapp.room.v1.SetRoomPasswordRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 98: hyapp.room.v1.SetRoomPasswordResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 99: hyapp.room.v1.SetRoomPasswordResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 100: hyapp.room.v1.SetRoomAdminRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 101: hyapp.room.v1.SetRoomAdminResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 102: hyapp.room.v1.SetRoomAdminResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 103: hyapp.room.v1.MuteUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 104: hyapp.room.v1.MuteUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 105: hyapp.room.v1.MuteUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 106: hyapp.room.v1.KickUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 107: hyapp.room.v1.KickUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 108: hyapp.room.v1.KickUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 109: hyapp.room.v1.UnbanUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 110: hyapp.room.v1.UnbanUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 111: hyapp.room.v1.UnbanUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 112: hyapp.room.v1.SystemEvictUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 113: hyapp.room.v1.SystemEvictUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 33, // 114: hyapp.room.v1.SystemEvictUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 115: hyapp.room.v1.SendGiftRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 116: hyapp.room.v1.SendGiftResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 117: hyapp.room.v1.SendGiftResponse.gift_rank:type_name -> hyapp.room.v1.RankItem - 33, // 118: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 11, // 119: hyapp.room.v1.SendGiftResponse.rocket:type_name -> hyapp.room.v1.RoomRocketState - 6, // 120: hyapp.room.v1.SendGiftResponse.lucky_gift:type_name -> hyapp.room.v1.LuckyGiftDrawResult - 6, // 121: hyapp.room.v1.SendGiftResponse.lucky_gifts:type_name -> hyapp.room.v1.LuckyGiftDrawResult - 0, // 122: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 123: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 102, // 124: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser - 0, // 125: hyapp.room.v1.ListRoomGiftLeaderboardRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 103, // 126: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem - 103, // 127: hyapp.room.v1.RoomGiftLeaderboardItem.room:type_name -> hyapp.room.v1.RoomListItem - 105, // 128: hyapp.room.v1.ListRoomGiftLeaderboardResponse.items:type_name -> hyapp.room.v1.RoomGiftLeaderboardItem - 0, // 129: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 103, // 130: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem - 0, // 131: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 132: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 33, // 133: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 134: hyapp.room.v1.GetRoomRocketRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 12, // 135: hyapp.room.v1.GetRoomRocketResponse.rocket:type_name -> hyapp.room.v1.RoomRocketInfo - 0, // 136: hyapp.room.v1.ListRoomOnlineUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 2, // 137: hyapp.room.v1.ListRoomOnlineUsersResponse.users:type_name -> hyapp.room.v1.RoomUser - 3, // 138: hyapp.room.v1.ListRoomOnlineUsersResponse.items:type_name -> hyapp.room.v1.RoomOnlineUser - 0, // 139: hyapp.room.v1.ListRoomBannedUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 117, // 140: hyapp.room.v1.ListRoomBannedUsersResponse.items:type_name -> hyapp.room.v1.RoomBannedUser - 0, // 141: hyapp.room.v1.FollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 142: hyapp.room.v1.UnfollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 41, // 143: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest - 43, // 144: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:input_type -> hyapp.room.v1.UpdateRoomProfileRequest - 35, // 145: hyapp.room.v1.RoomCommandService.SaveRoomBackground:input_type -> hyapp.room.v1.SaveRoomBackgroundRequest - 39, // 146: hyapp.room.v1.RoomCommandService.SetRoomBackground:input_type -> hyapp.room.v1.SetRoomBackgroundRequest - 46, // 147: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest - 48, // 148: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest - 50, // 149: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest - 52, // 150: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest - 59, // 151: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:input_type -> hyapp.room.v1.AdminUpdateRoomRequest - 61, // 152: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:input_type -> hyapp.room.v1.AdminDeleteRoomRequest - 17, // 153: hyapp.room.v1.RoomCommandService.AdminUpdateRoomRocketConfig:input_type -> hyapp.room.v1.AdminUpdateRoomRocketConfigRequest - 22, // 154: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:input_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigRequest - 28, // 155: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:input_type -> hyapp.room.v1.AdminCreateRoomPinRequest - 30, // 156: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:input_type -> hyapp.room.v1.AdminCancelRoomPinRequest - 63, // 157: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest - 65, // 158: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest - 67, // 159: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest - 69, // 160: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest - 71, // 161: hyapp.room.v1.RoomCommandService.MicHeartbeat:input_type -> hyapp.room.v1.MicHeartbeatRequest - 73, // 162: hyapp.room.v1.RoomCommandService.SetMicMute:input_type -> hyapp.room.v1.SetMicMuteRequest - 75, // 163: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest - 77, // 164: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest - 79, // 165: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest - 81, // 166: hyapp.room.v1.RoomCommandService.SetRoomPassword:input_type -> hyapp.room.v1.SetRoomPasswordRequest - 83, // 167: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest - 85, // 168: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest - 87, // 169: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest - 89, // 170: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest - 91, // 171: hyapp.room.v1.RoomCommandService.SystemEvictUser:input_type -> hyapp.room.v1.SystemEvictUserRequest - 93, // 172: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest - 120, // 173: hyapp.room.v1.RoomCommandService.FollowRoom:input_type -> hyapp.room.v1.FollowRoomRequest - 122, // 174: hyapp.room.v1.RoomCommandService.UnfollowRoom:input_type -> hyapp.room.v1.UnfollowRoomRequest - 95, // 175: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest - 97, // 176: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest - 55, // 177: hyapp.room.v1.RoomQueryService.AdminListRooms:input_type -> hyapp.room.v1.AdminListRoomsRequest - 57, // 178: hyapp.room.v1.RoomQueryService.AdminGetRoom:input_type -> hyapp.room.v1.AdminGetRoomRequest - 15, // 179: hyapp.room.v1.RoomQueryService.AdminGetRoomRocketConfig:input_type -> hyapp.room.v1.AdminGetRoomRocketConfigRequest - 20, // 180: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:input_type -> hyapp.room.v1.AdminGetRoomSeatConfigRequest - 26, // 181: hyapp.room.v1.RoomQueryService.AdminListRoomPins:input_type -> hyapp.room.v1.AdminListRoomPinsRequest - 99, // 182: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest - 100, // 183: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest - 101, // 184: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:input_type -> hyapp.room.v1.ListRoomGiftLeaderboardRequest - 107, // 185: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest - 109, // 186: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest - 111, // 187: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest - 37, // 188: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:input_type -> hyapp.room.v1.ListRoomBackgroundsRequest - 113, // 189: hyapp.room.v1.RoomQueryService.GetRoomRocket:input_type -> hyapp.room.v1.GetRoomRocketRequest - 115, // 190: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:input_type -> hyapp.room.v1.ListRoomOnlineUsersRequest - 118, // 191: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:input_type -> hyapp.room.v1.ListRoomBannedUsersRequest - 42, // 192: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse - 44, // 193: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:output_type -> hyapp.room.v1.UpdateRoomProfileResponse - 36, // 194: hyapp.room.v1.RoomCommandService.SaveRoomBackground:output_type -> hyapp.room.v1.SaveRoomBackgroundResponse - 40, // 195: hyapp.room.v1.RoomCommandService.SetRoomBackground:output_type -> hyapp.room.v1.SetRoomBackgroundResponse - 47, // 196: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse - 49, // 197: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse - 51, // 198: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse - 53, // 199: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse - 60, // 200: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:output_type -> hyapp.room.v1.AdminUpdateRoomResponse - 62, // 201: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:output_type -> hyapp.room.v1.AdminDeleteRoomResponse - 18, // 202: hyapp.room.v1.RoomCommandService.AdminUpdateRoomRocketConfig:output_type -> hyapp.room.v1.AdminUpdateRoomRocketConfigResponse - 23, // 203: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:output_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigResponse - 29, // 204: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:output_type -> hyapp.room.v1.AdminCreateRoomPinResponse - 31, // 205: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:output_type -> hyapp.room.v1.AdminCancelRoomPinResponse - 64, // 206: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse - 66, // 207: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse - 68, // 208: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse - 70, // 209: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse - 72, // 210: hyapp.room.v1.RoomCommandService.MicHeartbeat:output_type -> hyapp.room.v1.MicHeartbeatResponse - 74, // 211: hyapp.room.v1.RoomCommandService.SetMicMute:output_type -> hyapp.room.v1.SetMicMuteResponse - 76, // 212: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse - 78, // 213: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse - 80, // 214: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse - 82, // 215: hyapp.room.v1.RoomCommandService.SetRoomPassword:output_type -> hyapp.room.v1.SetRoomPasswordResponse - 84, // 216: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse - 86, // 217: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse - 88, // 218: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse - 90, // 219: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse - 92, // 220: hyapp.room.v1.RoomCommandService.SystemEvictUser:output_type -> hyapp.room.v1.SystemEvictUserResponse - 94, // 221: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse - 121, // 222: hyapp.room.v1.RoomCommandService.FollowRoom:output_type -> hyapp.room.v1.FollowRoomResponse - 123, // 223: hyapp.room.v1.RoomCommandService.UnfollowRoom:output_type -> hyapp.room.v1.UnfollowRoomResponse - 96, // 224: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse - 98, // 225: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse - 56, // 226: hyapp.room.v1.RoomQueryService.AdminListRooms:output_type -> hyapp.room.v1.AdminListRoomsResponse - 58, // 227: hyapp.room.v1.RoomQueryService.AdminGetRoom:output_type -> hyapp.room.v1.AdminGetRoomResponse - 16, // 228: hyapp.room.v1.RoomQueryService.AdminGetRoomRocketConfig:output_type -> hyapp.room.v1.AdminGetRoomRocketConfigResponse - 21, // 229: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:output_type -> hyapp.room.v1.AdminGetRoomSeatConfigResponse - 27, // 230: hyapp.room.v1.RoomQueryService.AdminListRoomPins:output_type -> hyapp.room.v1.AdminListRoomPinsResponse - 104, // 231: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse - 104, // 232: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse - 106, // 233: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:output_type -> hyapp.room.v1.ListRoomGiftLeaderboardResponse - 108, // 234: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse - 110, // 235: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse - 112, // 236: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse - 38, // 237: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:output_type -> hyapp.room.v1.ListRoomBackgroundsResponse - 114, // 238: hyapp.room.v1.RoomQueryService.GetRoomRocket:output_type -> hyapp.room.v1.GetRoomRocketResponse - 116, // 239: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:output_type -> hyapp.room.v1.ListRoomOnlineUsersResponse - 119, // 240: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:output_type -> hyapp.room.v1.ListRoomBannedUsersResponse - 192, // [192:241] is the sub-list for method output_type - 143, // [143:192] is the sub-list for method input_type - 143, // [143:143] is the sub-list for extension type_name - 143, // [143:143] is the sub-list for extension extendee - 0, // [0:143] is the sub-list for field type_name + 11, // 4: hyapp.room.v1.RoomRocketState.pending_launches:type_name -> hyapp.room.v1.RoomRocketPendingLaunch + 10, // 5: hyapp.room.v1.RoomRocketState.current_contributions:type_name -> hyapp.room.v1.RoomRocketContribution + 8, // 6: hyapp.room.v1.RoomRocketInfo.levels:type_name -> hyapp.room.v1.RoomRocketLevel + 12, // 7: hyapp.room.v1.RoomRocketInfo.state:type_name -> hyapp.room.v1.RoomRocketState + 8, // 8: hyapp.room.v1.AdminRoomRocketConfig.levels:type_name -> hyapp.room.v1.RoomRocketLevel + 14, // 9: hyapp.room.v1.AdminRoomRocketConfig.gift_fuel_rules:type_name -> hyapp.room.v1.RoomRocketGiftFuelRule + 0, // 10: hyapp.room.v1.AdminGetRoomRocketConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 15, // 11: hyapp.room.v1.AdminGetRoomRocketConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig + 0, // 12: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 15, // 13: hyapp.room.v1.AdminUpdateRoomRocketConfigRequest.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig + 15, // 14: hyapp.room.v1.AdminUpdateRoomRocketConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomRocketConfig + 0, // 15: hyapp.room.v1.AdminGetRoomSeatConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 20, // 16: hyapp.room.v1.AdminGetRoomSeatConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomSeatConfig + 0, // 17: hyapp.room.v1.AdminUpdateRoomSeatConfigRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 20, // 18: hyapp.room.v1.AdminUpdateRoomSeatConfigResponse.config:type_name -> hyapp.room.v1.AdminRoomSeatConfig + 25, // 19: hyapp.room.v1.AdminRoomPin.room:type_name -> hyapp.room.v1.AdminRoomPinRoom + 0, // 20: hyapp.room.v1.AdminListRoomPinsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 26, // 21: hyapp.room.v1.AdminListRoomPinsResponse.pins:type_name -> hyapp.room.v1.AdminRoomPin + 0, // 22: hyapp.room.v1.AdminCreateRoomPinRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 26, // 23: hyapp.room.v1.AdminCreateRoomPinResponse.pin:type_name -> hyapp.room.v1.AdminRoomPin + 0, // 24: hyapp.room.v1.AdminCancelRoomPinRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 26, // 25: hyapp.room.v1.AdminCancelRoomPinResponse.pin:type_name -> hyapp.room.v1.AdminRoomPin + 4, // 26: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState + 2, // 27: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser + 5, // 28: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem + 125, // 29: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry + 12, // 30: hyapp.room.v1.RoomSnapshot.rocket:type_name -> hyapp.room.v1.RoomRocketState + 33, // 31: hyapp.room.v1.RoomSnapshot.ban_states:type_name -> hyapp.room.v1.RoomBanState + 0, // 32: hyapp.room.v1.SaveRoomBackgroundRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 35, // 33: hyapp.room.v1.SaveRoomBackgroundResponse.background:type_name -> hyapp.room.v1.RoomBackgroundImage + 0, // 34: hyapp.room.v1.ListRoomBackgroundsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 35, // 35: hyapp.room.v1.ListRoomBackgroundsResponse.backgrounds:type_name -> hyapp.room.v1.RoomBackgroundImage + 0, // 36: hyapp.room.v1.SetRoomBackgroundRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 37: hyapp.room.v1.SetRoomBackgroundResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 38: hyapp.room.v1.SetRoomBackgroundResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 35, // 39: hyapp.room.v1.SetRoomBackgroundResponse.background:type_name -> hyapp.room.v1.RoomBackgroundImage + 0, // 40: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 41: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 42: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 43: hyapp.room.v1.UpdateRoomProfileRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 44: hyapp.room.v1.UpdateRoomProfileResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 45: hyapp.room.v1.UpdateRoomProfileResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 46: hyapp.room.v1.JoinRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 46, // 47: hyapp.room.v1.JoinRoomRequest.entry_vehicle:type_name -> hyapp.room.v1.RoomEntryVehicleSnapshot + 1, // 48: hyapp.room.v1.JoinRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 2, // 49: hyapp.room.v1.JoinRoomResponse.user:type_name -> hyapp.room.v1.RoomUser + 34, // 50: hyapp.room.v1.JoinRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 51: hyapp.room.v1.RoomHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 52: hyapp.room.v1.RoomHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult + 2, // 53: hyapp.room.v1.RoomHeartbeatResponse.user:type_name -> hyapp.room.v1.RoomUser + 34, // 54: hyapp.room.v1.RoomHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 55: hyapp.room.v1.LeaveRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 56: hyapp.room.v1.LeaveRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 57: hyapp.room.v1.LeaveRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 58: hyapp.room.v1.CloseRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 59: hyapp.room.v1.CloseRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 60: hyapp.room.v1.CloseRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 61: hyapp.room.v1.AdminListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 55, // 62: hyapp.room.v1.AdminListRoomsResponse.rooms:type_name -> hyapp.room.v1.AdminRoomListItem + 0, // 63: hyapp.room.v1.AdminGetRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 55, // 64: hyapp.room.v1.AdminGetRoomResponse.room:type_name -> hyapp.room.v1.AdminRoomListItem + 0, // 65: hyapp.room.v1.AdminUpdateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 66: hyapp.room.v1.AdminUpdateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 67: hyapp.room.v1.AdminUpdateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 68: hyapp.room.v1.AdminDeleteRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 69: hyapp.room.v1.AdminDeleteRoomResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 70: hyapp.room.v1.AdminDeleteRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 71: hyapp.room.v1.MicUpRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 72: hyapp.room.v1.MicUpResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 73: hyapp.room.v1.MicUpResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 74: hyapp.room.v1.MicDownRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 75: hyapp.room.v1.MicDownResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 76: hyapp.room.v1.MicDownResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 77: hyapp.room.v1.ChangeMicSeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 78: hyapp.room.v1.ChangeMicSeatResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 79: hyapp.room.v1.ChangeMicSeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 80: hyapp.room.v1.ConfirmMicPublishingRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 81: hyapp.room.v1.ConfirmMicPublishingResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 82: hyapp.room.v1.ConfirmMicPublishingResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 83: hyapp.room.v1.MicHeartbeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 84: hyapp.room.v1.MicHeartbeatResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 85: hyapp.room.v1.MicHeartbeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 86: hyapp.room.v1.SetMicMuteRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 87: hyapp.room.v1.SetMicMuteResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 88: hyapp.room.v1.SetMicMuteResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 89: hyapp.room.v1.ApplyRTCEventRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 90: hyapp.room.v1.ApplyRTCEventResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 91: hyapp.room.v1.ApplyRTCEventResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 92: hyapp.room.v1.SetMicSeatLockRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 93: hyapp.room.v1.SetMicSeatLockResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 94: hyapp.room.v1.SetMicSeatLockResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 95: hyapp.room.v1.SetChatEnabledRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 96: hyapp.room.v1.SetChatEnabledResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 97: hyapp.room.v1.SetChatEnabledResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 98: hyapp.room.v1.SetRoomPasswordRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 99: hyapp.room.v1.SetRoomPasswordResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 100: hyapp.room.v1.SetRoomPasswordResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 101: hyapp.room.v1.SetRoomAdminRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 102: hyapp.room.v1.SetRoomAdminResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 103: hyapp.room.v1.SetRoomAdminResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 104: hyapp.room.v1.MuteUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 105: hyapp.room.v1.MuteUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 106: hyapp.room.v1.MuteUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 107: hyapp.room.v1.KickUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 108: hyapp.room.v1.KickUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 109: hyapp.room.v1.KickUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 110: hyapp.room.v1.UnbanUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 111: hyapp.room.v1.UnbanUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 112: hyapp.room.v1.UnbanUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 113: hyapp.room.v1.SystemEvictUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 114: hyapp.room.v1.SystemEvictUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 34, // 115: hyapp.room.v1.SystemEvictUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 116: hyapp.room.v1.SendGiftRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 117: hyapp.room.v1.SendGiftResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 118: hyapp.room.v1.SendGiftResponse.gift_rank:type_name -> hyapp.room.v1.RankItem + 34, // 119: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 12, // 120: hyapp.room.v1.SendGiftResponse.rocket:type_name -> hyapp.room.v1.RoomRocketState + 6, // 121: hyapp.room.v1.SendGiftResponse.lucky_gift:type_name -> hyapp.room.v1.LuckyGiftDrawResult + 6, // 122: hyapp.room.v1.SendGiftResponse.lucky_gifts:type_name -> hyapp.room.v1.LuckyGiftDrawResult + 0, // 123: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 124: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 103, // 125: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser + 0, // 126: hyapp.room.v1.ListRoomGiftLeaderboardRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 104, // 127: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem + 104, // 128: hyapp.room.v1.RoomGiftLeaderboardItem.room:type_name -> hyapp.room.v1.RoomListItem + 106, // 129: hyapp.room.v1.ListRoomGiftLeaderboardResponse.items:type_name -> hyapp.room.v1.RoomGiftLeaderboardItem + 0, // 130: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 104, // 131: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem + 0, // 132: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 133: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 34, // 134: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 135: hyapp.room.v1.GetRoomRocketRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 13, // 136: hyapp.room.v1.GetRoomRocketResponse.rocket:type_name -> hyapp.room.v1.RoomRocketInfo + 0, // 137: hyapp.room.v1.ListRoomOnlineUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 2, // 138: hyapp.room.v1.ListRoomOnlineUsersResponse.users:type_name -> hyapp.room.v1.RoomUser + 3, // 139: hyapp.room.v1.ListRoomOnlineUsersResponse.items:type_name -> hyapp.room.v1.RoomOnlineUser + 0, // 140: hyapp.room.v1.ListRoomBannedUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 118, // 141: hyapp.room.v1.ListRoomBannedUsersResponse.items:type_name -> hyapp.room.v1.RoomBannedUser + 0, // 142: hyapp.room.v1.FollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 143: hyapp.room.v1.UnfollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 42, // 144: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest + 44, // 145: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:input_type -> hyapp.room.v1.UpdateRoomProfileRequest + 36, // 146: hyapp.room.v1.RoomCommandService.SaveRoomBackground:input_type -> hyapp.room.v1.SaveRoomBackgroundRequest + 40, // 147: hyapp.room.v1.RoomCommandService.SetRoomBackground:input_type -> hyapp.room.v1.SetRoomBackgroundRequest + 47, // 148: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest + 49, // 149: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest + 51, // 150: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest + 53, // 151: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest + 60, // 152: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:input_type -> hyapp.room.v1.AdminUpdateRoomRequest + 62, // 153: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:input_type -> hyapp.room.v1.AdminDeleteRoomRequest + 18, // 154: hyapp.room.v1.RoomCommandService.AdminUpdateRoomRocketConfig:input_type -> hyapp.room.v1.AdminUpdateRoomRocketConfigRequest + 23, // 155: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:input_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigRequest + 29, // 156: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:input_type -> hyapp.room.v1.AdminCreateRoomPinRequest + 31, // 157: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:input_type -> hyapp.room.v1.AdminCancelRoomPinRequest + 64, // 158: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest + 66, // 159: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest + 68, // 160: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest + 70, // 161: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest + 72, // 162: hyapp.room.v1.RoomCommandService.MicHeartbeat:input_type -> hyapp.room.v1.MicHeartbeatRequest + 74, // 163: hyapp.room.v1.RoomCommandService.SetMicMute:input_type -> hyapp.room.v1.SetMicMuteRequest + 76, // 164: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest + 78, // 165: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest + 80, // 166: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest + 82, // 167: hyapp.room.v1.RoomCommandService.SetRoomPassword:input_type -> hyapp.room.v1.SetRoomPasswordRequest + 84, // 168: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest + 86, // 169: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest + 88, // 170: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest + 90, // 171: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest + 92, // 172: hyapp.room.v1.RoomCommandService.SystemEvictUser:input_type -> hyapp.room.v1.SystemEvictUserRequest + 94, // 173: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest + 121, // 174: hyapp.room.v1.RoomCommandService.FollowRoom:input_type -> hyapp.room.v1.FollowRoomRequest + 123, // 175: hyapp.room.v1.RoomCommandService.UnfollowRoom:input_type -> hyapp.room.v1.UnfollowRoomRequest + 96, // 176: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest + 98, // 177: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest + 56, // 178: hyapp.room.v1.RoomQueryService.AdminListRooms:input_type -> hyapp.room.v1.AdminListRoomsRequest + 58, // 179: hyapp.room.v1.RoomQueryService.AdminGetRoom:input_type -> hyapp.room.v1.AdminGetRoomRequest + 16, // 180: hyapp.room.v1.RoomQueryService.AdminGetRoomRocketConfig:input_type -> hyapp.room.v1.AdminGetRoomRocketConfigRequest + 21, // 181: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:input_type -> hyapp.room.v1.AdminGetRoomSeatConfigRequest + 27, // 182: hyapp.room.v1.RoomQueryService.AdminListRoomPins:input_type -> hyapp.room.v1.AdminListRoomPinsRequest + 100, // 183: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest + 101, // 184: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest + 102, // 185: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:input_type -> hyapp.room.v1.ListRoomGiftLeaderboardRequest + 108, // 186: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest + 110, // 187: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest + 112, // 188: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest + 38, // 189: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:input_type -> hyapp.room.v1.ListRoomBackgroundsRequest + 114, // 190: hyapp.room.v1.RoomQueryService.GetRoomRocket:input_type -> hyapp.room.v1.GetRoomRocketRequest + 116, // 191: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:input_type -> hyapp.room.v1.ListRoomOnlineUsersRequest + 119, // 192: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:input_type -> hyapp.room.v1.ListRoomBannedUsersRequest + 43, // 193: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse + 45, // 194: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:output_type -> hyapp.room.v1.UpdateRoomProfileResponse + 37, // 195: hyapp.room.v1.RoomCommandService.SaveRoomBackground:output_type -> hyapp.room.v1.SaveRoomBackgroundResponse + 41, // 196: hyapp.room.v1.RoomCommandService.SetRoomBackground:output_type -> hyapp.room.v1.SetRoomBackgroundResponse + 48, // 197: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse + 50, // 198: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse + 52, // 199: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse + 54, // 200: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse + 61, // 201: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:output_type -> hyapp.room.v1.AdminUpdateRoomResponse + 63, // 202: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:output_type -> hyapp.room.v1.AdminDeleteRoomResponse + 19, // 203: hyapp.room.v1.RoomCommandService.AdminUpdateRoomRocketConfig:output_type -> hyapp.room.v1.AdminUpdateRoomRocketConfigResponse + 24, // 204: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:output_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigResponse + 30, // 205: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:output_type -> hyapp.room.v1.AdminCreateRoomPinResponse + 32, // 206: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:output_type -> hyapp.room.v1.AdminCancelRoomPinResponse + 65, // 207: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse + 67, // 208: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse + 69, // 209: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse + 71, // 210: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse + 73, // 211: hyapp.room.v1.RoomCommandService.MicHeartbeat:output_type -> hyapp.room.v1.MicHeartbeatResponse + 75, // 212: hyapp.room.v1.RoomCommandService.SetMicMute:output_type -> hyapp.room.v1.SetMicMuteResponse + 77, // 213: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse + 79, // 214: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse + 81, // 215: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse + 83, // 216: hyapp.room.v1.RoomCommandService.SetRoomPassword:output_type -> hyapp.room.v1.SetRoomPasswordResponse + 85, // 217: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse + 87, // 218: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse + 89, // 219: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse + 91, // 220: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse + 93, // 221: hyapp.room.v1.RoomCommandService.SystemEvictUser:output_type -> hyapp.room.v1.SystemEvictUserResponse + 95, // 222: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse + 122, // 223: hyapp.room.v1.RoomCommandService.FollowRoom:output_type -> hyapp.room.v1.FollowRoomResponse + 124, // 224: hyapp.room.v1.RoomCommandService.UnfollowRoom:output_type -> hyapp.room.v1.UnfollowRoomResponse + 97, // 225: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse + 99, // 226: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse + 57, // 227: hyapp.room.v1.RoomQueryService.AdminListRooms:output_type -> hyapp.room.v1.AdminListRoomsResponse + 59, // 228: hyapp.room.v1.RoomQueryService.AdminGetRoom:output_type -> hyapp.room.v1.AdminGetRoomResponse + 17, // 229: hyapp.room.v1.RoomQueryService.AdminGetRoomRocketConfig:output_type -> hyapp.room.v1.AdminGetRoomRocketConfigResponse + 22, // 230: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:output_type -> hyapp.room.v1.AdminGetRoomSeatConfigResponse + 28, // 231: hyapp.room.v1.RoomQueryService.AdminListRoomPins:output_type -> hyapp.room.v1.AdminListRoomPinsResponse + 105, // 232: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse + 105, // 233: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse + 107, // 234: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:output_type -> hyapp.room.v1.ListRoomGiftLeaderboardResponse + 109, // 235: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse + 111, // 236: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse + 113, // 237: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse + 39, // 238: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:output_type -> hyapp.room.v1.ListRoomBackgroundsResponse + 115, // 239: hyapp.room.v1.RoomQueryService.GetRoomRocket:output_type -> hyapp.room.v1.GetRoomRocketResponse + 117, // 240: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:output_type -> hyapp.room.v1.ListRoomOnlineUsersResponse + 120, // 241: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:output_type -> hyapp.room.v1.ListRoomBannedUsersResponse + 193, // [193:242] is the sub-list for method output_type + 144, // [144:193] is the sub-list for method input_type + 144, // [144:144] is the sub-list for extension type_name + 144, // [144:144] is the sub-list for extension extendee + 0, // [0:144] is the sub-list for field type_name } func init() { file_proto_room_v1_room_proto_init() } @@ -10615,15 +10691,15 @@ func file_proto_room_v1_room_proto_init() { if File_proto_room_v1_room_proto != nil { return } - file_proto_room_v1_room_proto_msgTypes[43].OneofWrappers = []any{} - file_proto_room_v1_room_proto_msgTypes[59].OneofWrappers = []any{} + file_proto_room_v1_room_proto_msgTypes[44].OneofWrappers = []any{} + file_proto_room_v1_room_proto_msgTypes[60].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_room_v1_room_proto_rawDesc), len(file_proto_room_v1_room_proto_rawDesc)), NumEnums: 0, - NumMessages: 125, + NumMessages: 126, NumExtensions: 0, NumServices: 3, }, diff --git a/api/proto/room/v1/room.proto b/api/proto/room/v1/room.proto index 5ec6fb2b..b2e90081 100644 --- a/api/proto/room/v1/room.proto +++ b/api/proto/room/v1/room.proto @@ -139,6 +139,13 @@ message RoomRocketRewardGrant { string status = 8; } +// RoomRocketContribution 是当前火箭等级内的实际加燃料贡献累计。 +message RoomRocketContribution { + int64 user_id = 1; + int64 fuel = 2; + int64 updated_at_ms = 3; +} + // RoomRocketPendingLaunch 是已经点火、等待延迟发射的单枚火箭。 message RoomRocketPendingLaunch { string rocket_id = 1; @@ -169,6 +176,7 @@ message RoomRocketState { int64 config_version = 12; repeated RoomRocketRewardGrant last_rewards = 13; repeated RoomRocketPendingLaunch pending_launches = 14; + repeated RoomRocketContribution current_contributions = 15; } // RoomRocketInfo 是 App 初始化火箭 UI 的完整读模型。 diff --git a/services/room-service/internal/room/command/command.go b/services/room-service/internal/room/command/command.go index 18161a57..728db698 100644 --- a/services/room-service/internal/room/command/command.go +++ b/services/room-service/internal/room/command/command.go @@ -457,6 +457,8 @@ type SendGift struct { RocketConfigVersion int64 `json:"rocket_config_version,omitempty"` // RocketPendingLaunches 是送礼提交后的待发射队列,恢复时不能只保存当前进度。 RocketPendingLaunches []RocketPendingLaunch `json:"rocket_pending_launches,omitempty"` + // RocketCurrentContributions 保存当前等级真实加燃料贡献,恢复时用于继续按本级贡献锁 top1。 + RocketCurrentContributions []RocketContribution `json:"rocket_current_contributions,omitempty"` } // Type 返回命令类型。 @@ -474,6 +476,13 @@ type RocketRewardGrant struct { Status string `json:"status"` } +// RocketContribution 记录当前火箭等级内的实际燃料贡献快照。 +type RocketContribution struct { + UserID int64 `json:"user_id"` + Fuel int64 `json:"fuel"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + // RocketPendingLaunch 记录一枚已点火火箭的延迟发射快照。 type RocketPendingLaunch struct { RocketID string `json:"rocket_id"` @@ -507,6 +516,7 @@ type LaunchRoomRocket struct { IgniterUserID int64 `json:"igniter_user_id,omitempty"` LaunchedAtMS int64 `json:"launched_at_ms"` InRoomUserIDs []int64 `json:"in_room_user_ids,omitempty"` + CurrentContributions []RocketContribution `json:"current_contributions,omitempty"` Rewards []RocketRewardGrant `json:"rewards,omitempty"` PendingLaunches []RocketPendingLaunch `json:"pending_launches,omitempty"` } @@ -564,6 +574,7 @@ func IdempotencyPayload(payload []byte) ([]byte, error) { delete(values, "rocket_id") delete(values, "rocket_config_version") delete(values, "rocket_pending_launches") + delete(values, "rocket_current_contributions") // LaunchRoomRocket 的以下字段是 worker 在发射时结算出的结果,不属于触发发射的幂等语义。 delete(values, "current_level") delete(values, "current_fuel") @@ -576,6 +587,7 @@ func IdempotencyPayload(payload []byte) ([]byte, error) { delete(values, "igniter_user_id") delete(values, "launched_at_ms") delete(values, "in_room_user_ids") + delete(values, "current_contributions") delete(values, "rewards") delete(values, "pending_launches") // SetRoomPassword 的哈希带随机盐;通用 payload 比较先去掉哈希,service 层再用 transient 明文和已存 bcrypt 哈希确认是否同一密码。 diff --git a/services/room-service/internal/room/service/recovery.go b/services/room-service/internal/room/service/recovery.go index 6b233456..4b8fee5e 100644 --- a/services/room-service/internal/room/service/recovery.go +++ b/services/room-service/internal/room/service/recovery.go @@ -443,36 +443,53 @@ func sendGiftTargetGiftValues(cmd *command.SendGift) map[int64]int64 { func rocketStateFromSettledGift(cmd *command.SendGift) *state.RocketState { return &state.RocketState{ - CurrentLevel: cmd.RocketLevel, - CurrentFuel: cmd.RocketFuel, - FuelThreshold: cmd.RocketFuelThreshold, - Status: cmd.RocketStatus, - IgnitedAtMS: cmd.RocketIgnitedAtMS, - LaunchAtMS: cmd.RocketLaunchAtMS, - ResetAtMS: cmd.RocketResetAtMS, - Top1UserID: cmd.RocketTop1UserID, - IgniterUserID: cmd.RocketIgniterUserID, - RocketID: cmd.RocketID, - ConfigVersion: cmd.RocketConfigVersion, - PendingLaunches: rocketPendingLaunchesFromCommand(cmd.RocketPendingLaunches), + CurrentLevel: cmd.RocketLevel, + CurrentFuel: cmd.RocketFuel, + FuelThreshold: cmd.RocketFuelThreshold, + Status: cmd.RocketStatus, + IgnitedAtMS: cmd.RocketIgnitedAtMS, + LaunchAtMS: cmd.RocketLaunchAtMS, + ResetAtMS: cmd.RocketResetAtMS, + Top1UserID: cmd.RocketTop1UserID, + IgniterUserID: cmd.RocketIgniterUserID, + RocketID: cmd.RocketID, + ConfigVersion: cmd.RocketConfigVersion, + PendingLaunches: rocketPendingLaunchesFromCommand(cmd.RocketPendingLaunches), + CurrentContributions: rocketContributionsFromCommand(cmd.RocketCurrentContributions), } } func rocketStateFromLaunchCommand(cmd *command.LaunchRoomRocket) *state.RocketState { return &state.RocketState{ - CurrentLevel: cmd.CurrentLevel, - CurrentFuel: cmd.CurrentFuel, - FuelThreshold: cmd.CurrentFuelThreshold, - Status: cmd.CurrentStatus, - LaunchedAtMS: cmd.LaunchedAtMS, - ResetAtMS: cmd.ResetAtMS, - RocketID: cmd.CurrentRocketID, - ConfigVersion: cmd.ConfigVersion, - PendingLaunches: rocketPendingLaunchesFromCommand(cmd.PendingLaunches), - LastRewards: rocketRewardsFromCommand(cmd.Rewards), + CurrentLevel: cmd.CurrentLevel, + CurrentFuel: cmd.CurrentFuel, + FuelThreshold: cmd.CurrentFuelThreshold, + Status: cmd.CurrentStatus, + LaunchedAtMS: cmd.LaunchedAtMS, + ResetAtMS: cmd.ResetAtMS, + RocketID: cmd.CurrentRocketID, + ConfigVersion: cmd.ConfigVersion, + PendingLaunches: rocketPendingLaunchesFromCommand(cmd.PendingLaunches), + CurrentContributions: rocketContributionsFromCommand(cmd.CurrentContributions), + LastRewards: rocketRewardsFromCommand(cmd.Rewards), } } +func rocketContributionsFromCommand(input []command.RocketContribution) []state.RocketContribution { + contributions := make([]state.RocketContribution, 0, len(input)) + for _, contribution := range input { + if contribution.UserID <= 0 || contribution.Fuel <= 0 { + continue + } + contributions = append(contributions, state.RocketContribution{ + UserID: contribution.UserID, + Fuel: contribution.Fuel, + UpdatedAtMS: contribution.UpdatedAtMS, + }) + } + return contributions +} + func rocketPendingLaunchExists(input []state.RocketPendingLaunch, rocketID string, level int32) bool { for _, launch := range input { if launch.RocketID == rocketID && launch.Level == level { diff --git a/services/room-service/internal/room/service/room_rocket.go b/services/room-service/internal/room/service/room_rocket.go index 32000158..79c48aeb 100644 --- a/services/room-service/internal/room/service/room_rocket.go +++ b/services/room-service/internal/room/service/room_rocket.go @@ -243,20 +243,21 @@ func roomRocketProtoStateForView(current *roomv1.RoomRocketState, config RoomRoc internal := state.RocketState{} if current != nil { internal = state.RocketState{ - CurrentLevel: current.GetCurrentLevel(), - CurrentFuel: current.GetCurrentFuel(), - FuelThreshold: current.GetFuelThreshold(), - Status: current.GetStatus(), - IgnitedAtMS: current.GetIgnitedAtMs(), - LaunchAtMS: current.GetLaunchAtMs(), - LaunchedAtMS: current.GetLaunchedAtMs(), - ResetAtMS: current.GetResetAtMs(), - Top1UserID: current.GetTop1UserId(), - IgniterUserID: current.GetIgniterUserId(), - RocketID: current.GetRocketId(), - ConfigVersion: current.GetConfigVersion(), - PendingLaunches: statePendingLaunchesFromProto(current.GetPendingLaunches()), - LastRewards: stateRewardsFromProto(current.GetLastRewards()), + CurrentLevel: current.GetCurrentLevel(), + CurrentFuel: current.GetCurrentFuel(), + FuelThreshold: current.GetFuelThreshold(), + Status: current.GetStatus(), + IgnitedAtMS: current.GetIgnitedAtMs(), + LaunchAtMS: current.GetLaunchAtMs(), + LaunchedAtMS: current.GetLaunchedAtMs(), + ResetAtMS: current.GetResetAtMs(), + Top1UserID: current.GetTop1UserId(), + IgniterUserID: current.GetIgniterUserId(), + RocketID: current.GetRocketId(), + ConfigVersion: current.GetConfigVersion(), + PendingLaunches: statePendingLaunchesFromProto(current.GetPendingLaunches()), + CurrentContributions: stateContributionsFromProto(current.GetCurrentContributions()), + LastRewards: stateRewardsFromProto(current.GetLastRewards()), } } normalized := rocketStateForNow(&internal, config, now, false) @@ -299,6 +300,21 @@ func statePendingLaunchesFromProto(input []*roomv1.RoomRocketPendingLaunch) []st return out } +func stateContributionsFromProto(input []*roomv1.RoomRocketContribution) []state.RocketContribution { + out := make([]state.RocketContribution, 0, len(input)) + for _, contribution := range input { + if contribution.GetUserId() <= 0 || contribution.GetFuel() <= 0 { + continue + } + out = append(out, state.RocketContribution{ + UserID: contribution.GetUserId(), + Fuel: contribution.GetFuel(), + UpdatedAtMS: contribution.GetUpdatedAtMs(), + }) + } + return out +} + func roomRocketLevelsToProto(levels []RoomRocketLevelConfig) []*roomv1.RoomRocketLevel { out := make([]*roomv1.RoomRocketLevel, 0, len(levels)) for _, level := range levels { @@ -367,11 +383,17 @@ func (s *Service) applyRoomRocketGift(now time.Time, current *state.RoomState, c overflowFuel = addedFuel - effectiveFuel if effectiveFuel > 0 { rocket.CurrentFuel += effectiveFuel + rocket.CurrentContributions = addRocketContribution(rocket.CurrentContributions, cmd.ActorUserID(), effectiveFuel, nowMS) progressFuel = rocket.CurrentFuel result.touched = true } if rocket.CurrentFuel >= rocket.FuelThreshold { levelCfg := roomRocketLevelByNumber(cfg, rocket.CurrentLevel) + top1UserID := firstRocketContributionUserID(rocket.CurrentContributions) + if top1UserID <= 0 { + // 理论上 effectiveFuel > 0 后一定有当前等级贡献;兜底用点火人,避免历史脏状态把 top1 奖励发给 0。 + top1UserID = cmd.ActorUserID() + } pending := state.RocketPendingLaunch{ RocketID: rocket.RocketID, Level: rocket.CurrentLevel, @@ -379,7 +401,7 @@ func (s *Service) applyRoomRocketGift(now time.Time, current *state.RoomState, c IgnitedAtMS: nowMS, LaunchAtMS: nowMS + cfg.LaunchDelayMS, ResetAtMS: rocket.ResetAtMS, - Top1UserID: firstRankUserID(current.GiftRank), + Top1UserID: top1UserID, IgniterUserID: cmd.ActorUserID(), ConfigVersion: cfg.ConfigVersion, CoverURL: levelCfg.CoverURL, @@ -420,10 +442,12 @@ func (s *Service) applyRoomRocketGift(now time.Time, current *state.RoomState, c rocket.FuelThreshold = nextLevelCfg.FuelThreshold rocket.Status = state.RocketStatusCharging rocket.RocketID = idgen.New("room_rocket") + rocket.CurrentContributions = nil } else { // 5 级是当天最高等级;点火后不再生成第 6 级,UTC 日界统一重置回 1 级。 rocket.Status = state.RocketStatusCompleted rocket.RocketID = "" + rocket.CurrentContributions = nil } } } @@ -468,6 +492,7 @@ func (s *Service) applyRoomRocketGift(now time.Time, current *state.RoomState, c settled.RocketID = rocket.RocketID settled.RocketConfigVersion = rocket.ConfigVersion settled.RocketPendingLaunches = rocketPendingLaunchesToCommand(rocket.PendingLaunches) + settled.RocketCurrentContributions = rocketContributionsToCommand(rocket.CurrentContributions) } return result, nil } @@ -634,11 +659,61 @@ func roomRocketFuelByRule(base int64, rule GiftFuelRuleConfig) int64 { return fuel } -func firstRankUserID(items []state.RankItem) int64 { - if len(items) == 0 { +func addRocketContribution(input []state.RocketContribution, userID int64, fuel int64, updatedAtMS int64) []state.RocketContribution { + if userID <= 0 || fuel <= 0 { + return normalizeRocketContributions(input) + } + out := append([]state.RocketContribution(nil), input...) + for index := range out { + if out[index].UserID == userID { + // top1 只看当前火箭等级真实写入燃料的累计;溢出部分不会带到下一等级,也不会影响当前等级排名。 + out[index].Fuel += fuel + out[index].UpdatedAtMS = updatedAtMS + return normalizeRocketContributions(out) + } + } + out = append(out, state.RocketContribution{UserID: userID, Fuel: fuel, UpdatedAtMS: updatedAtMS}) + return normalizeRocketContributions(out) +} + +func normalizeRocketContributions(input []state.RocketContribution) []state.RocketContribution { + out := make([]state.RocketContribution, 0, len(input)) + for _, item := range input { + if item.UserID <= 0 || item.Fuel <= 0 { + continue + } + out = append(out, item) + } + slices.SortFunc(out, func(left, right state.RocketContribution) int { + if left.Fuel != right.Fuel { + if left.Fuel > right.Fuel { + return -1 + } + return 1 + } + if left.UpdatedAtMS != right.UpdatedAtMS { + if left.UpdatedAtMS > right.UpdatedAtMS { + return -1 + } + return 1 + } + if left.UserID < right.UserID { + return -1 + } + if left.UserID > right.UserID { + return 1 + } + return 0 + }) + return out +} + +func firstRocketContributionUserID(items []state.RocketContribution) int64 { + normalized := normalizeRocketContributions(items) + if len(normalized) == 0 { return 0 } - return items[0].UserID + return normalized[0].UserID } func minInt64(left int64, right int64) int64 { @@ -680,21 +755,30 @@ func rocketStateToRoomProto(input *state.RocketState) *roomv1.RoomRocketState { CoverUrl: launch.CoverURL, }) } + contributions := make([]*roomv1.RoomRocketContribution, 0, len(input.CurrentContributions)) + for _, contribution := range normalizeRocketContributions(input.CurrentContributions) { + contributions = append(contributions, &roomv1.RoomRocketContribution{ + UserId: contribution.UserID, + Fuel: contribution.Fuel, + UpdatedAtMs: contribution.UpdatedAtMS, + }) + } return &roomv1.RoomRocketState{ - CurrentLevel: input.CurrentLevel, - CurrentFuel: input.CurrentFuel, - FuelThreshold: input.FuelThreshold, - Status: input.Status, - IgnitedAtMs: input.IgnitedAtMS, - LaunchAtMs: input.LaunchAtMS, - LaunchedAtMs: input.LaunchedAtMS, - ResetAtMs: input.ResetAtMS, - Top1UserId: input.Top1UserID, - IgniterUserId: input.IgniterUserID, - RocketId: input.RocketID, - ConfigVersion: input.ConfigVersion, - LastRewards: rewards, - PendingLaunches: pending, + CurrentLevel: input.CurrentLevel, + CurrentFuel: input.CurrentFuel, + FuelThreshold: input.FuelThreshold, + Status: input.Status, + IgnitedAtMs: input.IgnitedAtMS, + LaunchAtMs: input.LaunchAtMS, + LaunchedAtMs: input.LaunchedAtMS, + ResetAtMs: input.ResetAtMS, + Top1UserId: input.Top1UserID, + IgniterUserId: input.IgniterUserID, + RocketId: input.RocketID, + ConfigVersion: input.ConfigVersion, + LastRewards: rewards, + PendingLaunches: pending, + CurrentContributions: contributions, } } @@ -863,6 +947,7 @@ func (s *Service) launchRoomRocket(ctx context.Context, cmd command.LaunchRoomRo settledCommand.IgniterUserID = pending.IgniterUserID settledCommand.LaunchedAtMS = nowMS settledCommand.InRoomUserIDs = inRoomUserIDs + settledCommand.CurrentContributions = rocketContributionsToCommand(current.Rocket.CurrentContributions) settledCommand.Rewards = rocketRewardsToCommand(rewards) settledCommand.PendingLaunches = rocketPendingLaunchesToCommand(pendingAfter) commandPayload, err := command.Serialize(settledCommand) @@ -1073,6 +1158,19 @@ func rocketRewardsToCommand(input []state.RocketRewardGrant) []command.RocketRew return out } +func rocketContributionsToCommand(input []state.RocketContribution) []command.RocketContribution { + normalized := normalizeRocketContributions(input) + out := make([]command.RocketContribution, 0, len(normalized)) + for _, contribution := range normalized { + out = append(out, command.RocketContribution{ + UserID: contribution.UserID, + Fuel: contribution.Fuel, + UpdatedAtMS: contribution.UpdatedAtMS, + }) + } + return out +} + func rocketPendingLaunchesToCommand(input []state.RocketPendingLaunch) []command.RocketPendingLaunch { out := make([]command.RocketPendingLaunch, 0, len(input)) for _, launch := range input { diff --git a/services/room-service/internal/room/service/room_rocket_test.go b/services/room-service/internal/room/service/room_rocket_test.go index 92aace1f..bbdaaa65 100644 --- a/services/room-service/internal/room/service/room_rocket_test.go +++ b/services/room-service/internal/room/service/room_rocket_test.go @@ -436,6 +436,74 @@ func TestRoomRocketLaunchGrantsLockedTop1AndIgniterAfterLeave(t *testing.T) { } } +func TestRoomRocketTop1UsesCurrentLevelFuelContributionNotRoomGiftRank(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + now := &fixedRoomRocketClock{now: time.Date(2026, 5, 20, 12, 0, 0, 0, time.UTC)} + wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{ + {BillingReceiptId: "receipt-old-rank", GiftPointAdded: 10, HeatValue: 10, GiftTypeCode: "normal"}, + {BillingReceiptId: "receipt-current-level", GiftPointAdded: 90, HeatValue: 90, GiftTypeCode: "normal"}, + }} + svc := newRocketTestService(t, repository, wallet, now) + writeRocketConfig(t, ctx, repository, rocketConfigForTest(100, 1_000)) + + roomID := "room-rocket-current-level-top1" + ownerID := int64(501) + igniterID := int64(502) + createRocketRoom(t, ctx, svc, roomID, ownerID, 9004) + joinRocketRoom(t, ctx, svc, roomID, igniterID) + + if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{ + Meta: rocketMeta(roomID, ownerID, "old-room-rank"), + TargetType: "user", + TargetUserId: igniterID, + GiftId: "gift-old-room-rank", + GiftCount: 1, + }); err != nil { + t.Fatalf("seed old room gift rank failed: %v", err) + } + if _, err := svc.LeaveRoom(ctx, &roomv1.LeaveRoomRequest{Meta: rocketMeta(roomID, ownerID, "owner-leave")}); err != nil { + t.Fatalf("owner leave room failed: %v", err) + } + if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{ + Meta: rocketMeta(roomID, igniterID, "current-level-fill"), + TargetType: "user", + TargetUserId: igniterID, + GiftId: "gift-current-level-fill", + GiftCount: 1, + }); err != nil { + t.Fatalf("ignite current level rocket failed: %v", err) + } + + now.now = now.now.Add(1100 * time.Millisecond) + if err := svc.SweepRoomRocketLaunchings(ctx); err != nil { + t.Fatalf("sweep current-level top1 rocket failed: %v", err) + } + + if hasRocketGrant(wallet.grants, ownerID, 2001) { + t.Fatalf("left historical room GiftRank top1 must not receive current-level top1 reward: %+v", wallet.grants) + } + if !hasRocketGrant(wallet.grants, igniterID, 2001) || !hasRocketGrant(wallet.grants, igniterID, 3001) || !hasRocketGrant(wallet.grants, igniterID, 1001) { + t.Fatalf("only current-level contributor/igniter/in-room user must receive all level rewards: %+v", wallet.grants) + } + + info, err := svc.GetRoomRocket(ctx, &roomv1.GetRoomRocketRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID}, + RoomId: roomID, + ViewerUserId: igniterID, + }) + if err != nil { + t.Fatalf("get current-level top1 rocket failed: %v", err) + } + rewards := info.GetRocket().GetState().GetLastRewards() + if len(rewards) != 3 || countRocketRewards(rewards, ownerID, "top1") != 0 || + countRocketRewards(rewards, igniterID, "top1") != 1 || + countRocketRewards(rewards, igniterID, "igniter") != 1 || + countRocketRewards(rewards, igniterID, "in_room") != 1 { + t.Fatalf("reward view must not include historical room GiftRank top1: %+v", rewards) + } +} + func TestRoomRocketLaunchStacksAllSpecialPoolsAndRandomInRoomReward(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) diff --git a/services/room-service/internal/room/state/state.go b/services/room-service/internal/room/state/state.go index 5bde4274..c9c53b65 100644 --- a/services/room-service/internal/room/state/state.go +++ b/services/room-service/internal/room/state/state.go @@ -171,6 +171,16 @@ type RocketPendingLaunch struct { CoverURL string } +// RocketContribution 保存当前火箭等级里真正进入燃料条的用户贡献。 +type RocketContribution struct { + // UserID 是加燃料的送礼人;top1 只从这一批当前等级贡献里选。 + UserID int64 + // Fuel 是当前等级内实际写入进度条的燃料,溢出部分不算入下一等级。 + Fuel int64 + // UpdatedAtMS 用于贡献相同时按最近贡献排序,保持榜单和 UI 直觉一致。 + UpdatedAtMS int64 +} + // RocketState 保存 Room Cell 内当前火箭进度和最近一次发射结果。 type RocketState struct { // CurrentLevel 是当前正在积攒的火箭等级,UTC 日重置后回到 1。 @@ -196,6 +206,8 @@ type RocketState struct { ConfigVersion int64 // PendingLaunches 是已经满阈值但还没到延迟发射时间的火箭队列。 PendingLaunches []RocketPendingLaunch + // CurrentContributions 只记录当前正在积攒等级的实际燃料贡献,点火进入下一等级后清空。 + CurrentContributions []RocketContribution // LastRewards 保存最近一次发射发奖结果,用于断线重连后补 UI。 LastRewards []RocketRewardGrant } @@ -207,6 +219,7 @@ func (t *RocketState) Clone() *RocketState { } cloned := *t cloned.PendingLaunches = append([]RocketPendingLaunch(nil), t.PendingLaunches...) + cloned.CurrentContributions = append([]RocketContribution(nil), t.CurrentContributions...) cloned.LastRewards = append([]RocketRewardGrant(nil), t.LastRewards...) return &cloned } @@ -628,6 +641,7 @@ func cloneRocketState(input *RocketState) *RocketState { } cloned := *input cloned.PendingLaunches = append([]RocketPendingLaunch(nil), input.PendingLaunches...) + cloned.CurrentContributions = append([]RocketContribution(nil), input.CurrentContributions...) cloned.LastRewards = append([]RocketRewardGrant(nil), input.LastRewards...) return &cloned } @@ -664,21 +678,30 @@ func rocketStateToProto(input *RocketState) *roomv1.RoomRocketState { CoverUrl: launch.CoverURL, }) } + contributions := make([]*roomv1.RoomRocketContribution, 0, len(input.CurrentContributions)) + for _, contribution := range input.CurrentContributions { + contributions = append(contributions, &roomv1.RoomRocketContribution{ + UserId: contribution.UserID, + Fuel: contribution.Fuel, + UpdatedAtMs: contribution.UpdatedAtMS, + }) + } return &roomv1.RoomRocketState{ - CurrentLevel: input.CurrentLevel, - CurrentFuel: input.CurrentFuel, - FuelThreshold: input.FuelThreshold, - Status: input.Status, - IgnitedAtMs: input.IgnitedAtMS, - LaunchAtMs: input.LaunchAtMS, - LaunchedAtMs: input.LaunchedAtMS, - ResetAtMs: input.ResetAtMS, - Top1UserId: input.Top1UserID, - IgniterUserId: input.IgniterUserID, - RocketId: input.RocketID, - ConfigVersion: input.ConfigVersion, - LastRewards: rewards, - PendingLaunches: pending, + CurrentLevel: input.CurrentLevel, + CurrentFuel: input.CurrentFuel, + FuelThreshold: input.FuelThreshold, + Status: input.Status, + IgnitedAtMs: input.IgnitedAtMS, + LaunchAtMs: input.LaunchAtMS, + LaunchedAtMs: input.LaunchedAtMS, + ResetAtMs: input.ResetAtMS, + Top1UserId: input.Top1UserID, + IgniterUserId: input.IgniterUserID, + RocketId: input.RocketID, + ConfigVersion: input.ConfigVersion, + LastRewards: rewards, + PendingLaunches: pending, + CurrentContributions: contributions, } } @@ -714,20 +737,29 @@ func rocketStateFromProto(input *roomv1.RoomRocketState) *RocketState { CoverURL: launch.GetCoverUrl(), }) } + contributions := make([]RocketContribution, 0, len(input.GetCurrentContributions())) + for _, contribution := range input.GetCurrentContributions() { + contributions = append(contributions, RocketContribution{ + UserID: contribution.GetUserId(), + Fuel: contribution.GetFuel(), + UpdatedAtMS: contribution.GetUpdatedAtMs(), + }) + } return &RocketState{ - CurrentLevel: input.GetCurrentLevel(), - CurrentFuel: input.GetCurrentFuel(), - FuelThreshold: input.GetFuelThreshold(), - Status: input.GetStatus(), - IgnitedAtMS: input.GetIgnitedAtMs(), - LaunchAtMS: input.GetLaunchAtMs(), - LaunchedAtMS: input.GetLaunchedAtMs(), - ResetAtMS: input.GetResetAtMs(), - Top1UserID: input.GetTop1UserId(), - IgniterUserID: input.GetIgniterUserId(), - RocketID: input.GetRocketId(), - ConfigVersion: input.GetConfigVersion(), - PendingLaunches: pending, - LastRewards: rewards, + CurrentLevel: input.GetCurrentLevel(), + CurrentFuel: input.GetCurrentFuel(), + FuelThreshold: input.GetFuelThreshold(), + Status: input.GetStatus(), + IgnitedAtMS: input.GetIgnitedAtMs(), + LaunchAtMS: input.GetLaunchAtMs(), + LaunchedAtMS: input.GetLaunchedAtMs(), + ResetAtMS: input.GetResetAtMs(), + Top1UserID: input.GetTop1UserId(), + IgniterUserID: input.GetIgniterUserId(), + RocketID: input.GetRocketId(), + ConfigVersion: input.GetConfigVersion(), + PendingLaunches: pending, + CurrentContributions: contributions, + LastRewards: rewards, } } From 112382178504267212fca0547393c55f0223c6bc Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 17:11:59 +0800 Subject: [PATCH 19/28] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/proto/activity/v1/activity.pb.go | 3050 +++++++++++++---- api/proto/activity/v1/activity.proto | 163 + api/proto/activity/v1/activity_grpc.pb.go | 366 ++ api/proto/game/v1/game.pb.go | 1029 +++--- api/proto/game/v1/game.proto | 22 + api/proto/game/v1/game_grpc.pb.go | 38 + api/proto/user/v1/user.pb.go | 1889 +++++----- api/proto/user/v1/user.proto | 24 + api/proto/user/v1/user_grpc.pb.go | 38 + api/proto/wallet/v1/wallet.pb.go | 1650 +++++---- api/proto/wallet/v1/wallet.proto | 32 + api/proto/wallet/v1/wallet_grpc.pb.go | 76 + docker-compose.yml | 1 + docs/flutter对接/公共跳转参数.md | 212 ++ docs/flutter对接/弹窗配置Flutter对接.md | 72 + docs/flutter对接/礼物墙Flutter对接.md | 15 +- docs/openapi/gateway.swagger.yaml | 129 +- docs/接口清单.md | 3 + ...037_user_invite_valid_recharge_sources.sql | 6 + scripts/mysql/044_game_explore_winners.sql | 16 + server/admin/cmd/server/main.go | 2 + server/admin/docs/权限管理.md | 1 + .../integration/activityclient/client.go | 17 + .../integration/walletclient/client.go | 5 + server/admin/internal/model/models.go | 21 + .../internal/modules/appconfig/handler.go | 69 + .../internal/modules/appconfig/request.go | 13 + .../internal/modules/appconfig/routes.go | 5 + .../internal/modules/appconfig/service.go | 176 + .../modules/appconfig/service_test.go | 89 + .../modules/inviteactivityreward/handler.go | 356 ++ .../modules/inviteactivityreward/routes.go | 16 + .../internal/modules/resource/handler.go | 21 + .../internal/modules/resource/handler_test.go | 37 +- .../admin/internal/modules/resource/routes.go | 1 + .../repository/app_popup_repository.go | 76 + .../admin/internal/repository/repository.go | 1 + server/admin/internal/repository/seed.go | 10 +- server/admin/internal/router/router.go | 3 + .../admin/migrations/049_admin_app_popups.sql | 69 + .../050_invite_activity_reward_navigation.sql | 59 + .../migrations/051_gift_delete_permission.sql | 18 + .../configs/config.docker.yaml | 4 + .../configs/config.tencent.example.yaml | 4 + services/activity-service/configs/config.yaml | 4 + .../mysql/initdb/001_activity_service.sql | 89 + .../migrations/002_invite_activity_reward.sql | 90 + services/activity-service/internal/app/app.go | 3 + .../internal/app/grpc_registration.go | 2 + services/activity-service/internal/app/mq.go | 58 + .../activity-service/internal/app/services.go | 4 + .../internal/app/user_events.go | 37 + .../internal/app/wallet_events.go | 24 + .../client/invite_attribution_source.go | 33 + .../internal/config/config.go | 46 +- .../internal/config/config_test.go | 10 +- .../domain/inviteactivity/invite_activity.go | 163 + .../service/inviteactivity/service.go | 455 +++ .../service/inviteactivity/service_test.go | 198 ++ .../mysql/invite_activity_reward_migration.go | 98 + .../invite_activity_reward_repository.go | 588 ++++ .../internal/storage/mysql/repository.go | 3 + .../grpc/invite_activity_reward_server.go | 223 ++ .../tools/invite-activity-local-flow/main.go | 263 ++ .../deploy/mysql/initdata/001_self_games.sql | 48 + .../deploy/mysql/initdb/001_game_service.sql | 32 +- .../game-service/internal/domain/dice/dice.go | 19 + .../internal/service/dice/service.go | 24 + .../internal/service/dice/service_test.go | 8 + .../mysql/dice_matchmaking_repository.go | 176 +- .../internal/storage/mysql/repository_test.go | 52 + .../internal/transport/grpc/server.go | 29 + services/gateway-service/internal/app/app.go | 2 + .../internal/appconfig/mysql.go | 84 + .../internal/client/game_client.go | 6 + .../client/invite_activity_reward_client.go | 35 + .../transport/http/activityapi/handler.go | 90 +- .../invite_activity_reward_handler.go | 363 ++ .../invite_activity_reward_handler_test.go | 150 + .../http/appapi/app_config_handler.go | 19 + .../internal/transport/http/appapi/handler.go | 5 + .../transport/http/game_handler_test.go | 101 +- .../transport/http/gameapi/game_handler.go | 112 +- .../transport/http/gameapi/game_view.go | 39 + .../transport/http/gameapi/handler.go | 40 + .../internal/transport/http/handler.go | 80 +- .../transport/http/httproutes/router.go | 26 + .../internal/transport/http/response_test.go | 158 + .../internal/transport/http/router.go | 44 +- .../transport/http/userapi/handler.go | 1 + .../transport/http/userapi/user_handler.go | 40 + .../http/walletapi/app_wallet_handler.go | 24 +- .../transport/http/walletapi/handler.go | 4 + .../deploy/mysql/initdb/001_user_service.sql | 2 +- services/user-service/internal/app/app.go | 21 +- .../internal/domain/invite/invite.go | 22 + .../internal/service/user/moderation_test.go | 5 + .../internal/service/user/service.go | 3 + .../internal/service/user/user.go | 12 + .../storage/mysql/invite/repository.go | 86 +- .../storage/mysql/invite/repository_test.go | 59 + .../internal/storage/mysql/user/repository.go | 8 + .../internal/testutil/mysqltest/mysqltest.go | 4 + .../internal/transport/grpc/convert.go | 15 + .../internal/transport/grpc/server.go | 10 + .../internal/domain/ledger/ledger.go | 23 + .../internal/service/wallet/resource.go | 9 + .../internal/service/wallet/service.go | 28 + .../internal/service/wallet/service_test.go | 83 + .../internal/storage/mysql/repository.go | 168 + .../storage/mysql/resource_repository.go | 77 + .../internal/transport/grpc/resource.go | 12 + .../internal/transport/grpc/server.go | 34 + 113 files changed, 11968 insertions(+), 2819 deletions(-) create mode 100644 docs/flutter对接/公共跳转参数.md create mode 100644 docs/flutter对接/弹窗配置Flutter对接.md create mode 100644 scripts/mysql/037_user_invite_valid_recharge_sources.sql create mode 100644 scripts/mysql/044_game_explore_winners.sql create mode 100644 server/admin/internal/modules/inviteactivityreward/handler.go create mode 100644 server/admin/internal/modules/inviteactivityreward/routes.go create mode 100644 server/admin/internal/repository/app_popup_repository.go create mode 100644 server/admin/migrations/049_admin_app_popups.sql create mode 100644 server/admin/migrations/050_invite_activity_reward_navigation.sql create mode 100644 server/admin/migrations/051_gift_delete_permission.sql create mode 100644 services/activity-service/deploy/mysql/migrations/002_invite_activity_reward.sql create mode 100644 services/activity-service/internal/app/user_events.go create mode 100644 services/activity-service/internal/client/invite_attribution_source.go create mode 100644 services/activity-service/internal/domain/inviteactivity/invite_activity.go create mode 100644 services/activity-service/internal/service/inviteactivity/service.go create mode 100644 services/activity-service/internal/service/inviteactivity/service_test.go create mode 100644 services/activity-service/internal/storage/mysql/invite_activity_reward_migration.go create mode 100644 services/activity-service/internal/storage/mysql/invite_activity_reward_repository.go create mode 100644 services/activity-service/internal/transport/grpc/invite_activity_reward_server.go create mode 100644 services/activity-service/tools/invite-activity-local-flow/main.go create mode 100644 services/game-service/deploy/mysql/initdata/001_self_games.sql create mode 100644 services/gateway-service/internal/client/invite_activity_reward_client.go create mode 100644 services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler.go create mode 100644 services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler_test.go diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index 548011e9..d1db570e 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -7202,6 +7202,1372 @@ func (x *ListCumulativeRechargeRewardGrantsResponse) GetTotal() int64 { return 0 } +// InviteActivityRewardTier 是邀请活动的金币奖励档位;recharge 使用充值门槛,valid_invite 使用有效邀请人数门槛。 +type InviteActivityRewardTier struct { + state protoimpl.MessageState `protogen:"open.v1"` + TierId int64 `protobuf:"varint,1,opt,name=tier_id,json=tierId,proto3" json:"tier_id,omitempty"` + RewardType string `protobuf:"bytes,2,opt,name=reward_type,json=rewardType,proto3" json:"reward_type,omitempty"` + TierCode string `protobuf:"bytes,3,opt,name=tier_code,json=tierCode,proto3" json:"tier_code,omitempty"` + TierName string `protobuf:"bytes,4,opt,name=tier_name,json=tierName,proto3" json:"tier_name,omitempty"` + ThresholdCoinAmount int64 `protobuf:"varint,5,opt,name=threshold_coin_amount,json=thresholdCoinAmount,proto3" json:"threshold_coin_amount,omitempty"` + ThresholdValidInviteCount int64 `protobuf:"varint,6,opt,name=threshold_valid_invite_count,json=thresholdValidInviteCount,proto3" json:"threshold_valid_invite_count,omitempty"` + RewardCoinAmount int64 `protobuf:"varint,7,opt,name=reward_coin_amount,json=rewardCoinAmount,proto3" json:"reward_coin_amount,omitempty"` + Status string `protobuf:"bytes,8,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,9,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + CreatedAtMs int64 `protobuf:"varint,10,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,11,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InviteActivityRewardTier) Reset() { + *x = InviteActivityRewardTier{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[91] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InviteActivityRewardTier) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InviteActivityRewardTier) ProtoMessage() {} + +func (x *InviteActivityRewardTier) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[91] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InviteActivityRewardTier.ProtoReflect.Descriptor instead. +func (*InviteActivityRewardTier) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{91} +} + +func (x *InviteActivityRewardTier) GetTierId() int64 { + if x != nil { + return x.TierId + } + return 0 +} + +func (x *InviteActivityRewardTier) GetRewardType() string { + if x != nil { + return x.RewardType + } + return "" +} + +func (x *InviteActivityRewardTier) GetTierCode() string { + if x != nil { + return x.TierCode + } + return "" +} + +func (x *InviteActivityRewardTier) GetTierName() string { + if x != nil { + return x.TierName + } + return "" +} + +func (x *InviteActivityRewardTier) GetThresholdCoinAmount() int64 { + if x != nil { + return x.ThresholdCoinAmount + } + return 0 +} + +func (x *InviteActivityRewardTier) GetThresholdValidInviteCount() int64 { + if x != nil { + return x.ThresholdValidInviteCount + } + return 0 +} + +func (x *InviteActivityRewardTier) GetRewardCoinAmount() int64 { + if x != nil { + return x.RewardCoinAmount + } + return 0 +} + +func (x *InviteActivityRewardTier) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *InviteActivityRewardTier) GetSortOrder() int32 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *InviteActivityRewardTier) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *InviteActivityRewardTier) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// InviteActivityRewardConfig 是当前 App 的邀请活动配置;关闭时保留档位但 App 不允许领取。 +type InviteActivityRewardConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Tiers []*InviteActivityRewardTier `protobuf:"bytes,3,rep,name=tiers,proto3" json:"tiers,omitempty"` + UpdatedByAdminId int64 `protobuf:"varint,4,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,5,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,6,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InviteActivityRewardConfig) Reset() { + *x = InviteActivityRewardConfig{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[92] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InviteActivityRewardConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InviteActivityRewardConfig) ProtoMessage() {} + +func (x *InviteActivityRewardConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[92] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InviteActivityRewardConfig.ProtoReflect.Descriptor instead. +func (*InviteActivityRewardConfig) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{92} +} + +func (x *InviteActivityRewardConfig) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *InviteActivityRewardConfig) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *InviteActivityRewardConfig) GetTiers() []*InviteActivityRewardTier { + if x != nil { + return x.Tiers + } + return nil +} + +func (x *InviteActivityRewardConfig) GetUpdatedByAdminId() int64 { + if x != nil { + return x.UpdatedByAdminId + } + return 0 +} + +func (x *InviteActivityRewardConfig) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *InviteActivityRewardConfig) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// InviteActivityRewardProgress 是邀请人在 UTC 自然月内的充值和有效邀请累计。 +type InviteActivityRewardProgress struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + CycleKey string `protobuf:"bytes,2,opt,name=cycle_key,json=cycleKey,proto3" json:"cycle_key,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + TotalRechargeCoinAmount int64 `protobuf:"varint,4,opt,name=total_recharge_coin_amount,json=totalRechargeCoinAmount,proto3" json:"total_recharge_coin_amount,omitempty"` + ValidInviteCount int64 `protobuf:"varint,5,opt,name=valid_invite_count,json=validInviteCount,proto3" json:"valid_invite_count,omitempty"` + ClaimedRewardCoinAmount int64 `protobuf:"varint,6,opt,name=claimed_reward_coin_amount,json=claimedRewardCoinAmount,proto3" json:"claimed_reward_coin_amount,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,7,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InviteActivityRewardProgress) Reset() { + *x = InviteActivityRewardProgress{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[93] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InviteActivityRewardProgress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InviteActivityRewardProgress) ProtoMessage() {} + +func (x *InviteActivityRewardProgress) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[93] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InviteActivityRewardProgress.ProtoReflect.Descriptor instead. +func (*InviteActivityRewardProgress) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{93} +} + +func (x *InviteActivityRewardProgress) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *InviteActivityRewardProgress) GetCycleKey() string { + if x != nil { + return x.CycleKey + } + return "" +} + +func (x *InviteActivityRewardProgress) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *InviteActivityRewardProgress) GetTotalRechargeCoinAmount() int64 { + if x != nil { + return x.TotalRechargeCoinAmount + } + return 0 +} + +func (x *InviteActivityRewardProgress) GetValidInviteCount() int64 { + if x != nil { + return x.ValidInviteCount + } + return 0 +} + +func (x *InviteActivityRewardProgress) GetClaimedRewardCoinAmount() int64 { + if x != nil { + return x.ClaimedRewardCoinAmount + } + return 0 +} + +func (x *InviteActivityRewardProgress) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// InviteActivityRewardClaim 是用户点击领取后创建的钱包发奖事实。 +type InviteActivityRewardClaim struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClaimId string `protobuf:"bytes,1,opt,name=claim_id,json=claimId,proto3" json:"claim_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + CycleKey string `protobuf:"bytes,3,opt,name=cycle_key,json=cycleKey,proto3" json:"cycle_key,omitempty"` + UserId int64 `protobuf:"varint,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + RewardType string `protobuf:"bytes,5,opt,name=reward_type,json=rewardType,proto3" json:"reward_type,omitempty"` + CommandId string `protobuf:"bytes,6,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + TierId int64 `protobuf:"varint,7,opt,name=tier_id,json=tierId,proto3" json:"tier_id,omitempty"` + TierCode string `protobuf:"bytes,8,opt,name=tier_code,json=tierCode,proto3" json:"tier_code,omitempty"` + TierName string `protobuf:"bytes,9,opt,name=tier_name,json=tierName,proto3" json:"tier_name,omitempty"` + ThresholdCoinAmount int64 `protobuf:"varint,10,opt,name=threshold_coin_amount,json=thresholdCoinAmount,proto3" json:"threshold_coin_amount,omitempty"` + ThresholdValidInviteCount int64 `protobuf:"varint,11,opt,name=threshold_valid_invite_count,json=thresholdValidInviteCount,proto3" json:"threshold_valid_invite_count,omitempty"` + ReachedValue int64 `protobuf:"varint,12,opt,name=reached_value,json=reachedValue,proto3" json:"reached_value,omitempty"` + RewardCoinAmount int64 `protobuf:"varint,13,opt,name=reward_coin_amount,json=rewardCoinAmount,proto3" json:"reward_coin_amount,omitempty"` + Status string `protobuf:"bytes,14,opt,name=status,proto3" json:"status,omitempty"` + WalletCommandId string `protobuf:"bytes,15,opt,name=wallet_command_id,json=walletCommandId,proto3" json:"wallet_command_id,omitempty"` + WalletTransactionId string `protobuf:"bytes,16,opt,name=wallet_transaction_id,json=walletTransactionId,proto3" json:"wallet_transaction_id,omitempty"` + FailureReason string `protobuf:"bytes,17,opt,name=failure_reason,json=failureReason,proto3" json:"failure_reason,omitempty"` + GrantedAtMs int64 `protobuf:"varint,18,opt,name=granted_at_ms,json=grantedAtMs,proto3" json:"granted_at_ms,omitempty"` + CreatedAtMs int64 `protobuf:"varint,19,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,20,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InviteActivityRewardClaim) Reset() { + *x = InviteActivityRewardClaim{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[94] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InviteActivityRewardClaim) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InviteActivityRewardClaim) ProtoMessage() {} + +func (x *InviteActivityRewardClaim) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[94] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InviteActivityRewardClaim.ProtoReflect.Descriptor instead. +func (*InviteActivityRewardClaim) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{94} +} + +func (x *InviteActivityRewardClaim) GetClaimId() string { + if x != nil { + return x.ClaimId + } + return "" +} + +func (x *InviteActivityRewardClaim) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *InviteActivityRewardClaim) GetCycleKey() string { + if x != nil { + return x.CycleKey + } + return "" +} + +func (x *InviteActivityRewardClaim) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *InviteActivityRewardClaim) GetRewardType() string { + if x != nil { + return x.RewardType + } + return "" +} + +func (x *InviteActivityRewardClaim) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *InviteActivityRewardClaim) GetTierId() int64 { + if x != nil { + return x.TierId + } + return 0 +} + +func (x *InviteActivityRewardClaim) GetTierCode() string { + if x != nil { + return x.TierCode + } + return "" +} + +func (x *InviteActivityRewardClaim) GetTierName() string { + if x != nil { + return x.TierName + } + return "" +} + +func (x *InviteActivityRewardClaim) GetThresholdCoinAmount() int64 { + if x != nil { + return x.ThresholdCoinAmount + } + return 0 +} + +func (x *InviteActivityRewardClaim) GetThresholdValidInviteCount() int64 { + if x != nil { + return x.ThresholdValidInviteCount + } + return 0 +} + +func (x *InviteActivityRewardClaim) GetReachedValue() int64 { + if x != nil { + return x.ReachedValue + } + return 0 +} + +func (x *InviteActivityRewardClaim) GetRewardCoinAmount() int64 { + if x != nil { + return x.RewardCoinAmount + } + return 0 +} + +func (x *InviteActivityRewardClaim) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *InviteActivityRewardClaim) GetWalletCommandId() string { + if x != nil { + return x.WalletCommandId + } + return "" +} + +func (x *InviteActivityRewardClaim) GetWalletTransactionId() string { + if x != nil { + return x.WalletTransactionId + } + return "" +} + +func (x *InviteActivityRewardClaim) GetFailureReason() string { + if x != nil { + return x.FailureReason + } + return "" +} + +func (x *InviteActivityRewardClaim) GetGrantedAtMs() int64 { + if x != nil { + return x.GrantedAtMs + } + return 0 +} + +func (x *InviteActivityRewardClaim) GetCreatedAtMs() int64 { + if x != nil { + return x.CreatedAtMs + } + return 0 +} + +func (x *InviteActivityRewardClaim) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// InviteActivityRewardStatus 是 App 邀请奖励页可直接展示的当前周期状态。 +type InviteActivityRewardStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Config *InviteActivityRewardConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Progress *InviteActivityRewardProgress `protobuf:"bytes,2,opt,name=progress,proto3" json:"progress,omitempty"` + Claims []*InviteActivityRewardClaim `protobuf:"bytes,3,rep,name=claims,proto3" json:"claims,omitempty"` + CycleKey string `protobuf:"bytes,4,opt,name=cycle_key,json=cycleKey,proto3" json:"cycle_key,omitempty"` + PeriodStartMs int64 `protobuf:"varint,5,opt,name=period_start_ms,json=periodStartMs,proto3" json:"period_start_ms,omitempty"` + PeriodEndMs int64 `protobuf:"varint,6,opt,name=period_end_ms,json=periodEndMs,proto3" json:"period_end_ms,omitempty"` + ServerTimeMs int64 `protobuf:"varint,7,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InviteActivityRewardStatus) Reset() { + *x = InviteActivityRewardStatus{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[95] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InviteActivityRewardStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InviteActivityRewardStatus) ProtoMessage() {} + +func (x *InviteActivityRewardStatus) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[95] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InviteActivityRewardStatus.ProtoReflect.Descriptor instead. +func (*InviteActivityRewardStatus) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{95} +} + +func (x *InviteActivityRewardStatus) GetConfig() *InviteActivityRewardConfig { + if x != nil { + return x.Config + } + return nil +} + +func (x *InviteActivityRewardStatus) GetProgress() *InviteActivityRewardProgress { + if x != nil { + return x.Progress + } + return nil +} + +func (x *InviteActivityRewardStatus) GetClaims() []*InviteActivityRewardClaim { + if x != nil { + return x.Claims + } + return nil +} + +func (x *InviteActivityRewardStatus) GetCycleKey() string { + if x != nil { + return x.CycleKey + } + return "" +} + +func (x *InviteActivityRewardStatus) GetPeriodStartMs() int64 { + if x != nil { + return x.PeriodStartMs + } + return 0 +} + +func (x *InviteActivityRewardStatus) GetPeriodEndMs() int64 { + if x != nil { + return x.PeriodEndMs + } + return 0 +} + +func (x *InviteActivityRewardStatus) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type GetInviteActivityRewardStatusRequest 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"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetInviteActivityRewardStatusRequest) Reset() { + *x = GetInviteActivityRewardStatusRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[96] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetInviteActivityRewardStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetInviteActivityRewardStatusRequest) ProtoMessage() {} + +func (x *GetInviteActivityRewardStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[96] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetInviteActivityRewardStatusRequest.ProtoReflect.Descriptor instead. +func (*GetInviteActivityRewardStatusRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{96} +} + +func (x *GetInviteActivityRewardStatusRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *GetInviteActivityRewardStatusRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +type GetInviteActivityRewardStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *InviteActivityRewardStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetInviteActivityRewardStatusResponse) Reset() { + *x = GetInviteActivityRewardStatusResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[97] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetInviteActivityRewardStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetInviteActivityRewardStatusResponse) ProtoMessage() {} + +func (x *GetInviteActivityRewardStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[97] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetInviteActivityRewardStatusResponse.ProtoReflect.Descriptor instead. +func (*GetInviteActivityRewardStatusResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{97} +} + +func (x *GetInviteActivityRewardStatusResponse) GetStatus() *InviteActivityRewardStatus { + if x != nil { + return x.Status + } + return nil +} + +type ClaimInviteActivityRewardRequest 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"` + RewardType string `protobuf:"bytes,3,opt,name=reward_type,json=rewardType,proto3" json:"reward_type,omitempty"` + TierId int64 `protobuf:"varint,4,opt,name=tier_id,json=tierId,proto3" json:"tier_id,omitempty"` + CommandId string `protobuf:"bytes,5,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClaimInviteActivityRewardRequest) Reset() { + *x = ClaimInviteActivityRewardRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[98] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClaimInviteActivityRewardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClaimInviteActivityRewardRequest) ProtoMessage() {} + +func (x *ClaimInviteActivityRewardRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[98] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClaimInviteActivityRewardRequest.ProtoReflect.Descriptor instead. +func (*ClaimInviteActivityRewardRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{98} +} + +func (x *ClaimInviteActivityRewardRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ClaimInviteActivityRewardRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ClaimInviteActivityRewardRequest) GetRewardType() string { + if x != nil { + return x.RewardType + } + return "" +} + +func (x *ClaimInviteActivityRewardRequest) GetTierId() int64 { + if x != nil { + return x.TierId + } + return 0 +} + +func (x *ClaimInviteActivityRewardRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +type ClaimInviteActivityRewardResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Claim *InviteActivityRewardClaim `protobuf:"bytes,1,opt,name=claim,proto3" json:"claim,omitempty"` + Status *InviteActivityRewardStatus `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClaimInviteActivityRewardResponse) Reset() { + *x = ClaimInviteActivityRewardResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClaimInviteActivityRewardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClaimInviteActivityRewardResponse) ProtoMessage() {} + +func (x *ClaimInviteActivityRewardResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[99] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClaimInviteActivityRewardResponse.ProtoReflect.Descriptor instead. +func (*ClaimInviteActivityRewardResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{99} +} + +func (x *ClaimInviteActivityRewardResponse) GetClaim() *InviteActivityRewardClaim { + if x != nil { + return x.Claim + } + return nil +} + +func (x *ClaimInviteActivityRewardResponse) GetStatus() *InviteActivityRewardStatus { + if x != nil { + return x.Status + } + return nil +} + +// InviteActivityLeaderboardEntry 是邀请活动月榜的一行;用户资料由 gateway 出口补齐。 +type InviteActivityLeaderboardEntry struct { + state protoimpl.MessageState `protogen:"open.v1"` + RankNo int32 `protobuf:"varint,1,opt,name=rank_no,json=rankNo,proto3" json:"rank_no,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ValidInviteCount int64 `protobuf:"varint,3,opt,name=valid_invite_count,json=validInviteCount,proto3" json:"valid_invite_count,omitempty"` + TotalRechargeCoinAmount int64 `protobuf:"varint,4,opt,name=total_recharge_coin_amount,json=totalRechargeCoinAmount,proto3" json:"total_recharge_coin_amount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InviteActivityLeaderboardEntry) Reset() { + *x = InviteActivityLeaderboardEntry{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[100] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InviteActivityLeaderboardEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InviteActivityLeaderboardEntry) ProtoMessage() {} + +func (x *InviteActivityLeaderboardEntry) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[100] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InviteActivityLeaderboardEntry.ProtoReflect.Descriptor instead. +func (*InviteActivityLeaderboardEntry) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{100} +} + +func (x *InviteActivityLeaderboardEntry) GetRankNo() int32 { + if x != nil { + return x.RankNo + } + return 0 +} + +func (x *InviteActivityLeaderboardEntry) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *InviteActivityLeaderboardEntry) GetValidInviteCount() int64 { + if x != nil { + return x.ValidInviteCount + } + return 0 +} + +func (x *InviteActivityLeaderboardEntry) GetTotalRechargeCoinAmount() int64 { + if x != nil { + return x.TotalRechargeCoinAmount + } + return 0 +} + +type ListInviteActivityLeaderboardRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + CycleKey string `protobuf:"bytes,2,opt,name=cycle_key,json=cycleKey,proto3" json:"cycle_key,omitempty"` + Page int32 `protobuf:"varint,3,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListInviteActivityLeaderboardRequest) Reset() { + *x = ListInviteActivityLeaderboardRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[101] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListInviteActivityLeaderboardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListInviteActivityLeaderboardRequest) ProtoMessage() {} + +func (x *ListInviteActivityLeaderboardRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[101] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListInviteActivityLeaderboardRequest.ProtoReflect.Descriptor instead. +func (*ListInviteActivityLeaderboardRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{101} +} + +func (x *ListInviteActivityLeaderboardRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListInviteActivityLeaderboardRequest) GetCycleKey() string { + if x != nil { + return x.CycleKey + } + return "" +} + +func (x *ListInviteActivityLeaderboardRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListInviteActivityLeaderboardRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ListInviteActivityLeaderboardResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Entries []*InviteActivityLeaderboardEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + CycleKey string `protobuf:"bytes,3,opt,name=cycle_key,json=cycleKey,proto3" json:"cycle_key,omitempty"` + ServerTimeMs int64 `protobuf:"varint,4,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListInviteActivityLeaderboardResponse) Reset() { + *x = ListInviteActivityLeaderboardResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[102] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListInviteActivityLeaderboardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListInviteActivityLeaderboardResponse) ProtoMessage() {} + +func (x *ListInviteActivityLeaderboardResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[102] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListInviteActivityLeaderboardResponse.ProtoReflect.Descriptor instead. +func (*ListInviteActivityLeaderboardResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{102} +} + +func (x *ListInviteActivityLeaderboardResponse) GetEntries() []*InviteActivityLeaderboardEntry { + if x != nil { + return x.Entries + } + return nil +} + +func (x *ListInviteActivityLeaderboardResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListInviteActivityLeaderboardResponse) GetCycleKey() string { + if x != nil { + return x.CycleKey + } + return "" +} + +func (x *ListInviteActivityLeaderboardResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + +type GetInviteActivityRewardConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetInviteActivityRewardConfigRequest) Reset() { + *x = GetInviteActivityRewardConfigRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[103] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetInviteActivityRewardConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetInviteActivityRewardConfigRequest) ProtoMessage() {} + +func (x *GetInviteActivityRewardConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[103] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetInviteActivityRewardConfigRequest.ProtoReflect.Descriptor instead. +func (*GetInviteActivityRewardConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{103} +} + +func (x *GetInviteActivityRewardConfigRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +type GetInviteActivityRewardConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Config *InviteActivityRewardConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetInviteActivityRewardConfigResponse) Reset() { + *x = GetInviteActivityRewardConfigResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[104] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetInviteActivityRewardConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetInviteActivityRewardConfigResponse) ProtoMessage() {} + +func (x *GetInviteActivityRewardConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[104] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetInviteActivityRewardConfigResponse.ProtoReflect.Descriptor instead. +func (*GetInviteActivityRewardConfigResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{104} +} + +func (x *GetInviteActivityRewardConfigResponse) GetConfig() *InviteActivityRewardConfig { + if x != nil { + return x.Config + } + return nil +} + +type UpdateInviteActivityRewardConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Tiers []*InviteActivityRewardTier `protobuf:"bytes,3,rep,name=tiers,proto3" json:"tiers,omitempty"` + OperatorAdminId int64 `protobuf:"varint,4,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateInviteActivityRewardConfigRequest) Reset() { + *x = UpdateInviteActivityRewardConfigRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[105] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateInviteActivityRewardConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateInviteActivityRewardConfigRequest) ProtoMessage() {} + +func (x *UpdateInviteActivityRewardConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[105] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateInviteActivityRewardConfigRequest.ProtoReflect.Descriptor instead. +func (*UpdateInviteActivityRewardConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{105} +} + +func (x *UpdateInviteActivityRewardConfigRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *UpdateInviteActivityRewardConfigRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *UpdateInviteActivityRewardConfigRequest) GetTiers() []*InviteActivityRewardTier { + if x != nil { + return x.Tiers + } + return nil +} + +func (x *UpdateInviteActivityRewardConfigRequest) GetOperatorAdminId() int64 { + if x != nil { + return x.OperatorAdminId + } + return 0 +} + +type UpdateInviteActivityRewardConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Config *InviteActivityRewardConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateInviteActivityRewardConfigResponse) Reset() { + *x = UpdateInviteActivityRewardConfigResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[106] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateInviteActivityRewardConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateInviteActivityRewardConfigResponse) ProtoMessage() {} + +func (x *UpdateInviteActivityRewardConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[106] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateInviteActivityRewardConfigResponse.ProtoReflect.Descriptor instead. +func (*UpdateInviteActivityRewardConfigResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{106} +} + +func (x *UpdateInviteActivityRewardConfigResponse) GetConfig() *InviteActivityRewardConfig { + if x != nil { + return x.Config + } + return nil +} + +type ListInviteActivityRewardClaimsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + RewardType string `protobuf:"bytes,3,opt,name=reward_type,json=rewardType,proto3" json:"reward_type,omitempty"` + UserId int64 `protobuf:"varint,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + CycleKey string `protobuf:"bytes,5,opt,name=cycle_key,json=cycleKey,proto3" json:"cycle_key,omitempty"` + Page int32 `protobuf:"varint,6,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,7,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListInviteActivityRewardClaimsRequest) Reset() { + *x = ListInviteActivityRewardClaimsRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListInviteActivityRewardClaimsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListInviteActivityRewardClaimsRequest) ProtoMessage() {} + +func (x *ListInviteActivityRewardClaimsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[107] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListInviteActivityRewardClaimsRequest.ProtoReflect.Descriptor instead. +func (*ListInviteActivityRewardClaimsRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{107} +} + +func (x *ListInviteActivityRewardClaimsRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListInviteActivityRewardClaimsRequest) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *ListInviteActivityRewardClaimsRequest) GetRewardType() string { + if x != nil { + return x.RewardType + } + return "" +} + +func (x *ListInviteActivityRewardClaimsRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ListInviteActivityRewardClaimsRequest) GetCycleKey() string { + if x != nil { + return x.CycleKey + } + return "" +} + +func (x *ListInviteActivityRewardClaimsRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListInviteActivityRewardClaimsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ListInviteActivityRewardClaimsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Claims []*InviteActivityRewardClaim `protobuf:"bytes,1,rep,name=claims,proto3" json:"claims,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListInviteActivityRewardClaimsResponse) Reset() { + *x = ListInviteActivityRewardClaimsResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListInviteActivityRewardClaimsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListInviteActivityRewardClaimsResponse) ProtoMessage() {} + +func (x *ListInviteActivityRewardClaimsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[108] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListInviteActivityRewardClaimsResponse.ProtoReflect.Descriptor instead. +func (*ListInviteActivityRewardClaimsResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{108} +} + +func (x *ListInviteActivityRewardClaimsResponse) GetClaims() []*InviteActivityRewardClaim { + if x != nil { + return x.Claims + } + return nil +} + +func (x *ListInviteActivityRewardClaimsResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + // RoomTurnoverRewardTier 是房间流水奖励的单个档位;达到 threshold_coin_spent 后可命中奖励。 type RoomTurnoverRewardTier struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -7220,7 +8586,7 @@ type RoomTurnoverRewardTier struct { func (x *RoomTurnoverRewardTier) Reset() { *x = RoomTurnoverRewardTier{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[91] + mi := &file_proto_activity_v1_activity_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7232,7 +8598,7 @@ func (x *RoomTurnoverRewardTier) String() string { func (*RoomTurnoverRewardTier) ProtoMessage() {} func (x *RoomTurnoverRewardTier) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[91] + mi := &file_proto_activity_v1_activity_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7245,7 +8611,7 @@ func (x *RoomTurnoverRewardTier) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomTurnoverRewardTier.ProtoReflect.Descriptor instead. func (*RoomTurnoverRewardTier) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{91} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{109} } func (x *RoomTurnoverRewardTier) GetTierId() int64 { @@ -7326,7 +8692,7 @@ type RoomTurnoverRewardConfig struct { func (x *RoomTurnoverRewardConfig) Reset() { *x = RoomTurnoverRewardConfig{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[92] + mi := &file_proto_activity_v1_activity_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7338,7 +8704,7 @@ func (x *RoomTurnoverRewardConfig) String() string { func (*RoomTurnoverRewardConfig) ProtoMessage() {} func (x *RoomTurnoverRewardConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[92] + mi := &file_proto_activity_v1_activity_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7351,7 +8717,7 @@ func (x *RoomTurnoverRewardConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomTurnoverRewardConfig.ProtoReflect.Descriptor instead. func (*RoomTurnoverRewardConfig) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{92} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{110} } func (x *RoomTurnoverRewardConfig) GetAppCode() string { @@ -7423,7 +8789,7 @@ type RoomTurnoverRewardSettlement struct { func (x *RoomTurnoverRewardSettlement) Reset() { *x = RoomTurnoverRewardSettlement{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[93] + mi := &file_proto_activity_v1_activity_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7435,7 +8801,7 @@ func (x *RoomTurnoverRewardSettlement) String() string { func (*RoomTurnoverRewardSettlement) ProtoMessage() {} func (x *RoomTurnoverRewardSettlement) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[93] + mi := &file_proto_activity_v1_activity_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7448,7 +8814,7 @@ func (x *RoomTurnoverRewardSettlement) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomTurnoverRewardSettlement.ProtoReflect.Descriptor instead. func (*RoomTurnoverRewardSettlement) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{93} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{111} } func (x *RoomTurnoverRewardSettlement) GetSettlementId() string { @@ -7596,7 +8962,7 @@ type RoomTurnoverRewardStatus struct { func (x *RoomTurnoverRewardStatus) Reset() { *x = RoomTurnoverRewardStatus{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[94] + mi := &file_proto_activity_v1_activity_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7608,7 +8974,7 @@ func (x *RoomTurnoverRewardStatus) String() string { func (*RoomTurnoverRewardStatus) ProtoMessage() {} func (x *RoomTurnoverRewardStatus) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[94] + mi := &file_proto_activity_v1_activity_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7621,7 +8987,7 @@ func (x *RoomTurnoverRewardStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomTurnoverRewardStatus.ProtoReflect.Descriptor instead. func (*RoomTurnoverRewardStatus) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{94} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{112} } func (x *RoomTurnoverRewardStatus) GetConfig() *RoomTurnoverRewardConfig { @@ -7706,7 +9072,7 @@ type GetRoomTurnoverRewardStatusRequest struct { func (x *GetRoomTurnoverRewardStatusRequest) Reset() { *x = GetRoomTurnoverRewardStatusRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[95] + mi := &file_proto_activity_v1_activity_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7718,7 +9084,7 @@ func (x *GetRoomTurnoverRewardStatusRequest) String() string { func (*GetRoomTurnoverRewardStatusRequest) ProtoMessage() {} func (x *GetRoomTurnoverRewardStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[95] + mi := &file_proto_activity_v1_activity_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7731,7 +9097,7 @@ func (x *GetRoomTurnoverRewardStatusRequest) ProtoReflect() protoreflect.Message // Deprecated: Use GetRoomTurnoverRewardStatusRequest.ProtoReflect.Descriptor instead. func (*GetRoomTurnoverRewardStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{95} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{113} } func (x *GetRoomTurnoverRewardStatusRequest) GetMeta() *RequestMeta { @@ -7771,7 +9137,7 @@ type GetRoomTurnoverRewardStatusResponse struct { func (x *GetRoomTurnoverRewardStatusResponse) Reset() { *x = GetRoomTurnoverRewardStatusResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[96] + mi := &file_proto_activity_v1_activity_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7783,7 +9149,7 @@ func (x *GetRoomTurnoverRewardStatusResponse) String() string { func (*GetRoomTurnoverRewardStatusResponse) ProtoMessage() {} func (x *GetRoomTurnoverRewardStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[96] + mi := &file_proto_activity_v1_activity_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7796,7 +9162,7 @@ func (x *GetRoomTurnoverRewardStatusResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use GetRoomTurnoverRewardStatusResponse.ProtoReflect.Descriptor instead. func (*GetRoomTurnoverRewardStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{96} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{114} } func (x *GetRoomTurnoverRewardStatusResponse) GetStatus() *RoomTurnoverRewardStatus { @@ -7815,7 +9181,7 @@ type GetRoomTurnoverRewardConfigRequest struct { func (x *GetRoomTurnoverRewardConfigRequest) Reset() { *x = GetRoomTurnoverRewardConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[97] + mi := &file_proto_activity_v1_activity_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7827,7 +9193,7 @@ func (x *GetRoomTurnoverRewardConfigRequest) String() string { func (*GetRoomTurnoverRewardConfigRequest) ProtoMessage() {} func (x *GetRoomTurnoverRewardConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[97] + mi := &file_proto_activity_v1_activity_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7840,7 +9206,7 @@ func (x *GetRoomTurnoverRewardConfigRequest) ProtoReflect() protoreflect.Message // Deprecated: Use GetRoomTurnoverRewardConfigRequest.ProtoReflect.Descriptor instead. func (*GetRoomTurnoverRewardConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{97} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{115} } func (x *GetRoomTurnoverRewardConfigRequest) GetMeta() *RequestMeta { @@ -7859,7 +9225,7 @@ type GetRoomTurnoverRewardConfigResponse struct { func (x *GetRoomTurnoverRewardConfigResponse) Reset() { *x = GetRoomTurnoverRewardConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[98] + mi := &file_proto_activity_v1_activity_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7871,7 +9237,7 @@ func (x *GetRoomTurnoverRewardConfigResponse) String() string { func (*GetRoomTurnoverRewardConfigResponse) ProtoMessage() {} func (x *GetRoomTurnoverRewardConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[98] + mi := &file_proto_activity_v1_activity_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7884,7 +9250,7 @@ func (x *GetRoomTurnoverRewardConfigResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use GetRoomTurnoverRewardConfigResponse.ProtoReflect.Descriptor instead. func (*GetRoomTurnoverRewardConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{98} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{116} } func (x *GetRoomTurnoverRewardConfigResponse) GetConfig() *RoomTurnoverRewardConfig { @@ -7906,7 +9272,7 @@ type UpdateRoomTurnoverRewardConfigRequest struct { func (x *UpdateRoomTurnoverRewardConfigRequest) Reset() { *x = UpdateRoomTurnoverRewardConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[99] + mi := &file_proto_activity_v1_activity_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7918,7 +9284,7 @@ func (x *UpdateRoomTurnoverRewardConfigRequest) String() string { func (*UpdateRoomTurnoverRewardConfigRequest) ProtoMessage() {} func (x *UpdateRoomTurnoverRewardConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[99] + mi := &file_proto_activity_v1_activity_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7931,7 +9297,7 @@ func (x *UpdateRoomTurnoverRewardConfigRequest) ProtoReflect() protoreflect.Mess // Deprecated: Use UpdateRoomTurnoverRewardConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateRoomTurnoverRewardConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{99} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{117} } func (x *UpdateRoomTurnoverRewardConfigRequest) GetMeta() *RequestMeta { @@ -7971,7 +9337,7 @@ type UpdateRoomTurnoverRewardConfigResponse struct { func (x *UpdateRoomTurnoverRewardConfigResponse) Reset() { *x = UpdateRoomTurnoverRewardConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[100] + mi := &file_proto_activity_v1_activity_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7983,7 +9349,7 @@ func (x *UpdateRoomTurnoverRewardConfigResponse) String() string { func (*UpdateRoomTurnoverRewardConfigResponse) ProtoMessage() {} func (x *UpdateRoomTurnoverRewardConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[100] + mi := &file_proto_activity_v1_activity_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7996,7 +9362,7 @@ func (x *UpdateRoomTurnoverRewardConfigResponse) ProtoReflect() protoreflect.Mes // Deprecated: Use UpdateRoomTurnoverRewardConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateRoomTurnoverRewardConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{100} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{118} } func (x *UpdateRoomTurnoverRewardConfigResponse) GetConfig() *RoomTurnoverRewardConfig { @@ -8021,7 +9387,7 @@ type ListRoomTurnoverRewardSettlementsRequest struct { func (x *ListRoomTurnoverRewardSettlementsRequest) Reset() { *x = ListRoomTurnoverRewardSettlementsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[101] + mi := &file_proto_activity_v1_activity_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8033,7 +9399,7 @@ func (x *ListRoomTurnoverRewardSettlementsRequest) String() string { func (*ListRoomTurnoverRewardSettlementsRequest) ProtoMessage() {} func (x *ListRoomTurnoverRewardSettlementsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[101] + mi := &file_proto_activity_v1_activity_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8046,7 +9412,7 @@ func (x *ListRoomTurnoverRewardSettlementsRequest) ProtoReflect() protoreflect.M // Deprecated: Use ListRoomTurnoverRewardSettlementsRequest.ProtoReflect.Descriptor instead. func (*ListRoomTurnoverRewardSettlementsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{101} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{119} } func (x *ListRoomTurnoverRewardSettlementsRequest) GetMeta() *RequestMeta { @@ -8108,7 +9474,7 @@ type ListRoomTurnoverRewardSettlementsResponse struct { func (x *ListRoomTurnoverRewardSettlementsResponse) Reset() { *x = ListRoomTurnoverRewardSettlementsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[102] + mi := &file_proto_activity_v1_activity_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8120,7 +9486,7 @@ func (x *ListRoomTurnoverRewardSettlementsResponse) String() string { func (*ListRoomTurnoverRewardSettlementsResponse) ProtoMessage() {} func (x *ListRoomTurnoverRewardSettlementsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[102] + mi := &file_proto_activity_v1_activity_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8133,7 +9499,7 @@ func (x *ListRoomTurnoverRewardSettlementsResponse) ProtoReflect() protoreflect. // Deprecated: Use ListRoomTurnoverRewardSettlementsResponse.ProtoReflect.Descriptor instead. func (*ListRoomTurnoverRewardSettlementsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{102} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{120} } func (x *ListRoomTurnoverRewardSettlementsResponse) GetSettlements() []*RoomTurnoverRewardSettlement { @@ -8161,7 +9527,7 @@ type RetryRoomTurnoverRewardSettlementRequest struct { func (x *RetryRoomTurnoverRewardSettlementRequest) Reset() { *x = RetryRoomTurnoverRewardSettlementRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[103] + mi := &file_proto_activity_v1_activity_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8173,7 +9539,7 @@ func (x *RetryRoomTurnoverRewardSettlementRequest) String() string { func (*RetryRoomTurnoverRewardSettlementRequest) ProtoMessage() {} func (x *RetryRoomTurnoverRewardSettlementRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[103] + mi := &file_proto_activity_v1_activity_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8186,7 +9552,7 @@ func (x *RetryRoomTurnoverRewardSettlementRequest) ProtoReflect() protoreflect.M // Deprecated: Use RetryRoomTurnoverRewardSettlementRequest.ProtoReflect.Descriptor instead. func (*RetryRoomTurnoverRewardSettlementRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{103} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{121} } func (x *RetryRoomTurnoverRewardSettlementRequest) GetMeta() *RequestMeta { @@ -8219,7 +9585,7 @@ type RetryRoomTurnoverRewardSettlementResponse struct { func (x *RetryRoomTurnoverRewardSettlementResponse) Reset() { *x = RetryRoomTurnoverRewardSettlementResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[104] + mi := &file_proto_activity_v1_activity_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8231,7 +9597,7 @@ func (x *RetryRoomTurnoverRewardSettlementResponse) String() string { func (*RetryRoomTurnoverRewardSettlementResponse) ProtoMessage() {} func (x *RetryRoomTurnoverRewardSettlementResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[104] + mi := &file_proto_activity_v1_activity_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8244,7 +9610,7 @@ func (x *RetryRoomTurnoverRewardSettlementResponse) ProtoReflect() protoreflect. // Deprecated: Use RetryRoomTurnoverRewardSettlementResponse.ProtoReflect.Descriptor instead. func (*RetryRoomTurnoverRewardSettlementResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{104} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{122} } func (x *RetryRoomTurnoverRewardSettlementResponse) GetSettlement() *RoomTurnoverRewardSettlement { @@ -8265,7 +9631,7 @@ type SevenDayCheckInReward struct { func (x *SevenDayCheckInReward) Reset() { *x = SevenDayCheckInReward{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[105] + mi := &file_proto_activity_v1_activity_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8277,7 +9643,7 @@ func (x *SevenDayCheckInReward) String() string { func (*SevenDayCheckInReward) ProtoMessage() {} func (x *SevenDayCheckInReward) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[105] + mi := &file_proto_activity_v1_activity_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8290,7 +9656,7 @@ func (x *SevenDayCheckInReward) ProtoReflect() protoreflect.Message { // Deprecated: Use SevenDayCheckInReward.ProtoReflect.Descriptor instead. func (*SevenDayCheckInReward) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{105} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{123} } func (x *SevenDayCheckInReward) GetDayIndex() int32 { @@ -8323,7 +9689,7 @@ type SevenDayCheckInConfig struct { func (x *SevenDayCheckInConfig) Reset() { *x = SevenDayCheckInConfig{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[106] + mi := &file_proto_activity_v1_activity_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8335,7 +9701,7 @@ func (x *SevenDayCheckInConfig) String() string { func (*SevenDayCheckInConfig) ProtoMessage() {} func (x *SevenDayCheckInConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[106] + mi := &file_proto_activity_v1_activity_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8348,7 +9714,7 @@ func (x *SevenDayCheckInConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SevenDayCheckInConfig.ProtoReflect.Descriptor instead. func (*SevenDayCheckInConfig) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{106} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{124} } func (x *SevenDayCheckInConfig) GetAppCode() string { @@ -8415,7 +9781,7 @@ type SevenDayCheckInRewardStatus struct { func (x *SevenDayCheckInRewardStatus) Reset() { *x = SevenDayCheckInRewardStatus{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[107] + mi := &file_proto_activity_v1_activity_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8427,7 +9793,7 @@ func (x *SevenDayCheckInRewardStatus) String() string { func (*SevenDayCheckInRewardStatus) ProtoMessage() {} func (x *SevenDayCheckInRewardStatus) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[107] + mi := &file_proto_activity_v1_activity_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8440,7 +9806,7 @@ func (x *SevenDayCheckInRewardStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use SevenDayCheckInRewardStatus.ProtoReflect.Descriptor instead. func (*SevenDayCheckInRewardStatus) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{107} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{125} } func (x *SevenDayCheckInRewardStatus) GetDayIndex() int32 { @@ -8495,7 +9861,7 @@ type GetSevenDayCheckInStatusRequest struct { func (x *GetSevenDayCheckInStatusRequest) Reset() { *x = GetSevenDayCheckInStatusRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[108] + mi := &file_proto_activity_v1_activity_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8507,7 +9873,7 @@ func (x *GetSevenDayCheckInStatusRequest) String() string { func (*GetSevenDayCheckInStatusRequest) ProtoMessage() {} func (x *GetSevenDayCheckInStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[108] + mi := &file_proto_activity_v1_activity_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8520,7 +9886,7 @@ func (x *GetSevenDayCheckInStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSevenDayCheckInStatusRequest.ProtoReflect.Descriptor instead. func (*GetSevenDayCheckInStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{108} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{126} } func (x *GetSevenDayCheckInStatusRequest) GetMeta() *RequestMeta { @@ -8557,7 +9923,7 @@ type GetSevenDayCheckInStatusResponse struct { func (x *GetSevenDayCheckInStatusResponse) Reset() { *x = GetSevenDayCheckInStatusResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[109] + mi := &file_proto_activity_v1_activity_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8569,7 +9935,7 @@ func (x *GetSevenDayCheckInStatusResponse) String() string { func (*GetSevenDayCheckInStatusResponse) ProtoMessage() {} func (x *GetSevenDayCheckInStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[109] + mi := &file_proto_activity_v1_activity_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8582,7 +9948,7 @@ func (x *GetSevenDayCheckInStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSevenDayCheckInStatusResponse.ProtoReflect.Descriptor instead. func (*GetSevenDayCheckInStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{109} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{127} } func (x *GetSevenDayCheckInStatusResponse) GetEnabled() bool { @@ -8680,7 +10046,7 @@ type SignSevenDayCheckInRequest struct { func (x *SignSevenDayCheckInRequest) Reset() { *x = SignSevenDayCheckInRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[110] + mi := &file_proto_activity_v1_activity_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8692,7 +10058,7 @@ func (x *SignSevenDayCheckInRequest) String() string { func (*SignSevenDayCheckInRequest) ProtoMessage() {} func (x *SignSevenDayCheckInRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[110] + mi := &file_proto_activity_v1_activity_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8705,7 +10071,7 @@ func (x *SignSevenDayCheckInRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SignSevenDayCheckInRequest.ProtoReflect.Descriptor instead. func (*SignSevenDayCheckInRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{110} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{128} } func (x *SignSevenDayCheckInRequest) GetMeta() *RequestMeta { @@ -8746,7 +10112,7 @@ type SignSevenDayCheckInResponse struct { func (x *SignSevenDayCheckInResponse) Reset() { *x = SignSevenDayCheckInResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[111] + mi := &file_proto_activity_v1_activity_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8758,7 +10124,7 @@ func (x *SignSevenDayCheckInResponse) String() string { func (*SignSevenDayCheckInResponse) ProtoMessage() {} func (x *SignSevenDayCheckInResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[111] + mi := &file_proto_activity_v1_activity_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8771,7 +10137,7 @@ func (x *SignSevenDayCheckInResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SignSevenDayCheckInResponse.ProtoReflect.Descriptor instead. func (*SignSevenDayCheckInResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{111} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{129} } func (x *SignSevenDayCheckInResponse) GetClaimId() string { @@ -8846,7 +10212,7 @@ type GetSevenDayCheckInConfigRequest struct { func (x *GetSevenDayCheckInConfigRequest) Reset() { *x = GetSevenDayCheckInConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[112] + mi := &file_proto_activity_v1_activity_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8858,7 +10224,7 @@ func (x *GetSevenDayCheckInConfigRequest) String() string { func (*GetSevenDayCheckInConfigRequest) ProtoMessage() {} func (x *GetSevenDayCheckInConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[112] + mi := &file_proto_activity_v1_activity_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8871,7 +10237,7 @@ func (x *GetSevenDayCheckInConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSevenDayCheckInConfigRequest.ProtoReflect.Descriptor instead. func (*GetSevenDayCheckInConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{112} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{130} } func (x *GetSevenDayCheckInConfigRequest) GetMeta() *RequestMeta { @@ -8890,7 +10256,7 @@ type GetSevenDayCheckInConfigResponse struct { func (x *GetSevenDayCheckInConfigResponse) Reset() { *x = GetSevenDayCheckInConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[113] + mi := &file_proto_activity_v1_activity_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8902,7 +10268,7 @@ func (x *GetSevenDayCheckInConfigResponse) String() string { func (*GetSevenDayCheckInConfigResponse) ProtoMessage() {} func (x *GetSevenDayCheckInConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[113] + mi := &file_proto_activity_v1_activity_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8915,7 +10281,7 @@ func (x *GetSevenDayCheckInConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSevenDayCheckInConfigResponse.ProtoReflect.Descriptor instead. func (*GetSevenDayCheckInConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{113} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{131} } func (x *GetSevenDayCheckInConfigResponse) GetConfig() *SevenDayCheckInConfig { @@ -8937,7 +10303,7 @@ type UpdateSevenDayCheckInConfigRequest struct { func (x *UpdateSevenDayCheckInConfigRequest) Reset() { *x = UpdateSevenDayCheckInConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[114] + mi := &file_proto_activity_v1_activity_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8949,7 +10315,7 @@ func (x *UpdateSevenDayCheckInConfigRequest) String() string { func (*UpdateSevenDayCheckInConfigRequest) ProtoMessage() {} func (x *UpdateSevenDayCheckInConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[114] + mi := &file_proto_activity_v1_activity_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8962,7 +10328,7 @@ func (x *UpdateSevenDayCheckInConfigRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateSevenDayCheckInConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateSevenDayCheckInConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{114} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{132} } func (x *UpdateSevenDayCheckInConfigRequest) GetMeta() *RequestMeta { @@ -9002,7 +10368,7 @@ type UpdateSevenDayCheckInConfigResponse struct { func (x *UpdateSevenDayCheckInConfigResponse) Reset() { *x = UpdateSevenDayCheckInConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[115] + mi := &file_proto_activity_v1_activity_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9014,7 +10380,7 @@ func (x *UpdateSevenDayCheckInConfigResponse) String() string { func (*UpdateSevenDayCheckInConfigResponse) ProtoMessage() {} func (x *UpdateSevenDayCheckInConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[115] + mi := &file_proto_activity_v1_activity_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9027,7 +10393,7 @@ func (x *UpdateSevenDayCheckInConfigResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use UpdateSevenDayCheckInConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateSevenDayCheckInConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{115} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{133} } func (x *UpdateSevenDayCheckInConfigResponse) GetConfig() *SevenDayCheckInConfig { @@ -9062,7 +10428,7 @@ type SevenDayCheckInClaim struct { func (x *SevenDayCheckInClaim) Reset() { *x = SevenDayCheckInClaim{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[116] + mi := &file_proto_activity_v1_activity_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9074,7 +10440,7 @@ func (x *SevenDayCheckInClaim) String() string { func (*SevenDayCheckInClaim) ProtoMessage() {} func (x *SevenDayCheckInClaim) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[116] + mi := &file_proto_activity_v1_activity_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9087,7 +10453,7 @@ func (x *SevenDayCheckInClaim) ProtoReflect() protoreflect.Message { // Deprecated: Use SevenDayCheckInClaim.ProtoReflect.Descriptor instead. func (*SevenDayCheckInClaim) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{116} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{134} } func (x *SevenDayCheckInClaim) GetClaimId() string { @@ -9217,7 +10583,7 @@ type ListSevenDayCheckInClaimsRequest struct { func (x *ListSevenDayCheckInClaimsRequest) Reset() { *x = ListSevenDayCheckInClaimsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[117] + mi := &file_proto_activity_v1_activity_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9229,7 +10595,7 @@ func (x *ListSevenDayCheckInClaimsRequest) String() string { func (*ListSevenDayCheckInClaimsRequest) ProtoMessage() {} func (x *ListSevenDayCheckInClaimsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[117] + mi := &file_proto_activity_v1_activity_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9242,7 +10608,7 @@ func (x *ListSevenDayCheckInClaimsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSevenDayCheckInClaimsRequest.ProtoReflect.Descriptor instead. func (*ListSevenDayCheckInClaimsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{117} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{135} } func (x *ListSevenDayCheckInClaimsRequest) GetMeta() *RequestMeta { @@ -9304,7 +10670,7 @@ type ListSevenDayCheckInClaimsResponse struct { func (x *ListSevenDayCheckInClaimsResponse) Reset() { *x = ListSevenDayCheckInClaimsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[118] + mi := &file_proto_activity_v1_activity_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9316,7 +10682,7 @@ func (x *ListSevenDayCheckInClaimsResponse) String() string { func (*ListSevenDayCheckInClaimsResponse) ProtoMessage() {} func (x *ListSevenDayCheckInClaimsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[118] + mi := &file_proto_activity_v1_activity_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9329,7 +10695,7 @@ func (x *ListSevenDayCheckInClaimsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListSevenDayCheckInClaimsResponse.ProtoReflect.Descriptor instead. func (*ListSevenDayCheckInClaimsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{118} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{136} } func (x *ListSevenDayCheckInClaimsResponse) GetClaims() []*SevenDayCheckInClaim { @@ -9362,7 +10728,7 @@ type LevelTrack struct { func (x *LevelTrack) Reset() { *x = LevelTrack{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[119] + mi := &file_proto_activity_v1_activity_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9374,7 +10740,7 @@ func (x *LevelTrack) String() string { func (*LevelTrack) ProtoMessage() {} func (x *LevelTrack) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[119] + mi := &file_proto_activity_v1_activity_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9387,7 +10753,7 @@ func (x *LevelTrack) ProtoReflect() protoreflect.Message { // Deprecated: Use LevelTrack.ProtoReflect.Descriptor instead. func (*LevelTrack) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{119} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{137} } func (x *LevelTrack) GetTrack() string { @@ -9460,7 +10826,7 @@ type LevelRule struct { func (x *LevelRule) Reset() { *x = LevelRule{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[120] + mi := &file_proto_activity_v1_activity_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9472,7 +10838,7 @@ func (x *LevelRule) String() string { func (*LevelRule) ProtoMessage() {} func (x *LevelRule) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[120] + mi := &file_proto_activity_v1_activity_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9485,7 +10851,7 @@ func (x *LevelRule) ProtoReflect() protoreflect.Message { // Deprecated: Use LevelRule.ProtoReflect.Descriptor instead. func (*LevelRule) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{120} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{138} } func (x *LevelRule) GetTrack() string { @@ -9595,7 +10961,7 @@ type LevelTier struct { func (x *LevelTier) Reset() { *x = LevelTier{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[121] + mi := &file_proto_activity_v1_activity_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9607,7 +10973,7 @@ func (x *LevelTier) String() string { func (*LevelTier) ProtoMessage() {} func (x *LevelTier) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[121] + mi := &file_proto_activity_v1_activity_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9620,7 +10986,7 @@ func (x *LevelTier) ProtoReflect() protoreflect.Message { // Deprecated: Use LevelTier.ProtoReflect.Descriptor instead. func (*LevelTier) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{121} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{139} } func (x *LevelTier) GetTierId() int64 { @@ -9743,7 +11109,7 @@ type LevelTrackOverview struct { func (x *LevelTrackOverview) Reset() { *x = LevelTrackOverview{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[122] + mi := &file_proto_activity_v1_activity_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9755,7 +11121,7 @@ func (x *LevelTrackOverview) String() string { func (*LevelTrackOverview) ProtoMessage() {} func (x *LevelTrackOverview) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[122] + mi := &file_proto_activity_v1_activity_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9768,7 +11134,7 @@ func (x *LevelTrackOverview) ProtoReflect() protoreflect.Message { // Deprecated: Use LevelTrackOverview.ProtoReflect.Descriptor instead. func (*LevelTrackOverview) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{122} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{140} } func (x *LevelTrackOverview) GetTrack() string { @@ -9872,7 +11238,7 @@ type GetMyLevelOverviewRequest struct { func (x *GetMyLevelOverviewRequest) Reset() { *x = GetMyLevelOverviewRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[123] + mi := &file_proto_activity_v1_activity_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9884,7 +11250,7 @@ func (x *GetMyLevelOverviewRequest) String() string { func (*GetMyLevelOverviewRequest) ProtoMessage() {} func (x *GetMyLevelOverviewRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[123] + mi := &file_proto_activity_v1_activity_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9897,7 +11263,7 @@ func (x *GetMyLevelOverviewRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyLevelOverviewRequest.ProtoReflect.Descriptor instead. func (*GetMyLevelOverviewRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{123} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{141} } func (x *GetMyLevelOverviewRequest) GetMeta() *RequestMeta { @@ -9924,7 +11290,7 @@ type GetMyLevelOverviewResponse struct { func (x *GetMyLevelOverviewResponse) Reset() { *x = GetMyLevelOverviewResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[124] + mi := &file_proto_activity_v1_activity_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9936,7 +11302,7 @@ func (x *GetMyLevelOverviewResponse) String() string { func (*GetMyLevelOverviewResponse) ProtoMessage() {} func (x *GetMyLevelOverviewResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[124] + mi := &file_proto_activity_v1_activity_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9949,7 +11315,7 @@ func (x *GetMyLevelOverviewResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyLevelOverviewResponse.ProtoReflect.Descriptor instead. func (*GetMyLevelOverviewResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{124} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{142} } func (x *GetMyLevelOverviewResponse) GetTracks() []*LevelTrackOverview { @@ -9977,7 +11343,7 @@ type GetLevelTrackRequest struct { func (x *GetLevelTrackRequest) Reset() { *x = GetLevelTrackRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[125] + mi := &file_proto_activity_v1_activity_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9989,7 +11355,7 @@ func (x *GetLevelTrackRequest) String() string { func (*GetLevelTrackRequest) ProtoMessage() {} func (x *GetLevelTrackRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[125] + mi := &file_proto_activity_v1_activity_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10002,7 +11368,7 @@ func (x *GetLevelTrackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLevelTrackRequest.ProtoReflect.Descriptor instead. func (*GetLevelTrackRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{125} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{143} } func (x *GetLevelTrackRequest) GetMeta() *RequestMeta { @@ -10038,7 +11404,7 @@ type GetLevelTrackResponse struct { func (x *GetLevelTrackResponse) Reset() { *x = GetLevelTrackResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[126] + mi := &file_proto_activity_v1_activity_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10050,7 +11416,7 @@ func (x *GetLevelTrackResponse) String() string { func (*GetLevelTrackResponse) ProtoMessage() {} func (x *GetLevelTrackResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[126] + mi := &file_proto_activity_v1_activity_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10063,7 +11429,7 @@ func (x *GetLevelTrackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLevelTrackResponse.ProtoReflect.Descriptor instead. func (*GetLevelTrackResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{126} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{144} } func (x *GetLevelTrackResponse) GetOverview() *LevelTrackOverview { @@ -10110,7 +11476,7 @@ type LevelDisplayTrackProfile struct { func (x *LevelDisplayTrackProfile) Reset() { *x = LevelDisplayTrackProfile{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[127] + mi := &file_proto_activity_v1_activity_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10122,7 +11488,7 @@ func (x *LevelDisplayTrackProfile) String() string { func (*LevelDisplayTrackProfile) ProtoMessage() {} func (x *LevelDisplayTrackProfile) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[127] + mi := &file_proto_activity_v1_activity_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10135,7 +11501,7 @@ func (x *LevelDisplayTrackProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use LevelDisplayTrackProfile.ProtoReflect.Descriptor instead. func (*LevelDisplayTrackProfile) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{127} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{145} } func (x *LevelDisplayTrackProfile) GetTrack() string { @@ -10200,7 +11566,7 @@ type UserLevelDisplayProfile struct { func (x *UserLevelDisplayProfile) Reset() { *x = UserLevelDisplayProfile{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[128] + mi := &file_proto_activity_v1_activity_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10212,7 +11578,7 @@ func (x *UserLevelDisplayProfile) String() string { func (*UserLevelDisplayProfile) ProtoMessage() {} func (x *UserLevelDisplayProfile) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[128] + mi := &file_proto_activity_v1_activity_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10225,7 +11591,7 @@ func (x *UserLevelDisplayProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use UserLevelDisplayProfile.ProtoReflect.Descriptor instead. func (*UserLevelDisplayProfile) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{128} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{146} } func (x *UserLevelDisplayProfile) GetUserId() int64 { @@ -10266,7 +11632,7 @@ type BatchGetUserLevelDisplayProfilesRequest struct { func (x *BatchGetUserLevelDisplayProfilesRequest) Reset() { *x = BatchGetUserLevelDisplayProfilesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[129] + mi := &file_proto_activity_v1_activity_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10278,7 +11644,7 @@ func (x *BatchGetUserLevelDisplayProfilesRequest) String() string { func (*BatchGetUserLevelDisplayProfilesRequest) ProtoMessage() {} func (x *BatchGetUserLevelDisplayProfilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[129] + mi := &file_proto_activity_v1_activity_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10291,7 +11657,7 @@ func (x *BatchGetUserLevelDisplayProfilesRequest) ProtoReflect() protoreflect.Me // Deprecated: Use BatchGetUserLevelDisplayProfilesRequest.ProtoReflect.Descriptor instead. func (*BatchGetUserLevelDisplayProfilesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{129} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{147} } func (x *BatchGetUserLevelDisplayProfilesRequest) GetMeta() *RequestMeta { @@ -10318,7 +11684,7 @@ type BatchGetUserLevelDisplayProfilesResponse struct { func (x *BatchGetUserLevelDisplayProfilesResponse) Reset() { *x = BatchGetUserLevelDisplayProfilesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[130] + mi := &file_proto_activity_v1_activity_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10330,7 +11696,7 @@ func (x *BatchGetUserLevelDisplayProfilesResponse) String() string { func (*BatchGetUserLevelDisplayProfilesResponse) ProtoMessage() {} func (x *BatchGetUserLevelDisplayProfilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[130] + mi := &file_proto_activity_v1_activity_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10343,7 +11709,7 @@ func (x *BatchGetUserLevelDisplayProfilesResponse) ProtoReflect() protoreflect.M // Deprecated: Use BatchGetUserLevelDisplayProfilesResponse.ProtoReflect.Descriptor instead. func (*BatchGetUserLevelDisplayProfilesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{130} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{148} } func (x *BatchGetUserLevelDisplayProfilesResponse) GetProfiles() []*UserLevelDisplayProfile { @@ -10382,7 +11748,7 @@ type LevelRewardJob struct { func (x *LevelRewardJob) Reset() { *x = LevelRewardJob{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[131] + mi := &file_proto_activity_v1_activity_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10394,7 +11760,7 @@ func (x *LevelRewardJob) String() string { func (*LevelRewardJob) ProtoMessage() {} func (x *LevelRewardJob) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[131] + mi := &file_proto_activity_v1_activity_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10407,7 +11773,7 @@ func (x *LevelRewardJob) ProtoReflect() protoreflect.Message { // Deprecated: Use LevelRewardJob.ProtoReflect.Descriptor instead. func (*LevelRewardJob) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{131} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{149} } func (x *LevelRewardJob) GetRewardJobId() string { @@ -10515,7 +11881,7 @@ type ListLevelRewardsRequest struct { func (x *ListLevelRewardsRequest) Reset() { *x = ListLevelRewardsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[132] + mi := &file_proto_activity_v1_activity_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10527,7 +11893,7 @@ func (x *ListLevelRewardsRequest) String() string { func (*ListLevelRewardsRequest) ProtoMessage() {} func (x *ListLevelRewardsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[132] + mi := &file_proto_activity_v1_activity_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10540,7 +11906,7 @@ func (x *ListLevelRewardsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelRewardsRequest.ProtoReflect.Descriptor instead. func (*ListLevelRewardsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{132} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{150} } func (x *ListLevelRewardsRequest) GetMeta() *RequestMeta { @@ -10595,7 +11961,7 @@ type ListLevelRewardsResponse struct { func (x *ListLevelRewardsResponse) Reset() { *x = ListLevelRewardsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[133] + mi := &file_proto_activity_v1_activity_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10607,7 +11973,7 @@ func (x *ListLevelRewardsResponse) String() string { func (*ListLevelRewardsResponse) ProtoMessage() {} func (x *ListLevelRewardsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[133] + mi := &file_proto_activity_v1_activity_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10620,7 +11986,7 @@ func (x *ListLevelRewardsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelRewardsResponse.ProtoReflect.Descriptor instead. func (*ListLevelRewardsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{133} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{151} } func (x *ListLevelRewardsResponse) GetRewards() []*LevelRewardJob { @@ -10657,7 +12023,7 @@ type ConsumeLevelEventRequest struct { func (x *ConsumeLevelEventRequest) Reset() { *x = ConsumeLevelEventRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[134] + mi := &file_proto_activity_v1_activity_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10669,7 +12035,7 @@ func (x *ConsumeLevelEventRequest) String() string { func (*ConsumeLevelEventRequest) ProtoMessage() {} func (x *ConsumeLevelEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[134] + mi := &file_proto_activity_v1_activity_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10682,7 +12048,7 @@ func (x *ConsumeLevelEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeLevelEventRequest.ProtoReflect.Descriptor instead. func (*ConsumeLevelEventRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{134} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{152} } func (x *ConsumeLevelEventRequest) GetMeta() *RequestMeta { @@ -10776,7 +12142,7 @@ type ConsumeLevelEventResponse struct { func (x *ConsumeLevelEventResponse) Reset() { *x = ConsumeLevelEventResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[135] + mi := &file_proto_activity_v1_activity_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10788,7 +12154,7 @@ func (x *ConsumeLevelEventResponse) String() string { func (*ConsumeLevelEventResponse) ProtoMessage() {} func (x *ConsumeLevelEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[135] + mi := &file_proto_activity_v1_activity_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10801,7 +12167,7 @@ func (x *ConsumeLevelEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeLevelEventResponse.ProtoReflect.Descriptor instead. func (*ConsumeLevelEventResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{135} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{153} } func (x *ConsumeLevelEventResponse) GetEventId() string { @@ -10861,7 +12227,7 @@ type SetUserLevelRequest struct { func (x *SetUserLevelRequest) Reset() { *x = SetUserLevelRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[136] + mi := &file_proto_activity_v1_activity_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10873,7 +12239,7 @@ func (x *SetUserLevelRequest) String() string { func (*SetUserLevelRequest) ProtoMessage() {} func (x *SetUserLevelRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[136] + mi := &file_proto_activity_v1_activity_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10886,7 +12252,7 @@ func (x *SetUserLevelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetUserLevelRequest.ProtoReflect.Descriptor instead. func (*SetUserLevelRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{136} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{154} } func (x *SetUserLevelRequest) GetMeta() *RequestMeta { @@ -10954,7 +12320,7 @@ type SetUserLevelResponse struct { func (x *SetUserLevelResponse) Reset() { *x = SetUserLevelResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[137] + mi := &file_proto_activity_v1_activity_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10966,7 +12332,7 @@ func (x *SetUserLevelResponse) String() string { func (*SetUserLevelResponse) ProtoMessage() {} func (x *SetUserLevelResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[137] + mi := &file_proto_activity_v1_activity_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10979,7 +12345,7 @@ func (x *SetUserLevelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetUserLevelResponse.ProtoReflect.Descriptor instead. func (*SetUserLevelResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{137} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{155} } func (x *SetUserLevelResponse) GetEventId() string { @@ -11050,7 +12416,7 @@ type RegistrationLevelBadgeGrant struct { func (x *RegistrationLevelBadgeGrant) Reset() { *x = RegistrationLevelBadgeGrant{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[138] + mi := &file_proto_activity_v1_activity_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11062,7 +12428,7 @@ func (x *RegistrationLevelBadgeGrant) String() string { func (*RegistrationLevelBadgeGrant) ProtoMessage() {} func (x *RegistrationLevelBadgeGrant) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[138] + mi := &file_proto_activity_v1_activity_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11075,7 +12441,7 @@ func (x *RegistrationLevelBadgeGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use RegistrationLevelBadgeGrant.ProtoReflect.Descriptor instead. func (*RegistrationLevelBadgeGrant) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{138} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{156} } func (x *RegistrationLevelBadgeGrant) GetTrack() string { @@ -11117,7 +12483,7 @@ type IssueRegistrationLevelBadgesRequest struct { func (x *IssueRegistrationLevelBadgesRequest) Reset() { *x = IssueRegistrationLevelBadgesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[139] + mi := &file_proto_activity_v1_activity_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11129,7 +12495,7 @@ func (x *IssueRegistrationLevelBadgesRequest) String() string { func (*IssueRegistrationLevelBadgesRequest) ProtoMessage() {} func (x *IssueRegistrationLevelBadgesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[139] + mi := &file_proto_activity_v1_activity_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11142,7 +12508,7 @@ func (x *IssueRegistrationLevelBadgesRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use IssueRegistrationLevelBadgesRequest.ProtoReflect.Descriptor instead. func (*IssueRegistrationLevelBadgesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{139} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{157} } func (x *IssueRegistrationLevelBadgesRequest) GetMeta() *RequestMeta { @@ -11176,7 +12542,7 @@ type IssueRegistrationLevelBadgesResponse struct { func (x *IssueRegistrationLevelBadgesResponse) Reset() { *x = IssueRegistrationLevelBadgesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[140] + mi := &file_proto_activity_v1_activity_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11188,7 +12554,7 @@ func (x *IssueRegistrationLevelBadgesResponse) String() string { func (*IssueRegistrationLevelBadgesResponse) ProtoMessage() {} func (x *IssueRegistrationLevelBadgesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[140] + mi := &file_proto_activity_v1_activity_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11201,7 +12567,7 @@ func (x *IssueRegistrationLevelBadgesResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use IssueRegistrationLevelBadgesResponse.ProtoReflect.Descriptor instead. func (*IssueRegistrationLevelBadgesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{140} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{158} } func (x *IssueRegistrationLevelBadgesResponse) GetGrants() []*RegistrationLevelBadgeGrant { @@ -11232,7 +12598,7 @@ type UpsertLevelTrackRequest struct { func (x *UpsertLevelTrackRequest) Reset() { *x = UpsertLevelTrackRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[141] + mi := &file_proto_activity_v1_activity_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11244,7 +12610,7 @@ func (x *UpsertLevelTrackRequest) String() string { func (*UpsertLevelTrackRequest) ProtoMessage() {} func (x *UpsertLevelTrackRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[141] + mi := &file_proto_activity_v1_activity_proto_msgTypes[159] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11257,7 +12623,7 @@ func (x *UpsertLevelTrackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTrackRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelTrackRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{141} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{159} } func (x *UpsertLevelTrackRequest) GetMeta() *RequestMeta { @@ -11312,7 +12678,7 @@ type UpsertLevelTrackResponse struct { func (x *UpsertLevelTrackResponse) Reset() { *x = UpsertLevelTrackResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[142] + mi := &file_proto_activity_v1_activity_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11324,7 +12690,7 @@ func (x *UpsertLevelTrackResponse) String() string { func (*UpsertLevelTrackResponse) ProtoMessage() {} func (x *UpsertLevelTrackResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[142] + mi := &file_proto_activity_v1_activity_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11337,7 +12703,7 @@ func (x *UpsertLevelTrackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTrackResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelTrackResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{142} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{160} } func (x *UpsertLevelTrackResponse) GetTrack() *LevelTrack { @@ -11372,7 +12738,7 @@ type UpsertLevelRuleRequest struct { func (x *UpsertLevelRuleRequest) Reset() { *x = UpsertLevelRuleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[143] + mi := &file_proto_activity_v1_activity_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11384,7 +12750,7 @@ func (x *UpsertLevelRuleRequest) String() string { func (*UpsertLevelRuleRequest) ProtoMessage() {} func (x *UpsertLevelRuleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[143] + mi := &file_proto_activity_v1_activity_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11397,7 +12763,7 @@ func (x *UpsertLevelRuleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelRuleRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelRuleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{143} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{161} } func (x *UpsertLevelRuleRequest) GetMeta() *RequestMeta { @@ -11480,7 +12846,7 @@ type UpsertLevelRuleResponse struct { func (x *UpsertLevelRuleResponse) Reset() { *x = UpsertLevelRuleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[144] + mi := &file_proto_activity_v1_activity_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11492,7 +12858,7 @@ func (x *UpsertLevelRuleResponse) String() string { func (*UpsertLevelRuleResponse) ProtoMessage() {} func (x *UpsertLevelRuleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[144] + mi := &file_proto_activity_v1_activity_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11505,7 +12871,7 @@ func (x *UpsertLevelRuleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelRuleResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelRuleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{144} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{162} } func (x *UpsertLevelRuleResponse) GetRule() *LevelRule { @@ -11542,7 +12908,7 @@ type UpsertLevelTierRequest struct { func (x *UpsertLevelTierRequest) Reset() { *x = UpsertLevelTierRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[145] + mi := &file_proto_activity_v1_activity_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11554,7 +12920,7 @@ func (x *UpsertLevelTierRequest) String() string { func (*UpsertLevelTierRequest) ProtoMessage() {} func (x *UpsertLevelTierRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[145] + mi := &file_proto_activity_v1_activity_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11567,7 +12933,7 @@ func (x *UpsertLevelTierRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTierRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelTierRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{145} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{163} } func (x *UpsertLevelTierRequest) GetMeta() *RequestMeta { @@ -11664,7 +13030,7 @@ type UpsertLevelTierResponse struct { func (x *UpsertLevelTierResponse) Reset() { *x = UpsertLevelTierResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[146] + mi := &file_proto_activity_v1_activity_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11676,7 +13042,7 @@ func (x *UpsertLevelTierResponse) String() string { func (*UpsertLevelTierResponse) ProtoMessage() {} func (x *UpsertLevelTierResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[146] + mi := &file_proto_activity_v1_activity_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11689,7 +13055,7 @@ func (x *UpsertLevelTierResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTierResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelTierResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{146} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{164} } func (x *UpsertLevelTierResponse) GetTier() *LevelTier { @@ -11718,7 +13084,7 @@ type ListLevelConfigRequest struct { func (x *ListLevelConfigRequest) Reset() { *x = ListLevelConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[147] + mi := &file_proto_activity_v1_activity_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11730,7 +13096,7 @@ func (x *ListLevelConfigRequest) String() string { func (*ListLevelConfigRequest) ProtoMessage() {} func (x *ListLevelConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[147] + mi := &file_proto_activity_v1_activity_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11743,7 +13109,7 @@ func (x *ListLevelConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelConfigRequest.ProtoReflect.Descriptor instead. func (*ListLevelConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{147} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{165} } func (x *ListLevelConfigRequest) GetMeta() *RequestMeta { @@ -11779,7 +13145,7 @@ type ListLevelConfigResponse struct { func (x *ListLevelConfigResponse) Reset() { *x = ListLevelConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[148] + mi := &file_proto_activity_v1_activity_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11791,7 +13157,7 @@ func (x *ListLevelConfigResponse) String() string { func (*ListLevelConfigResponse) ProtoMessage() {} func (x *ListLevelConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[148] + mi := &file_proto_activity_v1_activity_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11804,7 +13170,7 @@ func (x *ListLevelConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelConfigResponse.ProtoReflect.Descriptor instead. func (*ListLevelConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{148} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{166} } func (x *ListLevelConfigResponse) GetTracks() []*LevelTrack { @@ -11850,7 +13216,7 @@ type AchievementCondition struct { func (x *AchievementCondition) Reset() { *x = AchievementCondition{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[149] + mi := &file_proto_activity_v1_activity_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11862,7 +13228,7 @@ func (x *AchievementCondition) String() string { func (*AchievementCondition) ProtoMessage() {} func (x *AchievementCondition) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[149] + mi := &file_proto_activity_v1_activity_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11875,7 +13241,7 @@ func (x *AchievementCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use AchievementCondition.ProtoReflect.Descriptor instead. func (*AchievementCondition) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{149} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{167} } func (x *AchievementCondition) GetConditionId() string { @@ -11956,7 +13322,7 @@ type AchievementDefinition struct { func (x *AchievementDefinition) Reset() { *x = AchievementDefinition{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[150] + mi := &file_proto_activity_v1_activity_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11968,7 +13334,7 @@ func (x *AchievementDefinition) String() string { func (*AchievementDefinition) ProtoMessage() {} func (x *AchievementDefinition) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[150] + mi := &file_proto_activity_v1_activity_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11981,7 +13347,7 @@ func (x *AchievementDefinition) ProtoReflect() protoreflect.Message { // Deprecated: Use AchievementDefinition.ProtoReflect.Descriptor instead. func (*AchievementDefinition) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{150} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{168} } func (x *AchievementDefinition) GetAchievementId() string { @@ -12146,7 +13512,7 @@ type UserAchievement struct { func (x *UserAchievement) Reset() { *x = UserAchievement{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[151] + mi := &file_proto_activity_v1_activity_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12158,7 +13524,7 @@ func (x *UserAchievement) String() string { func (*UserAchievement) ProtoMessage() {} func (x *UserAchievement) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[151] + mi := &file_proto_activity_v1_activity_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12171,7 +13537,7 @@ func (x *UserAchievement) ProtoReflect() protoreflect.Message { // Deprecated: Use UserAchievement.ProtoReflect.Descriptor instead. func (*UserAchievement) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{151} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{169} } func (x *UserAchievement) GetDefinition() *AchievementDefinition { @@ -12237,7 +13603,7 @@ type ListAchievementsRequest struct { func (x *ListAchievementsRequest) Reset() { *x = ListAchievementsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[152] + mi := &file_proto_activity_v1_activity_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12249,7 +13615,7 @@ func (x *ListAchievementsRequest) String() string { func (*ListAchievementsRequest) ProtoMessage() {} func (x *ListAchievementsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[152] + mi := &file_proto_activity_v1_activity_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12262,7 +13628,7 @@ func (x *ListAchievementsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAchievementsRequest.ProtoReflect.Descriptor instead. func (*ListAchievementsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{152} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{170} } func (x *ListAchievementsRequest) GetMeta() *RequestMeta { @@ -12318,7 +13684,7 @@ type ListAchievementsResponse struct { func (x *ListAchievementsResponse) Reset() { *x = ListAchievementsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[153] + mi := &file_proto_activity_v1_activity_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12330,7 +13696,7 @@ func (x *ListAchievementsResponse) String() string { func (*ListAchievementsResponse) ProtoMessage() {} func (x *ListAchievementsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[153] + mi := &file_proto_activity_v1_activity_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12343,7 +13709,7 @@ func (x *ListAchievementsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAchievementsResponse.ProtoReflect.Descriptor instead. func (*ListAchievementsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{153} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{171} } func (x *ListAchievementsResponse) GetAchievements() []*UserAchievement { @@ -12384,7 +13750,7 @@ type ConsumeAchievementEventRequest struct { func (x *ConsumeAchievementEventRequest) Reset() { *x = ConsumeAchievementEventRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[154] + mi := &file_proto_activity_v1_activity_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12396,7 +13762,7 @@ func (x *ConsumeAchievementEventRequest) String() string { func (*ConsumeAchievementEventRequest) ProtoMessage() {} func (x *ConsumeAchievementEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[154] + mi := &file_proto_activity_v1_activity_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12409,7 +13775,7 @@ func (x *ConsumeAchievementEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeAchievementEventRequest.ProtoReflect.Descriptor instead. func (*ConsumeAchievementEventRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{154} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{172} } func (x *ConsumeAchievementEventRequest) GetMeta() *RequestMeta { @@ -12488,7 +13854,7 @@ type ConsumeAchievementEventResponse struct { func (x *ConsumeAchievementEventResponse) Reset() { *x = ConsumeAchievementEventResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[155] + mi := &file_proto_activity_v1_activity_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12500,7 +13866,7 @@ func (x *ConsumeAchievementEventResponse) String() string { func (*ConsumeAchievementEventResponse) ProtoMessage() {} func (x *ConsumeAchievementEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[155] + mi := &file_proto_activity_v1_activity_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12513,7 +13879,7 @@ func (x *ConsumeAchievementEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeAchievementEventResponse.ProtoReflect.Descriptor instead. func (*ConsumeAchievementEventResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{155} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{173} } func (x *ConsumeAchievementEventResponse) GetEventId() string { @@ -12568,7 +13934,7 @@ type BadgeDisplayItem struct { func (x *BadgeDisplayItem) Reset() { *x = BadgeDisplayItem{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[156] + mi := &file_proto_activity_v1_activity_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12580,7 +13946,7 @@ func (x *BadgeDisplayItem) String() string { func (*BadgeDisplayItem) ProtoMessage() {} func (x *BadgeDisplayItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[156] + mi := &file_proto_activity_v1_activity_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12593,7 +13959,7 @@ func (x *BadgeDisplayItem) ProtoReflect() protoreflect.Message { // Deprecated: Use BadgeDisplayItem.ProtoReflect.Descriptor instead. func (*BadgeDisplayItem) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{156} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{174} } func (x *BadgeDisplayItem) GetSlot() string { @@ -12669,7 +14035,7 @@ type ListMyBadgesRequest struct { func (x *ListMyBadgesRequest) Reset() { *x = ListMyBadgesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[157] + mi := &file_proto_activity_v1_activity_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12681,7 +14047,7 @@ func (x *ListMyBadgesRequest) String() string { func (*ListMyBadgesRequest) ProtoMessage() {} func (x *ListMyBadgesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[157] + mi := &file_proto_activity_v1_activity_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12694,7 +14060,7 @@ func (x *ListMyBadgesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMyBadgesRequest.ProtoReflect.Descriptor instead. func (*ListMyBadgesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{157} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{175} } func (x *ListMyBadgesRequest) GetMeta() *RequestMeta { @@ -12723,7 +14089,7 @@ type ListMyBadgesResponse struct { func (x *ListMyBadgesResponse) Reset() { *x = ListMyBadgesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[158] + mi := &file_proto_activity_v1_activity_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12735,7 +14101,7 @@ func (x *ListMyBadgesResponse) String() string { func (*ListMyBadgesResponse) ProtoMessage() {} func (x *ListMyBadgesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[158] + mi := &file_proto_activity_v1_activity_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12748,7 +14114,7 @@ func (x *ListMyBadgesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMyBadgesResponse.ProtoReflect.Descriptor instead. func (*ListMyBadgesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{158} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{176} } func (x *ListMyBadgesResponse) GetStripBadges() []*BadgeDisplayItem { @@ -12792,7 +14158,7 @@ type SetBadgeDisplayRequest struct { func (x *SetBadgeDisplayRequest) Reset() { *x = SetBadgeDisplayRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[159] + mi := &file_proto_activity_v1_activity_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12804,7 +14170,7 @@ func (x *SetBadgeDisplayRequest) String() string { func (*SetBadgeDisplayRequest) ProtoMessage() {} func (x *SetBadgeDisplayRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[159] + mi := &file_proto_activity_v1_activity_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12817,7 +14183,7 @@ func (x *SetBadgeDisplayRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetBadgeDisplayRequest.ProtoReflect.Descriptor instead. func (*SetBadgeDisplayRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{159} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{177} } func (x *SetBadgeDisplayRequest) GetMeta() *RequestMeta { @@ -12864,7 +14230,7 @@ type SetBadgeDisplayResponse struct { func (x *SetBadgeDisplayResponse) Reset() { *x = SetBadgeDisplayResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[160] + mi := &file_proto_activity_v1_activity_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12876,7 +14242,7 @@ func (x *SetBadgeDisplayResponse) String() string { func (*SetBadgeDisplayResponse) ProtoMessage() {} func (x *SetBadgeDisplayResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[160] + mi := &file_proto_activity_v1_activity_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12889,7 +14255,7 @@ func (x *SetBadgeDisplayResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetBadgeDisplayResponse.ProtoReflect.Descriptor instead. func (*SetBadgeDisplayResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{160} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{178} } func (x *SetBadgeDisplayResponse) GetProfile() *ListMyBadgesResponse { @@ -12925,7 +14291,7 @@ type UpsertAchievementDefinitionRequest struct { func (x *UpsertAchievementDefinitionRequest) Reset() { *x = UpsertAchievementDefinitionRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[161] + mi := &file_proto_activity_v1_activity_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12937,7 +14303,7 @@ func (x *UpsertAchievementDefinitionRequest) String() string { func (*UpsertAchievementDefinitionRequest) ProtoMessage() {} func (x *UpsertAchievementDefinitionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[161] + mi := &file_proto_activity_v1_activity_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12950,7 +14316,7 @@ func (x *UpsertAchievementDefinitionRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpsertAchievementDefinitionRequest.ProtoReflect.Descriptor instead. func (*UpsertAchievementDefinitionRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{161} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{179} } func (x *UpsertAchievementDefinitionRequest) GetMeta() *RequestMeta { @@ -13089,7 +14455,7 @@ type UpsertAchievementDefinitionResponse struct { func (x *UpsertAchievementDefinitionResponse) Reset() { *x = UpsertAchievementDefinitionResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[162] + mi := &file_proto_activity_v1_activity_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13101,7 +14467,7 @@ func (x *UpsertAchievementDefinitionResponse) String() string { func (*UpsertAchievementDefinitionResponse) ProtoMessage() {} func (x *UpsertAchievementDefinitionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[162] + mi := &file_proto_activity_v1_activity_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13114,7 +14480,7 @@ func (x *UpsertAchievementDefinitionResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use UpsertAchievementDefinitionResponse.ProtoReflect.Descriptor instead. func (*UpsertAchievementDefinitionResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{162} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{180} } func (x *UpsertAchievementDefinitionResponse) GetAchievement() *AchievementDefinition { @@ -13153,7 +14519,7 @@ type LuckyGiftMeta struct { func (x *LuckyGiftMeta) Reset() { *x = LuckyGiftMeta{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[163] + mi := &file_proto_activity_v1_activity_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13165,7 +14531,7 @@ func (x *LuckyGiftMeta) String() string { func (*LuckyGiftMeta) ProtoMessage() {} func (x *LuckyGiftMeta) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[163] + mi := &file_proto_activity_v1_activity_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13178,7 +14544,7 @@ func (x *LuckyGiftMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftMeta.ProtoReflect.Descriptor instead. func (*LuckyGiftMeta) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{163} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{181} } func (x *LuckyGiftMeta) GetMeta() *RequestMeta { @@ -13287,7 +14653,7 @@ type LuckyGiftTier struct { func (x *LuckyGiftTier) Reset() { *x = LuckyGiftTier{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[164] + mi := &file_proto_activity_v1_activity_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13299,7 +14665,7 @@ func (x *LuckyGiftTier) String() string { func (*LuckyGiftTier) ProtoMessage() {} func (x *LuckyGiftTier) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[164] + mi := &file_proto_activity_v1_activity_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13312,7 +14678,7 @@ func (x *LuckyGiftTier) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftTier.ProtoReflect.Descriptor instead. func (*LuckyGiftTier) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{164} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{182} } func (x *LuckyGiftTier) GetPool() string { @@ -13412,7 +14778,7 @@ type LuckyGiftConfig struct { func (x *LuckyGiftConfig) Reset() { *x = LuckyGiftConfig{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[165] + mi := &file_proto_activity_v1_activity_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13424,7 +14790,7 @@ func (x *LuckyGiftConfig) String() string { func (*LuckyGiftConfig) ProtoMessage() {} func (x *LuckyGiftConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[165] + mi := &file_proto_activity_v1_activity_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13437,7 +14803,7 @@ func (x *LuckyGiftConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftConfig.ProtoReflect.Descriptor instead. func (*LuckyGiftConfig) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{165} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{183} } func (x *LuckyGiftConfig) GetAppCode() string { @@ -13736,7 +15102,7 @@ type LuckyGiftRuleTier struct { func (x *LuckyGiftRuleTier) Reset() { *x = LuckyGiftRuleTier{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[166] + mi := &file_proto_activity_v1_activity_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13748,7 +15114,7 @@ func (x *LuckyGiftRuleTier) String() string { func (*LuckyGiftRuleTier) ProtoMessage() {} func (x *LuckyGiftRuleTier) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[166] + mi := &file_proto_activity_v1_activity_proto_msgTypes[184] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13761,7 +15127,7 @@ func (x *LuckyGiftRuleTier) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftRuleTier.ProtoReflect.Descriptor instead. func (*LuckyGiftRuleTier) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{166} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{184} } func (x *LuckyGiftRuleTier) GetStage() string { @@ -13830,7 +15196,7 @@ type LuckyGiftRuleStage struct { func (x *LuckyGiftRuleStage) Reset() { *x = LuckyGiftRuleStage{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[167] + mi := &file_proto_activity_v1_activity_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13842,7 +15208,7 @@ func (x *LuckyGiftRuleStage) String() string { func (*LuckyGiftRuleStage) ProtoMessage() {} func (x *LuckyGiftRuleStage) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[167] + mi := &file_proto_activity_v1_activity_proto_msgTypes[185] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13855,7 +15221,7 @@ func (x *LuckyGiftRuleStage) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftRuleStage.ProtoReflect.Descriptor instead. func (*LuckyGiftRuleStage) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{167} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{185} } func (x *LuckyGiftRuleStage) GetStage() string { @@ -13901,7 +15267,7 @@ type LuckyGiftRuleConfig struct { func (x *LuckyGiftRuleConfig) Reset() { *x = LuckyGiftRuleConfig{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[168] + mi := &file_proto_activity_v1_activity_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13913,7 +15279,7 @@ func (x *LuckyGiftRuleConfig) String() string { func (*LuckyGiftRuleConfig) ProtoMessage() {} func (x *LuckyGiftRuleConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[168] + mi := &file_proto_activity_v1_activity_proto_msgTypes[186] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13926,7 +15292,7 @@ func (x *LuckyGiftRuleConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftRuleConfig.ProtoReflect.Descriptor instead. func (*LuckyGiftRuleConfig) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{168} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{186} } func (x *LuckyGiftRuleConfig) GetAppCode() string { @@ -14089,7 +15455,7 @@ type CheckLuckyGiftRequest struct { func (x *CheckLuckyGiftRequest) Reset() { *x = CheckLuckyGiftRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[169] + mi := &file_proto_activity_v1_activity_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14101,7 +15467,7 @@ func (x *CheckLuckyGiftRequest) String() string { func (*CheckLuckyGiftRequest) ProtoMessage() {} func (x *CheckLuckyGiftRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[169] + mi := &file_proto_activity_v1_activity_proto_msgTypes[187] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14114,7 +15480,7 @@ func (x *CheckLuckyGiftRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckLuckyGiftRequest.ProtoReflect.Descriptor instead. func (*CheckLuckyGiftRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{169} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{187} } func (x *CheckLuckyGiftRequest) GetMeta() *RequestMeta { @@ -14168,7 +15534,7 @@ type CheckLuckyGiftResponse struct { func (x *CheckLuckyGiftResponse) Reset() { *x = CheckLuckyGiftResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[170] + mi := &file_proto_activity_v1_activity_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14180,7 +15546,7 @@ func (x *CheckLuckyGiftResponse) String() string { func (*CheckLuckyGiftResponse) ProtoMessage() {} func (x *CheckLuckyGiftResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[170] + mi := &file_proto_activity_v1_activity_proto_msgTypes[188] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14193,7 +15559,7 @@ func (x *CheckLuckyGiftResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckLuckyGiftResponse.ProtoReflect.Descriptor instead. func (*CheckLuckyGiftResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{170} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{188} } func (x *CheckLuckyGiftResponse) GetEnabled() bool { @@ -14283,7 +15649,7 @@ type LuckyGiftDrawResult struct { func (x *LuckyGiftDrawResult) Reset() { *x = LuckyGiftDrawResult{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[171] + mi := &file_proto_activity_v1_activity_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14295,7 +15661,7 @@ func (x *LuckyGiftDrawResult) String() string { func (*LuckyGiftDrawResult) ProtoMessage() {} func (x *LuckyGiftDrawResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[171] + mi := &file_proto_activity_v1_activity_proto_msgTypes[189] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14308,7 +15674,7 @@ func (x *LuckyGiftDrawResult) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftDrawResult.ProtoReflect.Descriptor instead. func (*LuckyGiftDrawResult) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{171} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{189} } func (x *LuckyGiftDrawResult) GetDrawId() string { @@ -14481,7 +15847,7 @@ type ExecuteLuckyGiftDrawRequest struct { func (x *ExecuteLuckyGiftDrawRequest) Reset() { *x = ExecuteLuckyGiftDrawRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[172] + mi := &file_proto_activity_v1_activity_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14493,7 +15859,7 @@ func (x *ExecuteLuckyGiftDrawRequest) String() string { func (*ExecuteLuckyGiftDrawRequest) ProtoMessage() {} func (x *ExecuteLuckyGiftDrawRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[172] + mi := &file_proto_activity_v1_activity_proto_msgTypes[190] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14506,7 +15872,7 @@ func (x *ExecuteLuckyGiftDrawRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteLuckyGiftDrawRequest.ProtoReflect.Descriptor instead. func (*ExecuteLuckyGiftDrawRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{172} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{190} } func (x *ExecuteLuckyGiftDrawRequest) GetLuckyGift() *LuckyGiftMeta { @@ -14525,7 +15891,7 @@ type ExecuteLuckyGiftDrawResponse struct { func (x *ExecuteLuckyGiftDrawResponse) Reset() { *x = ExecuteLuckyGiftDrawResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[173] + mi := &file_proto_activity_v1_activity_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14537,7 +15903,7 @@ func (x *ExecuteLuckyGiftDrawResponse) String() string { func (*ExecuteLuckyGiftDrawResponse) ProtoMessage() {} func (x *ExecuteLuckyGiftDrawResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[173] + mi := &file_proto_activity_v1_activity_proto_msgTypes[191] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14550,7 +15916,7 @@ func (x *ExecuteLuckyGiftDrawResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteLuckyGiftDrawResponse.ProtoReflect.Descriptor instead. func (*ExecuteLuckyGiftDrawResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{173} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{191} } func (x *ExecuteLuckyGiftDrawResponse) GetResult() *LuckyGiftDrawResult { @@ -14571,7 +15937,7 @@ type GetLuckyGiftConfigRequest struct { func (x *GetLuckyGiftConfigRequest) Reset() { *x = GetLuckyGiftConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[174] + mi := &file_proto_activity_v1_activity_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14583,7 +15949,7 @@ func (x *GetLuckyGiftConfigRequest) String() string { func (*GetLuckyGiftConfigRequest) ProtoMessage() {} func (x *GetLuckyGiftConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[174] + mi := &file_proto_activity_v1_activity_proto_msgTypes[192] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14596,7 +15962,7 @@ func (x *GetLuckyGiftConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLuckyGiftConfigRequest.ProtoReflect.Descriptor instead. func (*GetLuckyGiftConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{174} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{192} } func (x *GetLuckyGiftConfigRequest) GetMeta() *RequestMeta { @@ -14629,7 +15995,7 @@ type GetLuckyGiftConfigResponse struct { func (x *GetLuckyGiftConfigResponse) Reset() { *x = GetLuckyGiftConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[175] + mi := &file_proto_activity_v1_activity_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14641,7 +16007,7 @@ func (x *GetLuckyGiftConfigResponse) String() string { func (*GetLuckyGiftConfigResponse) ProtoMessage() {} func (x *GetLuckyGiftConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[175] + mi := &file_proto_activity_v1_activity_proto_msgTypes[193] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14654,7 +16020,7 @@ func (x *GetLuckyGiftConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLuckyGiftConfigResponse.ProtoReflect.Descriptor instead. func (*GetLuckyGiftConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{175} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{193} } func (x *GetLuckyGiftConfigResponse) GetConfig() *LuckyGiftRuleConfig { @@ -14675,7 +16041,7 @@ type UpsertLuckyGiftConfigRequest struct { func (x *UpsertLuckyGiftConfigRequest) Reset() { *x = UpsertLuckyGiftConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[176] + mi := &file_proto_activity_v1_activity_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14687,7 +16053,7 @@ func (x *UpsertLuckyGiftConfigRequest) String() string { func (*UpsertLuckyGiftConfigRequest) ProtoMessage() {} func (x *UpsertLuckyGiftConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[176] + mi := &file_proto_activity_v1_activity_proto_msgTypes[194] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14700,7 +16066,7 @@ func (x *UpsertLuckyGiftConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLuckyGiftConfigRequest.ProtoReflect.Descriptor instead. func (*UpsertLuckyGiftConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{176} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{194} } func (x *UpsertLuckyGiftConfigRequest) GetMeta() *RequestMeta { @@ -14733,7 +16099,7 @@ type UpsertLuckyGiftConfigResponse struct { func (x *UpsertLuckyGiftConfigResponse) Reset() { *x = UpsertLuckyGiftConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[177] + mi := &file_proto_activity_v1_activity_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14745,7 +16111,7 @@ func (x *UpsertLuckyGiftConfigResponse) String() string { func (*UpsertLuckyGiftConfigResponse) ProtoMessage() {} func (x *UpsertLuckyGiftConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[177] + mi := &file_proto_activity_v1_activity_proto_msgTypes[195] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14758,7 +16124,7 @@ func (x *UpsertLuckyGiftConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLuckyGiftConfigResponse.ProtoReflect.Descriptor instead. func (*UpsertLuckyGiftConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{177} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{195} } func (x *UpsertLuckyGiftConfigResponse) GetConfig() *LuckyGiftRuleConfig { @@ -14777,7 +16143,7 @@ type ListLuckyGiftConfigsRequest struct { func (x *ListLuckyGiftConfigsRequest) Reset() { *x = ListLuckyGiftConfigsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[178] + mi := &file_proto_activity_v1_activity_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14789,7 +16155,7 @@ func (x *ListLuckyGiftConfigsRequest) String() string { func (*ListLuckyGiftConfigsRequest) ProtoMessage() {} func (x *ListLuckyGiftConfigsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[178] + mi := &file_proto_activity_v1_activity_proto_msgTypes[196] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14802,7 +16168,7 @@ func (x *ListLuckyGiftConfigsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLuckyGiftConfigsRequest.ProtoReflect.Descriptor instead. func (*ListLuckyGiftConfigsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{178} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{196} } func (x *ListLuckyGiftConfigsRequest) GetMeta() *RequestMeta { @@ -14821,7 +16187,7 @@ type ListLuckyGiftConfigsResponse struct { func (x *ListLuckyGiftConfigsResponse) Reset() { *x = ListLuckyGiftConfigsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[179] + mi := &file_proto_activity_v1_activity_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14833,7 +16199,7 @@ func (x *ListLuckyGiftConfigsResponse) String() string { func (*ListLuckyGiftConfigsResponse) ProtoMessage() {} func (x *ListLuckyGiftConfigsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[179] + mi := &file_proto_activity_v1_activity_proto_msgTypes[197] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14846,7 +16212,7 @@ func (x *ListLuckyGiftConfigsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLuckyGiftConfigsResponse.ProtoReflect.Descriptor instead. func (*ListLuckyGiftConfigsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{179} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{197} } func (x *ListLuckyGiftConfigsResponse) GetConfigs() []*LuckyGiftRuleConfig { @@ -14872,7 +16238,7 @@ type ListLuckyGiftDrawsRequest struct { func (x *ListLuckyGiftDrawsRequest) Reset() { *x = ListLuckyGiftDrawsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[180] + mi := &file_proto_activity_v1_activity_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14884,7 +16250,7 @@ func (x *ListLuckyGiftDrawsRequest) String() string { func (*ListLuckyGiftDrawsRequest) ProtoMessage() {} func (x *ListLuckyGiftDrawsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[180] + mi := &file_proto_activity_v1_activity_proto_msgTypes[198] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14897,7 +16263,7 @@ func (x *ListLuckyGiftDrawsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLuckyGiftDrawsRequest.ProtoReflect.Descriptor instead. func (*ListLuckyGiftDrawsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{180} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{198} } func (x *ListLuckyGiftDrawsRequest) GetMeta() *RequestMeta { @@ -14966,7 +16332,7 @@ type ListLuckyGiftDrawsResponse struct { func (x *ListLuckyGiftDrawsResponse) Reset() { *x = ListLuckyGiftDrawsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[181] + mi := &file_proto_activity_v1_activity_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14978,7 +16344,7 @@ func (x *ListLuckyGiftDrawsResponse) String() string { func (*ListLuckyGiftDrawsResponse) ProtoMessage() {} func (x *ListLuckyGiftDrawsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[181] + mi := &file_proto_activity_v1_activity_proto_msgTypes[199] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14991,7 +16357,7 @@ func (x *ListLuckyGiftDrawsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLuckyGiftDrawsResponse.ProtoReflect.Descriptor instead. func (*ListLuckyGiftDrawsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{181} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{199} } func (x *ListLuckyGiftDrawsResponse) GetDraws() []*LuckyGiftDrawResult { @@ -15029,7 +16395,7 @@ type LuckyGiftDrawSummary struct { func (x *LuckyGiftDrawSummary) Reset() { *x = LuckyGiftDrawSummary{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[182] + mi := &file_proto_activity_v1_activity_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15041,7 +16407,7 @@ func (x *LuckyGiftDrawSummary) String() string { func (*LuckyGiftDrawSummary) ProtoMessage() {} func (x *LuckyGiftDrawSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[182] + mi := &file_proto_activity_v1_activity_proto_msgTypes[200] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15054,7 +16420,7 @@ func (x *LuckyGiftDrawSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftDrawSummary.ProtoReflect.Descriptor instead. func (*LuckyGiftDrawSummary) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{182} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{200} } func (x *LuckyGiftDrawSummary) GetPoolId() string { @@ -15162,7 +16528,7 @@ type GetLuckyGiftDrawSummaryRequest struct { func (x *GetLuckyGiftDrawSummaryRequest) Reset() { *x = GetLuckyGiftDrawSummaryRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[183] + mi := &file_proto_activity_v1_activity_proto_msgTypes[201] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15174,7 +16540,7 @@ func (x *GetLuckyGiftDrawSummaryRequest) String() string { func (*GetLuckyGiftDrawSummaryRequest) ProtoMessage() {} func (x *GetLuckyGiftDrawSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[183] + mi := &file_proto_activity_v1_activity_proto_msgTypes[201] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15187,7 +16553,7 @@ func (x *GetLuckyGiftDrawSummaryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLuckyGiftDrawSummaryRequest.ProtoReflect.Descriptor instead. func (*GetLuckyGiftDrawSummaryRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{183} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{201} } func (x *GetLuckyGiftDrawSummaryRequest) GetMeta() *RequestMeta { @@ -15241,7 +16607,7 @@ type GetLuckyGiftDrawSummaryResponse struct { func (x *GetLuckyGiftDrawSummaryResponse) Reset() { *x = GetLuckyGiftDrawSummaryResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[184] + mi := &file_proto_activity_v1_activity_proto_msgTypes[202] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15253,7 +16619,7 @@ func (x *GetLuckyGiftDrawSummaryResponse) String() string { func (*GetLuckyGiftDrawSummaryResponse) ProtoMessage() {} func (x *GetLuckyGiftDrawSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[184] + mi := &file_proto_activity_v1_activity_proto_msgTypes[202] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15266,7 +16632,7 @@ func (x *GetLuckyGiftDrawSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLuckyGiftDrawSummaryResponse.ProtoReflect.Descriptor instead. func (*GetLuckyGiftDrawSummaryResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{184} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{202} } func (x *GetLuckyGiftDrawSummaryResponse) GetSummary() *LuckyGiftDrawSummary { @@ -15287,7 +16653,7 @@ type WeeklyStarGift struct { func (x *WeeklyStarGift) Reset() { *x = WeeklyStarGift{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[185] + mi := &file_proto_activity_v1_activity_proto_msgTypes[203] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15299,7 +16665,7 @@ func (x *WeeklyStarGift) String() string { func (*WeeklyStarGift) ProtoMessage() {} func (x *WeeklyStarGift) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[185] + mi := &file_proto_activity_v1_activity_proto_msgTypes[203] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15312,7 +16678,7 @@ func (x *WeeklyStarGift) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarGift.ProtoReflect.Descriptor instead. func (*WeeklyStarGift) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{185} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{203} } func (x *WeeklyStarGift) GetGiftId() string { @@ -15340,7 +16706,7 @@ type WeeklyStarReward struct { func (x *WeeklyStarReward) Reset() { *x = WeeklyStarReward{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[186] + mi := &file_proto_activity_v1_activity_proto_msgTypes[204] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15352,7 +16718,7 @@ func (x *WeeklyStarReward) String() string { func (*WeeklyStarReward) ProtoMessage() {} func (x *WeeklyStarReward) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[186] + mi := &file_proto_activity_v1_activity_proto_msgTypes[204] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15365,7 +16731,7 @@ func (x *WeeklyStarReward) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarReward.ProtoReflect.Descriptor instead. func (*WeeklyStarReward) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{186} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{204} } func (x *WeeklyStarReward) GetRankNo() int32 { @@ -15406,7 +16772,7 @@ type WeeklyStarCycle struct { func (x *WeeklyStarCycle) Reset() { *x = WeeklyStarCycle{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[187] + mi := &file_proto_activity_v1_activity_proto_msgTypes[205] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15418,7 +16784,7 @@ func (x *WeeklyStarCycle) String() string { func (*WeeklyStarCycle) ProtoMessage() {} func (x *WeeklyStarCycle) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[187] + mi := &file_proto_activity_v1_activity_proto_msgTypes[205] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15431,7 +16797,7 @@ func (x *WeeklyStarCycle) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarCycle.ProtoReflect.Descriptor instead. func (*WeeklyStarCycle) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{187} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{205} } func (x *WeeklyStarCycle) GetCycleId() string { @@ -15553,7 +16919,7 @@ type WeeklyStarLeaderboardEntry struct { func (x *WeeklyStarLeaderboardEntry) Reset() { *x = WeeklyStarLeaderboardEntry{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[188] + mi := &file_proto_activity_v1_activity_proto_msgTypes[206] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15565,7 +16931,7 @@ func (x *WeeklyStarLeaderboardEntry) String() string { func (*WeeklyStarLeaderboardEntry) ProtoMessage() {} func (x *WeeklyStarLeaderboardEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[188] + mi := &file_proto_activity_v1_activity_proto_msgTypes[206] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15578,7 +16944,7 @@ func (x *WeeklyStarLeaderboardEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarLeaderboardEntry.ProtoReflect.Descriptor instead. func (*WeeklyStarLeaderboardEntry) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{188} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{206} } func (x *WeeklyStarLeaderboardEntry) GetRankNo() int32 { @@ -15638,7 +17004,7 @@ type WeeklyStarSettlement struct { func (x *WeeklyStarSettlement) Reset() { *x = WeeklyStarSettlement{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[189] + mi := &file_proto_activity_v1_activity_proto_msgTypes[207] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15650,7 +17016,7 @@ func (x *WeeklyStarSettlement) String() string { func (*WeeklyStarSettlement) ProtoMessage() {} func (x *WeeklyStarSettlement) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[189] + mi := &file_proto_activity_v1_activity_proto_msgTypes[207] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15663,7 +17029,7 @@ func (x *WeeklyStarSettlement) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarSettlement.ProtoReflect.Descriptor instead. func (*WeeklyStarSettlement) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{189} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{207} } func (x *WeeklyStarSettlement) GetSettlementId() string { @@ -15772,7 +17138,7 @@ type ListWeeklyStarCyclesRequest struct { func (x *ListWeeklyStarCyclesRequest) Reset() { *x = ListWeeklyStarCyclesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[190] + mi := &file_proto_activity_v1_activity_proto_msgTypes[208] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15784,7 +17150,7 @@ func (x *ListWeeklyStarCyclesRequest) String() string { func (*ListWeeklyStarCyclesRequest) ProtoMessage() {} func (x *ListWeeklyStarCyclesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[190] + mi := &file_proto_activity_v1_activity_proto_msgTypes[208] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15797,7 +17163,7 @@ func (x *ListWeeklyStarCyclesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarCyclesRequest.ProtoReflect.Descriptor instead. func (*ListWeeklyStarCyclesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{190} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{208} } func (x *ListWeeklyStarCyclesRequest) GetMeta() *RequestMeta { @@ -15859,7 +17225,7 @@ type ListWeeklyStarCyclesResponse struct { func (x *ListWeeklyStarCyclesResponse) Reset() { *x = ListWeeklyStarCyclesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[191] + mi := &file_proto_activity_v1_activity_proto_msgTypes[209] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15871,7 +17237,7 @@ func (x *ListWeeklyStarCyclesResponse) String() string { func (*ListWeeklyStarCyclesResponse) ProtoMessage() {} func (x *ListWeeklyStarCyclesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[191] + mi := &file_proto_activity_v1_activity_proto_msgTypes[209] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15884,7 +17250,7 @@ func (x *ListWeeklyStarCyclesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarCyclesResponse.ProtoReflect.Descriptor instead. func (*ListWeeklyStarCyclesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{191} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{209} } func (x *ListWeeklyStarCyclesResponse) GetCycles() []*WeeklyStarCycle { @@ -15911,7 +17277,7 @@ type GetWeeklyStarCycleRequest struct { func (x *GetWeeklyStarCycleRequest) Reset() { *x = GetWeeklyStarCycleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[192] + mi := &file_proto_activity_v1_activity_proto_msgTypes[210] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15923,7 +17289,7 @@ func (x *GetWeeklyStarCycleRequest) String() string { func (*GetWeeklyStarCycleRequest) ProtoMessage() {} func (x *GetWeeklyStarCycleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[192] + mi := &file_proto_activity_v1_activity_proto_msgTypes[210] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15936,7 +17302,7 @@ func (x *GetWeeklyStarCycleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWeeklyStarCycleRequest.ProtoReflect.Descriptor instead. func (*GetWeeklyStarCycleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{192} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{210} } func (x *GetWeeklyStarCycleRequest) GetMeta() *RequestMeta { @@ -15962,7 +17328,7 @@ type GetWeeklyStarCycleResponse struct { func (x *GetWeeklyStarCycleResponse) Reset() { *x = GetWeeklyStarCycleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[193] + mi := &file_proto_activity_v1_activity_proto_msgTypes[211] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15974,7 +17340,7 @@ func (x *GetWeeklyStarCycleResponse) String() string { func (*GetWeeklyStarCycleResponse) ProtoMessage() {} func (x *GetWeeklyStarCycleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[193] + mi := &file_proto_activity_v1_activity_proto_msgTypes[211] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15987,7 +17353,7 @@ func (x *GetWeeklyStarCycleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWeeklyStarCycleResponse.ProtoReflect.Descriptor instead. func (*GetWeeklyStarCycleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{193} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{211} } func (x *GetWeeklyStarCycleResponse) GetCycle() *WeeklyStarCycle { @@ -16008,7 +17374,7 @@ type UpsertWeeklyStarCycleRequest struct { func (x *UpsertWeeklyStarCycleRequest) Reset() { *x = UpsertWeeklyStarCycleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[194] + mi := &file_proto_activity_v1_activity_proto_msgTypes[212] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16020,7 +17386,7 @@ func (x *UpsertWeeklyStarCycleRequest) String() string { func (*UpsertWeeklyStarCycleRequest) ProtoMessage() {} func (x *UpsertWeeklyStarCycleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[194] + mi := &file_proto_activity_v1_activity_proto_msgTypes[212] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16033,7 +17399,7 @@ func (x *UpsertWeeklyStarCycleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertWeeklyStarCycleRequest.ProtoReflect.Descriptor instead. func (*UpsertWeeklyStarCycleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{194} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{212} } func (x *UpsertWeeklyStarCycleRequest) GetMeta() *RequestMeta { @@ -16067,7 +17433,7 @@ type UpsertWeeklyStarCycleResponse struct { func (x *UpsertWeeklyStarCycleResponse) Reset() { *x = UpsertWeeklyStarCycleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[195] + mi := &file_proto_activity_v1_activity_proto_msgTypes[213] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16079,7 +17445,7 @@ func (x *UpsertWeeklyStarCycleResponse) String() string { func (*UpsertWeeklyStarCycleResponse) ProtoMessage() {} func (x *UpsertWeeklyStarCycleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[195] + mi := &file_proto_activity_v1_activity_proto_msgTypes[213] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16092,7 +17458,7 @@ func (x *UpsertWeeklyStarCycleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertWeeklyStarCycleResponse.ProtoReflect.Descriptor instead. func (*UpsertWeeklyStarCycleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{195} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{213} } func (x *UpsertWeeklyStarCycleResponse) GetCycle() *WeeklyStarCycle { @@ -16121,7 +17487,7 @@ type SetWeeklyStarCycleStatusRequest struct { func (x *SetWeeklyStarCycleStatusRequest) Reset() { *x = SetWeeklyStarCycleStatusRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[196] + mi := &file_proto_activity_v1_activity_proto_msgTypes[214] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16133,7 +17499,7 @@ func (x *SetWeeklyStarCycleStatusRequest) String() string { func (*SetWeeklyStarCycleStatusRequest) ProtoMessage() {} func (x *SetWeeklyStarCycleStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[196] + mi := &file_proto_activity_v1_activity_proto_msgTypes[214] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16146,7 +17512,7 @@ func (x *SetWeeklyStarCycleStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWeeklyStarCycleStatusRequest.ProtoReflect.Descriptor instead. func (*SetWeeklyStarCycleStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{196} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{214} } func (x *SetWeeklyStarCycleStatusRequest) GetMeta() *RequestMeta { @@ -16186,7 +17552,7 @@ type SetWeeklyStarCycleStatusResponse struct { func (x *SetWeeklyStarCycleStatusResponse) Reset() { *x = SetWeeklyStarCycleStatusResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[197] + mi := &file_proto_activity_v1_activity_proto_msgTypes[215] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16198,7 +17564,7 @@ func (x *SetWeeklyStarCycleStatusResponse) String() string { func (*SetWeeklyStarCycleStatusResponse) ProtoMessage() {} func (x *SetWeeklyStarCycleStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[197] + mi := &file_proto_activity_v1_activity_proto_msgTypes[215] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16211,7 +17577,7 @@ func (x *SetWeeklyStarCycleStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWeeklyStarCycleStatusResponse.ProtoReflect.Descriptor instead. func (*SetWeeklyStarCycleStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{197} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{215} } func (x *SetWeeklyStarCycleStatusResponse) GetCycle() *WeeklyStarCycle { @@ -16234,7 +17600,7 @@ type ListWeeklyStarLeaderboardRequest struct { func (x *ListWeeklyStarLeaderboardRequest) Reset() { *x = ListWeeklyStarLeaderboardRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[198] + mi := &file_proto_activity_v1_activity_proto_msgTypes[216] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16246,7 +17612,7 @@ func (x *ListWeeklyStarLeaderboardRequest) String() string { func (*ListWeeklyStarLeaderboardRequest) ProtoMessage() {} func (x *ListWeeklyStarLeaderboardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[198] + mi := &file_proto_activity_v1_activity_proto_msgTypes[216] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16259,7 +17625,7 @@ func (x *ListWeeklyStarLeaderboardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarLeaderboardRequest.ProtoReflect.Descriptor instead. func (*ListWeeklyStarLeaderboardRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{198} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{216} } func (x *ListWeeklyStarLeaderboardRequest) GetMeta() *RequestMeta { @@ -16309,7 +17675,7 @@ type ListWeeklyStarLeaderboardResponse struct { func (x *ListWeeklyStarLeaderboardResponse) Reset() { *x = ListWeeklyStarLeaderboardResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[199] + mi := &file_proto_activity_v1_activity_proto_msgTypes[217] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16321,7 +17687,7 @@ func (x *ListWeeklyStarLeaderboardResponse) String() string { func (*ListWeeklyStarLeaderboardResponse) ProtoMessage() {} func (x *ListWeeklyStarLeaderboardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[199] + mi := &file_proto_activity_v1_activity_proto_msgTypes[217] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16334,7 +17700,7 @@ func (x *ListWeeklyStarLeaderboardResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListWeeklyStarLeaderboardResponse.ProtoReflect.Descriptor instead. func (*ListWeeklyStarLeaderboardResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{199} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{217} } func (x *ListWeeklyStarLeaderboardResponse) GetCycle() *WeeklyStarCycle { @@ -16375,7 +17741,7 @@ type ListWeeklyStarSettlementsRequest struct { func (x *ListWeeklyStarSettlementsRequest) Reset() { *x = ListWeeklyStarSettlementsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[200] + mi := &file_proto_activity_v1_activity_proto_msgTypes[218] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16387,7 +17753,7 @@ func (x *ListWeeklyStarSettlementsRequest) String() string { func (*ListWeeklyStarSettlementsRequest) ProtoMessage() {} func (x *ListWeeklyStarSettlementsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[200] + mi := &file_proto_activity_v1_activity_proto_msgTypes[218] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16400,7 +17766,7 @@ func (x *ListWeeklyStarSettlementsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarSettlementsRequest.ProtoReflect.Descriptor instead. func (*ListWeeklyStarSettlementsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{200} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{218} } func (x *ListWeeklyStarSettlementsRequest) GetMeta() *RequestMeta { @@ -16426,7 +17792,7 @@ type ListWeeklyStarSettlementsResponse struct { func (x *ListWeeklyStarSettlementsResponse) Reset() { *x = ListWeeklyStarSettlementsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[201] + mi := &file_proto_activity_v1_activity_proto_msgTypes[219] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16438,7 +17804,7 @@ func (x *ListWeeklyStarSettlementsResponse) String() string { func (*ListWeeklyStarSettlementsResponse) ProtoMessage() {} func (x *ListWeeklyStarSettlementsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[201] + mi := &file_proto_activity_v1_activity_proto_msgTypes[219] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16451,7 +17817,7 @@ func (x *ListWeeklyStarSettlementsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListWeeklyStarSettlementsResponse.ProtoReflect.Descriptor instead. func (*ListWeeklyStarSettlementsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{201} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{219} } func (x *ListWeeklyStarSettlementsResponse) GetSettlements() []*WeeklyStarSettlement { @@ -16472,7 +17838,7 @@ type GetWeeklyStarCurrentRequest struct { func (x *GetWeeklyStarCurrentRequest) Reset() { *x = GetWeeklyStarCurrentRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[202] + mi := &file_proto_activity_v1_activity_proto_msgTypes[220] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16484,7 +17850,7 @@ func (x *GetWeeklyStarCurrentRequest) String() string { func (*GetWeeklyStarCurrentRequest) ProtoMessage() {} func (x *GetWeeklyStarCurrentRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[202] + mi := &file_proto_activity_v1_activity_proto_msgTypes[220] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16497,7 +17863,7 @@ func (x *GetWeeklyStarCurrentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWeeklyStarCurrentRequest.ProtoReflect.Descriptor instead. func (*GetWeeklyStarCurrentRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{202} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{220} } func (x *GetWeeklyStarCurrentRequest) GetMeta() *RequestMeta { @@ -16533,7 +17899,7 @@ type GetWeeklyStarCurrentResponse struct { func (x *GetWeeklyStarCurrentResponse) Reset() { *x = GetWeeklyStarCurrentResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[203] + mi := &file_proto_activity_v1_activity_proto_msgTypes[221] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16545,7 +17911,7 @@ func (x *GetWeeklyStarCurrentResponse) String() string { func (*GetWeeklyStarCurrentResponse) ProtoMessage() {} func (x *GetWeeklyStarCurrentResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[203] + mi := &file_proto_activity_v1_activity_proto_msgTypes[221] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16558,7 +17924,7 @@ func (x *GetWeeklyStarCurrentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWeeklyStarCurrentResponse.ProtoReflect.Descriptor instead. func (*GetWeeklyStarCurrentResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{203} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{221} } func (x *GetWeeklyStarCurrentResponse) GetCycle() *WeeklyStarCycle { @@ -16600,7 +17966,7 @@ type ListWeeklyStarHistoryRequest struct { func (x *ListWeeklyStarHistoryRequest) Reset() { *x = ListWeeklyStarHistoryRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[204] + mi := &file_proto_activity_v1_activity_proto_msgTypes[222] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16612,7 +17978,7 @@ func (x *ListWeeklyStarHistoryRequest) String() string { func (*ListWeeklyStarHistoryRequest) ProtoMessage() {} func (x *ListWeeklyStarHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[204] + mi := &file_proto_activity_v1_activity_proto_msgTypes[222] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16625,7 +17991,7 @@ func (x *ListWeeklyStarHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarHistoryRequest.ProtoReflect.Descriptor instead. func (*ListWeeklyStarHistoryRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{204} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{222} } func (x *ListWeeklyStarHistoryRequest) GetMeta() *RequestMeta { @@ -16659,7 +18025,7 @@ type WeeklyStarHistoryCycle struct { func (x *WeeklyStarHistoryCycle) Reset() { *x = WeeklyStarHistoryCycle{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[205] + mi := &file_proto_activity_v1_activity_proto_msgTypes[223] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16671,7 +18037,7 @@ func (x *WeeklyStarHistoryCycle) String() string { func (*WeeklyStarHistoryCycle) ProtoMessage() {} func (x *WeeklyStarHistoryCycle) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[205] + mi := &file_proto_activity_v1_activity_proto_msgTypes[223] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16684,7 +18050,7 @@ func (x *WeeklyStarHistoryCycle) ProtoReflect() protoreflect.Message { // Deprecated: Use WeeklyStarHistoryCycle.ProtoReflect.Descriptor instead. func (*WeeklyStarHistoryCycle) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{205} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{223} } func (x *WeeklyStarHistoryCycle) GetCycle() *WeeklyStarCycle { @@ -16711,7 +18077,7 @@ type ListWeeklyStarHistoryResponse struct { func (x *ListWeeklyStarHistoryResponse) Reset() { *x = ListWeeklyStarHistoryResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[206] + mi := &file_proto_activity_v1_activity_proto_msgTypes[224] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16723,7 +18089,7 @@ func (x *ListWeeklyStarHistoryResponse) String() string { func (*ListWeeklyStarHistoryResponse) ProtoMessage() {} func (x *ListWeeklyStarHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[206] + mi := &file_proto_activity_v1_activity_proto_msgTypes[224] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16736,7 +18102,7 @@ func (x *ListWeeklyStarHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWeeklyStarHistoryResponse.ProtoReflect.Descriptor instead. func (*ListWeeklyStarHistoryResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{206} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{224} } func (x *ListWeeklyStarHistoryResponse) GetCycles() []*WeeklyStarHistoryCycle { @@ -17402,6 +18768,122 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\tpage_size\x18\x06 \x01(\x05R\bpageSize\"\x8c\x01\n" + "*ListCumulativeRechargeRewardGrantsResponse\x12H\n" + "\x06grants\x18\x01 \x03(\v20.hyapp.activity.v1.CumulativeRechargeRewardGrantR\x06grants\x12\x14\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\"\xb0\x03\n" + + "\x18InviteActivityRewardTier\x12\x17\n" + + "\atier_id\x18\x01 \x01(\x03R\x06tierId\x12\x1f\n" + + "\vreward_type\x18\x02 \x01(\tR\n" + + "rewardType\x12\x1b\n" + + "\ttier_code\x18\x03 \x01(\tR\btierCode\x12\x1b\n" + + "\ttier_name\x18\x04 \x01(\tR\btierName\x122\n" + + "\x15threshold_coin_amount\x18\x05 \x01(\x03R\x13thresholdCoinAmount\x12?\n" + + "\x1cthreshold_valid_invite_count\x18\x06 \x01(\x03R\x19thresholdValidInviteCount\x12,\n" + + "\x12reward_coin_amount\x18\a \x01(\x03R\x10rewardCoinAmount\x12\x16\n" + + "\x06status\x18\b \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "sort_order\x18\t \x01(\x05R\tsortOrder\x12\"\n" + + "\rcreated_at_ms\x18\n" + + " \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\v \x01(\x03R\vupdatedAtMs\"\x8b\x02\n" + + "\x1aInviteActivityRewardConfig\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\x12A\n" + + "\x05tiers\x18\x03 \x03(\v2+.hyapp.activity.v1.InviteActivityRewardTierR\x05tiers\x12-\n" + + "\x13updated_by_admin_id\x18\x04 \x01(\x03R\x10updatedByAdminId\x12\"\n" + + "\rcreated_at_ms\x18\x05 \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x06 \x01(\x03R\vupdatedAtMs\"\xbb\x02\n" + + "\x1cInviteActivityRewardProgress\x12\x19\n" + + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x1b\n" + + "\tcycle_key\x18\x02 \x01(\tR\bcycleKey\x12\x17\n" + + "\auser_id\x18\x03 \x01(\x03R\x06userId\x12;\n" + + "\x1atotal_recharge_coin_amount\x18\x04 \x01(\x03R\x17totalRechargeCoinAmount\x12,\n" + + "\x12valid_invite_count\x18\x05 \x01(\x03R\x10validInviteCount\x12;\n" + + "\x1aclaimed_reward_coin_amount\x18\x06 \x01(\x03R\x17claimedRewardCoinAmount\x12\"\n" + + "\rupdated_at_ms\x18\a \x01(\x03R\vupdatedAtMs\"\xed\x05\n" + + "\x19InviteActivityRewardClaim\x12\x19\n" + + "\bclaim_id\x18\x01 \x01(\tR\aclaimId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x1b\n" + + "\tcycle_key\x18\x03 \x01(\tR\bcycleKey\x12\x17\n" + + "\auser_id\x18\x04 \x01(\x03R\x06userId\x12\x1f\n" + + "\vreward_type\x18\x05 \x01(\tR\n" + + "rewardType\x12\x1d\n" + + "\n" + + "command_id\x18\x06 \x01(\tR\tcommandId\x12\x17\n" + + "\atier_id\x18\a \x01(\x03R\x06tierId\x12\x1b\n" + + "\ttier_code\x18\b \x01(\tR\btierCode\x12\x1b\n" + + "\ttier_name\x18\t \x01(\tR\btierName\x122\n" + + "\x15threshold_coin_amount\x18\n" + + " \x01(\x03R\x13thresholdCoinAmount\x12?\n" + + "\x1cthreshold_valid_invite_count\x18\v \x01(\x03R\x19thresholdValidInviteCount\x12#\n" + + "\rreached_value\x18\f \x01(\x03R\freachedValue\x12,\n" + + "\x12reward_coin_amount\x18\r \x01(\x03R\x10rewardCoinAmount\x12\x16\n" + + "\x06status\x18\x0e \x01(\tR\x06status\x12*\n" + + "\x11wallet_command_id\x18\x0f \x01(\tR\x0fwalletCommandId\x122\n" + + "\x15wallet_transaction_id\x18\x10 \x01(\tR\x13walletTransactionId\x12%\n" + + "\x0efailure_reason\x18\x11 \x01(\tR\rfailureReason\x12\"\n" + + "\rgranted_at_ms\x18\x12 \x01(\x03R\vgrantedAtMs\x12\"\n" + + "\rcreated_at_ms\x18\x13 \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x14 \x01(\x03R\vupdatedAtMs\"\x85\x03\n" + + "\x1aInviteActivityRewardStatus\x12E\n" + + "\x06config\x18\x01 \x01(\v2-.hyapp.activity.v1.InviteActivityRewardConfigR\x06config\x12K\n" + + "\bprogress\x18\x02 \x01(\v2/.hyapp.activity.v1.InviteActivityRewardProgressR\bprogress\x12D\n" + + "\x06claims\x18\x03 \x03(\v2,.hyapp.activity.v1.InviteActivityRewardClaimR\x06claims\x12\x1b\n" + + "\tcycle_key\x18\x04 \x01(\tR\bcycleKey\x12&\n" + + "\x0fperiod_start_ms\x18\x05 \x01(\x03R\rperiodStartMs\x12\"\n" + + "\rperiod_end_ms\x18\x06 \x01(\x03R\vperiodEndMs\x12$\n" + + "\x0eserver_time_ms\x18\a \x01(\x03R\fserverTimeMs\"s\n" + + "$GetInviteActivityRewardStatusRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\"n\n" + + "%GetInviteActivityRewardStatusResponse\x12E\n" + + "\x06status\x18\x01 \x01(\v2-.hyapp.activity.v1.InviteActivityRewardStatusR\x06status\"\xc8\x01\n" + + " ClaimInviteActivityRewardRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1f\n" + + "\vreward_type\x18\x03 \x01(\tR\n" + + "rewardType\x12\x17\n" + + "\atier_id\x18\x04 \x01(\x03R\x06tierId\x12\x1d\n" + + "\n" + + "command_id\x18\x05 \x01(\tR\tcommandId\"\xae\x01\n" + + "!ClaimInviteActivityRewardResponse\x12B\n" + + "\x05claim\x18\x01 \x01(\v2,.hyapp.activity.v1.InviteActivityRewardClaimR\x05claim\x12E\n" + + "\x06status\x18\x02 \x01(\v2-.hyapp.activity.v1.InviteActivityRewardStatusR\x06status\"\xbd\x01\n" + + "\x1eInviteActivityLeaderboardEntry\x12\x17\n" + + "\arank_no\x18\x01 \x01(\x05R\x06rankNo\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12,\n" + + "\x12valid_invite_count\x18\x03 \x01(\x03R\x10validInviteCount\x12;\n" + + "\x1atotal_recharge_coin_amount\x18\x04 \x01(\x03R\x17totalRechargeCoinAmount\"\xa8\x01\n" + + "$ListInviteActivityLeaderboardRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x1b\n" + + "\tcycle_key\x18\x02 \x01(\tR\bcycleKey\x12\x12\n" + + "\x04page\x18\x03 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\"\xcd\x01\n" + + "%ListInviteActivityLeaderboardResponse\x12K\n" + + "\aentries\x18\x01 \x03(\v21.hyapp.activity.v1.InviteActivityLeaderboardEntryR\aentries\x12\x14\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\x12\x1b\n" + + "\tcycle_key\x18\x03 \x01(\tR\bcycleKey\x12$\n" + + "\x0eserver_time_ms\x18\x04 \x01(\x03R\fserverTimeMs\"Z\n" + + "$GetInviteActivityRewardConfigRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\"n\n" + + "%GetInviteActivityRewardConfigResponse\x12E\n" + + "\x06config\x18\x01 \x01(\v2-.hyapp.activity.v1.InviteActivityRewardConfigR\x06config\"\xe6\x01\n" + + "'UpdateInviteActivityRewardConfigRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\x12A\n" + + "\x05tiers\x18\x03 \x03(\v2+.hyapp.activity.v1.InviteActivityRewardTierR\x05tiers\x12*\n" + + "\x11operator_admin_id\x18\x04 \x01(\x03R\x0foperatorAdminId\"q\n" + + "(UpdateInviteActivityRewardConfigResponse\x12E\n" + + "\x06config\x18\x01 \x01(\v2-.hyapp.activity.v1.InviteActivityRewardConfigR\x06config\"\xfb\x01\n" + + "%ListInviteActivityRewardClaimsRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x1f\n" + + "\vreward_type\x18\x03 \x01(\tR\n" + + "rewardType\x12\x17\n" + + "\auser_id\x18\x04 \x01(\x03R\x06userId\x12\x1b\n" + + "\tcycle_key\x18\x05 \x01(\tR\bcycleKey\x12\x12\n" + + "\x04page\x18\x06 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\a \x01(\x05R\bpageSize\"\x84\x01\n" + + "&ListInviteActivityRewardClaimsResponse\x12D\n" + + "\x06claims\x18\x01 \x03(\v2,.hyapp.activity.v1.InviteActivityRewardClaimR\x06claims\x12\x14\n" + "\x05total\x18\x02 \x01(\x03R\x05total\"\xca\x02\n" + "\x16RoomTurnoverRewardTier\x12\x17\n" + "\atier_id\x18\x01 \x01(\x03R\x06tierId\x12\x1b\n" + @@ -18334,11 +19816,19 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\x1dListFirstRechargeRewardClaims\x127.hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest\x1a8.hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse2\xdd\x02\n" + "\x1fCumulativeRechargeRewardService\x12\x9e\x01\n" + "!GetCumulativeRechargeRewardStatus\x12;.hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest\x1a<.hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse\x12\x98\x01\n" + - "\x1fConsumeCumulativeRechargeReward\x129.hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest\x1a:.hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse2\x95\x04\n" + + "\x1fConsumeCumulativeRechargeReward\x129.hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest\x1a:.hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse2\xd0\x03\n" + + "\x1bInviteActivityRewardService\x12\x92\x01\n" + + "\x1dGetInviteActivityRewardStatus\x127.hyapp.activity.v1.GetInviteActivityRewardStatusRequest\x1a8.hyapp.activity.v1.GetInviteActivityRewardStatusResponse\x12\x86\x01\n" + + "\x19ClaimInviteActivityReward\x123.hyapp.activity.v1.ClaimInviteActivityRewardRequest\x1a4.hyapp.activity.v1.ClaimInviteActivityRewardResponse\x12\x92\x01\n" + + "\x1dListInviteActivityLeaderboard\x127.hyapp.activity.v1.ListInviteActivityLeaderboardRequest\x1a8.hyapp.activity.v1.ListInviteActivityLeaderboardResponse2\x95\x04\n" + "$AdminCumulativeRechargeRewardService\x12\x9e\x01\n" + "!GetCumulativeRechargeRewardConfig\x12;.hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest\x1a<.hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse\x12\xa7\x01\n" + "$UpdateCumulativeRechargeRewardConfig\x12>.hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest\x1a?.hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse\x12\xa1\x01\n" + - "\"ListCumulativeRechargeRewardGrants\x12<.hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest\x1a=.hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse2\x89\x05\n" + + "\"ListCumulativeRechargeRewardGrants\x12<.hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest\x1a=.hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse2\xed\x03\n" + + " AdminInviteActivityRewardService\x12\x92\x01\n" + + "\x1dGetInviteActivityRewardConfig\x127.hyapp.activity.v1.GetInviteActivityRewardConfigRequest\x1a8.hyapp.activity.v1.GetInviteActivityRewardConfigResponse\x12\x9b\x01\n" + + " UpdateInviteActivityRewardConfig\x12:.hyapp.activity.v1.UpdateInviteActivityRewardConfigRequest\x1a;.hyapp.activity.v1.UpdateInviteActivityRewardConfigResponse\x12\x95\x01\n" + + "\x1eListInviteActivityRewardClaims\x128.hyapp.activity.v1.ListInviteActivityRewardClaimsRequest\x1a9.hyapp.activity.v1.ListInviteActivityRewardClaimsResponse2\x89\x05\n" + "\x1eAdminRoomTurnoverRewardService\x12\x8c\x01\n" + "\x1bGetRoomTurnoverRewardConfig\x125.hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest\x1a6.hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse\x12\x95\x01\n" + "\x1eUpdateRoomTurnoverRewardConfig\x128.hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest\x1a9.hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse\x12\x9e\x01\n" + @@ -18386,7 +19876,7 @@ func file_proto_activity_v1_activity_proto_rawDescGZIP() []byte { return file_proto_activity_v1_activity_proto_rawDescData } -var file_proto_activity_v1_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 207) +var file_proto_activity_v1_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 225) var file_proto_activity_v1_activity_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.activity.v1.RequestMeta (*PingActivityRequest)(nil), // 1: hyapp.activity.v1.PingActivityRequest @@ -18479,123 +19969,141 @@ var file_proto_activity_v1_activity_proto_goTypes = []any{ (*UpdateCumulativeRechargeRewardConfigResponse)(nil), // 88: hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse (*ListCumulativeRechargeRewardGrantsRequest)(nil), // 89: hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest (*ListCumulativeRechargeRewardGrantsResponse)(nil), // 90: hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse - (*RoomTurnoverRewardTier)(nil), // 91: hyapp.activity.v1.RoomTurnoverRewardTier - (*RoomTurnoverRewardConfig)(nil), // 92: hyapp.activity.v1.RoomTurnoverRewardConfig - (*RoomTurnoverRewardSettlement)(nil), // 93: hyapp.activity.v1.RoomTurnoverRewardSettlement - (*RoomTurnoverRewardStatus)(nil), // 94: hyapp.activity.v1.RoomTurnoverRewardStatus - (*GetRoomTurnoverRewardStatusRequest)(nil), // 95: hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest - (*GetRoomTurnoverRewardStatusResponse)(nil), // 96: hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse - (*GetRoomTurnoverRewardConfigRequest)(nil), // 97: hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest - (*GetRoomTurnoverRewardConfigResponse)(nil), // 98: hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse - (*UpdateRoomTurnoverRewardConfigRequest)(nil), // 99: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest - (*UpdateRoomTurnoverRewardConfigResponse)(nil), // 100: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse - (*ListRoomTurnoverRewardSettlementsRequest)(nil), // 101: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest - (*ListRoomTurnoverRewardSettlementsResponse)(nil), // 102: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse - (*RetryRoomTurnoverRewardSettlementRequest)(nil), // 103: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest - (*RetryRoomTurnoverRewardSettlementResponse)(nil), // 104: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse - (*SevenDayCheckInReward)(nil), // 105: hyapp.activity.v1.SevenDayCheckInReward - (*SevenDayCheckInConfig)(nil), // 106: hyapp.activity.v1.SevenDayCheckInConfig - (*SevenDayCheckInRewardStatus)(nil), // 107: hyapp.activity.v1.SevenDayCheckInRewardStatus - (*GetSevenDayCheckInStatusRequest)(nil), // 108: hyapp.activity.v1.GetSevenDayCheckInStatusRequest - (*GetSevenDayCheckInStatusResponse)(nil), // 109: hyapp.activity.v1.GetSevenDayCheckInStatusResponse - (*SignSevenDayCheckInRequest)(nil), // 110: hyapp.activity.v1.SignSevenDayCheckInRequest - (*SignSevenDayCheckInResponse)(nil), // 111: hyapp.activity.v1.SignSevenDayCheckInResponse - (*GetSevenDayCheckInConfigRequest)(nil), // 112: hyapp.activity.v1.GetSevenDayCheckInConfigRequest - (*GetSevenDayCheckInConfigResponse)(nil), // 113: hyapp.activity.v1.GetSevenDayCheckInConfigResponse - (*UpdateSevenDayCheckInConfigRequest)(nil), // 114: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest - (*UpdateSevenDayCheckInConfigResponse)(nil), // 115: hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse - (*SevenDayCheckInClaim)(nil), // 116: hyapp.activity.v1.SevenDayCheckInClaim - (*ListSevenDayCheckInClaimsRequest)(nil), // 117: hyapp.activity.v1.ListSevenDayCheckInClaimsRequest - (*ListSevenDayCheckInClaimsResponse)(nil), // 118: hyapp.activity.v1.ListSevenDayCheckInClaimsResponse - (*LevelTrack)(nil), // 119: hyapp.activity.v1.LevelTrack - (*LevelRule)(nil), // 120: hyapp.activity.v1.LevelRule - (*LevelTier)(nil), // 121: hyapp.activity.v1.LevelTier - (*LevelTrackOverview)(nil), // 122: hyapp.activity.v1.LevelTrackOverview - (*GetMyLevelOverviewRequest)(nil), // 123: hyapp.activity.v1.GetMyLevelOverviewRequest - (*GetMyLevelOverviewResponse)(nil), // 124: hyapp.activity.v1.GetMyLevelOverviewResponse - (*GetLevelTrackRequest)(nil), // 125: hyapp.activity.v1.GetLevelTrackRequest - (*GetLevelTrackResponse)(nil), // 126: hyapp.activity.v1.GetLevelTrackResponse - (*LevelDisplayTrackProfile)(nil), // 127: hyapp.activity.v1.LevelDisplayTrackProfile - (*UserLevelDisplayProfile)(nil), // 128: hyapp.activity.v1.UserLevelDisplayProfile - (*BatchGetUserLevelDisplayProfilesRequest)(nil), // 129: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest - (*BatchGetUserLevelDisplayProfilesResponse)(nil), // 130: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse - (*LevelRewardJob)(nil), // 131: hyapp.activity.v1.LevelRewardJob - (*ListLevelRewardsRequest)(nil), // 132: hyapp.activity.v1.ListLevelRewardsRequest - (*ListLevelRewardsResponse)(nil), // 133: hyapp.activity.v1.ListLevelRewardsResponse - (*ConsumeLevelEventRequest)(nil), // 134: hyapp.activity.v1.ConsumeLevelEventRequest - (*ConsumeLevelEventResponse)(nil), // 135: hyapp.activity.v1.ConsumeLevelEventResponse - (*SetUserLevelRequest)(nil), // 136: hyapp.activity.v1.SetUserLevelRequest - (*SetUserLevelResponse)(nil), // 137: hyapp.activity.v1.SetUserLevelResponse - (*RegistrationLevelBadgeGrant)(nil), // 138: hyapp.activity.v1.RegistrationLevelBadgeGrant - (*IssueRegistrationLevelBadgesRequest)(nil), // 139: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest - (*IssueRegistrationLevelBadgesResponse)(nil), // 140: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse - (*UpsertLevelTrackRequest)(nil), // 141: hyapp.activity.v1.UpsertLevelTrackRequest - (*UpsertLevelTrackResponse)(nil), // 142: hyapp.activity.v1.UpsertLevelTrackResponse - (*UpsertLevelRuleRequest)(nil), // 143: hyapp.activity.v1.UpsertLevelRuleRequest - (*UpsertLevelRuleResponse)(nil), // 144: hyapp.activity.v1.UpsertLevelRuleResponse - (*UpsertLevelTierRequest)(nil), // 145: hyapp.activity.v1.UpsertLevelTierRequest - (*UpsertLevelTierResponse)(nil), // 146: hyapp.activity.v1.UpsertLevelTierResponse - (*ListLevelConfigRequest)(nil), // 147: hyapp.activity.v1.ListLevelConfigRequest - (*ListLevelConfigResponse)(nil), // 148: hyapp.activity.v1.ListLevelConfigResponse - (*AchievementCondition)(nil), // 149: hyapp.activity.v1.AchievementCondition - (*AchievementDefinition)(nil), // 150: hyapp.activity.v1.AchievementDefinition - (*UserAchievement)(nil), // 151: hyapp.activity.v1.UserAchievement - (*ListAchievementsRequest)(nil), // 152: hyapp.activity.v1.ListAchievementsRequest - (*ListAchievementsResponse)(nil), // 153: hyapp.activity.v1.ListAchievementsResponse - (*ConsumeAchievementEventRequest)(nil), // 154: hyapp.activity.v1.ConsumeAchievementEventRequest - (*ConsumeAchievementEventResponse)(nil), // 155: hyapp.activity.v1.ConsumeAchievementEventResponse - (*BadgeDisplayItem)(nil), // 156: hyapp.activity.v1.BadgeDisplayItem - (*ListMyBadgesRequest)(nil), // 157: hyapp.activity.v1.ListMyBadgesRequest - (*ListMyBadgesResponse)(nil), // 158: hyapp.activity.v1.ListMyBadgesResponse - (*SetBadgeDisplayRequest)(nil), // 159: hyapp.activity.v1.SetBadgeDisplayRequest - (*SetBadgeDisplayResponse)(nil), // 160: hyapp.activity.v1.SetBadgeDisplayResponse - (*UpsertAchievementDefinitionRequest)(nil), // 161: hyapp.activity.v1.UpsertAchievementDefinitionRequest - (*UpsertAchievementDefinitionResponse)(nil), // 162: hyapp.activity.v1.UpsertAchievementDefinitionResponse - (*LuckyGiftMeta)(nil), // 163: hyapp.activity.v1.LuckyGiftMeta - (*LuckyGiftTier)(nil), // 164: hyapp.activity.v1.LuckyGiftTier - (*LuckyGiftConfig)(nil), // 165: hyapp.activity.v1.LuckyGiftConfig - (*LuckyGiftRuleTier)(nil), // 166: hyapp.activity.v1.LuckyGiftRuleTier - (*LuckyGiftRuleStage)(nil), // 167: hyapp.activity.v1.LuckyGiftRuleStage - (*LuckyGiftRuleConfig)(nil), // 168: hyapp.activity.v1.LuckyGiftRuleConfig - (*CheckLuckyGiftRequest)(nil), // 169: hyapp.activity.v1.CheckLuckyGiftRequest - (*CheckLuckyGiftResponse)(nil), // 170: hyapp.activity.v1.CheckLuckyGiftResponse - (*LuckyGiftDrawResult)(nil), // 171: hyapp.activity.v1.LuckyGiftDrawResult - (*ExecuteLuckyGiftDrawRequest)(nil), // 172: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest - (*ExecuteLuckyGiftDrawResponse)(nil), // 173: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse - (*GetLuckyGiftConfigRequest)(nil), // 174: hyapp.activity.v1.GetLuckyGiftConfigRequest - (*GetLuckyGiftConfigResponse)(nil), // 175: hyapp.activity.v1.GetLuckyGiftConfigResponse - (*UpsertLuckyGiftConfigRequest)(nil), // 176: hyapp.activity.v1.UpsertLuckyGiftConfigRequest - (*UpsertLuckyGiftConfigResponse)(nil), // 177: hyapp.activity.v1.UpsertLuckyGiftConfigResponse - (*ListLuckyGiftConfigsRequest)(nil), // 178: hyapp.activity.v1.ListLuckyGiftConfigsRequest - (*ListLuckyGiftConfigsResponse)(nil), // 179: hyapp.activity.v1.ListLuckyGiftConfigsResponse - (*ListLuckyGiftDrawsRequest)(nil), // 180: hyapp.activity.v1.ListLuckyGiftDrawsRequest - (*ListLuckyGiftDrawsResponse)(nil), // 181: hyapp.activity.v1.ListLuckyGiftDrawsResponse - (*LuckyGiftDrawSummary)(nil), // 182: hyapp.activity.v1.LuckyGiftDrawSummary - (*GetLuckyGiftDrawSummaryRequest)(nil), // 183: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest - (*GetLuckyGiftDrawSummaryResponse)(nil), // 184: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse - (*WeeklyStarGift)(nil), // 185: hyapp.activity.v1.WeeklyStarGift - (*WeeklyStarReward)(nil), // 186: hyapp.activity.v1.WeeklyStarReward - (*WeeklyStarCycle)(nil), // 187: hyapp.activity.v1.WeeklyStarCycle - (*WeeklyStarLeaderboardEntry)(nil), // 188: hyapp.activity.v1.WeeklyStarLeaderboardEntry - (*WeeklyStarSettlement)(nil), // 189: hyapp.activity.v1.WeeklyStarSettlement - (*ListWeeklyStarCyclesRequest)(nil), // 190: hyapp.activity.v1.ListWeeklyStarCyclesRequest - (*ListWeeklyStarCyclesResponse)(nil), // 191: hyapp.activity.v1.ListWeeklyStarCyclesResponse - (*GetWeeklyStarCycleRequest)(nil), // 192: hyapp.activity.v1.GetWeeklyStarCycleRequest - (*GetWeeklyStarCycleResponse)(nil), // 193: hyapp.activity.v1.GetWeeklyStarCycleResponse - (*UpsertWeeklyStarCycleRequest)(nil), // 194: hyapp.activity.v1.UpsertWeeklyStarCycleRequest - (*UpsertWeeklyStarCycleResponse)(nil), // 195: hyapp.activity.v1.UpsertWeeklyStarCycleResponse - (*SetWeeklyStarCycleStatusRequest)(nil), // 196: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest - (*SetWeeklyStarCycleStatusResponse)(nil), // 197: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse - (*ListWeeklyStarLeaderboardRequest)(nil), // 198: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest - (*ListWeeklyStarLeaderboardResponse)(nil), // 199: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse - (*ListWeeklyStarSettlementsRequest)(nil), // 200: hyapp.activity.v1.ListWeeklyStarSettlementsRequest - (*ListWeeklyStarSettlementsResponse)(nil), // 201: hyapp.activity.v1.ListWeeklyStarSettlementsResponse - (*GetWeeklyStarCurrentRequest)(nil), // 202: hyapp.activity.v1.GetWeeklyStarCurrentRequest - (*GetWeeklyStarCurrentResponse)(nil), // 203: hyapp.activity.v1.GetWeeklyStarCurrentResponse - (*ListWeeklyStarHistoryRequest)(nil), // 204: hyapp.activity.v1.ListWeeklyStarHistoryRequest - (*WeeklyStarHistoryCycle)(nil), // 205: hyapp.activity.v1.WeeklyStarHistoryCycle - (*ListWeeklyStarHistoryResponse)(nil), // 206: hyapp.activity.v1.ListWeeklyStarHistoryResponse - (*v1.EventEnvelope)(nil), // 207: hyapp.events.room.v1.EventEnvelope + (*InviteActivityRewardTier)(nil), // 91: hyapp.activity.v1.InviteActivityRewardTier + (*InviteActivityRewardConfig)(nil), // 92: hyapp.activity.v1.InviteActivityRewardConfig + (*InviteActivityRewardProgress)(nil), // 93: hyapp.activity.v1.InviteActivityRewardProgress + (*InviteActivityRewardClaim)(nil), // 94: hyapp.activity.v1.InviteActivityRewardClaim + (*InviteActivityRewardStatus)(nil), // 95: hyapp.activity.v1.InviteActivityRewardStatus + (*GetInviteActivityRewardStatusRequest)(nil), // 96: hyapp.activity.v1.GetInviteActivityRewardStatusRequest + (*GetInviteActivityRewardStatusResponse)(nil), // 97: hyapp.activity.v1.GetInviteActivityRewardStatusResponse + (*ClaimInviteActivityRewardRequest)(nil), // 98: hyapp.activity.v1.ClaimInviteActivityRewardRequest + (*ClaimInviteActivityRewardResponse)(nil), // 99: hyapp.activity.v1.ClaimInviteActivityRewardResponse + (*InviteActivityLeaderboardEntry)(nil), // 100: hyapp.activity.v1.InviteActivityLeaderboardEntry + (*ListInviteActivityLeaderboardRequest)(nil), // 101: hyapp.activity.v1.ListInviteActivityLeaderboardRequest + (*ListInviteActivityLeaderboardResponse)(nil), // 102: hyapp.activity.v1.ListInviteActivityLeaderboardResponse + (*GetInviteActivityRewardConfigRequest)(nil), // 103: hyapp.activity.v1.GetInviteActivityRewardConfigRequest + (*GetInviteActivityRewardConfigResponse)(nil), // 104: hyapp.activity.v1.GetInviteActivityRewardConfigResponse + (*UpdateInviteActivityRewardConfigRequest)(nil), // 105: hyapp.activity.v1.UpdateInviteActivityRewardConfigRequest + (*UpdateInviteActivityRewardConfigResponse)(nil), // 106: hyapp.activity.v1.UpdateInviteActivityRewardConfigResponse + (*ListInviteActivityRewardClaimsRequest)(nil), // 107: hyapp.activity.v1.ListInviteActivityRewardClaimsRequest + (*ListInviteActivityRewardClaimsResponse)(nil), // 108: hyapp.activity.v1.ListInviteActivityRewardClaimsResponse + (*RoomTurnoverRewardTier)(nil), // 109: hyapp.activity.v1.RoomTurnoverRewardTier + (*RoomTurnoverRewardConfig)(nil), // 110: hyapp.activity.v1.RoomTurnoverRewardConfig + (*RoomTurnoverRewardSettlement)(nil), // 111: hyapp.activity.v1.RoomTurnoverRewardSettlement + (*RoomTurnoverRewardStatus)(nil), // 112: hyapp.activity.v1.RoomTurnoverRewardStatus + (*GetRoomTurnoverRewardStatusRequest)(nil), // 113: hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest + (*GetRoomTurnoverRewardStatusResponse)(nil), // 114: hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse + (*GetRoomTurnoverRewardConfigRequest)(nil), // 115: hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest + (*GetRoomTurnoverRewardConfigResponse)(nil), // 116: hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse + (*UpdateRoomTurnoverRewardConfigRequest)(nil), // 117: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest + (*UpdateRoomTurnoverRewardConfigResponse)(nil), // 118: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse + (*ListRoomTurnoverRewardSettlementsRequest)(nil), // 119: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest + (*ListRoomTurnoverRewardSettlementsResponse)(nil), // 120: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse + (*RetryRoomTurnoverRewardSettlementRequest)(nil), // 121: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest + (*RetryRoomTurnoverRewardSettlementResponse)(nil), // 122: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse + (*SevenDayCheckInReward)(nil), // 123: hyapp.activity.v1.SevenDayCheckInReward + (*SevenDayCheckInConfig)(nil), // 124: hyapp.activity.v1.SevenDayCheckInConfig + (*SevenDayCheckInRewardStatus)(nil), // 125: hyapp.activity.v1.SevenDayCheckInRewardStatus + (*GetSevenDayCheckInStatusRequest)(nil), // 126: hyapp.activity.v1.GetSevenDayCheckInStatusRequest + (*GetSevenDayCheckInStatusResponse)(nil), // 127: hyapp.activity.v1.GetSevenDayCheckInStatusResponse + (*SignSevenDayCheckInRequest)(nil), // 128: hyapp.activity.v1.SignSevenDayCheckInRequest + (*SignSevenDayCheckInResponse)(nil), // 129: hyapp.activity.v1.SignSevenDayCheckInResponse + (*GetSevenDayCheckInConfigRequest)(nil), // 130: hyapp.activity.v1.GetSevenDayCheckInConfigRequest + (*GetSevenDayCheckInConfigResponse)(nil), // 131: hyapp.activity.v1.GetSevenDayCheckInConfigResponse + (*UpdateSevenDayCheckInConfigRequest)(nil), // 132: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest + (*UpdateSevenDayCheckInConfigResponse)(nil), // 133: hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse + (*SevenDayCheckInClaim)(nil), // 134: hyapp.activity.v1.SevenDayCheckInClaim + (*ListSevenDayCheckInClaimsRequest)(nil), // 135: hyapp.activity.v1.ListSevenDayCheckInClaimsRequest + (*ListSevenDayCheckInClaimsResponse)(nil), // 136: hyapp.activity.v1.ListSevenDayCheckInClaimsResponse + (*LevelTrack)(nil), // 137: hyapp.activity.v1.LevelTrack + (*LevelRule)(nil), // 138: hyapp.activity.v1.LevelRule + (*LevelTier)(nil), // 139: hyapp.activity.v1.LevelTier + (*LevelTrackOverview)(nil), // 140: hyapp.activity.v1.LevelTrackOverview + (*GetMyLevelOverviewRequest)(nil), // 141: hyapp.activity.v1.GetMyLevelOverviewRequest + (*GetMyLevelOverviewResponse)(nil), // 142: hyapp.activity.v1.GetMyLevelOverviewResponse + (*GetLevelTrackRequest)(nil), // 143: hyapp.activity.v1.GetLevelTrackRequest + (*GetLevelTrackResponse)(nil), // 144: hyapp.activity.v1.GetLevelTrackResponse + (*LevelDisplayTrackProfile)(nil), // 145: hyapp.activity.v1.LevelDisplayTrackProfile + (*UserLevelDisplayProfile)(nil), // 146: hyapp.activity.v1.UserLevelDisplayProfile + (*BatchGetUserLevelDisplayProfilesRequest)(nil), // 147: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest + (*BatchGetUserLevelDisplayProfilesResponse)(nil), // 148: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse + (*LevelRewardJob)(nil), // 149: hyapp.activity.v1.LevelRewardJob + (*ListLevelRewardsRequest)(nil), // 150: hyapp.activity.v1.ListLevelRewardsRequest + (*ListLevelRewardsResponse)(nil), // 151: hyapp.activity.v1.ListLevelRewardsResponse + (*ConsumeLevelEventRequest)(nil), // 152: hyapp.activity.v1.ConsumeLevelEventRequest + (*ConsumeLevelEventResponse)(nil), // 153: hyapp.activity.v1.ConsumeLevelEventResponse + (*SetUserLevelRequest)(nil), // 154: hyapp.activity.v1.SetUserLevelRequest + (*SetUserLevelResponse)(nil), // 155: hyapp.activity.v1.SetUserLevelResponse + (*RegistrationLevelBadgeGrant)(nil), // 156: hyapp.activity.v1.RegistrationLevelBadgeGrant + (*IssueRegistrationLevelBadgesRequest)(nil), // 157: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest + (*IssueRegistrationLevelBadgesResponse)(nil), // 158: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse + (*UpsertLevelTrackRequest)(nil), // 159: hyapp.activity.v1.UpsertLevelTrackRequest + (*UpsertLevelTrackResponse)(nil), // 160: hyapp.activity.v1.UpsertLevelTrackResponse + (*UpsertLevelRuleRequest)(nil), // 161: hyapp.activity.v1.UpsertLevelRuleRequest + (*UpsertLevelRuleResponse)(nil), // 162: hyapp.activity.v1.UpsertLevelRuleResponse + (*UpsertLevelTierRequest)(nil), // 163: hyapp.activity.v1.UpsertLevelTierRequest + (*UpsertLevelTierResponse)(nil), // 164: hyapp.activity.v1.UpsertLevelTierResponse + (*ListLevelConfigRequest)(nil), // 165: hyapp.activity.v1.ListLevelConfigRequest + (*ListLevelConfigResponse)(nil), // 166: hyapp.activity.v1.ListLevelConfigResponse + (*AchievementCondition)(nil), // 167: hyapp.activity.v1.AchievementCondition + (*AchievementDefinition)(nil), // 168: hyapp.activity.v1.AchievementDefinition + (*UserAchievement)(nil), // 169: hyapp.activity.v1.UserAchievement + (*ListAchievementsRequest)(nil), // 170: hyapp.activity.v1.ListAchievementsRequest + (*ListAchievementsResponse)(nil), // 171: hyapp.activity.v1.ListAchievementsResponse + (*ConsumeAchievementEventRequest)(nil), // 172: hyapp.activity.v1.ConsumeAchievementEventRequest + (*ConsumeAchievementEventResponse)(nil), // 173: hyapp.activity.v1.ConsumeAchievementEventResponse + (*BadgeDisplayItem)(nil), // 174: hyapp.activity.v1.BadgeDisplayItem + (*ListMyBadgesRequest)(nil), // 175: hyapp.activity.v1.ListMyBadgesRequest + (*ListMyBadgesResponse)(nil), // 176: hyapp.activity.v1.ListMyBadgesResponse + (*SetBadgeDisplayRequest)(nil), // 177: hyapp.activity.v1.SetBadgeDisplayRequest + (*SetBadgeDisplayResponse)(nil), // 178: hyapp.activity.v1.SetBadgeDisplayResponse + (*UpsertAchievementDefinitionRequest)(nil), // 179: hyapp.activity.v1.UpsertAchievementDefinitionRequest + (*UpsertAchievementDefinitionResponse)(nil), // 180: hyapp.activity.v1.UpsertAchievementDefinitionResponse + (*LuckyGiftMeta)(nil), // 181: hyapp.activity.v1.LuckyGiftMeta + (*LuckyGiftTier)(nil), // 182: hyapp.activity.v1.LuckyGiftTier + (*LuckyGiftConfig)(nil), // 183: hyapp.activity.v1.LuckyGiftConfig + (*LuckyGiftRuleTier)(nil), // 184: hyapp.activity.v1.LuckyGiftRuleTier + (*LuckyGiftRuleStage)(nil), // 185: hyapp.activity.v1.LuckyGiftRuleStage + (*LuckyGiftRuleConfig)(nil), // 186: hyapp.activity.v1.LuckyGiftRuleConfig + (*CheckLuckyGiftRequest)(nil), // 187: hyapp.activity.v1.CheckLuckyGiftRequest + (*CheckLuckyGiftResponse)(nil), // 188: hyapp.activity.v1.CheckLuckyGiftResponse + (*LuckyGiftDrawResult)(nil), // 189: hyapp.activity.v1.LuckyGiftDrawResult + (*ExecuteLuckyGiftDrawRequest)(nil), // 190: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest + (*ExecuteLuckyGiftDrawResponse)(nil), // 191: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse + (*GetLuckyGiftConfigRequest)(nil), // 192: hyapp.activity.v1.GetLuckyGiftConfigRequest + (*GetLuckyGiftConfigResponse)(nil), // 193: hyapp.activity.v1.GetLuckyGiftConfigResponse + (*UpsertLuckyGiftConfigRequest)(nil), // 194: hyapp.activity.v1.UpsertLuckyGiftConfigRequest + (*UpsertLuckyGiftConfigResponse)(nil), // 195: hyapp.activity.v1.UpsertLuckyGiftConfigResponse + (*ListLuckyGiftConfigsRequest)(nil), // 196: hyapp.activity.v1.ListLuckyGiftConfigsRequest + (*ListLuckyGiftConfigsResponse)(nil), // 197: hyapp.activity.v1.ListLuckyGiftConfigsResponse + (*ListLuckyGiftDrawsRequest)(nil), // 198: hyapp.activity.v1.ListLuckyGiftDrawsRequest + (*ListLuckyGiftDrawsResponse)(nil), // 199: hyapp.activity.v1.ListLuckyGiftDrawsResponse + (*LuckyGiftDrawSummary)(nil), // 200: hyapp.activity.v1.LuckyGiftDrawSummary + (*GetLuckyGiftDrawSummaryRequest)(nil), // 201: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest + (*GetLuckyGiftDrawSummaryResponse)(nil), // 202: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse + (*WeeklyStarGift)(nil), // 203: hyapp.activity.v1.WeeklyStarGift + (*WeeklyStarReward)(nil), // 204: hyapp.activity.v1.WeeklyStarReward + (*WeeklyStarCycle)(nil), // 205: hyapp.activity.v1.WeeklyStarCycle + (*WeeklyStarLeaderboardEntry)(nil), // 206: hyapp.activity.v1.WeeklyStarLeaderboardEntry + (*WeeklyStarSettlement)(nil), // 207: hyapp.activity.v1.WeeklyStarSettlement + (*ListWeeklyStarCyclesRequest)(nil), // 208: hyapp.activity.v1.ListWeeklyStarCyclesRequest + (*ListWeeklyStarCyclesResponse)(nil), // 209: hyapp.activity.v1.ListWeeklyStarCyclesResponse + (*GetWeeklyStarCycleRequest)(nil), // 210: hyapp.activity.v1.GetWeeklyStarCycleRequest + (*GetWeeklyStarCycleResponse)(nil), // 211: hyapp.activity.v1.GetWeeklyStarCycleResponse + (*UpsertWeeklyStarCycleRequest)(nil), // 212: hyapp.activity.v1.UpsertWeeklyStarCycleRequest + (*UpsertWeeklyStarCycleResponse)(nil), // 213: hyapp.activity.v1.UpsertWeeklyStarCycleResponse + (*SetWeeklyStarCycleStatusRequest)(nil), // 214: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest + (*SetWeeklyStarCycleStatusResponse)(nil), // 215: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse + (*ListWeeklyStarLeaderboardRequest)(nil), // 216: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest + (*ListWeeklyStarLeaderboardResponse)(nil), // 217: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse + (*ListWeeklyStarSettlementsRequest)(nil), // 218: hyapp.activity.v1.ListWeeklyStarSettlementsRequest + (*ListWeeklyStarSettlementsResponse)(nil), // 219: hyapp.activity.v1.ListWeeklyStarSettlementsResponse + (*GetWeeklyStarCurrentRequest)(nil), // 220: hyapp.activity.v1.GetWeeklyStarCurrentRequest + (*GetWeeklyStarCurrentResponse)(nil), // 221: hyapp.activity.v1.GetWeeklyStarCurrentResponse + (*ListWeeklyStarHistoryRequest)(nil), // 222: hyapp.activity.v1.ListWeeklyStarHistoryRequest + (*WeeklyStarHistoryCycle)(nil), // 223: hyapp.activity.v1.WeeklyStarHistoryCycle + (*ListWeeklyStarHistoryResponse)(nil), // 224: hyapp.activity.v1.ListWeeklyStarHistoryResponse + (*v1.EventEnvelope)(nil), // 225: hyapp.events.room.v1.EventEnvelope } var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 0, // 0: hyapp.activity.v1.PingActivityRequest.meta:type_name -> hyapp.activity.v1.RequestMeta @@ -18620,7 +20128,7 @@ var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 0, // 19: hyapp.activity.v1.PublishGlobalBroadcastRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 0, // 20: hyapp.activity.v1.RemoveRegionBroadcastMemberRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 0, // 21: hyapp.activity.v1.ConsumeRoomEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 207, // 22: hyapp.activity.v1.ConsumeRoomEventRequest.envelope:type_name -> hyapp.events.room.v1.EventEnvelope + 225, // 22: hyapp.activity.v1.ConsumeRoomEventRequest.envelope:type_name -> hyapp.events.room.v1.EventEnvelope 0, // 23: hyapp.activity.v1.ListTaskDefinitionsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 42, // 24: hyapp.activity.v1.ListTaskDefinitionsResponse.tasks:type_name -> hyapp.activity.v1.TaskDefinition 0, // 25: hyapp.activity.v1.UpsertTaskDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta @@ -18668,291 +20176,321 @@ var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 77, // 67: hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse.config:type_name -> hyapp.activity.v1.CumulativeRechargeRewardConfig 0, // 68: hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 78, // 69: hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse.grants:type_name -> hyapp.activity.v1.CumulativeRechargeRewardGrant - 91, // 70: hyapp.activity.v1.RoomTurnoverRewardConfig.tiers:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier - 92, // 71: hyapp.activity.v1.RoomTurnoverRewardStatus.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig - 91, // 72: hyapp.activity.v1.RoomTurnoverRewardStatus.matched_tier:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier - 93, // 73: hyapp.activity.v1.RoomTurnoverRewardStatus.latest_settlement:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement - 0, // 74: hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 94, // 75: hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse.status:type_name -> hyapp.activity.v1.RoomTurnoverRewardStatus - 0, // 76: hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 92, // 77: hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig - 0, // 78: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 91, // 79: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier - 92, // 80: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig - 0, // 81: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 93, // 82: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse.settlements:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement - 0, // 83: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 93, // 84: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse.settlement:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement - 105, // 85: hyapp.activity.v1.SevenDayCheckInConfig.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInReward - 0, // 86: hyapp.activity.v1.GetSevenDayCheckInStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 107, // 87: hyapp.activity.v1.GetSevenDayCheckInStatusResponse.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInRewardStatus - 0, // 88: hyapp.activity.v1.SignSevenDayCheckInRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 89: hyapp.activity.v1.GetSevenDayCheckInConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 106, // 90: hyapp.activity.v1.GetSevenDayCheckInConfigResponse.config:type_name -> hyapp.activity.v1.SevenDayCheckInConfig - 0, // 91: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 105, // 92: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInReward - 106, // 93: hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse.config:type_name -> hyapp.activity.v1.SevenDayCheckInConfig - 0, // 94: hyapp.activity.v1.ListSevenDayCheckInClaimsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 116, // 95: hyapp.activity.v1.ListSevenDayCheckInClaimsResponse.claims:type_name -> hyapp.activity.v1.SevenDayCheckInClaim - 0, // 96: hyapp.activity.v1.GetMyLevelOverviewRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 122, // 97: hyapp.activity.v1.GetMyLevelOverviewResponse.tracks:type_name -> hyapp.activity.v1.LevelTrackOverview - 0, // 98: hyapp.activity.v1.GetLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 122, // 99: hyapp.activity.v1.GetLevelTrackResponse.overview:type_name -> hyapp.activity.v1.LevelTrackOverview - 120, // 100: hyapp.activity.v1.GetLevelTrackResponse.rules:type_name -> hyapp.activity.v1.LevelRule - 121, // 101: hyapp.activity.v1.GetLevelTrackResponse.tiers:type_name -> hyapp.activity.v1.LevelTier - 127, // 102: hyapp.activity.v1.UserLevelDisplayProfile.wealth:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile - 127, // 103: hyapp.activity.v1.UserLevelDisplayProfile.game:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile - 127, // 104: hyapp.activity.v1.UserLevelDisplayProfile.charm:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile - 0, // 105: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 128, // 106: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse.profiles:type_name -> hyapp.activity.v1.UserLevelDisplayProfile - 0, // 107: hyapp.activity.v1.ListLevelRewardsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 131, // 108: hyapp.activity.v1.ListLevelRewardsResponse.rewards:type_name -> hyapp.activity.v1.LevelRewardJob - 0, // 109: hyapp.activity.v1.ConsumeLevelEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 110: hyapp.activity.v1.SetUserLevelRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 111: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 138, // 112: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse.grants:type_name -> hyapp.activity.v1.RegistrationLevelBadgeGrant - 0, // 113: hyapp.activity.v1.UpsertLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 119, // 114: hyapp.activity.v1.UpsertLevelTrackResponse.track:type_name -> hyapp.activity.v1.LevelTrack - 0, // 115: hyapp.activity.v1.UpsertLevelRuleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 120, // 116: hyapp.activity.v1.UpsertLevelRuleResponse.rule:type_name -> hyapp.activity.v1.LevelRule - 0, // 117: hyapp.activity.v1.UpsertLevelTierRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 121, // 118: hyapp.activity.v1.UpsertLevelTierResponse.tier:type_name -> hyapp.activity.v1.LevelTier - 0, // 119: hyapp.activity.v1.ListLevelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 119, // 120: hyapp.activity.v1.ListLevelConfigResponse.tracks:type_name -> hyapp.activity.v1.LevelTrack - 120, // 121: hyapp.activity.v1.ListLevelConfigResponse.rules:type_name -> hyapp.activity.v1.LevelRule - 121, // 122: hyapp.activity.v1.ListLevelConfigResponse.tiers:type_name -> hyapp.activity.v1.LevelTier - 149, // 123: hyapp.activity.v1.AchievementDefinition.conditions:type_name -> hyapp.activity.v1.AchievementCondition - 150, // 124: hyapp.activity.v1.UserAchievement.definition:type_name -> hyapp.activity.v1.AchievementDefinition - 0, // 125: hyapp.activity.v1.ListAchievementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 151, // 126: hyapp.activity.v1.ListAchievementsResponse.achievements:type_name -> hyapp.activity.v1.UserAchievement - 0, // 127: hyapp.activity.v1.ConsumeAchievementEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 128: hyapp.activity.v1.ListMyBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 156, // 129: hyapp.activity.v1.ListMyBadgesResponse.strip_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 156, // 130: hyapp.activity.v1.ListMyBadgesResponse.profile_tile_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 156, // 131: hyapp.activity.v1.ListMyBadgesResponse.honor_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 0, // 132: hyapp.activity.v1.SetBadgeDisplayRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 156, // 133: hyapp.activity.v1.SetBadgeDisplayRequest.items:type_name -> hyapp.activity.v1.BadgeDisplayItem - 158, // 134: hyapp.activity.v1.SetBadgeDisplayResponse.profile:type_name -> hyapp.activity.v1.ListMyBadgesResponse - 0, // 135: hyapp.activity.v1.UpsertAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 149, // 136: hyapp.activity.v1.UpsertAchievementDefinitionRequest.conditions:type_name -> hyapp.activity.v1.AchievementCondition - 150, // 137: hyapp.activity.v1.UpsertAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition - 0, // 138: hyapp.activity.v1.LuckyGiftMeta.meta:type_name -> hyapp.activity.v1.RequestMeta - 164, // 139: hyapp.activity.v1.LuckyGiftConfig.tiers:type_name -> hyapp.activity.v1.LuckyGiftTier - 166, // 140: hyapp.activity.v1.LuckyGiftRuleStage.tiers:type_name -> hyapp.activity.v1.LuckyGiftRuleTier - 167, // 141: hyapp.activity.v1.LuckyGiftRuleConfig.stages:type_name -> hyapp.activity.v1.LuckyGiftRuleStage - 0, // 142: hyapp.activity.v1.CheckLuckyGiftRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 163, // 143: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest.lucky_gift:type_name -> hyapp.activity.v1.LuckyGiftMeta - 171, // 144: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse.result:type_name -> hyapp.activity.v1.LuckyGiftDrawResult - 0, // 145: hyapp.activity.v1.GetLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 168, // 146: hyapp.activity.v1.GetLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 147: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 168, // 148: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 168, // 149: hyapp.activity.v1.UpsertLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 150: hyapp.activity.v1.ListLuckyGiftConfigsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 168, // 151: hyapp.activity.v1.ListLuckyGiftConfigsResponse.configs:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 152: hyapp.activity.v1.ListLuckyGiftDrawsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 171, // 153: hyapp.activity.v1.ListLuckyGiftDrawsResponse.draws:type_name -> hyapp.activity.v1.LuckyGiftDrawResult - 0, // 154: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 182, // 155: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.activity.v1.LuckyGiftDrawSummary - 185, // 156: hyapp.activity.v1.WeeklyStarCycle.gifts:type_name -> hyapp.activity.v1.WeeklyStarGift - 186, // 157: hyapp.activity.v1.WeeklyStarCycle.rewards:type_name -> hyapp.activity.v1.WeeklyStarReward - 0, // 158: hyapp.activity.v1.ListWeeklyStarCyclesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 159: hyapp.activity.v1.ListWeeklyStarCyclesResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 160: hyapp.activity.v1.GetWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 161: hyapp.activity.v1.GetWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 162: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 163: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 187, // 164: hyapp.activity.v1.UpsertWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 165: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 166: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 0, // 167: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 168: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 188, // 169: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 0, // 170: hyapp.activity.v1.ListWeeklyStarSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 189, // 171: hyapp.activity.v1.ListWeeklyStarSettlementsResponse.settlements:type_name -> hyapp.activity.v1.WeeklyStarSettlement - 0, // 172: hyapp.activity.v1.GetWeeklyStarCurrentRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 173: hyapp.activity.v1.GetWeeklyStarCurrentResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 188, // 174: hyapp.activity.v1.GetWeeklyStarCurrentResponse.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 188, // 175: hyapp.activity.v1.GetWeeklyStarCurrentResponse.my_entry:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 0, // 176: hyapp.activity.v1.ListWeeklyStarHistoryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 187, // 177: hyapp.activity.v1.WeeklyStarHistoryCycle.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle - 188, // 178: hyapp.activity.v1.WeeklyStarHistoryCycle.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry - 205, // 179: hyapp.activity.v1.ListWeeklyStarHistoryResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarHistoryCycle - 1, // 180: hyapp.activity.v1.ActivityService.PingActivity:input_type -> hyapp.activity.v1.PingActivityRequest - 3, // 181: hyapp.activity.v1.ActivityService.GetActivityStatus:input_type -> hyapp.activity.v1.GetActivityStatusRequest - 6, // 182: hyapp.activity.v1.MessageInboxService.ListMessageTabs:input_type -> hyapp.activity.v1.ListMessageTabsRequest - 9, // 183: hyapp.activity.v1.MessageInboxService.ListInboxMessages:input_type -> hyapp.activity.v1.ListInboxMessagesRequest - 11, // 184: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:input_type -> hyapp.activity.v1.MarkInboxMessageReadRequest - 13, // 185: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:input_type -> hyapp.activity.v1.MarkInboxSectionReadRequest - 15, // 186: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:input_type -> hyapp.activity.v1.DeleteInboxMessageRequest - 17, // 187: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:input_type -> hyapp.activity.v1.CreateInboxMessageRequest - 19, // 188: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:input_type -> hyapp.activity.v1.CreateFanoutJobRequest - 21, // 189: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 190: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 191: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 192: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 193: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 25, // 194: hyapp.activity.v1.TaskService.ListUserTasks:input_type -> hyapp.activity.v1.ListUserTasksRequest - 27, // 195: hyapp.activity.v1.TaskService.ClaimTaskReward:input_type -> hyapp.activity.v1.ClaimTaskRewardRequest - 29, // 196: hyapp.activity.v1.TaskService.ConsumeTaskEvent:input_type -> hyapp.activity.v1.ConsumeTaskEventRequest - 123, // 197: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:input_type -> hyapp.activity.v1.GetMyLevelOverviewRequest - 125, // 198: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:input_type -> hyapp.activity.v1.GetLevelTrackRequest - 129, // 199: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:input_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest - 132, // 200: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:input_type -> hyapp.activity.v1.ListLevelRewardsRequest - 134, // 201: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:input_type -> hyapp.activity.v1.ConsumeLevelEventRequest - 136, // 202: hyapp.activity.v1.GrowthLevelService.SetUserLevel:input_type -> hyapp.activity.v1.SetUserLevelRequest - 139, // 203: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:input_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesRequest - 152, // 204: hyapp.activity.v1.AchievementService.ListAchievements:input_type -> hyapp.activity.v1.ListAchievementsRequest - 154, // 205: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:input_type -> hyapp.activity.v1.ConsumeAchievementEventRequest - 157, // 206: hyapp.activity.v1.AchievementService.ListMyBadges:input_type -> hyapp.activity.v1.ListMyBadgesRequest - 159, // 207: hyapp.activity.v1.AchievementService.SetBadgeDisplay:input_type -> hyapp.activity.v1.SetBadgeDisplayRequest - 169, // 208: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.activity.v1.CheckLuckyGiftRequest - 172, // 209: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawRequest - 95, // 210: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest - 202, // 211: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:input_type -> hyapp.activity.v1.GetWeeklyStarCurrentRequest - 198, // 212: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest - 204, // 213: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:input_type -> hyapp.activity.v1.ListWeeklyStarHistoryRequest - 32, // 214: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:input_type -> hyapp.activity.v1.EnsureBroadcastGroupsRequest - 34, // 215: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:input_type -> hyapp.activity.v1.PublishRegionBroadcastRequest - 35, // 216: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:input_type -> hyapp.activity.v1.PublishGlobalBroadcastRequest - 37, // 217: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:input_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberRequest - 21, // 218: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 40, // 219: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:input_type -> hyapp.activity.v1.ConsumeRoomEventRequest - 43, // 220: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:input_type -> hyapp.activity.v1.ListTaskDefinitionsRequest - 45, // 221: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:input_type -> hyapp.activity.v1.UpsertTaskDefinitionRequest - 47, // 222: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:input_type -> hyapp.activity.v1.SetTaskDefinitionStatusRequest - 52, // 223: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:input_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityRequest - 54, // 224: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:input_type -> hyapp.activity.v1.IssueRegistrationRewardRequest - 56, // 225: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:input_type -> hyapp.activity.v1.GetRegistrationRewardConfigRequest - 58, // 226: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:input_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigRequest - 60, // 227: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:input_type -> hyapp.activity.v1.ListRegistrationRewardClaimsRequest - 66, // 228: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:input_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusRequest - 68, // 229: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:input_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardRequest - 70, // 230: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigRequest - 72, // 231: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest - 74, // 232: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:input_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest - 81, // 233: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest - 83, // 234: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:input_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest - 85, // 235: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest - 87, // 236: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest - 89, // 237: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:input_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest - 97, // 238: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest - 99, // 239: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest - 101, // 240: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:input_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest - 103, // 241: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:input_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest - 190, // 242: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:input_type -> hyapp.activity.v1.ListWeeklyStarCyclesRequest - 194, // 243: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest - 192, // 244: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:input_type -> hyapp.activity.v1.GetWeeklyStarCycleRequest - 194, // 245: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest - 196, // 246: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:input_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusRequest - 198, // 247: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest - 200, // 248: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:input_type -> hyapp.activity.v1.ListWeeklyStarSettlementsRequest - 108, // 249: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:input_type -> hyapp.activity.v1.GetSevenDayCheckInStatusRequest - 110, // 250: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:input_type -> hyapp.activity.v1.SignSevenDayCheckInRequest - 112, // 251: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:input_type -> hyapp.activity.v1.GetSevenDayCheckInConfigRequest - 114, // 252: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:input_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest - 117, // 253: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:input_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsRequest - 147, // 254: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:input_type -> hyapp.activity.v1.ListLevelConfigRequest - 141, // 255: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:input_type -> hyapp.activity.v1.UpsertLevelTrackRequest - 143, // 256: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:input_type -> hyapp.activity.v1.UpsertLevelRuleRequest - 145, // 257: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:input_type -> hyapp.activity.v1.UpsertLevelTierRequest - 152, // 258: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:input_type -> hyapp.activity.v1.ListAchievementsRequest - 161, // 259: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:input_type -> hyapp.activity.v1.UpsertAchievementDefinitionRequest - 174, // 260: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.activity.v1.GetLuckyGiftConfigRequest - 176, // 261: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.activity.v1.UpsertLuckyGiftConfigRequest - 178, // 262: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.activity.v1.ListLuckyGiftConfigsRequest - 180, // 263: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.activity.v1.ListLuckyGiftDrawsRequest - 183, // 264: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest - 2, // 265: hyapp.activity.v1.ActivityService.PingActivity:output_type -> hyapp.activity.v1.PingActivityResponse - 4, // 266: hyapp.activity.v1.ActivityService.GetActivityStatus:output_type -> hyapp.activity.v1.GetActivityStatusResponse - 7, // 267: hyapp.activity.v1.MessageInboxService.ListMessageTabs:output_type -> hyapp.activity.v1.ListMessageTabsResponse - 10, // 268: hyapp.activity.v1.MessageInboxService.ListInboxMessages:output_type -> hyapp.activity.v1.ListInboxMessagesResponse - 12, // 269: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:output_type -> hyapp.activity.v1.MarkInboxMessageReadResponse - 14, // 270: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:output_type -> hyapp.activity.v1.MarkInboxSectionReadResponse - 16, // 271: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:output_type -> hyapp.activity.v1.DeleteInboxMessageResponse - 18, // 272: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:output_type -> hyapp.activity.v1.CreateInboxMessageResponse - 20, // 273: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:output_type -> hyapp.activity.v1.CreateFanoutJobResponse - 22, // 274: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 275: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 276: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 277: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 278: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 26, // 279: hyapp.activity.v1.TaskService.ListUserTasks:output_type -> hyapp.activity.v1.ListUserTasksResponse - 28, // 280: hyapp.activity.v1.TaskService.ClaimTaskReward:output_type -> hyapp.activity.v1.ClaimTaskRewardResponse - 30, // 281: hyapp.activity.v1.TaskService.ConsumeTaskEvent:output_type -> hyapp.activity.v1.ConsumeTaskEventResponse - 124, // 282: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:output_type -> hyapp.activity.v1.GetMyLevelOverviewResponse - 126, // 283: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:output_type -> hyapp.activity.v1.GetLevelTrackResponse - 130, // 284: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:output_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse - 133, // 285: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:output_type -> hyapp.activity.v1.ListLevelRewardsResponse - 135, // 286: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:output_type -> hyapp.activity.v1.ConsumeLevelEventResponse - 137, // 287: hyapp.activity.v1.GrowthLevelService.SetUserLevel:output_type -> hyapp.activity.v1.SetUserLevelResponse - 140, // 288: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:output_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesResponse - 153, // 289: hyapp.activity.v1.AchievementService.ListAchievements:output_type -> hyapp.activity.v1.ListAchievementsResponse - 155, // 290: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:output_type -> hyapp.activity.v1.ConsumeAchievementEventResponse - 158, // 291: hyapp.activity.v1.AchievementService.ListMyBadges:output_type -> hyapp.activity.v1.ListMyBadgesResponse - 160, // 292: hyapp.activity.v1.AchievementService.SetBadgeDisplay:output_type -> hyapp.activity.v1.SetBadgeDisplayResponse - 170, // 293: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.activity.v1.CheckLuckyGiftResponse - 173, // 294: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawResponse - 96, // 295: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse - 203, // 296: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:output_type -> hyapp.activity.v1.GetWeeklyStarCurrentResponse - 199, // 297: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse - 206, // 298: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:output_type -> hyapp.activity.v1.ListWeeklyStarHistoryResponse - 33, // 299: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:output_type -> hyapp.activity.v1.EnsureBroadcastGroupsResponse - 36, // 300: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse - 36, // 301: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse - 38, // 302: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:output_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberResponse - 39, // 303: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:output_type -> hyapp.activity.v1.ProcessBroadcastOutboxBatchResponse - 41, // 304: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:output_type -> hyapp.activity.v1.ConsumeRoomEventResponse - 44, // 305: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:output_type -> hyapp.activity.v1.ListTaskDefinitionsResponse - 46, // 306: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:output_type -> hyapp.activity.v1.UpsertTaskDefinitionResponse - 48, // 307: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:output_type -> hyapp.activity.v1.SetTaskDefinitionStatusResponse - 53, // 308: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:output_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityResponse - 55, // 309: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:output_type -> hyapp.activity.v1.IssueRegistrationRewardResponse - 57, // 310: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:output_type -> hyapp.activity.v1.GetRegistrationRewardConfigResponse - 59, // 311: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:output_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigResponse - 61, // 312: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:output_type -> hyapp.activity.v1.ListRegistrationRewardClaimsResponse - 67, // 313: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:output_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusResponse - 69, // 314: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:output_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardResponse - 71, // 315: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigResponse - 73, // 316: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse - 75, // 317: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:output_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse - 82, // 318: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse - 84, // 319: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:output_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse - 86, // 320: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse - 88, // 321: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse - 90, // 322: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:output_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse - 98, // 323: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse - 100, // 324: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse - 102, // 325: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:output_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse - 104, // 326: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:output_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse - 191, // 327: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:output_type -> hyapp.activity.v1.ListWeeklyStarCyclesResponse - 195, // 328: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse - 193, // 329: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:output_type -> hyapp.activity.v1.GetWeeklyStarCycleResponse - 195, // 330: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse - 197, // 331: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:output_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusResponse - 199, // 332: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse - 201, // 333: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:output_type -> hyapp.activity.v1.ListWeeklyStarSettlementsResponse - 109, // 334: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:output_type -> hyapp.activity.v1.GetSevenDayCheckInStatusResponse - 111, // 335: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:output_type -> hyapp.activity.v1.SignSevenDayCheckInResponse - 113, // 336: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:output_type -> hyapp.activity.v1.GetSevenDayCheckInConfigResponse - 115, // 337: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:output_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse - 118, // 338: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:output_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsResponse - 148, // 339: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:output_type -> hyapp.activity.v1.ListLevelConfigResponse - 142, // 340: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:output_type -> hyapp.activity.v1.UpsertLevelTrackResponse - 144, // 341: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:output_type -> hyapp.activity.v1.UpsertLevelRuleResponse - 146, // 342: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:output_type -> hyapp.activity.v1.UpsertLevelTierResponse - 153, // 343: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:output_type -> hyapp.activity.v1.ListAchievementsResponse - 162, // 344: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:output_type -> hyapp.activity.v1.UpsertAchievementDefinitionResponse - 175, // 345: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.activity.v1.GetLuckyGiftConfigResponse - 177, // 346: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.activity.v1.UpsertLuckyGiftConfigResponse - 179, // 347: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.activity.v1.ListLuckyGiftConfigsResponse - 181, // 348: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.activity.v1.ListLuckyGiftDrawsResponse - 184, // 349: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse - 265, // [265:350] is the sub-list for method output_type - 180, // [180:265] is the sub-list for method input_type - 180, // [180:180] is the sub-list for extension type_name - 180, // [180:180] is the sub-list for extension extendee - 0, // [0:180] is the sub-list for field type_name + 91, // 70: hyapp.activity.v1.InviteActivityRewardConfig.tiers:type_name -> hyapp.activity.v1.InviteActivityRewardTier + 92, // 71: hyapp.activity.v1.InviteActivityRewardStatus.config:type_name -> hyapp.activity.v1.InviteActivityRewardConfig + 93, // 72: hyapp.activity.v1.InviteActivityRewardStatus.progress:type_name -> hyapp.activity.v1.InviteActivityRewardProgress + 94, // 73: hyapp.activity.v1.InviteActivityRewardStatus.claims:type_name -> hyapp.activity.v1.InviteActivityRewardClaim + 0, // 74: hyapp.activity.v1.GetInviteActivityRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 95, // 75: hyapp.activity.v1.GetInviteActivityRewardStatusResponse.status:type_name -> hyapp.activity.v1.InviteActivityRewardStatus + 0, // 76: hyapp.activity.v1.ClaimInviteActivityRewardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 94, // 77: hyapp.activity.v1.ClaimInviteActivityRewardResponse.claim:type_name -> hyapp.activity.v1.InviteActivityRewardClaim + 95, // 78: hyapp.activity.v1.ClaimInviteActivityRewardResponse.status:type_name -> hyapp.activity.v1.InviteActivityRewardStatus + 0, // 79: hyapp.activity.v1.ListInviteActivityLeaderboardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 100, // 80: hyapp.activity.v1.ListInviteActivityLeaderboardResponse.entries:type_name -> hyapp.activity.v1.InviteActivityLeaderboardEntry + 0, // 81: hyapp.activity.v1.GetInviteActivityRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 92, // 82: hyapp.activity.v1.GetInviteActivityRewardConfigResponse.config:type_name -> hyapp.activity.v1.InviteActivityRewardConfig + 0, // 83: hyapp.activity.v1.UpdateInviteActivityRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 91, // 84: hyapp.activity.v1.UpdateInviteActivityRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.InviteActivityRewardTier + 92, // 85: hyapp.activity.v1.UpdateInviteActivityRewardConfigResponse.config:type_name -> hyapp.activity.v1.InviteActivityRewardConfig + 0, // 86: hyapp.activity.v1.ListInviteActivityRewardClaimsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 94, // 87: hyapp.activity.v1.ListInviteActivityRewardClaimsResponse.claims:type_name -> hyapp.activity.v1.InviteActivityRewardClaim + 109, // 88: hyapp.activity.v1.RoomTurnoverRewardConfig.tiers:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier + 110, // 89: hyapp.activity.v1.RoomTurnoverRewardStatus.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig + 109, // 90: hyapp.activity.v1.RoomTurnoverRewardStatus.matched_tier:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier + 111, // 91: hyapp.activity.v1.RoomTurnoverRewardStatus.latest_settlement:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement + 0, // 92: hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 112, // 93: hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse.status:type_name -> hyapp.activity.v1.RoomTurnoverRewardStatus + 0, // 94: hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 110, // 95: hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig + 0, // 96: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 109, // 97: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest.tiers:type_name -> hyapp.activity.v1.RoomTurnoverRewardTier + 110, // 98: hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse.config:type_name -> hyapp.activity.v1.RoomTurnoverRewardConfig + 0, // 99: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 111, // 100: hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse.settlements:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement + 0, // 101: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 111, // 102: hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse.settlement:type_name -> hyapp.activity.v1.RoomTurnoverRewardSettlement + 123, // 103: hyapp.activity.v1.SevenDayCheckInConfig.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInReward + 0, // 104: hyapp.activity.v1.GetSevenDayCheckInStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 125, // 105: hyapp.activity.v1.GetSevenDayCheckInStatusResponse.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInRewardStatus + 0, // 106: hyapp.activity.v1.SignSevenDayCheckInRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 107: hyapp.activity.v1.GetSevenDayCheckInConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 124, // 108: hyapp.activity.v1.GetSevenDayCheckInConfigResponse.config:type_name -> hyapp.activity.v1.SevenDayCheckInConfig + 0, // 109: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 123, // 110: hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest.rewards:type_name -> hyapp.activity.v1.SevenDayCheckInReward + 124, // 111: hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse.config:type_name -> hyapp.activity.v1.SevenDayCheckInConfig + 0, // 112: hyapp.activity.v1.ListSevenDayCheckInClaimsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 134, // 113: hyapp.activity.v1.ListSevenDayCheckInClaimsResponse.claims:type_name -> hyapp.activity.v1.SevenDayCheckInClaim + 0, // 114: hyapp.activity.v1.GetMyLevelOverviewRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 140, // 115: hyapp.activity.v1.GetMyLevelOverviewResponse.tracks:type_name -> hyapp.activity.v1.LevelTrackOverview + 0, // 116: hyapp.activity.v1.GetLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 140, // 117: hyapp.activity.v1.GetLevelTrackResponse.overview:type_name -> hyapp.activity.v1.LevelTrackOverview + 138, // 118: hyapp.activity.v1.GetLevelTrackResponse.rules:type_name -> hyapp.activity.v1.LevelRule + 139, // 119: hyapp.activity.v1.GetLevelTrackResponse.tiers:type_name -> hyapp.activity.v1.LevelTier + 145, // 120: hyapp.activity.v1.UserLevelDisplayProfile.wealth:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 145, // 121: hyapp.activity.v1.UserLevelDisplayProfile.game:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 145, // 122: hyapp.activity.v1.UserLevelDisplayProfile.charm:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 0, // 123: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 146, // 124: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse.profiles:type_name -> hyapp.activity.v1.UserLevelDisplayProfile + 0, // 125: hyapp.activity.v1.ListLevelRewardsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 149, // 126: hyapp.activity.v1.ListLevelRewardsResponse.rewards:type_name -> hyapp.activity.v1.LevelRewardJob + 0, // 127: hyapp.activity.v1.ConsumeLevelEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 128: hyapp.activity.v1.SetUserLevelRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 129: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 156, // 130: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse.grants:type_name -> hyapp.activity.v1.RegistrationLevelBadgeGrant + 0, // 131: hyapp.activity.v1.UpsertLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 137, // 132: hyapp.activity.v1.UpsertLevelTrackResponse.track:type_name -> hyapp.activity.v1.LevelTrack + 0, // 133: hyapp.activity.v1.UpsertLevelRuleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 138, // 134: hyapp.activity.v1.UpsertLevelRuleResponse.rule:type_name -> hyapp.activity.v1.LevelRule + 0, // 135: hyapp.activity.v1.UpsertLevelTierRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 139, // 136: hyapp.activity.v1.UpsertLevelTierResponse.tier:type_name -> hyapp.activity.v1.LevelTier + 0, // 137: hyapp.activity.v1.ListLevelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 137, // 138: hyapp.activity.v1.ListLevelConfigResponse.tracks:type_name -> hyapp.activity.v1.LevelTrack + 138, // 139: hyapp.activity.v1.ListLevelConfigResponse.rules:type_name -> hyapp.activity.v1.LevelRule + 139, // 140: hyapp.activity.v1.ListLevelConfigResponse.tiers:type_name -> hyapp.activity.v1.LevelTier + 167, // 141: hyapp.activity.v1.AchievementDefinition.conditions:type_name -> hyapp.activity.v1.AchievementCondition + 168, // 142: hyapp.activity.v1.UserAchievement.definition:type_name -> hyapp.activity.v1.AchievementDefinition + 0, // 143: hyapp.activity.v1.ListAchievementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 169, // 144: hyapp.activity.v1.ListAchievementsResponse.achievements:type_name -> hyapp.activity.v1.UserAchievement + 0, // 145: hyapp.activity.v1.ConsumeAchievementEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 146: hyapp.activity.v1.ListMyBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 174, // 147: hyapp.activity.v1.ListMyBadgesResponse.strip_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 174, // 148: hyapp.activity.v1.ListMyBadgesResponse.profile_tile_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 174, // 149: hyapp.activity.v1.ListMyBadgesResponse.honor_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 0, // 150: hyapp.activity.v1.SetBadgeDisplayRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 174, // 151: hyapp.activity.v1.SetBadgeDisplayRequest.items:type_name -> hyapp.activity.v1.BadgeDisplayItem + 176, // 152: hyapp.activity.v1.SetBadgeDisplayResponse.profile:type_name -> hyapp.activity.v1.ListMyBadgesResponse + 0, // 153: hyapp.activity.v1.UpsertAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 167, // 154: hyapp.activity.v1.UpsertAchievementDefinitionRequest.conditions:type_name -> hyapp.activity.v1.AchievementCondition + 168, // 155: hyapp.activity.v1.UpsertAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition + 0, // 156: hyapp.activity.v1.LuckyGiftMeta.meta:type_name -> hyapp.activity.v1.RequestMeta + 182, // 157: hyapp.activity.v1.LuckyGiftConfig.tiers:type_name -> hyapp.activity.v1.LuckyGiftTier + 184, // 158: hyapp.activity.v1.LuckyGiftRuleStage.tiers:type_name -> hyapp.activity.v1.LuckyGiftRuleTier + 185, // 159: hyapp.activity.v1.LuckyGiftRuleConfig.stages:type_name -> hyapp.activity.v1.LuckyGiftRuleStage + 0, // 160: hyapp.activity.v1.CheckLuckyGiftRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 181, // 161: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest.lucky_gift:type_name -> hyapp.activity.v1.LuckyGiftMeta + 189, // 162: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse.result:type_name -> hyapp.activity.v1.LuckyGiftDrawResult + 0, // 163: hyapp.activity.v1.GetLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 186, // 164: hyapp.activity.v1.GetLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 165: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 186, // 166: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 186, // 167: hyapp.activity.v1.UpsertLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 168: hyapp.activity.v1.ListLuckyGiftConfigsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 186, // 169: hyapp.activity.v1.ListLuckyGiftConfigsResponse.configs:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 170: hyapp.activity.v1.ListLuckyGiftDrawsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 189, // 171: hyapp.activity.v1.ListLuckyGiftDrawsResponse.draws:type_name -> hyapp.activity.v1.LuckyGiftDrawResult + 0, // 172: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 200, // 173: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.activity.v1.LuckyGiftDrawSummary + 203, // 174: hyapp.activity.v1.WeeklyStarCycle.gifts:type_name -> hyapp.activity.v1.WeeklyStarGift + 204, // 175: hyapp.activity.v1.WeeklyStarCycle.rewards:type_name -> hyapp.activity.v1.WeeklyStarReward + 0, // 176: hyapp.activity.v1.ListWeeklyStarCyclesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 205, // 177: hyapp.activity.v1.ListWeeklyStarCyclesResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 178: hyapp.activity.v1.GetWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 205, // 179: hyapp.activity.v1.GetWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 180: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 205, // 181: hyapp.activity.v1.UpsertWeeklyStarCycleRequest.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 205, // 182: hyapp.activity.v1.UpsertWeeklyStarCycleResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 183: hyapp.activity.v1.SetWeeklyStarCycleStatusRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 205, // 184: hyapp.activity.v1.SetWeeklyStarCycleStatusResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 0, // 185: hyapp.activity.v1.ListWeeklyStarLeaderboardRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 205, // 186: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 206, // 187: hyapp.activity.v1.ListWeeklyStarLeaderboardResponse.entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 0, // 188: hyapp.activity.v1.ListWeeklyStarSettlementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 207, // 189: hyapp.activity.v1.ListWeeklyStarSettlementsResponse.settlements:type_name -> hyapp.activity.v1.WeeklyStarSettlement + 0, // 190: hyapp.activity.v1.GetWeeklyStarCurrentRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 205, // 191: hyapp.activity.v1.GetWeeklyStarCurrentResponse.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 206, // 192: hyapp.activity.v1.GetWeeklyStarCurrentResponse.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 206, // 193: hyapp.activity.v1.GetWeeklyStarCurrentResponse.my_entry:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 0, // 194: hyapp.activity.v1.ListWeeklyStarHistoryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 205, // 195: hyapp.activity.v1.WeeklyStarHistoryCycle.cycle:type_name -> hyapp.activity.v1.WeeklyStarCycle + 206, // 196: hyapp.activity.v1.WeeklyStarHistoryCycle.top_entries:type_name -> hyapp.activity.v1.WeeklyStarLeaderboardEntry + 223, // 197: hyapp.activity.v1.ListWeeklyStarHistoryResponse.cycles:type_name -> hyapp.activity.v1.WeeklyStarHistoryCycle + 1, // 198: hyapp.activity.v1.ActivityService.PingActivity:input_type -> hyapp.activity.v1.PingActivityRequest + 3, // 199: hyapp.activity.v1.ActivityService.GetActivityStatus:input_type -> hyapp.activity.v1.GetActivityStatusRequest + 6, // 200: hyapp.activity.v1.MessageInboxService.ListMessageTabs:input_type -> hyapp.activity.v1.ListMessageTabsRequest + 9, // 201: hyapp.activity.v1.MessageInboxService.ListInboxMessages:input_type -> hyapp.activity.v1.ListInboxMessagesRequest + 11, // 202: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:input_type -> hyapp.activity.v1.MarkInboxMessageReadRequest + 13, // 203: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:input_type -> hyapp.activity.v1.MarkInboxSectionReadRequest + 15, // 204: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:input_type -> hyapp.activity.v1.DeleteInboxMessageRequest + 17, // 205: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:input_type -> hyapp.activity.v1.CreateInboxMessageRequest + 19, // 206: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:input_type -> hyapp.activity.v1.CreateFanoutJobRequest + 21, // 207: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 208: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 209: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 210: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 211: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 25, // 212: hyapp.activity.v1.TaskService.ListUserTasks:input_type -> hyapp.activity.v1.ListUserTasksRequest + 27, // 213: hyapp.activity.v1.TaskService.ClaimTaskReward:input_type -> hyapp.activity.v1.ClaimTaskRewardRequest + 29, // 214: hyapp.activity.v1.TaskService.ConsumeTaskEvent:input_type -> hyapp.activity.v1.ConsumeTaskEventRequest + 141, // 215: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:input_type -> hyapp.activity.v1.GetMyLevelOverviewRequest + 143, // 216: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:input_type -> hyapp.activity.v1.GetLevelTrackRequest + 147, // 217: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:input_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest + 150, // 218: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:input_type -> hyapp.activity.v1.ListLevelRewardsRequest + 152, // 219: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:input_type -> hyapp.activity.v1.ConsumeLevelEventRequest + 154, // 220: hyapp.activity.v1.GrowthLevelService.SetUserLevel:input_type -> hyapp.activity.v1.SetUserLevelRequest + 157, // 221: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:input_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesRequest + 170, // 222: hyapp.activity.v1.AchievementService.ListAchievements:input_type -> hyapp.activity.v1.ListAchievementsRequest + 172, // 223: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:input_type -> hyapp.activity.v1.ConsumeAchievementEventRequest + 175, // 224: hyapp.activity.v1.AchievementService.ListMyBadges:input_type -> hyapp.activity.v1.ListMyBadgesRequest + 177, // 225: hyapp.activity.v1.AchievementService.SetBadgeDisplay:input_type -> hyapp.activity.v1.SetBadgeDisplayRequest + 187, // 226: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.activity.v1.CheckLuckyGiftRequest + 190, // 227: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawRequest + 113, // 228: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusRequest + 220, // 229: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:input_type -> hyapp.activity.v1.GetWeeklyStarCurrentRequest + 216, // 230: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest + 222, // 231: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:input_type -> hyapp.activity.v1.ListWeeklyStarHistoryRequest + 32, // 232: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:input_type -> hyapp.activity.v1.EnsureBroadcastGroupsRequest + 34, // 233: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:input_type -> hyapp.activity.v1.PublishRegionBroadcastRequest + 35, // 234: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:input_type -> hyapp.activity.v1.PublishGlobalBroadcastRequest + 37, // 235: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:input_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberRequest + 21, // 236: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 40, // 237: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:input_type -> hyapp.activity.v1.ConsumeRoomEventRequest + 43, // 238: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:input_type -> hyapp.activity.v1.ListTaskDefinitionsRequest + 45, // 239: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:input_type -> hyapp.activity.v1.UpsertTaskDefinitionRequest + 47, // 240: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:input_type -> hyapp.activity.v1.SetTaskDefinitionStatusRequest + 52, // 241: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:input_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityRequest + 54, // 242: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:input_type -> hyapp.activity.v1.IssueRegistrationRewardRequest + 56, // 243: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:input_type -> hyapp.activity.v1.GetRegistrationRewardConfigRequest + 58, // 244: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:input_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigRequest + 60, // 245: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:input_type -> hyapp.activity.v1.ListRegistrationRewardClaimsRequest + 66, // 246: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:input_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusRequest + 68, // 247: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:input_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardRequest + 70, // 248: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigRequest + 72, // 249: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest + 74, // 250: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:input_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest + 81, // 251: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusRequest + 83, // 252: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:input_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardRequest + 96, // 253: hyapp.activity.v1.InviteActivityRewardService.GetInviteActivityRewardStatus:input_type -> hyapp.activity.v1.GetInviteActivityRewardStatusRequest + 98, // 254: hyapp.activity.v1.InviteActivityRewardService.ClaimInviteActivityReward:input_type -> hyapp.activity.v1.ClaimInviteActivityRewardRequest + 101, // 255: hyapp.activity.v1.InviteActivityRewardService.ListInviteActivityLeaderboard:input_type -> hyapp.activity.v1.ListInviteActivityLeaderboardRequest + 85, // 256: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigRequest + 87, // 257: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigRequest + 89, // 258: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:input_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsRequest + 103, // 259: hyapp.activity.v1.AdminInviteActivityRewardService.GetInviteActivityRewardConfig:input_type -> hyapp.activity.v1.GetInviteActivityRewardConfigRequest + 105, // 260: hyapp.activity.v1.AdminInviteActivityRewardService.UpdateInviteActivityRewardConfig:input_type -> hyapp.activity.v1.UpdateInviteActivityRewardConfigRequest + 107, // 261: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardClaims:input_type -> hyapp.activity.v1.ListInviteActivityRewardClaimsRequest + 115, // 262: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigRequest + 117, // 263: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:input_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigRequest + 119, // 264: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:input_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsRequest + 121, // 265: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:input_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementRequest + 208, // 266: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:input_type -> hyapp.activity.v1.ListWeeklyStarCyclesRequest + 212, // 267: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest + 210, // 268: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:input_type -> hyapp.activity.v1.GetWeeklyStarCycleRequest + 212, // 269: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:input_type -> hyapp.activity.v1.UpsertWeeklyStarCycleRequest + 214, // 270: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:input_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusRequest + 216, // 271: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:input_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardRequest + 218, // 272: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:input_type -> hyapp.activity.v1.ListWeeklyStarSettlementsRequest + 126, // 273: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:input_type -> hyapp.activity.v1.GetSevenDayCheckInStatusRequest + 128, // 274: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:input_type -> hyapp.activity.v1.SignSevenDayCheckInRequest + 130, // 275: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:input_type -> hyapp.activity.v1.GetSevenDayCheckInConfigRequest + 132, // 276: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:input_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest + 135, // 277: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:input_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsRequest + 165, // 278: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:input_type -> hyapp.activity.v1.ListLevelConfigRequest + 159, // 279: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:input_type -> hyapp.activity.v1.UpsertLevelTrackRequest + 161, // 280: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:input_type -> hyapp.activity.v1.UpsertLevelRuleRequest + 163, // 281: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:input_type -> hyapp.activity.v1.UpsertLevelTierRequest + 170, // 282: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:input_type -> hyapp.activity.v1.ListAchievementsRequest + 179, // 283: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:input_type -> hyapp.activity.v1.UpsertAchievementDefinitionRequest + 192, // 284: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.activity.v1.GetLuckyGiftConfigRequest + 194, // 285: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.activity.v1.UpsertLuckyGiftConfigRequest + 196, // 286: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.activity.v1.ListLuckyGiftConfigsRequest + 198, // 287: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.activity.v1.ListLuckyGiftDrawsRequest + 201, // 288: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest + 2, // 289: hyapp.activity.v1.ActivityService.PingActivity:output_type -> hyapp.activity.v1.PingActivityResponse + 4, // 290: hyapp.activity.v1.ActivityService.GetActivityStatus:output_type -> hyapp.activity.v1.GetActivityStatusResponse + 7, // 291: hyapp.activity.v1.MessageInboxService.ListMessageTabs:output_type -> hyapp.activity.v1.ListMessageTabsResponse + 10, // 292: hyapp.activity.v1.MessageInboxService.ListInboxMessages:output_type -> hyapp.activity.v1.ListInboxMessagesResponse + 12, // 293: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:output_type -> hyapp.activity.v1.MarkInboxMessageReadResponse + 14, // 294: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:output_type -> hyapp.activity.v1.MarkInboxSectionReadResponse + 16, // 295: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:output_type -> hyapp.activity.v1.DeleteInboxMessageResponse + 18, // 296: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:output_type -> hyapp.activity.v1.CreateInboxMessageResponse + 20, // 297: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:output_type -> hyapp.activity.v1.CreateFanoutJobResponse + 22, // 298: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 299: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 300: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 301: hyapp.activity.v1.ActivityCronService.ProcessRoomTurnoverRewardSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 302: hyapp.activity.v1.ActivityCronService.ProcessWeeklyStarSettlementBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 26, // 303: hyapp.activity.v1.TaskService.ListUserTasks:output_type -> hyapp.activity.v1.ListUserTasksResponse + 28, // 304: hyapp.activity.v1.TaskService.ClaimTaskReward:output_type -> hyapp.activity.v1.ClaimTaskRewardResponse + 30, // 305: hyapp.activity.v1.TaskService.ConsumeTaskEvent:output_type -> hyapp.activity.v1.ConsumeTaskEventResponse + 142, // 306: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:output_type -> hyapp.activity.v1.GetMyLevelOverviewResponse + 144, // 307: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:output_type -> hyapp.activity.v1.GetLevelTrackResponse + 148, // 308: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:output_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse + 151, // 309: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:output_type -> hyapp.activity.v1.ListLevelRewardsResponse + 153, // 310: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:output_type -> hyapp.activity.v1.ConsumeLevelEventResponse + 155, // 311: hyapp.activity.v1.GrowthLevelService.SetUserLevel:output_type -> hyapp.activity.v1.SetUserLevelResponse + 158, // 312: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:output_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesResponse + 171, // 313: hyapp.activity.v1.AchievementService.ListAchievements:output_type -> hyapp.activity.v1.ListAchievementsResponse + 173, // 314: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:output_type -> hyapp.activity.v1.ConsumeAchievementEventResponse + 176, // 315: hyapp.activity.v1.AchievementService.ListMyBadges:output_type -> hyapp.activity.v1.ListMyBadgesResponse + 178, // 316: hyapp.activity.v1.AchievementService.SetBadgeDisplay:output_type -> hyapp.activity.v1.SetBadgeDisplayResponse + 188, // 317: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.activity.v1.CheckLuckyGiftResponse + 191, // 318: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawResponse + 114, // 319: hyapp.activity.v1.RoomTurnoverRewardService.GetRoomTurnoverRewardStatus:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardStatusResponse + 221, // 320: hyapp.activity.v1.WeeklyStarService.GetWeeklyStarCurrent:output_type -> hyapp.activity.v1.GetWeeklyStarCurrentResponse + 217, // 321: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse + 224, // 322: hyapp.activity.v1.WeeklyStarService.ListWeeklyStarHistory:output_type -> hyapp.activity.v1.ListWeeklyStarHistoryResponse + 33, // 323: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:output_type -> hyapp.activity.v1.EnsureBroadcastGroupsResponse + 36, // 324: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse + 36, // 325: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse + 38, // 326: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:output_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberResponse + 39, // 327: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:output_type -> hyapp.activity.v1.ProcessBroadcastOutboxBatchResponse + 41, // 328: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:output_type -> hyapp.activity.v1.ConsumeRoomEventResponse + 44, // 329: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:output_type -> hyapp.activity.v1.ListTaskDefinitionsResponse + 46, // 330: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:output_type -> hyapp.activity.v1.UpsertTaskDefinitionResponse + 48, // 331: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:output_type -> hyapp.activity.v1.SetTaskDefinitionStatusResponse + 53, // 332: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:output_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityResponse + 55, // 333: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:output_type -> hyapp.activity.v1.IssueRegistrationRewardResponse + 57, // 334: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:output_type -> hyapp.activity.v1.GetRegistrationRewardConfigResponse + 59, // 335: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:output_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigResponse + 61, // 336: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:output_type -> hyapp.activity.v1.ListRegistrationRewardClaimsResponse + 67, // 337: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:output_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusResponse + 69, // 338: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:output_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardResponse + 71, // 339: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigResponse + 73, // 340: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse + 75, // 341: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:output_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse + 82, // 342: hyapp.activity.v1.CumulativeRechargeRewardService.GetCumulativeRechargeRewardStatus:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardStatusResponse + 84, // 343: hyapp.activity.v1.CumulativeRechargeRewardService.ConsumeCumulativeRechargeReward:output_type -> hyapp.activity.v1.ConsumeCumulativeRechargeRewardResponse + 97, // 344: hyapp.activity.v1.InviteActivityRewardService.GetInviteActivityRewardStatus:output_type -> hyapp.activity.v1.GetInviteActivityRewardStatusResponse + 99, // 345: hyapp.activity.v1.InviteActivityRewardService.ClaimInviteActivityReward:output_type -> hyapp.activity.v1.ClaimInviteActivityRewardResponse + 102, // 346: hyapp.activity.v1.InviteActivityRewardService.ListInviteActivityLeaderboard:output_type -> hyapp.activity.v1.ListInviteActivityLeaderboardResponse + 86, // 347: hyapp.activity.v1.AdminCumulativeRechargeRewardService.GetCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.GetCumulativeRechargeRewardConfigResponse + 88, // 348: hyapp.activity.v1.AdminCumulativeRechargeRewardService.UpdateCumulativeRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateCumulativeRechargeRewardConfigResponse + 90, // 349: hyapp.activity.v1.AdminCumulativeRechargeRewardService.ListCumulativeRechargeRewardGrants:output_type -> hyapp.activity.v1.ListCumulativeRechargeRewardGrantsResponse + 104, // 350: hyapp.activity.v1.AdminInviteActivityRewardService.GetInviteActivityRewardConfig:output_type -> hyapp.activity.v1.GetInviteActivityRewardConfigResponse + 106, // 351: hyapp.activity.v1.AdminInviteActivityRewardService.UpdateInviteActivityRewardConfig:output_type -> hyapp.activity.v1.UpdateInviteActivityRewardConfigResponse + 108, // 352: hyapp.activity.v1.AdminInviteActivityRewardService.ListInviteActivityRewardClaims:output_type -> hyapp.activity.v1.ListInviteActivityRewardClaimsResponse + 116, // 353: hyapp.activity.v1.AdminRoomTurnoverRewardService.GetRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.GetRoomTurnoverRewardConfigResponse + 118, // 354: hyapp.activity.v1.AdminRoomTurnoverRewardService.UpdateRoomTurnoverRewardConfig:output_type -> hyapp.activity.v1.UpdateRoomTurnoverRewardConfigResponse + 120, // 355: hyapp.activity.v1.AdminRoomTurnoverRewardService.ListRoomTurnoverRewardSettlements:output_type -> hyapp.activity.v1.ListRoomTurnoverRewardSettlementsResponse + 122, // 356: hyapp.activity.v1.AdminRoomTurnoverRewardService.RetryRoomTurnoverRewardSettlement:output_type -> hyapp.activity.v1.RetryRoomTurnoverRewardSettlementResponse + 209, // 357: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarCycles:output_type -> hyapp.activity.v1.ListWeeklyStarCyclesResponse + 213, // 358: hyapp.activity.v1.AdminWeeklyStarService.CreateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse + 211, // 359: hyapp.activity.v1.AdminWeeklyStarService.GetWeeklyStarCycle:output_type -> hyapp.activity.v1.GetWeeklyStarCycleResponse + 213, // 360: hyapp.activity.v1.AdminWeeklyStarService.UpdateWeeklyStarCycle:output_type -> hyapp.activity.v1.UpsertWeeklyStarCycleResponse + 215, // 361: hyapp.activity.v1.AdminWeeklyStarService.SetWeeklyStarCycleStatus:output_type -> hyapp.activity.v1.SetWeeklyStarCycleStatusResponse + 217, // 362: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarLeaderboard:output_type -> hyapp.activity.v1.ListWeeklyStarLeaderboardResponse + 219, // 363: hyapp.activity.v1.AdminWeeklyStarService.ListWeeklyStarSettlements:output_type -> hyapp.activity.v1.ListWeeklyStarSettlementsResponse + 127, // 364: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:output_type -> hyapp.activity.v1.GetSevenDayCheckInStatusResponse + 129, // 365: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:output_type -> hyapp.activity.v1.SignSevenDayCheckInResponse + 131, // 366: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:output_type -> hyapp.activity.v1.GetSevenDayCheckInConfigResponse + 133, // 367: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:output_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse + 136, // 368: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:output_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsResponse + 166, // 369: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:output_type -> hyapp.activity.v1.ListLevelConfigResponse + 160, // 370: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:output_type -> hyapp.activity.v1.UpsertLevelTrackResponse + 162, // 371: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:output_type -> hyapp.activity.v1.UpsertLevelRuleResponse + 164, // 372: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:output_type -> hyapp.activity.v1.UpsertLevelTierResponse + 171, // 373: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:output_type -> hyapp.activity.v1.ListAchievementsResponse + 180, // 374: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:output_type -> hyapp.activity.v1.UpsertAchievementDefinitionResponse + 193, // 375: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.activity.v1.GetLuckyGiftConfigResponse + 195, // 376: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.activity.v1.UpsertLuckyGiftConfigResponse + 197, // 377: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.activity.v1.ListLuckyGiftConfigsResponse + 199, // 378: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.activity.v1.ListLuckyGiftDrawsResponse + 202, // 379: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse + 289, // [289:380] is the sub-list for method output_type + 198, // [198:289] is the sub-list for method input_type + 198, // [198:198] is the sub-list for extension type_name + 198, // [198:198] is the sub-list for extension extendee + 0, // [0:198] is the sub-list for field type_name } func init() { file_proto_activity_v1_activity_proto_init() } @@ -18966,9 +20504,9 @@ func file_proto_activity_v1_activity_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_activity_v1_activity_proto_rawDesc), len(file_proto_activity_v1_activity_proto_rawDesc)), NumEnums: 0, - NumMessages: 207, + NumMessages: 225, NumExtensions: 0, - NumServices: 25, + NumServices: 27, }, GoTypes: file_proto_activity_v1_activity_proto_goTypes, DependencyIndexes: file_proto_activity_v1_activity_proto_depIdxs, diff --git a/api/proto/activity/v1/activity.proto b/api/proto/activity/v1/activity.proto index 534051ee..735afeed 100644 --- a/api/proto/activity/v1/activity.proto +++ b/api/proto/activity/v1/activity.proto @@ -816,6 +816,155 @@ message ListCumulativeRechargeRewardGrantsResponse { int64 total = 2; } +// InviteActivityRewardTier 是邀请活动的金币奖励档位;recharge 使用充值门槛,valid_invite 使用有效邀请人数门槛。 +message InviteActivityRewardTier { + int64 tier_id = 1; + string reward_type = 2; + string tier_code = 3; + string tier_name = 4; + int64 threshold_coin_amount = 5; + int64 threshold_valid_invite_count = 6; + int64 reward_coin_amount = 7; + string status = 8; + int32 sort_order = 9; + int64 created_at_ms = 10; + int64 updated_at_ms = 11; +} + +// InviteActivityRewardConfig 是当前 App 的邀请活动配置;关闭时保留档位但 App 不允许领取。 +message InviteActivityRewardConfig { + string app_code = 1; + bool enabled = 2; + repeated InviteActivityRewardTier tiers = 3; + int64 updated_by_admin_id = 4; + int64 created_at_ms = 5; + int64 updated_at_ms = 6; +} + +// InviteActivityRewardProgress 是邀请人在 UTC 自然月内的充值和有效邀请累计。 +message InviteActivityRewardProgress { + string app_code = 1; + string cycle_key = 2; + int64 user_id = 3; + int64 total_recharge_coin_amount = 4; + int64 valid_invite_count = 5; + int64 claimed_reward_coin_amount = 6; + int64 updated_at_ms = 7; +} + +// InviteActivityRewardClaim 是用户点击领取后创建的钱包发奖事实。 +message InviteActivityRewardClaim { + string claim_id = 1; + string app_code = 2; + string cycle_key = 3; + int64 user_id = 4; + string reward_type = 5; + string command_id = 6; + int64 tier_id = 7; + string tier_code = 8; + string tier_name = 9; + int64 threshold_coin_amount = 10; + int64 threshold_valid_invite_count = 11; + int64 reached_value = 12; + int64 reward_coin_amount = 13; + string status = 14; + string wallet_command_id = 15; + string wallet_transaction_id = 16; + string failure_reason = 17; + int64 granted_at_ms = 18; + int64 created_at_ms = 19; + int64 updated_at_ms = 20; +} + +// InviteActivityRewardStatus 是 App 邀请奖励页可直接展示的当前周期状态。 +message InviteActivityRewardStatus { + InviteActivityRewardConfig config = 1; + InviteActivityRewardProgress progress = 2; + repeated InviteActivityRewardClaim claims = 3; + string cycle_key = 4; + int64 period_start_ms = 5; + int64 period_end_ms = 6; + int64 server_time_ms = 7; +} + +message GetInviteActivityRewardStatusRequest { + RequestMeta meta = 1; + int64 user_id = 2; +} + +message GetInviteActivityRewardStatusResponse { + InviteActivityRewardStatus status = 1; +} + +message ClaimInviteActivityRewardRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string reward_type = 3; + int64 tier_id = 4; + string command_id = 5; +} + +message ClaimInviteActivityRewardResponse { + InviteActivityRewardClaim claim = 1; + InviteActivityRewardStatus status = 2; +} + +// InviteActivityLeaderboardEntry 是邀请活动月榜的一行;用户资料由 gateway 出口补齐。 +message InviteActivityLeaderboardEntry { + int32 rank_no = 1; + int64 user_id = 2; + int64 valid_invite_count = 3; + int64 total_recharge_coin_amount = 4; +} + +message ListInviteActivityLeaderboardRequest { + RequestMeta meta = 1; + string cycle_key = 2; + int32 page = 3; + int32 page_size = 4; +} + +message ListInviteActivityLeaderboardResponse { + repeated InviteActivityLeaderboardEntry entries = 1; + int64 total = 2; + string cycle_key = 3; + int64 server_time_ms = 4; +} + +message GetInviteActivityRewardConfigRequest { + RequestMeta meta = 1; +} + +message GetInviteActivityRewardConfigResponse { + InviteActivityRewardConfig config = 1; +} + +message UpdateInviteActivityRewardConfigRequest { + RequestMeta meta = 1; + bool enabled = 2; + repeated InviteActivityRewardTier tiers = 3; + int64 operator_admin_id = 4; +} + +message UpdateInviteActivityRewardConfigResponse { + InviteActivityRewardConfig config = 1; +} + +message ListInviteActivityRewardClaimsRequest { + RequestMeta meta = 1; + string status = 2; + string reward_type = 3; + int64 user_id = 4; + string cycle_key = 5; + int32 page = 6; + int32 page_size = 7; +} + +message ListInviteActivityRewardClaimsResponse { + repeated InviteActivityRewardClaim claims = 1; + int64 total = 2; +} + // RoomTurnoverRewardTier 是房间流水奖励的单个档位;达到 threshold_coin_spent 后可命中奖励。 message RoomTurnoverRewardTier { int64 tier_id = 1; @@ -1979,6 +2128,13 @@ service CumulativeRechargeRewardService { rpc ConsumeCumulativeRechargeReward(ConsumeCumulativeRechargeRewardRequest) returns (ConsumeCumulativeRechargeRewardResponse); } +// InviteActivityRewardService 拥有 App 邀请活动状态、领奖和排行榜入口。 +service InviteActivityRewardService { + rpc GetInviteActivityRewardStatus(GetInviteActivityRewardStatusRequest) returns (GetInviteActivityRewardStatusResponse); + rpc ClaimInviteActivityReward(ClaimInviteActivityRewardRequest) returns (ClaimInviteActivityRewardResponse); + rpc ListInviteActivityLeaderboard(ListInviteActivityLeaderboardRequest) returns (ListInviteActivityLeaderboardResponse); +} + // AdminCumulativeRechargeRewardService 是后台活动管理访问累充奖励配置和发放记录的唯一入口。 service AdminCumulativeRechargeRewardService { rpc GetCumulativeRechargeRewardConfig(GetCumulativeRechargeRewardConfigRequest) returns (GetCumulativeRechargeRewardConfigResponse); @@ -1986,6 +2142,13 @@ service AdminCumulativeRechargeRewardService { rpc ListCumulativeRechargeRewardGrants(ListCumulativeRechargeRewardGrantsRequest) returns (ListCumulativeRechargeRewardGrantsResponse); } +// AdminInviteActivityRewardService 是后台活动管理访问邀请活动配置和领取记录的唯一入口。 +service AdminInviteActivityRewardService { + rpc GetInviteActivityRewardConfig(GetInviteActivityRewardConfigRequest) returns (GetInviteActivityRewardConfigResponse); + rpc UpdateInviteActivityRewardConfig(UpdateInviteActivityRewardConfigRequest) returns (UpdateInviteActivityRewardConfigResponse); + rpc ListInviteActivityRewardClaims(ListInviteActivityRewardClaimsRequest) returns (ListInviteActivityRewardClaimsResponse); +} + // AdminRoomTurnoverRewardService 是后台活动管理访问房间流水奖励配置和结算记录的唯一入口。 service AdminRoomTurnoverRewardService { rpc GetRoomTurnoverRewardConfig(GetRoomTurnoverRewardConfigRequest) returns (GetRoomTurnoverRewardConfigResponse); diff --git a/api/proto/activity/v1/activity_grpc.pb.go b/api/proto/activity/v1/activity_grpc.pb.go index dbe828e9..ecd5f6bf 100644 --- a/api/proto/activity/v1/activity_grpc.pb.go +++ b/api/proto/activity/v1/activity_grpc.pb.go @@ -3271,6 +3271,189 @@ var CumulativeRechargeRewardService_ServiceDesc = grpc.ServiceDesc{ Metadata: "proto/activity/v1/activity.proto", } +const ( + InviteActivityRewardService_GetInviteActivityRewardStatus_FullMethodName = "/hyapp.activity.v1.InviteActivityRewardService/GetInviteActivityRewardStatus" + InviteActivityRewardService_ClaimInviteActivityReward_FullMethodName = "/hyapp.activity.v1.InviteActivityRewardService/ClaimInviteActivityReward" + InviteActivityRewardService_ListInviteActivityLeaderboard_FullMethodName = "/hyapp.activity.v1.InviteActivityRewardService/ListInviteActivityLeaderboard" +) + +// InviteActivityRewardServiceClient is the client API for InviteActivityRewardService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// InviteActivityRewardService 拥有 App 邀请活动状态、领奖和排行榜入口。 +type InviteActivityRewardServiceClient interface { + GetInviteActivityRewardStatus(ctx context.Context, in *GetInviteActivityRewardStatusRequest, opts ...grpc.CallOption) (*GetInviteActivityRewardStatusResponse, error) + ClaimInviteActivityReward(ctx context.Context, in *ClaimInviteActivityRewardRequest, opts ...grpc.CallOption) (*ClaimInviteActivityRewardResponse, error) + ListInviteActivityLeaderboard(ctx context.Context, in *ListInviteActivityLeaderboardRequest, opts ...grpc.CallOption) (*ListInviteActivityLeaderboardResponse, error) +} + +type inviteActivityRewardServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewInviteActivityRewardServiceClient(cc grpc.ClientConnInterface) InviteActivityRewardServiceClient { + return &inviteActivityRewardServiceClient{cc} +} + +func (c *inviteActivityRewardServiceClient) GetInviteActivityRewardStatus(ctx context.Context, in *GetInviteActivityRewardStatusRequest, opts ...grpc.CallOption) (*GetInviteActivityRewardStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetInviteActivityRewardStatusResponse) + err := c.cc.Invoke(ctx, InviteActivityRewardService_GetInviteActivityRewardStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inviteActivityRewardServiceClient) ClaimInviteActivityReward(ctx context.Context, in *ClaimInviteActivityRewardRequest, opts ...grpc.CallOption) (*ClaimInviteActivityRewardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ClaimInviteActivityRewardResponse) + err := c.cc.Invoke(ctx, InviteActivityRewardService_ClaimInviteActivityReward_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inviteActivityRewardServiceClient) ListInviteActivityLeaderboard(ctx context.Context, in *ListInviteActivityLeaderboardRequest, opts ...grpc.CallOption) (*ListInviteActivityLeaderboardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListInviteActivityLeaderboardResponse) + err := c.cc.Invoke(ctx, InviteActivityRewardService_ListInviteActivityLeaderboard_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// InviteActivityRewardServiceServer is the server API for InviteActivityRewardService service. +// All implementations must embed UnimplementedInviteActivityRewardServiceServer +// for forward compatibility. +// +// InviteActivityRewardService 拥有 App 邀请活动状态、领奖和排行榜入口。 +type InviteActivityRewardServiceServer interface { + GetInviteActivityRewardStatus(context.Context, *GetInviteActivityRewardStatusRequest) (*GetInviteActivityRewardStatusResponse, error) + ClaimInviteActivityReward(context.Context, *ClaimInviteActivityRewardRequest) (*ClaimInviteActivityRewardResponse, error) + ListInviteActivityLeaderboard(context.Context, *ListInviteActivityLeaderboardRequest) (*ListInviteActivityLeaderboardResponse, error) + mustEmbedUnimplementedInviteActivityRewardServiceServer() +} + +// UnimplementedInviteActivityRewardServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedInviteActivityRewardServiceServer struct{} + +func (UnimplementedInviteActivityRewardServiceServer) GetInviteActivityRewardStatus(context.Context, *GetInviteActivityRewardStatusRequest) (*GetInviteActivityRewardStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetInviteActivityRewardStatus not implemented") +} +func (UnimplementedInviteActivityRewardServiceServer) ClaimInviteActivityReward(context.Context, *ClaimInviteActivityRewardRequest) (*ClaimInviteActivityRewardResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ClaimInviteActivityReward not implemented") +} +func (UnimplementedInviteActivityRewardServiceServer) ListInviteActivityLeaderboard(context.Context, *ListInviteActivityLeaderboardRequest) (*ListInviteActivityLeaderboardResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListInviteActivityLeaderboard not implemented") +} +func (UnimplementedInviteActivityRewardServiceServer) mustEmbedUnimplementedInviteActivityRewardServiceServer() { +} +func (UnimplementedInviteActivityRewardServiceServer) testEmbeddedByValue() {} + +// UnsafeInviteActivityRewardServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to InviteActivityRewardServiceServer will +// result in compilation errors. +type UnsafeInviteActivityRewardServiceServer interface { + mustEmbedUnimplementedInviteActivityRewardServiceServer() +} + +func RegisterInviteActivityRewardServiceServer(s grpc.ServiceRegistrar, srv InviteActivityRewardServiceServer) { + // If the following call panics, it indicates UnimplementedInviteActivityRewardServiceServer 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. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&InviteActivityRewardService_ServiceDesc, srv) +} + +func _InviteActivityRewardService_GetInviteActivityRewardStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetInviteActivityRewardStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InviteActivityRewardServiceServer).GetInviteActivityRewardStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InviteActivityRewardService_GetInviteActivityRewardStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InviteActivityRewardServiceServer).GetInviteActivityRewardStatus(ctx, req.(*GetInviteActivityRewardStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InviteActivityRewardService_ClaimInviteActivityReward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClaimInviteActivityRewardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InviteActivityRewardServiceServer).ClaimInviteActivityReward(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InviteActivityRewardService_ClaimInviteActivityReward_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InviteActivityRewardServiceServer).ClaimInviteActivityReward(ctx, req.(*ClaimInviteActivityRewardRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _InviteActivityRewardService_ListInviteActivityLeaderboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListInviteActivityLeaderboardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InviteActivityRewardServiceServer).ListInviteActivityLeaderboard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InviteActivityRewardService_ListInviteActivityLeaderboard_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InviteActivityRewardServiceServer).ListInviteActivityLeaderboard(ctx, req.(*ListInviteActivityLeaderboardRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// InviteActivityRewardService_ServiceDesc is the grpc.ServiceDesc for InviteActivityRewardService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var InviteActivityRewardService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hyapp.activity.v1.InviteActivityRewardService", + HandlerType: (*InviteActivityRewardServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetInviteActivityRewardStatus", + Handler: _InviteActivityRewardService_GetInviteActivityRewardStatus_Handler, + }, + { + MethodName: "ClaimInviteActivityReward", + Handler: _InviteActivityRewardService_ClaimInviteActivityReward_Handler, + }, + { + MethodName: "ListInviteActivityLeaderboard", + Handler: _InviteActivityRewardService_ListInviteActivityLeaderboard_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/activity/v1/activity.proto", +} + const ( AdminCumulativeRechargeRewardService_GetCumulativeRechargeRewardConfig_FullMethodName = "/hyapp.activity.v1.AdminCumulativeRechargeRewardService/GetCumulativeRechargeRewardConfig" AdminCumulativeRechargeRewardService_UpdateCumulativeRechargeRewardConfig_FullMethodName = "/hyapp.activity.v1.AdminCumulativeRechargeRewardService/UpdateCumulativeRechargeRewardConfig" @@ -3454,6 +3637,189 @@ var AdminCumulativeRechargeRewardService_ServiceDesc = grpc.ServiceDesc{ Metadata: "proto/activity/v1/activity.proto", } +const ( + AdminInviteActivityRewardService_GetInviteActivityRewardConfig_FullMethodName = "/hyapp.activity.v1.AdminInviteActivityRewardService/GetInviteActivityRewardConfig" + AdminInviteActivityRewardService_UpdateInviteActivityRewardConfig_FullMethodName = "/hyapp.activity.v1.AdminInviteActivityRewardService/UpdateInviteActivityRewardConfig" + AdminInviteActivityRewardService_ListInviteActivityRewardClaims_FullMethodName = "/hyapp.activity.v1.AdminInviteActivityRewardService/ListInviteActivityRewardClaims" +) + +// AdminInviteActivityRewardServiceClient is the client API for AdminInviteActivityRewardService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// AdminInviteActivityRewardService 是后台活动管理访问邀请活动配置和领取记录的唯一入口。 +type AdminInviteActivityRewardServiceClient interface { + GetInviteActivityRewardConfig(ctx context.Context, in *GetInviteActivityRewardConfigRequest, opts ...grpc.CallOption) (*GetInviteActivityRewardConfigResponse, error) + UpdateInviteActivityRewardConfig(ctx context.Context, in *UpdateInviteActivityRewardConfigRequest, opts ...grpc.CallOption) (*UpdateInviteActivityRewardConfigResponse, error) + ListInviteActivityRewardClaims(ctx context.Context, in *ListInviteActivityRewardClaimsRequest, opts ...grpc.CallOption) (*ListInviteActivityRewardClaimsResponse, error) +} + +type adminInviteActivityRewardServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAdminInviteActivityRewardServiceClient(cc grpc.ClientConnInterface) AdminInviteActivityRewardServiceClient { + return &adminInviteActivityRewardServiceClient{cc} +} + +func (c *adminInviteActivityRewardServiceClient) GetInviteActivityRewardConfig(ctx context.Context, in *GetInviteActivityRewardConfigRequest, opts ...grpc.CallOption) (*GetInviteActivityRewardConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetInviteActivityRewardConfigResponse) + err := c.cc.Invoke(ctx, AdminInviteActivityRewardService_GetInviteActivityRewardConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminInviteActivityRewardServiceClient) UpdateInviteActivityRewardConfig(ctx context.Context, in *UpdateInviteActivityRewardConfigRequest, opts ...grpc.CallOption) (*UpdateInviteActivityRewardConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateInviteActivityRewardConfigResponse) + err := c.cc.Invoke(ctx, AdminInviteActivityRewardService_UpdateInviteActivityRewardConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminInviteActivityRewardServiceClient) ListInviteActivityRewardClaims(ctx context.Context, in *ListInviteActivityRewardClaimsRequest, opts ...grpc.CallOption) (*ListInviteActivityRewardClaimsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListInviteActivityRewardClaimsResponse) + err := c.cc.Invoke(ctx, AdminInviteActivityRewardService_ListInviteActivityRewardClaims_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AdminInviteActivityRewardServiceServer is the server API for AdminInviteActivityRewardService service. +// All implementations must embed UnimplementedAdminInviteActivityRewardServiceServer +// for forward compatibility. +// +// AdminInviteActivityRewardService 是后台活动管理访问邀请活动配置和领取记录的唯一入口。 +type AdminInviteActivityRewardServiceServer interface { + GetInviteActivityRewardConfig(context.Context, *GetInviteActivityRewardConfigRequest) (*GetInviteActivityRewardConfigResponse, error) + UpdateInviteActivityRewardConfig(context.Context, *UpdateInviteActivityRewardConfigRequest) (*UpdateInviteActivityRewardConfigResponse, error) + ListInviteActivityRewardClaims(context.Context, *ListInviteActivityRewardClaimsRequest) (*ListInviteActivityRewardClaimsResponse, error) + mustEmbedUnimplementedAdminInviteActivityRewardServiceServer() +} + +// UnimplementedAdminInviteActivityRewardServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAdminInviteActivityRewardServiceServer struct{} + +func (UnimplementedAdminInviteActivityRewardServiceServer) GetInviteActivityRewardConfig(context.Context, *GetInviteActivityRewardConfigRequest) (*GetInviteActivityRewardConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetInviteActivityRewardConfig not implemented") +} +func (UnimplementedAdminInviteActivityRewardServiceServer) UpdateInviteActivityRewardConfig(context.Context, *UpdateInviteActivityRewardConfigRequest) (*UpdateInviteActivityRewardConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateInviteActivityRewardConfig not implemented") +} +func (UnimplementedAdminInviteActivityRewardServiceServer) ListInviteActivityRewardClaims(context.Context, *ListInviteActivityRewardClaimsRequest) (*ListInviteActivityRewardClaimsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListInviteActivityRewardClaims not implemented") +} +func (UnimplementedAdminInviteActivityRewardServiceServer) mustEmbedUnimplementedAdminInviteActivityRewardServiceServer() { +} +func (UnimplementedAdminInviteActivityRewardServiceServer) testEmbeddedByValue() {} + +// UnsafeAdminInviteActivityRewardServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AdminInviteActivityRewardServiceServer will +// result in compilation errors. +type UnsafeAdminInviteActivityRewardServiceServer interface { + mustEmbedUnimplementedAdminInviteActivityRewardServiceServer() +} + +func RegisterAdminInviteActivityRewardServiceServer(s grpc.ServiceRegistrar, srv AdminInviteActivityRewardServiceServer) { + // If the following call panics, it indicates UnimplementedAdminInviteActivityRewardServiceServer 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. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AdminInviteActivityRewardService_ServiceDesc, srv) +} + +func _AdminInviteActivityRewardService_GetInviteActivityRewardConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetInviteActivityRewardConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminInviteActivityRewardServiceServer).GetInviteActivityRewardConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminInviteActivityRewardService_GetInviteActivityRewardConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminInviteActivityRewardServiceServer).GetInviteActivityRewardConfig(ctx, req.(*GetInviteActivityRewardConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminInviteActivityRewardService_UpdateInviteActivityRewardConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateInviteActivityRewardConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminInviteActivityRewardServiceServer).UpdateInviteActivityRewardConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminInviteActivityRewardService_UpdateInviteActivityRewardConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminInviteActivityRewardServiceServer).UpdateInviteActivityRewardConfig(ctx, req.(*UpdateInviteActivityRewardConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminInviteActivityRewardService_ListInviteActivityRewardClaims_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListInviteActivityRewardClaimsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminInviteActivityRewardServiceServer).ListInviteActivityRewardClaims(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminInviteActivityRewardService_ListInviteActivityRewardClaims_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminInviteActivityRewardServiceServer).ListInviteActivityRewardClaims(ctx, req.(*ListInviteActivityRewardClaimsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AdminInviteActivityRewardService_ServiceDesc is the grpc.ServiceDesc for AdminInviteActivityRewardService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AdminInviteActivityRewardService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hyapp.activity.v1.AdminInviteActivityRewardService", + HandlerType: (*AdminInviteActivityRewardServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetInviteActivityRewardConfig", + Handler: _AdminInviteActivityRewardService_GetInviteActivityRewardConfig_Handler, + }, + { + MethodName: "UpdateInviteActivityRewardConfig", + Handler: _AdminInviteActivityRewardService_UpdateInviteActivityRewardConfig_Handler, + }, + { + MethodName: "ListInviteActivityRewardClaims", + Handler: _AdminInviteActivityRewardService_ListInviteActivityRewardClaims_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/activity/v1/activity.proto", +} + const ( AdminRoomTurnoverRewardService_GetRoomTurnoverRewardConfig_FullMethodName = "/hyapp.activity.v1.AdminRoomTurnoverRewardService/GetRoomTurnoverRewardConfig" AdminRoomTurnoverRewardService_UpdateRoomTurnoverRewardConfig_FullMethodName = "/hyapp.activity.v1.AdminRoomTurnoverRewardService/UpdateRoomTurnoverRewardConfig" diff --git a/api/proto/game/v1/game.pb.go b/api/proto/game/v1/game.pb.go index aa00cc12..635a7818 100644 --- a/api/proto/game/v1/game.pb.go +++ b/api/proto/game/v1/game.pb.go @@ -811,6 +811,210 @@ func (x *ListRecentGamesRequest) GetPageSize() int32 { return 0 } +type ListExploreWinnersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListExploreWinnersRequest) Reset() { + *x = ListExploreWinnersRequest{} + mi := &file_proto_game_v1_game_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListExploreWinnersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListExploreWinnersRequest) ProtoMessage() {} + +func (x *ListExploreWinnersRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListExploreWinnersRequest.ProtoReflect.Descriptor instead. +func (*ListExploreWinnersRequest) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{7} +} + +func (x *ListExploreWinnersRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ListExploreWinnersRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ExploreWinner struct { + state protoimpl.MessageState `protogen:"open.v1"` + WinId string `protobuf:"bytes,1,opt,name=win_id,json=winId,proto3" json:"win_id,omitempty"` + GameCode string `protobuf:"bytes,2,opt,name=game_code,json=gameCode,proto3" json:"game_code,omitempty"` + GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + UserId int64 `protobuf:"varint,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + DisplayName string `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + AvatarUrl string `protobuf:"bytes,6,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty"` + CoinAmount int64 `protobuf:"varint,7,opt,name=coin_amount,json=coinAmount,proto3" json:"coin_amount,omitempty"` + WonAtMs int64 `protobuf:"varint,8,opt,name=won_at_ms,json=wonAtMs,proto3" json:"won_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExploreWinner) Reset() { + *x = ExploreWinner{} + mi := &file_proto_game_v1_game_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExploreWinner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExploreWinner) ProtoMessage() {} + +func (x *ExploreWinner) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExploreWinner.ProtoReflect.Descriptor instead. +func (*ExploreWinner) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{8} +} + +func (x *ExploreWinner) GetWinId() string { + if x != nil { + return x.WinId + } + return "" +} + +func (x *ExploreWinner) GetGameCode() string { + if x != nil { + return x.GameCode + } + return "" +} + +func (x *ExploreWinner) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *ExploreWinner) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ExploreWinner) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *ExploreWinner) GetAvatarUrl() string { + if x != nil { + return x.AvatarUrl + } + return "" +} + +func (x *ExploreWinner) GetCoinAmount() int64 { + if x != nil { + return x.CoinAmount + } + return 0 +} + +func (x *ExploreWinner) GetWonAtMs() int64 { + if x != nil { + return x.WonAtMs + } + return 0 +} + +type ListExploreWinnersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Winners []*ExploreWinner `protobuf:"bytes,1,rep,name=winners,proto3" json:"winners,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListExploreWinnersResponse) Reset() { + *x = ListExploreWinnersResponse{} + mi := &file_proto_game_v1_game_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListExploreWinnersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListExploreWinnersResponse) ProtoMessage() {} + +func (x *ListExploreWinnersResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_v1_game_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListExploreWinnersResponse.ProtoReflect.Descriptor instead. +func (*ListExploreWinnersResponse) Descriptor() ([]byte, []int) { + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{9} +} + +func (x *ListExploreWinnersResponse) GetWinners() []*ExploreWinner { + if x != nil { + return x.Winners + } + return nil +} + +func (x *ListExploreWinnersResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + type GetBridgeScriptRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -820,7 +1024,7 @@ type GetBridgeScriptRequest struct { func (x *GetBridgeScriptRequest) Reset() { *x = GetBridgeScriptRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[7] + mi := &file_proto_game_v1_game_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -832,7 +1036,7 @@ func (x *GetBridgeScriptRequest) String() string { func (*GetBridgeScriptRequest) ProtoMessage() {} func (x *GetBridgeScriptRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[7] + mi := &file_proto_game_v1_game_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -845,7 +1049,7 @@ func (x *GetBridgeScriptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBridgeScriptRequest.ProtoReflect.Descriptor instead. func (*GetBridgeScriptRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{7} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{10} } func (x *GetBridgeScriptRequest) GetMeta() *RequestMeta { @@ -866,7 +1070,7 @@ type GameBridgeScriptConfig struct { func (x *GameBridgeScriptConfig) Reset() { *x = GameBridgeScriptConfig{} - mi := &file_proto_game_v1_game_proto_msgTypes[8] + mi := &file_proto_game_v1_game_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -878,7 +1082,7 @@ func (x *GameBridgeScriptConfig) String() string { func (*GameBridgeScriptConfig) ProtoMessage() {} func (x *GameBridgeScriptConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[8] + mi := &file_proto_game_v1_game_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -891,7 +1095,7 @@ func (x *GameBridgeScriptConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GameBridgeScriptConfig.ProtoReflect.Descriptor instead. func (*GameBridgeScriptConfig) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{8} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{11} } func (x *GameBridgeScriptConfig) GetBridgeScriptUrl() string { @@ -925,7 +1129,7 @@ type GetBridgeScriptResponse struct { func (x *GetBridgeScriptResponse) Reset() { *x = GetBridgeScriptResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[9] + mi := &file_proto_game_v1_game_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -937,7 +1141,7 @@ func (x *GetBridgeScriptResponse) String() string { func (*GetBridgeScriptResponse) ProtoMessage() {} func (x *GetBridgeScriptResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[9] + mi := &file_proto_game_v1_game_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -950,7 +1154,7 @@ func (x *GetBridgeScriptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBridgeScriptResponse.ProtoReflect.Descriptor instead. func (*GetBridgeScriptResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{9} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{12} } func (x *GetBridgeScriptResponse) GetConfig() *GameBridgeScriptConfig { @@ -984,7 +1188,7 @@ type LaunchGameRequest struct { func (x *LaunchGameRequest) Reset() { *x = LaunchGameRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[10] + mi := &file_proto_game_v1_game_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -996,7 +1200,7 @@ func (x *LaunchGameRequest) String() string { func (*LaunchGameRequest) ProtoMessage() {} func (x *LaunchGameRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[10] + mi := &file_proto_game_v1_game_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1009,7 +1213,7 @@ func (x *LaunchGameRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LaunchGameRequest.ProtoReflect.Descriptor instead. func (*LaunchGameRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{10} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{13} } func (x *LaunchGameRequest) GetMeta() *RequestMeta { @@ -1075,7 +1279,7 @@ type LaunchGameResponse struct { func (x *LaunchGameResponse) Reset() { *x = LaunchGameResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[11] + mi := &file_proto_game_v1_game_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1087,7 +1291,7 @@ func (x *LaunchGameResponse) String() string { func (*LaunchGameResponse) ProtoMessage() {} func (x *LaunchGameResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[11] + mi := &file_proto_game_v1_game_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1100,7 +1304,7 @@ func (x *LaunchGameResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LaunchGameResponse.ProtoReflect.Descriptor instead. func (*LaunchGameResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{11} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{14} } func (x *LaunchGameResponse) GetSessionId() string { @@ -1165,7 +1369,7 @@ type DiceParticipant struct { func (x *DiceParticipant) Reset() { *x = DiceParticipant{} - mi := &file_proto_game_v1_game_proto_msgTypes[12] + mi := &file_proto_game_v1_game_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1177,7 +1381,7 @@ func (x *DiceParticipant) String() string { func (*DiceParticipant) ProtoMessage() {} func (x *DiceParticipant) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[12] + mi := &file_proto_game_v1_game_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1190,7 +1394,7 @@ func (x *DiceParticipant) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceParticipant.ProtoReflect.Descriptor instead. func (*DiceParticipant) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{12} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{15} } func (x *DiceParticipant) GetUserId() int64 { @@ -1311,7 +1515,7 @@ type DiceMatch struct { func (x *DiceMatch) Reset() { *x = DiceMatch{} - mi := &file_proto_game_v1_game_proto_msgTypes[13] + mi := &file_proto_game_v1_game_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1323,7 +1527,7 @@ func (x *DiceMatch) String() string { func (*DiceMatch) ProtoMessage() {} func (x *DiceMatch) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[13] + mi := &file_proto_game_v1_game_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1336,7 +1540,7 @@ func (x *DiceMatch) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceMatch.ProtoReflect.Descriptor instead. func (*DiceMatch) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{13} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{16} } func (x *DiceMatch) GetAppCode() string { @@ -1532,7 +1736,7 @@ type DiceStakeOption struct { func (x *DiceStakeOption) Reset() { *x = DiceStakeOption{} - mi := &file_proto_game_v1_game_proto_msgTypes[14] + mi := &file_proto_game_v1_game_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1544,7 +1748,7 @@ func (x *DiceStakeOption) String() string { func (*DiceStakeOption) ProtoMessage() {} func (x *DiceStakeOption) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[14] + mi := &file_proto_game_v1_game_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1557,7 +1761,7 @@ func (x *DiceStakeOption) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceStakeOption.ProtoReflect.Descriptor instead. func (*DiceStakeOption) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{14} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{17} } func (x *DiceStakeOption) GetStakeCoin() int64 { @@ -1602,7 +1806,7 @@ type DiceConfig struct { func (x *DiceConfig) Reset() { *x = DiceConfig{} - mi := &file_proto_game_v1_game_proto_msgTypes[15] + mi := &file_proto_game_v1_game_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1614,7 +1818,7 @@ func (x *DiceConfig) String() string { func (*DiceConfig) ProtoMessage() {} func (x *DiceConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[15] + mi := &file_proto_game_v1_game_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1627,7 +1831,7 @@ func (x *DiceConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceConfig.ProtoReflect.Descriptor instead. func (*DiceConfig) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{15} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{18} } func (x *DiceConfig) GetAppCode() string { @@ -1731,7 +1935,7 @@ type GetDiceConfigRequest struct { func (x *GetDiceConfigRequest) Reset() { *x = GetDiceConfigRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[16] + mi := &file_proto_game_v1_game_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1743,7 +1947,7 @@ func (x *GetDiceConfigRequest) String() string { func (*GetDiceConfigRequest) ProtoMessage() {} func (x *GetDiceConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[16] + mi := &file_proto_game_v1_game_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1756,7 +1960,7 @@ func (x *GetDiceConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiceConfigRequest.ProtoReflect.Descriptor instead. func (*GetDiceConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{16} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{19} } func (x *GetDiceConfigRequest) GetMeta() *RequestMeta { @@ -1783,7 +1987,7 @@ type DiceConfigResponse struct { func (x *DiceConfigResponse) Reset() { *x = DiceConfigResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[17] + mi := &file_proto_game_v1_game_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1795,7 +1999,7 @@ func (x *DiceConfigResponse) String() string { func (*DiceConfigResponse) ProtoMessage() {} func (x *DiceConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[17] + mi := &file_proto_game_v1_game_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1808,7 +2012,7 @@ func (x *DiceConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceConfigResponse.ProtoReflect.Descriptor instead. func (*DiceConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{17} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{20} } func (x *DiceConfigResponse) GetConfig() *DiceConfig { @@ -1839,7 +2043,7 @@ type RoomRPSStakeGift struct { func (x *RoomRPSStakeGift) Reset() { *x = RoomRPSStakeGift{} - mi := &file_proto_game_v1_game_proto_msgTypes[18] + mi := &file_proto_game_v1_game_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1851,7 +2055,7 @@ func (x *RoomRPSStakeGift) String() string { func (*RoomRPSStakeGift) ProtoMessage() {} func (x *RoomRPSStakeGift) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[18] + mi := &file_proto_game_v1_game_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1864,7 +2068,7 @@ func (x *RoomRPSStakeGift) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRPSStakeGift.ProtoReflect.Descriptor instead. func (*RoomRPSStakeGift) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{18} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{21} } func (x *RoomRPSStakeGift) GetGiftId() int64 { @@ -1925,7 +2129,7 @@ type RoomRPSConfig struct { func (x *RoomRPSConfig) Reset() { *x = RoomRPSConfig{} - mi := &file_proto_game_v1_game_proto_msgTypes[19] + mi := &file_proto_game_v1_game_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1937,7 +2141,7 @@ func (x *RoomRPSConfig) String() string { func (*RoomRPSConfig) ProtoMessage() {} func (x *RoomRPSConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[19] + mi := &file_proto_game_v1_game_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1950,7 +2154,7 @@ func (x *RoomRPSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRPSConfig.ProtoReflect.Descriptor instead. func (*RoomRPSConfig) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{19} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{22} } func (x *RoomRPSConfig) GetAppCode() string { @@ -2022,7 +2226,7 @@ type RoomRPSPlayer struct { func (x *RoomRPSPlayer) Reset() { *x = RoomRPSPlayer{} - mi := &file_proto_game_v1_game_proto_msgTypes[20] + mi := &file_proto_game_v1_game_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2034,7 +2238,7 @@ func (x *RoomRPSPlayer) String() string { func (*RoomRPSPlayer) ProtoMessage() {} func (x *RoomRPSPlayer) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[20] + mi := &file_proto_game_v1_game_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2047,7 +2251,7 @@ func (x *RoomRPSPlayer) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRPSPlayer.ProtoReflect.Descriptor instead. func (*RoomRPSPlayer) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{20} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{23} } func (x *RoomRPSPlayer) GetUserId() int64 { @@ -2111,7 +2315,7 @@ type RoomRPSChallenge struct { func (x *RoomRPSChallenge) Reset() { *x = RoomRPSChallenge{} - mi := &file_proto_game_v1_game_proto_msgTypes[21] + mi := &file_proto_game_v1_game_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2123,7 +2327,7 @@ func (x *RoomRPSChallenge) String() string { func (*RoomRPSChallenge) ProtoMessage() {} func (x *RoomRPSChallenge) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[21] + mi := &file_proto_game_v1_game_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2136,7 +2340,7 @@ func (x *RoomRPSChallenge) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRPSChallenge.ProtoReflect.Descriptor instead. func (*RoomRPSChallenge) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{21} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{24} } func (x *RoomRPSChallenge) GetAppCode() string { @@ -2274,7 +2478,7 @@ type GetRoomRPSConfigRequest struct { func (x *GetRoomRPSConfigRequest) Reset() { *x = GetRoomRPSConfigRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[22] + mi := &file_proto_game_v1_game_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2286,7 +2490,7 @@ func (x *GetRoomRPSConfigRequest) String() string { func (*GetRoomRPSConfigRequest) ProtoMessage() {} func (x *GetRoomRPSConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[22] + mi := &file_proto_game_v1_game_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2299,7 +2503,7 @@ func (x *GetRoomRPSConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomRPSConfigRequest.ProtoReflect.Descriptor instead. func (*GetRoomRPSConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{22} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{25} } func (x *GetRoomRPSConfigRequest) GetMeta() *RequestMeta { @@ -2319,7 +2523,7 @@ type RoomRPSConfigResponse struct { func (x *RoomRPSConfigResponse) Reset() { *x = RoomRPSConfigResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[23] + mi := &file_proto_game_v1_game_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2331,7 +2535,7 @@ func (x *RoomRPSConfigResponse) String() string { func (*RoomRPSConfigResponse) ProtoMessage() {} func (x *RoomRPSConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[23] + mi := &file_proto_game_v1_game_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2344,7 +2548,7 @@ func (x *RoomRPSConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRPSConfigResponse.ProtoReflect.Descriptor instead. func (*RoomRPSConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{23} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{26} } func (x *RoomRPSConfigResponse) GetConfig() *RoomRPSConfig { @@ -2375,7 +2579,7 @@ type CreateRoomRPSChallengeRequest struct { func (x *CreateRoomRPSChallengeRequest) Reset() { *x = CreateRoomRPSChallengeRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[24] + mi := &file_proto_game_v1_game_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2387,7 +2591,7 @@ func (x *CreateRoomRPSChallengeRequest) String() string { func (*CreateRoomRPSChallengeRequest) ProtoMessage() {} func (x *CreateRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[24] + mi := &file_proto_game_v1_game_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2400,7 +2604,7 @@ func (x *CreateRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRoomRPSChallengeRequest.ProtoReflect.Descriptor instead. func (*CreateRoomRPSChallengeRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{24} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{27} } func (x *CreateRoomRPSChallengeRequest) GetMeta() *RequestMeta { @@ -2457,7 +2661,7 @@ type AcceptRoomRPSChallengeRequest struct { func (x *AcceptRoomRPSChallengeRequest) Reset() { *x = AcceptRoomRPSChallengeRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[25] + mi := &file_proto_game_v1_game_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2469,7 +2673,7 @@ func (x *AcceptRoomRPSChallengeRequest) String() string { func (*AcceptRoomRPSChallengeRequest) ProtoMessage() {} func (x *AcceptRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[25] + mi := &file_proto_game_v1_game_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2482,7 +2686,7 @@ func (x *AcceptRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptRoomRPSChallengeRequest.ProtoReflect.Descriptor instead. func (*AcceptRoomRPSChallengeRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{25} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{28} } func (x *AcceptRoomRPSChallengeRequest) GetMeta() *RequestMeta { @@ -2524,7 +2728,7 @@ type GetRoomRPSChallengeRequest struct { func (x *GetRoomRPSChallengeRequest) Reset() { *x = GetRoomRPSChallengeRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[26] + mi := &file_proto_game_v1_game_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2536,7 +2740,7 @@ func (x *GetRoomRPSChallengeRequest) String() string { func (*GetRoomRPSChallengeRequest) ProtoMessage() {} func (x *GetRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[26] + mi := &file_proto_game_v1_game_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2549,7 +2753,7 @@ func (x *GetRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoomRPSChallengeRequest.ProtoReflect.Descriptor instead. func (*GetRoomRPSChallengeRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{26} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{29} } func (x *GetRoomRPSChallengeRequest) GetMeta() *RequestMeta { @@ -2591,7 +2795,7 @@ type ListRoomRPSChallengesRequest struct { func (x *ListRoomRPSChallengesRequest) Reset() { *x = ListRoomRPSChallengesRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[27] + mi := &file_proto_game_v1_game_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2603,7 +2807,7 @@ func (x *ListRoomRPSChallengesRequest) String() string { func (*ListRoomRPSChallengesRequest) ProtoMessage() {} func (x *ListRoomRPSChallengesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[27] + mi := &file_proto_game_v1_game_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2616,7 +2820,7 @@ func (x *ListRoomRPSChallengesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomRPSChallengesRequest.ProtoReflect.Descriptor instead. func (*ListRoomRPSChallengesRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{27} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{30} } func (x *ListRoomRPSChallengesRequest) GetMeta() *RequestMeta { @@ -2699,7 +2903,7 @@ type RoomRPSChallengeResponse struct { func (x *RoomRPSChallengeResponse) Reset() { *x = RoomRPSChallengeResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[28] + mi := &file_proto_game_v1_game_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2711,7 +2915,7 @@ func (x *RoomRPSChallengeResponse) String() string { func (*RoomRPSChallengeResponse) ProtoMessage() {} func (x *RoomRPSChallengeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[28] + mi := &file_proto_game_v1_game_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2724,7 +2928,7 @@ func (x *RoomRPSChallengeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRPSChallengeResponse.ProtoReflect.Descriptor instead. func (*RoomRPSChallengeResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{28} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{31} } func (x *RoomRPSChallengeResponse) GetChallenge() *RoomRPSChallenge { @@ -2753,7 +2957,7 @@ type ListRoomRPSChallengesResponse struct { func (x *ListRoomRPSChallengesResponse) Reset() { *x = ListRoomRPSChallengesResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[29] + mi := &file_proto_game_v1_game_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2765,7 +2969,7 @@ func (x *ListRoomRPSChallengesResponse) String() string { func (*ListRoomRPSChallengesResponse) ProtoMessage() {} func (x *ListRoomRPSChallengesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[29] + mi := &file_proto_game_v1_game_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2778,7 +2982,7 @@ func (x *ListRoomRPSChallengesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomRPSChallengesResponse.ProtoReflect.Descriptor instead. func (*ListRoomRPSChallengesResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{29} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{32} } func (x *ListRoomRPSChallengesResponse) GetChallenges() []*RoomRPSChallenge { @@ -2819,7 +3023,7 @@ type UpdateRoomRPSConfigRequest struct { func (x *UpdateRoomRPSConfigRequest) Reset() { *x = UpdateRoomRPSConfigRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[30] + mi := &file_proto_game_v1_game_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2831,7 +3035,7 @@ func (x *UpdateRoomRPSConfigRequest) String() string { func (*UpdateRoomRPSConfigRequest) ProtoMessage() {} func (x *UpdateRoomRPSConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[30] + mi := &file_proto_game_v1_game_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2844,7 +3048,7 @@ func (x *UpdateRoomRPSConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRoomRPSConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateRoomRPSConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{30} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{33} } func (x *UpdateRoomRPSConfigRequest) GetMeta() *RequestMeta { @@ -2871,7 +3075,7 @@ type RetryRoomRPSSettlementRequest struct { func (x *RetryRoomRPSSettlementRequest) Reset() { *x = RetryRoomRPSSettlementRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[31] + mi := &file_proto_game_v1_game_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2883,7 +3087,7 @@ func (x *RetryRoomRPSSettlementRequest) String() string { func (*RetryRoomRPSSettlementRequest) ProtoMessage() {} func (x *RetryRoomRPSSettlementRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[31] + mi := &file_proto_game_v1_game_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2896,7 +3100,7 @@ func (x *RetryRoomRPSSettlementRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryRoomRPSSettlementRequest.ProtoReflect.Descriptor instead. func (*RetryRoomRPSSettlementRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{31} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{34} } func (x *RetryRoomRPSSettlementRequest) GetMeta() *RequestMeta { @@ -2923,7 +3127,7 @@ type ExpireRoomRPSChallengeRequest struct { func (x *ExpireRoomRPSChallengeRequest) Reset() { *x = ExpireRoomRPSChallengeRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[32] + mi := &file_proto_game_v1_game_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2935,7 +3139,7 @@ func (x *ExpireRoomRPSChallengeRequest) String() string { func (*ExpireRoomRPSChallengeRequest) ProtoMessage() {} func (x *ExpireRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[32] + mi := &file_proto_game_v1_game_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2948,7 +3152,7 @@ func (x *ExpireRoomRPSChallengeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRoomRPSChallengeRequest.ProtoReflect.Descriptor instead. func (*ExpireRoomRPSChallengeRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{32} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{35} } func (x *ExpireRoomRPSChallengeRequest) GetMeta() *RequestMeta { @@ -2981,7 +3185,7 @@ type CreateDiceMatchRequest struct { func (x *CreateDiceMatchRequest) Reset() { *x = CreateDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[33] + mi := &file_proto_game_v1_game_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2993,7 +3197,7 @@ func (x *CreateDiceMatchRequest) String() string { func (*CreateDiceMatchRequest) ProtoMessage() {} func (x *CreateDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[33] + mi := &file_proto_game_v1_game_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3006,7 +3210,7 @@ func (x *CreateDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateDiceMatchRequest.ProtoReflect.Descriptor instead. func (*CreateDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{33} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{36} } func (x *CreateDiceMatchRequest) GetMeta() *RequestMeta { @@ -3076,7 +3280,7 @@ type JoinDiceMatchRequest struct { func (x *JoinDiceMatchRequest) Reset() { *x = JoinDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[34] + mi := &file_proto_game_v1_game_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3088,7 +3292,7 @@ func (x *JoinDiceMatchRequest) String() string { func (*JoinDiceMatchRequest) ProtoMessage() {} func (x *JoinDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[34] + mi := &file_proto_game_v1_game_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3101,7 +3305,7 @@ func (x *JoinDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinDiceMatchRequest.ProtoReflect.Descriptor instead. func (*JoinDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{34} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{37} } func (x *JoinDiceMatchRequest) GetMeta() *RequestMeta { @@ -3136,7 +3340,7 @@ type GetDiceMatchRequest struct { func (x *GetDiceMatchRequest) Reset() { *x = GetDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[35] + mi := &file_proto_game_v1_game_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3148,7 +3352,7 @@ func (x *GetDiceMatchRequest) String() string { func (*GetDiceMatchRequest) ProtoMessage() {} func (x *GetDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[35] + mi := &file_proto_game_v1_game_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3161,7 +3365,7 @@ func (x *GetDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiceMatchRequest.ProtoReflect.Descriptor instead. func (*GetDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{35} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{38} } func (x *GetDiceMatchRequest) GetMeta() *RequestMeta { @@ -3196,7 +3400,7 @@ type RollDiceMatchRequest struct { func (x *RollDiceMatchRequest) Reset() { *x = RollDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[36] + mi := &file_proto_game_v1_game_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3208,7 +3412,7 @@ func (x *RollDiceMatchRequest) String() string { func (*RollDiceMatchRequest) ProtoMessage() {} func (x *RollDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[36] + mi := &file_proto_game_v1_game_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3221,7 +3425,7 @@ func (x *RollDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RollDiceMatchRequest.ProtoReflect.Descriptor instead. func (*RollDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{36} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{39} } func (x *RollDiceMatchRequest) GetMeta() *RequestMeta { @@ -3259,7 +3463,7 @@ type MatchDiceRequest struct { func (x *MatchDiceRequest) Reset() { *x = MatchDiceRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[37] + mi := &file_proto_game_v1_game_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3271,7 +3475,7 @@ func (x *MatchDiceRequest) String() string { func (*MatchDiceRequest) ProtoMessage() {} func (x *MatchDiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[37] + mi := &file_proto_game_v1_game_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3284,7 +3488,7 @@ func (x *MatchDiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MatchDiceRequest.ProtoReflect.Descriptor instead. func (*MatchDiceRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{37} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{40} } func (x *MatchDiceRequest) GetMeta() *RequestMeta { @@ -3340,7 +3544,7 @@ type CancelDiceMatchRequest struct { func (x *CancelDiceMatchRequest) Reset() { *x = CancelDiceMatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[38] + mi := &file_proto_game_v1_game_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3352,7 +3556,7 @@ func (x *CancelDiceMatchRequest) String() string { func (*CancelDiceMatchRequest) ProtoMessage() {} func (x *CancelDiceMatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[38] + mi := &file_proto_game_v1_game_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3365,7 +3569,7 @@ func (x *CancelDiceMatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelDiceMatchRequest.ProtoReflect.Descriptor instead. func (*CancelDiceMatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{38} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{41} } func (x *CancelDiceMatchRequest) GetMeta() *RequestMeta { @@ -3399,7 +3603,7 @@ type DiceMatchResponse struct { func (x *DiceMatchResponse) Reset() { *x = DiceMatchResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[39] + mi := &file_proto_game_v1_game_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3411,7 +3615,7 @@ func (x *DiceMatchResponse) String() string { func (*DiceMatchResponse) ProtoMessage() {} func (x *DiceMatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[39] + mi := &file_proto_game_v1_game_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3424,7 +3628,7 @@ func (x *DiceMatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceMatchResponse.ProtoReflect.Descriptor instead. func (*DiceMatchResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{39} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{42} } func (x *DiceMatchResponse) GetMatch() *DiceMatch { @@ -3456,7 +3660,7 @@ type CallbackRequest struct { func (x *CallbackRequest) Reset() { *x = CallbackRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[40] + mi := &file_proto_game_v1_game_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3468,7 +3672,7 @@ func (x *CallbackRequest) String() string { func (*CallbackRequest) ProtoMessage() {} func (x *CallbackRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[40] + mi := &file_proto_game_v1_game_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3481,7 +3685,7 @@ func (x *CallbackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackRequest.ProtoReflect.Descriptor instead. func (*CallbackRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{40} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{43} } func (x *CallbackRequest) GetMeta() *RequestMeta { @@ -3543,7 +3747,7 @@ type CallbackResponse struct { func (x *CallbackResponse) Reset() { *x = CallbackResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[41] + mi := &file_proto_game_v1_game_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3555,7 +3759,7 @@ func (x *CallbackResponse) String() string { func (*CallbackResponse) ProtoMessage() {} func (x *CallbackResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[41] + mi := &file_proto_game_v1_game_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3568,7 +3772,7 @@ func (x *CallbackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CallbackResponse.ProtoReflect.Descriptor instead. func (*CallbackResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{41} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{44} } func (x *CallbackResponse) GetRawBody() []byte { @@ -3598,7 +3802,7 @@ type CronBatchRequest struct { func (x *CronBatchRequest) Reset() { *x = CronBatchRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[42] + mi := &file_proto_game_v1_game_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3610,7 +3814,7 @@ func (x *CronBatchRequest) String() string { func (*CronBatchRequest) ProtoMessage() {} func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[42] + mi := &file_proto_game_v1_game_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3623,7 +3827,7 @@ func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchRequest.ProtoReflect.Descriptor instead. func (*CronBatchRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{42} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{45} } func (x *CronBatchRequest) GetMeta() *RequestMeta { @@ -3674,7 +3878,7 @@ type CronBatchResponse struct { func (x *CronBatchResponse) Reset() { *x = CronBatchResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[43] + mi := &file_proto_game_v1_game_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3686,7 +3890,7 @@ func (x *CronBatchResponse) String() string { func (*CronBatchResponse) ProtoMessage() {} func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[43] + mi := &file_proto_game_v1_game_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3699,7 +3903,7 @@ func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchResponse.ProtoReflect.Descriptor instead. func (*CronBatchResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{43} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{46} } func (x *CronBatchResponse) GetClaimedCount() int32 { @@ -3747,7 +3951,7 @@ type ListPlatformsRequest struct { func (x *ListPlatformsRequest) Reset() { *x = ListPlatformsRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[44] + mi := &file_proto_game_v1_game_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3759,7 +3963,7 @@ func (x *ListPlatformsRequest) String() string { func (*ListPlatformsRequest) ProtoMessage() {} func (x *ListPlatformsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[44] + mi := &file_proto_game_v1_game_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3772,7 +3976,7 @@ func (x *ListPlatformsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPlatformsRequest.ProtoReflect.Descriptor instead. func (*ListPlatformsRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{44} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{47} } func (x *ListPlatformsRequest) GetMeta() *RequestMeta { @@ -3799,7 +4003,7 @@ type ListPlatformsResponse struct { func (x *ListPlatformsResponse) Reset() { *x = ListPlatformsResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[45] + mi := &file_proto_game_v1_game_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3811,7 +4015,7 @@ func (x *ListPlatformsResponse) String() string { func (*ListPlatformsResponse) ProtoMessage() {} func (x *ListPlatformsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[45] + mi := &file_proto_game_v1_game_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3824,7 +4028,7 @@ func (x *ListPlatformsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPlatformsResponse.ProtoReflect.Descriptor instead. func (*ListPlatformsResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{45} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{48} } func (x *ListPlatformsResponse) GetPlatforms() []*GamePlatform { @@ -3851,7 +4055,7 @@ type UpsertPlatformRequest struct { func (x *UpsertPlatformRequest) Reset() { *x = UpsertPlatformRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[46] + mi := &file_proto_game_v1_game_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3863,7 +4067,7 @@ func (x *UpsertPlatformRequest) String() string { func (*UpsertPlatformRequest) ProtoMessage() {} func (x *UpsertPlatformRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[46] + mi := &file_proto_game_v1_game_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3876,7 +4080,7 @@ func (x *UpsertPlatformRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertPlatformRequest.ProtoReflect.Descriptor instead. func (*UpsertPlatformRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{46} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{49} } func (x *UpsertPlatformRequest) GetMeta() *RequestMeta { @@ -3903,7 +4107,7 @@ type PlatformResponse struct { func (x *PlatformResponse) Reset() { *x = PlatformResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[47] + mi := &file_proto_game_v1_game_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3915,7 +4119,7 @@ func (x *PlatformResponse) String() string { func (*PlatformResponse) ProtoMessage() {} func (x *PlatformResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[47] + mi := &file_proto_game_v1_game_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3928,7 +4132,7 @@ func (x *PlatformResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PlatformResponse.ProtoReflect.Descriptor instead. func (*PlatformResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{47} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{50} } func (x *PlatformResponse) GetPlatform() *GamePlatform { @@ -3958,7 +4162,7 @@ type ListCatalogRequest struct { func (x *ListCatalogRequest) Reset() { *x = ListCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[48] + mi := &file_proto_game_v1_game_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3970,7 +4174,7 @@ func (x *ListCatalogRequest) String() string { func (*ListCatalogRequest) ProtoMessage() {} func (x *ListCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[48] + mi := &file_proto_game_v1_game_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3983,7 +4187,7 @@ func (x *ListCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCatalogRequest.ProtoReflect.Descriptor instead. func (*ListCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{48} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{51} } func (x *ListCatalogRequest) GetMeta() *RequestMeta { @@ -4032,7 +4236,7 @@ type ListCatalogResponse struct { func (x *ListCatalogResponse) Reset() { *x = ListCatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[49] + mi := &file_proto_game_v1_game_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4044,7 +4248,7 @@ func (x *ListCatalogResponse) String() string { func (*ListCatalogResponse) ProtoMessage() {} func (x *ListCatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[49] + mi := &file_proto_game_v1_game_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4057,7 +4261,7 @@ func (x *ListCatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCatalogResponse.ProtoReflect.Descriptor instead. func (*ListCatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{49} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{52} } func (x *ListCatalogResponse) GetGames() []*GameCatalogItem { @@ -4091,7 +4295,7 @@ type UpsertCatalogRequest struct { func (x *UpsertCatalogRequest) Reset() { *x = UpsertCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[50] + mi := &file_proto_game_v1_game_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4103,7 +4307,7 @@ func (x *UpsertCatalogRequest) String() string { func (*UpsertCatalogRequest) ProtoMessage() {} func (x *UpsertCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[50] + mi := &file_proto_game_v1_game_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4116,7 +4320,7 @@ func (x *UpsertCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertCatalogRequest.ProtoReflect.Descriptor instead. func (*UpsertCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{50} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{53} } func (x *UpsertCatalogRequest) GetMeta() *RequestMeta { @@ -4143,7 +4347,7 @@ type CatalogResponse struct { func (x *CatalogResponse) Reset() { *x = CatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[51] + mi := &file_proto_game_v1_game_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4155,7 +4359,7 @@ func (x *CatalogResponse) String() string { func (*CatalogResponse) ProtoMessage() {} func (x *CatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[51] + mi := &file_proto_game_v1_game_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4168,7 +4372,7 @@ func (x *CatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CatalogResponse.ProtoReflect.Descriptor instead. func (*CatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{51} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{54} } func (x *CatalogResponse) GetGame() *GameCatalogItem { @@ -4196,7 +4400,7 @@ type SetGameStatusRequest struct { func (x *SetGameStatusRequest) Reset() { *x = SetGameStatusRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[52] + mi := &file_proto_game_v1_game_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4208,7 +4412,7 @@ func (x *SetGameStatusRequest) String() string { func (*SetGameStatusRequest) ProtoMessage() {} func (x *SetGameStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[52] + mi := &file_proto_game_v1_game_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4221,7 +4425,7 @@ func (x *SetGameStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetGameStatusRequest.ProtoReflect.Descriptor instead. func (*SetGameStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{52} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{55} } func (x *SetGameStatusRequest) GetMeta() *RequestMeta { @@ -4255,7 +4459,7 @@ type DeleteCatalogRequest struct { func (x *DeleteCatalogRequest) Reset() { *x = DeleteCatalogRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[53] + mi := &file_proto_game_v1_game_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4267,7 +4471,7 @@ func (x *DeleteCatalogRequest) String() string { func (*DeleteCatalogRequest) ProtoMessage() {} func (x *DeleteCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[53] + mi := &file_proto_game_v1_game_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4280,7 +4484,7 @@ func (x *DeleteCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteCatalogRequest.ProtoReflect.Descriptor instead. func (*DeleteCatalogRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{53} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{56} } func (x *DeleteCatalogRequest) GetMeta() *RequestMeta { @@ -4306,7 +4510,7 @@ type DeleteCatalogResponse struct { func (x *DeleteCatalogResponse) Reset() { *x = DeleteCatalogResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[54] + mi := &file_proto_game_v1_game_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4318,7 +4522,7 @@ func (x *DeleteCatalogResponse) String() string { func (*DeleteCatalogResponse) ProtoMessage() {} func (x *DeleteCatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[54] + mi := &file_proto_game_v1_game_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4331,7 +4535,7 @@ func (x *DeleteCatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteCatalogResponse.ProtoReflect.Descriptor instead. func (*DeleteCatalogResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{54} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{57} } func (x *DeleteCatalogResponse) GetServerTimeMs() int64 { @@ -4350,7 +4554,7 @@ type ListSelfGamesRequest struct { func (x *ListSelfGamesRequest) Reset() { *x = ListSelfGamesRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[55] + mi := &file_proto_game_v1_game_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4362,7 +4566,7 @@ func (x *ListSelfGamesRequest) String() string { func (*ListSelfGamesRequest) ProtoMessage() {} func (x *ListSelfGamesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[55] + mi := &file_proto_game_v1_game_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4375,7 +4579,7 @@ func (x *ListSelfGamesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSelfGamesRequest.ProtoReflect.Descriptor instead. func (*ListSelfGamesRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{55} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{58} } func (x *ListSelfGamesRequest) GetMeta() *RequestMeta { @@ -4395,7 +4599,7 @@ type ListSelfGamesResponse struct { func (x *ListSelfGamesResponse) Reset() { *x = ListSelfGamesResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[56] + mi := &file_proto_game_v1_game_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4407,7 +4611,7 @@ func (x *ListSelfGamesResponse) String() string { func (*ListSelfGamesResponse) ProtoMessage() {} func (x *ListSelfGamesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[56] + mi := &file_proto_game_v1_game_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4420,7 +4624,7 @@ func (x *ListSelfGamesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSelfGamesResponse.ProtoReflect.Descriptor instead. func (*ListSelfGamesResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{56} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{59} } func (x *ListSelfGamesResponse) GetGames() []*DiceConfig { @@ -4447,7 +4651,7 @@ type UpdateDiceConfigRequest struct { func (x *UpdateDiceConfigRequest) Reset() { *x = UpdateDiceConfigRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[57] + mi := &file_proto_game_v1_game_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4459,7 +4663,7 @@ func (x *UpdateDiceConfigRequest) String() string { func (*UpdateDiceConfigRequest) ProtoMessage() {} func (x *UpdateDiceConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[57] + mi := &file_proto_game_v1_game_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4472,7 +4676,7 @@ func (x *UpdateDiceConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateDiceConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateDiceConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{57} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{60} } func (x *UpdateDiceConfigRequest) GetMeta() *RequestMeta { @@ -4504,7 +4708,7 @@ type DicePoolAdjustment struct { func (x *DicePoolAdjustment) Reset() { *x = DicePoolAdjustment{} - mi := &file_proto_game_v1_game_proto_msgTypes[58] + mi := &file_proto_game_v1_game_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4516,7 +4720,7 @@ func (x *DicePoolAdjustment) String() string { func (*DicePoolAdjustment) ProtoMessage() {} func (x *DicePoolAdjustment) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[58] + mi := &file_proto_game_v1_game_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4529,7 +4733,7 @@ func (x *DicePoolAdjustment) ProtoReflect() protoreflect.Message { // Deprecated: Use DicePoolAdjustment.ProtoReflect.Descriptor instead. func (*DicePoolAdjustment) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{58} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{61} } func (x *DicePoolAdjustment) GetAdjustmentId() string { @@ -4594,7 +4798,7 @@ type AdjustDicePoolRequest struct { func (x *AdjustDicePoolRequest) Reset() { *x = AdjustDicePoolRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[59] + mi := &file_proto_game_v1_game_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4606,7 +4810,7 @@ func (x *AdjustDicePoolRequest) String() string { func (*AdjustDicePoolRequest) ProtoMessage() {} func (x *AdjustDicePoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[59] + mi := &file_proto_game_v1_game_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4619,7 +4823,7 @@ func (x *AdjustDicePoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdjustDicePoolRequest.ProtoReflect.Descriptor instead. func (*AdjustDicePoolRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{59} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{62} } func (x *AdjustDicePoolRequest) GetMeta() *RequestMeta { @@ -4668,7 +4872,7 @@ type AdjustDicePoolResponse struct { func (x *AdjustDicePoolResponse) Reset() { *x = AdjustDicePoolResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[60] + mi := &file_proto_game_v1_game_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4680,7 +4884,7 @@ func (x *AdjustDicePoolResponse) String() string { func (*AdjustDicePoolResponse) ProtoMessage() {} func (x *AdjustDicePoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[60] + mi := &file_proto_game_v1_game_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4693,7 +4897,7 @@ func (x *AdjustDicePoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdjustDicePoolResponse.ProtoReflect.Descriptor instead. func (*AdjustDicePoolResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{60} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{63} } func (x *AdjustDicePoolResponse) GetAdjustment() *DicePoolAdjustment { @@ -4732,7 +4936,7 @@ type DiceRobot struct { func (x *DiceRobot) Reset() { *x = DiceRobot{} - mi := &file_proto_game_v1_game_proto_msgTypes[61] + mi := &file_proto_game_v1_game_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4744,7 +4948,7 @@ func (x *DiceRobot) String() string { func (*DiceRobot) ProtoMessage() {} func (x *DiceRobot) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[61] + mi := &file_proto_game_v1_game_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4757,7 +4961,7 @@ func (x *DiceRobot) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceRobot.ProtoReflect.Descriptor instead. func (*DiceRobot) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{61} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{64} } func (x *DiceRobot) GetAppCode() string { @@ -4822,7 +5026,7 @@ type ListDiceRobotsRequest struct { func (x *ListDiceRobotsRequest) Reset() { *x = ListDiceRobotsRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[62] + mi := &file_proto_game_v1_game_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4834,7 +5038,7 @@ func (x *ListDiceRobotsRequest) String() string { func (*ListDiceRobotsRequest) ProtoMessage() {} func (x *ListDiceRobotsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[62] + mi := &file_proto_game_v1_game_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4847,7 +5051,7 @@ func (x *ListDiceRobotsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDiceRobotsRequest.ProtoReflect.Descriptor instead. func (*ListDiceRobotsRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{62} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{65} } func (x *ListDiceRobotsRequest) GetMeta() *RequestMeta { @@ -4896,7 +5100,7 @@ type ListDiceRobotsResponse struct { func (x *ListDiceRobotsResponse) Reset() { *x = ListDiceRobotsResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[63] + mi := &file_proto_game_v1_game_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4908,7 +5112,7 @@ func (x *ListDiceRobotsResponse) String() string { func (*ListDiceRobotsResponse) ProtoMessage() {} func (x *ListDiceRobotsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[63] + mi := &file_proto_game_v1_game_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4921,7 +5125,7 @@ func (x *ListDiceRobotsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDiceRobotsResponse.ProtoReflect.Descriptor instead. func (*ListDiceRobotsResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{63} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{66} } func (x *ListDiceRobotsResponse) GetRobots() []*DiceRobot { @@ -4956,7 +5160,7 @@ type RegisterDiceRobotsRequest struct { func (x *RegisterDiceRobotsRequest) Reset() { *x = RegisterDiceRobotsRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[64] + mi := &file_proto_game_v1_game_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4968,7 +5172,7 @@ func (x *RegisterDiceRobotsRequest) String() string { func (*RegisterDiceRobotsRequest) ProtoMessage() {} func (x *RegisterDiceRobotsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[64] + mi := &file_proto_game_v1_game_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4981,7 +5185,7 @@ func (x *RegisterDiceRobotsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterDiceRobotsRequest.ProtoReflect.Descriptor instead. func (*RegisterDiceRobotsRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{64} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{67} } func (x *RegisterDiceRobotsRequest) GetMeta() *RequestMeta { @@ -5015,7 +5219,7 @@ type RegisterDiceRobotsResponse struct { func (x *RegisterDiceRobotsResponse) Reset() { *x = RegisterDiceRobotsResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[65] + mi := &file_proto_game_v1_game_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5027,7 +5231,7 @@ func (x *RegisterDiceRobotsResponse) String() string { func (*RegisterDiceRobotsResponse) ProtoMessage() {} func (x *RegisterDiceRobotsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[65] + mi := &file_proto_game_v1_game_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5040,7 +5244,7 @@ func (x *RegisterDiceRobotsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterDiceRobotsResponse.ProtoReflect.Descriptor instead. func (*RegisterDiceRobotsResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{65} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{68} } func (x *RegisterDiceRobotsResponse) GetRobots() []*DiceRobot { @@ -5069,7 +5273,7 @@ type SetDiceRobotStatusRequest struct { func (x *SetDiceRobotStatusRequest) Reset() { *x = SetDiceRobotStatusRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[66] + mi := &file_proto_game_v1_game_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5081,7 +5285,7 @@ func (x *SetDiceRobotStatusRequest) String() string { func (*SetDiceRobotStatusRequest) ProtoMessage() {} func (x *SetDiceRobotStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[66] + mi := &file_proto_game_v1_game_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5094,7 +5298,7 @@ func (x *SetDiceRobotStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetDiceRobotStatusRequest.ProtoReflect.Descriptor instead. func (*SetDiceRobotStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{66} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{69} } func (x *SetDiceRobotStatusRequest) GetMeta() *RequestMeta { @@ -5136,7 +5340,7 @@ type DeleteDiceRobotRequest struct { func (x *DeleteDiceRobotRequest) Reset() { *x = DeleteDiceRobotRequest{} - mi := &file_proto_game_v1_game_proto_msgTypes[67] + mi := &file_proto_game_v1_game_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5148,7 +5352,7 @@ func (x *DeleteDiceRobotRequest) String() string { func (*DeleteDiceRobotRequest) ProtoMessage() {} func (x *DeleteDiceRobotRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[67] + mi := &file_proto_game_v1_game_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5161,7 +5365,7 @@ func (x *DeleteDiceRobotRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteDiceRobotRequest.ProtoReflect.Descriptor instead. func (*DeleteDiceRobotRequest) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{67} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{70} } func (x *DeleteDiceRobotRequest) GetMeta() *RequestMeta { @@ -5195,7 +5399,7 @@ type DeleteDiceRobotResponse struct { func (x *DeleteDiceRobotResponse) Reset() { *x = DeleteDiceRobotResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[68] + mi := &file_proto_game_v1_game_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5207,7 +5411,7 @@ func (x *DeleteDiceRobotResponse) String() string { func (*DeleteDiceRobotResponse) ProtoMessage() {} func (x *DeleteDiceRobotResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[68] + mi := &file_proto_game_v1_game_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5220,7 +5424,7 @@ func (x *DeleteDiceRobotResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteDiceRobotResponse.ProtoReflect.Descriptor instead. func (*DeleteDiceRobotResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{68} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{71} } func (x *DeleteDiceRobotResponse) GetDeleted() bool { @@ -5247,7 +5451,7 @@ type DiceRobotResponse struct { func (x *DiceRobotResponse) Reset() { *x = DiceRobotResponse{} - mi := &file_proto_game_v1_game_proto_msgTypes[69] + mi := &file_proto_game_v1_game_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5259,7 +5463,7 @@ func (x *DiceRobotResponse) String() string { func (*DiceRobotResponse) ProtoMessage() {} func (x *DiceRobotResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_v1_game_proto_msgTypes[69] + mi := &file_proto_game_v1_game_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5272,7 +5476,7 @@ func (x *DiceRobotResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiceRobotResponse.ProtoReflect.Descriptor instead. func (*DiceRobotResponse) Descriptor() ([]byte, []int) { - return file_proto_game_v1_game_proto_rawDescGZIP(), []int{69} + return file_proto_game_v1_game_proto_rawDescGZIP(), []int{72} } func (x *DiceRobotResponse) GetRobot() *DiceRobot { @@ -5380,7 +5584,24 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\tregion_id\x18\x05 \x01(\x03R\bregionId\x12\x1a\n" + "\blanguage\x18\x06 \x01(\tR\blanguage\x12'\n" + "\x0fclient_platform\x18\a \x01(\tR\x0eclientPlatform\x12\x1b\n" + - "\tpage_size\x18\b \x01(\x05R\bpageSize\"H\n" + + "\tpage_size\x18\b \x01(\x05R\bpageSize\"h\n" + + "\x19ListExploreWinnersRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x1b\n" + + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\"\xf4\x01\n" + + "\rExploreWinner\x12\x15\n" + + "\x06win_id\x18\x01 \x01(\tR\x05winId\x12\x1b\n" + + "\tgame_code\x18\x02 \x01(\tR\bgameCode\x12\x17\n" + + "\agame_id\x18\x03 \x01(\tR\x06gameId\x12\x17\n" + + "\auser_id\x18\x04 \x01(\x03R\x06userId\x12!\n" + + "\fdisplay_name\x18\x05 \x01(\tR\vdisplayName\x12\x1d\n" + + "\n" + + "avatar_url\x18\x06 \x01(\tR\tavatarUrl\x12\x1f\n" + + "\vcoin_amount\x18\a \x01(\x03R\n" + + "coinAmount\x12\x1a\n" + + "\twon_at_ms\x18\b \x01(\x03R\awonAtMs\"z\n" + + "\x1aListExploreWinnersResponse\x126\n" + + "\awinners\x18\x01 \x03(\v2\x1c.hyapp.game.v1.ExploreWinnerR\awinners\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"H\n" + "\x16GetBridgeScriptRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\"\xaa\x01\n" + "\x16GameBridgeScriptConfig\x12*\n" + @@ -5769,10 +5990,11 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"i\n" + "\x11DiceRobotResponse\x12.\n" + "\x05robot\x18\x01 \x01(\v2\x18.hyapp.game.v1.DiceRobotR\x05robot\x12$\n" + - "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs2\xfb\v\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs2\xe6\f\n" + "\x0eGameAppService\x12N\n" + "\tListGames\x12\x1f.hyapp.game.v1.ListGamesRequest\x1a .hyapp.game.v1.ListGamesResponse\x12Z\n" + - "\x0fListRecentGames\x12%.hyapp.game.v1.ListRecentGamesRequest\x1a .hyapp.game.v1.ListGamesResponse\x12`\n" + + "\x0fListRecentGames\x12%.hyapp.game.v1.ListRecentGamesRequest\x1a .hyapp.game.v1.ListGamesResponse\x12i\n" + + "\x12ListExploreWinners\x12(.hyapp.game.v1.ListExploreWinnersRequest\x1a).hyapp.game.v1.ListExploreWinnersResponse\x12`\n" + "\x0fGetBridgeScript\x12%.hyapp.game.v1.GetBridgeScriptRequest\x1a&.hyapp.game.v1.GetBridgeScriptResponse\x12Q\n" + "\n" + "LaunchGame\x12 .hyapp.game.v1.LaunchGameRequest\x1a!.hyapp.game.v1.LaunchGameResponse\x12W\n" + @@ -5825,7 +6047,7 @@ func file_proto_game_v1_game_proto_rawDescGZIP() []byte { return file_proto_game_v1_game_proto_rawDescData } -var file_proto_game_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 72) +var file_proto_game_v1_game_proto_msgTypes = make([]protoimpl.MessageInfo, 75) var file_proto_game_v1_game_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.game.v1.RequestMeta (*GamePlatform)(nil), // 1: hyapp.game.v1.GamePlatform @@ -5834,214 +6056,221 @@ var file_proto_game_v1_game_proto_goTypes = []any{ (*ListGamesRequest)(nil), // 4: hyapp.game.v1.ListGamesRequest (*ListGamesResponse)(nil), // 5: hyapp.game.v1.ListGamesResponse (*ListRecentGamesRequest)(nil), // 6: hyapp.game.v1.ListRecentGamesRequest - (*GetBridgeScriptRequest)(nil), // 7: hyapp.game.v1.GetBridgeScriptRequest - (*GameBridgeScriptConfig)(nil), // 8: hyapp.game.v1.GameBridgeScriptConfig - (*GetBridgeScriptResponse)(nil), // 9: hyapp.game.v1.GetBridgeScriptResponse - (*LaunchGameRequest)(nil), // 10: hyapp.game.v1.LaunchGameRequest - (*LaunchGameResponse)(nil), // 11: hyapp.game.v1.LaunchGameResponse - (*DiceParticipant)(nil), // 12: hyapp.game.v1.DiceParticipant - (*DiceMatch)(nil), // 13: hyapp.game.v1.DiceMatch - (*DiceStakeOption)(nil), // 14: hyapp.game.v1.DiceStakeOption - (*DiceConfig)(nil), // 15: hyapp.game.v1.DiceConfig - (*GetDiceConfigRequest)(nil), // 16: hyapp.game.v1.GetDiceConfigRequest - (*DiceConfigResponse)(nil), // 17: hyapp.game.v1.DiceConfigResponse - (*RoomRPSStakeGift)(nil), // 18: hyapp.game.v1.RoomRPSStakeGift - (*RoomRPSConfig)(nil), // 19: hyapp.game.v1.RoomRPSConfig - (*RoomRPSPlayer)(nil), // 20: hyapp.game.v1.RoomRPSPlayer - (*RoomRPSChallenge)(nil), // 21: hyapp.game.v1.RoomRPSChallenge - (*GetRoomRPSConfigRequest)(nil), // 22: hyapp.game.v1.GetRoomRPSConfigRequest - (*RoomRPSConfigResponse)(nil), // 23: hyapp.game.v1.RoomRPSConfigResponse - (*CreateRoomRPSChallengeRequest)(nil), // 24: hyapp.game.v1.CreateRoomRPSChallengeRequest - (*AcceptRoomRPSChallengeRequest)(nil), // 25: hyapp.game.v1.AcceptRoomRPSChallengeRequest - (*GetRoomRPSChallengeRequest)(nil), // 26: hyapp.game.v1.GetRoomRPSChallengeRequest - (*ListRoomRPSChallengesRequest)(nil), // 27: hyapp.game.v1.ListRoomRPSChallengesRequest - (*RoomRPSChallengeResponse)(nil), // 28: hyapp.game.v1.RoomRPSChallengeResponse - (*ListRoomRPSChallengesResponse)(nil), // 29: hyapp.game.v1.ListRoomRPSChallengesResponse - (*UpdateRoomRPSConfigRequest)(nil), // 30: hyapp.game.v1.UpdateRoomRPSConfigRequest - (*RetryRoomRPSSettlementRequest)(nil), // 31: hyapp.game.v1.RetryRoomRPSSettlementRequest - (*ExpireRoomRPSChallengeRequest)(nil), // 32: hyapp.game.v1.ExpireRoomRPSChallengeRequest - (*CreateDiceMatchRequest)(nil), // 33: hyapp.game.v1.CreateDiceMatchRequest - (*JoinDiceMatchRequest)(nil), // 34: hyapp.game.v1.JoinDiceMatchRequest - (*GetDiceMatchRequest)(nil), // 35: hyapp.game.v1.GetDiceMatchRequest - (*RollDiceMatchRequest)(nil), // 36: hyapp.game.v1.RollDiceMatchRequest - (*MatchDiceRequest)(nil), // 37: hyapp.game.v1.MatchDiceRequest - (*CancelDiceMatchRequest)(nil), // 38: hyapp.game.v1.CancelDiceMatchRequest - (*DiceMatchResponse)(nil), // 39: hyapp.game.v1.DiceMatchResponse - (*CallbackRequest)(nil), // 40: hyapp.game.v1.CallbackRequest - (*CallbackResponse)(nil), // 41: hyapp.game.v1.CallbackResponse - (*CronBatchRequest)(nil), // 42: hyapp.game.v1.CronBatchRequest - (*CronBatchResponse)(nil), // 43: hyapp.game.v1.CronBatchResponse - (*ListPlatformsRequest)(nil), // 44: hyapp.game.v1.ListPlatformsRequest - (*ListPlatformsResponse)(nil), // 45: hyapp.game.v1.ListPlatformsResponse - (*UpsertPlatformRequest)(nil), // 46: hyapp.game.v1.UpsertPlatformRequest - (*PlatformResponse)(nil), // 47: hyapp.game.v1.PlatformResponse - (*ListCatalogRequest)(nil), // 48: hyapp.game.v1.ListCatalogRequest - (*ListCatalogResponse)(nil), // 49: hyapp.game.v1.ListCatalogResponse - (*UpsertCatalogRequest)(nil), // 50: hyapp.game.v1.UpsertCatalogRequest - (*CatalogResponse)(nil), // 51: hyapp.game.v1.CatalogResponse - (*SetGameStatusRequest)(nil), // 52: hyapp.game.v1.SetGameStatusRequest - (*DeleteCatalogRequest)(nil), // 53: hyapp.game.v1.DeleteCatalogRequest - (*DeleteCatalogResponse)(nil), // 54: hyapp.game.v1.DeleteCatalogResponse - (*ListSelfGamesRequest)(nil), // 55: hyapp.game.v1.ListSelfGamesRequest - (*ListSelfGamesResponse)(nil), // 56: hyapp.game.v1.ListSelfGamesResponse - (*UpdateDiceConfigRequest)(nil), // 57: hyapp.game.v1.UpdateDiceConfigRequest - (*DicePoolAdjustment)(nil), // 58: hyapp.game.v1.DicePoolAdjustment - (*AdjustDicePoolRequest)(nil), // 59: hyapp.game.v1.AdjustDicePoolRequest - (*AdjustDicePoolResponse)(nil), // 60: hyapp.game.v1.AdjustDicePoolResponse - (*DiceRobot)(nil), // 61: hyapp.game.v1.DiceRobot - (*ListDiceRobotsRequest)(nil), // 62: hyapp.game.v1.ListDiceRobotsRequest - (*ListDiceRobotsResponse)(nil), // 63: hyapp.game.v1.ListDiceRobotsResponse - (*RegisterDiceRobotsRequest)(nil), // 64: hyapp.game.v1.RegisterDiceRobotsRequest - (*RegisterDiceRobotsResponse)(nil), // 65: hyapp.game.v1.RegisterDiceRobotsResponse - (*SetDiceRobotStatusRequest)(nil), // 66: hyapp.game.v1.SetDiceRobotStatusRequest - (*DeleteDiceRobotRequest)(nil), // 67: hyapp.game.v1.DeleteDiceRobotRequest - (*DeleteDiceRobotResponse)(nil), // 68: hyapp.game.v1.DeleteDiceRobotResponse - (*DiceRobotResponse)(nil), // 69: hyapp.game.v1.DiceRobotResponse - nil, // 70: hyapp.game.v1.CallbackRequest.HeadersEntry - nil, // 71: hyapp.game.v1.CallbackRequest.QueryEntry + (*ListExploreWinnersRequest)(nil), // 7: hyapp.game.v1.ListExploreWinnersRequest + (*ExploreWinner)(nil), // 8: hyapp.game.v1.ExploreWinner + (*ListExploreWinnersResponse)(nil), // 9: hyapp.game.v1.ListExploreWinnersResponse + (*GetBridgeScriptRequest)(nil), // 10: hyapp.game.v1.GetBridgeScriptRequest + (*GameBridgeScriptConfig)(nil), // 11: hyapp.game.v1.GameBridgeScriptConfig + (*GetBridgeScriptResponse)(nil), // 12: hyapp.game.v1.GetBridgeScriptResponse + (*LaunchGameRequest)(nil), // 13: hyapp.game.v1.LaunchGameRequest + (*LaunchGameResponse)(nil), // 14: hyapp.game.v1.LaunchGameResponse + (*DiceParticipant)(nil), // 15: hyapp.game.v1.DiceParticipant + (*DiceMatch)(nil), // 16: hyapp.game.v1.DiceMatch + (*DiceStakeOption)(nil), // 17: hyapp.game.v1.DiceStakeOption + (*DiceConfig)(nil), // 18: hyapp.game.v1.DiceConfig + (*GetDiceConfigRequest)(nil), // 19: hyapp.game.v1.GetDiceConfigRequest + (*DiceConfigResponse)(nil), // 20: hyapp.game.v1.DiceConfigResponse + (*RoomRPSStakeGift)(nil), // 21: hyapp.game.v1.RoomRPSStakeGift + (*RoomRPSConfig)(nil), // 22: hyapp.game.v1.RoomRPSConfig + (*RoomRPSPlayer)(nil), // 23: hyapp.game.v1.RoomRPSPlayer + (*RoomRPSChallenge)(nil), // 24: hyapp.game.v1.RoomRPSChallenge + (*GetRoomRPSConfigRequest)(nil), // 25: hyapp.game.v1.GetRoomRPSConfigRequest + (*RoomRPSConfigResponse)(nil), // 26: hyapp.game.v1.RoomRPSConfigResponse + (*CreateRoomRPSChallengeRequest)(nil), // 27: hyapp.game.v1.CreateRoomRPSChallengeRequest + (*AcceptRoomRPSChallengeRequest)(nil), // 28: hyapp.game.v1.AcceptRoomRPSChallengeRequest + (*GetRoomRPSChallengeRequest)(nil), // 29: hyapp.game.v1.GetRoomRPSChallengeRequest + (*ListRoomRPSChallengesRequest)(nil), // 30: hyapp.game.v1.ListRoomRPSChallengesRequest + (*RoomRPSChallengeResponse)(nil), // 31: hyapp.game.v1.RoomRPSChallengeResponse + (*ListRoomRPSChallengesResponse)(nil), // 32: hyapp.game.v1.ListRoomRPSChallengesResponse + (*UpdateRoomRPSConfigRequest)(nil), // 33: hyapp.game.v1.UpdateRoomRPSConfigRequest + (*RetryRoomRPSSettlementRequest)(nil), // 34: hyapp.game.v1.RetryRoomRPSSettlementRequest + (*ExpireRoomRPSChallengeRequest)(nil), // 35: hyapp.game.v1.ExpireRoomRPSChallengeRequest + (*CreateDiceMatchRequest)(nil), // 36: hyapp.game.v1.CreateDiceMatchRequest + (*JoinDiceMatchRequest)(nil), // 37: hyapp.game.v1.JoinDiceMatchRequest + (*GetDiceMatchRequest)(nil), // 38: hyapp.game.v1.GetDiceMatchRequest + (*RollDiceMatchRequest)(nil), // 39: hyapp.game.v1.RollDiceMatchRequest + (*MatchDiceRequest)(nil), // 40: hyapp.game.v1.MatchDiceRequest + (*CancelDiceMatchRequest)(nil), // 41: hyapp.game.v1.CancelDiceMatchRequest + (*DiceMatchResponse)(nil), // 42: hyapp.game.v1.DiceMatchResponse + (*CallbackRequest)(nil), // 43: hyapp.game.v1.CallbackRequest + (*CallbackResponse)(nil), // 44: hyapp.game.v1.CallbackResponse + (*CronBatchRequest)(nil), // 45: hyapp.game.v1.CronBatchRequest + (*CronBatchResponse)(nil), // 46: hyapp.game.v1.CronBatchResponse + (*ListPlatformsRequest)(nil), // 47: hyapp.game.v1.ListPlatformsRequest + (*ListPlatformsResponse)(nil), // 48: hyapp.game.v1.ListPlatformsResponse + (*UpsertPlatformRequest)(nil), // 49: hyapp.game.v1.UpsertPlatformRequest + (*PlatformResponse)(nil), // 50: hyapp.game.v1.PlatformResponse + (*ListCatalogRequest)(nil), // 51: hyapp.game.v1.ListCatalogRequest + (*ListCatalogResponse)(nil), // 52: hyapp.game.v1.ListCatalogResponse + (*UpsertCatalogRequest)(nil), // 53: hyapp.game.v1.UpsertCatalogRequest + (*CatalogResponse)(nil), // 54: hyapp.game.v1.CatalogResponse + (*SetGameStatusRequest)(nil), // 55: hyapp.game.v1.SetGameStatusRequest + (*DeleteCatalogRequest)(nil), // 56: hyapp.game.v1.DeleteCatalogRequest + (*DeleteCatalogResponse)(nil), // 57: hyapp.game.v1.DeleteCatalogResponse + (*ListSelfGamesRequest)(nil), // 58: hyapp.game.v1.ListSelfGamesRequest + (*ListSelfGamesResponse)(nil), // 59: hyapp.game.v1.ListSelfGamesResponse + (*UpdateDiceConfigRequest)(nil), // 60: hyapp.game.v1.UpdateDiceConfigRequest + (*DicePoolAdjustment)(nil), // 61: hyapp.game.v1.DicePoolAdjustment + (*AdjustDicePoolRequest)(nil), // 62: hyapp.game.v1.AdjustDicePoolRequest + (*AdjustDicePoolResponse)(nil), // 63: hyapp.game.v1.AdjustDicePoolResponse + (*DiceRobot)(nil), // 64: hyapp.game.v1.DiceRobot + (*ListDiceRobotsRequest)(nil), // 65: hyapp.game.v1.ListDiceRobotsRequest + (*ListDiceRobotsResponse)(nil), // 66: hyapp.game.v1.ListDiceRobotsResponse + (*RegisterDiceRobotsRequest)(nil), // 67: hyapp.game.v1.RegisterDiceRobotsRequest + (*RegisterDiceRobotsResponse)(nil), // 68: hyapp.game.v1.RegisterDiceRobotsResponse + (*SetDiceRobotStatusRequest)(nil), // 69: hyapp.game.v1.SetDiceRobotStatusRequest + (*DeleteDiceRobotRequest)(nil), // 70: hyapp.game.v1.DeleteDiceRobotRequest + (*DeleteDiceRobotResponse)(nil), // 71: hyapp.game.v1.DeleteDiceRobotResponse + (*DiceRobotResponse)(nil), // 72: hyapp.game.v1.DiceRobotResponse + nil, // 73: hyapp.game.v1.CallbackRequest.HeadersEntry + nil, // 74: hyapp.game.v1.CallbackRequest.QueryEntry } var file_proto_game_v1_game_proto_depIdxs = []int32{ - 0, // 0: hyapp.game.v1.ListGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 3, // 1: hyapp.game.v1.ListGamesResponse.games:type_name -> hyapp.game.v1.AppGame - 0, // 2: hyapp.game.v1.ListRecentGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 3: hyapp.game.v1.GetBridgeScriptRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 8, // 4: hyapp.game.v1.GetBridgeScriptResponse.config:type_name -> hyapp.game.v1.GameBridgeScriptConfig - 0, // 5: hyapp.game.v1.LaunchGameRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 12, // 6: hyapp.game.v1.DiceMatch.participants:type_name -> hyapp.game.v1.DiceParticipant - 14, // 7: hyapp.game.v1.DiceConfig.stake_options:type_name -> hyapp.game.v1.DiceStakeOption - 0, // 8: hyapp.game.v1.GetDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 15, // 9: hyapp.game.v1.DiceConfigResponse.config:type_name -> hyapp.game.v1.DiceConfig - 18, // 10: hyapp.game.v1.RoomRPSConfig.stake_gifts:type_name -> hyapp.game.v1.RoomRPSStakeGift - 20, // 11: hyapp.game.v1.RoomRPSChallenge.initiator:type_name -> hyapp.game.v1.RoomRPSPlayer - 20, // 12: hyapp.game.v1.RoomRPSChallenge.challenger:type_name -> hyapp.game.v1.RoomRPSPlayer - 0, // 13: hyapp.game.v1.GetRoomRPSConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 19, // 14: hyapp.game.v1.RoomRPSConfigResponse.config:type_name -> hyapp.game.v1.RoomRPSConfig - 0, // 15: hyapp.game.v1.CreateRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 16: hyapp.game.v1.AcceptRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 17: hyapp.game.v1.GetRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 18: hyapp.game.v1.ListRoomRPSChallengesRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 21, // 19: hyapp.game.v1.RoomRPSChallengeResponse.challenge:type_name -> hyapp.game.v1.RoomRPSChallenge - 21, // 20: hyapp.game.v1.ListRoomRPSChallengesResponse.challenges:type_name -> hyapp.game.v1.RoomRPSChallenge - 0, // 21: hyapp.game.v1.UpdateRoomRPSConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 19, // 22: hyapp.game.v1.UpdateRoomRPSConfigRequest.config:type_name -> hyapp.game.v1.RoomRPSConfig - 0, // 23: hyapp.game.v1.RetryRoomRPSSettlementRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 24: hyapp.game.v1.ExpireRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 25: hyapp.game.v1.CreateDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 26: hyapp.game.v1.JoinDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 27: hyapp.game.v1.GetDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 28: hyapp.game.v1.RollDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 29: hyapp.game.v1.MatchDiceRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 30: hyapp.game.v1.CancelDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 13, // 31: hyapp.game.v1.DiceMatchResponse.match:type_name -> hyapp.game.v1.DiceMatch - 0, // 32: hyapp.game.v1.CallbackRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 70, // 33: hyapp.game.v1.CallbackRequest.headers:type_name -> hyapp.game.v1.CallbackRequest.HeadersEntry - 71, // 34: hyapp.game.v1.CallbackRequest.query:type_name -> hyapp.game.v1.CallbackRequest.QueryEntry - 0, // 35: hyapp.game.v1.CronBatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 36: hyapp.game.v1.ListPlatformsRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 1, // 37: hyapp.game.v1.ListPlatformsResponse.platforms:type_name -> hyapp.game.v1.GamePlatform - 0, // 38: hyapp.game.v1.UpsertPlatformRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 1, // 39: hyapp.game.v1.UpsertPlatformRequest.platform:type_name -> hyapp.game.v1.GamePlatform - 1, // 40: hyapp.game.v1.PlatformResponse.platform:type_name -> hyapp.game.v1.GamePlatform - 0, // 41: hyapp.game.v1.ListCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 2, // 42: hyapp.game.v1.ListCatalogResponse.games:type_name -> hyapp.game.v1.GameCatalogItem - 0, // 43: hyapp.game.v1.UpsertCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 2, // 44: hyapp.game.v1.UpsertCatalogRequest.game:type_name -> hyapp.game.v1.GameCatalogItem - 2, // 45: hyapp.game.v1.CatalogResponse.game:type_name -> hyapp.game.v1.GameCatalogItem - 0, // 46: hyapp.game.v1.SetGameStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 47: hyapp.game.v1.DeleteCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 48: hyapp.game.v1.ListSelfGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 15, // 49: hyapp.game.v1.ListSelfGamesResponse.games:type_name -> hyapp.game.v1.DiceConfig - 0, // 50: hyapp.game.v1.UpdateDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 15, // 51: hyapp.game.v1.UpdateDiceConfigRequest.config:type_name -> hyapp.game.v1.DiceConfig - 0, // 52: hyapp.game.v1.AdjustDicePoolRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 58, // 53: hyapp.game.v1.AdjustDicePoolResponse.adjustment:type_name -> hyapp.game.v1.DicePoolAdjustment - 15, // 54: hyapp.game.v1.AdjustDicePoolResponse.config:type_name -> hyapp.game.v1.DiceConfig - 0, // 55: hyapp.game.v1.ListDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 61, // 56: hyapp.game.v1.ListDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot - 0, // 57: hyapp.game.v1.RegisterDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 61, // 58: hyapp.game.v1.RegisterDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot - 0, // 59: hyapp.game.v1.SetDiceRobotStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 0, // 60: hyapp.game.v1.DeleteDiceRobotRequest.meta:type_name -> hyapp.game.v1.RequestMeta - 61, // 61: hyapp.game.v1.DiceRobotResponse.robot:type_name -> hyapp.game.v1.DiceRobot - 4, // 62: hyapp.game.v1.GameAppService.ListGames:input_type -> hyapp.game.v1.ListGamesRequest - 6, // 63: hyapp.game.v1.GameAppService.ListRecentGames:input_type -> hyapp.game.v1.ListRecentGamesRequest - 7, // 64: hyapp.game.v1.GameAppService.GetBridgeScript:input_type -> hyapp.game.v1.GetBridgeScriptRequest - 10, // 65: hyapp.game.v1.GameAppService.LaunchGame:input_type -> hyapp.game.v1.LaunchGameRequest - 16, // 66: hyapp.game.v1.GameAppService.GetDiceConfig:input_type -> hyapp.game.v1.GetDiceConfigRequest - 37, // 67: hyapp.game.v1.GameAppService.MatchDice:input_type -> hyapp.game.v1.MatchDiceRequest - 33, // 68: hyapp.game.v1.GameAppService.CreateDiceMatch:input_type -> hyapp.game.v1.CreateDiceMatchRequest - 34, // 69: hyapp.game.v1.GameAppService.JoinDiceMatch:input_type -> hyapp.game.v1.JoinDiceMatchRequest - 35, // 70: hyapp.game.v1.GameAppService.GetDiceMatch:input_type -> hyapp.game.v1.GetDiceMatchRequest - 36, // 71: hyapp.game.v1.GameAppService.RollDiceMatch:input_type -> hyapp.game.v1.RollDiceMatchRequest - 38, // 72: hyapp.game.v1.GameAppService.CancelDiceMatch:input_type -> hyapp.game.v1.CancelDiceMatchRequest - 22, // 73: hyapp.game.v1.GameAppService.GetRoomRPSConfig:input_type -> hyapp.game.v1.GetRoomRPSConfigRequest - 27, // 74: hyapp.game.v1.GameAppService.ListRoomRPSChallenges:input_type -> hyapp.game.v1.ListRoomRPSChallengesRequest - 24, // 75: hyapp.game.v1.GameAppService.CreateRoomRPSChallenge:input_type -> hyapp.game.v1.CreateRoomRPSChallengeRequest - 25, // 76: hyapp.game.v1.GameAppService.AcceptRoomRPSChallenge:input_type -> hyapp.game.v1.AcceptRoomRPSChallengeRequest - 26, // 77: hyapp.game.v1.GameAppService.GetRoomRPSChallenge:input_type -> hyapp.game.v1.GetRoomRPSChallengeRequest - 40, // 78: hyapp.game.v1.GameCallbackService.HandleCallback:input_type -> hyapp.game.v1.CallbackRequest - 42, // 79: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:input_type -> hyapp.game.v1.CronBatchRequest - 44, // 80: hyapp.game.v1.GameAdminService.ListPlatforms:input_type -> hyapp.game.v1.ListPlatformsRequest - 46, // 81: hyapp.game.v1.GameAdminService.UpsertPlatform:input_type -> hyapp.game.v1.UpsertPlatformRequest - 48, // 82: hyapp.game.v1.GameAdminService.ListCatalog:input_type -> hyapp.game.v1.ListCatalogRequest - 50, // 83: hyapp.game.v1.GameAdminService.UpsertCatalog:input_type -> hyapp.game.v1.UpsertCatalogRequest - 52, // 84: hyapp.game.v1.GameAdminService.SetGameStatus:input_type -> hyapp.game.v1.SetGameStatusRequest - 53, // 85: hyapp.game.v1.GameAdminService.DeleteCatalog:input_type -> hyapp.game.v1.DeleteCatalogRequest - 55, // 86: hyapp.game.v1.GameAdminService.ListSelfGames:input_type -> hyapp.game.v1.ListSelfGamesRequest - 57, // 87: hyapp.game.v1.GameAdminService.UpdateDiceConfig:input_type -> hyapp.game.v1.UpdateDiceConfigRequest - 59, // 88: hyapp.game.v1.GameAdminService.AdjustDicePool:input_type -> hyapp.game.v1.AdjustDicePoolRequest - 62, // 89: hyapp.game.v1.GameAdminService.ListDiceRobots:input_type -> hyapp.game.v1.ListDiceRobotsRequest - 64, // 90: hyapp.game.v1.GameAdminService.RegisterDiceRobots:input_type -> hyapp.game.v1.RegisterDiceRobotsRequest - 66, // 91: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:input_type -> hyapp.game.v1.SetDiceRobotStatusRequest - 67, // 92: hyapp.game.v1.GameAdminService.DeleteDiceRobot:input_type -> hyapp.game.v1.DeleteDiceRobotRequest - 22, // 93: hyapp.game.v1.GameAdminService.GetRoomRPSConfig:input_type -> hyapp.game.v1.GetRoomRPSConfigRequest - 30, // 94: hyapp.game.v1.GameAdminService.UpdateRoomRPSConfig:input_type -> hyapp.game.v1.UpdateRoomRPSConfigRequest - 27, // 95: hyapp.game.v1.GameAdminService.ListRoomRPSChallenges:input_type -> hyapp.game.v1.ListRoomRPSChallengesRequest - 26, // 96: hyapp.game.v1.GameAdminService.GetRoomRPSChallenge:input_type -> hyapp.game.v1.GetRoomRPSChallengeRequest - 31, // 97: hyapp.game.v1.GameAdminService.RetryRoomRPSSettlement:input_type -> hyapp.game.v1.RetryRoomRPSSettlementRequest - 32, // 98: hyapp.game.v1.GameAdminService.ExpireRoomRPSChallenge:input_type -> hyapp.game.v1.ExpireRoomRPSChallengeRequest - 5, // 99: hyapp.game.v1.GameAppService.ListGames:output_type -> hyapp.game.v1.ListGamesResponse - 5, // 100: hyapp.game.v1.GameAppService.ListRecentGames:output_type -> hyapp.game.v1.ListGamesResponse - 9, // 101: hyapp.game.v1.GameAppService.GetBridgeScript:output_type -> hyapp.game.v1.GetBridgeScriptResponse - 11, // 102: hyapp.game.v1.GameAppService.LaunchGame:output_type -> hyapp.game.v1.LaunchGameResponse - 17, // 103: hyapp.game.v1.GameAppService.GetDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse - 39, // 104: hyapp.game.v1.GameAppService.MatchDice:output_type -> hyapp.game.v1.DiceMatchResponse - 39, // 105: hyapp.game.v1.GameAppService.CreateDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 39, // 106: hyapp.game.v1.GameAppService.JoinDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 39, // 107: hyapp.game.v1.GameAppService.GetDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 39, // 108: hyapp.game.v1.GameAppService.RollDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 39, // 109: hyapp.game.v1.GameAppService.CancelDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse - 23, // 110: hyapp.game.v1.GameAppService.GetRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse - 29, // 111: hyapp.game.v1.GameAppService.ListRoomRPSChallenges:output_type -> hyapp.game.v1.ListRoomRPSChallengesResponse - 28, // 112: hyapp.game.v1.GameAppService.CreateRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse - 28, // 113: hyapp.game.v1.GameAppService.AcceptRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse - 28, // 114: hyapp.game.v1.GameAppService.GetRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse - 41, // 115: hyapp.game.v1.GameCallbackService.HandleCallback:output_type -> hyapp.game.v1.CallbackResponse - 43, // 116: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:output_type -> hyapp.game.v1.CronBatchResponse - 45, // 117: hyapp.game.v1.GameAdminService.ListPlatforms:output_type -> hyapp.game.v1.ListPlatformsResponse - 47, // 118: hyapp.game.v1.GameAdminService.UpsertPlatform:output_type -> hyapp.game.v1.PlatformResponse - 49, // 119: hyapp.game.v1.GameAdminService.ListCatalog:output_type -> hyapp.game.v1.ListCatalogResponse - 51, // 120: hyapp.game.v1.GameAdminService.UpsertCatalog:output_type -> hyapp.game.v1.CatalogResponse - 51, // 121: hyapp.game.v1.GameAdminService.SetGameStatus:output_type -> hyapp.game.v1.CatalogResponse - 54, // 122: hyapp.game.v1.GameAdminService.DeleteCatalog:output_type -> hyapp.game.v1.DeleteCatalogResponse - 56, // 123: hyapp.game.v1.GameAdminService.ListSelfGames:output_type -> hyapp.game.v1.ListSelfGamesResponse - 17, // 124: hyapp.game.v1.GameAdminService.UpdateDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse - 60, // 125: hyapp.game.v1.GameAdminService.AdjustDicePool:output_type -> hyapp.game.v1.AdjustDicePoolResponse - 63, // 126: hyapp.game.v1.GameAdminService.ListDiceRobots:output_type -> hyapp.game.v1.ListDiceRobotsResponse - 65, // 127: hyapp.game.v1.GameAdminService.RegisterDiceRobots:output_type -> hyapp.game.v1.RegisterDiceRobotsResponse - 69, // 128: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:output_type -> hyapp.game.v1.DiceRobotResponse - 68, // 129: hyapp.game.v1.GameAdminService.DeleteDiceRobot:output_type -> hyapp.game.v1.DeleteDiceRobotResponse - 23, // 130: hyapp.game.v1.GameAdminService.GetRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse - 23, // 131: hyapp.game.v1.GameAdminService.UpdateRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse - 29, // 132: hyapp.game.v1.GameAdminService.ListRoomRPSChallenges:output_type -> hyapp.game.v1.ListRoomRPSChallengesResponse - 28, // 133: hyapp.game.v1.GameAdminService.GetRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse - 28, // 134: hyapp.game.v1.GameAdminService.RetryRoomRPSSettlement:output_type -> hyapp.game.v1.RoomRPSChallengeResponse - 28, // 135: hyapp.game.v1.GameAdminService.ExpireRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse - 99, // [99:136] is the sub-list for method output_type - 62, // [62:99] is the sub-list for method input_type - 62, // [62:62] is the sub-list for extension type_name - 62, // [62:62] is the sub-list for extension extendee - 0, // [0:62] is the sub-list for field type_name + 0, // 0: hyapp.game.v1.ListGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 3, // 1: hyapp.game.v1.ListGamesResponse.games:type_name -> hyapp.game.v1.AppGame + 0, // 2: hyapp.game.v1.ListRecentGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 3: hyapp.game.v1.ListExploreWinnersRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 8, // 4: hyapp.game.v1.ListExploreWinnersResponse.winners:type_name -> hyapp.game.v1.ExploreWinner + 0, // 5: hyapp.game.v1.GetBridgeScriptRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 11, // 6: hyapp.game.v1.GetBridgeScriptResponse.config:type_name -> hyapp.game.v1.GameBridgeScriptConfig + 0, // 7: hyapp.game.v1.LaunchGameRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 15, // 8: hyapp.game.v1.DiceMatch.participants:type_name -> hyapp.game.v1.DiceParticipant + 17, // 9: hyapp.game.v1.DiceConfig.stake_options:type_name -> hyapp.game.v1.DiceStakeOption + 0, // 10: hyapp.game.v1.GetDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 18, // 11: hyapp.game.v1.DiceConfigResponse.config:type_name -> hyapp.game.v1.DiceConfig + 21, // 12: hyapp.game.v1.RoomRPSConfig.stake_gifts:type_name -> hyapp.game.v1.RoomRPSStakeGift + 23, // 13: hyapp.game.v1.RoomRPSChallenge.initiator:type_name -> hyapp.game.v1.RoomRPSPlayer + 23, // 14: hyapp.game.v1.RoomRPSChallenge.challenger:type_name -> hyapp.game.v1.RoomRPSPlayer + 0, // 15: hyapp.game.v1.GetRoomRPSConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 22, // 16: hyapp.game.v1.RoomRPSConfigResponse.config:type_name -> hyapp.game.v1.RoomRPSConfig + 0, // 17: hyapp.game.v1.CreateRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 18: hyapp.game.v1.AcceptRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 19: hyapp.game.v1.GetRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 20: hyapp.game.v1.ListRoomRPSChallengesRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 24, // 21: hyapp.game.v1.RoomRPSChallengeResponse.challenge:type_name -> hyapp.game.v1.RoomRPSChallenge + 24, // 22: hyapp.game.v1.ListRoomRPSChallengesResponse.challenges:type_name -> hyapp.game.v1.RoomRPSChallenge + 0, // 23: hyapp.game.v1.UpdateRoomRPSConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 22, // 24: hyapp.game.v1.UpdateRoomRPSConfigRequest.config:type_name -> hyapp.game.v1.RoomRPSConfig + 0, // 25: hyapp.game.v1.RetryRoomRPSSettlementRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 26: hyapp.game.v1.ExpireRoomRPSChallengeRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 27: hyapp.game.v1.CreateDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 28: hyapp.game.v1.JoinDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 29: hyapp.game.v1.GetDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 30: hyapp.game.v1.RollDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 31: hyapp.game.v1.MatchDiceRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 32: hyapp.game.v1.CancelDiceMatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 16, // 33: hyapp.game.v1.DiceMatchResponse.match:type_name -> hyapp.game.v1.DiceMatch + 0, // 34: hyapp.game.v1.CallbackRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 73, // 35: hyapp.game.v1.CallbackRequest.headers:type_name -> hyapp.game.v1.CallbackRequest.HeadersEntry + 74, // 36: hyapp.game.v1.CallbackRequest.query:type_name -> hyapp.game.v1.CallbackRequest.QueryEntry + 0, // 37: hyapp.game.v1.CronBatchRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 38: hyapp.game.v1.ListPlatformsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 1, // 39: hyapp.game.v1.ListPlatformsResponse.platforms:type_name -> hyapp.game.v1.GamePlatform + 0, // 40: hyapp.game.v1.UpsertPlatformRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 1, // 41: hyapp.game.v1.UpsertPlatformRequest.platform:type_name -> hyapp.game.v1.GamePlatform + 1, // 42: hyapp.game.v1.PlatformResponse.platform:type_name -> hyapp.game.v1.GamePlatform + 0, // 43: hyapp.game.v1.ListCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 2, // 44: hyapp.game.v1.ListCatalogResponse.games:type_name -> hyapp.game.v1.GameCatalogItem + 0, // 45: hyapp.game.v1.UpsertCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 2, // 46: hyapp.game.v1.UpsertCatalogRequest.game:type_name -> hyapp.game.v1.GameCatalogItem + 2, // 47: hyapp.game.v1.CatalogResponse.game:type_name -> hyapp.game.v1.GameCatalogItem + 0, // 48: hyapp.game.v1.SetGameStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 49: hyapp.game.v1.DeleteCatalogRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 50: hyapp.game.v1.ListSelfGamesRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 18, // 51: hyapp.game.v1.ListSelfGamesResponse.games:type_name -> hyapp.game.v1.DiceConfig + 0, // 52: hyapp.game.v1.UpdateDiceConfigRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 18, // 53: hyapp.game.v1.UpdateDiceConfigRequest.config:type_name -> hyapp.game.v1.DiceConfig + 0, // 54: hyapp.game.v1.AdjustDicePoolRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 61, // 55: hyapp.game.v1.AdjustDicePoolResponse.adjustment:type_name -> hyapp.game.v1.DicePoolAdjustment + 18, // 56: hyapp.game.v1.AdjustDicePoolResponse.config:type_name -> hyapp.game.v1.DiceConfig + 0, // 57: hyapp.game.v1.ListDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 64, // 58: hyapp.game.v1.ListDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot + 0, // 59: hyapp.game.v1.RegisterDiceRobotsRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 64, // 60: hyapp.game.v1.RegisterDiceRobotsResponse.robots:type_name -> hyapp.game.v1.DiceRobot + 0, // 61: hyapp.game.v1.SetDiceRobotStatusRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 0, // 62: hyapp.game.v1.DeleteDiceRobotRequest.meta:type_name -> hyapp.game.v1.RequestMeta + 64, // 63: hyapp.game.v1.DiceRobotResponse.robot:type_name -> hyapp.game.v1.DiceRobot + 4, // 64: hyapp.game.v1.GameAppService.ListGames:input_type -> hyapp.game.v1.ListGamesRequest + 6, // 65: hyapp.game.v1.GameAppService.ListRecentGames:input_type -> hyapp.game.v1.ListRecentGamesRequest + 7, // 66: hyapp.game.v1.GameAppService.ListExploreWinners:input_type -> hyapp.game.v1.ListExploreWinnersRequest + 10, // 67: hyapp.game.v1.GameAppService.GetBridgeScript:input_type -> hyapp.game.v1.GetBridgeScriptRequest + 13, // 68: hyapp.game.v1.GameAppService.LaunchGame:input_type -> hyapp.game.v1.LaunchGameRequest + 19, // 69: hyapp.game.v1.GameAppService.GetDiceConfig:input_type -> hyapp.game.v1.GetDiceConfigRequest + 40, // 70: hyapp.game.v1.GameAppService.MatchDice:input_type -> hyapp.game.v1.MatchDiceRequest + 36, // 71: hyapp.game.v1.GameAppService.CreateDiceMatch:input_type -> hyapp.game.v1.CreateDiceMatchRequest + 37, // 72: hyapp.game.v1.GameAppService.JoinDiceMatch:input_type -> hyapp.game.v1.JoinDiceMatchRequest + 38, // 73: hyapp.game.v1.GameAppService.GetDiceMatch:input_type -> hyapp.game.v1.GetDiceMatchRequest + 39, // 74: hyapp.game.v1.GameAppService.RollDiceMatch:input_type -> hyapp.game.v1.RollDiceMatchRequest + 41, // 75: hyapp.game.v1.GameAppService.CancelDiceMatch:input_type -> hyapp.game.v1.CancelDiceMatchRequest + 25, // 76: hyapp.game.v1.GameAppService.GetRoomRPSConfig:input_type -> hyapp.game.v1.GetRoomRPSConfigRequest + 30, // 77: hyapp.game.v1.GameAppService.ListRoomRPSChallenges:input_type -> hyapp.game.v1.ListRoomRPSChallengesRequest + 27, // 78: hyapp.game.v1.GameAppService.CreateRoomRPSChallenge:input_type -> hyapp.game.v1.CreateRoomRPSChallengeRequest + 28, // 79: hyapp.game.v1.GameAppService.AcceptRoomRPSChallenge:input_type -> hyapp.game.v1.AcceptRoomRPSChallengeRequest + 29, // 80: hyapp.game.v1.GameAppService.GetRoomRPSChallenge:input_type -> hyapp.game.v1.GetRoomRPSChallengeRequest + 43, // 81: hyapp.game.v1.GameCallbackService.HandleCallback:input_type -> hyapp.game.v1.CallbackRequest + 45, // 82: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:input_type -> hyapp.game.v1.CronBatchRequest + 47, // 83: hyapp.game.v1.GameAdminService.ListPlatforms:input_type -> hyapp.game.v1.ListPlatformsRequest + 49, // 84: hyapp.game.v1.GameAdminService.UpsertPlatform:input_type -> hyapp.game.v1.UpsertPlatformRequest + 51, // 85: hyapp.game.v1.GameAdminService.ListCatalog:input_type -> hyapp.game.v1.ListCatalogRequest + 53, // 86: hyapp.game.v1.GameAdminService.UpsertCatalog:input_type -> hyapp.game.v1.UpsertCatalogRequest + 55, // 87: hyapp.game.v1.GameAdminService.SetGameStatus:input_type -> hyapp.game.v1.SetGameStatusRequest + 56, // 88: hyapp.game.v1.GameAdminService.DeleteCatalog:input_type -> hyapp.game.v1.DeleteCatalogRequest + 58, // 89: hyapp.game.v1.GameAdminService.ListSelfGames:input_type -> hyapp.game.v1.ListSelfGamesRequest + 60, // 90: hyapp.game.v1.GameAdminService.UpdateDiceConfig:input_type -> hyapp.game.v1.UpdateDiceConfigRequest + 62, // 91: hyapp.game.v1.GameAdminService.AdjustDicePool:input_type -> hyapp.game.v1.AdjustDicePoolRequest + 65, // 92: hyapp.game.v1.GameAdminService.ListDiceRobots:input_type -> hyapp.game.v1.ListDiceRobotsRequest + 67, // 93: hyapp.game.v1.GameAdminService.RegisterDiceRobots:input_type -> hyapp.game.v1.RegisterDiceRobotsRequest + 69, // 94: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:input_type -> hyapp.game.v1.SetDiceRobotStatusRequest + 70, // 95: hyapp.game.v1.GameAdminService.DeleteDiceRobot:input_type -> hyapp.game.v1.DeleteDiceRobotRequest + 25, // 96: hyapp.game.v1.GameAdminService.GetRoomRPSConfig:input_type -> hyapp.game.v1.GetRoomRPSConfigRequest + 33, // 97: hyapp.game.v1.GameAdminService.UpdateRoomRPSConfig:input_type -> hyapp.game.v1.UpdateRoomRPSConfigRequest + 30, // 98: hyapp.game.v1.GameAdminService.ListRoomRPSChallenges:input_type -> hyapp.game.v1.ListRoomRPSChallengesRequest + 29, // 99: hyapp.game.v1.GameAdminService.GetRoomRPSChallenge:input_type -> hyapp.game.v1.GetRoomRPSChallengeRequest + 34, // 100: hyapp.game.v1.GameAdminService.RetryRoomRPSSettlement:input_type -> hyapp.game.v1.RetryRoomRPSSettlementRequest + 35, // 101: hyapp.game.v1.GameAdminService.ExpireRoomRPSChallenge:input_type -> hyapp.game.v1.ExpireRoomRPSChallengeRequest + 5, // 102: hyapp.game.v1.GameAppService.ListGames:output_type -> hyapp.game.v1.ListGamesResponse + 5, // 103: hyapp.game.v1.GameAppService.ListRecentGames:output_type -> hyapp.game.v1.ListGamesResponse + 9, // 104: hyapp.game.v1.GameAppService.ListExploreWinners:output_type -> hyapp.game.v1.ListExploreWinnersResponse + 12, // 105: hyapp.game.v1.GameAppService.GetBridgeScript:output_type -> hyapp.game.v1.GetBridgeScriptResponse + 14, // 106: hyapp.game.v1.GameAppService.LaunchGame:output_type -> hyapp.game.v1.LaunchGameResponse + 20, // 107: hyapp.game.v1.GameAppService.GetDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse + 42, // 108: hyapp.game.v1.GameAppService.MatchDice:output_type -> hyapp.game.v1.DiceMatchResponse + 42, // 109: hyapp.game.v1.GameAppService.CreateDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 42, // 110: hyapp.game.v1.GameAppService.JoinDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 42, // 111: hyapp.game.v1.GameAppService.GetDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 42, // 112: hyapp.game.v1.GameAppService.RollDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 42, // 113: hyapp.game.v1.GameAppService.CancelDiceMatch:output_type -> hyapp.game.v1.DiceMatchResponse + 26, // 114: hyapp.game.v1.GameAppService.GetRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse + 32, // 115: hyapp.game.v1.GameAppService.ListRoomRPSChallenges:output_type -> hyapp.game.v1.ListRoomRPSChallengesResponse + 31, // 116: hyapp.game.v1.GameAppService.CreateRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 31, // 117: hyapp.game.v1.GameAppService.AcceptRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 31, // 118: hyapp.game.v1.GameAppService.GetRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 44, // 119: hyapp.game.v1.GameCallbackService.HandleCallback:output_type -> hyapp.game.v1.CallbackResponse + 46, // 120: hyapp.game.v1.GameCronService.ProcessLevelEventOutboxBatch:output_type -> hyapp.game.v1.CronBatchResponse + 48, // 121: hyapp.game.v1.GameAdminService.ListPlatforms:output_type -> hyapp.game.v1.ListPlatformsResponse + 50, // 122: hyapp.game.v1.GameAdminService.UpsertPlatform:output_type -> hyapp.game.v1.PlatformResponse + 52, // 123: hyapp.game.v1.GameAdminService.ListCatalog:output_type -> hyapp.game.v1.ListCatalogResponse + 54, // 124: hyapp.game.v1.GameAdminService.UpsertCatalog:output_type -> hyapp.game.v1.CatalogResponse + 54, // 125: hyapp.game.v1.GameAdminService.SetGameStatus:output_type -> hyapp.game.v1.CatalogResponse + 57, // 126: hyapp.game.v1.GameAdminService.DeleteCatalog:output_type -> hyapp.game.v1.DeleteCatalogResponse + 59, // 127: hyapp.game.v1.GameAdminService.ListSelfGames:output_type -> hyapp.game.v1.ListSelfGamesResponse + 20, // 128: hyapp.game.v1.GameAdminService.UpdateDiceConfig:output_type -> hyapp.game.v1.DiceConfigResponse + 63, // 129: hyapp.game.v1.GameAdminService.AdjustDicePool:output_type -> hyapp.game.v1.AdjustDicePoolResponse + 66, // 130: hyapp.game.v1.GameAdminService.ListDiceRobots:output_type -> hyapp.game.v1.ListDiceRobotsResponse + 68, // 131: hyapp.game.v1.GameAdminService.RegisterDiceRobots:output_type -> hyapp.game.v1.RegisterDiceRobotsResponse + 72, // 132: hyapp.game.v1.GameAdminService.SetDiceRobotStatus:output_type -> hyapp.game.v1.DiceRobotResponse + 71, // 133: hyapp.game.v1.GameAdminService.DeleteDiceRobot:output_type -> hyapp.game.v1.DeleteDiceRobotResponse + 26, // 134: hyapp.game.v1.GameAdminService.GetRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse + 26, // 135: hyapp.game.v1.GameAdminService.UpdateRoomRPSConfig:output_type -> hyapp.game.v1.RoomRPSConfigResponse + 32, // 136: hyapp.game.v1.GameAdminService.ListRoomRPSChallenges:output_type -> hyapp.game.v1.ListRoomRPSChallengesResponse + 31, // 137: hyapp.game.v1.GameAdminService.GetRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 31, // 138: hyapp.game.v1.GameAdminService.RetryRoomRPSSettlement:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 31, // 139: hyapp.game.v1.GameAdminService.ExpireRoomRPSChallenge:output_type -> hyapp.game.v1.RoomRPSChallengeResponse + 102, // [102:140] is the sub-list for method output_type + 64, // [64:102] is the sub-list for method input_type + 64, // [64:64] is the sub-list for extension type_name + 64, // [64:64] is the sub-list for extension extendee + 0, // [0:64] is the sub-list for field type_name } func init() { file_proto_game_v1_game_proto_init() } @@ -6055,7 +6284,7 @@ func file_proto_game_v1_game_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_game_v1_game_proto_rawDesc), len(file_proto_game_v1_game_proto_rawDesc)), NumEnums: 0, - NumMessages: 72, + NumMessages: 75, NumExtensions: 0, NumServices: 4, }, diff --git a/api/proto/game/v1/game.proto b/api/proto/game/v1/game.proto index dc2ed67d..c49d0e02 100644 --- a/api/proto/game/v1/game.proto +++ b/api/proto/game/v1/game.proto @@ -94,6 +94,27 @@ message ListRecentGamesRequest { int32 page_size = 8; } +message ListExploreWinnersRequest { + RequestMeta meta = 1; + int32 page_size = 2; +} + +message ExploreWinner { + string win_id = 1; + string game_code = 2; + string game_id = 3; + int64 user_id = 4; + string display_name = 5; + string avatar_url = 6; + int64 coin_amount = 7; + int64 won_at_ms = 8; +} + +message ListExploreWinnersResponse { + repeated ExploreWinner winners = 1; + int64 server_time_ms = 2; +} + message GetBridgeScriptRequest { RequestMeta meta = 1; } @@ -564,6 +585,7 @@ message DiceRobotResponse { service GameAppService { rpc ListGames(ListGamesRequest) returns (ListGamesResponse); rpc ListRecentGames(ListRecentGamesRequest) returns (ListGamesResponse); + rpc ListExploreWinners(ListExploreWinnersRequest) returns (ListExploreWinnersResponse); rpc GetBridgeScript(GetBridgeScriptRequest) returns (GetBridgeScriptResponse); rpc LaunchGame(LaunchGameRequest) returns (LaunchGameResponse); rpc GetDiceConfig(GetDiceConfigRequest) returns (DiceConfigResponse); diff --git a/api/proto/game/v1/game_grpc.pb.go b/api/proto/game/v1/game_grpc.pb.go index ee5c8050..ce6d12ac 100644 --- a/api/proto/game/v1/game_grpc.pb.go +++ b/api/proto/game/v1/game_grpc.pb.go @@ -21,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( GameAppService_ListGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListGames" GameAppService_ListRecentGames_FullMethodName = "/hyapp.game.v1.GameAppService/ListRecentGames" + GameAppService_ListExploreWinners_FullMethodName = "/hyapp.game.v1.GameAppService/ListExploreWinners" GameAppService_GetBridgeScript_FullMethodName = "/hyapp.game.v1.GameAppService/GetBridgeScript" GameAppService_LaunchGame_FullMethodName = "/hyapp.game.v1.GameAppService/LaunchGame" GameAppService_GetDiceConfig_FullMethodName = "/hyapp.game.v1.GameAppService/GetDiceConfig" @@ -43,6 +44,7 @@ const ( type GameAppServiceClient interface { ListGames(ctx context.Context, in *ListGamesRequest, opts ...grpc.CallOption) (*ListGamesResponse, error) ListRecentGames(ctx context.Context, in *ListRecentGamesRequest, opts ...grpc.CallOption) (*ListGamesResponse, error) + ListExploreWinners(ctx context.Context, in *ListExploreWinnersRequest, opts ...grpc.CallOption) (*ListExploreWinnersResponse, error) GetBridgeScript(ctx context.Context, in *GetBridgeScriptRequest, opts ...grpc.CallOption) (*GetBridgeScriptResponse, error) LaunchGame(ctx context.Context, in *LaunchGameRequest, opts ...grpc.CallOption) (*LaunchGameResponse, error) GetDiceConfig(ctx context.Context, in *GetDiceConfigRequest, opts ...grpc.CallOption) (*DiceConfigResponse, error) @@ -87,6 +89,16 @@ func (c *gameAppServiceClient) ListRecentGames(ctx context.Context, in *ListRece return out, nil } +func (c *gameAppServiceClient) ListExploreWinners(ctx context.Context, in *ListExploreWinnersRequest, opts ...grpc.CallOption) (*ListExploreWinnersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListExploreWinnersResponse) + err := c.cc.Invoke(ctx, GameAppService_ListExploreWinners_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *gameAppServiceClient) GetBridgeScript(ctx context.Context, in *GetBridgeScriptRequest, opts ...grpc.CallOption) (*GetBridgeScriptResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetBridgeScriptResponse) @@ -233,6 +245,7 @@ func (c *gameAppServiceClient) GetRoomRPSChallenge(ctx context.Context, in *GetR type GameAppServiceServer interface { ListGames(context.Context, *ListGamesRequest) (*ListGamesResponse, error) ListRecentGames(context.Context, *ListRecentGamesRequest) (*ListGamesResponse, error) + ListExploreWinners(context.Context, *ListExploreWinnersRequest) (*ListExploreWinnersResponse, error) GetBridgeScript(context.Context, *GetBridgeScriptRequest) (*GetBridgeScriptResponse, error) LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) GetDiceConfig(context.Context, *GetDiceConfigRequest) (*DiceConfigResponse, error) @@ -263,6 +276,9 @@ func (UnimplementedGameAppServiceServer) ListGames(context.Context, *ListGamesRe func (UnimplementedGameAppServiceServer) ListRecentGames(context.Context, *ListRecentGamesRequest) (*ListGamesResponse, error) { return nil, status.Error(codes.Unimplemented, "method ListRecentGames not implemented") } +func (UnimplementedGameAppServiceServer) ListExploreWinners(context.Context, *ListExploreWinnersRequest) (*ListExploreWinnersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListExploreWinners not implemented") +} func (UnimplementedGameAppServiceServer) GetBridgeScript(context.Context, *GetBridgeScriptRequest) (*GetBridgeScriptResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetBridgeScript not implemented") } @@ -362,6 +378,24 @@ func _GameAppService_ListRecentGames_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _GameAppService_ListExploreWinners_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListExploreWinnersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GameAppServiceServer).ListExploreWinners(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GameAppService_ListExploreWinners_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GameAppServiceServer).ListExploreWinners(ctx, req.(*ListExploreWinnersRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _GameAppService_GetBridgeScript_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetBridgeScriptRequest) if err := dec(in); err != nil { @@ -629,6 +663,10 @@ var GameAppService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListRecentGames", Handler: _GameAppService_ListRecentGames_Handler, }, + { + MethodName: "ListExploreWinners", + Handler: _GameAppService_ListExploreWinners_Handler, + }, { MethodName: "GetBridgeScript", Handler: _GameAppService_GetBridgeScript_Handler, diff --git a/api/proto/user/v1/user.pb.go b/api/proto/user/v1/user.pb.go index 719c21d9..2f4a1917 100644 --- a/api/proto/user/v1/user.pb.go +++ b/api/proto/user/v1/user.pb.go @@ -844,6 +844,219 @@ func (x *InviteBinding) GetInviterUserId() int64 { return 0 } +// InviteAttribution 是被邀请用户到邀请人的只读归因快照,供活动服务按充值事实累计邀请奖励。 +type InviteAttribution struct { + state protoimpl.MessageState `protogen:"open.v1"` + Found bool `protobuf:"varint,1,opt,name=found,proto3" json:"found,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + InvitedUserId int64 `protobuf:"varint,3,opt,name=invited_user_id,json=invitedUserId,proto3" json:"invited_user_id,omitempty"` + InviterUserId int64 `protobuf:"varint,4,opt,name=inviter_user_id,json=inviterUserId,proto3" json:"inviter_user_id,omitempty"` + InviteCode string `protobuf:"bytes,5,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"` + BoundAtMs int64 `protobuf:"varint,6,opt,name=bound_at_ms,json=boundAtMs,proto3" json:"bound_at_ms,omitempty"` + ValidityStatus string `protobuf:"bytes,7,opt,name=validity_status,json=validityStatus,proto3" json:"validity_status,omitempty"` + AccumulatedRechargeCoinAmount int64 `protobuf:"varint,8,opt,name=accumulated_recharge_coin_amount,json=accumulatedRechargeCoinAmount,proto3" json:"accumulated_recharge_coin_amount,omitempty"` + RequiredRechargeCoinAmount int64 `protobuf:"varint,9,opt,name=required_recharge_coin_amount,json=requiredRechargeCoinAmount,proto3" json:"required_recharge_coin_amount,omitempty"` + ValidAtMs int64 `protobuf:"varint,10,opt,name=valid_at_ms,json=validAtMs,proto3" json:"valid_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InviteAttribution) Reset() { + *x = InviteAttribution{} + mi := &file_proto_user_v1_user_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InviteAttribution) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InviteAttribution) ProtoMessage() {} + +func (x *InviteAttribution) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InviteAttribution.ProtoReflect.Descriptor instead. +func (*InviteAttribution) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{7} +} + +func (x *InviteAttribution) GetFound() bool { + if x != nil { + return x.Found + } + return false +} + +func (x *InviteAttribution) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *InviteAttribution) GetInvitedUserId() int64 { + if x != nil { + return x.InvitedUserId + } + return 0 +} + +func (x *InviteAttribution) GetInviterUserId() int64 { + if x != nil { + return x.InviterUserId + } + return 0 +} + +func (x *InviteAttribution) GetInviteCode() string { + if x != nil { + return x.InviteCode + } + return "" +} + +func (x *InviteAttribution) GetBoundAtMs() int64 { + if x != nil { + return x.BoundAtMs + } + return 0 +} + +func (x *InviteAttribution) GetValidityStatus() string { + if x != nil { + return x.ValidityStatus + } + return "" +} + +func (x *InviteAttribution) GetAccumulatedRechargeCoinAmount() int64 { + if x != nil { + return x.AccumulatedRechargeCoinAmount + } + return 0 +} + +func (x *InviteAttribution) GetRequiredRechargeCoinAmount() int64 { + if x != nil { + return x.RequiredRechargeCoinAmount + } + return 0 +} + +func (x *InviteAttribution) GetValidAtMs() int64 { + if x != nil { + return x.ValidAtMs + } + return 0 +} + +type GetInviteAttributionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + InvitedUserId int64 `protobuf:"varint,2,opt,name=invited_user_id,json=invitedUserId,proto3" json:"invited_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetInviteAttributionRequest) Reset() { + *x = GetInviteAttributionRequest{} + mi := &file_proto_user_v1_user_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetInviteAttributionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetInviteAttributionRequest) ProtoMessage() {} + +func (x *GetInviteAttributionRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetInviteAttributionRequest.ProtoReflect.Descriptor instead. +func (*GetInviteAttributionRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{8} +} + +func (x *GetInviteAttributionRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *GetInviteAttributionRequest) GetInvitedUserId() int64 { + if x != nil { + return x.InvitedUserId + } + return 0 +} + +type GetInviteAttributionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Attribution *InviteAttribution `protobuf:"bytes,1,opt,name=attribution,proto3" json:"attribution,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetInviteAttributionResponse) Reset() { + *x = GetInviteAttributionResponse{} + mi := &file_proto_user_v1_user_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetInviteAttributionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetInviteAttributionResponse) ProtoMessage() {} + +func (x *GetInviteAttributionResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_user_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetInviteAttributionResponse.ProtoReflect.Descriptor instead. +func (*GetInviteAttributionResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{9} +} + +func (x *GetInviteAttributionResponse) GetAttribution() *InviteAttribution { + if x != nil { + return x.Attribution + } + return nil +} + // UserMicLifetimeStats 是用户维度的麦上累计基础指标,不区分主播/公会身份。 type UserMicLifetimeStats struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -861,7 +1074,7 @@ type UserMicLifetimeStats struct { func (x *UserMicLifetimeStats) Reset() { *x = UserMicLifetimeStats{} - mi := &file_proto_user_v1_user_proto_msgTypes[7] + mi := &file_proto_user_v1_user_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -873,7 +1086,7 @@ func (x *UserMicLifetimeStats) String() string { func (*UserMicLifetimeStats) ProtoMessage() {} func (x *UserMicLifetimeStats) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[7] + mi := &file_proto_user_v1_user_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -886,7 +1099,7 @@ func (x *UserMicLifetimeStats) ProtoReflect() protoreflect.Message { // Deprecated: Use UserMicLifetimeStats.ProtoReflect.Descriptor instead. func (*UserMicLifetimeStats) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{7} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{10} } func (x *UserMicLifetimeStats) GetUserId() int64 { @@ -956,7 +1169,7 @@ type GetUserMicLifetimeStatsRequest struct { func (x *GetUserMicLifetimeStatsRequest) Reset() { *x = GetUserMicLifetimeStatsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[8] + mi := &file_proto_user_v1_user_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -968,7 +1181,7 @@ func (x *GetUserMicLifetimeStatsRequest) String() string { func (*GetUserMicLifetimeStatsRequest) ProtoMessage() {} func (x *GetUserMicLifetimeStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[8] + mi := &file_proto_user_v1_user_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -981,7 +1194,7 @@ func (x *GetUserMicLifetimeStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserMicLifetimeStatsRequest.ProtoReflect.Descriptor instead. func (*GetUserMicLifetimeStatsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{8} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{11} } func (x *GetUserMicLifetimeStatsRequest) GetMeta() *RequestMeta { @@ -1007,7 +1220,7 @@ type GetUserMicLifetimeStatsResponse struct { func (x *GetUserMicLifetimeStatsResponse) Reset() { *x = GetUserMicLifetimeStatsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[9] + mi := &file_proto_user_v1_user_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1019,7 +1232,7 @@ func (x *GetUserMicLifetimeStatsResponse) String() string { func (*GetUserMicLifetimeStatsResponse) ProtoMessage() {} func (x *GetUserMicLifetimeStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[9] + mi := &file_proto_user_v1_user_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1032,7 +1245,7 @@ func (x *GetUserMicLifetimeStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserMicLifetimeStatsResponse.ProtoReflect.Descriptor instead. func (*GetUserMicLifetimeStatsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{9} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{12} } func (x *GetUserMicLifetimeStatsResponse) GetStats() *UserMicLifetimeStats { @@ -1058,7 +1271,7 @@ type CronBatchRequest struct { func (x *CronBatchRequest) Reset() { *x = CronBatchRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[10] + mi := &file_proto_user_v1_user_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1070,7 +1283,7 @@ func (x *CronBatchRequest) String() string { func (*CronBatchRequest) ProtoMessage() {} func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[10] + mi := &file_proto_user_v1_user_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1083,7 +1296,7 @@ func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchRequest.ProtoReflect.Descriptor instead. func (*CronBatchRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{10} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{13} } func (x *CronBatchRequest) GetMeta() *RequestMeta { @@ -1149,7 +1362,7 @@ type CronBatchResponse struct { func (x *CronBatchResponse) Reset() { *x = CronBatchResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[11] + mi := &file_proto_user_v1_user_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1161,7 +1374,7 @@ func (x *CronBatchResponse) String() string { func (*CronBatchResponse) ProtoMessage() {} func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[11] + mi := &file_proto_user_v1_user_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1174,7 +1387,7 @@ func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchResponse.ProtoReflect.Descriptor instead. func (*CronBatchResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{11} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{14} } func (x *CronBatchResponse) GetClaimedCount() int32 { @@ -1234,7 +1447,7 @@ type AuthToken struct { func (x *AuthToken) Reset() { *x = AuthToken{} - mi := &file_proto_user_v1_user_proto_msgTypes[12] + mi := &file_proto_user_v1_user_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1246,7 +1459,7 @@ func (x *AuthToken) String() string { func (*AuthToken) ProtoMessage() {} func (x *AuthToken) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[12] + mi := &file_proto_user_v1_user_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1259,7 +1472,7 @@ func (x *AuthToken) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthToken.ProtoReflect.Descriptor instead. func (*AuthToken) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{12} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{15} } func (x *AuthToken) GetUserId() int64 { @@ -1364,7 +1577,7 @@ type GetUserRequest struct { func (x *GetUserRequest) Reset() { *x = GetUserRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[13] + mi := &file_proto_user_v1_user_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1376,7 +1589,7 @@ func (x *GetUserRequest) String() string { func (*GetUserRequest) ProtoMessage() {} func (x *GetUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[13] + mi := &file_proto_user_v1_user_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1389,7 +1602,7 @@ func (x *GetUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserRequest.ProtoReflect.Descriptor instead. func (*GetUserRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{13} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{16} } func (x *GetUserRequest) GetMeta() *RequestMeta { @@ -1416,7 +1629,7 @@ type GetUserResponse struct { func (x *GetUserResponse) Reset() { *x = GetUserResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[14] + mi := &file_proto_user_v1_user_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1428,7 +1641,7 @@ func (x *GetUserResponse) String() string { func (*GetUserResponse) ProtoMessage() {} func (x *GetUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[14] + mi := &file_proto_user_v1_user_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1441,7 +1654,7 @@ func (x *GetUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserResponse.ProtoReflect.Descriptor instead. func (*GetUserResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{14} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{17} } func (x *GetUserResponse) GetUser() *User { @@ -1464,7 +1677,7 @@ type BusinessUserLookupRequest struct { func (x *BusinessUserLookupRequest) Reset() { *x = BusinessUserLookupRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[15] + mi := &file_proto_user_v1_user_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1476,7 +1689,7 @@ func (x *BusinessUserLookupRequest) String() string { func (*BusinessUserLookupRequest) ProtoMessage() {} func (x *BusinessUserLookupRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[15] + mi := &file_proto_user_v1_user_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1489,7 +1702,7 @@ func (x *BusinessUserLookupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BusinessUserLookupRequest.ProtoReflect.Descriptor instead. func (*BusinessUserLookupRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{15} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{18} } func (x *BusinessUserLookupRequest) GetMeta() *RequestMeta { @@ -1543,7 +1756,7 @@ type BusinessUserLookupItem struct { func (x *BusinessUserLookupItem) Reset() { *x = BusinessUserLookupItem{} - mi := &file_proto_user_v1_user_proto_msgTypes[16] + mi := &file_proto_user_v1_user_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1555,7 +1768,7 @@ func (x *BusinessUserLookupItem) String() string { func (*BusinessUserLookupItem) ProtoMessage() {} func (x *BusinessUserLookupItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[16] + mi := &file_proto_user_v1_user_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1568,7 +1781,7 @@ func (x *BusinessUserLookupItem) ProtoReflect() protoreflect.Message { // Deprecated: Use BusinessUserLookupItem.ProtoReflect.Descriptor instead. func (*BusinessUserLookupItem) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{16} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{19} } func (x *BusinessUserLookupItem) GetUserId() int64 { @@ -1636,7 +1849,7 @@ type BusinessUserLookupResponse struct { func (x *BusinessUserLookupResponse) Reset() { *x = BusinessUserLookupResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[17] + mi := &file_proto_user_v1_user_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1648,7 +1861,7 @@ func (x *BusinessUserLookupResponse) String() string { func (*BusinessUserLookupResponse) ProtoMessage() {} func (x *BusinessUserLookupResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[17] + mi := &file_proto_user_v1_user_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1661,7 +1874,7 @@ func (x *BusinessUserLookupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BusinessUserLookupResponse.ProtoReflect.Descriptor instead. func (*BusinessUserLookupResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{17} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{20} } func (x *BusinessUserLookupResponse) GetUsers() []*BusinessUserLookupItem { @@ -1686,7 +1899,7 @@ type UserProfileStats struct { func (x *UserProfileStats) Reset() { *x = UserProfileStats{} - mi := &file_proto_user_v1_user_proto_msgTypes[18] + mi := &file_proto_user_v1_user_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1698,7 +1911,7 @@ func (x *UserProfileStats) String() string { func (*UserProfileStats) ProtoMessage() {} func (x *UserProfileStats) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[18] + mi := &file_proto_user_v1_user_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1711,7 +1924,7 @@ func (x *UserProfileStats) ProtoReflect() protoreflect.Message { // Deprecated: Use UserProfileStats.ProtoReflect.Descriptor instead. func (*UserProfileStats) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{18} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{21} } func (x *UserProfileStats) GetUserId() int64 { @@ -1767,7 +1980,7 @@ type GetMyProfileStatsRequest struct { func (x *GetMyProfileStatsRequest) Reset() { *x = GetMyProfileStatsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[19] + mi := &file_proto_user_v1_user_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1779,7 +1992,7 @@ func (x *GetMyProfileStatsRequest) String() string { func (*GetMyProfileStatsRequest) ProtoMessage() {} func (x *GetMyProfileStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[19] + mi := &file_proto_user_v1_user_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1792,7 +2005,7 @@ func (x *GetMyProfileStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyProfileStatsRequest.ProtoReflect.Descriptor instead. func (*GetMyProfileStatsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{19} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{22} } func (x *GetMyProfileStatsRequest) GetMeta() *RequestMeta { @@ -1818,7 +2031,7 @@ type GetMyProfileStatsResponse struct { func (x *GetMyProfileStatsResponse) Reset() { *x = GetMyProfileStatsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[20] + mi := &file_proto_user_v1_user_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1830,7 +2043,7 @@ func (x *GetMyProfileStatsResponse) String() string { func (*GetMyProfileStatsResponse) ProtoMessage() {} func (x *GetMyProfileStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[20] + mi := &file_proto_user_v1_user_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1843,7 +2056,7 @@ func (x *GetMyProfileStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyProfileStatsResponse.ProtoReflect.Descriptor instead. func (*GetMyProfileStatsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{20} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{23} } func (x *GetMyProfileStatsResponse) GetStats() *UserProfileStats { @@ -1864,7 +2077,7 @@ type RecordProfileVisitRequest struct { func (x *RecordProfileVisitRequest) Reset() { *x = RecordProfileVisitRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[21] + mi := &file_proto_user_v1_user_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1876,7 +2089,7 @@ func (x *RecordProfileVisitRequest) String() string { func (*RecordProfileVisitRequest) ProtoMessage() {} func (x *RecordProfileVisitRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[21] + mi := &file_proto_user_v1_user_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1889,7 +2102,7 @@ func (x *RecordProfileVisitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordProfileVisitRequest.ProtoReflect.Descriptor instead. func (*RecordProfileVisitRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{21} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{24} } func (x *RecordProfileVisitRequest) GetMeta() *RequestMeta { @@ -1923,7 +2136,7 @@ type RecordProfileVisitResponse struct { func (x *RecordProfileVisitResponse) Reset() { *x = RecordProfileVisitResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[22] + mi := &file_proto_user_v1_user_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1935,7 +2148,7 @@ func (x *RecordProfileVisitResponse) String() string { func (*RecordProfileVisitResponse) ProtoMessage() {} func (x *RecordProfileVisitResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[22] + mi := &file_proto_user_v1_user_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1948,7 +2161,7 @@ func (x *RecordProfileVisitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordProfileVisitResponse.ProtoReflect.Descriptor instead. func (*RecordProfileVisitResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{22} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{25} } func (x *RecordProfileVisitResponse) GetRecorded() bool { @@ -1977,7 +2190,7 @@ type ProfileVisitRecord struct { func (x *ProfileVisitRecord) Reset() { *x = ProfileVisitRecord{} - mi := &file_proto_user_v1_user_proto_msgTypes[23] + mi := &file_proto_user_v1_user_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1989,7 +2202,7 @@ func (x *ProfileVisitRecord) String() string { func (*ProfileVisitRecord) ProtoMessage() {} func (x *ProfileVisitRecord) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[23] + mi := &file_proto_user_v1_user_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2002,7 +2215,7 @@ func (x *ProfileVisitRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use ProfileVisitRecord.ProtoReflect.Descriptor instead. func (*ProfileVisitRecord) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{23} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{26} } func (x *ProfileVisitRecord) GetVisitorUserId() int64 { @@ -2045,7 +2258,7 @@ type ListProfileVisitorsRequest struct { func (x *ListProfileVisitorsRequest) Reset() { *x = ListProfileVisitorsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[24] + mi := &file_proto_user_v1_user_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2057,7 +2270,7 @@ func (x *ListProfileVisitorsRequest) String() string { func (*ListProfileVisitorsRequest) ProtoMessage() {} func (x *ListProfileVisitorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[24] + mi := &file_proto_user_v1_user_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2070,7 +2283,7 @@ func (x *ListProfileVisitorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProfileVisitorsRequest.ProtoReflect.Descriptor instead. func (*ListProfileVisitorsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{24} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{27} } func (x *ListProfileVisitorsRequest) GetMeta() *RequestMeta { @@ -2111,7 +2324,7 @@ type ListProfileVisitorsResponse struct { func (x *ListProfileVisitorsResponse) Reset() { *x = ListProfileVisitorsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[25] + mi := &file_proto_user_v1_user_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2123,7 +2336,7 @@ func (x *ListProfileVisitorsResponse) String() string { func (*ListProfileVisitorsResponse) ProtoMessage() {} func (x *ListProfileVisitorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[25] + mi := &file_proto_user_v1_user_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2136,7 +2349,7 @@ func (x *ListProfileVisitorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProfileVisitorsResponse.ProtoReflect.Descriptor instead. func (*ListProfileVisitorsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{25} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{28} } func (x *ListProfileVisitorsResponse) GetRecords() []*ProfileVisitRecord { @@ -2164,7 +2377,7 @@ type FollowUserRequest struct { func (x *FollowUserRequest) Reset() { *x = FollowUserRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[26] + mi := &file_proto_user_v1_user_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2176,7 +2389,7 @@ func (x *FollowUserRequest) String() string { func (*FollowUserRequest) ProtoMessage() {} func (x *FollowUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[26] + mi := &file_proto_user_v1_user_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2189,7 +2402,7 @@ func (x *FollowUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FollowUserRequest.ProtoReflect.Descriptor instead. func (*FollowUserRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{26} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{29} } func (x *FollowUserRequest) GetMeta() *RequestMeta { @@ -2223,7 +2436,7 @@ type FollowUserResponse struct { func (x *FollowUserResponse) Reset() { *x = FollowUserResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[27] + mi := &file_proto_user_v1_user_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2235,7 +2448,7 @@ func (x *FollowUserResponse) String() string { func (*FollowUserResponse) ProtoMessage() {} func (x *FollowUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[27] + mi := &file_proto_user_v1_user_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2248,7 +2461,7 @@ func (x *FollowUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FollowUserResponse.ProtoReflect.Descriptor instead. func (*FollowUserResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{27} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{30} } func (x *FollowUserResponse) GetFollowing() bool { @@ -2276,7 +2489,7 @@ type UnfollowUserRequest struct { func (x *UnfollowUserRequest) Reset() { *x = UnfollowUserRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[28] + mi := &file_proto_user_v1_user_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2288,7 +2501,7 @@ func (x *UnfollowUserRequest) String() string { func (*UnfollowUserRequest) ProtoMessage() {} func (x *UnfollowUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[28] + mi := &file_proto_user_v1_user_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2301,7 +2514,7 @@ func (x *UnfollowUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnfollowUserRequest.ProtoReflect.Descriptor instead. func (*UnfollowUserRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{28} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{31} } func (x *UnfollowUserRequest) GetMeta() *RequestMeta { @@ -2335,7 +2548,7 @@ type UnfollowUserResponse struct { func (x *UnfollowUserResponse) Reset() { *x = UnfollowUserResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[29] + mi := &file_proto_user_v1_user_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2347,7 +2560,7 @@ func (x *UnfollowUserResponse) String() string { func (*UnfollowUserResponse) ProtoMessage() {} func (x *UnfollowUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[29] + mi := &file_proto_user_v1_user_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2360,7 +2573,7 @@ func (x *UnfollowUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnfollowUserResponse.ProtoReflect.Descriptor instead. func (*UnfollowUserResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{29} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{32} } func (x *UnfollowUserResponse) GetFollowing() bool { @@ -2388,7 +2601,7 @@ type FollowRecord struct { func (x *FollowRecord) Reset() { *x = FollowRecord{} - mi := &file_proto_user_v1_user_proto_msgTypes[30] + mi := &file_proto_user_v1_user_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2400,7 +2613,7 @@ func (x *FollowRecord) String() string { func (*FollowRecord) ProtoMessage() {} func (x *FollowRecord) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[30] + mi := &file_proto_user_v1_user_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2413,7 +2626,7 @@ func (x *FollowRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use FollowRecord.ProtoReflect.Descriptor instead. func (*FollowRecord) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{30} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{33} } func (x *FollowRecord) GetFollowerUserId() int64 { @@ -2452,7 +2665,7 @@ type ListFollowingRequest struct { func (x *ListFollowingRequest) Reset() { *x = ListFollowingRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[31] + mi := &file_proto_user_v1_user_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2464,7 +2677,7 @@ func (x *ListFollowingRequest) String() string { func (*ListFollowingRequest) ProtoMessage() {} func (x *ListFollowingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[31] + mi := &file_proto_user_v1_user_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2477,7 +2690,7 @@ func (x *ListFollowingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFollowingRequest.ProtoReflect.Descriptor instead. func (*ListFollowingRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{31} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{34} } func (x *ListFollowingRequest) GetMeta() *RequestMeta { @@ -2532,7 +2745,7 @@ type ListFollowingResponse struct { func (x *ListFollowingResponse) Reset() { *x = ListFollowingResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[32] + mi := &file_proto_user_v1_user_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2544,7 +2757,7 @@ func (x *ListFollowingResponse) String() string { func (*ListFollowingResponse) ProtoMessage() {} func (x *ListFollowingResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[32] + mi := &file_proto_user_v1_user_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2557,7 +2770,7 @@ func (x *ListFollowingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFollowingResponse.ProtoReflect.Descriptor instead. func (*ListFollowingResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{32} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{35} } func (x *ListFollowingResponse) GetRecords() []*FollowRecord { @@ -2585,7 +2798,7 @@ type ApplyFriendRequest struct { func (x *ApplyFriendRequest) Reset() { *x = ApplyFriendRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[33] + mi := &file_proto_user_v1_user_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2597,7 +2810,7 @@ func (x *ApplyFriendRequest) String() string { func (*ApplyFriendRequest) ProtoMessage() {} func (x *ApplyFriendRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[33] + mi := &file_proto_user_v1_user_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2610,7 +2823,7 @@ func (x *ApplyFriendRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyFriendRequest.ProtoReflect.Descriptor instead. func (*ApplyFriendRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{33} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{36} } func (x *ApplyFriendRequest) GetMeta() *RequestMeta { @@ -2644,7 +2857,7 @@ type ApplyFriendResponse struct { func (x *ApplyFriendResponse) Reset() { *x = ApplyFriendResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[34] + mi := &file_proto_user_v1_user_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2656,7 +2869,7 @@ func (x *ApplyFriendResponse) String() string { func (*ApplyFriendResponse) ProtoMessage() {} func (x *ApplyFriendResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[34] + mi := &file_proto_user_v1_user_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2669,7 +2882,7 @@ func (x *ApplyFriendResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyFriendResponse.ProtoReflect.Descriptor instead. func (*ApplyFriendResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{34} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{37} } func (x *ApplyFriendResponse) GetApplication() *FriendApplication { @@ -2697,7 +2910,7 @@ type AcceptFriendApplicationRequest struct { func (x *AcceptFriendApplicationRequest) Reset() { *x = AcceptFriendApplicationRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[35] + mi := &file_proto_user_v1_user_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2709,7 +2922,7 @@ func (x *AcceptFriendApplicationRequest) String() string { func (*AcceptFriendApplicationRequest) ProtoMessage() {} func (x *AcceptFriendApplicationRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[35] + mi := &file_proto_user_v1_user_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2722,7 +2935,7 @@ func (x *AcceptFriendApplicationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptFriendApplicationRequest.ProtoReflect.Descriptor instead. func (*AcceptFriendApplicationRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{35} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{38} } func (x *AcceptFriendApplicationRequest) GetMeta() *RequestMeta { @@ -2755,7 +2968,7 @@ type AcceptFriendApplicationResponse struct { func (x *AcceptFriendApplicationResponse) Reset() { *x = AcceptFriendApplicationResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[36] + mi := &file_proto_user_v1_user_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2767,7 +2980,7 @@ func (x *AcceptFriendApplicationResponse) String() string { func (*AcceptFriendApplicationResponse) ProtoMessage() {} func (x *AcceptFriendApplicationResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[36] + mi := &file_proto_user_v1_user_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2780,7 +2993,7 @@ func (x *AcceptFriendApplicationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptFriendApplicationResponse.ProtoReflect.Descriptor instead. func (*AcceptFriendApplicationResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{36} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{39} } func (x *AcceptFriendApplicationResponse) GetFriend() *FriendRecord { @@ -2801,7 +3014,7 @@ type DeleteFriendRequest struct { func (x *DeleteFriendRequest) Reset() { *x = DeleteFriendRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[37] + mi := &file_proto_user_v1_user_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2813,7 +3026,7 @@ func (x *DeleteFriendRequest) String() string { func (*DeleteFriendRequest) ProtoMessage() {} func (x *DeleteFriendRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[37] + mi := &file_proto_user_v1_user_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2826,7 +3039,7 @@ func (x *DeleteFriendRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteFriendRequest.ProtoReflect.Descriptor instead. func (*DeleteFriendRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{37} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{40} } func (x *DeleteFriendRequest) GetMeta() *RequestMeta { @@ -2859,7 +3072,7 @@ type DeleteFriendResponse struct { func (x *DeleteFriendResponse) Reset() { *x = DeleteFriendResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[38] + mi := &file_proto_user_v1_user_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2871,7 +3084,7 @@ func (x *DeleteFriendResponse) String() string { func (*DeleteFriendResponse) ProtoMessage() {} func (x *DeleteFriendResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[38] + mi := &file_proto_user_v1_user_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2884,7 +3097,7 @@ func (x *DeleteFriendResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteFriendResponse.ProtoReflect.Descriptor instead. func (*DeleteFriendResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{38} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{41} } func (x *DeleteFriendResponse) GetDeleted() bool { @@ -2905,7 +3118,7 @@ type FriendRecord struct { func (x *FriendRecord) Reset() { *x = FriendRecord{} - mi := &file_proto_user_v1_user_proto_msgTypes[39] + mi := &file_proto_user_v1_user_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2917,7 +3130,7 @@ func (x *FriendRecord) String() string { func (*FriendRecord) ProtoMessage() {} func (x *FriendRecord) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[39] + mi := &file_proto_user_v1_user_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2930,7 +3143,7 @@ func (x *FriendRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use FriendRecord.ProtoReflect.Descriptor instead. func (*FriendRecord) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{39} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{42} } func (x *FriendRecord) GetUserId() int64 { @@ -2969,7 +3182,7 @@ type ListFriendsRequest struct { func (x *ListFriendsRequest) Reset() { *x = ListFriendsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[40] + mi := &file_proto_user_v1_user_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2981,7 +3194,7 @@ func (x *ListFriendsRequest) String() string { func (*ListFriendsRequest) ProtoMessage() {} func (x *ListFriendsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[40] + mi := &file_proto_user_v1_user_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2994,7 +3207,7 @@ func (x *ListFriendsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFriendsRequest.ProtoReflect.Descriptor instead. func (*ListFriendsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{40} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{43} } func (x *ListFriendsRequest) GetMeta() *RequestMeta { @@ -3049,7 +3262,7 @@ type ListFriendsResponse struct { func (x *ListFriendsResponse) Reset() { *x = ListFriendsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[41] + mi := &file_proto_user_v1_user_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3061,7 +3274,7 @@ func (x *ListFriendsResponse) String() string { func (*ListFriendsResponse) ProtoMessage() {} func (x *ListFriendsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[41] + mi := &file_proto_user_v1_user_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3074,7 +3287,7 @@ func (x *ListFriendsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFriendsResponse.ProtoReflect.Descriptor instead. func (*ListFriendsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{41} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{44} } func (x *ListFriendsResponse) GetRecords() []*FriendRecord { @@ -3104,7 +3317,7 @@ type FriendApplication struct { func (x *FriendApplication) Reset() { *x = FriendApplication{} - mi := &file_proto_user_v1_user_proto_msgTypes[42] + mi := &file_proto_user_v1_user_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3116,7 +3329,7 @@ func (x *FriendApplication) String() string { func (*FriendApplication) ProtoMessage() {} func (x *FriendApplication) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[42] + mi := &file_proto_user_v1_user_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3129,7 +3342,7 @@ func (x *FriendApplication) ProtoReflect() protoreflect.Message { // Deprecated: Use FriendApplication.ProtoReflect.Descriptor instead. func (*FriendApplication) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{42} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{45} } func (x *FriendApplication) GetRequesterUserId() int64 { @@ -3180,7 +3393,7 @@ type ListFriendApplicationsRequest struct { func (x *ListFriendApplicationsRequest) Reset() { *x = ListFriendApplicationsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[43] + mi := &file_proto_user_v1_user_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3192,7 +3405,7 @@ func (x *ListFriendApplicationsRequest) String() string { func (*ListFriendApplicationsRequest) ProtoMessage() {} func (x *ListFriendApplicationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[43] + mi := &file_proto_user_v1_user_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3205,7 +3418,7 @@ func (x *ListFriendApplicationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFriendApplicationsRequest.ProtoReflect.Descriptor instead. func (*ListFriendApplicationsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{43} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{46} } func (x *ListFriendApplicationsRequest) GetMeta() *RequestMeta { @@ -3253,7 +3466,7 @@ type ListFriendApplicationsResponse struct { func (x *ListFriendApplicationsResponse) Reset() { *x = ListFriendApplicationsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[44] + mi := &file_proto_user_v1_user_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3265,7 +3478,7 @@ func (x *ListFriendApplicationsResponse) String() string { func (*ListFriendApplicationsResponse) ProtoMessage() {} func (x *ListFriendApplicationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[44] + mi := &file_proto_user_v1_user_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3278,7 +3491,7 @@ func (x *ListFriendApplicationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFriendApplicationsResponse.ProtoReflect.Descriptor instead. func (*ListFriendApplicationsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{44} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{47} } func (x *ListFriendApplicationsResponse) GetApplications() []*FriendApplication { @@ -3308,7 +3521,7 @@ type CPUserProfile struct { func (x *CPUserProfile) Reset() { *x = CPUserProfile{} - mi := &file_proto_user_v1_user_proto_msgTypes[45] + mi := &file_proto_user_v1_user_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3320,7 +3533,7 @@ func (x *CPUserProfile) String() string { func (*CPUserProfile) ProtoMessage() {} func (x *CPUserProfile) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[45] + mi := &file_proto_user_v1_user_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3333,7 +3546,7 @@ func (x *CPUserProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use CPUserProfile.ProtoReflect.Descriptor instead. func (*CPUserProfile) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{45} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{48} } func (x *CPUserProfile) GetUserId() int64 { @@ -3380,7 +3593,7 @@ type CPGiftSnapshot struct { func (x *CPGiftSnapshot) Reset() { *x = CPGiftSnapshot{} - mi := &file_proto_user_v1_user_proto_msgTypes[46] + mi := &file_proto_user_v1_user_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3392,7 +3605,7 @@ func (x *CPGiftSnapshot) String() string { func (*CPGiftSnapshot) ProtoMessage() {} func (x *CPGiftSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[46] + mi := &file_proto_user_v1_user_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3405,7 +3618,7 @@ func (x *CPGiftSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use CPGiftSnapshot.ProtoReflect.Descriptor instead. func (*CPGiftSnapshot) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{46} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{49} } func (x *CPGiftSnapshot) GetGiftId() string { @@ -3478,7 +3691,7 @@ type CPApplication struct { func (x *CPApplication) Reset() { *x = CPApplication{} - mi := &file_proto_user_v1_user_proto_msgTypes[47] + mi := &file_proto_user_v1_user_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3490,7 +3703,7 @@ func (x *CPApplication) String() string { func (*CPApplication) ProtoMessage() {} func (x *CPApplication) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[47] + mi := &file_proto_user_v1_user_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3503,7 +3716,7 @@ func (x *CPApplication) ProtoReflect() protoreflect.Message { // Deprecated: Use CPApplication.ProtoReflect.Descriptor instead. func (*CPApplication) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{47} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{50} } func (x *CPApplication) GetApplicationId() string { @@ -3616,7 +3829,7 @@ type CPRelationship struct { func (x *CPRelationship) Reset() { *x = CPRelationship{} - mi := &file_proto_user_v1_user_proto_msgTypes[48] + mi := &file_proto_user_v1_user_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3628,7 +3841,7 @@ func (x *CPRelationship) String() string { func (*CPRelationship) ProtoMessage() {} func (x *CPRelationship) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[48] + mi := &file_proto_user_v1_user_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3641,7 +3854,7 @@ func (x *CPRelationship) ProtoReflect() protoreflect.Message { // Deprecated: Use CPRelationship.ProtoReflect.Descriptor instead. func (*CPRelationship) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{48} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{51} } func (x *CPRelationship) GetRelationshipId() string { @@ -3772,7 +3985,7 @@ type CPAvatarFrameSnapshot struct { func (x *CPAvatarFrameSnapshot) Reset() { *x = CPAvatarFrameSnapshot{} - mi := &file_proto_user_v1_user_proto_msgTypes[49] + mi := &file_proto_user_v1_user_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3784,7 +3997,7 @@ func (x *CPAvatarFrameSnapshot) String() string { func (*CPAvatarFrameSnapshot) ProtoMessage() {} func (x *CPAvatarFrameSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[49] + mi := &file_proto_user_v1_user_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3797,7 +4010,7 @@ func (x *CPAvatarFrameSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use CPAvatarFrameSnapshot.ProtoReflect.Descriptor instead. func (*CPAvatarFrameSnapshot) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{49} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{52} } func (x *CPAvatarFrameSnapshot) GetResourceId() int64 { @@ -3863,7 +4076,7 @@ type CPIntimacyLeaderboardUser struct { func (x *CPIntimacyLeaderboardUser) Reset() { *x = CPIntimacyLeaderboardUser{} - mi := &file_proto_user_v1_user_proto_msgTypes[50] + mi := &file_proto_user_v1_user_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3875,7 +4088,7 @@ func (x *CPIntimacyLeaderboardUser) String() string { func (*CPIntimacyLeaderboardUser) ProtoMessage() {} func (x *CPIntimacyLeaderboardUser) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[50] + mi := &file_proto_user_v1_user_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3888,7 +4101,7 @@ func (x *CPIntimacyLeaderboardUser) ProtoReflect() protoreflect.Message { // Deprecated: Use CPIntimacyLeaderboardUser.ProtoReflect.Descriptor instead. func (*CPIntimacyLeaderboardUser) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{50} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{53} } func (x *CPIntimacyLeaderboardUser) GetUserId() int64 { @@ -3944,7 +4157,7 @@ type CPIntimacyLeaderboardItem struct { func (x *CPIntimacyLeaderboardItem) Reset() { *x = CPIntimacyLeaderboardItem{} - mi := &file_proto_user_v1_user_proto_msgTypes[51] + mi := &file_proto_user_v1_user_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3956,7 +4169,7 @@ func (x *CPIntimacyLeaderboardItem) String() string { func (*CPIntimacyLeaderboardItem) ProtoMessage() {} func (x *CPIntimacyLeaderboardItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[51] + mi := &file_proto_user_v1_user_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3969,7 +4182,7 @@ func (x *CPIntimacyLeaderboardItem) ProtoReflect() protoreflect.Message { // Deprecated: Use CPIntimacyLeaderboardItem.ProtoReflect.Descriptor instead. func (*CPIntimacyLeaderboardItem) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{51} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{54} } func (x *CPIntimacyLeaderboardItem) GetRank() int64 { @@ -4049,7 +4262,7 @@ type ListCPApplicationsRequest struct { func (x *ListCPApplicationsRequest) Reset() { *x = ListCPApplicationsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[52] + mi := &file_proto_user_v1_user_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4061,7 +4274,7 @@ func (x *ListCPApplicationsRequest) String() string { func (*ListCPApplicationsRequest) ProtoMessage() {} func (x *ListCPApplicationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[52] + mi := &file_proto_user_v1_user_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4074,7 +4287,7 @@ func (x *ListCPApplicationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPApplicationsRequest.ProtoReflect.Descriptor instead. func (*ListCPApplicationsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{52} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{55} } func (x *ListCPApplicationsRequest) GetMeta() *RequestMeta { @@ -4130,7 +4343,7 @@ type ListCPApplicationsResponse struct { func (x *ListCPApplicationsResponse) Reset() { *x = ListCPApplicationsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[53] + mi := &file_proto_user_v1_user_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4142,7 +4355,7 @@ func (x *ListCPApplicationsResponse) String() string { func (*ListCPApplicationsResponse) ProtoMessage() {} func (x *ListCPApplicationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[53] + mi := &file_proto_user_v1_user_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4155,7 +4368,7 @@ func (x *ListCPApplicationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPApplicationsResponse.ProtoReflect.Descriptor instead. func (*ListCPApplicationsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{53} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{56} } func (x *ListCPApplicationsResponse) GetApplications() []*CPApplication { @@ -4190,7 +4403,7 @@ type AcceptCPApplicationRequest struct { func (x *AcceptCPApplicationRequest) Reset() { *x = AcceptCPApplicationRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[54] + mi := &file_proto_user_v1_user_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4202,7 +4415,7 @@ func (x *AcceptCPApplicationRequest) String() string { func (*AcceptCPApplicationRequest) ProtoMessage() {} func (x *AcceptCPApplicationRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[54] + mi := &file_proto_user_v1_user_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4215,7 +4428,7 @@ func (x *AcceptCPApplicationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptCPApplicationRequest.ProtoReflect.Descriptor instead. func (*AcceptCPApplicationRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{54} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{57} } func (x *AcceptCPApplicationRequest) GetMeta() *RequestMeta { @@ -4249,7 +4462,7 @@ type AcceptCPApplicationResponse struct { func (x *AcceptCPApplicationResponse) Reset() { *x = AcceptCPApplicationResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[55] + mi := &file_proto_user_v1_user_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4261,7 +4474,7 @@ func (x *AcceptCPApplicationResponse) String() string { func (*AcceptCPApplicationResponse) ProtoMessage() {} func (x *AcceptCPApplicationResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[55] + mi := &file_proto_user_v1_user_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4274,7 +4487,7 @@ func (x *AcceptCPApplicationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptCPApplicationResponse.ProtoReflect.Descriptor instead. func (*AcceptCPApplicationResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{55} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{58} } func (x *AcceptCPApplicationResponse) GetApplication() *CPApplication { @@ -4303,7 +4516,7 @@ type RejectCPApplicationRequest struct { func (x *RejectCPApplicationRequest) Reset() { *x = RejectCPApplicationRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[56] + mi := &file_proto_user_v1_user_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4315,7 +4528,7 @@ func (x *RejectCPApplicationRequest) String() string { func (*RejectCPApplicationRequest) ProtoMessage() {} func (x *RejectCPApplicationRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[56] + mi := &file_proto_user_v1_user_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4328,7 +4541,7 @@ func (x *RejectCPApplicationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectCPApplicationRequest.ProtoReflect.Descriptor instead. func (*RejectCPApplicationRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{56} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{59} } func (x *RejectCPApplicationRequest) GetMeta() *RequestMeta { @@ -4368,7 +4581,7 @@ type RejectCPApplicationResponse struct { func (x *RejectCPApplicationResponse) Reset() { *x = RejectCPApplicationResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[57] + mi := &file_proto_user_v1_user_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4380,7 +4593,7 @@ func (x *RejectCPApplicationResponse) String() string { func (*RejectCPApplicationResponse) ProtoMessage() {} func (x *RejectCPApplicationResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[57] + mi := &file_proto_user_v1_user_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4393,7 +4606,7 @@ func (x *RejectCPApplicationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RejectCPApplicationResponse.ProtoReflect.Descriptor instead. func (*RejectCPApplicationResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{57} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{60} } func (x *RejectCPApplicationResponse) GetApplication() *CPApplication { @@ -4416,7 +4629,7 @@ type ListCPRelationshipsRequest struct { func (x *ListCPRelationshipsRequest) Reset() { *x = ListCPRelationshipsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[58] + mi := &file_proto_user_v1_user_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4428,7 +4641,7 @@ func (x *ListCPRelationshipsRequest) String() string { func (*ListCPRelationshipsRequest) ProtoMessage() {} func (x *ListCPRelationshipsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[58] + mi := &file_proto_user_v1_user_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4441,7 +4654,7 @@ func (x *ListCPRelationshipsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPRelationshipsRequest.ProtoReflect.Descriptor instead. func (*ListCPRelationshipsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{58} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{61} } func (x *ListCPRelationshipsRequest) GetMeta() *RequestMeta { @@ -4489,7 +4702,7 @@ type ListCPRelationshipsResponse struct { func (x *ListCPRelationshipsResponse) Reset() { *x = ListCPRelationshipsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[59] + mi := &file_proto_user_v1_user_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4501,7 +4714,7 @@ func (x *ListCPRelationshipsResponse) String() string { func (*ListCPRelationshipsResponse) ProtoMessage() {} func (x *ListCPRelationshipsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[59] + mi := &file_proto_user_v1_user_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4514,7 +4727,7 @@ func (x *ListCPRelationshipsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPRelationshipsResponse.ProtoReflect.Descriptor instead. func (*ListCPRelationshipsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{59} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{62} } func (x *ListCPRelationshipsResponse) GetRelationships() []*CPRelationship { @@ -4543,7 +4756,7 @@ type ListCPIntimacyLeaderboardRequest struct { func (x *ListCPIntimacyLeaderboardRequest) Reset() { *x = ListCPIntimacyLeaderboardRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[60] + mi := &file_proto_user_v1_user_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4555,7 +4768,7 @@ func (x *ListCPIntimacyLeaderboardRequest) String() string { func (*ListCPIntimacyLeaderboardRequest) ProtoMessage() {} func (x *ListCPIntimacyLeaderboardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[60] + mi := &file_proto_user_v1_user_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4568,7 +4781,7 @@ func (x *ListCPIntimacyLeaderboardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCPIntimacyLeaderboardRequest.ProtoReflect.Descriptor instead. func (*ListCPIntimacyLeaderboardRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{60} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{63} } func (x *ListCPIntimacyLeaderboardRequest) GetMeta() *RequestMeta { @@ -4612,7 +4825,7 @@ type ListCPIntimacyLeaderboardResponse struct { func (x *ListCPIntimacyLeaderboardResponse) Reset() { *x = ListCPIntimacyLeaderboardResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[61] + mi := &file_proto_user_v1_user_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4624,7 +4837,7 @@ func (x *ListCPIntimacyLeaderboardResponse) String() string { func (*ListCPIntimacyLeaderboardResponse) ProtoMessage() {} func (x *ListCPIntimacyLeaderboardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[61] + mi := &file_proto_user_v1_user_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4637,7 +4850,7 @@ func (x *ListCPIntimacyLeaderboardResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListCPIntimacyLeaderboardResponse.ProtoReflect.Descriptor instead. func (*ListCPIntimacyLeaderboardResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{61} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{64} } func (x *ListCPIntimacyLeaderboardResponse) GetItems() []*CPIntimacyLeaderboardItem { @@ -4687,7 +4900,7 @@ type PrepareBreakCPRelationshipRequest struct { func (x *PrepareBreakCPRelationshipRequest) Reset() { *x = PrepareBreakCPRelationshipRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[62] + mi := &file_proto_user_v1_user_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4699,7 +4912,7 @@ func (x *PrepareBreakCPRelationshipRequest) String() string { func (*PrepareBreakCPRelationshipRequest) ProtoMessage() {} func (x *PrepareBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[62] + mi := &file_proto_user_v1_user_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4712,7 +4925,7 @@ func (x *PrepareBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message // Deprecated: Use PrepareBreakCPRelationshipRequest.ProtoReflect.Descriptor instead. func (*PrepareBreakCPRelationshipRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{62} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{65} } func (x *PrepareBreakCPRelationshipRequest) GetMeta() *RequestMeta { @@ -4757,7 +4970,7 @@ type PrepareBreakCPRelationshipResponse struct { func (x *PrepareBreakCPRelationshipResponse) Reset() { *x = PrepareBreakCPRelationshipResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[63] + mi := &file_proto_user_v1_user_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4769,7 +4982,7 @@ func (x *PrepareBreakCPRelationshipResponse) String() string { func (*PrepareBreakCPRelationshipResponse) ProtoMessage() {} func (x *PrepareBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[63] + mi := &file_proto_user_v1_user_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4782,7 +4995,7 @@ func (x *PrepareBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message // Deprecated: Use PrepareBreakCPRelationshipResponse.ProtoReflect.Descriptor instead. func (*PrepareBreakCPRelationshipResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{63} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{66} } func (x *PrepareBreakCPRelationshipResponse) GetRelationship() *CPRelationship { @@ -4842,7 +5055,7 @@ type ConfirmBreakCPRelationshipRequest struct { func (x *ConfirmBreakCPRelationshipRequest) Reset() { *x = ConfirmBreakCPRelationshipRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[64] + mi := &file_proto_user_v1_user_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4854,7 +5067,7 @@ func (x *ConfirmBreakCPRelationshipRequest) String() string { func (*ConfirmBreakCPRelationshipRequest) ProtoMessage() {} func (x *ConfirmBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[64] + mi := &file_proto_user_v1_user_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4867,7 +5080,7 @@ func (x *ConfirmBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ConfirmBreakCPRelationshipRequest.ProtoReflect.Descriptor instead. func (*ConfirmBreakCPRelationshipRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{64} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{67} } func (x *ConfirmBreakCPRelationshipRequest) GetMeta() *RequestMeta { @@ -4931,7 +5144,7 @@ type ConfirmBreakCPRelationshipResponse struct { func (x *ConfirmBreakCPRelationshipResponse) Reset() { *x = ConfirmBreakCPRelationshipResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[65] + mi := &file_proto_user_v1_user_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4943,7 +5156,7 @@ func (x *ConfirmBreakCPRelationshipResponse) String() string { func (*ConfirmBreakCPRelationshipResponse) ProtoMessage() {} func (x *ConfirmBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[65] + mi := &file_proto_user_v1_user_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4956,7 +5169,7 @@ func (x *ConfirmBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ConfirmBreakCPRelationshipResponse.ProtoReflect.Descriptor instead. func (*ConfirmBreakCPRelationshipResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{65} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{68} } func (x *ConfirmBreakCPRelationshipResponse) GetRelationship() *CPRelationship { @@ -5000,7 +5213,7 @@ type CancelBreakCPRelationshipRequest struct { func (x *CancelBreakCPRelationshipRequest) Reset() { *x = CancelBreakCPRelationshipRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[66] + mi := &file_proto_user_v1_user_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5012,7 +5225,7 @@ func (x *CancelBreakCPRelationshipRequest) String() string { func (*CancelBreakCPRelationshipRequest) ProtoMessage() {} func (x *CancelBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[66] + mi := &file_proto_user_v1_user_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5025,7 +5238,7 @@ func (x *CancelBreakCPRelationshipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelBreakCPRelationshipRequest.ProtoReflect.Descriptor instead. func (*CancelBreakCPRelationshipRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{66} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{69} } func (x *CancelBreakCPRelationshipRequest) GetMeta() *RequestMeta { @@ -5073,7 +5286,7 @@ type CancelBreakCPRelationshipResponse struct { func (x *CancelBreakCPRelationshipResponse) Reset() { *x = CancelBreakCPRelationshipResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[67] + mi := &file_proto_user_v1_user_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5085,7 +5298,7 @@ func (x *CancelBreakCPRelationshipResponse) String() string { func (*CancelBreakCPRelationshipResponse) ProtoMessage() {} func (x *CancelBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[67] + mi := &file_proto_user_v1_user_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5098,7 +5311,7 @@ func (x *CancelBreakCPRelationshipResponse) ProtoReflect() protoreflect.Message // Deprecated: Use CancelBreakCPRelationshipResponse.ProtoReflect.Descriptor instead. func (*CancelBreakCPRelationshipResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{67} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{70} } func (x *CancelBreakCPRelationshipResponse) GetCommandId() string { @@ -5141,7 +5354,7 @@ type RoomGiftCPEvent struct { func (x *RoomGiftCPEvent) Reset() { *x = RoomGiftCPEvent{} - mi := &file_proto_user_v1_user_proto_msgTypes[68] + mi := &file_proto_user_v1_user_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5153,7 +5366,7 @@ func (x *RoomGiftCPEvent) String() string { func (*RoomGiftCPEvent) ProtoMessage() {} func (x *RoomGiftCPEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[68] + mi := &file_proto_user_v1_user_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5166,7 +5379,7 @@ func (x *RoomGiftCPEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomGiftCPEvent.ProtoReflect.Descriptor instead. func (*RoomGiftCPEvent) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{68} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{71} } func (x *RoomGiftCPEvent) GetMeta() *RequestMeta { @@ -5305,7 +5518,7 @@ type ConsumeRoomGiftCPEventRequest struct { func (x *ConsumeRoomGiftCPEventRequest) Reset() { *x = ConsumeRoomGiftCPEventRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[69] + mi := &file_proto_user_v1_user_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5317,7 +5530,7 @@ func (x *ConsumeRoomGiftCPEventRequest) String() string { func (*ConsumeRoomGiftCPEventRequest) ProtoMessage() {} func (x *ConsumeRoomGiftCPEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[69] + mi := &file_proto_user_v1_user_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5330,7 +5543,7 @@ func (x *ConsumeRoomGiftCPEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeRoomGiftCPEventRequest.ProtoReflect.Descriptor instead. func (*ConsumeRoomGiftCPEventRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{69} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{72} } func (x *ConsumeRoomGiftCPEventRequest) GetMeta() *RequestMeta { @@ -5358,7 +5571,7 @@ type ConsumeRoomGiftCPEventResponse struct { func (x *ConsumeRoomGiftCPEventResponse) Reset() { *x = ConsumeRoomGiftCPEventResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[70] + mi := &file_proto_user_v1_user_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5370,7 +5583,7 @@ func (x *ConsumeRoomGiftCPEventResponse) String() string { func (*ConsumeRoomGiftCPEventResponse) ProtoMessage() {} func (x *ConsumeRoomGiftCPEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[70] + mi := &file_proto_user_v1_user_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5383,7 +5596,7 @@ func (x *ConsumeRoomGiftCPEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeRoomGiftCPEventResponse.ProtoReflect.Descriptor instead. func (*ConsumeRoomGiftCPEventResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{70} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{73} } func (x *ConsumeRoomGiftCPEventResponse) GetConsumed() bool { @@ -5426,7 +5639,7 @@ type UserReport struct { func (x *UserReport) Reset() { *x = UserReport{} - mi := &file_proto_user_v1_user_proto_msgTypes[71] + mi := &file_proto_user_v1_user_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5438,7 +5651,7 @@ func (x *UserReport) String() string { func (*UserReport) ProtoMessage() {} func (x *UserReport) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[71] + mi := &file_proto_user_v1_user_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5451,7 +5664,7 @@ func (x *UserReport) ProtoReflect() protoreflect.Message { // Deprecated: Use UserReport.ProtoReflect.Descriptor instead. func (*UserReport) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{71} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{74} } func (x *UserReport) GetReportId() string { @@ -5540,7 +5753,7 @@ type SubmitReportRequest struct { func (x *SubmitReportRequest) Reset() { *x = SubmitReportRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[72] + mi := &file_proto_user_v1_user_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5552,7 +5765,7 @@ func (x *SubmitReportRequest) String() string { func (*SubmitReportRequest) ProtoMessage() {} func (x *SubmitReportRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[72] + mi := &file_proto_user_v1_user_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5565,7 +5778,7 @@ func (x *SubmitReportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitReportRequest.ProtoReflect.Descriptor instead. func (*SubmitReportRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{72} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{75} } func (x *SubmitReportRequest) GetMeta() *RequestMeta { @@ -5626,7 +5839,7 @@ type SubmitReportResponse struct { func (x *SubmitReportResponse) Reset() { *x = SubmitReportResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[73] + mi := &file_proto_user_v1_user_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5638,7 +5851,7 @@ func (x *SubmitReportResponse) String() string { func (*SubmitReportResponse) ProtoMessage() {} func (x *SubmitReportResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[73] + mi := &file_proto_user_v1_user_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5651,7 +5864,7 @@ func (x *SubmitReportResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitReportResponse.ProtoReflect.Descriptor instead. func (*SubmitReportResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{73} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{76} } func (x *SubmitReportResponse) GetReport() *UserReport { @@ -5672,7 +5885,7 @@ type BatchGetUsersRequest struct { func (x *BatchGetUsersRequest) Reset() { *x = BatchGetUsersRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[74] + mi := &file_proto_user_v1_user_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5684,7 +5897,7 @@ func (x *BatchGetUsersRequest) String() string { func (*BatchGetUsersRequest) ProtoMessage() {} func (x *BatchGetUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[74] + mi := &file_proto_user_v1_user_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5697,7 +5910,7 @@ func (x *BatchGetUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchGetUsersRequest.ProtoReflect.Descriptor instead. func (*BatchGetUsersRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{74} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{77} } func (x *BatchGetUsersRequest) GetMeta() *RequestMeta { @@ -5724,7 +5937,7 @@ type BatchGetUsersResponse struct { func (x *BatchGetUsersResponse) Reset() { *x = BatchGetUsersResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[75] + mi := &file_proto_user_v1_user_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5736,7 +5949,7 @@ func (x *BatchGetUsersResponse) String() string { func (*BatchGetUsersResponse) ProtoMessage() {} func (x *BatchGetUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[75] + mi := &file_proto_user_v1_user_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5749,7 +5962,7 @@ func (x *BatchGetUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchGetUsersResponse.ProtoReflect.Descriptor instead. func (*BatchGetUsersResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{75} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{78} } func (x *BatchGetUsersResponse) GetUsers() map[int64]*User { @@ -5774,7 +5987,7 @@ type ListUserIDsRequest struct { func (x *ListUserIDsRequest) Reset() { *x = ListUserIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[76] + mi := &file_proto_user_v1_user_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5786,7 +5999,7 @@ func (x *ListUserIDsRequest) String() string { func (*ListUserIDsRequest) ProtoMessage() {} func (x *ListUserIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[76] + mi := &file_proto_user_v1_user_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5799,7 +6012,7 @@ func (x *ListUserIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserIDsRequest.ProtoReflect.Descriptor instead. func (*ListUserIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{76} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{79} } func (x *ListUserIDsRequest) GetMeta() *RequestMeta { @@ -5856,7 +6069,7 @@ type ListUserIDsResponse struct { func (x *ListUserIDsResponse) Reset() { *x = ListUserIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[77] + mi := &file_proto_user_v1_user_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5868,7 +6081,7 @@ func (x *ListUserIDsResponse) String() string { func (*ListUserIDsResponse) ProtoMessage() {} func (x *ListUserIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[77] + mi := &file_proto_user_v1_user_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5881,7 +6094,7 @@ func (x *ListUserIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserIDsResponse.ProtoReflect.Descriptor instead. func (*ListUserIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{77} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{80} } func (x *ListUserIDsResponse) GetUserIds() []int64 { @@ -5920,7 +6133,7 @@ type UpdateUserProfileRequest struct { func (x *UpdateUserProfileRequest) Reset() { *x = UpdateUserProfileRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[78] + mi := &file_proto_user_v1_user_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5932,7 +6145,7 @@ func (x *UpdateUserProfileRequest) String() string { func (*UpdateUserProfileRequest) ProtoMessage() {} func (x *UpdateUserProfileRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[78] + mi := &file_proto_user_v1_user_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5945,7 +6158,7 @@ func (x *UpdateUserProfileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserProfileRequest.ProtoReflect.Descriptor instead. func (*UpdateUserProfileRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{78} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{81} } func (x *UpdateUserProfileRequest) GetMeta() *RequestMeta { @@ -6000,7 +6213,7 @@ type UpdateUserProfileResponse struct { func (x *UpdateUserProfileResponse) Reset() { *x = UpdateUserProfileResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[79] + mi := &file_proto_user_v1_user_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6012,7 +6225,7 @@ func (x *UpdateUserProfileResponse) String() string { func (*UpdateUserProfileResponse) ProtoMessage() {} func (x *UpdateUserProfileResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[79] + mi := &file_proto_user_v1_user_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6025,7 +6238,7 @@ func (x *UpdateUserProfileResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserProfileResponse.ProtoReflect.Descriptor instead. func (*UpdateUserProfileResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{79} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{82} } func (x *UpdateUserProfileResponse) GetUser() *User { @@ -6047,7 +6260,7 @@ type ChangeUserCountryRequest struct { func (x *ChangeUserCountryRequest) Reset() { *x = ChangeUserCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[80] + mi := &file_proto_user_v1_user_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6059,7 +6272,7 @@ func (x *ChangeUserCountryRequest) String() string { func (*ChangeUserCountryRequest) ProtoMessage() {} func (x *ChangeUserCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[80] + mi := &file_proto_user_v1_user_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6072,7 +6285,7 @@ func (x *ChangeUserCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeUserCountryRequest.ProtoReflect.Descriptor instead. func (*ChangeUserCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{80} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{83} } func (x *ChangeUserCountryRequest) GetMeta() *RequestMeta { @@ -6110,7 +6323,7 @@ type AdminChangeUserCountryRequest struct { func (x *AdminChangeUserCountryRequest) Reset() { *x = AdminChangeUserCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[81] + mi := &file_proto_user_v1_user_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6122,7 +6335,7 @@ func (x *AdminChangeUserCountryRequest) String() string { func (*AdminChangeUserCountryRequest) ProtoMessage() {} func (x *AdminChangeUserCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[81] + mi := &file_proto_user_v1_user_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6135,7 +6348,7 @@ func (x *AdminChangeUserCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminChangeUserCountryRequest.ProtoReflect.Descriptor instead. func (*AdminChangeUserCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{81} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{84} } func (x *AdminChangeUserCountryRequest) GetMeta() *RequestMeta { @@ -6187,7 +6400,7 @@ type ChangeUserCountryResponse struct { func (x *ChangeUserCountryResponse) Reset() { *x = ChangeUserCountryResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[82] + mi := &file_proto_user_v1_user_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6199,7 +6412,7 @@ func (x *ChangeUserCountryResponse) String() string { func (*ChangeUserCountryResponse) ProtoMessage() {} func (x *ChangeUserCountryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[82] + mi := &file_proto_user_v1_user_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6212,7 +6425,7 @@ func (x *ChangeUserCountryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeUserCountryResponse.ProtoReflect.Descriptor instead. func (*ChangeUserCountryResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{82} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{85} } func (x *ChangeUserCountryResponse) GetUser() *User { @@ -6267,7 +6480,7 @@ type SetUserStatusRequest struct { func (x *SetUserStatusRequest) Reset() { *x = SetUserStatusRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[83] + mi := &file_proto_user_v1_user_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6279,7 +6492,7 @@ func (x *SetUserStatusRequest) String() string { func (*SetUserStatusRequest) ProtoMessage() {} func (x *SetUserStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[83] + mi := &file_proto_user_v1_user_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6292,7 +6505,7 @@ func (x *SetUserStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetUserStatusRequest.ProtoReflect.Descriptor instead. func (*SetUserStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{83} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{86} } func (x *SetUserStatusRequest) GetMeta() *RequestMeta { @@ -6358,7 +6571,7 @@ type SetUserStatusResponse struct { func (x *SetUserStatusResponse) Reset() { *x = SetUserStatusResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[84] + mi := &file_proto_user_v1_user_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6370,7 +6583,7 @@ func (x *SetUserStatusResponse) String() string { func (*SetUserStatusResponse) ProtoMessage() {} func (x *SetUserStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[84] + mi := &file_proto_user_v1_user_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6383,7 +6596,7 @@ func (x *SetUserStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetUserStatusResponse.ProtoReflect.Descriptor instead. func (*SetUserStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{84} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{87} } func (x *SetUserStatusResponse) GetUser() *User { @@ -6483,7 +6696,7 @@ type ManagerUserBlock struct { func (x *ManagerUserBlock) Reset() { *x = ManagerUserBlock{} - mi := &file_proto_user_v1_user_proto_msgTypes[85] + mi := &file_proto_user_v1_user_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6495,7 +6708,7 @@ func (x *ManagerUserBlock) String() string { func (*ManagerUserBlock) ProtoMessage() {} func (x *ManagerUserBlock) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[85] + mi := &file_proto_user_v1_user_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6508,7 +6721,7 @@ func (x *ManagerUserBlock) ProtoReflect() protoreflect.Message { // Deprecated: Use ManagerUserBlock.ProtoReflect.Descriptor instead. func (*ManagerUserBlock) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{85} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{88} } func (x *ManagerUserBlock) GetBlockId() string { @@ -6609,7 +6822,7 @@ type CreateManagerUserBlockRequest struct { func (x *CreateManagerUserBlockRequest) Reset() { *x = CreateManagerUserBlockRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[86] + mi := &file_proto_user_v1_user_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6621,7 +6834,7 @@ func (x *CreateManagerUserBlockRequest) String() string { func (*CreateManagerUserBlockRequest) ProtoMessage() {} func (x *CreateManagerUserBlockRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[86] + mi := &file_proto_user_v1_user_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6634,7 +6847,7 @@ func (x *CreateManagerUserBlockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateManagerUserBlockRequest.ProtoReflect.Descriptor instead. func (*CreateManagerUserBlockRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{86} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{89} } func (x *CreateManagerUserBlockRequest) GetMeta() *RequestMeta { @@ -6689,7 +6902,7 @@ type CreateManagerUserBlockResponse struct { func (x *CreateManagerUserBlockResponse) Reset() { *x = CreateManagerUserBlockResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[87] + mi := &file_proto_user_v1_user_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6701,7 +6914,7 @@ func (x *CreateManagerUserBlockResponse) String() string { func (*CreateManagerUserBlockResponse) ProtoMessage() {} func (x *CreateManagerUserBlockResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[87] + mi := &file_proto_user_v1_user_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6714,7 +6927,7 @@ func (x *CreateManagerUserBlockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateManagerUserBlockResponse.ProtoReflect.Descriptor instead. func (*CreateManagerUserBlockResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{87} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{90} } func (x *CreateManagerUserBlockResponse) GetBlock() *ManagerUserBlock { @@ -6743,7 +6956,7 @@ type ListManagerUserBlocksRequest struct { func (x *ListManagerUserBlocksRequest) Reset() { *x = ListManagerUserBlocksRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[88] + mi := &file_proto_user_v1_user_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6755,7 +6968,7 @@ func (x *ListManagerUserBlocksRequest) String() string { func (*ListManagerUserBlocksRequest) ProtoMessage() {} func (x *ListManagerUserBlocksRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[88] + mi := &file_proto_user_v1_user_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6768,7 +6981,7 @@ func (x *ListManagerUserBlocksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListManagerUserBlocksRequest.ProtoReflect.Descriptor instead. func (*ListManagerUserBlocksRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{88} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{91} } func (x *ListManagerUserBlocksRequest) GetMeta() *RequestMeta { @@ -6808,7 +7021,7 @@ type ListManagerUserBlocksResponse struct { func (x *ListManagerUserBlocksResponse) Reset() { *x = ListManagerUserBlocksResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[89] + mi := &file_proto_user_v1_user_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6820,7 +7033,7 @@ func (x *ListManagerUserBlocksResponse) String() string { func (*ListManagerUserBlocksResponse) ProtoMessage() {} func (x *ListManagerUserBlocksResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[89] + mi := &file_proto_user_v1_user_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6833,7 +7046,7 @@ func (x *ListManagerUserBlocksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListManagerUserBlocksResponse.ProtoReflect.Descriptor instead. func (*ListManagerUserBlocksResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{89} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{92} } func (x *ListManagerUserBlocksResponse) GetBlocks() []*ManagerUserBlock { @@ -6855,7 +7068,7 @@ type UnblockManagerUserRequest struct { func (x *UnblockManagerUserRequest) Reset() { *x = UnblockManagerUserRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[90] + mi := &file_proto_user_v1_user_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6867,7 +7080,7 @@ func (x *UnblockManagerUserRequest) String() string { func (*UnblockManagerUserRequest) ProtoMessage() {} func (x *UnblockManagerUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[90] + mi := &file_proto_user_v1_user_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6880,7 +7093,7 @@ func (x *UnblockManagerUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnblockManagerUserRequest.ProtoReflect.Descriptor instead. func (*UnblockManagerUserRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{90} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{93} } func (x *UnblockManagerUserRequest) GetMeta() *RequestMeta { @@ -6921,7 +7134,7 @@ type UnblockManagerUserResponse struct { func (x *UnblockManagerUserResponse) Reset() { *x = UnblockManagerUserResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[91] + mi := &file_proto_user_v1_user_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6933,7 +7146,7 @@ func (x *UnblockManagerUserResponse) String() string { func (*UnblockManagerUserResponse) ProtoMessage() {} func (x *UnblockManagerUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[91] + mi := &file_proto_user_v1_user_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6946,7 +7159,7 @@ func (x *UnblockManagerUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnblockManagerUserResponse.ProtoReflect.Descriptor instead. func (*UnblockManagerUserResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{91} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{94} } func (x *UnblockManagerUserResponse) GetBlock() *ManagerUserBlock { @@ -6979,7 +7192,7 @@ type CompleteOnboardingRequest struct { func (x *CompleteOnboardingRequest) Reset() { *x = CompleteOnboardingRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[92] + mi := &file_proto_user_v1_user_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6991,7 +7204,7 @@ func (x *CompleteOnboardingRequest) String() string { func (*CompleteOnboardingRequest) ProtoMessage() {} func (x *CompleteOnboardingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[92] + mi := &file_proto_user_v1_user_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7004,7 +7217,7 @@ func (x *CompleteOnboardingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CompleteOnboardingRequest.ProtoReflect.Descriptor instead. func (*CompleteOnboardingRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{92} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{95} } func (x *CompleteOnboardingRequest) GetMeta() *RequestMeta { @@ -7071,7 +7284,7 @@ type CompleteOnboardingResponse struct { func (x *CompleteOnboardingResponse) Reset() { *x = CompleteOnboardingResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[93] + mi := &file_proto_user_v1_user_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7083,7 +7296,7 @@ func (x *CompleteOnboardingResponse) String() string { func (*CompleteOnboardingResponse) ProtoMessage() {} func (x *CompleteOnboardingResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[93] + mi := &file_proto_user_v1_user_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7096,7 +7309,7 @@ func (x *CompleteOnboardingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CompleteOnboardingResponse.ProtoReflect.Descriptor instead. func (*CompleteOnboardingResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{93} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{96} } func (x *CompleteOnboardingResponse) GetUser() *User { @@ -7160,7 +7373,7 @@ type BindPushTokenRequest struct { func (x *BindPushTokenRequest) Reset() { *x = BindPushTokenRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[94] + mi := &file_proto_user_v1_user_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7172,7 +7385,7 @@ func (x *BindPushTokenRequest) String() string { func (*BindPushTokenRequest) ProtoMessage() {} func (x *BindPushTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[94] + mi := &file_proto_user_v1_user_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7185,7 +7398,7 @@ func (x *BindPushTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BindPushTokenRequest.ProtoReflect.Descriptor instead. func (*BindPushTokenRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{94} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{97} } func (x *BindPushTokenRequest) GetMeta() *RequestMeta { @@ -7261,7 +7474,7 @@ type BindPushTokenResponse struct { func (x *BindPushTokenResponse) Reset() { *x = BindPushTokenResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[95] + mi := &file_proto_user_v1_user_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7273,7 +7486,7 @@ func (x *BindPushTokenResponse) String() string { func (*BindPushTokenResponse) ProtoMessage() {} func (x *BindPushTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[95] + mi := &file_proto_user_v1_user_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7286,7 +7499,7 @@ func (x *BindPushTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BindPushTokenResponse.ProtoReflect.Descriptor instead. func (*BindPushTokenResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{95} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{98} } func (x *BindPushTokenResponse) GetBound() bool { @@ -7317,7 +7530,7 @@ type DeletePushTokenRequest struct { func (x *DeletePushTokenRequest) Reset() { *x = DeletePushTokenRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[96] + mi := &file_proto_user_v1_user_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7329,7 +7542,7 @@ func (x *DeletePushTokenRequest) String() string { func (*DeletePushTokenRequest) ProtoMessage() {} func (x *DeletePushTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[96] + mi := &file_proto_user_v1_user_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7342,7 +7555,7 @@ func (x *DeletePushTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePushTokenRequest.ProtoReflect.Descriptor instead. func (*DeletePushTokenRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{96} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{99} } func (x *DeletePushTokenRequest) GetMeta() *RequestMeta { @@ -7383,7 +7596,7 @@ type DeletePushTokenResponse struct { func (x *DeletePushTokenResponse) Reset() { *x = DeletePushTokenResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[97] + mi := &file_proto_user_v1_user_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7395,7 +7608,7 @@ func (x *DeletePushTokenResponse) String() string { func (*DeletePushTokenResponse) ProtoMessage() {} func (x *DeletePushTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[97] + mi := &file_proto_user_v1_user_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7408,7 +7621,7 @@ func (x *DeletePushTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePushTokenResponse.ProtoReflect.Descriptor instead. func (*DeletePushTokenResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{97} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{100} } func (x *DeletePushTokenResponse) GetDeleted() bool { @@ -7447,7 +7660,7 @@ type Country struct { func (x *Country) Reset() { *x = Country{} - mi := &file_proto_user_v1_user_proto_msgTypes[98] + mi := &file_proto_user_v1_user_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7459,7 +7672,7 @@ func (x *Country) String() string { func (*Country) ProtoMessage() {} func (x *Country) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[98] + mi := &file_proto_user_v1_user_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7472,7 +7685,7 @@ func (x *Country) ProtoReflect() protoreflect.Message { // Deprecated: Use Country.ProtoReflect.Descriptor instead. func (*Country) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{98} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{101} } func (x *Country) GetCountryId() int64 { @@ -7584,7 +7797,7 @@ type Region struct { func (x *Region) Reset() { *x = Region{} - mi := &file_proto_user_v1_user_proto_msgTypes[99] + mi := &file_proto_user_v1_user_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7596,7 +7809,7 @@ func (x *Region) String() string { func (*Region) ProtoMessage() {} func (x *Region) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[99] + mi := &file_proto_user_v1_user_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7609,7 +7822,7 @@ func (x *Region) ProtoReflect() protoreflect.Message { // Deprecated: Use Region.ProtoReflect.Descriptor instead. func (*Region) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{99} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{102} } func (x *Region) GetRegionId() int64 { @@ -7685,7 +7898,7 @@ type ListCountriesRequest struct { func (x *ListCountriesRequest) Reset() { *x = ListCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[100] + mi := &file_proto_user_v1_user_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7697,7 +7910,7 @@ func (x *ListCountriesRequest) String() string { func (*ListCountriesRequest) ProtoMessage() {} func (x *ListCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[100] + mi := &file_proto_user_v1_user_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7710,7 +7923,7 @@ func (x *ListCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCountriesRequest.ProtoReflect.Descriptor instead. func (*ListCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{100} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{103} } func (x *ListCountriesRequest) GetMeta() *RequestMeta { @@ -7736,7 +7949,7 @@ type ListCountriesResponse struct { func (x *ListCountriesResponse) Reset() { *x = ListCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[101] + mi := &file_proto_user_v1_user_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7748,7 +7961,7 @@ func (x *ListCountriesResponse) String() string { func (*ListCountriesResponse) ProtoMessage() {} func (x *ListCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[101] + mi := &file_proto_user_v1_user_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7761,7 +7974,7 @@ func (x *ListCountriesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCountriesResponse.ProtoReflect.Descriptor instead. func (*ListCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{101} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{104} } func (x *ListCountriesResponse) GetCountries() []*Country { @@ -7790,7 +8003,7 @@ type UpdateCountryRequest struct { func (x *UpdateCountryRequest) Reset() { *x = UpdateCountryRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[102] + mi := &file_proto_user_v1_user_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7802,7 +8015,7 @@ func (x *UpdateCountryRequest) String() string { func (*UpdateCountryRequest) ProtoMessage() {} func (x *UpdateCountryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[102] + mi := &file_proto_user_v1_user_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7815,7 +8028,7 @@ func (x *UpdateCountryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateCountryRequest.ProtoReflect.Descriptor instead. func (*UpdateCountryRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{102} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{105} } func (x *UpdateCountryRequest) GetMeta() *RequestMeta { @@ -7897,7 +8110,7 @@ type CountryResponse struct { func (x *CountryResponse) Reset() { *x = CountryResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[103] + mi := &file_proto_user_v1_user_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7909,7 +8122,7 @@ func (x *CountryResponse) String() string { func (*CountryResponse) ProtoMessage() {} func (x *CountryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[103] + mi := &file_proto_user_v1_user_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7922,7 +8135,7 @@ func (x *CountryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CountryResponse.ProtoReflect.Descriptor instead. func (*CountryResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{103} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{106} } func (x *CountryResponse) GetCountry() *Country { @@ -7941,7 +8154,7 @@ type ListRegistrationCountriesRequest struct { func (x *ListRegistrationCountriesRequest) Reset() { *x = ListRegistrationCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[104] + mi := &file_proto_user_v1_user_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7953,7 +8166,7 @@ func (x *ListRegistrationCountriesRequest) String() string { func (*ListRegistrationCountriesRequest) ProtoMessage() {} func (x *ListRegistrationCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[104] + mi := &file_proto_user_v1_user_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7966,7 +8179,7 @@ func (x *ListRegistrationCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegistrationCountriesRequest.ProtoReflect.Descriptor instead. func (*ListRegistrationCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{104} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{107} } func (x *ListRegistrationCountriesRequest) GetMeta() *RequestMeta { @@ -7985,7 +8198,7 @@ type ListRegistrationCountriesResponse struct { func (x *ListRegistrationCountriesResponse) Reset() { *x = ListRegistrationCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[105] + mi := &file_proto_user_v1_user_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7997,7 +8210,7 @@ func (x *ListRegistrationCountriesResponse) String() string { func (*ListRegistrationCountriesResponse) ProtoMessage() {} func (x *ListRegistrationCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[105] + mi := &file_proto_user_v1_user_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8010,7 +8223,7 @@ func (x *ListRegistrationCountriesResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListRegistrationCountriesResponse.ProtoReflect.Descriptor instead. func (*ListRegistrationCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{105} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{108} } func (x *ListRegistrationCountriesResponse) GetCountries() []*Country { @@ -8031,7 +8244,7 @@ type LoginRiskBlockedCountry struct { func (x *LoginRiskBlockedCountry) Reset() { *x = LoginRiskBlockedCountry{} - mi := &file_proto_user_v1_user_proto_msgTypes[106] + mi := &file_proto_user_v1_user_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8043,7 +8256,7 @@ func (x *LoginRiskBlockedCountry) String() string { func (*LoginRiskBlockedCountry) ProtoMessage() {} func (x *LoginRiskBlockedCountry) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[106] + mi := &file_proto_user_v1_user_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8056,7 +8269,7 @@ func (x *LoginRiskBlockedCountry) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRiskBlockedCountry.ProtoReflect.Descriptor instead. func (*LoginRiskBlockedCountry) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{106} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{109} } func (x *LoginRiskBlockedCountry) GetCountryCode() string { @@ -8089,7 +8302,7 @@ type ListLoginRiskBlockedCountriesRequest struct { func (x *ListLoginRiskBlockedCountriesRequest) Reset() { *x = ListLoginRiskBlockedCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[107] + mi := &file_proto_user_v1_user_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8101,7 +8314,7 @@ func (x *ListLoginRiskBlockedCountriesRequest) String() string { func (*ListLoginRiskBlockedCountriesRequest) ProtoMessage() {} func (x *ListLoginRiskBlockedCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[107] + mi := &file_proto_user_v1_user_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8114,7 +8327,7 @@ func (x *ListLoginRiskBlockedCountriesRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListLoginRiskBlockedCountriesRequest.ProtoReflect.Descriptor instead. func (*ListLoginRiskBlockedCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{107} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{110} } func (x *ListLoginRiskBlockedCountriesRequest) GetMeta() *RequestMeta { @@ -8134,7 +8347,7 @@ type ListLoginRiskBlockedCountriesResponse struct { func (x *ListLoginRiskBlockedCountriesResponse) Reset() { *x = ListLoginRiskBlockedCountriesResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[108] + mi := &file_proto_user_v1_user_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8146,7 +8359,7 @@ func (x *ListLoginRiskBlockedCountriesResponse) String() string { func (*ListLoginRiskBlockedCountriesResponse) ProtoMessage() {} func (x *ListLoginRiskBlockedCountriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[108] + mi := &file_proto_user_v1_user_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8159,7 +8372,7 @@ func (x *ListLoginRiskBlockedCountriesResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListLoginRiskBlockedCountriesResponse.ProtoReflect.Descriptor instead. func (*ListLoginRiskBlockedCountriesResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{108} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{111} } func (x *ListLoginRiskBlockedCountriesResponse) GetCountries() []*LoginRiskBlockedCountry { @@ -8186,7 +8399,7 @@ type ListRegionsRequest struct { func (x *ListRegionsRequest) Reset() { *x = ListRegionsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[109] + mi := &file_proto_user_v1_user_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8198,7 +8411,7 @@ func (x *ListRegionsRequest) String() string { func (*ListRegionsRequest) ProtoMessage() {} func (x *ListRegionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[109] + mi := &file_proto_user_v1_user_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8211,7 +8424,7 @@ func (x *ListRegionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegionsRequest.ProtoReflect.Descriptor instead. func (*ListRegionsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{109} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{112} } func (x *ListRegionsRequest) GetMeta() *RequestMeta { @@ -8237,7 +8450,7 @@ type ListRegionsResponse struct { func (x *ListRegionsResponse) Reset() { *x = ListRegionsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[110] + mi := &file_proto_user_v1_user_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8249,7 +8462,7 @@ func (x *ListRegionsResponse) String() string { func (*ListRegionsResponse) ProtoMessage() {} func (x *ListRegionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[110] + mi := &file_proto_user_v1_user_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8262,7 +8475,7 @@ func (x *ListRegionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRegionsResponse.ProtoReflect.Descriptor instead. func (*ListRegionsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{110} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{113} } func (x *ListRegionsResponse) GetRegions() []*Region { @@ -8282,7 +8495,7 @@ type GetRegionRequest struct { func (x *GetRegionRequest) Reset() { *x = GetRegionRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[111] + mi := &file_proto_user_v1_user_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8294,7 +8507,7 @@ func (x *GetRegionRequest) String() string { func (*GetRegionRequest) ProtoMessage() {} func (x *GetRegionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[111] + mi := &file_proto_user_v1_user_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8307,7 +8520,7 @@ func (x *GetRegionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRegionRequest.ProtoReflect.Descriptor instead. func (*GetRegionRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{111} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{114} } func (x *GetRegionRequest) GetMeta() *RequestMeta { @@ -8339,7 +8552,7 @@ type UpdateRegionRequest struct { func (x *UpdateRegionRequest) Reset() { *x = UpdateRegionRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[112] + mi := &file_proto_user_v1_user_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8351,7 +8564,7 @@ func (x *UpdateRegionRequest) String() string { func (*UpdateRegionRequest) ProtoMessage() {} func (x *UpdateRegionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[112] + mi := &file_proto_user_v1_user_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8364,7 +8577,7 @@ func (x *UpdateRegionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRegionRequest.ProtoReflect.Descriptor instead. func (*UpdateRegionRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{112} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{115} } func (x *UpdateRegionRequest) GetMeta() *RequestMeta { @@ -8421,7 +8634,7 @@ type ReplaceRegionCountriesRequest struct { func (x *ReplaceRegionCountriesRequest) Reset() { *x = ReplaceRegionCountriesRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[113] + mi := &file_proto_user_v1_user_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8433,7 +8646,7 @@ func (x *ReplaceRegionCountriesRequest) String() string { func (*ReplaceRegionCountriesRequest) ProtoMessage() {} func (x *ReplaceRegionCountriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[113] + mi := &file_proto_user_v1_user_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8446,7 +8659,7 @@ func (x *ReplaceRegionCountriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReplaceRegionCountriesRequest.ProtoReflect.Descriptor instead. func (*ReplaceRegionCountriesRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{113} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{116} } func (x *ReplaceRegionCountriesRequest) GetMeta() *RequestMeta { @@ -8486,7 +8699,7 @@ type RegionResponse struct { func (x *RegionResponse) Reset() { *x = RegionResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[114] + mi := &file_proto_user_v1_user_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8498,7 +8711,7 @@ func (x *RegionResponse) String() string { func (*RegionResponse) ProtoMessage() {} func (x *RegionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[114] + mi := &file_proto_user_v1_user_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8511,7 +8724,7 @@ func (x *RegionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegionResponse.ProtoReflect.Descriptor instead. func (*RegionResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{114} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{117} } func (x *RegionResponse) GetRegion() *Region { @@ -8539,7 +8752,7 @@ type UserIdentity struct { func (x *UserIdentity) Reset() { *x = UserIdentity{} - mi := &file_proto_user_v1_user_proto_msgTypes[115] + mi := &file_proto_user_v1_user_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8551,7 +8764,7 @@ func (x *UserIdentity) String() string { func (*UserIdentity) ProtoMessage() {} func (x *UserIdentity) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[115] + mi := &file_proto_user_v1_user_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8564,7 +8777,7 @@ func (x *UserIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use UserIdentity.ProtoReflect.Descriptor instead. func (*UserIdentity) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{115} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{118} } func (x *UserIdentity) GetUserId() int64 { @@ -8641,7 +8854,7 @@ type GetUserIdentityRequest struct { func (x *GetUserIdentityRequest) Reset() { *x = GetUserIdentityRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[116] + mi := &file_proto_user_v1_user_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8653,7 +8866,7 @@ func (x *GetUserIdentityRequest) String() string { func (*GetUserIdentityRequest) ProtoMessage() {} func (x *GetUserIdentityRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[116] + mi := &file_proto_user_v1_user_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8666,7 +8879,7 @@ func (x *GetUserIdentityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserIdentityRequest.ProtoReflect.Descriptor instead. func (*GetUserIdentityRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{116} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{119} } func (x *GetUserIdentityRequest) GetMeta() *RequestMeta { @@ -8693,7 +8906,7 @@ type GetUserIdentityResponse struct { func (x *GetUserIdentityResponse) Reset() { *x = GetUserIdentityResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[117] + mi := &file_proto_user_v1_user_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8705,7 +8918,7 @@ func (x *GetUserIdentityResponse) String() string { func (*GetUserIdentityResponse) ProtoMessage() {} func (x *GetUserIdentityResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[117] + mi := &file_proto_user_v1_user_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8718,7 +8931,7 @@ func (x *GetUserIdentityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserIdentityResponse.ProtoReflect.Descriptor instead. func (*GetUserIdentityResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{117} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{120} } func (x *GetUserIdentityResponse) GetIdentity() *UserIdentity { @@ -8739,7 +8952,7 @@ type ResolveDisplayUserIDRequest struct { func (x *ResolveDisplayUserIDRequest) Reset() { *x = ResolveDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[118] + mi := &file_proto_user_v1_user_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8751,7 +8964,7 @@ func (x *ResolveDisplayUserIDRequest) String() string { func (*ResolveDisplayUserIDRequest) ProtoMessage() {} func (x *ResolveDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[118] + mi := &file_proto_user_v1_user_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8764,7 +8977,7 @@ func (x *ResolveDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ResolveDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{118} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{121} } func (x *ResolveDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -8791,7 +9004,7 @@ type ResolveDisplayUserIDResponse struct { func (x *ResolveDisplayUserIDResponse) Reset() { *x = ResolveDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[119] + mi := &file_proto_user_v1_user_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8803,7 +9016,7 @@ func (x *ResolveDisplayUserIDResponse) String() string { func (*ResolveDisplayUserIDResponse) ProtoMessage() {} func (x *ResolveDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[119] + mi := &file_proto_user_v1_user_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8816,7 +9029,7 @@ func (x *ResolveDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ResolveDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{119} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{122} } func (x *ResolveDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -8840,7 +9053,7 @@ type ChangeDisplayUserIDRequest struct { func (x *ChangeDisplayUserIDRequest) Reset() { *x = ChangeDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[120] + mi := &file_proto_user_v1_user_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8852,7 +9065,7 @@ func (x *ChangeDisplayUserIDRequest) String() string { func (*ChangeDisplayUserIDRequest) ProtoMessage() {} func (x *ChangeDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[120] + mi := &file_proto_user_v1_user_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8865,7 +9078,7 @@ func (x *ChangeDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ChangeDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{120} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{123} } func (x *ChangeDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -8913,7 +9126,7 @@ type ChangeDisplayUserIDResponse struct { func (x *ChangeDisplayUserIDResponse) Reset() { *x = ChangeDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[121] + mi := &file_proto_user_v1_user_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8925,7 +9138,7 @@ func (x *ChangeDisplayUserIDResponse) String() string { func (*ChangeDisplayUserIDResponse) ProtoMessage() {} func (x *ChangeDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[121] + mi := &file_proto_user_v1_user_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8938,7 +9151,7 @@ func (x *ChangeDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ChangeDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{121} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{124} } func (x *ChangeDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -8962,7 +9175,7 @@ type ApplyPrettyDisplayUserIDRequest struct { func (x *ApplyPrettyDisplayUserIDRequest) Reset() { *x = ApplyPrettyDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[122] + mi := &file_proto_user_v1_user_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8974,7 +9187,7 @@ func (x *ApplyPrettyDisplayUserIDRequest) String() string { func (*ApplyPrettyDisplayUserIDRequest) ProtoMessage() {} func (x *ApplyPrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[122] + mi := &file_proto_user_v1_user_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8987,7 +9200,7 @@ func (x *ApplyPrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyPrettyDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{122} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{125} } func (x *ApplyPrettyDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -9036,7 +9249,7 @@ type ApplyPrettyDisplayUserIDResponse struct { func (x *ApplyPrettyDisplayUserIDResponse) Reset() { *x = ApplyPrettyDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[123] + mi := &file_proto_user_v1_user_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9048,7 +9261,7 @@ func (x *ApplyPrettyDisplayUserIDResponse) String() string { func (*ApplyPrettyDisplayUserIDResponse) ProtoMessage() {} func (x *ApplyPrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[123] + mi := &file_proto_user_v1_user_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9061,7 +9274,7 @@ func (x *ApplyPrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyPrettyDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{123} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{126} } func (x *ApplyPrettyDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -9090,7 +9303,7 @@ type ExpirePrettyDisplayUserIDRequest struct { func (x *ExpirePrettyDisplayUserIDRequest) Reset() { *x = ExpirePrettyDisplayUserIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[124] + mi := &file_proto_user_v1_user_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9102,7 +9315,7 @@ func (x *ExpirePrettyDisplayUserIDRequest) String() string { func (*ExpirePrettyDisplayUserIDRequest) ProtoMessage() {} func (x *ExpirePrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[124] + mi := &file_proto_user_v1_user_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9115,7 +9328,7 @@ func (x *ExpirePrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpirePrettyDisplayUserIDRequest.ProtoReflect.Descriptor instead. func (*ExpirePrettyDisplayUserIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{124} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{127} } func (x *ExpirePrettyDisplayUserIDRequest) GetMeta() *RequestMeta { @@ -9149,7 +9362,7 @@ type ExpirePrettyDisplayUserIDResponse struct { func (x *ExpirePrettyDisplayUserIDResponse) Reset() { *x = ExpirePrettyDisplayUserIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[125] + mi := &file_proto_user_v1_user_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9161,7 +9374,7 @@ func (x *ExpirePrettyDisplayUserIDResponse) String() string { func (*ExpirePrettyDisplayUserIDResponse) ProtoMessage() {} func (x *ExpirePrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[125] + mi := &file_proto_user_v1_user_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9174,7 +9387,7 @@ func (x *ExpirePrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ExpirePrettyDisplayUserIDResponse.ProtoReflect.Descriptor instead. func (*ExpirePrettyDisplayUserIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{125} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{128} } func (x *ExpirePrettyDisplayUserIDResponse) GetIdentity() *UserIdentity { @@ -9207,7 +9420,7 @@ type PrettyDisplayIDPool struct { func (x *PrettyDisplayIDPool) Reset() { *x = PrettyDisplayIDPool{} - mi := &file_proto_user_v1_user_proto_msgTypes[126] + mi := &file_proto_user_v1_user_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9219,7 +9432,7 @@ func (x *PrettyDisplayIDPool) String() string { func (*PrettyDisplayIDPool) ProtoMessage() {} func (x *PrettyDisplayIDPool) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[126] + mi := &file_proto_user_v1_user_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9232,7 +9445,7 @@ func (x *PrettyDisplayIDPool) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDPool.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDPool) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{126} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{129} } func (x *PrettyDisplayIDPool) GetAppCode() string { @@ -9358,7 +9571,7 @@ type PrettyDisplayID struct { func (x *PrettyDisplayID) Reset() { *x = PrettyDisplayID{} - mi := &file_proto_user_v1_user_proto_msgTypes[127] + mi := &file_proto_user_v1_user_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9370,7 +9583,7 @@ func (x *PrettyDisplayID) String() string { func (*PrettyDisplayID) ProtoMessage() {} func (x *PrettyDisplayID) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[127] + mi := &file_proto_user_v1_user_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9383,7 +9596,7 @@ func (x *PrettyDisplayID) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayID.ProtoReflect.Descriptor instead. func (*PrettyDisplayID) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{127} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{130} } func (x *PrettyDisplayID) GetAppCode() string { @@ -9519,7 +9732,7 @@ type PrettyDisplayIDGenerationBatch struct { func (x *PrettyDisplayIDGenerationBatch) Reset() { *x = PrettyDisplayIDGenerationBatch{} - mi := &file_proto_user_v1_user_proto_msgTypes[128] + mi := &file_proto_user_v1_user_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9531,7 +9744,7 @@ func (x *PrettyDisplayIDGenerationBatch) String() string { func (*PrettyDisplayIDGenerationBatch) ProtoMessage() {} func (x *PrettyDisplayIDGenerationBatch) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[128] + mi := &file_proto_user_v1_user_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9544,7 +9757,7 @@ func (x *PrettyDisplayIDGenerationBatch) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDGenerationBatch.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDGenerationBatch) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{128} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{131} } func (x *PrettyDisplayIDGenerationBatch) GetAppCode() string { @@ -9650,7 +9863,7 @@ type ListAvailablePrettyDisplayIDsRequest struct { func (x *ListAvailablePrettyDisplayIDsRequest) Reset() { *x = ListAvailablePrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[129] + mi := &file_proto_user_v1_user_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9662,7 +9875,7 @@ func (x *ListAvailablePrettyDisplayIDsRequest) String() string { func (*ListAvailablePrettyDisplayIDsRequest) ProtoMessage() {} func (x *ListAvailablePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[129] + mi := &file_proto_user_v1_user_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9675,7 +9888,7 @@ func (x *ListAvailablePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListAvailablePrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*ListAvailablePrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{129} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{132} } func (x *ListAvailablePrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -9716,7 +9929,7 @@ type ListAvailablePrettyDisplayIDsResponse struct { func (x *ListAvailablePrettyDisplayIDsResponse) Reset() { *x = ListAvailablePrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[130] + mi := &file_proto_user_v1_user_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9728,7 +9941,7 @@ func (x *ListAvailablePrettyDisplayIDsResponse) String() string { func (*ListAvailablePrettyDisplayIDsResponse) ProtoMessage() {} func (x *ListAvailablePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[130] + mi := &file_proto_user_v1_user_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9741,7 +9954,7 @@ func (x *ListAvailablePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListAvailablePrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*ListAvailablePrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{130} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{133} } func (x *ListAvailablePrettyDisplayIDsResponse) GetItems() []*PrettyDisplayID { @@ -9769,7 +9982,7 @@ type ApplyPrettyDisplayIDFromPoolRequest struct { func (x *ApplyPrettyDisplayIDFromPoolRequest) Reset() { *x = ApplyPrettyDisplayIDFromPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[131] + mi := &file_proto_user_v1_user_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9781,7 +9994,7 @@ func (x *ApplyPrettyDisplayIDFromPoolRequest) String() string { func (*ApplyPrettyDisplayIDFromPoolRequest) ProtoMessage() {} func (x *ApplyPrettyDisplayIDFromPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[131] + mi := &file_proto_user_v1_user_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9794,7 +10007,7 @@ func (x *ApplyPrettyDisplayIDFromPoolRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ApplyPrettyDisplayIDFromPoolRequest.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayIDFromPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{131} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{134} } func (x *ApplyPrettyDisplayIDFromPoolRequest) GetMeta() *RequestMeta { @@ -9829,7 +10042,7 @@ type ApplyPrettyDisplayIDFromPoolResponse struct { func (x *ApplyPrettyDisplayIDFromPoolResponse) Reset() { *x = ApplyPrettyDisplayIDFromPoolResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[132] + mi := &file_proto_user_v1_user_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9841,7 +10054,7 @@ func (x *ApplyPrettyDisplayIDFromPoolResponse) String() string { func (*ApplyPrettyDisplayIDFromPoolResponse) ProtoMessage() {} func (x *ApplyPrettyDisplayIDFromPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[132] + mi := &file_proto_user_v1_user_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9854,7 +10067,7 @@ func (x *ApplyPrettyDisplayIDFromPoolResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ApplyPrettyDisplayIDFromPoolResponse.ProtoReflect.Descriptor instead. func (*ApplyPrettyDisplayIDFromPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{132} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{135} } func (x *ApplyPrettyDisplayIDFromPoolResponse) GetIdentity() *UserIdentity { @@ -9891,7 +10104,7 @@ type ListPrettyDisplayIDPoolsRequest struct { func (x *ListPrettyDisplayIDPoolsRequest) Reset() { *x = ListPrettyDisplayIDPoolsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[133] + mi := &file_proto_user_v1_user_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9903,7 +10116,7 @@ func (x *ListPrettyDisplayIDPoolsRequest) String() string { func (*ListPrettyDisplayIDPoolsRequest) ProtoMessage() {} func (x *ListPrettyDisplayIDPoolsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[133] + mi := &file_proto_user_v1_user_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9916,7 +10129,7 @@ func (x *ListPrettyDisplayIDPoolsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDPoolsRequest.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDPoolsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{133} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{136} } func (x *ListPrettyDisplayIDPoolsRequest) GetMeta() *RequestMeta { @@ -9964,7 +10177,7 @@ type ListPrettyDisplayIDPoolsResponse struct { func (x *ListPrettyDisplayIDPoolsResponse) Reset() { *x = ListPrettyDisplayIDPoolsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[134] + mi := &file_proto_user_v1_user_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9976,7 +10189,7 @@ func (x *ListPrettyDisplayIDPoolsResponse) String() string { func (*ListPrettyDisplayIDPoolsResponse) ProtoMessage() {} func (x *ListPrettyDisplayIDPoolsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[134] + mi := &file_proto_user_v1_user_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9989,7 +10202,7 @@ func (x *ListPrettyDisplayIDPoolsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDPoolsResponse.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDPoolsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{134} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{137} } func (x *ListPrettyDisplayIDPoolsResponse) GetItems() []*PrettyDisplayIDPool { @@ -10024,7 +10237,7 @@ type CreatePrettyDisplayIDPoolRequest struct { func (x *CreatePrettyDisplayIDPoolRequest) Reset() { *x = CreatePrettyDisplayIDPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[135] + mi := &file_proto_user_v1_user_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10036,7 +10249,7 @@ func (x *CreatePrettyDisplayIDPoolRequest) String() string { func (*CreatePrettyDisplayIDPoolRequest) ProtoMessage() {} func (x *CreatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[135] + mi := &file_proto_user_v1_user_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10049,7 +10262,7 @@ func (x *CreatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatePrettyDisplayIDPoolRequest.ProtoReflect.Descriptor instead. func (*CreatePrettyDisplayIDPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{135} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{138} } func (x *CreatePrettyDisplayIDPoolRequest) GetMeta() *RequestMeta { @@ -10141,7 +10354,7 @@ type UpdatePrettyDisplayIDPoolRequest struct { func (x *UpdatePrettyDisplayIDPoolRequest) Reset() { *x = UpdatePrettyDisplayIDPoolRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[136] + mi := &file_proto_user_v1_user_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10153,7 +10366,7 @@ func (x *UpdatePrettyDisplayIDPoolRequest) String() string { func (*UpdatePrettyDisplayIDPoolRequest) ProtoMessage() {} func (x *UpdatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[136] + mi := &file_proto_user_v1_user_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10166,7 +10379,7 @@ func (x *UpdatePrettyDisplayIDPoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdatePrettyDisplayIDPoolRequest.ProtoReflect.Descriptor instead. func (*UpdatePrettyDisplayIDPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{136} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{139} } func (x *UpdatePrettyDisplayIDPoolRequest) GetMeta() *RequestMeta { @@ -10255,7 +10468,7 @@ type PrettyDisplayIDPoolResponse struct { func (x *PrettyDisplayIDPoolResponse) Reset() { *x = PrettyDisplayIDPoolResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[137] + mi := &file_proto_user_v1_user_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10267,7 +10480,7 @@ func (x *PrettyDisplayIDPoolResponse) String() string { func (*PrettyDisplayIDPoolResponse) ProtoMessage() {} func (x *PrettyDisplayIDPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[137] + mi := &file_proto_user_v1_user_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10280,7 +10493,7 @@ func (x *PrettyDisplayIDPoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDPoolResponse.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{137} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{140} } func (x *PrettyDisplayIDPoolResponse) GetPool() *PrettyDisplayIDPool { @@ -10304,7 +10517,7 @@ type GeneratePrettyDisplayIDsRequest struct { func (x *GeneratePrettyDisplayIDsRequest) Reset() { *x = GeneratePrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[138] + mi := &file_proto_user_v1_user_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10316,7 +10529,7 @@ func (x *GeneratePrettyDisplayIDsRequest) String() string { func (*GeneratePrettyDisplayIDsRequest) ProtoMessage() {} func (x *GeneratePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[138] + mi := &file_proto_user_v1_user_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10329,7 +10542,7 @@ func (x *GeneratePrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GeneratePrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*GeneratePrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{138} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{141} } func (x *GeneratePrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -10383,7 +10596,7 @@ type GeneratePrettyDisplayIDsResponse struct { func (x *GeneratePrettyDisplayIDsResponse) Reset() { *x = GeneratePrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[139] + mi := &file_proto_user_v1_user_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10395,7 +10608,7 @@ func (x *GeneratePrettyDisplayIDsResponse) String() string { func (*GeneratePrettyDisplayIDsResponse) ProtoMessage() {} func (x *GeneratePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[139] + mi := &file_proto_user_v1_user_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10408,7 +10621,7 @@ func (x *GeneratePrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GeneratePrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*GeneratePrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{139} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{142} } func (x *GeneratePrettyDisplayIDsResponse) GetBatch() *PrettyDisplayIDGenerationBatch { @@ -10434,7 +10647,7 @@ type ListPrettyDisplayIDsRequest struct { func (x *ListPrettyDisplayIDsRequest) Reset() { *x = ListPrettyDisplayIDsRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[140] + mi := &file_proto_user_v1_user_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10446,7 +10659,7 @@ func (x *ListPrettyDisplayIDsRequest) String() string { func (*ListPrettyDisplayIDsRequest) ProtoMessage() {} func (x *ListPrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[140] + mi := &file_proto_user_v1_user_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10459,7 +10672,7 @@ func (x *ListPrettyDisplayIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDsRequest.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDsRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{140} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{143} } func (x *ListPrettyDisplayIDsRequest) GetMeta() *RequestMeta { @@ -10528,7 +10741,7 @@ type ListPrettyDisplayIDsResponse struct { func (x *ListPrettyDisplayIDsResponse) Reset() { *x = ListPrettyDisplayIDsResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[141] + mi := &file_proto_user_v1_user_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10540,7 +10753,7 @@ func (x *ListPrettyDisplayIDsResponse) String() string { func (*ListPrettyDisplayIDsResponse) ProtoMessage() {} func (x *ListPrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[141] + mi := &file_proto_user_v1_user_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10553,7 +10766,7 @@ func (x *ListPrettyDisplayIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPrettyDisplayIDsResponse.ProtoReflect.Descriptor instead. func (*ListPrettyDisplayIDsResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{141} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{144} } func (x *ListPrettyDisplayIDsResponse) GetItems() []*PrettyDisplayID { @@ -10583,7 +10796,7 @@ type SetPrettyDisplayIDStatusRequest struct { func (x *SetPrettyDisplayIDStatusRequest) Reset() { *x = SetPrettyDisplayIDStatusRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[142] + mi := &file_proto_user_v1_user_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10595,7 +10808,7 @@ func (x *SetPrettyDisplayIDStatusRequest) String() string { func (*SetPrettyDisplayIDStatusRequest) ProtoMessage() {} func (x *SetPrettyDisplayIDStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[142] + mi := &file_proto_user_v1_user_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10608,7 +10821,7 @@ func (x *SetPrettyDisplayIDStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetPrettyDisplayIDStatusRequest.ProtoReflect.Descriptor instead. func (*SetPrettyDisplayIDStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{142} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{145} } func (x *SetPrettyDisplayIDStatusRequest) GetMeta() *RequestMeta { @@ -10655,7 +10868,7 @@ type PrettyDisplayIDResponse struct { func (x *PrettyDisplayIDResponse) Reset() { *x = PrettyDisplayIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[143] + mi := &file_proto_user_v1_user_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10667,7 +10880,7 @@ func (x *PrettyDisplayIDResponse) String() string { func (*PrettyDisplayIDResponse) ProtoMessage() {} func (x *PrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[143] + mi := &file_proto_user_v1_user_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10680,7 +10893,7 @@ func (x *PrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PrettyDisplayIDResponse.ProtoReflect.Descriptor instead. func (*PrettyDisplayIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{143} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{146} } func (x *PrettyDisplayIDResponse) GetItem() *PrettyDisplayID { @@ -10704,7 +10917,7 @@ type AdminGrantPrettyDisplayIDRequest struct { func (x *AdminGrantPrettyDisplayIDRequest) Reset() { *x = AdminGrantPrettyDisplayIDRequest{} - mi := &file_proto_user_v1_user_proto_msgTypes[144] + mi := &file_proto_user_v1_user_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10716,7 +10929,7 @@ func (x *AdminGrantPrettyDisplayIDRequest) String() string { func (*AdminGrantPrettyDisplayIDRequest) ProtoMessage() {} func (x *AdminGrantPrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[144] + mi := &file_proto_user_v1_user_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10729,7 +10942,7 @@ func (x *AdminGrantPrettyDisplayIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminGrantPrettyDisplayIDRequest.ProtoReflect.Descriptor instead. func (*AdminGrantPrettyDisplayIDRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{144} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{147} } func (x *AdminGrantPrettyDisplayIDRequest) GetMeta() *RequestMeta { @@ -10785,7 +10998,7 @@ type AdminGrantPrettyDisplayIDResponse struct { func (x *AdminGrantPrettyDisplayIDResponse) Reset() { *x = AdminGrantPrettyDisplayIDResponse{} - mi := &file_proto_user_v1_user_proto_msgTypes[145] + mi := &file_proto_user_v1_user_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10797,7 +11010,7 @@ func (x *AdminGrantPrettyDisplayIDResponse) String() string { func (*AdminGrantPrettyDisplayIDResponse) ProtoMessage() {} func (x *AdminGrantPrettyDisplayIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_user_proto_msgTypes[145] + mi := &file_proto_user_v1_user_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10810,7 +11023,7 @@ func (x *AdminGrantPrettyDisplayIDResponse) ProtoReflect() protoreflect.Message // Deprecated: Use AdminGrantPrettyDisplayIDResponse.ProtoReflect.Descriptor instead. func (*AdminGrantPrettyDisplayIDResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_user_proto_rawDescGZIP(), []int{145} + return file_proto_user_v1_user_proto_rawDescGZIP(), []int{148} } func (x *AdminGrantPrettyDisplayIDResponse) GetIdentity() *UserIdentity { @@ -10920,7 +11133,25 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\x05bound\x18\x01 \x01(\bR\x05bound\x12\x1f\n" + "\vinvite_code\x18\x02 \x01(\tR\n" + "inviteCode\x12&\n" + - "\x0finviter_user_id\x18\x03 \x01(\x03R\rinviterUserId\"\xad\x02\n" + + "\x0finviter_user_id\x18\x03 \x01(\x03R\rinviterUserId\"\xaa\x03\n" + + "\x11InviteAttribution\x12\x14\n" + + "\x05found\x18\x01 \x01(\bR\x05found\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12&\n" + + "\x0finvited_user_id\x18\x03 \x01(\x03R\rinvitedUserId\x12&\n" + + "\x0finviter_user_id\x18\x04 \x01(\x03R\rinviterUserId\x12\x1f\n" + + "\vinvite_code\x18\x05 \x01(\tR\n" + + "inviteCode\x12\x1e\n" + + "\vbound_at_ms\x18\x06 \x01(\x03R\tboundAtMs\x12'\n" + + "\x0fvalidity_status\x18\a \x01(\tR\x0evalidityStatus\x12G\n" + + " accumulated_recharge_coin_amount\x18\b \x01(\x03R\x1daccumulatedRechargeCoinAmount\x12A\n" + + "\x1drequired_recharge_coin_amount\x18\t \x01(\x03R\x1arequiredRechargeCoinAmount\x12\x1e\n" + + "\vvalid_at_ms\x18\n" + + " \x01(\x03R\tvalidAtMs\"u\n" + + "\x1bGetInviteAttributionRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12&\n" + + "\x0finvited_user_id\x18\x02 \x01(\x03R\rinvitedUserId\"b\n" + + "\x1cGetInviteAttributionResponse\x12B\n" + + "\vattribution\x18\x01 \x01(\v2 .hyapp.user.v1.InviteAttributionR\vattribution\"\xad\x02\n" + "\x14UserMicLifetimeStats\x12\x17\n" + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12(\n" + "\x10seat_occupied_ms\x18\x02 \x01(\x03R\x0eseatOccupiedMs\x12\"\n" + @@ -11758,9 +11989,10 @@ const file_proto_user_v1_user_proto_rawDesc = "" + "\x17USER_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12USER_STATUS_ACTIVE\x10\x01\x12\x18\n" + "\x14USER_STATUS_DISABLED\x10\x02\x12\x16\n" + - "\x12USER_STATUS_BANNED\x10\x032\xb5\v\n" + + "\x12USER_STATUS_BANNED\x10\x032\xa6\f\n" + "\vUserService\x12H\n" + - "\aGetUser\x12\x1d.hyapp.user.v1.GetUserRequest\x1a\x1e.hyapp.user.v1.GetUserResponse\x12i\n" + + "\aGetUser\x12\x1d.hyapp.user.v1.GetUserRequest\x1a\x1e.hyapp.user.v1.GetUserResponse\x12o\n" + + "\x14GetInviteAttribution\x12*.hyapp.user.v1.GetInviteAttributionRequest\x1a+.hyapp.user.v1.GetInviteAttributionResponse\x12i\n" + "\x12BusinessUserLookup\x12(.hyapp.user.v1.BusinessUserLookupRequest\x1a).hyapp.user.v1.BusinessUserLookupResponse\x12f\n" + "\x11GetMyProfileStats\x12'.hyapp.user.v1.GetMyProfileStatsRequest\x1a(.hyapp.user.v1.GetMyProfileStatsResponse\x12Z\n" + "\rBatchGetUsers\x12#.hyapp.user.v1.BatchGetUsersRequest\x1a$.hyapp.user.v1.BatchGetUsersResponse\x12T\n" + @@ -11851,7 +12083,7 @@ func file_proto_user_v1_user_proto_rawDescGZIP() []byte { } var file_proto_user_v1_user_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 147) +var file_proto_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 150) var file_proto_user_v1_user_proto_goTypes = []any{ (UserStatus)(0), // 0: hyapp.user.v1.UserStatus (*RequestMeta)(nil), // 1: hyapp.user.v1.RequestMeta @@ -11861,413 +12093,420 @@ var file_proto_user_v1_user_proto_goTypes = []any{ (*User)(nil), // 5: hyapp.user.v1.User (*InviteOverview)(nil), // 6: hyapp.user.v1.InviteOverview (*InviteBinding)(nil), // 7: hyapp.user.v1.InviteBinding - (*UserMicLifetimeStats)(nil), // 8: hyapp.user.v1.UserMicLifetimeStats - (*GetUserMicLifetimeStatsRequest)(nil), // 9: hyapp.user.v1.GetUserMicLifetimeStatsRequest - (*GetUserMicLifetimeStatsResponse)(nil), // 10: hyapp.user.v1.GetUserMicLifetimeStatsResponse - (*CronBatchRequest)(nil), // 11: hyapp.user.v1.CronBatchRequest - (*CronBatchResponse)(nil), // 12: hyapp.user.v1.CronBatchResponse - (*AuthToken)(nil), // 13: hyapp.user.v1.AuthToken - (*GetUserRequest)(nil), // 14: hyapp.user.v1.GetUserRequest - (*GetUserResponse)(nil), // 15: hyapp.user.v1.GetUserResponse - (*BusinessUserLookupRequest)(nil), // 16: hyapp.user.v1.BusinessUserLookupRequest - (*BusinessUserLookupItem)(nil), // 17: hyapp.user.v1.BusinessUserLookupItem - (*BusinessUserLookupResponse)(nil), // 18: hyapp.user.v1.BusinessUserLookupResponse - (*UserProfileStats)(nil), // 19: hyapp.user.v1.UserProfileStats - (*GetMyProfileStatsRequest)(nil), // 20: hyapp.user.v1.GetMyProfileStatsRequest - (*GetMyProfileStatsResponse)(nil), // 21: hyapp.user.v1.GetMyProfileStatsResponse - (*RecordProfileVisitRequest)(nil), // 22: hyapp.user.v1.RecordProfileVisitRequest - (*RecordProfileVisitResponse)(nil), // 23: hyapp.user.v1.RecordProfileVisitResponse - (*ProfileVisitRecord)(nil), // 24: hyapp.user.v1.ProfileVisitRecord - (*ListProfileVisitorsRequest)(nil), // 25: hyapp.user.v1.ListProfileVisitorsRequest - (*ListProfileVisitorsResponse)(nil), // 26: hyapp.user.v1.ListProfileVisitorsResponse - (*FollowUserRequest)(nil), // 27: hyapp.user.v1.FollowUserRequest - (*FollowUserResponse)(nil), // 28: hyapp.user.v1.FollowUserResponse - (*UnfollowUserRequest)(nil), // 29: hyapp.user.v1.UnfollowUserRequest - (*UnfollowUserResponse)(nil), // 30: hyapp.user.v1.UnfollowUserResponse - (*FollowRecord)(nil), // 31: hyapp.user.v1.FollowRecord - (*ListFollowingRequest)(nil), // 32: hyapp.user.v1.ListFollowingRequest - (*ListFollowingResponse)(nil), // 33: hyapp.user.v1.ListFollowingResponse - (*ApplyFriendRequest)(nil), // 34: hyapp.user.v1.ApplyFriendRequest - (*ApplyFriendResponse)(nil), // 35: hyapp.user.v1.ApplyFriendResponse - (*AcceptFriendApplicationRequest)(nil), // 36: hyapp.user.v1.AcceptFriendApplicationRequest - (*AcceptFriendApplicationResponse)(nil), // 37: hyapp.user.v1.AcceptFriendApplicationResponse - (*DeleteFriendRequest)(nil), // 38: hyapp.user.v1.DeleteFriendRequest - (*DeleteFriendResponse)(nil), // 39: hyapp.user.v1.DeleteFriendResponse - (*FriendRecord)(nil), // 40: hyapp.user.v1.FriendRecord - (*ListFriendsRequest)(nil), // 41: hyapp.user.v1.ListFriendsRequest - (*ListFriendsResponse)(nil), // 42: hyapp.user.v1.ListFriendsResponse - (*FriendApplication)(nil), // 43: hyapp.user.v1.FriendApplication - (*ListFriendApplicationsRequest)(nil), // 44: hyapp.user.v1.ListFriendApplicationsRequest - (*ListFriendApplicationsResponse)(nil), // 45: hyapp.user.v1.ListFriendApplicationsResponse - (*CPUserProfile)(nil), // 46: hyapp.user.v1.CPUserProfile - (*CPGiftSnapshot)(nil), // 47: hyapp.user.v1.CPGiftSnapshot - (*CPApplication)(nil), // 48: hyapp.user.v1.CPApplication - (*CPRelationship)(nil), // 49: hyapp.user.v1.CPRelationship - (*CPAvatarFrameSnapshot)(nil), // 50: hyapp.user.v1.CPAvatarFrameSnapshot - (*CPIntimacyLeaderboardUser)(nil), // 51: hyapp.user.v1.CPIntimacyLeaderboardUser - (*CPIntimacyLeaderboardItem)(nil), // 52: hyapp.user.v1.CPIntimacyLeaderboardItem - (*ListCPApplicationsRequest)(nil), // 53: hyapp.user.v1.ListCPApplicationsRequest - (*ListCPApplicationsResponse)(nil), // 54: hyapp.user.v1.ListCPApplicationsResponse - (*AcceptCPApplicationRequest)(nil), // 55: hyapp.user.v1.AcceptCPApplicationRequest - (*AcceptCPApplicationResponse)(nil), // 56: hyapp.user.v1.AcceptCPApplicationResponse - (*RejectCPApplicationRequest)(nil), // 57: hyapp.user.v1.RejectCPApplicationRequest - (*RejectCPApplicationResponse)(nil), // 58: hyapp.user.v1.RejectCPApplicationResponse - (*ListCPRelationshipsRequest)(nil), // 59: hyapp.user.v1.ListCPRelationshipsRequest - (*ListCPRelationshipsResponse)(nil), // 60: hyapp.user.v1.ListCPRelationshipsResponse - (*ListCPIntimacyLeaderboardRequest)(nil), // 61: hyapp.user.v1.ListCPIntimacyLeaderboardRequest - (*ListCPIntimacyLeaderboardResponse)(nil), // 62: hyapp.user.v1.ListCPIntimacyLeaderboardResponse - (*PrepareBreakCPRelationshipRequest)(nil), // 63: hyapp.user.v1.PrepareBreakCPRelationshipRequest - (*PrepareBreakCPRelationshipResponse)(nil), // 64: hyapp.user.v1.PrepareBreakCPRelationshipResponse - (*ConfirmBreakCPRelationshipRequest)(nil), // 65: hyapp.user.v1.ConfirmBreakCPRelationshipRequest - (*ConfirmBreakCPRelationshipResponse)(nil), // 66: hyapp.user.v1.ConfirmBreakCPRelationshipResponse - (*CancelBreakCPRelationshipRequest)(nil), // 67: hyapp.user.v1.CancelBreakCPRelationshipRequest - (*CancelBreakCPRelationshipResponse)(nil), // 68: hyapp.user.v1.CancelBreakCPRelationshipResponse - (*RoomGiftCPEvent)(nil), // 69: hyapp.user.v1.RoomGiftCPEvent - (*ConsumeRoomGiftCPEventRequest)(nil), // 70: hyapp.user.v1.ConsumeRoomGiftCPEventRequest - (*ConsumeRoomGiftCPEventResponse)(nil), // 71: hyapp.user.v1.ConsumeRoomGiftCPEventResponse - (*UserReport)(nil), // 72: hyapp.user.v1.UserReport - (*SubmitReportRequest)(nil), // 73: hyapp.user.v1.SubmitReportRequest - (*SubmitReportResponse)(nil), // 74: hyapp.user.v1.SubmitReportResponse - (*BatchGetUsersRequest)(nil), // 75: hyapp.user.v1.BatchGetUsersRequest - (*BatchGetUsersResponse)(nil), // 76: hyapp.user.v1.BatchGetUsersResponse - (*ListUserIDsRequest)(nil), // 77: hyapp.user.v1.ListUserIDsRequest - (*ListUserIDsResponse)(nil), // 78: hyapp.user.v1.ListUserIDsResponse - (*UpdateUserProfileRequest)(nil), // 79: hyapp.user.v1.UpdateUserProfileRequest - (*UpdateUserProfileResponse)(nil), // 80: hyapp.user.v1.UpdateUserProfileResponse - (*ChangeUserCountryRequest)(nil), // 81: hyapp.user.v1.ChangeUserCountryRequest - (*AdminChangeUserCountryRequest)(nil), // 82: hyapp.user.v1.AdminChangeUserCountryRequest - (*ChangeUserCountryResponse)(nil), // 83: hyapp.user.v1.ChangeUserCountryResponse - (*SetUserStatusRequest)(nil), // 84: hyapp.user.v1.SetUserStatusRequest - (*SetUserStatusResponse)(nil), // 85: hyapp.user.v1.SetUserStatusResponse - (*ManagerUserBlock)(nil), // 86: hyapp.user.v1.ManagerUserBlock - (*CreateManagerUserBlockRequest)(nil), // 87: hyapp.user.v1.CreateManagerUserBlockRequest - (*CreateManagerUserBlockResponse)(nil), // 88: hyapp.user.v1.CreateManagerUserBlockResponse - (*ListManagerUserBlocksRequest)(nil), // 89: hyapp.user.v1.ListManagerUserBlocksRequest - (*ListManagerUserBlocksResponse)(nil), // 90: hyapp.user.v1.ListManagerUserBlocksResponse - (*UnblockManagerUserRequest)(nil), // 91: hyapp.user.v1.UnblockManagerUserRequest - (*UnblockManagerUserResponse)(nil), // 92: hyapp.user.v1.UnblockManagerUserResponse - (*CompleteOnboardingRequest)(nil), // 93: hyapp.user.v1.CompleteOnboardingRequest - (*CompleteOnboardingResponse)(nil), // 94: hyapp.user.v1.CompleteOnboardingResponse - (*BindPushTokenRequest)(nil), // 95: hyapp.user.v1.BindPushTokenRequest - (*BindPushTokenResponse)(nil), // 96: hyapp.user.v1.BindPushTokenResponse - (*DeletePushTokenRequest)(nil), // 97: hyapp.user.v1.DeletePushTokenRequest - (*DeletePushTokenResponse)(nil), // 98: hyapp.user.v1.DeletePushTokenResponse - (*Country)(nil), // 99: hyapp.user.v1.Country - (*Region)(nil), // 100: hyapp.user.v1.Region - (*ListCountriesRequest)(nil), // 101: hyapp.user.v1.ListCountriesRequest - (*ListCountriesResponse)(nil), // 102: hyapp.user.v1.ListCountriesResponse - (*UpdateCountryRequest)(nil), // 103: hyapp.user.v1.UpdateCountryRequest - (*CountryResponse)(nil), // 104: hyapp.user.v1.CountryResponse - (*ListRegistrationCountriesRequest)(nil), // 105: hyapp.user.v1.ListRegistrationCountriesRequest - (*ListRegistrationCountriesResponse)(nil), // 106: hyapp.user.v1.ListRegistrationCountriesResponse - (*LoginRiskBlockedCountry)(nil), // 107: hyapp.user.v1.LoginRiskBlockedCountry - (*ListLoginRiskBlockedCountriesRequest)(nil), // 108: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest - (*ListLoginRiskBlockedCountriesResponse)(nil), // 109: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse - (*ListRegionsRequest)(nil), // 110: hyapp.user.v1.ListRegionsRequest - (*ListRegionsResponse)(nil), // 111: hyapp.user.v1.ListRegionsResponse - (*GetRegionRequest)(nil), // 112: hyapp.user.v1.GetRegionRequest - (*UpdateRegionRequest)(nil), // 113: hyapp.user.v1.UpdateRegionRequest - (*ReplaceRegionCountriesRequest)(nil), // 114: hyapp.user.v1.ReplaceRegionCountriesRequest - (*RegionResponse)(nil), // 115: hyapp.user.v1.RegionResponse - (*UserIdentity)(nil), // 116: hyapp.user.v1.UserIdentity - (*GetUserIdentityRequest)(nil), // 117: hyapp.user.v1.GetUserIdentityRequest - (*GetUserIdentityResponse)(nil), // 118: hyapp.user.v1.GetUserIdentityResponse - (*ResolveDisplayUserIDRequest)(nil), // 119: hyapp.user.v1.ResolveDisplayUserIDRequest - (*ResolveDisplayUserIDResponse)(nil), // 120: hyapp.user.v1.ResolveDisplayUserIDResponse - (*ChangeDisplayUserIDRequest)(nil), // 121: hyapp.user.v1.ChangeDisplayUserIDRequest - (*ChangeDisplayUserIDResponse)(nil), // 122: hyapp.user.v1.ChangeDisplayUserIDResponse - (*ApplyPrettyDisplayUserIDRequest)(nil), // 123: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest - (*ApplyPrettyDisplayUserIDResponse)(nil), // 124: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse - (*ExpirePrettyDisplayUserIDRequest)(nil), // 125: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest - (*ExpirePrettyDisplayUserIDResponse)(nil), // 126: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse - (*PrettyDisplayIDPool)(nil), // 127: hyapp.user.v1.PrettyDisplayIDPool - (*PrettyDisplayID)(nil), // 128: hyapp.user.v1.PrettyDisplayID - (*PrettyDisplayIDGenerationBatch)(nil), // 129: hyapp.user.v1.PrettyDisplayIDGenerationBatch - (*ListAvailablePrettyDisplayIDsRequest)(nil), // 130: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest - (*ListAvailablePrettyDisplayIDsResponse)(nil), // 131: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse - (*ApplyPrettyDisplayIDFromPoolRequest)(nil), // 132: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest - (*ApplyPrettyDisplayIDFromPoolResponse)(nil), // 133: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse - (*ListPrettyDisplayIDPoolsRequest)(nil), // 134: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest - (*ListPrettyDisplayIDPoolsResponse)(nil), // 135: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse - (*CreatePrettyDisplayIDPoolRequest)(nil), // 136: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest - (*UpdatePrettyDisplayIDPoolRequest)(nil), // 137: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest - (*PrettyDisplayIDPoolResponse)(nil), // 138: hyapp.user.v1.PrettyDisplayIDPoolResponse - (*GeneratePrettyDisplayIDsRequest)(nil), // 139: hyapp.user.v1.GeneratePrettyDisplayIDsRequest - (*GeneratePrettyDisplayIDsResponse)(nil), // 140: hyapp.user.v1.GeneratePrettyDisplayIDsResponse - (*ListPrettyDisplayIDsRequest)(nil), // 141: hyapp.user.v1.ListPrettyDisplayIDsRequest - (*ListPrettyDisplayIDsResponse)(nil), // 142: hyapp.user.v1.ListPrettyDisplayIDsResponse - (*SetPrettyDisplayIDStatusRequest)(nil), // 143: hyapp.user.v1.SetPrettyDisplayIDStatusRequest - (*PrettyDisplayIDResponse)(nil), // 144: hyapp.user.v1.PrettyDisplayIDResponse - (*AdminGrantPrettyDisplayIDRequest)(nil), // 145: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest - (*AdminGrantPrettyDisplayIDResponse)(nil), // 146: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse - nil, // 147: hyapp.user.v1.BatchGetUsersResponse.UsersEntry + (*InviteAttribution)(nil), // 8: hyapp.user.v1.InviteAttribution + (*GetInviteAttributionRequest)(nil), // 9: hyapp.user.v1.GetInviteAttributionRequest + (*GetInviteAttributionResponse)(nil), // 10: hyapp.user.v1.GetInviteAttributionResponse + (*UserMicLifetimeStats)(nil), // 11: hyapp.user.v1.UserMicLifetimeStats + (*GetUserMicLifetimeStatsRequest)(nil), // 12: hyapp.user.v1.GetUserMicLifetimeStatsRequest + (*GetUserMicLifetimeStatsResponse)(nil), // 13: hyapp.user.v1.GetUserMicLifetimeStatsResponse + (*CronBatchRequest)(nil), // 14: hyapp.user.v1.CronBatchRequest + (*CronBatchResponse)(nil), // 15: hyapp.user.v1.CronBatchResponse + (*AuthToken)(nil), // 16: hyapp.user.v1.AuthToken + (*GetUserRequest)(nil), // 17: hyapp.user.v1.GetUserRequest + (*GetUserResponse)(nil), // 18: hyapp.user.v1.GetUserResponse + (*BusinessUserLookupRequest)(nil), // 19: hyapp.user.v1.BusinessUserLookupRequest + (*BusinessUserLookupItem)(nil), // 20: hyapp.user.v1.BusinessUserLookupItem + (*BusinessUserLookupResponse)(nil), // 21: hyapp.user.v1.BusinessUserLookupResponse + (*UserProfileStats)(nil), // 22: hyapp.user.v1.UserProfileStats + (*GetMyProfileStatsRequest)(nil), // 23: hyapp.user.v1.GetMyProfileStatsRequest + (*GetMyProfileStatsResponse)(nil), // 24: hyapp.user.v1.GetMyProfileStatsResponse + (*RecordProfileVisitRequest)(nil), // 25: hyapp.user.v1.RecordProfileVisitRequest + (*RecordProfileVisitResponse)(nil), // 26: hyapp.user.v1.RecordProfileVisitResponse + (*ProfileVisitRecord)(nil), // 27: hyapp.user.v1.ProfileVisitRecord + (*ListProfileVisitorsRequest)(nil), // 28: hyapp.user.v1.ListProfileVisitorsRequest + (*ListProfileVisitorsResponse)(nil), // 29: hyapp.user.v1.ListProfileVisitorsResponse + (*FollowUserRequest)(nil), // 30: hyapp.user.v1.FollowUserRequest + (*FollowUserResponse)(nil), // 31: hyapp.user.v1.FollowUserResponse + (*UnfollowUserRequest)(nil), // 32: hyapp.user.v1.UnfollowUserRequest + (*UnfollowUserResponse)(nil), // 33: hyapp.user.v1.UnfollowUserResponse + (*FollowRecord)(nil), // 34: hyapp.user.v1.FollowRecord + (*ListFollowingRequest)(nil), // 35: hyapp.user.v1.ListFollowingRequest + (*ListFollowingResponse)(nil), // 36: hyapp.user.v1.ListFollowingResponse + (*ApplyFriendRequest)(nil), // 37: hyapp.user.v1.ApplyFriendRequest + (*ApplyFriendResponse)(nil), // 38: hyapp.user.v1.ApplyFriendResponse + (*AcceptFriendApplicationRequest)(nil), // 39: hyapp.user.v1.AcceptFriendApplicationRequest + (*AcceptFriendApplicationResponse)(nil), // 40: hyapp.user.v1.AcceptFriendApplicationResponse + (*DeleteFriendRequest)(nil), // 41: hyapp.user.v1.DeleteFriendRequest + (*DeleteFriendResponse)(nil), // 42: hyapp.user.v1.DeleteFriendResponse + (*FriendRecord)(nil), // 43: hyapp.user.v1.FriendRecord + (*ListFriendsRequest)(nil), // 44: hyapp.user.v1.ListFriendsRequest + (*ListFriendsResponse)(nil), // 45: hyapp.user.v1.ListFriendsResponse + (*FriendApplication)(nil), // 46: hyapp.user.v1.FriendApplication + (*ListFriendApplicationsRequest)(nil), // 47: hyapp.user.v1.ListFriendApplicationsRequest + (*ListFriendApplicationsResponse)(nil), // 48: hyapp.user.v1.ListFriendApplicationsResponse + (*CPUserProfile)(nil), // 49: hyapp.user.v1.CPUserProfile + (*CPGiftSnapshot)(nil), // 50: hyapp.user.v1.CPGiftSnapshot + (*CPApplication)(nil), // 51: hyapp.user.v1.CPApplication + (*CPRelationship)(nil), // 52: hyapp.user.v1.CPRelationship + (*CPAvatarFrameSnapshot)(nil), // 53: hyapp.user.v1.CPAvatarFrameSnapshot + (*CPIntimacyLeaderboardUser)(nil), // 54: hyapp.user.v1.CPIntimacyLeaderboardUser + (*CPIntimacyLeaderboardItem)(nil), // 55: hyapp.user.v1.CPIntimacyLeaderboardItem + (*ListCPApplicationsRequest)(nil), // 56: hyapp.user.v1.ListCPApplicationsRequest + (*ListCPApplicationsResponse)(nil), // 57: hyapp.user.v1.ListCPApplicationsResponse + (*AcceptCPApplicationRequest)(nil), // 58: hyapp.user.v1.AcceptCPApplicationRequest + (*AcceptCPApplicationResponse)(nil), // 59: hyapp.user.v1.AcceptCPApplicationResponse + (*RejectCPApplicationRequest)(nil), // 60: hyapp.user.v1.RejectCPApplicationRequest + (*RejectCPApplicationResponse)(nil), // 61: hyapp.user.v1.RejectCPApplicationResponse + (*ListCPRelationshipsRequest)(nil), // 62: hyapp.user.v1.ListCPRelationshipsRequest + (*ListCPRelationshipsResponse)(nil), // 63: hyapp.user.v1.ListCPRelationshipsResponse + (*ListCPIntimacyLeaderboardRequest)(nil), // 64: hyapp.user.v1.ListCPIntimacyLeaderboardRequest + (*ListCPIntimacyLeaderboardResponse)(nil), // 65: hyapp.user.v1.ListCPIntimacyLeaderboardResponse + (*PrepareBreakCPRelationshipRequest)(nil), // 66: hyapp.user.v1.PrepareBreakCPRelationshipRequest + (*PrepareBreakCPRelationshipResponse)(nil), // 67: hyapp.user.v1.PrepareBreakCPRelationshipResponse + (*ConfirmBreakCPRelationshipRequest)(nil), // 68: hyapp.user.v1.ConfirmBreakCPRelationshipRequest + (*ConfirmBreakCPRelationshipResponse)(nil), // 69: hyapp.user.v1.ConfirmBreakCPRelationshipResponse + (*CancelBreakCPRelationshipRequest)(nil), // 70: hyapp.user.v1.CancelBreakCPRelationshipRequest + (*CancelBreakCPRelationshipResponse)(nil), // 71: hyapp.user.v1.CancelBreakCPRelationshipResponse + (*RoomGiftCPEvent)(nil), // 72: hyapp.user.v1.RoomGiftCPEvent + (*ConsumeRoomGiftCPEventRequest)(nil), // 73: hyapp.user.v1.ConsumeRoomGiftCPEventRequest + (*ConsumeRoomGiftCPEventResponse)(nil), // 74: hyapp.user.v1.ConsumeRoomGiftCPEventResponse + (*UserReport)(nil), // 75: hyapp.user.v1.UserReport + (*SubmitReportRequest)(nil), // 76: hyapp.user.v1.SubmitReportRequest + (*SubmitReportResponse)(nil), // 77: hyapp.user.v1.SubmitReportResponse + (*BatchGetUsersRequest)(nil), // 78: hyapp.user.v1.BatchGetUsersRequest + (*BatchGetUsersResponse)(nil), // 79: hyapp.user.v1.BatchGetUsersResponse + (*ListUserIDsRequest)(nil), // 80: hyapp.user.v1.ListUserIDsRequest + (*ListUserIDsResponse)(nil), // 81: hyapp.user.v1.ListUserIDsResponse + (*UpdateUserProfileRequest)(nil), // 82: hyapp.user.v1.UpdateUserProfileRequest + (*UpdateUserProfileResponse)(nil), // 83: hyapp.user.v1.UpdateUserProfileResponse + (*ChangeUserCountryRequest)(nil), // 84: hyapp.user.v1.ChangeUserCountryRequest + (*AdminChangeUserCountryRequest)(nil), // 85: hyapp.user.v1.AdminChangeUserCountryRequest + (*ChangeUserCountryResponse)(nil), // 86: hyapp.user.v1.ChangeUserCountryResponse + (*SetUserStatusRequest)(nil), // 87: hyapp.user.v1.SetUserStatusRequest + (*SetUserStatusResponse)(nil), // 88: hyapp.user.v1.SetUserStatusResponse + (*ManagerUserBlock)(nil), // 89: hyapp.user.v1.ManagerUserBlock + (*CreateManagerUserBlockRequest)(nil), // 90: hyapp.user.v1.CreateManagerUserBlockRequest + (*CreateManagerUserBlockResponse)(nil), // 91: hyapp.user.v1.CreateManagerUserBlockResponse + (*ListManagerUserBlocksRequest)(nil), // 92: hyapp.user.v1.ListManagerUserBlocksRequest + (*ListManagerUserBlocksResponse)(nil), // 93: hyapp.user.v1.ListManagerUserBlocksResponse + (*UnblockManagerUserRequest)(nil), // 94: hyapp.user.v1.UnblockManagerUserRequest + (*UnblockManagerUserResponse)(nil), // 95: hyapp.user.v1.UnblockManagerUserResponse + (*CompleteOnboardingRequest)(nil), // 96: hyapp.user.v1.CompleteOnboardingRequest + (*CompleteOnboardingResponse)(nil), // 97: hyapp.user.v1.CompleteOnboardingResponse + (*BindPushTokenRequest)(nil), // 98: hyapp.user.v1.BindPushTokenRequest + (*BindPushTokenResponse)(nil), // 99: hyapp.user.v1.BindPushTokenResponse + (*DeletePushTokenRequest)(nil), // 100: hyapp.user.v1.DeletePushTokenRequest + (*DeletePushTokenResponse)(nil), // 101: hyapp.user.v1.DeletePushTokenResponse + (*Country)(nil), // 102: hyapp.user.v1.Country + (*Region)(nil), // 103: hyapp.user.v1.Region + (*ListCountriesRequest)(nil), // 104: hyapp.user.v1.ListCountriesRequest + (*ListCountriesResponse)(nil), // 105: hyapp.user.v1.ListCountriesResponse + (*UpdateCountryRequest)(nil), // 106: hyapp.user.v1.UpdateCountryRequest + (*CountryResponse)(nil), // 107: hyapp.user.v1.CountryResponse + (*ListRegistrationCountriesRequest)(nil), // 108: hyapp.user.v1.ListRegistrationCountriesRequest + (*ListRegistrationCountriesResponse)(nil), // 109: hyapp.user.v1.ListRegistrationCountriesResponse + (*LoginRiskBlockedCountry)(nil), // 110: hyapp.user.v1.LoginRiskBlockedCountry + (*ListLoginRiskBlockedCountriesRequest)(nil), // 111: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest + (*ListLoginRiskBlockedCountriesResponse)(nil), // 112: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse + (*ListRegionsRequest)(nil), // 113: hyapp.user.v1.ListRegionsRequest + (*ListRegionsResponse)(nil), // 114: hyapp.user.v1.ListRegionsResponse + (*GetRegionRequest)(nil), // 115: hyapp.user.v1.GetRegionRequest + (*UpdateRegionRequest)(nil), // 116: hyapp.user.v1.UpdateRegionRequest + (*ReplaceRegionCountriesRequest)(nil), // 117: hyapp.user.v1.ReplaceRegionCountriesRequest + (*RegionResponse)(nil), // 118: hyapp.user.v1.RegionResponse + (*UserIdentity)(nil), // 119: hyapp.user.v1.UserIdentity + (*GetUserIdentityRequest)(nil), // 120: hyapp.user.v1.GetUserIdentityRequest + (*GetUserIdentityResponse)(nil), // 121: hyapp.user.v1.GetUserIdentityResponse + (*ResolveDisplayUserIDRequest)(nil), // 122: hyapp.user.v1.ResolveDisplayUserIDRequest + (*ResolveDisplayUserIDResponse)(nil), // 123: hyapp.user.v1.ResolveDisplayUserIDResponse + (*ChangeDisplayUserIDRequest)(nil), // 124: hyapp.user.v1.ChangeDisplayUserIDRequest + (*ChangeDisplayUserIDResponse)(nil), // 125: hyapp.user.v1.ChangeDisplayUserIDResponse + (*ApplyPrettyDisplayUserIDRequest)(nil), // 126: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest + (*ApplyPrettyDisplayUserIDResponse)(nil), // 127: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse + (*ExpirePrettyDisplayUserIDRequest)(nil), // 128: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest + (*ExpirePrettyDisplayUserIDResponse)(nil), // 129: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse + (*PrettyDisplayIDPool)(nil), // 130: hyapp.user.v1.PrettyDisplayIDPool + (*PrettyDisplayID)(nil), // 131: hyapp.user.v1.PrettyDisplayID + (*PrettyDisplayIDGenerationBatch)(nil), // 132: hyapp.user.v1.PrettyDisplayIDGenerationBatch + (*ListAvailablePrettyDisplayIDsRequest)(nil), // 133: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest + (*ListAvailablePrettyDisplayIDsResponse)(nil), // 134: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse + (*ApplyPrettyDisplayIDFromPoolRequest)(nil), // 135: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest + (*ApplyPrettyDisplayIDFromPoolResponse)(nil), // 136: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse + (*ListPrettyDisplayIDPoolsRequest)(nil), // 137: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest + (*ListPrettyDisplayIDPoolsResponse)(nil), // 138: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse + (*CreatePrettyDisplayIDPoolRequest)(nil), // 139: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest + (*UpdatePrettyDisplayIDPoolRequest)(nil), // 140: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest + (*PrettyDisplayIDPoolResponse)(nil), // 141: hyapp.user.v1.PrettyDisplayIDPoolResponse + (*GeneratePrettyDisplayIDsRequest)(nil), // 142: hyapp.user.v1.GeneratePrettyDisplayIDsRequest + (*GeneratePrettyDisplayIDsResponse)(nil), // 143: hyapp.user.v1.GeneratePrettyDisplayIDsResponse + (*ListPrettyDisplayIDsRequest)(nil), // 144: hyapp.user.v1.ListPrettyDisplayIDsRequest + (*ListPrettyDisplayIDsResponse)(nil), // 145: hyapp.user.v1.ListPrettyDisplayIDsResponse + (*SetPrettyDisplayIDStatusRequest)(nil), // 146: hyapp.user.v1.SetPrettyDisplayIDStatusRequest + (*PrettyDisplayIDResponse)(nil), // 147: hyapp.user.v1.PrettyDisplayIDResponse + (*AdminGrantPrettyDisplayIDRequest)(nil), // 148: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest + (*AdminGrantPrettyDisplayIDResponse)(nil), // 149: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse + nil, // 150: hyapp.user.v1.BatchGetUsersResponse.UsersEntry } var file_proto_user_v1_user_proto_depIdxs = []int32{ 1, // 0: hyapp.user.v1.ResolveAppRequest.meta:type_name -> hyapp.user.v1.RequestMeta 2, // 1: hyapp.user.v1.ResolveAppResponse.app:type_name -> hyapp.user.v1.App 0, // 2: hyapp.user.v1.User.status:type_name -> hyapp.user.v1.UserStatus 6, // 3: hyapp.user.v1.User.invite:type_name -> hyapp.user.v1.InviteOverview - 1, // 4: hyapp.user.v1.GetUserMicLifetimeStatsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 8, // 5: hyapp.user.v1.GetUserMicLifetimeStatsResponse.stats:type_name -> hyapp.user.v1.UserMicLifetimeStats - 1, // 6: hyapp.user.v1.CronBatchRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 7: hyapp.user.v1.GetUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 8: hyapp.user.v1.GetUserResponse.user:type_name -> hyapp.user.v1.User - 1, // 9: hyapp.user.v1.BusinessUserLookupRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 0, // 10: hyapp.user.v1.BusinessUserLookupItem.status:type_name -> hyapp.user.v1.UserStatus - 17, // 11: hyapp.user.v1.BusinessUserLookupResponse.users:type_name -> hyapp.user.v1.BusinessUserLookupItem - 1, // 12: hyapp.user.v1.GetMyProfileStatsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 19, // 13: hyapp.user.v1.GetMyProfileStatsResponse.stats:type_name -> hyapp.user.v1.UserProfileStats - 1, // 14: hyapp.user.v1.RecordProfileVisitRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 19, // 15: hyapp.user.v1.RecordProfileVisitResponse.target_stats:type_name -> hyapp.user.v1.UserProfileStats - 1, // 16: hyapp.user.v1.ListProfileVisitorsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 24, // 17: hyapp.user.v1.ListProfileVisitorsResponse.records:type_name -> hyapp.user.v1.ProfileVisitRecord - 1, // 18: hyapp.user.v1.FollowUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 19, // 19: hyapp.user.v1.FollowUserResponse.follower_stats:type_name -> hyapp.user.v1.UserProfileStats - 1, // 20: hyapp.user.v1.UnfollowUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 19, // 21: hyapp.user.v1.UnfollowUserResponse.follower_stats:type_name -> hyapp.user.v1.UserProfileStats - 1, // 22: hyapp.user.v1.ListFollowingRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 31, // 23: hyapp.user.v1.ListFollowingResponse.records:type_name -> hyapp.user.v1.FollowRecord - 1, // 24: hyapp.user.v1.ApplyFriendRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 43, // 25: hyapp.user.v1.ApplyFriendResponse.application:type_name -> hyapp.user.v1.FriendApplication - 1, // 26: hyapp.user.v1.AcceptFriendApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 40, // 27: hyapp.user.v1.AcceptFriendApplicationResponse.friend:type_name -> hyapp.user.v1.FriendRecord - 1, // 28: hyapp.user.v1.DeleteFriendRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 29: hyapp.user.v1.ListFriendsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 40, // 30: hyapp.user.v1.ListFriendsResponse.records:type_name -> hyapp.user.v1.FriendRecord - 1, // 31: hyapp.user.v1.ListFriendApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 43, // 32: hyapp.user.v1.ListFriendApplicationsResponse.applications:type_name -> hyapp.user.v1.FriendApplication - 46, // 33: hyapp.user.v1.CPApplication.requester:type_name -> hyapp.user.v1.CPUserProfile - 46, // 34: hyapp.user.v1.CPApplication.target:type_name -> hyapp.user.v1.CPUserProfile - 47, // 35: hyapp.user.v1.CPApplication.gift:type_name -> hyapp.user.v1.CPGiftSnapshot - 46, // 36: hyapp.user.v1.CPRelationship.me:type_name -> hyapp.user.v1.CPUserProfile - 46, // 37: hyapp.user.v1.CPRelationship.partner:type_name -> hyapp.user.v1.CPUserProfile - 50, // 38: hyapp.user.v1.CPIntimacyLeaderboardUser.avatar_frame:type_name -> hyapp.user.v1.CPAvatarFrameSnapshot - 51, // 39: hyapp.user.v1.CPIntimacyLeaderboardItem.user_a:type_name -> hyapp.user.v1.CPIntimacyLeaderboardUser - 51, // 40: hyapp.user.v1.CPIntimacyLeaderboardItem.user_b:type_name -> hyapp.user.v1.CPIntimacyLeaderboardUser - 1, // 41: hyapp.user.v1.ListCPApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 48, // 42: hyapp.user.v1.ListCPApplicationsResponse.applications:type_name -> hyapp.user.v1.CPApplication - 1, // 43: hyapp.user.v1.AcceptCPApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 48, // 44: hyapp.user.v1.AcceptCPApplicationResponse.application:type_name -> hyapp.user.v1.CPApplication - 49, // 45: hyapp.user.v1.AcceptCPApplicationResponse.relationship:type_name -> hyapp.user.v1.CPRelationship - 1, // 46: hyapp.user.v1.RejectCPApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 48, // 47: hyapp.user.v1.RejectCPApplicationResponse.application:type_name -> hyapp.user.v1.CPApplication - 1, // 48: hyapp.user.v1.ListCPRelationshipsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 49, // 49: hyapp.user.v1.ListCPRelationshipsResponse.relationships:type_name -> hyapp.user.v1.CPRelationship - 1, // 50: hyapp.user.v1.ListCPIntimacyLeaderboardRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 52, // 51: hyapp.user.v1.ListCPIntimacyLeaderboardResponse.items:type_name -> hyapp.user.v1.CPIntimacyLeaderboardItem - 1, // 52: hyapp.user.v1.PrepareBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 49, // 53: hyapp.user.v1.PrepareBreakCPRelationshipResponse.relationship:type_name -> hyapp.user.v1.CPRelationship - 1, // 54: hyapp.user.v1.ConfirmBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 49, // 55: hyapp.user.v1.ConfirmBreakCPRelationshipResponse.relationship:type_name -> hyapp.user.v1.CPRelationship - 1, // 56: hyapp.user.v1.CancelBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 57: hyapp.user.v1.RoomGiftCPEvent.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 58: hyapp.user.v1.ConsumeRoomGiftCPEventRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 69, // 59: hyapp.user.v1.ConsumeRoomGiftCPEventRequest.event:type_name -> hyapp.user.v1.RoomGiftCPEvent - 48, // 60: hyapp.user.v1.ConsumeRoomGiftCPEventResponse.application:type_name -> hyapp.user.v1.CPApplication - 49, // 61: hyapp.user.v1.ConsumeRoomGiftCPEventResponse.relationship:type_name -> hyapp.user.v1.CPRelationship - 1, // 62: hyapp.user.v1.SubmitReportRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 72, // 63: hyapp.user.v1.SubmitReportResponse.report:type_name -> hyapp.user.v1.UserReport - 1, // 64: hyapp.user.v1.BatchGetUsersRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 147, // 65: hyapp.user.v1.BatchGetUsersResponse.users:type_name -> hyapp.user.v1.BatchGetUsersResponse.UsersEntry - 1, // 66: hyapp.user.v1.ListUserIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 67: hyapp.user.v1.UpdateUserProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 68: hyapp.user.v1.UpdateUserProfileResponse.user:type_name -> hyapp.user.v1.User - 1, // 69: hyapp.user.v1.ChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 70: hyapp.user.v1.AdminChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 71: hyapp.user.v1.ChangeUserCountryResponse.user:type_name -> hyapp.user.v1.User - 1, // 72: hyapp.user.v1.SetUserStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 0, // 73: hyapp.user.v1.SetUserStatusRequest.status:type_name -> hyapp.user.v1.UserStatus - 5, // 74: hyapp.user.v1.SetUserStatusResponse.user:type_name -> hyapp.user.v1.User - 1, // 75: hyapp.user.v1.CreateManagerUserBlockRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 86, // 76: hyapp.user.v1.CreateManagerUserBlockResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock - 85, // 77: hyapp.user.v1.CreateManagerUserBlockResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse - 1, // 78: hyapp.user.v1.ListManagerUserBlocksRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 86, // 79: hyapp.user.v1.ListManagerUserBlocksResponse.blocks:type_name -> hyapp.user.v1.ManagerUserBlock - 1, // 80: hyapp.user.v1.UnblockManagerUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 86, // 81: hyapp.user.v1.UnblockManagerUserResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock - 85, // 82: hyapp.user.v1.UnblockManagerUserResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse - 1, // 83: hyapp.user.v1.CompleteOnboardingRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 5, // 84: hyapp.user.v1.CompleteOnboardingResponse.user:type_name -> hyapp.user.v1.User - 13, // 85: hyapp.user.v1.CompleteOnboardingResponse.token:type_name -> hyapp.user.v1.AuthToken - 7, // 86: hyapp.user.v1.CompleteOnboardingResponse.invite:type_name -> hyapp.user.v1.InviteBinding - 1, // 87: hyapp.user.v1.BindPushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 88: hyapp.user.v1.DeletePushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 89: hyapp.user.v1.ListCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 99, // 90: hyapp.user.v1.ListCountriesResponse.countries:type_name -> hyapp.user.v1.Country - 1, // 91: hyapp.user.v1.UpdateCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 99, // 92: hyapp.user.v1.CountryResponse.country:type_name -> hyapp.user.v1.Country - 1, // 93: hyapp.user.v1.ListRegistrationCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 99, // 94: hyapp.user.v1.ListRegistrationCountriesResponse.countries:type_name -> hyapp.user.v1.Country - 1, // 95: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 107, // 96: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse.countries:type_name -> hyapp.user.v1.LoginRiskBlockedCountry - 1, // 97: hyapp.user.v1.ListRegionsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 100, // 98: hyapp.user.v1.ListRegionsResponse.regions:type_name -> hyapp.user.v1.Region - 1, // 99: hyapp.user.v1.GetRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 100: hyapp.user.v1.UpdateRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 101: hyapp.user.v1.ReplaceRegionCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 100, // 102: hyapp.user.v1.RegionResponse.region:type_name -> hyapp.user.v1.Region - 1, // 103: hyapp.user.v1.GetUserIdentityRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 116, // 104: hyapp.user.v1.GetUserIdentityResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 105: hyapp.user.v1.ResolveDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 116, // 106: hyapp.user.v1.ResolveDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 107: hyapp.user.v1.ChangeDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 116, // 108: hyapp.user.v1.ChangeDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 109: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 116, // 110: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 111: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 116, // 112: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 127, // 113: hyapp.user.v1.PrettyDisplayID.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 114: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 128, // 115: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 116: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 116, // 117: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 1, // 118: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 127, // 119: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 120: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 1, // 121: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 127, // 122: hyapp.user.v1.PrettyDisplayIDPoolResponse.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool - 1, // 123: hyapp.user.v1.GeneratePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 129, // 124: hyapp.user.v1.GeneratePrettyDisplayIDsResponse.batch:type_name -> hyapp.user.v1.PrettyDisplayIDGenerationBatch - 1, // 125: hyapp.user.v1.ListPrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 128, // 126: hyapp.user.v1.ListPrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 127: hyapp.user.v1.SetPrettyDisplayIDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 128, // 128: hyapp.user.v1.PrettyDisplayIDResponse.item:type_name -> hyapp.user.v1.PrettyDisplayID - 1, // 129: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 116, // 130: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity - 5, // 131: hyapp.user.v1.BatchGetUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.User - 14, // 132: hyapp.user.v1.UserService.GetUser:input_type -> hyapp.user.v1.GetUserRequest - 16, // 133: hyapp.user.v1.UserService.BusinessUserLookup:input_type -> hyapp.user.v1.BusinessUserLookupRequest - 20, // 134: hyapp.user.v1.UserService.GetMyProfileStats:input_type -> hyapp.user.v1.GetMyProfileStatsRequest - 75, // 135: hyapp.user.v1.UserService.BatchGetUsers:input_type -> hyapp.user.v1.BatchGetUsersRequest - 77, // 136: hyapp.user.v1.UserService.ListUserIDs:input_type -> hyapp.user.v1.ListUserIDsRequest - 9, // 137: hyapp.user.v1.UserService.GetUserMicLifetimeStats:input_type -> hyapp.user.v1.GetUserMicLifetimeStatsRequest - 79, // 138: hyapp.user.v1.UserService.UpdateUserProfile:input_type -> hyapp.user.v1.UpdateUserProfileRequest - 81, // 139: hyapp.user.v1.UserService.ChangeUserCountry:input_type -> hyapp.user.v1.ChangeUserCountryRequest - 82, // 140: hyapp.user.v1.UserService.AdminChangeUserCountry:input_type -> hyapp.user.v1.AdminChangeUserCountryRequest - 84, // 141: hyapp.user.v1.UserService.SetUserStatus:input_type -> hyapp.user.v1.SetUserStatusRequest - 87, // 142: hyapp.user.v1.UserService.CreateManagerUserBlock:input_type -> hyapp.user.v1.CreateManagerUserBlockRequest - 89, // 143: hyapp.user.v1.UserService.ListManagerUserBlocks:input_type -> hyapp.user.v1.ListManagerUserBlocksRequest - 91, // 144: hyapp.user.v1.UserService.UnblockManagerUser:input_type -> hyapp.user.v1.UnblockManagerUserRequest - 93, // 145: hyapp.user.v1.UserService.CompleteOnboarding:input_type -> hyapp.user.v1.CompleteOnboardingRequest - 22, // 146: hyapp.user.v1.UserSocialService.RecordProfileVisit:input_type -> hyapp.user.v1.RecordProfileVisitRequest - 25, // 147: hyapp.user.v1.UserSocialService.ListProfileVisitors:input_type -> hyapp.user.v1.ListProfileVisitorsRequest - 27, // 148: hyapp.user.v1.UserSocialService.FollowUser:input_type -> hyapp.user.v1.FollowUserRequest - 29, // 149: hyapp.user.v1.UserSocialService.UnfollowUser:input_type -> hyapp.user.v1.UnfollowUserRequest - 32, // 150: hyapp.user.v1.UserSocialService.ListFollowing:input_type -> hyapp.user.v1.ListFollowingRequest - 34, // 151: hyapp.user.v1.UserSocialService.ApplyFriend:input_type -> hyapp.user.v1.ApplyFriendRequest - 36, // 152: hyapp.user.v1.UserSocialService.AcceptFriendApplication:input_type -> hyapp.user.v1.AcceptFriendApplicationRequest - 38, // 153: hyapp.user.v1.UserSocialService.DeleteFriend:input_type -> hyapp.user.v1.DeleteFriendRequest - 41, // 154: hyapp.user.v1.UserSocialService.ListFriends:input_type -> hyapp.user.v1.ListFriendsRequest - 44, // 155: hyapp.user.v1.UserSocialService.ListFriendApplications:input_type -> hyapp.user.v1.ListFriendApplicationsRequest - 73, // 156: hyapp.user.v1.UserSocialService.SubmitReport:input_type -> hyapp.user.v1.SubmitReportRequest - 53, // 157: hyapp.user.v1.UserCPService.ListCPApplications:input_type -> hyapp.user.v1.ListCPApplicationsRequest - 55, // 158: hyapp.user.v1.UserCPService.AcceptCPApplication:input_type -> hyapp.user.v1.AcceptCPApplicationRequest - 57, // 159: hyapp.user.v1.UserCPService.RejectCPApplication:input_type -> hyapp.user.v1.RejectCPApplicationRequest - 59, // 160: hyapp.user.v1.UserCPService.ListCPRelationships:input_type -> hyapp.user.v1.ListCPRelationshipsRequest - 61, // 161: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:input_type -> hyapp.user.v1.ListCPIntimacyLeaderboardRequest - 63, // 162: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:input_type -> hyapp.user.v1.PrepareBreakCPRelationshipRequest - 65, // 163: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:input_type -> hyapp.user.v1.ConfirmBreakCPRelationshipRequest - 67, // 164: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:input_type -> hyapp.user.v1.CancelBreakCPRelationshipRequest - 70, // 165: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:input_type -> hyapp.user.v1.ConsumeRoomGiftCPEventRequest - 11, // 166: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 167: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 168: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 169: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest - 11, // 170: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:input_type -> hyapp.user.v1.CronBatchRequest - 95, // 171: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest - 97, // 172: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest - 3, // 173: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest - 101, // 174: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest - 103, // 175: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest - 105, // 176: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest - 108, // 177: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest - 110, // 178: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest - 112, // 179: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest - 113, // 180: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest - 114, // 181: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest - 117, // 182: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest - 119, // 183: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest - 121, // 184: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest - 123, // 185: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest - 130, // 186: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest - 132, // 187: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest - 125, // 188: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest - 134, // 189: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest - 136, // 190: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest - 137, // 191: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest - 139, // 192: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest - 141, // 193: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest - 143, // 194: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest - 145, // 195: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest - 15, // 196: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse - 18, // 197: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse - 21, // 198: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse - 76, // 199: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse - 78, // 200: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse - 10, // 201: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse - 80, // 202: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse - 83, // 203: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse - 83, // 204: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse - 85, // 205: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse - 88, // 206: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse - 90, // 207: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse - 92, // 208: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse - 94, // 209: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse - 23, // 210: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse - 26, // 211: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse - 28, // 212: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse - 30, // 213: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse - 33, // 214: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse - 35, // 215: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse - 37, // 216: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse - 39, // 217: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse - 42, // 218: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse - 45, // 219: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse - 74, // 220: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse - 54, // 221: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse - 56, // 222: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse - 58, // 223: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse - 60, // 224: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse - 62, // 225: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:output_type -> hyapp.user.v1.ListCPIntimacyLeaderboardResponse - 64, // 226: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse - 66, // 227: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse - 68, // 228: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse - 71, // 229: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse - 12, // 230: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 231: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 232: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 233: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse - 12, // 234: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:output_type -> hyapp.user.v1.CronBatchResponse - 96, // 235: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse - 98, // 236: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse - 4, // 237: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse - 102, // 238: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse - 104, // 239: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse - 106, // 240: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse - 109, // 241: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse - 111, // 242: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse - 115, // 243: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse - 115, // 244: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse - 115, // 245: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse - 118, // 246: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse - 120, // 247: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse - 122, // 248: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse - 124, // 249: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse - 131, // 250: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse - 133, // 251: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse - 126, // 252: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse - 135, // 253: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse - 138, // 254: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse - 138, // 255: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse - 140, // 256: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse - 142, // 257: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse - 144, // 258: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse - 146, // 259: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse - 196, // [196:260] is the sub-list for method output_type - 132, // [132:196] is the sub-list for method input_type - 132, // [132:132] is the sub-list for extension type_name - 132, // [132:132] is the sub-list for extension extendee - 0, // [0:132] is the sub-list for field type_name + 1, // 4: hyapp.user.v1.GetInviteAttributionRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 8, // 5: hyapp.user.v1.GetInviteAttributionResponse.attribution:type_name -> hyapp.user.v1.InviteAttribution + 1, // 6: hyapp.user.v1.GetUserMicLifetimeStatsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 11, // 7: hyapp.user.v1.GetUserMicLifetimeStatsResponse.stats:type_name -> hyapp.user.v1.UserMicLifetimeStats + 1, // 8: hyapp.user.v1.CronBatchRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 9: hyapp.user.v1.GetUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 10: hyapp.user.v1.GetUserResponse.user:type_name -> hyapp.user.v1.User + 1, // 11: hyapp.user.v1.BusinessUserLookupRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 0, // 12: hyapp.user.v1.BusinessUserLookupItem.status:type_name -> hyapp.user.v1.UserStatus + 20, // 13: hyapp.user.v1.BusinessUserLookupResponse.users:type_name -> hyapp.user.v1.BusinessUserLookupItem + 1, // 14: hyapp.user.v1.GetMyProfileStatsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 22, // 15: hyapp.user.v1.GetMyProfileStatsResponse.stats:type_name -> hyapp.user.v1.UserProfileStats + 1, // 16: hyapp.user.v1.RecordProfileVisitRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 22, // 17: hyapp.user.v1.RecordProfileVisitResponse.target_stats:type_name -> hyapp.user.v1.UserProfileStats + 1, // 18: hyapp.user.v1.ListProfileVisitorsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 19: hyapp.user.v1.ListProfileVisitorsResponse.records:type_name -> hyapp.user.v1.ProfileVisitRecord + 1, // 20: hyapp.user.v1.FollowUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 22, // 21: hyapp.user.v1.FollowUserResponse.follower_stats:type_name -> hyapp.user.v1.UserProfileStats + 1, // 22: hyapp.user.v1.UnfollowUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 22, // 23: hyapp.user.v1.UnfollowUserResponse.follower_stats:type_name -> hyapp.user.v1.UserProfileStats + 1, // 24: hyapp.user.v1.ListFollowingRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 34, // 25: hyapp.user.v1.ListFollowingResponse.records:type_name -> hyapp.user.v1.FollowRecord + 1, // 26: hyapp.user.v1.ApplyFriendRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 46, // 27: hyapp.user.v1.ApplyFriendResponse.application:type_name -> hyapp.user.v1.FriendApplication + 1, // 28: hyapp.user.v1.AcceptFriendApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 43, // 29: hyapp.user.v1.AcceptFriendApplicationResponse.friend:type_name -> hyapp.user.v1.FriendRecord + 1, // 30: hyapp.user.v1.DeleteFriendRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 31: hyapp.user.v1.ListFriendsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 43, // 32: hyapp.user.v1.ListFriendsResponse.records:type_name -> hyapp.user.v1.FriendRecord + 1, // 33: hyapp.user.v1.ListFriendApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 46, // 34: hyapp.user.v1.ListFriendApplicationsResponse.applications:type_name -> hyapp.user.v1.FriendApplication + 49, // 35: hyapp.user.v1.CPApplication.requester:type_name -> hyapp.user.v1.CPUserProfile + 49, // 36: hyapp.user.v1.CPApplication.target:type_name -> hyapp.user.v1.CPUserProfile + 50, // 37: hyapp.user.v1.CPApplication.gift:type_name -> hyapp.user.v1.CPGiftSnapshot + 49, // 38: hyapp.user.v1.CPRelationship.me:type_name -> hyapp.user.v1.CPUserProfile + 49, // 39: hyapp.user.v1.CPRelationship.partner:type_name -> hyapp.user.v1.CPUserProfile + 53, // 40: hyapp.user.v1.CPIntimacyLeaderboardUser.avatar_frame:type_name -> hyapp.user.v1.CPAvatarFrameSnapshot + 54, // 41: hyapp.user.v1.CPIntimacyLeaderboardItem.user_a:type_name -> hyapp.user.v1.CPIntimacyLeaderboardUser + 54, // 42: hyapp.user.v1.CPIntimacyLeaderboardItem.user_b:type_name -> hyapp.user.v1.CPIntimacyLeaderboardUser + 1, // 43: hyapp.user.v1.ListCPApplicationsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 51, // 44: hyapp.user.v1.ListCPApplicationsResponse.applications:type_name -> hyapp.user.v1.CPApplication + 1, // 45: hyapp.user.v1.AcceptCPApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 51, // 46: hyapp.user.v1.AcceptCPApplicationResponse.application:type_name -> hyapp.user.v1.CPApplication + 52, // 47: hyapp.user.v1.AcceptCPApplicationResponse.relationship:type_name -> hyapp.user.v1.CPRelationship + 1, // 48: hyapp.user.v1.RejectCPApplicationRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 51, // 49: hyapp.user.v1.RejectCPApplicationResponse.application:type_name -> hyapp.user.v1.CPApplication + 1, // 50: hyapp.user.v1.ListCPRelationshipsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 52, // 51: hyapp.user.v1.ListCPRelationshipsResponse.relationships:type_name -> hyapp.user.v1.CPRelationship + 1, // 52: hyapp.user.v1.ListCPIntimacyLeaderboardRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 55, // 53: hyapp.user.v1.ListCPIntimacyLeaderboardResponse.items:type_name -> hyapp.user.v1.CPIntimacyLeaderboardItem + 1, // 54: hyapp.user.v1.PrepareBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 52, // 55: hyapp.user.v1.PrepareBreakCPRelationshipResponse.relationship:type_name -> hyapp.user.v1.CPRelationship + 1, // 56: hyapp.user.v1.ConfirmBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 52, // 57: hyapp.user.v1.ConfirmBreakCPRelationshipResponse.relationship:type_name -> hyapp.user.v1.CPRelationship + 1, // 58: hyapp.user.v1.CancelBreakCPRelationshipRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 59: hyapp.user.v1.RoomGiftCPEvent.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 60: hyapp.user.v1.ConsumeRoomGiftCPEventRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 72, // 61: hyapp.user.v1.ConsumeRoomGiftCPEventRequest.event:type_name -> hyapp.user.v1.RoomGiftCPEvent + 51, // 62: hyapp.user.v1.ConsumeRoomGiftCPEventResponse.application:type_name -> hyapp.user.v1.CPApplication + 52, // 63: hyapp.user.v1.ConsumeRoomGiftCPEventResponse.relationship:type_name -> hyapp.user.v1.CPRelationship + 1, // 64: hyapp.user.v1.SubmitReportRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 75, // 65: hyapp.user.v1.SubmitReportResponse.report:type_name -> hyapp.user.v1.UserReport + 1, // 66: hyapp.user.v1.BatchGetUsersRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 150, // 67: hyapp.user.v1.BatchGetUsersResponse.users:type_name -> hyapp.user.v1.BatchGetUsersResponse.UsersEntry + 1, // 68: hyapp.user.v1.ListUserIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 69: hyapp.user.v1.UpdateUserProfileRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 70: hyapp.user.v1.UpdateUserProfileResponse.user:type_name -> hyapp.user.v1.User + 1, // 71: hyapp.user.v1.ChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 72: hyapp.user.v1.AdminChangeUserCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 73: hyapp.user.v1.ChangeUserCountryResponse.user:type_name -> hyapp.user.v1.User + 1, // 74: hyapp.user.v1.SetUserStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 0, // 75: hyapp.user.v1.SetUserStatusRequest.status:type_name -> hyapp.user.v1.UserStatus + 5, // 76: hyapp.user.v1.SetUserStatusResponse.user:type_name -> hyapp.user.v1.User + 1, // 77: hyapp.user.v1.CreateManagerUserBlockRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 89, // 78: hyapp.user.v1.CreateManagerUserBlockResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock + 88, // 79: hyapp.user.v1.CreateManagerUserBlockResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse + 1, // 80: hyapp.user.v1.ListManagerUserBlocksRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 89, // 81: hyapp.user.v1.ListManagerUserBlocksResponse.blocks:type_name -> hyapp.user.v1.ManagerUserBlock + 1, // 82: hyapp.user.v1.UnblockManagerUserRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 89, // 83: hyapp.user.v1.UnblockManagerUserResponse.block:type_name -> hyapp.user.v1.ManagerUserBlock + 88, // 84: hyapp.user.v1.UnblockManagerUserResponse.status:type_name -> hyapp.user.v1.SetUserStatusResponse + 1, // 85: hyapp.user.v1.CompleteOnboardingRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 5, // 86: hyapp.user.v1.CompleteOnboardingResponse.user:type_name -> hyapp.user.v1.User + 16, // 87: hyapp.user.v1.CompleteOnboardingResponse.token:type_name -> hyapp.user.v1.AuthToken + 7, // 88: hyapp.user.v1.CompleteOnboardingResponse.invite:type_name -> hyapp.user.v1.InviteBinding + 1, // 89: hyapp.user.v1.BindPushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 90: hyapp.user.v1.DeletePushTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 91: hyapp.user.v1.ListCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 102, // 92: hyapp.user.v1.ListCountriesResponse.countries:type_name -> hyapp.user.v1.Country + 1, // 93: hyapp.user.v1.UpdateCountryRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 102, // 94: hyapp.user.v1.CountryResponse.country:type_name -> hyapp.user.v1.Country + 1, // 95: hyapp.user.v1.ListRegistrationCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 102, // 96: hyapp.user.v1.ListRegistrationCountriesResponse.countries:type_name -> hyapp.user.v1.Country + 1, // 97: hyapp.user.v1.ListLoginRiskBlockedCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 110, // 98: hyapp.user.v1.ListLoginRiskBlockedCountriesResponse.countries:type_name -> hyapp.user.v1.LoginRiskBlockedCountry + 1, // 99: hyapp.user.v1.ListRegionsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 100: hyapp.user.v1.ListRegionsResponse.regions:type_name -> hyapp.user.v1.Region + 1, // 101: hyapp.user.v1.GetRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 102: hyapp.user.v1.UpdateRegionRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 103: hyapp.user.v1.ReplaceRegionCountriesRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 103, // 104: hyapp.user.v1.RegionResponse.region:type_name -> hyapp.user.v1.Region + 1, // 105: hyapp.user.v1.GetUserIdentityRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 119, // 106: hyapp.user.v1.GetUserIdentityResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 107: hyapp.user.v1.ResolveDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 119, // 108: hyapp.user.v1.ResolveDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 109: hyapp.user.v1.ChangeDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 119, // 110: hyapp.user.v1.ChangeDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 111: hyapp.user.v1.ApplyPrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 119, // 112: hyapp.user.v1.ApplyPrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 113: hyapp.user.v1.ExpirePrettyDisplayUserIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 119, // 114: hyapp.user.v1.ExpirePrettyDisplayUserIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 130, // 115: hyapp.user.v1.PrettyDisplayID.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 116: hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 131, // 117: hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 118: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 119, // 119: hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 1, // 120: hyapp.user.v1.ListPrettyDisplayIDPoolsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 130, // 121: hyapp.user.v1.ListPrettyDisplayIDPoolsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 122: hyapp.user.v1.CreatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 1, // 123: hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 130, // 124: hyapp.user.v1.PrettyDisplayIDPoolResponse.pool:type_name -> hyapp.user.v1.PrettyDisplayIDPool + 1, // 125: hyapp.user.v1.GeneratePrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 132, // 126: hyapp.user.v1.GeneratePrettyDisplayIDsResponse.batch:type_name -> hyapp.user.v1.PrettyDisplayIDGenerationBatch + 1, // 127: hyapp.user.v1.ListPrettyDisplayIDsRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 131, // 128: hyapp.user.v1.ListPrettyDisplayIDsResponse.items:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 129: hyapp.user.v1.SetPrettyDisplayIDStatusRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 131, // 130: hyapp.user.v1.PrettyDisplayIDResponse.item:type_name -> hyapp.user.v1.PrettyDisplayID + 1, // 131: hyapp.user.v1.AdminGrantPrettyDisplayIDRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 119, // 132: hyapp.user.v1.AdminGrantPrettyDisplayIDResponse.identity:type_name -> hyapp.user.v1.UserIdentity + 5, // 133: hyapp.user.v1.BatchGetUsersResponse.UsersEntry.value:type_name -> hyapp.user.v1.User + 17, // 134: hyapp.user.v1.UserService.GetUser:input_type -> hyapp.user.v1.GetUserRequest + 9, // 135: hyapp.user.v1.UserService.GetInviteAttribution:input_type -> hyapp.user.v1.GetInviteAttributionRequest + 19, // 136: hyapp.user.v1.UserService.BusinessUserLookup:input_type -> hyapp.user.v1.BusinessUserLookupRequest + 23, // 137: hyapp.user.v1.UserService.GetMyProfileStats:input_type -> hyapp.user.v1.GetMyProfileStatsRequest + 78, // 138: hyapp.user.v1.UserService.BatchGetUsers:input_type -> hyapp.user.v1.BatchGetUsersRequest + 80, // 139: hyapp.user.v1.UserService.ListUserIDs:input_type -> hyapp.user.v1.ListUserIDsRequest + 12, // 140: hyapp.user.v1.UserService.GetUserMicLifetimeStats:input_type -> hyapp.user.v1.GetUserMicLifetimeStatsRequest + 82, // 141: hyapp.user.v1.UserService.UpdateUserProfile:input_type -> hyapp.user.v1.UpdateUserProfileRequest + 84, // 142: hyapp.user.v1.UserService.ChangeUserCountry:input_type -> hyapp.user.v1.ChangeUserCountryRequest + 85, // 143: hyapp.user.v1.UserService.AdminChangeUserCountry:input_type -> hyapp.user.v1.AdminChangeUserCountryRequest + 87, // 144: hyapp.user.v1.UserService.SetUserStatus:input_type -> hyapp.user.v1.SetUserStatusRequest + 90, // 145: hyapp.user.v1.UserService.CreateManagerUserBlock:input_type -> hyapp.user.v1.CreateManagerUserBlockRequest + 92, // 146: hyapp.user.v1.UserService.ListManagerUserBlocks:input_type -> hyapp.user.v1.ListManagerUserBlocksRequest + 94, // 147: hyapp.user.v1.UserService.UnblockManagerUser:input_type -> hyapp.user.v1.UnblockManagerUserRequest + 96, // 148: hyapp.user.v1.UserService.CompleteOnboarding:input_type -> hyapp.user.v1.CompleteOnboardingRequest + 25, // 149: hyapp.user.v1.UserSocialService.RecordProfileVisit:input_type -> hyapp.user.v1.RecordProfileVisitRequest + 28, // 150: hyapp.user.v1.UserSocialService.ListProfileVisitors:input_type -> hyapp.user.v1.ListProfileVisitorsRequest + 30, // 151: hyapp.user.v1.UserSocialService.FollowUser:input_type -> hyapp.user.v1.FollowUserRequest + 32, // 152: hyapp.user.v1.UserSocialService.UnfollowUser:input_type -> hyapp.user.v1.UnfollowUserRequest + 35, // 153: hyapp.user.v1.UserSocialService.ListFollowing:input_type -> hyapp.user.v1.ListFollowingRequest + 37, // 154: hyapp.user.v1.UserSocialService.ApplyFriend:input_type -> hyapp.user.v1.ApplyFriendRequest + 39, // 155: hyapp.user.v1.UserSocialService.AcceptFriendApplication:input_type -> hyapp.user.v1.AcceptFriendApplicationRequest + 41, // 156: hyapp.user.v1.UserSocialService.DeleteFriend:input_type -> hyapp.user.v1.DeleteFriendRequest + 44, // 157: hyapp.user.v1.UserSocialService.ListFriends:input_type -> hyapp.user.v1.ListFriendsRequest + 47, // 158: hyapp.user.v1.UserSocialService.ListFriendApplications:input_type -> hyapp.user.v1.ListFriendApplicationsRequest + 76, // 159: hyapp.user.v1.UserSocialService.SubmitReport:input_type -> hyapp.user.v1.SubmitReportRequest + 56, // 160: hyapp.user.v1.UserCPService.ListCPApplications:input_type -> hyapp.user.v1.ListCPApplicationsRequest + 58, // 161: hyapp.user.v1.UserCPService.AcceptCPApplication:input_type -> hyapp.user.v1.AcceptCPApplicationRequest + 60, // 162: hyapp.user.v1.UserCPService.RejectCPApplication:input_type -> hyapp.user.v1.RejectCPApplicationRequest + 62, // 163: hyapp.user.v1.UserCPService.ListCPRelationships:input_type -> hyapp.user.v1.ListCPRelationshipsRequest + 64, // 164: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:input_type -> hyapp.user.v1.ListCPIntimacyLeaderboardRequest + 66, // 165: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:input_type -> hyapp.user.v1.PrepareBreakCPRelationshipRequest + 68, // 166: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:input_type -> hyapp.user.v1.ConfirmBreakCPRelationshipRequest + 70, // 167: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:input_type -> hyapp.user.v1.CancelBreakCPRelationshipRequest + 73, // 168: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:input_type -> hyapp.user.v1.ConsumeRoomGiftCPEventRequest + 14, // 169: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 170: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 171: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 172: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:input_type -> hyapp.user.v1.CronBatchRequest + 14, // 173: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:input_type -> hyapp.user.v1.CronBatchRequest + 98, // 174: hyapp.user.v1.UserDeviceService.BindPushToken:input_type -> hyapp.user.v1.BindPushTokenRequest + 100, // 175: hyapp.user.v1.UserDeviceService.DeletePushToken:input_type -> hyapp.user.v1.DeletePushTokenRequest + 3, // 176: hyapp.user.v1.AppRegistryService.ResolveApp:input_type -> hyapp.user.v1.ResolveAppRequest + 104, // 177: hyapp.user.v1.CountryAdminService.ListCountries:input_type -> hyapp.user.v1.ListCountriesRequest + 106, // 178: hyapp.user.v1.CountryAdminService.UpdateCountry:input_type -> hyapp.user.v1.UpdateCountryRequest + 108, // 179: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:input_type -> hyapp.user.v1.ListRegistrationCountriesRequest + 111, // 180: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:input_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesRequest + 113, // 181: hyapp.user.v1.RegionAdminService.ListRegions:input_type -> hyapp.user.v1.ListRegionsRequest + 115, // 182: hyapp.user.v1.RegionAdminService.GetRegion:input_type -> hyapp.user.v1.GetRegionRequest + 116, // 183: hyapp.user.v1.RegionAdminService.UpdateRegion:input_type -> hyapp.user.v1.UpdateRegionRequest + 117, // 184: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:input_type -> hyapp.user.v1.ReplaceRegionCountriesRequest + 120, // 185: hyapp.user.v1.UserIdentityService.GetUserIdentity:input_type -> hyapp.user.v1.GetUserIdentityRequest + 122, // 186: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:input_type -> hyapp.user.v1.ResolveDisplayUserIDRequest + 124, // 187: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:input_type -> hyapp.user.v1.ChangeDisplayUserIDRequest + 126, // 188: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:input_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDRequest + 133, // 189: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:input_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsRequest + 135, // 190: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:input_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolRequest + 128, // 191: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:input_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDRequest + 137, // 192: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:input_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsRequest + 139, // 193: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:input_type -> hyapp.user.v1.CreatePrettyDisplayIDPoolRequest + 140, // 194: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:input_type -> hyapp.user.v1.UpdatePrettyDisplayIDPoolRequest + 142, // 195: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:input_type -> hyapp.user.v1.GeneratePrettyDisplayIDsRequest + 144, // 196: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:input_type -> hyapp.user.v1.ListPrettyDisplayIDsRequest + 146, // 197: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:input_type -> hyapp.user.v1.SetPrettyDisplayIDStatusRequest + 148, // 198: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:input_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDRequest + 18, // 199: hyapp.user.v1.UserService.GetUser:output_type -> hyapp.user.v1.GetUserResponse + 10, // 200: hyapp.user.v1.UserService.GetInviteAttribution:output_type -> hyapp.user.v1.GetInviteAttributionResponse + 21, // 201: hyapp.user.v1.UserService.BusinessUserLookup:output_type -> hyapp.user.v1.BusinessUserLookupResponse + 24, // 202: hyapp.user.v1.UserService.GetMyProfileStats:output_type -> hyapp.user.v1.GetMyProfileStatsResponse + 79, // 203: hyapp.user.v1.UserService.BatchGetUsers:output_type -> hyapp.user.v1.BatchGetUsersResponse + 81, // 204: hyapp.user.v1.UserService.ListUserIDs:output_type -> hyapp.user.v1.ListUserIDsResponse + 13, // 205: hyapp.user.v1.UserService.GetUserMicLifetimeStats:output_type -> hyapp.user.v1.GetUserMicLifetimeStatsResponse + 83, // 206: hyapp.user.v1.UserService.UpdateUserProfile:output_type -> hyapp.user.v1.UpdateUserProfileResponse + 86, // 207: hyapp.user.v1.UserService.ChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse + 86, // 208: hyapp.user.v1.UserService.AdminChangeUserCountry:output_type -> hyapp.user.v1.ChangeUserCountryResponse + 88, // 209: hyapp.user.v1.UserService.SetUserStatus:output_type -> hyapp.user.v1.SetUserStatusResponse + 91, // 210: hyapp.user.v1.UserService.CreateManagerUserBlock:output_type -> hyapp.user.v1.CreateManagerUserBlockResponse + 93, // 211: hyapp.user.v1.UserService.ListManagerUserBlocks:output_type -> hyapp.user.v1.ListManagerUserBlocksResponse + 95, // 212: hyapp.user.v1.UserService.UnblockManagerUser:output_type -> hyapp.user.v1.UnblockManagerUserResponse + 97, // 213: hyapp.user.v1.UserService.CompleteOnboarding:output_type -> hyapp.user.v1.CompleteOnboardingResponse + 26, // 214: hyapp.user.v1.UserSocialService.RecordProfileVisit:output_type -> hyapp.user.v1.RecordProfileVisitResponse + 29, // 215: hyapp.user.v1.UserSocialService.ListProfileVisitors:output_type -> hyapp.user.v1.ListProfileVisitorsResponse + 31, // 216: hyapp.user.v1.UserSocialService.FollowUser:output_type -> hyapp.user.v1.FollowUserResponse + 33, // 217: hyapp.user.v1.UserSocialService.UnfollowUser:output_type -> hyapp.user.v1.UnfollowUserResponse + 36, // 218: hyapp.user.v1.UserSocialService.ListFollowing:output_type -> hyapp.user.v1.ListFollowingResponse + 38, // 219: hyapp.user.v1.UserSocialService.ApplyFriend:output_type -> hyapp.user.v1.ApplyFriendResponse + 40, // 220: hyapp.user.v1.UserSocialService.AcceptFriendApplication:output_type -> hyapp.user.v1.AcceptFriendApplicationResponse + 42, // 221: hyapp.user.v1.UserSocialService.DeleteFriend:output_type -> hyapp.user.v1.DeleteFriendResponse + 45, // 222: hyapp.user.v1.UserSocialService.ListFriends:output_type -> hyapp.user.v1.ListFriendsResponse + 48, // 223: hyapp.user.v1.UserSocialService.ListFriendApplications:output_type -> hyapp.user.v1.ListFriendApplicationsResponse + 77, // 224: hyapp.user.v1.UserSocialService.SubmitReport:output_type -> hyapp.user.v1.SubmitReportResponse + 57, // 225: hyapp.user.v1.UserCPService.ListCPApplications:output_type -> hyapp.user.v1.ListCPApplicationsResponse + 59, // 226: hyapp.user.v1.UserCPService.AcceptCPApplication:output_type -> hyapp.user.v1.AcceptCPApplicationResponse + 61, // 227: hyapp.user.v1.UserCPService.RejectCPApplication:output_type -> hyapp.user.v1.RejectCPApplicationResponse + 63, // 228: hyapp.user.v1.UserCPService.ListCPRelationships:output_type -> hyapp.user.v1.ListCPRelationshipsResponse + 65, // 229: hyapp.user.v1.UserCPService.ListCPIntimacyLeaderboard:output_type -> hyapp.user.v1.ListCPIntimacyLeaderboardResponse + 67, // 230: hyapp.user.v1.UserCPService.PrepareBreakCPRelationship:output_type -> hyapp.user.v1.PrepareBreakCPRelationshipResponse + 69, // 231: hyapp.user.v1.UserCPService.ConfirmBreakCPRelationship:output_type -> hyapp.user.v1.ConfirmBreakCPRelationshipResponse + 71, // 232: hyapp.user.v1.UserCPService.CancelBreakCPRelationship:output_type -> hyapp.user.v1.CancelBreakCPRelationshipResponse + 74, // 233: hyapp.user.v1.UserCPInternalService.ConsumeRoomGiftCPEvent:output_type -> hyapp.user.v1.ConsumeRoomGiftCPEventResponse + 15, // 234: hyapp.user.v1.UserCronService.ProcessLoginIPRiskBatch:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 235: hyapp.user.v1.UserCronService.ProcessRegionRebuildBatch:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 236: hyapp.user.v1.UserCronService.CompensateMicOpenSessions:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 237: hyapp.user.v1.UserCronService.ExpireManagerUserBlocks:output_type -> hyapp.user.v1.CronBatchResponse + 15, // 238: hyapp.user.v1.UserCronService.RefreshCPIntimacyLeaderboard:output_type -> hyapp.user.v1.CronBatchResponse + 99, // 239: hyapp.user.v1.UserDeviceService.BindPushToken:output_type -> hyapp.user.v1.BindPushTokenResponse + 101, // 240: hyapp.user.v1.UserDeviceService.DeletePushToken:output_type -> hyapp.user.v1.DeletePushTokenResponse + 4, // 241: hyapp.user.v1.AppRegistryService.ResolveApp:output_type -> hyapp.user.v1.ResolveAppResponse + 105, // 242: hyapp.user.v1.CountryAdminService.ListCountries:output_type -> hyapp.user.v1.ListCountriesResponse + 107, // 243: hyapp.user.v1.CountryAdminService.UpdateCountry:output_type -> hyapp.user.v1.CountryResponse + 109, // 244: hyapp.user.v1.CountryQueryService.ListRegistrationCountries:output_type -> hyapp.user.v1.ListRegistrationCountriesResponse + 112, // 245: hyapp.user.v1.CountryQueryService.ListLoginRiskBlockedCountries:output_type -> hyapp.user.v1.ListLoginRiskBlockedCountriesResponse + 114, // 246: hyapp.user.v1.RegionAdminService.ListRegions:output_type -> hyapp.user.v1.ListRegionsResponse + 118, // 247: hyapp.user.v1.RegionAdminService.GetRegion:output_type -> hyapp.user.v1.RegionResponse + 118, // 248: hyapp.user.v1.RegionAdminService.UpdateRegion:output_type -> hyapp.user.v1.RegionResponse + 118, // 249: hyapp.user.v1.RegionAdminService.ReplaceRegionCountries:output_type -> hyapp.user.v1.RegionResponse + 121, // 250: hyapp.user.v1.UserIdentityService.GetUserIdentity:output_type -> hyapp.user.v1.GetUserIdentityResponse + 123, // 251: hyapp.user.v1.UserIdentityService.ResolveDisplayUserID:output_type -> hyapp.user.v1.ResolveDisplayUserIDResponse + 125, // 252: hyapp.user.v1.UserIdentityService.ChangeDisplayUserID:output_type -> hyapp.user.v1.ChangeDisplayUserIDResponse + 127, // 253: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayUserID:output_type -> hyapp.user.v1.ApplyPrettyDisplayUserIDResponse + 134, // 254: hyapp.user.v1.UserIdentityService.ListAvailablePrettyDisplayIDs:output_type -> hyapp.user.v1.ListAvailablePrettyDisplayIDsResponse + 136, // 255: hyapp.user.v1.UserIdentityService.ApplyPrettyDisplayIDFromPool:output_type -> hyapp.user.v1.ApplyPrettyDisplayIDFromPoolResponse + 129, // 256: hyapp.user.v1.UserIdentityService.ExpirePrettyDisplayUserID:output_type -> hyapp.user.v1.ExpirePrettyDisplayUserIDResponse + 138, // 257: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDPools:output_type -> hyapp.user.v1.ListPrettyDisplayIDPoolsResponse + 141, // 258: hyapp.user.v1.UserPrettyDisplayIDAdminService.CreatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse + 141, // 259: hyapp.user.v1.UserPrettyDisplayIDAdminService.UpdatePrettyDisplayIDPool:output_type -> hyapp.user.v1.PrettyDisplayIDPoolResponse + 143, // 260: hyapp.user.v1.UserPrettyDisplayIDAdminService.GeneratePrettyDisplayIDs:output_type -> hyapp.user.v1.GeneratePrettyDisplayIDsResponse + 145, // 261: hyapp.user.v1.UserPrettyDisplayIDAdminService.ListPrettyDisplayIDs:output_type -> hyapp.user.v1.ListPrettyDisplayIDsResponse + 147, // 262: hyapp.user.v1.UserPrettyDisplayIDAdminService.SetPrettyDisplayIDStatus:output_type -> hyapp.user.v1.PrettyDisplayIDResponse + 149, // 263: hyapp.user.v1.UserPrettyDisplayIDAdminService.AdminGrantPrettyDisplayID:output_type -> hyapp.user.v1.AdminGrantPrettyDisplayIDResponse + 199, // [199:264] is the sub-list for method output_type + 134, // [134:199] is the sub-list for method input_type + 134, // [134:134] is the sub-list for extension type_name + 134, // [134:134] is the sub-list for extension extendee + 0, // [0:134] is the sub-list for field type_name } func init() { file_proto_user_v1_user_proto_init() } @@ -12275,15 +12514,15 @@ func file_proto_user_v1_user_proto_init() { if File_proto_user_v1_user_proto != nil { return } - file_proto_user_v1_user_proto_msgTypes[78].OneofWrappers = []any{} - file_proto_user_v1_user_proto_msgTypes[100].OneofWrappers = []any{} + file_proto_user_v1_user_proto_msgTypes[81].OneofWrappers = []any{} + file_proto_user_v1_user_proto_msgTypes[103].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_user_v1_user_proto_rawDesc), len(file_proto_user_v1_user_proto_rawDesc)), NumEnums: 1, - NumMessages: 147, + NumMessages: 150, NumExtensions: 0, NumServices: 12, }, diff --git a/api/proto/user/v1/user.proto b/api/proto/user/v1/user.proto index b9e4290b..7aa8116b 100644 --- a/api/proto/user/v1/user.proto +++ b/api/proto/user/v1/user.proto @@ -103,6 +103,29 @@ message InviteBinding { int64 inviter_user_id = 3; } +// InviteAttribution 是被邀请用户到邀请人的只读归因快照,供活动服务按充值事实累计邀请奖励。 +message InviteAttribution { + bool found = 1; + string app_code = 2; + int64 invited_user_id = 3; + int64 inviter_user_id = 4; + string invite_code = 5; + int64 bound_at_ms = 6; + string validity_status = 7; + int64 accumulated_recharge_coin_amount = 8; + int64 required_recharge_coin_amount = 9; + int64 valid_at_ms = 10; +} + +message GetInviteAttributionRequest { + RequestMeta meta = 1; + int64 invited_user_id = 2; +} + +message GetInviteAttributionResponse { + InviteAttribution attribution = 1; +} + // UserMicLifetimeStats 是用户维度的麦上累计基础指标,不区分主播/公会身份。 message UserMicLifetimeStats { int64 user_id = 1; @@ -1201,6 +1224,7 @@ message AdminGrantPrettyDisplayIDResponse { // UserService 提供用户主状态、基础资料和注册资料读写接口。 service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse); + rpc GetInviteAttribution(GetInviteAttributionRequest) returns (GetInviteAttributionResponse); rpc BusinessUserLookup(BusinessUserLookupRequest) returns (BusinessUserLookupResponse); rpc GetMyProfileStats(GetMyProfileStatsRequest) returns (GetMyProfileStatsResponse); rpc BatchGetUsers(BatchGetUsersRequest) returns (BatchGetUsersResponse); diff --git a/api/proto/user/v1/user_grpc.pb.go b/api/proto/user/v1/user_grpc.pb.go index fe36fda0..d2595a1c 100644 --- a/api/proto/user/v1/user_grpc.pb.go +++ b/api/proto/user/v1/user_grpc.pb.go @@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( UserService_GetUser_FullMethodName = "/hyapp.user.v1.UserService/GetUser" + UserService_GetInviteAttribution_FullMethodName = "/hyapp.user.v1.UserService/GetInviteAttribution" UserService_BusinessUserLookup_FullMethodName = "/hyapp.user.v1.UserService/BusinessUserLookup" UserService_GetMyProfileStats_FullMethodName = "/hyapp.user.v1.UserService/GetMyProfileStats" UserService_BatchGetUsers_FullMethodName = "/hyapp.user.v1.UserService/BatchGetUsers" @@ -42,6 +43,7 @@ const ( // UserService 提供用户主状态、基础资料和注册资料读写接口。 type UserServiceClient interface { GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) + GetInviteAttribution(ctx context.Context, in *GetInviteAttributionRequest, opts ...grpc.CallOption) (*GetInviteAttributionResponse, error) BusinessUserLookup(ctx context.Context, in *BusinessUserLookupRequest, opts ...grpc.CallOption) (*BusinessUserLookupResponse, error) GetMyProfileStats(ctx context.Context, in *GetMyProfileStatsRequest, opts ...grpc.CallOption) (*GetMyProfileStatsResponse, error) BatchGetUsers(ctx context.Context, in *BatchGetUsersRequest, opts ...grpc.CallOption) (*BatchGetUsersResponse, error) @@ -75,6 +77,16 @@ func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opt return out, nil } +func (c *userServiceClient) GetInviteAttribution(ctx context.Context, in *GetInviteAttributionRequest, opts ...grpc.CallOption) (*GetInviteAttributionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetInviteAttributionResponse) + err := c.cc.Invoke(ctx, UserService_GetInviteAttribution_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *userServiceClient) BusinessUserLookup(ctx context.Context, in *BusinessUserLookupRequest, opts ...grpc.CallOption) (*BusinessUserLookupResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(BusinessUserLookupResponse) @@ -212,6 +224,7 @@ func (c *userServiceClient) CompleteOnboarding(ctx context.Context, in *Complete // UserService 提供用户主状态、基础资料和注册资料读写接口。 type UserServiceServer interface { GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) + GetInviteAttribution(context.Context, *GetInviteAttributionRequest) (*GetInviteAttributionResponse, error) BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error) GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) @@ -238,6 +251,9 @@ type UnimplementedUserServiceServer struct{} func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetUser not implemented") } +func (UnimplementedUserServiceServer) GetInviteAttribution(context.Context, *GetInviteAttributionRequest) (*GetInviteAttributionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetInviteAttribution not implemented") +} func (UnimplementedUserServiceServer) BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error) { return nil, status.Error(codes.Unimplemented, "method BusinessUserLookup not implemented") } @@ -316,6 +332,24 @@ func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _UserService_GetInviteAttribution_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetInviteAttributionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServiceServer).GetInviteAttribution(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UserService_GetInviteAttribution_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServiceServer).GetInviteAttribution(ctx, req.(*GetInviteAttributionRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _UserService_BusinessUserLookup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(BusinessUserLookupRequest) if err := dec(in); err != nil { @@ -561,6 +595,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetUser", Handler: _UserService_GetUser_Handler, }, + { + MethodName: "GetInviteAttribution", + Handler: _UserService_GetInviteAttribution_Handler, + }, { MethodName: "BusinessUserLookup", Handler: _UserService_BusinessUserLookup_Handler, diff --git a/api/proto/wallet/v1/wallet.pb.go b/api/proto/wallet/v1/wallet.pb.go index cf8ffcbe..ba73e0e4 100644 --- a/api/proto/wallet/v1/wallet.pb.go +++ b/api/proto/wallet/v1/wallet.pb.go @@ -6401,6 +6401,74 @@ func (x *SetGiftConfigStatusRequest) GetOperatorUserId() int64 { return 0 } +type DeleteGiftConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + OperatorUserId int64 `protobuf:"varint,4,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteGiftConfigRequest) Reset() { + *x = DeleteGiftConfigRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteGiftConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteGiftConfigRequest) ProtoMessage() {} + +func (x *DeleteGiftConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[64] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteGiftConfigRequest.ProtoReflect.Descriptor instead. +func (*DeleteGiftConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{64} +} + +func (x *DeleteGiftConfigRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *DeleteGiftConfigRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *DeleteGiftConfigRequest) GetGiftId() string { + if x != nil { + return x.GiftId + } + return "" +} + +func (x *DeleteGiftConfigRequest) GetOperatorUserId() int64 { + if x != nil { + return x.OperatorUserId + } + return 0 +} + type GiftConfigResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Gift *GiftConfig `protobuf:"bytes,1,opt,name=gift,proto3" json:"gift,omitempty"` @@ -6410,7 +6478,7 @@ type GiftConfigResponse struct { func (x *GiftConfigResponse) Reset() { *x = GiftConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[64] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6422,7 +6490,7 @@ func (x *GiftConfigResponse) String() string { func (*GiftConfigResponse) ProtoMessage() {} func (x *GiftConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[64] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6435,7 +6503,7 @@ func (x *GiftConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GiftConfigResponse.ProtoReflect.Descriptor instead. func (*GiftConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{64} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{65} } func (x *GiftConfigResponse) GetGift() *GiftConfig { @@ -6462,7 +6530,7 @@ type GrantResourceRequest struct { func (x *GrantResourceRequest) Reset() { *x = GrantResourceRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[65] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6474,7 +6542,7 @@ func (x *GrantResourceRequest) String() string { func (*GrantResourceRequest) ProtoMessage() {} func (x *GrantResourceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[65] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6487,7 +6555,7 @@ func (x *GrantResourceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantResourceRequest.ProtoReflect.Descriptor instead. func (*GrantResourceRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{65} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{66} } func (x *GrantResourceRequest) GetCommandId() string { @@ -6568,7 +6636,7 @@ type GrantResourceGroupRequest struct { func (x *GrantResourceGroupRequest) Reset() { *x = GrantResourceGroupRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[66] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6580,7 +6648,7 @@ func (x *GrantResourceGroupRequest) String() string { func (*GrantResourceGroupRequest) ProtoMessage() {} func (x *GrantResourceGroupRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[66] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6593,7 +6661,7 @@ func (x *GrantResourceGroupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantResourceGroupRequest.ProtoReflect.Descriptor instead. func (*GrantResourceGroupRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{66} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{67} } func (x *GrantResourceGroupRequest) GetCommandId() string { @@ -6654,7 +6722,7 @@ type ResourceGrantResponse struct { func (x *ResourceGrantResponse) Reset() { *x = ResourceGrantResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[67] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6666,7 +6734,7 @@ func (x *ResourceGrantResponse) String() string { func (*ResourceGrantResponse) ProtoMessage() {} func (x *ResourceGrantResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[67] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6679,7 +6747,7 @@ func (x *ResourceGrantResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceGrantResponse.ProtoReflect.Descriptor instead. func (*ResourceGrantResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{67} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{68} } func (x *ResourceGrantResponse) GetGrant() *ResourceGrant { @@ -6702,7 +6770,7 @@ type ListUserResourcesRequest struct { func (x *ListUserResourcesRequest) Reset() { *x = ListUserResourcesRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[68] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6714,7 +6782,7 @@ func (x *ListUserResourcesRequest) String() string { func (*ListUserResourcesRequest) ProtoMessage() {} func (x *ListUserResourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[68] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6727,7 +6795,7 @@ func (x *ListUserResourcesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserResourcesRequest.ProtoReflect.Descriptor instead. func (*ListUserResourcesRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{68} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{69} } func (x *ListUserResourcesRequest) GetRequestId() string { @@ -6774,7 +6842,7 @@ type ListUserResourcesResponse struct { func (x *ListUserResourcesResponse) Reset() { *x = ListUserResourcesResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[69] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6786,7 +6854,7 @@ func (x *ListUserResourcesResponse) String() string { func (*ListUserResourcesResponse) ProtoMessage() {} func (x *ListUserResourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[69] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6799,7 +6867,7 @@ func (x *ListUserResourcesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUserResourcesResponse.ProtoReflect.Descriptor instead. func (*ListUserResourcesResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{69} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{70} } func (x *ListUserResourcesResponse) GetResources() []*UserResourceEntitlement { @@ -6822,7 +6890,7 @@ type EquipUserResourceRequest struct { func (x *EquipUserResourceRequest) Reset() { *x = EquipUserResourceRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[70] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6834,7 +6902,7 @@ func (x *EquipUserResourceRequest) String() string { func (*EquipUserResourceRequest) ProtoMessage() {} func (x *EquipUserResourceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[70] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6847,7 +6915,7 @@ func (x *EquipUserResourceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EquipUserResourceRequest.ProtoReflect.Descriptor instead. func (*EquipUserResourceRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{70} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{71} } func (x *EquipUserResourceRequest) GetRequestId() string { @@ -6894,7 +6962,7 @@ type EquipUserResourceResponse struct { func (x *EquipUserResourceResponse) Reset() { *x = EquipUserResourceResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[71] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6906,7 +6974,7 @@ func (x *EquipUserResourceResponse) String() string { func (*EquipUserResourceResponse) ProtoMessage() {} func (x *EquipUserResourceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[71] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6919,7 +6987,7 @@ func (x *EquipUserResourceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EquipUserResourceResponse.ProtoReflect.Descriptor instead. func (*EquipUserResourceResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{71} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{72} } func (x *EquipUserResourceResponse) GetResource() *UserResourceEntitlement { @@ -6941,7 +7009,7 @@ type UnequipUserResourceRequest struct { func (x *UnequipUserResourceRequest) Reset() { *x = UnequipUserResourceRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[72] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6953,7 +7021,7 @@ func (x *UnequipUserResourceRequest) String() string { func (*UnequipUserResourceRequest) ProtoMessage() {} func (x *UnequipUserResourceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[72] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6966,7 +7034,7 @@ func (x *UnequipUserResourceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnequipUserResourceRequest.ProtoReflect.Descriptor instead. func (*UnequipUserResourceRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{72} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{73} } func (x *UnequipUserResourceRequest) GetRequestId() string { @@ -7008,7 +7076,7 @@ type UnequipUserResourceResponse struct { func (x *UnequipUserResourceResponse) Reset() { *x = UnequipUserResourceResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[73] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7020,7 +7088,7 @@ func (x *UnequipUserResourceResponse) String() string { func (*UnequipUserResourceResponse) ProtoMessage() {} func (x *UnequipUserResourceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[73] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7033,7 +7101,7 @@ func (x *UnequipUserResourceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnequipUserResourceResponse.ProtoReflect.Descriptor instead. func (*UnequipUserResourceResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{73} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{74} } func (x *UnequipUserResourceResponse) GetResourceType() string { @@ -7069,7 +7137,7 @@ type BatchGetUserEquippedResourcesRequest struct { func (x *BatchGetUserEquippedResourcesRequest) Reset() { *x = BatchGetUserEquippedResourcesRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[74] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7081,7 +7149,7 @@ func (x *BatchGetUserEquippedResourcesRequest) String() string { func (*BatchGetUserEquippedResourcesRequest) ProtoMessage() {} func (x *BatchGetUserEquippedResourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[74] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7094,7 +7162,7 @@ func (x *BatchGetUserEquippedResourcesRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use BatchGetUserEquippedResourcesRequest.ProtoReflect.Descriptor instead. func (*BatchGetUserEquippedResourcesRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{74} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{75} } func (x *BatchGetUserEquippedResourcesRequest) GetRequestId() string { @@ -7135,7 +7203,7 @@ type UserEquippedResources struct { func (x *UserEquippedResources) Reset() { *x = UserEquippedResources{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[75] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7147,7 +7215,7 @@ func (x *UserEquippedResources) String() string { func (*UserEquippedResources) ProtoMessage() {} func (x *UserEquippedResources) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[75] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7160,7 +7228,7 @@ func (x *UserEquippedResources) ProtoReflect() protoreflect.Message { // Deprecated: Use UserEquippedResources.ProtoReflect.Descriptor instead. func (*UserEquippedResources) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{75} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{76} } func (x *UserEquippedResources) GetUserId() int64 { @@ -7186,7 +7254,7 @@ type BatchGetUserEquippedResourcesResponse struct { func (x *BatchGetUserEquippedResourcesResponse) Reset() { *x = BatchGetUserEquippedResourcesResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[76] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7198,7 +7266,7 @@ func (x *BatchGetUserEquippedResourcesResponse) String() string { func (*BatchGetUserEquippedResourcesResponse) ProtoMessage() {} func (x *BatchGetUserEquippedResourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[76] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7211,7 +7279,7 @@ func (x *BatchGetUserEquippedResourcesResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use BatchGetUserEquippedResourcesResponse.ProtoReflect.Descriptor instead. func (*BatchGetUserEquippedResourcesResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{76} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{77} } func (x *BatchGetUserEquippedResourcesResponse) GetUsers() []*UserEquippedResources { @@ -7235,7 +7303,7 @@ type ListResourceGrantsRequest struct { func (x *ListResourceGrantsRequest) Reset() { *x = ListResourceGrantsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[77] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7247,7 +7315,7 @@ func (x *ListResourceGrantsRequest) String() string { func (*ListResourceGrantsRequest) ProtoMessage() {} func (x *ListResourceGrantsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[77] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7260,7 +7328,7 @@ func (x *ListResourceGrantsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResourceGrantsRequest.ProtoReflect.Descriptor instead. func (*ListResourceGrantsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{77} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{78} } func (x *ListResourceGrantsRequest) GetRequestId() string { @@ -7315,7 +7383,7 @@ type ListResourceGrantsResponse struct { func (x *ListResourceGrantsResponse) Reset() { *x = ListResourceGrantsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[78] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7327,7 +7395,7 @@ func (x *ListResourceGrantsResponse) String() string { func (*ListResourceGrantsResponse) ProtoMessage() {} func (x *ListResourceGrantsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[78] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7340,7 +7408,7 @@ func (x *ListResourceGrantsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResourceGrantsResponse.ProtoReflect.Descriptor instead. func (*ListResourceGrantsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{78} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{79} } func (x *ListResourceGrantsResponse) GetGrants() []*ResourceGrant { @@ -7372,7 +7440,7 @@ type ResourceShopItemInput struct { func (x *ResourceShopItemInput) Reset() { *x = ResourceShopItemInput{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[79] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7384,7 +7452,7 @@ func (x *ResourceShopItemInput) String() string { func (*ResourceShopItemInput) ProtoMessage() {} func (x *ResourceShopItemInput) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[79] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7397,7 +7465,7 @@ func (x *ResourceShopItemInput) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceShopItemInput.ProtoReflect.Descriptor instead. func (*ResourceShopItemInput) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{79} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{80} } func (x *ResourceShopItemInput) GetShopItemId() int64 { @@ -7465,7 +7533,7 @@ type ListResourceShopItemsRequest struct { func (x *ListResourceShopItemsRequest) Reset() { *x = ListResourceShopItemsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[80] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7477,7 +7545,7 @@ func (x *ListResourceShopItemsRequest) String() string { func (*ListResourceShopItemsRequest) ProtoMessage() {} func (x *ListResourceShopItemsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[80] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7490,7 +7558,7 @@ func (x *ListResourceShopItemsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResourceShopItemsRequest.ProtoReflect.Descriptor instead. func (*ListResourceShopItemsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{80} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{81} } func (x *ListResourceShopItemsRequest) GetRequestId() string { @@ -7559,7 +7627,7 @@ type ListResourceShopItemsResponse struct { func (x *ListResourceShopItemsResponse) Reset() { *x = ListResourceShopItemsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[81] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7571,7 +7639,7 @@ func (x *ListResourceShopItemsResponse) String() string { func (*ListResourceShopItemsResponse) ProtoMessage() {} func (x *ListResourceShopItemsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[81] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7584,7 +7652,7 @@ func (x *ListResourceShopItemsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResourceShopItemsResponse.ProtoReflect.Descriptor instead. func (*ListResourceShopItemsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{81} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{82} } func (x *ListResourceShopItemsResponse) GetItems() []*ResourceShopItem { @@ -7613,7 +7681,7 @@ type UpsertResourceShopItemsRequest struct { func (x *UpsertResourceShopItemsRequest) Reset() { *x = UpsertResourceShopItemsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[82] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7625,7 +7693,7 @@ func (x *UpsertResourceShopItemsRequest) String() string { func (*UpsertResourceShopItemsRequest) ProtoMessage() {} func (x *UpsertResourceShopItemsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[82] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7638,7 +7706,7 @@ func (x *UpsertResourceShopItemsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertResourceShopItemsRequest.ProtoReflect.Descriptor instead. func (*UpsertResourceShopItemsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{82} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{83} } func (x *UpsertResourceShopItemsRequest) GetRequestId() string { @@ -7678,7 +7746,7 @@ type UpsertResourceShopItemsResponse struct { func (x *UpsertResourceShopItemsResponse) Reset() { *x = UpsertResourceShopItemsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[83] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7690,7 +7758,7 @@ func (x *UpsertResourceShopItemsResponse) String() string { func (*UpsertResourceShopItemsResponse) ProtoMessage() {} func (x *UpsertResourceShopItemsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[83] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7703,7 +7771,7 @@ func (x *UpsertResourceShopItemsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertResourceShopItemsResponse.ProtoReflect.Descriptor instead. func (*UpsertResourceShopItemsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{83} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{84} } func (x *UpsertResourceShopItemsResponse) GetItems() []*ResourceShopItem { @@ -7726,7 +7794,7 @@ type SetResourceShopItemStatusRequest struct { func (x *SetResourceShopItemStatusRequest) Reset() { *x = SetResourceShopItemStatusRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[84] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7738,7 +7806,7 @@ func (x *SetResourceShopItemStatusRequest) String() string { func (*SetResourceShopItemStatusRequest) ProtoMessage() {} func (x *SetResourceShopItemStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[84] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7751,7 +7819,7 @@ func (x *SetResourceShopItemStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetResourceShopItemStatusRequest.ProtoReflect.Descriptor instead. func (*SetResourceShopItemStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{84} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{85} } func (x *SetResourceShopItemStatusRequest) GetRequestId() string { @@ -7798,7 +7866,7 @@ type ResourceShopItemResponse struct { func (x *ResourceShopItemResponse) Reset() { *x = ResourceShopItemResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[85] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7810,7 +7878,7 @@ func (x *ResourceShopItemResponse) String() string { func (*ResourceShopItemResponse) ProtoMessage() {} func (x *ResourceShopItemResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[85] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7823,7 +7891,7 @@ func (x *ResourceShopItemResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceShopItemResponse.ProtoReflect.Descriptor instead. func (*ResourceShopItemResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{85} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{86} } func (x *ResourceShopItemResponse) GetItem() *ResourceShopItem { @@ -7845,7 +7913,7 @@ type PurchaseResourceShopItemRequest struct { func (x *PurchaseResourceShopItemRequest) Reset() { *x = PurchaseResourceShopItemRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[86] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7857,7 +7925,7 @@ func (x *PurchaseResourceShopItemRequest) String() string { func (*PurchaseResourceShopItemRequest) ProtoMessage() {} func (x *PurchaseResourceShopItemRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[86] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7870,7 +7938,7 @@ func (x *PurchaseResourceShopItemRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PurchaseResourceShopItemRequest.ProtoReflect.Descriptor instead. func (*PurchaseResourceShopItemRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{86} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{87} } func (x *PurchaseResourceShopItemRequest) GetCommandId() string { @@ -7916,7 +7984,7 @@ type PurchaseResourceShopItemResponse struct { func (x *PurchaseResourceShopItemResponse) Reset() { *x = PurchaseResourceShopItemResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[87] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7928,7 +7996,7 @@ func (x *PurchaseResourceShopItemResponse) String() string { func (*PurchaseResourceShopItemResponse) ProtoMessage() {} func (x *PurchaseResourceShopItemResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[87] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7941,7 +8009,7 @@ func (x *PurchaseResourceShopItemResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PurchaseResourceShopItemResponse.ProtoReflect.Descriptor instead. func (*PurchaseResourceShopItemResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{87} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{88} } func (x *PurchaseResourceShopItemResponse) GetOrderId() string { @@ -8019,7 +8087,7 @@ type RechargeBill struct { func (x *RechargeBill) Reset() { *x = RechargeBill{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[88] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8031,7 +8099,7 @@ func (x *RechargeBill) String() string { func (*RechargeBill) ProtoMessage() {} func (x *RechargeBill) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[88] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8044,7 +8112,7 @@ func (x *RechargeBill) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeBill.ProtoReflect.Descriptor instead. func (*RechargeBill) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{88} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{89} } func (x *RechargeBill) GetAppCode() string { @@ -8193,7 +8261,7 @@ type ListRechargeBillsRequest struct { func (x *ListRechargeBillsRequest) Reset() { *x = ListRechargeBillsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[89] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8205,7 +8273,7 @@ func (x *ListRechargeBillsRequest) String() string { func (*ListRechargeBillsRequest) ProtoMessage() {} func (x *ListRechargeBillsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[89] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8218,7 +8286,7 @@ func (x *ListRechargeBillsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRechargeBillsRequest.ProtoReflect.Descriptor instead. func (*ListRechargeBillsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{89} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{90} } func (x *ListRechargeBillsRequest) GetRequestId() string { @@ -8315,7 +8383,7 @@ type ListRechargeBillsResponse struct { func (x *ListRechargeBillsResponse) Reset() { *x = ListRechargeBillsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[90] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8327,7 +8395,7 @@ func (x *ListRechargeBillsResponse) String() string { func (*ListRechargeBillsResponse) ProtoMessage() {} func (x *ListRechargeBillsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[90] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8340,7 +8408,7 @@ func (x *ListRechargeBillsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRechargeBillsResponse.ProtoReflect.Descriptor instead. func (*ListRechargeBillsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{90} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{91} } func (x *ListRechargeBillsResponse) GetBills() []*RechargeBill { @@ -8368,7 +8436,7 @@ type WalletFeatureFlags struct { func (x *WalletFeatureFlags) Reset() { *x = WalletFeatureFlags{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[91] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8380,7 +8448,7 @@ func (x *WalletFeatureFlags) String() string { func (*WalletFeatureFlags) ProtoMessage() {} func (x *WalletFeatureFlags) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[91] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8393,7 +8461,7 @@ func (x *WalletFeatureFlags) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletFeatureFlags.ProtoReflect.Descriptor instead. func (*WalletFeatureFlags) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{91} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{92} } func (x *WalletFeatureFlags) GetRechargeEnabled() bool { @@ -8421,7 +8489,7 @@ type GetWalletOverviewRequest struct { func (x *GetWalletOverviewRequest) Reset() { *x = GetWalletOverviewRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[92] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8433,7 +8501,7 @@ func (x *GetWalletOverviewRequest) String() string { func (*GetWalletOverviewRequest) ProtoMessage() {} func (x *GetWalletOverviewRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[92] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8446,7 +8514,7 @@ func (x *GetWalletOverviewRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWalletOverviewRequest.ProtoReflect.Descriptor instead. func (*GetWalletOverviewRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{92} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{93} } func (x *GetWalletOverviewRequest) GetRequestId() string { @@ -8480,7 +8548,7 @@ type GetWalletOverviewResponse struct { func (x *GetWalletOverviewResponse) Reset() { *x = GetWalletOverviewResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[93] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8492,7 +8560,7 @@ func (x *GetWalletOverviewResponse) String() string { func (*GetWalletOverviewResponse) ProtoMessage() {} func (x *GetWalletOverviewResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[93] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8505,7 +8573,7 @@ func (x *GetWalletOverviewResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWalletOverviewResponse.ProtoReflect.Descriptor instead. func (*GetWalletOverviewResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{93} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{94} } func (x *GetWalletOverviewResponse) GetBalances() []*AssetBalance { @@ -8533,7 +8601,7 @@ type WalletValueSummary struct { func (x *WalletValueSummary) Reset() { *x = WalletValueSummary{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[94] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8545,7 +8613,7 @@ func (x *WalletValueSummary) String() string { func (*WalletValueSummary) ProtoMessage() {} func (x *WalletValueSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[94] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8558,7 +8626,7 @@ func (x *WalletValueSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletValueSummary.ProtoReflect.Descriptor instead. func (*WalletValueSummary) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{94} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{95} } func (x *WalletValueSummary) GetCoinAmount() int64 { @@ -8586,7 +8654,7 @@ type GetWalletValueSummaryRequest struct { func (x *GetWalletValueSummaryRequest) Reset() { *x = GetWalletValueSummaryRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[95] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8598,7 +8666,7 @@ func (x *GetWalletValueSummaryRequest) String() string { func (*GetWalletValueSummaryRequest) ProtoMessage() {} func (x *GetWalletValueSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[95] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8611,7 +8679,7 @@ func (x *GetWalletValueSummaryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWalletValueSummaryRequest.ProtoReflect.Descriptor instead. func (*GetWalletValueSummaryRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{95} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{96} } func (x *GetWalletValueSummaryRequest) GetRequestId() string { @@ -8644,7 +8712,7 @@ type GetWalletValueSummaryResponse struct { func (x *GetWalletValueSummaryResponse) Reset() { *x = GetWalletValueSummaryResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[96] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8656,7 +8724,7 @@ func (x *GetWalletValueSummaryResponse) String() string { func (*GetWalletValueSummaryResponse) ProtoMessage() {} func (x *GetWalletValueSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[96] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8669,7 +8737,7 @@ func (x *GetWalletValueSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWalletValueSummaryResponse.ProtoReflect.Descriptor instead. func (*GetWalletValueSummaryResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{96} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{97} } func (x *GetWalletValueSummaryResponse) GetSummary() *WalletValueSummary { @@ -8707,7 +8775,7 @@ type GiftWallItem struct { func (x *GiftWallItem) Reset() { *x = GiftWallItem{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[97] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8719,7 +8787,7 @@ func (x *GiftWallItem) String() string { func (*GiftWallItem) ProtoMessage() {} func (x *GiftWallItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[97] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8732,7 +8800,7 @@ func (x *GiftWallItem) ProtoReflect() protoreflect.Message { // Deprecated: Use GiftWallItem.ProtoReflect.Descriptor instead. func (*GiftWallItem) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{97} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{98} } func (x *GiftWallItem) GetGiftId() string { @@ -8872,7 +8940,7 @@ type GetUserGiftWallRequest struct { func (x *GetUserGiftWallRequest) Reset() { *x = GetUserGiftWallRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[98] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8884,7 +8952,7 @@ func (x *GetUserGiftWallRequest) String() string { func (*GetUserGiftWallRequest) ProtoMessage() {} func (x *GetUserGiftWallRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[98] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8897,7 +8965,7 @@ func (x *GetUserGiftWallRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserGiftWallRequest.ProtoReflect.Descriptor instead. func (*GetUserGiftWallRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{98} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{99} } func (x *GetUserGiftWallRequest) GetRequestId() string { @@ -8938,7 +9006,7 @@ type GetUserGiftWallResponse struct { func (x *GetUserGiftWallResponse) Reset() { *x = GetUserGiftWallResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[99] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8950,7 +9018,7 @@ func (x *GetUserGiftWallResponse) String() string { func (*GetUserGiftWallResponse) ProtoMessage() {} func (x *GetUserGiftWallResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[99] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8963,7 +9031,7 @@ func (x *GetUserGiftWallResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserGiftWallResponse.ProtoReflect.Descriptor instead. func (*GetUserGiftWallResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{99} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{100} } func (x *GetUserGiftWallResponse) GetItems() []*GiftWallItem { @@ -9054,7 +9122,7 @@ type RechargeProduct struct { func (x *RechargeProduct) Reset() { *x = RechargeProduct{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[100] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9066,7 +9134,7 @@ func (x *RechargeProduct) String() string { func (*RechargeProduct) ProtoMessage() {} func (x *RechargeProduct) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[100] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9079,7 +9147,7 @@ func (x *RechargeProduct) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeProduct.ProtoReflect.Descriptor instead. func (*RechargeProduct) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{100} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{101} } func (x *RechargeProduct) GetProductId() int64 { @@ -9252,7 +9320,7 @@ type ListRechargeProductsRequest struct { func (x *ListRechargeProductsRequest) Reset() { *x = ListRechargeProductsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[101] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9264,7 +9332,7 @@ func (x *ListRechargeProductsRequest) String() string { func (*ListRechargeProductsRequest) ProtoMessage() {} func (x *ListRechargeProductsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[101] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9277,7 +9345,7 @@ func (x *ListRechargeProductsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRechargeProductsRequest.ProtoReflect.Descriptor instead. func (*ListRechargeProductsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{101} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{102} } func (x *ListRechargeProductsRequest) GetRequestId() string { @@ -9332,7 +9400,7 @@ type ListRechargeProductsResponse struct { func (x *ListRechargeProductsResponse) Reset() { *x = ListRechargeProductsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[102] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9344,7 +9412,7 @@ func (x *ListRechargeProductsResponse) String() string { func (*ListRechargeProductsResponse) ProtoMessage() {} func (x *ListRechargeProductsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[102] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9357,7 +9425,7 @@ func (x *ListRechargeProductsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRechargeProductsResponse.ProtoReflect.Descriptor instead. func (*ListRechargeProductsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{102} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{103} } func (x *ListRechargeProductsResponse) GetProducts() []*RechargeProduct { @@ -9393,7 +9461,7 @@ type ConfirmGooglePaymentRequest struct { func (x *ConfirmGooglePaymentRequest) Reset() { *x = ConfirmGooglePaymentRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[103] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9405,7 +9473,7 @@ func (x *ConfirmGooglePaymentRequest) String() string { func (*ConfirmGooglePaymentRequest) ProtoMessage() {} func (x *ConfirmGooglePaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[103] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9418,7 +9486,7 @@ func (x *ConfirmGooglePaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmGooglePaymentRequest.ProtoReflect.Descriptor instead. func (*ConfirmGooglePaymentRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{103} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{104} } func (x *ConfirmGooglePaymentRequest) GetRequestId() string { @@ -9515,7 +9583,7 @@ type ConfirmGooglePaymentResponse struct { func (x *ConfirmGooglePaymentResponse) Reset() { *x = ConfirmGooglePaymentResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[104] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9527,7 +9595,7 @@ func (x *ConfirmGooglePaymentResponse) String() string { func (*ConfirmGooglePaymentResponse) ProtoMessage() {} func (x *ConfirmGooglePaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[104] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9540,7 +9608,7 @@ func (x *ConfirmGooglePaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmGooglePaymentResponse.ProtoReflect.Descriptor instead. func (*ConfirmGooglePaymentResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{104} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{105} } func (x *ConfirmGooglePaymentResponse) GetPaymentOrderId() string { @@ -9624,7 +9692,7 @@ type ListAdminRechargeProductsRequest struct { func (x *ListAdminRechargeProductsRequest) Reset() { *x = ListAdminRechargeProductsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[105] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9636,7 +9704,7 @@ func (x *ListAdminRechargeProductsRequest) String() string { func (*ListAdminRechargeProductsRequest) ProtoMessage() {} func (x *ListAdminRechargeProductsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[105] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9649,7 +9717,7 @@ func (x *ListAdminRechargeProductsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAdminRechargeProductsRequest.ProtoReflect.Descriptor instead. func (*ListAdminRechargeProductsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{105} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{106} } func (x *ListAdminRechargeProductsRequest) GetRequestId() string { @@ -9725,7 +9793,7 @@ type ListAdminRechargeProductsResponse struct { func (x *ListAdminRechargeProductsResponse) Reset() { *x = ListAdminRechargeProductsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[106] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9737,7 +9805,7 @@ func (x *ListAdminRechargeProductsResponse) String() string { func (*ListAdminRechargeProductsResponse) ProtoMessage() {} func (x *ListAdminRechargeProductsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[106] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9750,7 +9818,7 @@ func (x *ListAdminRechargeProductsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListAdminRechargeProductsResponse.ProtoReflect.Descriptor instead. func (*ListAdminRechargeProductsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{106} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{107} } func (x *ListAdminRechargeProductsResponse) GetProducts() []*RechargeProduct { @@ -9787,7 +9855,7 @@ type CreateRechargeProductRequest struct { func (x *CreateRechargeProductRequest) Reset() { *x = CreateRechargeProductRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[107] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9799,7 +9867,7 @@ func (x *CreateRechargeProductRequest) String() string { func (*CreateRechargeProductRequest) ProtoMessage() {} func (x *CreateRechargeProductRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[107] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9812,7 +9880,7 @@ func (x *CreateRechargeProductRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRechargeProductRequest.ProtoReflect.Descriptor instead. func (*CreateRechargeProductRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{107} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{108} } func (x *CreateRechargeProductRequest) GetRequestId() string { @@ -9912,7 +9980,7 @@ type UpdateRechargeProductRequest struct { func (x *UpdateRechargeProductRequest) Reset() { *x = UpdateRechargeProductRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[108] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9924,7 +9992,7 @@ func (x *UpdateRechargeProductRequest) String() string { func (*UpdateRechargeProductRequest) ProtoMessage() {} func (x *UpdateRechargeProductRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[108] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9937,7 +10005,7 @@ func (x *UpdateRechargeProductRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRechargeProductRequest.ProtoReflect.Descriptor instead. func (*UpdateRechargeProductRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{108} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{109} } func (x *UpdateRechargeProductRequest) GetRequestId() string { @@ -10036,7 +10104,7 @@ type DeleteRechargeProductRequest struct { func (x *DeleteRechargeProductRequest) Reset() { *x = DeleteRechargeProductRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[109] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10048,7 +10116,7 @@ func (x *DeleteRechargeProductRequest) String() string { func (*DeleteRechargeProductRequest) ProtoMessage() {} func (x *DeleteRechargeProductRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[109] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10061,7 +10129,7 @@ func (x *DeleteRechargeProductRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteRechargeProductRequest.ProtoReflect.Descriptor instead. func (*DeleteRechargeProductRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{109} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{110} } func (x *DeleteRechargeProductRequest) GetRequestId() string { @@ -10101,7 +10169,7 @@ type RechargeProductResponse struct { func (x *RechargeProductResponse) Reset() { *x = RechargeProductResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[110] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10113,7 +10181,7 @@ func (x *RechargeProductResponse) String() string { func (*RechargeProductResponse) ProtoMessage() {} func (x *RechargeProductResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[110] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10126,7 +10194,7 @@ func (x *RechargeProductResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RechargeProductResponse.ProtoReflect.Descriptor instead. func (*RechargeProductResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{110} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{111} } func (x *RechargeProductResponse) GetProduct() *RechargeProduct { @@ -10145,7 +10213,7 @@ type DeleteRechargeProductResponse struct { func (x *DeleteRechargeProductResponse) Reset() { *x = DeleteRechargeProductResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[111] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10157,7 +10225,7 @@ func (x *DeleteRechargeProductResponse) String() string { func (*DeleteRechargeProductResponse) ProtoMessage() {} func (x *DeleteRechargeProductResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[111] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10170,7 +10238,7 @@ func (x *DeleteRechargeProductResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteRechargeProductResponse.ProtoReflect.Descriptor instead. func (*DeleteRechargeProductResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{111} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{112} } func (x *DeleteRechargeProductResponse) GetDeleted() bool { @@ -10205,7 +10273,7 @@ type ThirdPartyPaymentMethod struct { func (x *ThirdPartyPaymentMethod) Reset() { *x = ThirdPartyPaymentMethod{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10217,7 +10285,7 @@ func (x *ThirdPartyPaymentMethod) String() string { func (*ThirdPartyPaymentMethod) ProtoMessage() {} func (x *ThirdPartyPaymentMethod) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10230,7 +10298,7 @@ func (x *ThirdPartyPaymentMethod) ProtoReflect() protoreflect.Message { // Deprecated: Use ThirdPartyPaymentMethod.ProtoReflect.Descriptor instead. func (*ThirdPartyPaymentMethod) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{112} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{113} } func (x *ThirdPartyPaymentMethod) GetMethodId() int64 { @@ -10359,7 +10427,7 @@ type ThirdPartyPaymentChannel struct { func (x *ThirdPartyPaymentChannel) Reset() { *x = ThirdPartyPaymentChannel{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10371,7 +10439,7 @@ func (x *ThirdPartyPaymentChannel) String() string { func (*ThirdPartyPaymentChannel) ProtoMessage() {} func (x *ThirdPartyPaymentChannel) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10384,7 +10452,7 @@ func (x *ThirdPartyPaymentChannel) ProtoReflect() protoreflect.Message { // Deprecated: Use ThirdPartyPaymentChannel.ProtoReflect.Descriptor instead. func (*ThirdPartyPaymentChannel) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{113} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{114} } func (x *ThirdPartyPaymentChannel) GetAppCode() string { @@ -10442,7 +10510,7 @@ type ListThirdPartyPaymentChannelsRequest struct { func (x *ListThirdPartyPaymentChannelsRequest) Reset() { *x = ListThirdPartyPaymentChannelsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10454,7 +10522,7 @@ func (x *ListThirdPartyPaymentChannelsRequest) String() string { func (*ListThirdPartyPaymentChannelsRequest) ProtoMessage() {} func (x *ListThirdPartyPaymentChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10467,7 +10535,7 @@ func (x *ListThirdPartyPaymentChannelsRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use ListThirdPartyPaymentChannelsRequest.ProtoReflect.Descriptor instead. func (*ListThirdPartyPaymentChannelsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{114} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{115} } func (x *ListThirdPartyPaymentChannelsRequest) GetRequestId() string { @@ -10514,7 +10582,7 @@ type ListThirdPartyPaymentChannelsResponse struct { func (x *ListThirdPartyPaymentChannelsResponse) Reset() { *x = ListThirdPartyPaymentChannelsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10526,7 +10594,7 @@ func (x *ListThirdPartyPaymentChannelsResponse) String() string { func (*ListThirdPartyPaymentChannelsResponse) ProtoMessage() {} func (x *ListThirdPartyPaymentChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10539,7 +10607,7 @@ func (x *ListThirdPartyPaymentChannelsResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use ListThirdPartyPaymentChannelsResponse.ProtoReflect.Descriptor instead. func (*ListThirdPartyPaymentChannelsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{115} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{116} } func (x *ListThirdPartyPaymentChannelsResponse) GetChannels() []*ThirdPartyPaymentChannel { @@ -10562,7 +10630,7 @@ type SetThirdPartyPaymentMethodStatusRequest struct { func (x *SetThirdPartyPaymentMethodStatusRequest) Reset() { *x = SetThirdPartyPaymentMethodStatusRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10574,7 +10642,7 @@ func (x *SetThirdPartyPaymentMethodStatusRequest) String() string { func (*SetThirdPartyPaymentMethodStatusRequest) ProtoMessage() {} func (x *SetThirdPartyPaymentMethodStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10587,7 +10655,7 @@ func (x *SetThirdPartyPaymentMethodStatusRequest) ProtoReflect() protoreflect.Me // Deprecated: Use SetThirdPartyPaymentMethodStatusRequest.ProtoReflect.Descriptor instead. func (*SetThirdPartyPaymentMethodStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{116} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{117} } func (x *SetThirdPartyPaymentMethodStatusRequest) GetRequestId() string { @@ -10634,7 +10702,7 @@ type ThirdPartyPaymentMethodResponse struct { func (x *ThirdPartyPaymentMethodResponse) Reset() { *x = ThirdPartyPaymentMethodResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10646,7 +10714,7 @@ func (x *ThirdPartyPaymentMethodResponse) String() string { func (*ThirdPartyPaymentMethodResponse) ProtoMessage() {} func (x *ThirdPartyPaymentMethodResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10659,7 +10727,7 @@ func (x *ThirdPartyPaymentMethodResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ThirdPartyPaymentMethodResponse.ProtoReflect.Descriptor instead. func (*ThirdPartyPaymentMethodResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{117} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{118} } func (x *ThirdPartyPaymentMethodResponse) GetMethod() *ThirdPartyPaymentMethod { @@ -10682,7 +10750,7 @@ type UpdateThirdPartyPaymentRateRequest struct { func (x *UpdateThirdPartyPaymentRateRequest) Reset() { *x = UpdateThirdPartyPaymentRateRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10694,7 +10762,7 @@ func (x *UpdateThirdPartyPaymentRateRequest) String() string { func (*UpdateThirdPartyPaymentRateRequest) ProtoMessage() {} func (x *UpdateThirdPartyPaymentRateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10707,7 +10775,7 @@ func (x *UpdateThirdPartyPaymentRateRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateThirdPartyPaymentRateRequest.ProtoReflect.Descriptor instead. func (*UpdateThirdPartyPaymentRateRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{118} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{119} } func (x *UpdateThirdPartyPaymentRateRequest) GetRequestId() string { @@ -10759,7 +10827,7 @@ type H5RechargeOptionsRequest struct { func (x *H5RechargeOptionsRequest) Reset() { *x = H5RechargeOptionsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10771,7 +10839,7 @@ func (x *H5RechargeOptionsRequest) String() string { func (*H5RechargeOptionsRequest) ProtoMessage() {} func (x *H5RechargeOptionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10784,7 +10852,7 @@ func (x *H5RechargeOptionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use H5RechargeOptionsRequest.ProtoReflect.Descriptor instead. func (*H5RechargeOptionsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{119} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{120} } func (x *H5RechargeOptionsRequest) GetRequestId() string { @@ -10841,7 +10909,7 @@ type H5RechargeOptionsResponse struct { func (x *H5RechargeOptionsResponse) Reset() { *x = H5RechargeOptionsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10853,7 +10921,7 @@ func (x *H5RechargeOptionsResponse) String() string { func (*H5RechargeOptionsResponse) ProtoMessage() {} func (x *H5RechargeOptionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10866,7 +10934,7 @@ func (x *H5RechargeOptionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use H5RechargeOptionsResponse.ProtoReflect.Descriptor instead. func (*H5RechargeOptionsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{120} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{121} } func (x *H5RechargeOptionsResponse) GetProducts() []*RechargeProduct { @@ -10932,7 +11000,7 @@ type ExternalRechargeOrder struct { func (x *ExternalRechargeOrder) Reset() { *x = ExternalRechargeOrder{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10944,7 +11012,7 @@ func (x *ExternalRechargeOrder) String() string { func (*ExternalRechargeOrder) ProtoMessage() {} func (x *ExternalRechargeOrder) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10957,7 +11025,7 @@ func (x *ExternalRechargeOrder) ProtoReflect() protoreflect.Message { // Deprecated: Use ExternalRechargeOrder.ProtoReflect.Descriptor instead. func (*ExternalRechargeOrder) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{121} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{122} } func (x *ExternalRechargeOrder) GetOrderId() string { @@ -11171,7 +11239,7 @@ type CreateH5RechargeOrderRequest struct { func (x *CreateH5RechargeOrderRequest) Reset() { *x = CreateH5RechargeOrderRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11183,7 +11251,7 @@ func (x *CreateH5RechargeOrderRequest) String() string { func (*CreateH5RechargeOrderRequest) ProtoMessage() {} func (x *CreateH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11196,7 +11264,7 @@ func (x *CreateH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateH5RechargeOrderRequest.ProtoReflect.Descriptor instead. func (*CreateH5RechargeOrderRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{122} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{123} } func (x *CreateH5RechargeOrderRequest) GetRequestId() string { @@ -11310,7 +11378,7 @@ type SubmitH5RechargeTxRequest struct { func (x *SubmitH5RechargeTxRequest) Reset() { *x = SubmitH5RechargeTxRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11322,7 +11390,7 @@ func (x *SubmitH5RechargeTxRequest) String() string { func (*SubmitH5RechargeTxRequest) ProtoMessage() {} func (x *SubmitH5RechargeTxRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11335,7 +11403,7 @@ func (x *SubmitH5RechargeTxRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitH5RechargeTxRequest.ProtoReflect.Descriptor instead. func (*SubmitH5RechargeTxRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{123} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{124} } func (x *SubmitH5RechargeTxRequest) GetRequestId() string { @@ -11385,7 +11453,7 @@ type GetH5RechargeOrderRequest struct { func (x *GetH5RechargeOrderRequest) Reset() { *x = GetH5RechargeOrderRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11397,7 +11465,7 @@ func (x *GetH5RechargeOrderRequest) String() string { func (*GetH5RechargeOrderRequest) ProtoMessage() {} func (x *GetH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11410,7 +11478,7 @@ func (x *GetH5RechargeOrderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetH5RechargeOrderRequest.ProtoReflect.Descriptor instead. func (*GetH5RechargeOrderRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{124} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{125} } func (x *GetH5RechargeOrderRequest) GetRequestId() string { @@ -11450,7 +11518,7 @@ type H5RechargeOrderResponse struct { func (x *H5RechargeOrderResponse) Reset() { *x = H5RechargeOrderResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11462,7 +11530,7 @@ func (x *H5RechargeOrderResponse) String() string { func (*H5RechargeOrderResponse) ProtoMessage() {} func (x *H5RechargeOrderResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11475,7 +11543,7 @@ func (x *H5RechargeOrderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use H5RechargeOrderResponse.ProtoReflect.Descriptor instead. func (*H5RechargeOrderResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{125} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{126} } func (x *H5RechargeOrderResponse) GetOrder() *ExternalRechargeOrder { @@ -11498,7 +11566,7 @@ type HandleMifapayNotifyRequest struct { func (x *HandleMifapayNotifyRequest) Reset() { *x = HandleMifapayNotifyRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11510,7 +11578,7 @@ func (x *HandleMifapayNotifyRequest) String() string { func (*HandleMifapayNotifyRequest) ProtoMessage() {} func (x *HandleMifapayNotifyRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11523,7 +11591,7 @@ func (x *HandleMifapayNotifyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HandleMifapayNotifyRequest.ProtoReflect.Descriptor instead. func (*HandleMifapayNotifyRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{126} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{127} } func (x *HandleMifapayNotifyRequest) GetRequestId() string { @@ -11572,7 +11640,7 @@ type HandleMifapayNotifyResponse struct { func (x *HandleMifapayNotifyResponse) Reset() { *x = HandleMifapayNotifyResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11584,7 +11652,7 @@ func (x *HandleMifapayNotifyResponse) String() string { func (*HandleMifapayNotifyResponse) ProtoMessage() {} func (x *HandleMifapayNotifyResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[127] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11597,7 +11665,7 @@ func (x *HandleMifapayNotifyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HandleMifapayNotifyResponse.ProtoReflect.Descriptor instead. func (*HandleMifapayNotifyResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{127} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{128} } func (x *HandleMifapayNotifyResponse) GetAccepted() bool { @@ -11635,7 +11703,7 @@ type DiamondExchangeRule struct { func (x *DiamondExchangeRule) Reset() { *x = DiamondExchangeRule{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11647,7 +11715,7 @@ func (x *DiamondExchangeRule) String() string { func (*DiamondExchangeRule) ProtoMessage() {} func (x *DiamondExchangeRule) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[128] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11660,7 +11728,7 @@ func (x *DiamondExchangeRule) ProtoReflect() protoreflect.Message { // Deprecated: Use DiamondExchangeRule.ProtoReflect.Descriptor instead. func (*DiamondExchangeRule) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{128} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{129} } func (x *DiamondExchangeRule) GetExchangeType() string { @@ -11716,7 +11784,7 @@ type GetDiamondExchangeConfigRequest struct { func (x *GetDiamondExchangeConfigRequest) Reset() { *x = GetDiamondExchangeConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11728,7 +11796,7 @@ func (x *GetDiamondExchangeConfigRequest) String() string { func (*GetDiamondExchangeConfigRequest) ProtoMessage() {} func (x *GetDiamondExchangeConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[129] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11741,7 +11809,7 @@ func (x *GetDiamondExchangeConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiamondExchangeConfigRequest.ProtoReflect.Descriptor instead. func (*GetDiamondExchangeConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{129} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{130} } func (x *GetDiamondExchangeConfigRequest) GetRequestId() string { @@ -11774,7 +11842,7 @@ type GetDiamondExchangeConfigResponse struct { func (x *GetDiamondExchangeConfigResponse) Reset() { *x = GetDiamondExchangeConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11786,7 +11854,7 @@ func (x *GetDiamondExchangeConfigResponse) String() string { func (*GetDiamondExchangeConfigResponse) ProtoMessage() {} func (x *GetDiamondExchangeConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[130] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11799,7 +11867,7 @@ func (x *GetDiamondExchangeConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDiamondExchangeConfigResponse.ProtoReflect.Descriptor instead. func (*GetDiamondExchangeConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{130} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{131} } func (x *GetDiamondExchangeConfigResponse) GetRules() []*DiamondExchangeRule { @@ -11829,7 +11897,7 @@ type WalletTransaction struct { func (x *WalletTransaction) Reset() { *x = WalletTransaction{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11841,7 +11909,7 @@ func (x *WalletTransaction) String() string { func (*WalletTransaction) ProtoMessage() {} func (x *WalletTransaction) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[131] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11854,7 +11922,7 @@ func (x *WalletTransaction) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletTransaction.ProtoReflect.Descriptor instead. func (*WalletTransaction) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{131} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{132} } func (x *WalletTransaction) GetEntryId() int64 { @@ -11948,7 +12016,7 @@ type ListWalletTransactionsRequest struct { func (x *ListWalletTransactionsRequest) Reset() { *x = ListWalletTransactionsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11960,7 +12028,7 @@ func (x *ListWalletTransactionsRequest) String() string { func (*ListWalletTransactionsRequest) ProtoMessage() {} func (x *ListWalletTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[132] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11973,7 +12041,7 @@ func (x *ListWalletTransactionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWalletTransactionsRequest.ProtoReflect.Descriptor instead. func (*ListWalletTransactionsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{132} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{133} } func (x *ListWalletTransactionsRequest) GetRequestId() string { @@ -12028,7 +12096,7 @@ type ListWalletTransactionsResponse struct { func (x *ListWalletTransactionsResponse) Reset() { *x = ListWalletTransactionsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12040,7 +12108,7 @@ func (x *ListWalletTransactionsResponse) String() string { func (*ListWalletTransactionsResponse) ProtoMessage() {} func (x *ListWalletTransactionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[133] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12053,7 +12121,7 @@ func (x *ListWalletTransactionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWalletTransactionsResponse.ProtoReflect.Descriptor instead. func (*ListWalletTransactionsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{133} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{134} } func (x *ListWalletTransactionsResponse) GetTransactions() []*WalletTransaction { @@ -12087,7 +12155,7 @@ type VipRewardItem struct { func (x *VipRewardItem) Reset() { *x = VipRewardItem{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12099,7 +12167,7 @@ func (x *VipRewardItem) String() string { func (*VipRewardItem) ProtoMessage() {} func (x *VipRewardItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[134] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12112,7 +12180,7 @@ func (x *VipRewardItem) ProtoReflect() protoreflect.Message { // Deprecated: Use VipRewardItem.ProtoReflect.Descriptor instead. func (*VipRewardItem) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{134} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{135} } func (x *VipRewardItem) GetResourceId() int64 { @@ -12201,7 +12269,7 @@ type VipLevel struct { func (x *VipLevel) Reset() { *x = VipLevel{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12213,7 +12281,7 @@ func (x *VipLevel) String() string { func (*VipLevel) ProtoMessage() {} func (x *VipLevel) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[135] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12226,7 +12294,7 @@ func (x *VipLevel) ProtoReflect() protoreflect.Message { // Deprecated: Use VipLevel.ProtoReflect.Descriptor instead. func (*VipLevel) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{135} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{136} } func (x *VipLevel) GetLevel() int32 { @@ -12349,7 +12417,7 @@ type UserVip struct { func (x *UserVip) Reset() { *x = UserVip{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12361,7 +12429,7 @@ func (x *UserVip) String() string { func (*UserVip) ProtoMessage() {} func (x *UserVip) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[136] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12374,7 +12442,7 @@ func (x *UserVip) ProtoReflect() protoreflect.Message { // Deprecated: Use UserVip.ProtoReflect.Descriptor instead. func (*UserVip) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{136} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{137} } func (x *UserVip) GetUserId() int64 { @@ -12437,7 +12505,7 @@ type ListVipPackagesRequest struct { func (x *ListVipPackagesRequest) Reset() { *x = ListVipPackagesRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12449,7 +12517,7 @@ func (x *ListVipPackagesRequest) String() string { func (*ListVipPackagesRequest) ProtoMessage() {} func (x *ListVipPackagesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12462,7 +12530,7 @@ func (x *ListVipPackagesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVipPackagesRequest.ProtoReflect.Descriptor instead. func (*ListVipPackagesRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{137} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{138} } func (x *ListVipPackagesRequest) GetRequestId() string { @@ -12496,7 +12564,7 @@ type ListVipPackagesResponse struct { func (x *ListVipPackagesResponse) Reset() { *x = ListVipPackagesResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12508,7 +12576,7 @@ func (x *ListVipPackagesResponse) String() string { func (*ListVipPackagesResponse) ProtoMessage() {} func (x *ListVipPackagesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12521,7 +12589,7 @@ func (x *ListVipPackagesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVipPackagesResponse.ProtoReflect.Descriptor instead. func (*ListVipPackagesResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{138} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{139} } func (x *ListVipPackagesResponse) GetCurrentVip() *UserVip { @@ -12549,7 +12617,7 @@ type GetMyVipRequest struct { func (x *GetMyVipRequest) Reset() { *x = GetMyVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12561,7 +12629,7 @@ func (x *GetMyVipRequest) String() string { func (*GetMyVipRequest) ProtoMessage() {} func (x *GetMyVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[139] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12574,7 +12642,7 @@ func (x *GetMyVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyVipRequest.ProtoReflect.Descriptor instead. func (*GetMyVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{139} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{140} } func (x *GetMyVipRequest) GetRequestId() string { @@ -12607,7 +12675,7 @@ type GetMyVipResponse struct { func (x *GetMyVipResponse) Reset() { *x = GetMyVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12619,7 +12687,7 @@ func (x *GetMyVipResponse) String() string { func (*GetMyVipResponse) ProtoMessage() {} func (x *GetMyVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[140] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12632,7 +12700,7 @@ func (x *GetMyVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyVipResponse.ProtoReflect.Descriptor instead. func (*GetMyVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{140} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{141} } func (x *GetMyVipResponse) GetVip() *UserVip { @@ -12654,7 +12722,7 @@ type PurchaseVipRequest struct { func (x *PurchaseVipRequest) Reset() { *x = PurchaseVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12666,7 +12734,7 @@ func (x *PurchaseVipRequest) String() string { func (*PurchaseVipRequest) ProtoMessage() {} func (x *PurchaseVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[141] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12679,7 +12747,7 @@ func (x *PurchaseVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PurchaseVipRequest.ProtoReflect.Descriptor instead. func (*PurchaseVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{141} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{142} } func (x *PurchaseVipRequest) GetCommandId() string { @@ -12724,7 +12792,7 @@ type PurchaseVipResponse struct { func (x *PurchaseVipResponse) Reset() { *x = PurchaseVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12736,7 +12804,7 @@ func (x *PurchaseVipResponse) String() string { func (*PurchaseVipResponse) ProtoMessage() {} func (x *PurchaseVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[142] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12749,7 +12817,7 @@ func (x *PurchaseVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PurchaseVipResponse.ProtoReflect.Descriptor instead. func (*PurchaseVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{142} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{143} } func (x *PurchaseVipResponse) GetOrderId() string { @@ -12808,7 +12876,7 @@ type DebitCPBreakupFeeRequest struct { func (x *DebitCPBreakupFeeRequest) Reset() { *x = DebitCPBreakupFeeRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12820,7 +12888,7 @@ func (x *DebitCPBreakupFeeRequest) String() string { func (*DebitCPBreakupFeeRequest) ProtoMessage() {} func (x *DebitCPBreakupFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[143] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12833,7 +12901,7 @@ func (x *DebitCPBreakupFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DebitCPBreakupFeeRequest.ProtoReflect.Descriptor instead. func (*DebitCPBreakupFeeRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{143} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{144} } func (x *DebitCPBreakupFeeRequest) GetCommandId() string { @@ -12890,7 +12958,7 @@ type DebitCPBreakupFeeResponse struct { func (x *DebitCPBreakupFeeResponse) Reset() { *x = DebitCPBreakupFeeResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12902,7 +12970,7 @@ func (x *DebitCPBreakupFeeResponse) String() string { func (*DebitCPBreakupFeeResponse) ProtoMessage() {} func (x *DebitCPBreakupFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[144] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12915,7 +12983,7 @@ func (x *DebitCPBreakupFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DebitCPBreakupFeeResponse.ProtoReflect.Descriptor instead. func (*DebitCPBreakupFeeResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{144} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{145} } func (x *DebitCPBreakupFeeResponse) GetTransactionId() string { @@ -12961,7 +13029,7 @@ type GrantVipRequest struct { func (x *GrantVipRequest) Reset() { *x = GrantVipRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12973,7 +13041,7 @@ func (x *GrantVipRequest) String() string { func (*GrantVipRequest) ProtoMessage() {} func (x *GrantVipRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[145] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12986,7 +13054,7 @@ func (x *GrantVipRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantVipRequest.ProtoReflect.Descriptor instead. func (*GrantVipRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{145} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{146} } func (x *GrantVipRequest) GetCommandId() string { @@ -13050,7 +13118,7 @@ type GrantVipResponse struct { func (x *GrantVipResponse) Reset() { *x = GrantVipResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13062,7 +13130,7 @@ func (x *GrantVipResponse) String() string { func (*GrantVipResponse) ProtoMessage() {} func (x *GrantVipResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[146] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13075,7 +13143,7 @@ func (x *GrantVipResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GrantVipResponse.ProtoReflect.Descriptor instead. func (*GrantVipResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{146} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{147} } func (x *GrantVipResponse) GetTransactionId() string { @@ -13122,7 +13190,7 @@ type AdminVipLevelInput struct { func (x *AdminVipLevelInput) Reset() { *x = AdminVipLevelInput{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13134,7 +13202,7 @@ func (x *AdminVipLevelInput) String() string { func (*AdminVipLevelInput) ProtoMessage() {} func (x *AdminVipLevelInput) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[147] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13147,7 +13215,7 @@ func (x *AdminVipLevelInput) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminVipLevelInput.ProtoReflect.Descriptor instead. func (*AdminVipLevelInput) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{147} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{148} } func (x *AdminVipLevelInput) GetLevel() int32 { @@ -13216,7 +13284,7 @@ type ListAdminVipLevelsRequest struct { func (x *ListAdminVipLevelsRequest) Reset() { *x = ListAdminVipLevelsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13228,7 +13296,7 @@ func (x *ListAdminVipLevelsRequest) String() string { func (*ListAdminVipLevelsRequest) ProtoMessage() {} func (x *ListAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[148] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13241,7 +13309,7 @@ func (x *ListAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAdminVipLevelsRequest.ProtoReflect.Descriptor instead. func (*ListAdminVipLevelsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{148} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{149} } func (x *ListAdminVipLevelsRequest) GetRequestId() string { @@ -13268,7 +13336,7 @@ type ListAdminVipLevelsResponse struct { func (x *ListAdminVipLevelsResponse) Reset() { *x = ListAdminVipLevelsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13280,7 +13348,7 @@ func (x *ListAdminVipLevelsResponse) String() string { func (*ListAdminVipLevelsResponse) ProtoMessage() {} func (x *ListAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[149] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13293,7 +13361,7 @@ func (x *ListAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAdminVipLevelsResponse.ProtoReflect.Descriptor instead. func (*ListAdminVipLevelsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{149} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{150} } func (x *ListAdminVipLevelsResponse) GetLevels() []*VipLevel { @@ -13322,7 +13390,7 @@ type UpdateAdminVipLevelsRequest struct { func (x *UpdateAdminVipLevelsRequest) Reset() { *x = UpdateAdminVipLevelsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13334,7 +13402,7 @@ func (x *UpdateAdminVipLevelsRequest) String() string { func (*UpdateAdminVipLevelsRequest) ProtoMessage() {} func (x *UpdateAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[150] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13347,7 +13415,7 @@ func (x *UpdateAdminVipLevelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateAdminVipLevelsRequest.ProtoReflect.Descriptor instead. func (*UpdateAdminVipLevelsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{150} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{151} } func (x *UpdateAdminVipLevelsRequest) GetRequestId() string { @@ -13388,7 +13456,7 @@ type UpdateAdminVipLevelsResponse struct { func (x *UpdateAdminVipLevelsResponse) Reset() { *x = UpdateAdminVipLevelsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13400,7 +13468,7 @@ func (x *UpdateAdminVipLevelsResponse) String() string { func (*UpdateAdminVipLevelsResponse) ProtoMessage() {} func (x *UpdateAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[151] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13413,7 +13481,7 @@ func (x *UpdateAdminVipLevelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateAdminVipLevelsResponse.ProtoReflect.Descriptor instead. func (*UpdateAdminVipLevelsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{151} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{152} } func (x *UpdateAdminVipLevelsResponse) GetLevels() []*VipLevel { @@ -13447,7 +13515,7 @@ type CreditTaskRewardRequest struct { func (x *CreditTaskRewardRequest) Reset() { *x = CreditTaskRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13459,7 +13527,7 @@ func (x *CreditTaskRewardRequest) String() string { func (*CreditTaskRewardRequest) ProtoMessage() {} func (x *CreditTaskRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[152] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13472,7 +13540,7 @@ func (x *CreditTaskRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditTaskRewardRequest.ProtoReflect.Descriptor instead. func (*CreditTaskRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{152} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{153} } func (x *CreditTaskRewardRequest) GetCommandId() string { @@ -13544,7 +13612,7 @@ type CreditTaskRewardResponse struct { func (x *CreditTaskRewardResponse) Reset() { *x = CreditTaskRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13556,7 +13624,7 @@ func (x *CreditTaskRewardResponse) String() string { func (*CreditTaskRewardResponse) ProtoMessage() {} func (x *CreditTaskRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[153] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13569,7 +13637,7 @@ func (x *CreditTaskRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditTaskRewardResponse.ProtoReflect.Descriptor instead. func (*CreditTaskRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{153} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{154} } func (x *CreditTaskRewardResponse) GetTransactionId() string { @@ -13619,7 +13687,7 @@ type CreditLuckyGiftRewardRequest struct { func (x *CreditLuckyGiftRewardRequest) Reset() { *x = CreditLuckyGiftRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13631,7 +13699,7 @@ func (x *CreditLuckyGiftRewardRequest) String() string { func (*CreditLuckyGiftRewardRequest) ProtoMessage() {} func (x *CreditLuckyGiftRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[154] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13644,7 +13712,7 @@ func (x *CreditLuckyGiftRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditLuckyGiftRewardRequest.ProtoReflect.Descriptor instead. func (*CreditLuckyGiftRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{154} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{155} } func (x *CreditLuckyGiftRewardRequest) GetCommandId() string { @@ -13730,7 +13798,7 @@ type CreditLuckyGiftRewardResponse struct { func (x *CreditLuckyGiftRewardResponse) Reset() { *x = CreditLuckyGiftRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13742,7 +13810,7 @@ func (x *CreditLuckyGiftRewardResponse) String() string { func (*CreditLuckyGiftRewardResponse) ProtoMessage() {} func (x *CreditLuckyGiftRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[155] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13755,7 +13823,7 @@ func (x *CreditLuckyGiftRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditLuckyGiftRewardResponse.ProtoReflect.Descriptor instead. func (*CreditLuckyGiftRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{155} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{156} } func (x *CreditLuckyGiftRewardResponse) GetTransactionId() string { @@ -13807,7 +13875,7 @@ type CreditRoomTurnoverRewardRequest struct { func (x *CreditRoomTurnoverRewardRequest) Reset() { *x = CreditRoomTurnoverRewardRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13819,7 +13887,7 @@ func (x *CreditRoomTurnoverRewardRequest) String() string { func (*CreditRoomTurnoverRewardRequest) ProtoMessage() {} func (x *CreditRoomTurnoverRewardRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[156] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13832,7 +13900,7 @@ func (x *CreditRoomTurnoverRewardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditRoomTurnoverRewardRequest.ProtoReflect.Descriptor instead. func (*CreditRoomTurnoverRewardRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{156} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{157} } func (x *CreditRoomTurnoverRewardRequest) GetCommandId() string { @@ -13932,7 +14000,7 @@ type CreditRoomTurnoverRewardResponse struct { func (x *CreditRoomTurnoverRewardResponse) Reset() { *x = CreditRoomTurnoverRewardResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13944,7 +14012,7 @@ func (x *CreditRoomTurnoverRewardResponse) String() string { func (*CreditRoomTurnoverRewardResponse) ProtoMessage() {} func (x *CreditRoomTurnoverRewardResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[157] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13957,7 +14025,7 @@ func (x *CreditRoomTurnoverRewardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreditRoomTurnoverRewardResponse.ProtoReflect.Descriptor instead. func (*CreditRoomTurnoverRewardResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{157} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{158} } func (x *CreditRoomTurnoverRewardResponse) GetTransactionId() string { @@ -13988,6 +14056,200 @@ func (x *CreditRoomTurnoverRewardResponse) GetGrantedAtMs() int64 { return 0 } +// CreditInviteActivityRewardRequest 是 activity-service 邀请活动达标后发起的 COIN 奖励入账命令。 +type CreditInviteActivityRewardRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + Amount int64 `protobuf:"varint,4,opt,name=amount,proto3" json:"amount,omitempty"` + ClaimId string `protobuf:"bytes,5,opt,name=claim_id,json=claimId,proto3" json:"claim_id,omitempty"` + RewardType string `protobuf:"bytes,6,opt,name=reward_type,json=rewardType,proto3" json:"reward_type,omitempty"` + TierId int64 `protobuf:"varint,7,opt,name=tier_id,json=tierId,proto3" json:"tier_id,omitempty"` + TierCode string `protobuf:"bytes,8,opt,name=tier_code,json=tierCode,proto3" json:"tier_code,omitempty"` + CycleKey string `protobuf:"bytes,9,opt,name=cycle_key,json=cycleKey,proto3" json:"cycle_key,omitempty"` + ReachedValue int64 `protobuf:"varint,10,opt,name=reached_value,json=reachedValue,proto3" json:"reached_value,omitempty"` + Reason string `protobuf:"bytes,11,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreditInviteActivityRewardRequest) Reset() { + *x = CreditInviteActivityRewardRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreditInviteActivityRewardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreditInviteActivityRewardRequest) ProtoMessage() {} + +func (x *CreditInviteActivityRewardRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreditInviteActivityRewardRequest.ProtoReflect.Descriptor instead. +func (*CreditInviteActivityRewardRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{159} +} + +func (x *CreditInviteActivityRewardRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *CreditInviteActivityRewardRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *CreditInviteActivityRewardRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *CreditInviteActivityRewardRequest) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *CreditInviteActivityRewardRequest) GetClaimId() string { + if x != nil { + return x.ClaimId + } + return "" +} + +func (x *CreditInviteActivityRewardRequest) GetRewardType() string { + if x != nil { + return x.RewardType + } + return "" +} + +func (x *CreditInviteActivityRewardRequest) GetTierId() int64 { + if x != nil { + return x.TierId + } + return 0 +} + +func (x *CreditInviteActivityRewardRequest) GetTierCode() string { + if x != nil { + return x.TierCode + } + return "" +} + +func (x *CreditInviteActivityRewardRequest) GetCycleKey() string { + if x != nil { + return x.CycleKey + } + return "" +} + +func (x *CreditInviteActivityRewardRequest) GetReachedValue() int64 { + if x != nil { + return x.ReachedValue + } + return 0 +} + +func (x *CreditInviteActivityRewardRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +// CreditInviteActivityRewardResponse 返回邀请活动奖励入账流水和用户 COIN 账后余额。 +type CreditInviteActivityRewardResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + Balance *AssetBalance `protobuf:"bytes,2,opt,name=balance,proto3" json:"balance,omitempty"` + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + GrantedAtMs int64 `protobuf:"varint,4,opt,name=granted_at_ms,json=grantedAtMs,proto3" json:"granted_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreditInviteActivityRewardResponse) Reset() { + *x = CreditInviteActivityRewardResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreditInviteActivityRewardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreditInviteActivityRewardResponse) ProtoMessage() {} + +func (x *CreditInviteActivityRewardResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreditInviteActivityRewardResponse.ProtoReflect.Descriptor instead. +func (*CreditInviteActivityRewardResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{160} +} + +func (x *CreditInviteActivityRewardResponse) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +func (x *CreditInviteActivityRewardResponse) GetBalance() *AssetBalance { + if x != nil { + return x.Balance + } + return nil +} + +func (x *CreditInviteActivityRewardResponse) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *CreditInviteActivityRewardResponse) GetGrantedAtMs() int64 { + if x != nil { + return x.GrantedAtMs + } + return 0 +} + // ApplyGameCoinChangeRequest 是 game-service 唯一可以调用的钱包游戏改账入口。 type ApplyGameCoinChangeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -14009,7 +14271,7 @@ type ApplyGameCoinChangeRequest struct { func (x *ApplyGameCoinChangeRequest) Reset() { *x = ApplyGameCoinChangeRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14021,7 +14283,7 @@ func (x *ApplyGameCoinChangeRequest) String() string { func (*ApplyGameCoinChangeRequest) ProtoMessage() {} func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[158] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14034,7 +14296,7 @@ func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyGameCoinChangeRequest.ProtoReflect.Descriptor instead. func (*ApplyGameCoinChangeRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{158} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{161} } func (x *ApplyGameCoinChangeRequest) GetRequestId() string { @@ -14133,7 +14395,7 @@ type ApplyGameCoinChangeResponse struct { func (x *ApplyGameCoinChangeResponse) Reset() { *x = ApplyGameCoinChangeResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14145,7 +14407,7 @@ func (x *ApplyGameCoinChangeResponse) String() string { func (*ApplyGameCoinChangeResponse) ProtoMessage() {} func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[159] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14158,7 +14420,7 @@ func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyGameCoinChangeResponse.ProtoReflect.Descriptor instead. func (*ApplyGameCoinChangeResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{159} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{162} } func (x *ApplyGameCoinChangeResponse) GetWalletTransactionId() string { @@ -14201,7 +14463,7 @@ type RedPacketConfig struct { func (x *RedPacketConfig) Reset() { *x = RedPacketConfig{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14213,7 +14475,7 @@ func (x *RedPacketConfig) String() string { func (*RedPacketConfig) ProtoMessage() {} func (x *RedPacketConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[160] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14226,7 +14488,7 @@ func (x *RedPacketConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacketConfig.ProtoReflect.Descriptor instead. func (*RedPacketConfig) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{160} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{163} } func (x *RedPacketConfig) GetAppCode() string { @@ -14325,7 +14587,7 @@ type RedPacketClaim struct { func (x *RedPacketClaim) Reset() { *x = RedPacketClaim{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14337,7 +14599,7 @@ func (x *RedPacketClaim) String() string { func (*RedPacketClaim) ProtoMessage() {} func (x *RedPacketClaim) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[161] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14350,7 +14612,7 @@ func (x *RedPacketClaim) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacketClaim.ProtoReflect.Descriptor instead. func (*RedPacketClaim) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{161} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{164} } func (x *RedPacketClaim) GetAppCode() string { @@ -14460,7 +14722,7 @@ type RedPacket struct { func (x *RedPacket) Reset() { *x = RedPacket{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14472,7 +14734,7 @@ func (x *RedPacket) String() string { func (*RedPacket) ProtoMessage() {} func (x *RedPacket) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[162] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14485,7 +14747,7 @@ func (x *RedPacket) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacket.ProtoReflect.Descriptor instead. func (*RedPacket) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{162} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{165} } func (x *RedPacket) GetAppCode() string { @@ -14652,7 +14914,7 @@ type GetRedPacketConfigRequest struct { func (x *GetRedPacketConfigRequest) Reset() { *x = GetRedPacketConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14664,7 +14926,7 @@ func (x *GetRedPacketConfigRequest) String() string { func (*GetRedPacketConfigRequest) ProtoMessage() {} func (x *GetRedPacketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[163] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14677,7 +14939,7 @@ func (x *GetRedPacketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketConfigRequest.ProtoReflect.Descriptor instead. func (*GetRedPacketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{163} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{166} } func (x *GetRedPacketConfigRequest) GetRequestId() string { @@ -14704,7 +14966,7 @@ type GetRedPacketConfigResponse struct { func (x *GetRedPacketConfigResponse) Reset() { *x = GetRedPacketConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14716,7 +14978,7 @@ func (x *GetRedPacketConfigResponse) String() string { func (*GetRedPacketConfigResponse) ProtoMessage() {} func (x *GetRedPacketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[164] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14729,7 +14991,7 @@ func (x *GetRedPacketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketConfigResponse.ProtoReflect.Descriptor instead. func (*GetRedPacketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{164} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{167} } func (x *GetRedPacketConfigResponse) GetConfig() *RedPacketConfig { @@ -14764,7 +15026,7 @@ type UpdateRedPacketConfigRequest struct { func (x *UpdateRedPacketConfigRequest) Reset() { *x = UpdateRedPacketConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14776,7 +15038,7 @@ func (x *UpdateRedPacketConfigRequest) String() string { func (*UpdateRedPacketConfigRequest) ProtoMessage() {} func (x *UpdateRedPacketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[165] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14789,7 +15051,7 @@ func (x *UpdateRedPacketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRedPacketConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateRedPacketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{165} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{168} } func (x *UpdateRedPacketConfigRequest) GetRequestId() string { @@ -14872,7 +15134,7 @@ type UpdateRedPacketConfigResponse struct { func (x *UpdateRedPacketConfigResponse) Reset() { *x = UpdateRedPacketConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14884,7 +15146,7 @@ func (x *UpdateRedPacketConfigResponse) String() string { func (*UpdateRedPacketConfigResponse) ProtoMessage() {} func (x *UpdateRedPacketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[166] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14897,7 +15159,7 @@ func (x *UpdateRedPacketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRedPacketConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateRedPacketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{166} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{169} } func (x *UpdateRedPacketConfigResponse) GetConfig() *RedPacketConfig { @@ -14931,7 +15193,7 @@ type CreateRedPacketRequest struct { func (x *CreateRedPacketRequest) Reset() { *x = CreateRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14943,7 +15205,7 @@ func (x *CreateRedPacketRequest) String() string { func (*CreateRedPacketRequest) ProtoMessage() {} func (x *CreateRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[167] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14956,7 +15218,7 @@ func (x *CreateRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRedPacketRequest.ProtoReflect.Descriptor instead. func (*CreateRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{167} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{170} } func (x *CreateRedPacketRequest) GetRequestId() string { @@ -15033,7 +15295,7 @@ type CreateRedPacketResponse struct { func (x *CreateRedPacketResponse) Reset() { *x = CreateRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15045,7 +15307,7 @@ func (x *CreateRedPacketResponse) String() string { func (*CreateRedPacketResponse) ProtoMessage() {} func (x *CreateRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[168] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15058,7 +15320,7 @@ func (x *CreateRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRedPacketResponse.ProtoReflect.Descriptor instead. func (*CreateRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{168} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{171} } func (x *CreateRedPacketResponse) GetPacket() *RedPacket { @@ -15095,7 +15357,7 @@ type ClaimRedPacketRequest struct { func (x *ClaimRedPacketRequest) Reset() { *x = ClaimRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15107,7 +15369,7 @@ func (x *ClaimRedPacketRequest) String() string { func (*ClaimRedPacketRequest) ProtoMessage() {} func (x *ClaimRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[169] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15120,7 +15382,7 @@ func (x *ClaimRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimRedPacketRequest.ProtoReflect.Descriptor instead. func (*ClaimRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{169} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{172} } func (x *ClaimRedPacketRequest) GetRequestId() string { @@ -15170,7 +15432,7 @@ type ClaimRedPacketResponse struct { func (x *ClaimRedPacketResponse) Reset() { *x = ClaimRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15182,7 +15444,7 @@ func (x *ClaimRedPacketResponse) String() string { func (*ClaimRedPacketResponse) ProtoMessage() {} func (x *ClaimRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[170] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15195,7 +15457,7 @@ func (x *ClaimRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimRedPacketResponse.ProtoReflect.Descriptor instead. func (*ClaimRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{170} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{173} } func (x *ClaimRedPacketResponse) GetClaim() *RedPacketClaim { @@ -15246,7 +15508,7 @@ type ListRedPacketsRequest struct { func (x *ListRedPacketsRequest) Reset() { *x = ListRedPacketsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15258,7 +15520,7 @@ func (x *ListRedPacketsRequest) String() string { func (*ListRedPacketsRequest) ProtoMessage() {} func (x *ListRedPacketsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[171] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15271,7 +15533,7 @@ func (x *ListRedPacketsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRedPacketsRequest.ProtoReflect.Descriptor instead. func (*ListRedPacketsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{171} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{174} } func (x *ListRedPacketsRequest) GetRequestId() string { @@ -15369,7 +15631,7 @@ type ListRedPacketsResponse struct { func (x *ListRedPacketsResponse) Reset() { *x = ListRedPacketsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15381,7 +15643,7 @@ func (x *ListRedPacketsResponse) String() string { func (*ListRedPacketsResponse) ProtoMessage() {} func (x *ListRedPacketsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[172] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15394,7 +15656,7 @@ func (x *ListRedPacketsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRedPacketsResponse.ProtoReflect.Descriptor instead. func (*ListRedPacketsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{172} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{175} } func (x *ListRedPacketsResponse) GetPackets() []*RedPacket { @@ -15431,7 +15693,7 @@ type GetRedPacketRequest struct { func (x *GetRedPacketRequest) Reset() { *x = GetRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15443,7 +15705,7 @@ func (x *GetRedPacketRequest) String() string { func (*GetRedPacketRequest) ProtoMessage() {} func (x *GetRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[173] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15456,7 +15718,7 @@ func (x *GetRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketRequest.ProtoReflect.Descriptor instead. func (*GetRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{173} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{176} } func (x *GetRedPacketRequest) GetRequestId() string { @@ -15504,7 +15766,7 @@ type GetRedPacketResponse struct { func (x *GetRedPacketResponse) Reset() { *x = GetRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15516,7 +15778,7 @@ func (x *GetRedPacketResponse) String() string { func (*GetRedPacketResponse) ProtoMessage() {} func (x *GetRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[174] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15529,7 +15791,7 @@ func (x *GetRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketResponse.ProtoReflect.Descriptor instead. func (*GetRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{174} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{177} } func (x *GetRedPacketResponse) GetPacket() *RedPacket { @@ -15557,7 +15819,7 @@ type ExpireRedPacketsRequest struct { func (x *ExpireRedPacketsRequest) Reset() { *x = ExpireRedPacketsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15569,7 +15831,7 @@ func (x *ExpireRedPacketsRequest) String() string { func (*ExpireRedPacketsRequest) ProtoMessage() {} func (x *ExpireRedPacketsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[175] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15582,7 +15844,7 @@ func (x *ExpireRedPacketsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRedPacketsRequest.ProtoReflect.Descriptor instead. func (*ExpireRedPacketsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{175} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{178} } func (x *ExpireRedPacketsRequest) GetRequestId() string { @@ -15617,7 +15879,7 @@ type ExpireRedPacketsResponse struct { func (x *ExpireRedPacketsResponse) Reset() { *x = ExpireRedPacketsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15629,7 +15891,7 @@ func (x *ExpireRedPacketsResponse) String() string { func (*ExpireRedPacketsResponse) ProtoMessage() {} func (x *ExpireRedPacketsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[176] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15642,7 +15904,7 @@ func (x *ExpireRedPacketsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRedPacketsResponse.ProtoReflect.Descriptor instead. func (*ExpireRedPacketsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{176} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{179} } func (x *ExpireRedPacketsResponse) GetExpiredCount() int32 { @@ -15678,7 +15940,7 @@ type RetryRedPacketRefundRequest struct { func (x *RetryRedPacketRefundRequest) Reset() { *x = RetryRedPacketRefundRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15690,7 +15952,7 @@ func (x *RetryRedPacketRefundRequest) String() string { func (*RetryRedPacketRefundRequest) ProtoMessage() {} func (x *RetryRedPacketRefundRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[177] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15703,7 +15965,7 @@ func (x *RetryRedPacketRefundRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryRedPacketRefundRequest.ProtoReflect.Descriptor instead. func (*RetryRedPacketRefundRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{177} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{180} } func (x *RetryRedPacketRefundRequest) GetRequestId() string { @@ -15745,7 +16007,7 @@ type RetryRedPacketRefundResponse struct { func (x *RetryRedPacketRefundResponse) Reset() { *x = RetryRedPacketRefundResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15757,7 +16019,7 @@ func (x *RetryRedPacketRefundResponse) String() string { func (*RetryRedPacketRefundResponse) ProtoMessage() {} func (x *RetryRedPacketRefundResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[178] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15770,7 +16032,7 @@ func (x *RetryRedPacketRefundResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryRedPacketRefundResponse.ProtoReflect.Descriptor instead. func (*RetryRedPacketRefundResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{178} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{181} } func (x *RetryRedPacketRefundResponse) GetPacket() *RedPacket { @@ -15809,7 +16071,7 @@ type CronBatchRequest struct { func (x *CronBatchRequest) Reset() { *x = CronBatchRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15821,7 +16083,7 @@ func (x *CronBatchRequest) String() string { func (*CronBatchRequest) ProtoMessage() {} func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[179] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[182] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15834,7 +16096,7 @@ func (x *CronBatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchRequest.ProtoReflect.Descriptor instead. func (*CronBatchRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{179} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{182} } func (x *CronBatchRequest) GetRequestId() string { @@ -15893,7 +16155,7 @@ type CronBatchResponse struct { func (x *CronBatchResponse) Reset() { *x = CronBatchResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15905,7 +16167,7 @@ func (x *CronBatchResponse) String() string { func (*CronBatchResponse) ProtoMessage() {} func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[180] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[183] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15918,7 +16180,7 @@ func (x *CronBatchResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CronBatchResponse.ProtoReflect.Descriptor instead. func (*CronBatchResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{180} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{183} } func (x *CronBatchResponse) GetClaimedCount() int32 { @@ -16663,7 +16925,13 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x17\n" + "\agift_id\x18\x03 \x01(\tR\x06giftId\x12\x16\n" + "\x06status\x18\x04 \x01(\tR\x06status\x12(\n" + - "\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\"E\n" + + "\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\"\x96\x01\n" + + "\x17DeleteGiftConfigRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x17\n" + + "\agift_id\x18\x03 \x01(\tR\x06giftId\x12(\n" + + "\x10operator_user_id\x18\x04 \x01(\x03R\x0eoperatorUserId\"E\n" + "\x12GiftConfigResponse\x12/\n" + "\x04gift\x18\x01 \x01(\v2\x1b.hyapp.wallet.v1.GiftConfigR\x04gift\"\xb9\x02\n" + "\x14GrantResourceRequest\x12\x1d\n" + @@ -17395,6 +17663,26 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x0etransaction_id\x18\x01 \x01(\tR\rtransactionId\x127\n" + "\abalance\x18\x02 \x01(\v2\x1d.hyapp.wallet.v1.AssetBalanceR\abalance\x12\x16\n" + "\x06amount\x18\x03 \x01(\x03R\x06amount\x12\"\n" + + "\rgranted_at_ms\x18\x04 \x01(\x03R\vgrantedAtMs\"\xe7\x02\n" + + "!CreditInviteActivityRewardRequest\x12\x1d\n" + + "\n" + + "command_id\x18\x01 \x01(\tR\tcommandId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12$\n" + + "\x0etarget_user_id\x18\x03 \x01(\x03R\ftargetUserId\x12\x16\n" + + "\x06amount\x18\x04 \x01(\x03R\x06amount\x12\x19\n" + + "\bclaim_id\x18\x05 \x01(\tR\aclaimId\x12\x1f\n" + + "\vreward_type\x18\x06 \x01(\tR\n" + + "rewardType\x12\x17\n" + + "\atier_id\x18\a \x01(\x03R\x06tierId\x12\x1b\n" + + "\ttier_code\x18\b \x01(\tR\btierCode\x12\x1b\n" + + "\tcycle_key\x18\t \x01(\tR\bcycleKey\x12#\n" + + "\rreached_value\x18\n" + + " \x01(\x03R\freachedValue\x12\x16\n" + + "\x06reason\x18\v \x01(\tR\x06reason\"\xc0\x01\n" + + "\"CreditInviteActivityRewardResponse\x12%\n" + + "\x0etransaction_id\x18\x01 \x01(\tR\rtransactionId\x127\n" + + "\abalance\x18\x02 \x01(\v2\x1d.hyapp.wallet.v1.AssetBalanceR\abalance\x12\x16\n" + + "\x06amount\x18\x03 \x01(\x03R\x06amount\x12\"\n" + "\rgranted_at_ms\x18\x04 \x01(\x03R\vgrantedAtMs\"\x9a\x03\n" + "\x1aApplyGameCoinChangeRequest\x12\x1d\n" + "\n" + @@ -17594,7 +17882,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x11WalletCronService\x12n\n" + "%ProcessHostSalaryDailySettlementBatch\x12!.hyapp.wallet.v1.CronBatchRequest\x1a\".hyapp.wallet.v1.CronBatchResponse\x12r\n" + ")ProcessHostSalaryHalfMonthSettlementBatch\x12!.hyapp.wallet.v1.CronBatchRequest\x1a\".hyapp.wallet.v1.CronBatchResponse\x12g\n" + - "\x1eProcessHostSalaryMonthEndBatch\x12!.hyapp.wallet.v1.CronBatchRequest\x1a\".hyapp.wallet.v1.CronBatchResponse2\xcaC\n" + + "\x1eProcessHostSalaryMonthEndBatch\x12!.hyapp.wallet.v1.CronBatchRequest\x1a\".hyapp.wallet.v1.CronBatchResponse2\xb5E\n" + "\rWalletService\x12R\n" + "\tDebitGift\x12!.hyapp.wallet.v1.DebitGiftRequest\x1a\".hyapp.wallet.v1.DebitGiftResponse\x12a\n" + "\x0eBatchDebitGift\x12&.hyapp.wallet.v1.BatchDebitGiftRequest\x1a'.hyapp.wallet.v1.BatchDebitGiftResponse\x12X\n" + @@ -17621,7 +17909,8 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x13ListGiftTypeConfigs\x12+.hyapp.wallet.v1.ListGiftTypeConfigsRequest\x1a,.hyapp.wallet.v1.ListGiftTypeConfigsResponse\x12a\n" + "\x10CreateGiftConfig\x12(.hyapp.wallet.v1.CreateGiftConfigRequest\x1a#.hyapp.wallet.v1.GiftConfigResponse\x12a\n" + "\x10UpdateGiftConfig\x12(.hyapp.wallet.v1.UpdateGiftConfigRequest\x1a#.hyapp.wallet.v1.GiftConfigResponse\x12g\n" + - "\x13SetGiftConfigStatus\x12+.hyapp.wallet.v1.SetGiftConfigStatusRequest\x1a#.hyapp.wallet.v1.GiftConfigResponse\x12m\n" + + "\x13SetGiftConfigStatus\x12+.hyapp.wallet.v1.SetGiftConfigStatusRequest\x1a#.hyapp.wallet.v1.GiftConfigResponse\x12a\n" + + "\x10DeleteGiftConfig\x12(.hyapp.wallet.v1.DeleteGiftConfigRequest\x1a#.hyapp.wallet.v1.GiftConfigResponse\x12m\n" + "\x14UpsertGiftTypeConfig\x12,.hyapp.wallet.v1.UpsertGiftTypeConfigRequest\x1a'.hyapp.wallet.v1.GiftTypeConfigResponse\x12^\n" + "\rGrantResource\x12%.hyapp.wallet.v1.GrantResourceRequest\x1a&.hyapp.wallet.v1.ResourceGrantResponse\x12h\n" + "\x12GrantResourceGroup\x12*.hyapp.wallet.v1.GrantResourceGroupRequest\x1a&.hyapp.wallet.v1.ResourceGrantResponse\x12j\n" + @@ -17663,7 +17952,8 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x14UpdateAdminVipLevels\x12,.hyapp.wallet.v1.UpdateAdminVipLevelsRequest\x1a-.hyapp.wallet.v1.UpdateAdminVipLevelsResponse\x12g\n" + "\x10CreditTaskReward\x12(.hyapp.wallet.v1.CreditTaskRewardRequest\x1a).hyapp.wallet.v1.CreditTaskRewardResponse\x12v\n" + "\x15CreditLuckyGiftReward\x12-.hyapp.wallet.v1.CreditLuckyGiftRewardRequest\x1a..hyapp.wallet.v1.CreditLuckyGiftRewardResponse\x12\x7f\n" + - "\x18CreditRoomTurnoverReward\x120.hyapp.wallet.v1.CreditRoomTurnoverRewardRequest\x1a1.hyapp.wallet.v1.CreditRoomTurnoverRewardResponse\x12p\n" + + "\x18CreditRoomTurnoverReward\x120.hyapp.wallet.v1.CreditRoomTurnoverRewardRequest\x1a1.hyapp.wallet.v1.CreditRoomTurnoverRewardResponse\x12\x85\x01\n" + + "\x1aCreditInviteActivityReward\x122.hyapp.wallet.v1.CreditInviteActivityRewardRequest\x1a3.hyapp.wallet.v1.CreditInviteActivityRewardResponse\x12p\n" + "\x13ApplyGameCoinChange\x12+.hyapp.wallet.v1.ApplyGameCoinChangeRequest\x1a,.hyapp.wallet.v1.ApplyGameCoinChangeResponse\x12m\n" + "\x12GetRedPacketConfig\x12*.hyapp.wallet.v1.GetRedPacketConfigRequest\x1a+.hyapp.wallet.v1.GetRedPacketConfigResponse\x12v\n" + "\x15UpdateRedPacketConfig\x12-.hyapp.wallet.v1.UpdateRedPacketConfigRequest\x1a..hyapp.wallet.v1.UpdateRedPacketConfigResponse\x12d\n" + @@ -17686,7 +17976,7 @@ func file_proto_wallet_v1_wallet_proto_rawDescGZIP() []byte { return file_proto_wallet_v1_wallet_proto_rawDescData } -var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 181) +var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 184) var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*DebitGiftRequest)(nil), // 0: hyapp.wallet.v1.DebitGiftRequest (*DebitGiftResponse)(nil), // 1: hyapp.wallet.v1.DebitGiftResponse @@ -17752,123 +18042,126 @@ var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*CreateGiftConfigRequest)(nil), // 61: hyapp.wallet.v1.CreateGiftConfigRequest (*UpdateGiftConfigRequest)(nil), // 62: hyapp.wallet.v1.UpdateGiftConfigRequest (*SetGiftConfigStatusRequest)(nil), // 63: hyapp.wallet.v1.SetGiftConfigStatusRequest - (*GiftConfigResponse)(nil), // 64: hyapp.wallet.v1.GiftConfigResponse - (*GrantResourceRequest)(nil), // 65: hyapp.wallet.v1.GrantResourceRequest - (*GrantResourceGroupRequest)(nil), // 66: hyapp.wallet.v1.GrantResourceGroupRequest - (*ResourceGrantResponse)(nil), // 67: hyapp.wallet.v1.ResourceGrantResponse - (*ListUserResourcesRequest)(nil), // 68: hyapp.wallet.v1.ListUserResourcesRequest - (*ListUserResourcesResponse)(nil), // 69: hyapp.wallet.v1.ListUserResourcesResponse - (*EquipUserResourceRequest)(nil), // 70: hyapp.wallet.v1.EquipUserResourceRequest - (*EquipUserResourceResponse)(nil), // 71: hyapp.wallet.v1.EquipUserResourceResponse - (*UnequipUserResourceRequest)(nil), // 72: hyapp.wallet.v1.UnequipUserResourceRequest - (*UnequipUserResourceResponse)(nil), // 73: hyapp.wallet.v1.UnequipUserResourceResponse - (*BatchGetUserEquippedResourcesRequest)(nil), // 74: hyapp.wallet.v1.BatchGetUserEquippedResourcesRequest - (*UserEquippedResources)(nil), // 75: hyapp.wallet.v1.UserEquippedResources - (*BatchGetUserEquippedResourcesResponse)(nil), // 76: hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse - (*ListResourceGrantsRequest)(nil), // 77: hyapp.wallet.v1.ListResourceGrantsRequest - (*ListResourceGrantsResponse)(nil), // 78: hyapp.wallet.v1.ListResourceGrantsResponse - (*ResourceShopItemInput)(nil), // 79: hyapp.wallet.v1.ResourceShopItemInput - (*ListResourceShopItemsRequest)(nil), // 80: hyapp.wallet.v1.ListResourceShopItemsRequest - (*ListResourceShopItemsResponse)(nil), // 81: hyapp.wallet.v1.ListResourceShopItemsResponse - (*UpsertResourceShopItemsRequest)(nil), // 82: hyapp.wallet.v1.UpsertResourceShopItemsRequest - (*UpsertResourceShopItemsResponse)(nil), // 83: hyapp.wallet.v1.UpsertResourceShopItemsResponse - (*SetResourceShopItemStatusRequest)(nil), // 84: hyapp.wallet.v1.SetResourceShopItemStatusRequest - (*ResourceShopItemResponse)(nil), // 85: hyapp.wallet.v1.ResourceShopItemResponse - (*PurchaseResourceShopItemRequest)(nil), // 86: hyapp.wallet.v1.PurchaseResourceShopItemRequest - (*PurchaseResourceShopItemResponse)(nil), // 87: hyapp.wallet.v1.PurchaseResourceShopItemResponse - (*RechargeBill)(nil), // 88: hyapp.wallet.v1.RechargeBill - (*ListRechargeBillsRequest)(nil), // 89: hyapp.wallet.v1.ListRechargeBillsRequest - (*ListRechargeBillsResponse)(nil), // 90: hyapp.wallet.v1.ListRechargeBillsResponse - (*WalletFeatureFlags)(nil), // 91: hyapp.wallet.v1.WalletFeatureFlags - (*GetWalletOverviewRequest)(nil), // 92: hyapp.wallet.v1.GetWalletOverviewRequest - (*GetWalletOverviewResponse)(nil), // 93: hyapp.wallet.v1.GetWalletOverviewResponse - (*WalletValueSummary)(nil), // 94: hyapp.wallet.v1.WalletValueSummary - (*GetWalletValueSummaryRequest)(nil), // 95: hyapp.wallet.v1.GetWalletValueSummaryRequest - (*GetWalletValueSummaryResponse)(nil), // 96: hyapp.wallet.v1.GetWalletValueSummaryResponse - (*GiftWallItem)(nil), // 97: hyapp.wallet.v1.GiftWallItem - (*GetUserGiftWallRequest)(nil), // 98: hyapp.wallet.v1.GetUserGiftWallRequest - (*GetUserGiftWallResponse)(nil), // 99: hyapp.wallet.v1.GetUserGiftWallResponse - (*RechargeProduct)(nil), // 100: hyapp.wallet.v1.RechargeProduct - (*ListRechargeProductsRequest)(nil), // 101: hyapp.wallet.v1.ListRechargeProductsRequest - (*ListRechargeProductsResponse)(nil), // 102: hyapp.wallet.v1.ListRechargeProductsResponse - (*ConfirmGooglePaymentRequest)(nil), // 103: hyapp.wallet.v1.ConfirmGooglePaymentRequest - (*ConfirmGooglePaymentResponse)(nil), // 104: hyapp.wallet.v1.ConfirmGooglePaymentResponse - (*ListAdminRechargeProductsRequest)(nil), // 105: hyapp.wallet.v1.ListAdminRechargeProductsRequest - (*ListAdminRechargeProductsResponse)(nil), // 106: hyapp.wallet.v1.ListAdminRechargeProductsResponse - (*CreateRechargeProductRequest)(nil), // 107: hyapp.wallet.v1.CreateRechargeProductRequest - (*UpdateRechargeProductRequest)(nil), // 108: hyapp.wallet.v1.UpdateRechargeProductRequest - (*DeleteRechargeProductRequest)(nil), // 109: hyapp.wallet.v1.DeleteRechargeProductRequest - (*RechargeProductResponse)(nil), // 110: hyapp.wallet.v1.RechargeProductResponse - (*DeleteRechargeProductResponse)(nil), // 111: hyapp.wallet.v1.DeleteRechargeProductResponse - (*ThirdPartyPaymentMethod)(nil), // 112: hyapp.wallet.v1.ThirdPartyPaymentMethod - (*ThirdPartyPaymentChannel)(nil), // 113: hyapp.wallet.v1.ThirdPartyPaymentChannel - (*ListThirdPartyPaymentChannelsRequest)(nil), // 114: hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest - (*ListThirdPartyPaymentChannelsResponse)(nil), // 115: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse - (*SetThirdPartyPaymentMethodStatusRequest)(nil), // 116: hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest - (*ThirdPartyPaymentMethodResponse)(nil), // 117: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse - (*UpdateThirdPartyPaymentRateRequest)(nil), // 118: hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest - (*H5RechargeOptionsRequest)(nil), // 119: hyapp.wallet.v1.H5RechargeOptionsRequest - (*H5RechargeOptionsResponse)(nil), // 120: hyapp.wallet.v1.H5RechargeOptionsResponse - (*ExternalRechargeOrder)(nil), // 121: hyapp.wallet.v1.ExternalRechargeOrder - (*CreateH5RechargeOrderRequest)(nil), // 122: hyapp.wallet.v1.CreateH5RechargeOrderRequest - (*SubmitH5RechargeTxRequest)(nil), // 123: hyapp.wallet.v1.SubmitH5RechargeTxRequest - (*GetH5RechargeOrderRequest)(nil), // 124: hyapp.wallet.v1.GetH5RechargeOrderRequest - (*H5RechargeOrderResponse)(nil), // 125: hyapp.wallet.v1.H5RechargeOrderResponse - (*HandleMifapayNotifyRequest)(nil), // 126: hyapp.wallet.v1.HandleMifapayNotifyRequest - (*HandleMifapayNotifyResponse)(nil), // 127: hyapp.wallet.v1.HandleMifapayNotifyResponse - (*DiamondExchangeRule)(nil), // 128: hyapp.wallet.v1.DiamondExchangeRule - (*GetDiamondExchangeConfigRequest)(nil), // 129: hyapp.wallet.v1.GetDiamondExchangeConfigRequest - (*GetDiamondExchangeConfigResponse)(nil), // 130: hyapp.wallet.v1.GetDiamondExchangeConfigResponse - (*WalletTransaction)(nil), // 131: hyapp.wallet.v1.WalletTransaction - (*ListWalletTransactionsRequest)(nil), // 132: hyapp.wallet.v1.ListWalletTransactionsRequest - (*ListWalletTransactionsResponse)(nil), // 133: hyapp.wallet.v1.ListWalletTransactionsResponse - (*VipRewardItem)(nil), // 134: hyapp.wallet.v1.VipRewardItem - (*VipLevel)(nil), // 135: hyapp.wallet.v1.VipLevel - (*UserVip)(nil), // 136: hyapp.wallet.v1.UserVip - (*ListVipPackagesRequest)(nil), // 137: hyapp.wallet.v1.ListVipPackagesRequest - (*ListVipPackagesResponse)(nil), // 138: hyapp.wallet.v1.ListVipPackagesResponse - (*GetMyVipRequest)(nil), // 139: hyapp.wallet.v1.GetMyVipRequest - (*GetMyVipResponse)(nil), // 140: hyapp.wallet.v1.GetMyVipResponse - (*PurchaseVipRequest)(nil), // 141: hyapp.wallet.v1.PurchaseVipRequest - (*PurchaseVipResponse)(nil), // 142: hyapp.wallet.v1.PurchaseVipResponse - (*DebitCPBreakupFeeRequest)(nil), // 143: hyapp.wallet.v1.DebitCPBreakupFeeRequest - (*DebitCPBreakupFeeResponse)(nil), // 144: hyapp.wallet.v1.DebitCPBreakupFeeResponse - (*GrantVipRequest)(nil), // 145: hyapp.wallet.v1.GrantVipRequest - (*GrantVipResponse)(nil), // 146: hyapp.wallet.v1.GrantVipResponse - (*AdminVipLevelInput)(nil), // 147: hyapp.wallet.v1.AdminVipLevelInput - (*ListAdminVipLevelsRequest)(nil), // 148: hyapp.wallet.v1.ListAdminVipLevelsRequest - (*ListAdminVipLevelsResponse)(nil), // 149: hyapp.wallet.v1.ListAdminVipLevelsResponse - (*UpdateAdminVipLevelsRequest)(nil), // 150: hyapp.wallet.v1.UpdateAdminVipLevelsRequest - (*UpdateAdminVipLevelsResponse)(nil), // 151: hyapp.wallet.v1.UpdateAdminVipLevelsResponse - (*CreditTaskRewardRequest)(nil), // 152: hyapp.wallet.v1.CreditTaskRewardRequest - (*CreditTaskRewardResponse)(nil), // 153: hyapp.wallet.v1.CreditTaskRewardResponse - (*CreditLuckyGiftRewardRequest)(nil), // 154: hyapp.wallet.v1.CreditLuckyGiftRewardRequest - (*CreditLuckyGiftRewardResponse)(nil), // 155: hyapp.wallet.v1.CreditLuckyGiftRewardResponse - (*CreditRoomTurnoverRewardRequest)(nil), // 156: hyapp.wallet.v1.CreditRoomTurnoverRewardRequest - (*CreditRoomTurnoverRewardResponse)(nil), // 157: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse - (*ApplyGameCoinChangeRequest)(nil), // 158: hyapp.wallet.v1.ApplyGameCoinChangeRequest - (*ApplyGameCoinChangeResponse)(nil), // 159: hyapp.wallet.v1.ApplyGameCoinChangeResponse - (*RedPacketConfig)(nil), // 160: hyapp.wallet.v1.RedPacketConfig - (*RedPacketClaim)(nil), // 161: hyapp.wallet.v1.RedPacketClaim - (*RedPacket)(nil), // 162: hyapp.wallet.v1.RedPacket - (*GetRedPacketConfigRequest)(nil), // 163: hyapp.wallet.v1.GetRedPacketConfigRequest - (*GetRedPacketConfigResponse)(nil), // 164: hyapp.wallet.v1.GetRedPacketConfigResponse - (*UpdateRedPacketConfigRequest)(nil), // 165: hyapp.wallet.v1.UpdateRedPacketConfigRequest - (*UpdateRedPacketConfigResponse)(nil), // 166: hyapp.wallet.v1.UpdateRedPacketConfigResponse - (*CreateRedPacketRequest)(nil), // 167: hyapp.wallet.v1.CreateRedPacketRequest - (*CreateRedPacketResponse)(nil), // 168: hyapp.wallet.v1.CreateRedPacketResponse - (*ClaimRedPacketRequest)(nil), // 169: hyapp.wallet.v1.ClaimRedPacketRequest - (*ClaimRedPacketResponse)(nil), // 170: hyapp.wallet.v1.ClaimRedPacketResponse - (*ListRedPacketsRequest)(nil), // 171: hyapp.wallet.v1.ListRedPacketsRequest - (*ListRedPacketsResponse)(nil), // 172: hyapp.wallet.v1.ListRedPacketsResponse - (*GetRedPacketRequest)(nil), // 173: hyapp.wallet.v1.GetRedPacketRequest - (*GetRedPacketResponse)(nil), // 174: hyapp.wallet.v1.GetRedPacketResponse - (*ExpireRedPacketsRequest)(nil), // 175: hyapp.wallet.v1.ExpireRedPacketsRequest - (*ExpireRedPacketsResponse)(nil), // 176: hyapp.wallet.v1.ExpireRedPacketsResponse - (*RetryRedPacketRefundRequest)(nil), // 177: hyapp.wallet.v1.RetryRedPacketRefundRequest - (*RetryRedPacketRefundResponse)(nil), // 178: hyapp.wallet.v1.RetryRedPacketRefundResponse - (*CronBatchRequest)(nil), // 179: hyapp.wallet.v1.CronBatchRequest - (*CronBatchResponse)(nil), // 180: hyapp.wallet.v1.CronBatchResponse + (*DeleteGiftConfigRequest)(nil), // 64: hyapp.wallet.v1.DeleteGiftConfigRequest + (*GiftConfigResponse)(nil), // 65: hyapp.wallet.v1.GiftConfigResponse + (*GrantResourceRequest)(nil), // 66: hyapp.wallet.v1.GrantResourceRequest + (*GrantResourceGroupRequest)(nil), // 67: hyapp.wallet.v1.GrantResourceGroupRequest + (*ResourceGrantResponse)(nil), // 68: hyapp.wallet.v1.ResourceGrantResponse + (*ListUserResourcesRequest)(nil), // 69: hyapp.wallet.v1.ListUserResourcesRequest + (*ListUserResourcesResponse)(nil), // 70: hyapp.wallet.v1.ListUserResourcesResponse + (*EquipUserResourceRequest)(nil), // 71: hyapp.wallet.v1.EquipUserResourceRequest + (*EquipUserResourceResponse)(nil), // 72: hyapp.wallet.v1.EquipUserResourceResponse + (*UnequipUserResourceRequest)(nil), // 73: hyapp.wallet.v1.UnequipUserResourceRequest + (*UnequipUserResourceResponse)(nil), // 74: hyapp.wallet.v1.UnequipUserResourceResponse + (*BatchGetUserEquippedResourcesRequest)(nil), // 75: hyapp.wallet.v1.BatchGetUserEquippedResourcesRequest + (*UserEquippedResources)(nil), // 76: hyapp.wallet.v1.UserEquippedResources + (*BatchGetUserEquippedResourcesResponse)(nil), // 77: hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse + (*ListResourceGrantsRequest)(nil), // 78: hyapp.wallet.v1.ListResourceGrantsRequest + (*ListResourceGrantsResponse)(nil), // 79: hyapp.wallet.v1.ListResourceGrantsResponse + (*ResourceShopItemInput)(nil), // 80: hyapp.wallet.v1.ResourceShopItemInput + (*ListResourceShopItemsRequest)(nil), // 81: hyapp.wallet.v1.ListResourceShopItemsRequest + (*ListResourceShopItemsResponse)(nil), // 82: hyapp.wallet.v1.ListResourceShopItemsResponse + (*UpsertResourceShopItemsRequest)(nil), // 83: hyapp.wallet.v1.UpsertResourceShopItemsRequest + (*UpsertResourceShopItemsResponse)(nil), // 84: hyapp.wallet.v1.UpsertResourceShopItemsResponse + (*SetResourceShopItemStatusRequest)(nil), // 85: hyapp.wallet.v1.SetResourceShopItemStatusRequest + (*ResourceShopItemResponse)(nil), // 86: hyapp.wallet.v1.ResourceShopItemResponse + (*PurchaseResourceShopItemRequest)(nil), // 87: hyapp.wallet.v1.PurchaseResourceShopItemRequest + (*PurchaseResourceShopItemResponse)(nil), // 88: hyapp.wallet.v1.PurchaseResourceShopItemResponse + (*RechargeBill)(nil), // 89: hyapp.wallet.v1.RechargeBill + (*ListRechargeBillsRequest)(nil), // 90: hyapp.wallet.v1.ListRechargeBillsRequest + (*ListRechargeBillsResponse)(nil), // 91: hyapp.wallet.v1.ListRechargeBillsResponse + (*WalletFeatureFlags)(nil), // 92: hyapp.wallet.v1.WalletFeatureFlags + (*GetWalletOverviewRequest)(nil), // 93: hyapp.wallet.v1.GetWalletOverviewRequest + (*GetWalletOverviewResponse)(nil), // 94: hyapp.wallet.v1.GetWalletOverviewResponse + (*WalletValueSummary)(nil), // 95: hyapp.wallet.v1.WalletValueSummary + (*GetWalletValueSummaryRequest)(nil), // 96: hyapp.wallet.v1.GetWalletValueSummaryRequest + (*GetWalletValueSummaryResponse)(nil), // 97: hyapp.wallet.v1.GetWalletValueSummaryResponse + (*GiftWallItem)(nil), // 98: hyapp.wallet.v1.GiftWallItem + (*GetUserGiftWallRequest)(nil), // 99: hyapp.wallet.v1.GetUserGiftWallRequest + (*GetUserGiftWallResponse)(nil), // 100: hyapp.wallet.v1.GetUserGiftWallResponse + (*RechargeProduct)(nil), // 101: hyapp.wallet.v1.RechargeProduct + (*ListRechargeProductsRequest)(nil), // 102: hyapp.wallet.v1.ListRechargeProductsRequest + (*ListRechargeProductsResponse)(nil), // 103: hyapp.wallet.v1.ListRechargeProductsResponse + (*ConfirmGooglePaymentRequest)(nil), // 104: hyapp.wallet.v1.ConfirmGooglePaymentRequest + (*ConfirmGooglePaymentResponse)(nil), // 105: hyapp.wallet.v1.ConfirmGooglePaymentResponse + (*ListAdminRechargeProductsRequest)(nil), // 106: hyapp.wallet.v1.ListAdminRechargeProductsRequest + (*ListAdminRechargeProductsResponse)(nil), // 107: hyapp.wallet.v1.ListAdminRechargeProductsResponse + (*CreateRechargeProductRequest)(nil), // 108: hyapp.wallet.v1.CreateRechargeProductRequest + (*UpdateRechargeProductRequest)(nil), // 109: hyapp.wallet.v1.UpdateRechargeProductRequest + (*DeleteRechargeProductRequest)(nil), // 110: hyapp.wallet.v1.DeleteRechargeProductRequest + (*RechargeProductResponse)(nil), // 111: hyapp.wallet.v1.RechargeProductResponse + (*DeleteRechargeProductResponse)(nil), // 112: hyapp.wallet.v1.DeleteRechargeProductResponse + (*ThirdPartyPaymentMethod)(nil), // 113: hyapp.wallet.v1.ThirdPartyPaymentMethod + (*ThirdPartyPaymentChannel)(nil), // 114: hyapp.wallet.v1.ThirdPartyPaymentChannel + (*ListThirdPartyPaymentChannelsRequest)(nil), // 115: hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest + (*ListThirdPartyPaymentChannelsResponse)(nil), // 116: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse + (*SetThirdPartyPaymentMethodStatusRequest)(nil), // 117: hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest + (*ThirdPartyPaymentMethodResponse)(nil), // 118: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + (*UpdateThirdPartyPaymentRateRequest)(nil), // 119: hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest + (*H5RechargeOptionsRequest)(nil), // 120: hyapp.wallet.v1.H5RechargeOptionsRequest + (*H5RechargeOptionsResponse)(nil), // 121: hyapp.wallet.v1.H5RechargeOptionsResponse + (*ExternalRechargeOrder)(nil), // 122: hyapp.wallet.v1.ExternalRechargeOrder + (*CreateH5RechargeOrderRequest)(nil), // 123: hyapp.wallet.v1.CreateH5RechargeOrderRequest + (*SubmitH5RechargeTxRequest)(nil), // 124: hyapp.wallet.v1.SubmitH5RechargeTxRequest + (*GetH5RechargeOrderRequest)(nil), // 125: hyapp.wallet.v1.GetH5RechargeOrderRequest + (*H5RechargeOrderResponse)(nil), // 126: hyapp.wallet.v1.H5RechargeOrderResponse + (*HandleMifapayNotifyRequest)(nil), // 127: hyapp.wallet.v1.HandleMifapayNotifyRequest + (*HandleMifapayNotifyResponse)(nil), // 128: hyapp.wallet.v1.HandleMifapayNotifyResponse + (*DiamondExchangeRule)(nil), // 129: hyapp.wallet.v1.DiamondExchangeRule + (*GetDiamondExchangeConfigRequest)(nil), // 130: hyapp.wallet.v1.GetDiamondExchangeConfigRequest + (*GetDiamondExchangeConfigResponse)(nil), // 131: hyapp.wallet.v1.GetDiamondExchangeConfigResponse + (*WalletTransaction)(nil), // 132: hyapp.wallet.v1.WalletTransaction + (*ListWalletTransactionsRequest)(nil), // 133: hyapp.wallet.v1.ListWalletTransactionsRequest + (*ListWalletTransactionsResponse)(nil), // 134: hyapp.wallet.v1.ListWalletTransactionsResponse + (*VipRewardItem)(nil), // 135: hyapp.wallet.v1.VipRewardItem + (*VipLevel)(nil), // 136: hyapp.wallet.v1.VipLevel + (*UserVip)(nil), // 137: hyapp.wallet.v1.UserVip + (*ListVipPackagesRequest)(nil), // 138: hyapp.wallet.v1.ListVipPackagesRequest + (*ListVipPackagesResponse)(nil), // 139: hyapp.wallet.v1.ListVipPackagesResponse + (*GetMyVipRequest)(nil), // 140: hyapp.wallet.v1.GetMyVipRequest + (*GetMyVipResponse)(nil), // 141: hyapp.wallet.v1.GetMyVipResponse + (*PurchaseVipRequest)(nil), // 142: hyapp.wallet.v1.PurchaseVipRequest + (*PurchaseVipResponse)(nil), // 143: hyapp.wallet.v1.PurchaseVipResponse + (*DebitCPBreakupFeeRequest)(nil), // 144: hyapp.wallet.v1.DebitCPBreakupFeeRequest + (*DebitCPBreakupFeeResponse)(nil), // 145: hyapp.wallet.v1.DebitCPBreakupFeeResponse + (*GrantVipRequest)(nil), // 146: hyapp.wallet.v1.GrantVipRequest + (*GrantVipResponse)(nil), // 147: hyapp.wallet.v1.GrantVipResponse + (*AdminVipLevelInput)(nil), // 148: hyapp.wallet.v1.AdminVipLevelInput + (*ListAdminVipLevelsRequest)(nil), // 149: hyapp.wallet.v1.ListAdminVipLevelsRequest + (*ListAdminVipLevelsResponse)(nil), // 150: hyapp.wallet.v1.ListAdminVipLevelsResponse + (*UpdateAdminVipLevelsRequest)(nil), // 151: hyapp.wallet.v1.UpdateAdminVipLevelsRequest + (*UpdateAdminVipLevelsResponse)(nil), // 152: hyapp.wallet.v1.UpdateAdminVipLevelsResponse + (*CreditTaskRewardRequest)(nil), // 153: hyapp.wallet.v1.CreditTaskRewardRequest + (*CreditTaskRewardResponse)(nil), // 154: hyapp.wallet.v1.CreditTaskRewardResponse + (*CreditLuckyGiftRewardRequest)(nil), // 155: hyapp.wallet.v1.CreditLuckyGiftRewardRequest + (*CreditLuckyGiftRewardResponse)(nil), // 156: hyapp.wallet.v1.CreditLuckyGiftRewardResponse + (*CreditRoomTurnoverRewardRequest)(nil), // 157: hyapp.wallet.v1.CreditRoomTurnoverRewardRequest + (*CreditRoomTurnoverRewardResponse)(nil), // 158: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse + (*CreditInviteActivityRewardRequest)(nil), // 159: hyapp.wallet.v1.CreditInviteActivityRewardRequest + (*CreditInviteActivityRewardResponse)(nil), // 160: hyapp.wallet.v1.CreditInviteActivityRewardResponse + (*ApplyGameCoinChangeRequest)(nil), // 161: hyapp.wallet.v1.ApplyGameCoinChangeRequest + (*ApplyGameCoinChangeResponse)(nil), // 162: hyapp.wallet.v1.ApplyGameCoinChangeResponse + (*RedPacketConfig)(nil), // 163: hyapp.wallet.v1.RedPacketConfig + (*RedPacketClaim)(nil), // 164: hyapp.wallet.v1.RedPacketClaim + (*RedPacket)(nil), // 165: hyapp.wallet.v1.RedPacket + (*GetRedPacketConfigRequest)(nil), // 166: hyapp.wallet.v1.GetRedPacketConfigRequest + (*GetRedPacketConfigResponse)(nil), // 167: hyapp.wallet.v1.GetRedPacketConfigResponse + (*UpdateRedPacketConfigRequest)(nil), // 168: hyapp.wallet.v1.UpdateRedPacketConfigRequest + (*UpdateRedPacketConfigResponse)(nil), // 169: hyapp.wallet.v1.UpdateRedPacketConfigResponse + (*CreateRedPacketRequest)(nil), // 170: hyapp.wallet.v1.CreateRedPacketRequest + (*CreateRedPacketResponse)(nil), // 171: hyapp.wallet.v1.CreateRedPacketResponse + (*ClaimRedPacketRequest)(nil), // 172: hyapp.wallet.v1.ClaimRedPacketRequest + (*ClaimRedPacketResponse)(nil), // 173: hyapp.wallet.v1.ClaimRedPacketResponse + (*ListRedPacketsRequest)(nil), // 174: hyapp.wallet.v1.ListRedPacketsRequest + (*ListRedPacketsResponse)(nil), // 175: hyapp.wallet.v1.ListRedPacketsResponse + (*GetRedPacketRequest)(nil), // 176: hyapp.wallet.v1.GetRedPacketRequest + (*GetRedPacketResponse)(nil), // 177: hyapp.wallet.v1.GetRedPacketResponse + (*ExpireRedPacketsRequest)(nil), // 178: hyapp.wallet.v1.ExpireRedPacketsRequest + (*ExpireRedPacketsResponse)(nil), // 179: hyapp.wallet.v1.ExpireRedPacketsResponse + (*RetryRedPacketRefundRequest)(nil), // 180: hyapp.wallet.v1.RetryRedPacketRefundRequest + (*RetryRedPacketRefundResponse)(nil), // 181: hyapp.wallet.v1.RetryRedPacketRefundResponse + (*CronBatchRequest)(nil), // 182: hyapp.wallet.v1.CronBatchRequest + (*CronBatchResponse)(nil), // 183: hyapp.wallet.v1.CronBatchResponse } var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 2, // 0: hyapp.wallet.v1.BatchDebitGiftRequest.targets:type_name -> hyapp.wallet.v1.DebitGiftTarget @@ -17903,222 +18196,227 @@ var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 35, // 29: hyapp.wallet.v1.ListUserResourcesResponse.resources:type_name -> hyapp.wallet.v1.UserResourceEntitlement 35, // 30: hyapp.wallet.v1.EquipUserResourceResponse.resource:type_name -> hyapp.wallet.v1.UserResourceEntitlement 35, // 31: hyapp.wallet.v1.UserEquippedResources.resources:type_name -> hyapp.wallet.v1.UserResourceEntitlement - 75, // 32: hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse.users:type_name -> hyapp.wallet.v1.UserEquippedResources + 76, // 32: hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse.users:type_name -> hyapp.wallet.v1.UserEquippedResources 37, // 33: hyapp.wallet.v1.ListResourceGrantsResponse.grants:type_name -> hyapp.wallet.v1.ResourceGrant 34, // 34: hyapp.wallet.v1.ListResourceShopItemsResponse.items:type_name -> hyapp.wallet.v1.ResourceShopItem - 79, // 35: hyapp.wallet.v1.UpsertResourceShopItemsRequest.items:type_name -> hyapp.wallet.v1.ResourceShopItemInput + 80, // 35: hyapp.wallet.v1.UpsertResourceShopItemsRequest.items:type_name -> hyapp.wallet.v1.ResourceShopItemInput 34, // 36: hyapp.wallet.v1.UpsertResourceShopItemsResponse.items:type_name -> hyapp.wallet.v1.ResourceShopItem 34, // 37: hyapp.wallet.v1.ResourceShopItemResponse.item:type_name -> hyapp.wallet.v1.ResourceShopItem 34, // 38: hyapp.wallet.v1.PurchaseResourceShopItemResponse.shop_item:type_name -> hyapp.wallet.v1.ResourceShopItem 35, // 39: hyapp.wallet.v1.PurchaseResourceShopItemResponse.resource:type_name -> hyapp.wallet.v1.UserResourceEntitlement 6, // 40: hyapp.wallet.v1.PurchaseResourceShopItemResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 88, // 41: hyapp.wallet.v1.ListRechargeBillsResponse.bills:type_name -> hyapp.wallet.v1.RechargeBill + 89, // 41: hyapp.wallet.v1.ListRechargeBillsResponse.bills:type_name -> hyapp.wallet.v1.RechargeBill 6, // 42: hyapp.wallet.v1.GetWalletOverviewResponse.balances:type_name -> hyapp.wallet.v1.AssetBalance - 91, // 43: hyapp.wallet.v1.GetWalletOverviewResponse.feature_flags:type_name -> hyapp.wallet.v1.WalletFeatureFlags - 94, // 44: hyapp.wallet.v1.GetWalletValueSummaryResponse.summary:type_name -> hyapp.wallet.v1.WalletValueSummary + 92, // 43: hyapp.wallet.v1.GetWalletOverviewResponse.feature_flags:type_name -> hyapp.wallet.v1.WalletFeatureFlags + 95, // 44: hyapp.wallet.v1.GetWalletValueSummaryResponse.summary:type_name -> hyapp.wallet.v1.WalletValueSummary 29, // 45: hyapp.wallet.v1.GiftWallItem.resource:type_name -> hyapp.wallet.v1.Resource - 97, // 46: hyapp.wallet.v1.GetUserGiftWallResponse.items:type_name -> hyapp.wallet.v1.GiftWallItem - 100, // 47: hyapp.wallet.v1.ListRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct + 98, // 46: hyapp.wallet.v1.GetUserGiftWallResponse.items:type_name -> hyapp.wallet.v1.GiftWallItem + 101, // 47: hyapp.wallet.v1.ListRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct 6, // 48: hyapp.wallet.v1.ConfirmGooglePaymentResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 100, // 49: hyapp.wallet.v1.ListAdminRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct - 100, // 50: hyapp.wallet.v1.RechargeProductResponse.product:type_name -> hyapp.wallet.v1.RechargeProduct - 112, // 51: hyapp.wallet.v1.ThirdPartyPaymentChannel.methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod - 113, // 52: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse.channels:type_name -> hyapp.wallet.v1.ThirdPartyPaymentChannel - 112, // 53: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse.method:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod - 100, // 54: hyapp.wallet.v1.H5RechargeOptionsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct - 112, // 55: hyapp.wallet.v1.H5RechargeOptionsResponse.payment_methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod - 121, // 56: hyapp.wallet.v1.H5RechargeOrderResponse.order:type_name -> hyapp.wallet.v1.ExternalRechargeOrder - 128, // 57: hyapp.wallet.v1.GetDiamondExchangeConfigResponse.rules:type_name -> hyapp.wallet.v1.DiamondExchangeRule - 131, // 58: hyapp.wallet.v1.ListWalletTransactionsResponse.transactions:type_name -> hyapp.wallet.v1.WalletTransaction - 134, // 59: hyapp.wallet.v1.VipLevel.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem - 136, // 60: hyapp.wallet.v1.ListVipPackagesResponse.current_vip:type_name -> hyapp.wallet.v1.UserVip - 135, // 61: hyapp.wallet.v1.ListVipPackagesResponse.packages:type_name -> hyapp.wallet.v1.VipLevel - 136, // 62: hyapp.wallet.v1.GetMyVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 136, // 63: hyapp.wallet.v1.PurchaseVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 134, // 64: hyapp.wallet.v1.PurchaseVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 101, // 49: hyapp.wallet.v1.ListAdminRechargeProductsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct + 101, // 50: hyapp.wallet.v1.RechargeProductResponse.product:type_name -> hyapp.wallet.v1.RechargeProduct + 113, // 51: hyapp.wallet.v1.ThirdPartyPaymentChannel.methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 114, // 52: hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse.channels:type_name -> hyapp.wallet.v1.ThirdPartyPaymentChannel + 113, // 53: hyapp.wallet.v1.ThirdPartyPaymentMethodResponse.method:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 101, // 54: hyapp.wallet.v1.H5RechargeOptionsResponse.products:type_name -> hyapp.wallet.v1.RechargeProduct + 113, // 55: hyapp.wallet.v1.H5RechargeOptionsResponse.payment_methods:type_name -> hyapp.wallet.v1.ThirdPartyPaymentMethod + 122, // 56: hyapp.wallet.v1.H5RechargeOrderResponse.order:type_name -> hyapp.wallet.v1.ExternalRechargeOrder + 129, // 57: hyapp.wallet.v1.GetDiamondExchangeConfigResponse.rules:type_name -> hyapp.wallet.v1.DiamondExchangeRule + 132, // 58: hyapp.wallet.v1.ListWalletTransactionsResponse.transactions:type_name -> hyapp.wallet.v1.WalletTransaction + 135, // 59: hyapp.wallet.v1.VipLevel.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 137, // 60: hyapp.wallet.v1.ListVipPackagesResponse.current_vip:type_name -> hyapp.wallet.v1.UserVip + 136, // 61: hyapp.wallet.v1.ListVipPackagesResponse.packages:type_name -> hyapp.wallet.v1.VipLevel + 137, // 62: hyapp.wallet.v1.GetMyVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 137, // 63: hyapp.wallet.v1.PurchaseVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 135, // 64: hyapp.wallet.v1.PurchaseVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem 6, // 65: hyapp.wallet.v1.DebitCPBreakupFeeResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 136, // 66: hyapp.wallet.v1.GrantVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip - 134, // 67: hyapp.wallet.v1.GrantVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem - 135, // 68: hyapp.wallet.v1.ListAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel - 147, // 69: hyapp.wallet.v1.UpdateAdminVipLevelsRequest.levels:type_name -> hyapp.wallet.v1.AdminVipLevelInput - 135, // 70: hyapp.wallet.v1.UpdateAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel + 137, // 66: hyapp.wallet.v1.GrantVipResponse.vip:type_name -> hyapp.wallet.v1.UserVip + 135, // 67: hyapp.wallet.v1.GrantVipResponse.reward_items:type_name -> hyapp.wallet.v1.VipRewardItem + 136, // 68: hyapp.wallet.v1.ListAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel + 148, // 69: hyapp.wallet.v1.UpdateAdminVipLevelsRequest.levels:type_name -> hyapp.wallet.v1.AdminVipLevelInput + 136, // 70: hyapp.wallet.v1.UpdateAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel 6, // 71: hyapp.wallet.v1.CreditTaskRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance 6, // 72: hyapp.wallet.v1.CreditLuckyGiftRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance 6, // 73: hyapp.wallet.v1.CreditRoomTurnoverRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 161, // 74: hyapp.wallet.v1.RedPacket.claims:type_name -> hyapp.wallet.v1.RedPacketClaim - 160, // 75: hyapp.wallet.v1.GetRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig - 160, // 76: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig - 162, // 77: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 161, // 78: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim - 162, // 79: hyapp.wallet.v1.ClaimRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 162, // 80: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket - 162, // 81: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 162, // 82: hyapp.wallet.v1.RetryRedPacketRefundResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 179, // 83: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 179, // 84: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 179, // 85: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:input_type -> hyapp.wallet.v1.CronBatchRequest - 0, // 86: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest - 3, // 87: hyapp.wallet.v1.WalletService.BatchDebitGift:input_type -> hyapp.wallet.v1.BatchDebitGiftRequest - 7, // 88: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest - 11, // 89: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:input_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyRequest - 14, // 90: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:input_type -> hyapp.wallet.v1.GetHostSalaryProgressRequest - 16, // 91: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest - 18, // 92: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest - 20, // 93: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest - 23, // 94: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:input_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersRequest - 25, // 95: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:input_type -> hyapp.wallet.v1.ExchangeSalaryToCoinRequest - 27, // 96: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:input_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerRequest - 39, // 97: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest - 41, // 98: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest - 43, // 99: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest - 44, // 100: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest - 45, // 101: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest - 47, // 102: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest - 49, // 103: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest - 51, // 104: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest - 52, // 105: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest - 53, // 106: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest - 55, // 107: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest - 57, // 108: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest - 61, // 109: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest - 62, // 110: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest - 63, // 111: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest - 59, // 112: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest - 65, // 113: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest - 66, // 114: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest - 68, // 115: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest - 70, // 116: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest - 72, // 117: hyapp.wallet.v1.WalletService.UnequipUserResource:input_type -> hyapp.wallet.v1.UnequipUserResourceRequest - 74, // 118: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:input_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesRequest - 77, // 119: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest - 80, // 120: hyapp.wallet.v1.WalletService.ListResourceShopItems:input_type -> hyapp.wallet.v1.ListResourceShopItemsRequest - 82, // 121: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:input_type -> hyapp.wallet.v1.UpsertResourceShopItemsRequest - 84, // 122: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:input_type -> hyapp.wallet.v1.SetResourceShopItemStatusRequest - 86, // 123: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:input_type -> hyapp.wallet.v1.PurchaseResourceShopItemRequest - 89, // 124: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest - 92, // 125: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest - 95, // 126: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest - 98, // 127: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest - 101, // 128: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest - 103, // 129: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest - 105, // 130: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest - 107, // 131: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest - 108, // 132: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest - 109, // 133: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest - 114, // 134: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:input_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest - 116, // 135: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:input_type -> hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest - 118, // 136: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:input_type -> hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest - 119, // 137: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:input_type -> hyapp.wallet.v1.H5RechargeOptionsRequest - 122, // 138: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:input_type -> hyapp.wallet.v1.CreateH5RechargeOrderRequest - 123, // 139: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:input_type -> hyapp.wallet.v1.SubmitH5RechargeTxRequest - 124, // 140: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:input_type -> hyapp.wallet.v1.GetH5RechargeOrderRequest - 126, // 141: hyapp.wallet.v1.WalletService.HandleMifapayNotify:input_type -> hyapp.wallet.v1.HandleMifapayNotifyRequest - 129, // 142: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest - 132, // 143: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest - 137, // 144: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest - 139, // 145: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest - 141, // 146: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest - 143, // 147: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:input_type -> hyapp.wallet.v1.DebitCPBreakupFeeRequest - 145, // 148: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest - 148, // 149: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest - 150, // 150: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest - 152, // 151: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest - 154, // 152: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest - 156, // 153: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:input_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardRequest - 158, // 154: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest - 163, // 155: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest - 165, // 156: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest - 167, // 157: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest - 169, // 158: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest - 171, // 159: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest - 173, // 160: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest - 175, // 161: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest - 177, // 162: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:input_type -> hyapp.wallet.v1.RetryRedPacketRefundRequest - 180, // 163: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 180, // 164: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 180, // 165: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:output_type -> hyapp.wallet.v1.CronBatchResponse - 1, // 166: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 5, // 167: hyapp.wallet.v1.WalletService.BatchDebitGift:output_type -> hyapp.wallet.v1.BatchDebitGiftResponse - 8, // 168: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse - 12, // 169: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:output_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyResponse - 15, // 170: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:output_type -> hyapp.wallet.v1.GetHostSalaryProgressResponse - 17, // 171: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse - 19, // 172: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse - 21, // 173: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse - 24, // 174: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:output_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersResponse - 26, // 175: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:output_type -> hyapp.wallet.v1.ExchangeSalaryToCoinResponse - 28, // 176: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:output_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerResponse - 40, // 177: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse - 42, // 178: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse - 46, // 179: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 46, // 180: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 46, // 181: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse - 48, // 182: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse - 50, // 183: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse - 54, // 184: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 54, // 185: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 54, // 186: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 56, // 187: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse - 58, // 188: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse - 64, // 189: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 64, // 190: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 64, // 191: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse - 60, // 192: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse - 67, // 193: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 67, // 194: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 69, // 195: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse - 71, // 196: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse - 73, // 197: hyapp.wallet.v1.WalletService.UnequipUserResource:output_type -> hyapp.wallet.v1.UnequipUserResourceResponse - 76, // 198: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:output_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse - 78, // 199: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse - 81, // 200: hyapp.wallet.v1.WalletService.ListResourceShopItems:output_type -> hyapp.wallet.v1.ListResourceShopItemsResponse - 83, // 201: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:output_type -> hyapp.wallet.v1.UpsertResourceShopItemsResponse - 85, // 202: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:output_type -> hyapp.wallet.v1.ResourceShopItemResponse - 87, // 203: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:output_type -> hyapp.wallet.v1.PurchaseResourceShopItemResponse - 90, // 204: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse - 93, // 205: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse - 96, // 206: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse - 99, // 207: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse - 102, // 208: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse - 104, // 209: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse - 106, // 210: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse - 110, // 211: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 110, // 212: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 111, // 213: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse - 115, // 214: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:output_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse - 117, // 215: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse - 117, // 216: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse - 120, // 217: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:output_type -> hyapp.wallet.v1.H5RechargeOptionsResponse - 125, // 218: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse - 125, // 219: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse - 125, // 220: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse - 127, // 221: hyapp.wallet.v1.WalletService.HandleMifapayNotify:output_type -> hyapp.wallet.v1.HandleMifapayNotifyResponse - 130, // 222: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse - 133, // 223: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse - 138, // 224: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse - 140, // 225: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse - 142, // 226: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse - 144, // 227: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:output_type -> hyapp.wallet.v1.DebitCPBreakupFeeResponse - 146, // 228: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse - 149, // 229: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse - 151, // 230: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse - 153, // 231: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse - 155, // 232: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse - 157, // 233: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:output_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardResponse - 159, // 234: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse - 164, // 235: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse - 166, // 236: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse - 168, // 237: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse - 170, // 238: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse - 172, // 239: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse - 174, // 240: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse - 176, // 241: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse - 178, // 242: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:output_type -> hyapp.wallet.v1.RetryRedPacketRefundResponse - 163, // [163:243] is the sub-list for method output_type - 83, // [83:163] is the sub-list for method input_type - 83, // [83:83] is the sub-list for extension type_name - 83, // [83:83] is the sub-list for extension extendee - 0, // [0:83] is the sub-list for field type_name + 6, // 74: hyapp.wallet.v1.CreditInviteActivityRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 164, // 75: hyapp.wallet.v1.RedPacket.claims:type_name -> hyapp.wallet.v1.RedPacketClaim + 163, // 76: hyapp.wallet.v1.GetRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig + 163, // 77: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig + 165, // 78: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 164, // 79: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim + 165, // 80: hyapp.wallet.v1.ClaimRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 165, // 81: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket + 165, // 82: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 165, // 83: hyapp.wallet.v1.RetryRedPacketRefundResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 182, // 84: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 182, // 85: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 182, // 86: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:input_type -> hyapp.wallet.v1.CronBatchRequest + 0, // 87: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest + 3, // 88: hyapp.wallet.v1.WalletService.BatchDebitGift:input_type -> hyapp.wallet.v1.BatchDebitGiftRequest + 7, // 89: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest + 11, // 90: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:input_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyRequest + 14, // 91: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:input_type -> hyapp.wallet.v1.GetHostSalaryProgressRequest + 16, // 92: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest + 18, // 93: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest + 20, // 94: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest + 23, // 95: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:input_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersRequest + 25, // 96: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:input_type -> hyapp.wallet.v1.ExchangeSalaryToCoinRequest + 27, // 97: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:input_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerRequest + 39, // 98: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest + 41, // 99: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest + 43, // 100: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest + 44, // 101: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest + 45, // 102: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest + 47, // 103: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest + 49, // 104: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest + 51, // 105: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest + 52, // 106: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest + 53, // 107: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest + 55, // 108: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest + 57, // 109: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest + 61, // 110: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest + 62, // 111: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest + 63, // 112: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest + 64, // 113: hyapp.wallet.v1.WalletService.DeleteGiftConfig:input_type -> hyapp.wallet.v1.DeleteGiftConfigRequest + 59, // 114: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest + 66, // 115: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest + 67, // 116: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest + 69, // 117: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest + 71, // 118: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest + 73, // 119: hyapp.wallet.v1.WalletService.UnequipUserResource:input_type -> hyapp.wallet.v1.UnequipUserResourceRequest + 75, // 120: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:input_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesRequest + 78, // 121: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest + 81, // 122: hyapp.wallet.v1.WalletService.ListResourceShopItems:input_type -> hyapp.wallet.v1.ListResourceShopItemsRequest + 83, // 123: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:input_type -> hyapp.wallet.v1.UpsertResourceShopItemsRequest + 85, // 124: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:input_type -> hyapp.wallet.v1.SetResourceShopItemStatusRequest + 87, // 125: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:input_type -> hyapp.wallet.v1.PurchaseResourceShopItemRequest + 90, // 126: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest + 93, // 127: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest + 96, // 128: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest + 99, // 129: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest + 102, // 130: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest + 104, // 131: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest + 106, // 132: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest + 108, // 133: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest + 109, // 134: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest + 110, // 135: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest + 115, // 136: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:input_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsRequest + 117, // 137: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:input_type -> hyapp.wallet.v1.SetThirdPartyPaymentMethodStatusRequest + 119, // 138: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:input_type -> hyapp.wallet.v1.UpdateThirdPartyPaymentRateRequest + 120, // 139: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:input_type -> hyapp.wallet.v1.H5RechargeOptionsRequest + 123, // 140: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:input_type -> hyapp.wallet.v1.CreateH5RechargeOrderRequest + 124, // 141: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:input_type -> hyapp.wallet.v1.SubmitH5RechargeTxRequest + 125, // 142: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:input_type -> hyapp.wallet.v1.GetH5RechargeOrderRequest + 127, // 143: hyapp.wallet.v1.WalletService.HandleMifapayNotify:input_type -> hyapp.wallet.v1.HandleMifapayNotifyRequest + 130, // 144: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest + 133, // 145: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest + 138, // 146: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest + 140, // 147: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest + 142, // 148: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest + 144, // 149: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:input_type -> hyapp.wallet.v1.DebitCPBreakupFeeRequest + 146, // 150: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest + 149, // 151: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest + 151, // 152: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest + 153, // 153: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest + 155, // 154: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest + 157, // 155: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:input_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardRequest + 159, // 156: hyapp.wallet.v1.WalletService.CreditInviteActivityReward:input_type -> hyapp.wallet.v1.CreditInviteActivityRewardRequest + 161, // 157: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest + 166, // 158: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest + 168, // 159: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest + 170, // 160: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest + 172, // 161: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest + 174, // 162: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest + 176, // 163: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest + 178, // 164: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest + 180, // 165: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:input_type -> hyapp.wallet.v1.RetryRedPacketRefundRequest + 183, // 166: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryDailySettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 183, // 167: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryHalfMonthSettlementBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 183, // 168: hyapp.wallet.v1.WalletCronService.ProcessHostSalaryMonthEndBatch:output_type -> hyapp.wallet.v1.CronBatchResponse + 1, // 169: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 5, // 170: hyapp.wallet.v1.WalletService.BatchDebitGift:output_type -> hyapp.wallet.v1.BatchDebitGiftResponse + 8, // 171: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse + 12, // 172: hyapp.wallet.v1.WalletService.GetActiveHostSalaryPolicy:output_type -> hyapp.wallet.v1.GetActiveHostSalaryPolicyResponse + 15, // 173: hyapp.wallet.v1.WalletService.GetHostSalaryProgress:output_type -> hyapp.wallet.v1.GetHostSalaryProgressResponse + 17, // 174: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse + 19, // 175: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse + 21, // 176: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse + 24, // 177: hyapp.wallet.v1.WalletService.ListCoinSellerSalaryExchangeRateTiers:output_type -> hyapp.wallet.v1.ListCoinSellerSalaryExchangeRateTiersResponse + 26, // 178: hyapp.wallet.v1.WalletService.ExchangeSalaryToCoin:output_type -> hyapp.wallet.v1.ExchangeSalaryToCoinResponse + 28, // 179: hyapp.wallet.v1.WalletService.TransferSalaryToCoinSeller:output_type -> hyapp.wallet.v1.TransferSalaryToCoinSellerResponse + 40, // 180: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse + 42, // 181: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse + 46, // 182: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 46, // 183: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 46, // 184: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse + 48, // 185: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse + 50, // 186: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse + 54, // 187: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 54, // 188: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 54, // 189: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 56, // 190: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse + 58, // 191: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse + 65, // 192: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 65, // 193: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 65, // 194: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse + 65, // 195: hyapp.wallet.v1.WalletService.DeleteGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 60, // 196: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse + 68, // 197: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 68, // 198: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 70, // 199: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse + 72, // 200: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse + 74, // 201: hyapp.wallet.v1.WalletService.UnequipUserResource:output_type -> hyapp.wallet.v1.UnequipUserResourceResponse + 77, // 202: hyapp.wallet.v1.WalletService.BatchGetUserEquippedResources:output_type -> hyapp.wallet.v1.BatchGetUserEquippedResourcesResponse + 79, // 203: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse + 82, // 204: hyapp.wallet.v1.WalletService.ListResourceShopItems:output_type -> hyapp.wallet.v1.ListResourceShopItemsResponse + 84, // 205: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:output_type -> hyapp.wallet.v1.UpsertResourceShopItemsResponse + 86, // 206: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:output_type -> hyapp.wallet.v1.ResourceShopItemResponse + 88, // 207: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:output_type -> hyapp.wallet.v1.PurchaseResourceShopItemResponse + 91, // 208: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse + 94, // 209: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse + 97, // 210: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse + 100, // 211: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse + 103, // 212: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse + 105, // 213: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse + 107, // 214: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse + 111, // 215: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 111, // 216: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 112, // 217: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse + 116, // 218: hyapp.wallet.v1.WalletService.ListThirdPartyPaymentChannels:output_type -> hyapp.wallet.v1.ListThirdPartyPaymentChannelsResponse + 118, // 219: hyapp.wallet.v1.WalletService.SetThirdPartyPaymentMethodStatus:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + 118, // 220: hyapp.wallet.v1.WalletService.UpdateThirdPartyPaymentRate:output_type -> hyapp.wallet.v1.ThirdPartyPaymentMethodResponse + 121, // 221: hyapp.wallet.v1.WalletService.ListH5RechargeOptions:output_type -> hyapp.wallet.v1.H5RechargeOptionsResponse + 126, // 222: hyapp.wallet.v1.WalletService.CreateH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 126, // 223: hyapp.wallet.v1.WalletService.SubmitH5RechargeTx:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 126, // 224: hyapp.wallet.v1.WalletService.GetH5RechargeOrder:output_type -> hyapp.wallet.v1.H5RechargeOrderResponse + 128, // 225: hyapp.wallet.v1.WalletService.HandleMifapayNotify:output_type -> hyapp.wallet.v1.HandleMifapayNotifyResponse + 131, // 226: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse + 134, // 227: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse + 139, // 228: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse + 141, // 229: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse + 143, // 230: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse + 145, // 231: hyapp.wallet.v1.WalletService.DebitCPBreakupFee:output_type -> hyapp.wallet.v1.DebitCPBreakupFeeResponse + 147, // 232: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse + 150, // 233: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse + 152, // 234: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse + 154, // 235: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse + 156, // 236: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse + 158, // 237: hyapp.wallet.v1.WalletService.CreditRoomTurnoverReward:output_type -> hyapp.wallet.v1.CreditRoomTurnoverRewardResponse + 160, // 238: hyapp.wallet.v1.WalletService.CreditInviteActivityReward:output_type -> hyapp.wallet.v1.CreditInviteActivityRewardResponse + 162, // 239: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse + 167, // 240: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse + 169, // 241: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse + 171, // 242: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse + 173, // 243: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse + 175, // 244: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse + 177, // 245: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse + 179, // 246: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse + 181, // 247: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:output_type -> hyapp.wallet.v1.RetryRedPacketRefundResponse + 166, // [166:248] is the sub-list for method output_type + 84, // [84:166] is the sub-list for method input_type + 84, // [84:84] is the sub-list for extension type_name + 84, // [84:84] is the sub-list for extension extendee + 0, // [0:84] is the sub-list for field type_name } func init() { file_proto_wallet_v1_wallet_proto_init() } @@ -18134,7 +18432,7 @@ func file_proto_wallet_v1_wallet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wallet_v1_wallet_proto_rawDesc), len(file_proto_wallet_v1_wallet_proto_rawDesc)), NumEnums: 0, - NumMessages: 181, + NumMessages: 184, NumExtensions: 0, NumServices: 2, }, diff --git a/api/proto/wallet/v1/wallet.proto b/api/proto/wallet/v1/wallet.proto index 38a2c70d..5347b3bd 100644 --- a/api/proto/wallet/v1/wallet.proto +++ b/api/proto/wallet/v1/wallet.proto @@ -737,6 +737,13 @@ message SetGiftConfigStatusRequest { int64 operator_user_id = 5; } +message DeleteGiftConfigRequest { + string request_id = 1; + string app_code = 2; + string gift_id = 3; + int64 operator_user_id = 4; +} + message GiftConfigResponse { GiftConfig gift = 1; } @@ -1561,6 +1568,29 @@ message CreditRoomTurnoverRewardResponse { int64 granted_at_ms = 4; } +// CreditInviteActivityRewardRequest 是 activity-service 邀请活动达标后发起的 COIN 奖励入账命令。 +message CreditInviteActivityRewardRequest { + string command_id = 1; + string app_code = 2; + int64 target_user_id = 3; + int64 amount = 4; + string claim_id = 5; + string reward_type = 6; + int64 tier_id = 7; + string tier_code = 8; + string cycle_key = 9; + int64 reached_value = 10; + string reason = 11; +} + +// CreditInviteActivityRewardResponse 返回邀请活动奖励入账流水和用户 COIN 账后余额。 +message CreditInviteActivityRewardResponse { + string transaction_id = 1; + AssetBalance balance = 2; + int64 amount = 3; + int64 granted_at_ms = 4; +} + // ApplyGameCoinChangeRequest 是 game-service 唯一可以调用的钱包游戏改账入口。 message ApplyGameCoinChangeRequest { string request_id = 1; @@ -1811,6 +1841,7 @@ service WalletService { rpc CreateGiftConfig(CreateGiftConfigRequest) returns (GiftConfigResponse); rpc UpdateGiftConfig(UpdateGiftConfigRequest) returns (GiftConfigResponse); rpc SetGiftConfigStatus(SetGiftConfigStatusRequest) returns (GiftConfigResponse); + rpc DeleteGiftConfig(DeleteGiftConfigRequest) returns (GiftConfigResponse); rpc UpsertGiftTypeConfig(UpsertGiftTypeConfigRequest) returns (GiftTypeConfigResponse); rpc GrantResource(GrantResourceRequest) returns (ResourceGrantResponse); rpc GrantResourceGroup(GrantResourceGroupRequest) returns (ResourceGrantResponse); @@ -1853,6 +1884,7 @@ service WalletService { rpc CreditTaskReward(CreditTaskRewardRequest) returns (CreditTaskRewardResponse); rpc CreditLuckyGiftReward(CreditLuckyGiftRewardRequest) returns (CreditLuckyGiftRewardResponse); rpc CreditRoomTurnoverReward(CreditRoomTurnoverRewardRequest) returns (CreditRoomTurnoverRewardResponse); + rpc CreditInviteActivityReward(CreditInviteActivityRewardRequest) returns (CreditInviteActivityRewardResponse); rpc ApplyGameCoinChange(ApplyGameCoinChangeRequest) returns (ApplyGameCoinChangeResponse); rpc GetRedPacketConfig(GetRedPacketConfigRequest) returns (GetRedPacketConfigResponse); rpc UpdateRedPacketConfig(UpdateRedPacketConfigRequest) returns (UpdateRedPacketConfigResponse); diff --git a/api/proto/wallet/v1/wallet_grpc.pb.go b/api/proto/wallet/v1/wallet_grpc.pb.go index 8d1b7a14..808819e7 100644 --- a/api/proto/wallet/v1/wallet_grpc.pb.go +++ b/api/proto/wallet/v1/wallet_grpc.pb.go @@ -227,6 +227,7 @@ const ( WalletService_CreateGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateGiftConfig" WalletService_UpdateGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateGiftConfig" WalletService_SetGiftConfigStatus_FullMethodName = "/hyapp.wallet.v1.WalletService/SetGiftConfigStatus" + WalletService_DeleteGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/DeleteGiftConfig" WalletService_UpsertGiftTypeConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpsertGiftTypeConfig" WalletService_GrantResource_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantResource" WalletService_GrantResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantResourceGroup" @@ -269,6 +270,7 @@ const ( WalletService_CreditTaskReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditTaskReward" WalletService_CreditLuckyGiftReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditLuckyGiftReward" WalletService_CreditRoomTurnoverReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditRoomTurnoverReward" + WalletService_CreditInviteActivityReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditInviteActivityReward" WalletService_ApplyGameCoinChange_FullMethodName = "/hyapp.wallet.v1.WalletService/ApplyGameCoinChange" WalletService_GetRedPacketConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/GetRedPacketConfig" WalletService_UpdateRedPacketConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateRedPacketConfig" @@ -312,6 +314,7 @@ type WalletServiceClient interface { CreateGiftConfig(ctx context.Context, in *CreateGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) UpdateGiftConfig(ctx context.Context, in *UpdateGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) SetGiftConfigStatus(ctx context.Context, in *SetGiftConfigStatusRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) + DeleteGiftConfig(ctx context.Context, in *DeleteGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) UpsertGiftTypeConfig(ctx context.Context, in *UpsertGiftTypeConfigRequest, opts ...grpc.CallOption) (*GiftTypeConfigResponse, error) GrantResource(ctx context.Context, in *GrantResourceRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) GrantResourceGroup(ctx context.Context, in *GrantResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) @@ -354,6 +357,7 @@ type WalletServiceClient interface { CreditTaskReward(ctx context.Context, in *CreditTaskRewardRequest, opts ...grpc.CallOption) (*CreditTaskRewardResponse, error) CreditLuckyGiftReward(ctx context.Context, in *CreditLuckyGiftRewardRequest, opts ...grpc.CallOption) (*CreditLuckyGiftRewardResponse, error) CreditRoomTurnoverReward(ctx context.Context, in *CreditRoomTurnoverRewardRequest, opts ...grpc.CallOption) (*CreditRoomTurnoverRewardResponse, error) + CreditInviteActivityReward(ctx context.Context, in *CreditInviteActivityRewardRequest, opts ...grpc.CallOption) (*CreditInviteActivityRewardResponse, error) ApplyGameCoinChange(ctx context.Context, in *ApplyGameCoinChangeRequest, opts ...grpc.CallOption) (*ApplyGameCoinChangeResponse, error) GetRedPacketConfig(ctx context.Context, in *GetRedPacketConfigRequest, opts ...grpc.CallOption) (*GetRedPacketConfigResponse, error) UpdateRedPacketConfig(ctx context.Context, in *UpdateRedPacketConfigRequest, opts ...grpc.CallOption) (*UpdateRedPacketConfigResponse, error) @@ -633,6 +637,16 @@ func (c *walletServiceClient) SetGiftConfigStatus(ctx context.Context, in *SetGi return out, nil } +func (c *walletServiceClient) DeleteGiftConfig(ctx context.Context, in *DeleteGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GiftConfigResponse) + err := c.cc.Invoke(ctx, WalletService_DeleteGiftConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) UpsertGiftTypeConfig(ctx context.Context, in *UpsertGiftTypeConfigRequest, opts ...grpc.CallOption) (*GiftTypeConfigResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GiftTypeConfigResponse) @@ -1053,6 +1067,16 @@ func (c *walletServiceClient) CreditRoomTurnoverReward(ctx context.Context, in * return out, nil } +func (c *walletServiceClient) CreditInviteActivityReward(ctx context.Context, in *CreditInviteActivityRewardRequest, opts ...grpc.CallOption) (*CreditInviteActivityRewardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreditInviteActivityRewardResponse) + err := c.cc.Invoke(ctx, WalletService_CreditInviteActivityReward_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) ApplyGameCoinChange(ctx context.Context, in *ApplyGameCoinChangeRequest, opts ...grpc.CallOption) (*ApplyGameCoinChangeResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ApplyGameCoinChangeResponse) @@ -1175,6 +1199,7 @@ type WalletServiceServer interface { CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error) UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error) SetGiftConfigStatus(context.Context, *SetGiftConfigStatusRequest) (*GiftConfigResponse, error) + DeleteGiftConfig(context.Context, *DeleteGiftConfigRequest) (*GiftConfigResponse, error) UpsertGiftTypeConfig(context.Context, *UpsertGiftTypeConfigRequest) (*GiftTypeConfigResponse, error) GrantResource(context.Context, *GrantResourceRequest) (*ResourceGrantResponse, error) GrantResourceGroup(context.Context, *GrantResourceGroupRequest) (*ResourceGrantResponse, error) @@ -1217,6 +1242,7 @@ type WalletServiceServer interface { CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error) CreditRoomTurnoverReward(context.Context, *CreditRoomTurnoverRewardRequest) (*CreditRoomTurnoverRewardResponse, error) + CreditInviteActivityReward(context.Context, *CreditInviteActivityRewardRequest) (*CreditInviteActivityRewardResponse, error) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) GetRedPacketConfig(context.Context, *GetRedPacketConfigRequest) (*GetRedPacketConfigResponse, error) UpdateRedPacketConfig(context.Context, *UpdateRedPacketConfigRequest) (*UpdateRedPacketConfigResponse, error) @@ -1314,6 +1340,9 @@ func (UnimplementedWalletServiceServer) UpdateGiftConfig(context.Context, *Updat func (UnimplementedWalletServiceServer) SetGiftConfigStatus(context.Context, *SetGiftConfigStatusRequest) (*GiftConfigResponse, error) { return nil, status.Error(codes.Unimplemented, "method SetGiftConfigStatus not implemented") } +func (UnimplementedWalletServiceServer) DeleteGiftConfig(context.Context, *DeleteGiftConfigRequest) (*GiftConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteGiftConfig not implemented") +} func (UnimplementedWalletServiceServer) UpsertGiftTypeConfig(context.Context, *UpsertGiftTypeConfigRequest) (*GiftTypeConfigResponse, error) { return nil, status.Error(codes.Unimplemented, "method UpsertGiftTypeConfig not implemented") } @@ -1440,6 +1469,9 @@ func (UnimplementedWalletServiceServer) CreditLuckyGiftReward(context.Context, * func (UnimplementedWalletServiceServer) CreditRoomTurnoverReward(context.Context, *CreditRoomTurnoverRewardRequest) (*CreditRoomTurnoverRewardResponse, error) { return nil, status.Error(codes.Unimplemented, "method CreditRoomTurnoverReward not implemented") } +func (UnimplementedWalletServiceServer) CreditInviteActivityReward(context.Context, *CreditInviteActivityRewardRequest) (*CreditInviteActivityRewardResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreditInviteActivityReward not implemented") +} func (UnimplementedWalletServiceServer) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) { return nil, status.Error(codes.Unimplemented, "method ApplyGameCoinChange not implemented") } @@ -1956,6 +1988,24 @@ func _WalletService_SetGiftConfigStatus_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } +func _WalletService_DeleteGiftConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteGiftConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).DeleteGiftConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_DeleteGiftConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).DeleteGiftConfig(ctx, req.(*DeleteGiftConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_UpsertGiftTypeConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpsertGiftTypeConfigRequest) if err := dec(in); err != nil { @@ -2712,6 +2762,24 @@ func _WalletService_CreditRoomTurnoverReward_Handler(srv interface{}, ctx contex return interceptor(ctx, in, info, handler) } +func _WalletService_CreditInviteActivityReward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreditInviteActivityRewardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).CreditInviteActivityReward(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_CreditInviteActivityReward_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).CreditInviteActivityReward(ctx, req.(*CreditInviteActivityRewardRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_ApplyGameCoinChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ApplyGameCoinChangeRequest) if err := dec(in); err != nil { @@ -2985,6 +3053,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SetGiftConfigStatus", Handler: _WalletService_SetGiftConfigStatus_Handler, }, + { + MethodName: "DeleteGiftConfig", + Handler: _WalletService_DeleteGiftConfig_Handler, + }, { MethodName: "UpsertGiftTypeConfig", Handler: _WalletService_UpsertGiftTypeConfig_Handler, @@ -3153,6 +3225,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CreditRoomTurnoverReward", Handler: _WalletService_CreditRoomTurnoverReward_Handler, }, + { + MethodName: "CreditInviteActivityReward", + Handler: _WalletService_CreditInviteActivityReward_Handler, + }, { MethodName: "ApplyGameCoinChange", Handler: _WalletService_ApplyGameCoinChange_Handler, diff --git a/docker-compose.yml b/docker-compose.yml index 9c1e27c9..8f2164b8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -289,6 +289,7 @@ services: - ${DEPLOY_PLATFORM_ROOT:-../deploy-platform}/deploy/mysql/initdb/007_resource_group_wallet_asset_items.sql:/docker-entrypoint-initdb.d/007_resource_group_wallet_asset_items.sql:ro - ./services/cron-service/deploy/mysql/initdb/001_cron_service.sql:/docker-entrypoint-initdb.d/009_cron_service.sql:ro - ./services/game-service/deploy/mysql/initdb/001_game_service.sql:/docker-entrypoint-initdb.d/010_game_service.sql:ro + - ./services/game-service/deploy/mysql/initdata/001_self_games.sql:/docker-entrypoint-initdb.d/010_game_service_data.sql:ro - ./services/notice-service/deploy/mysql/initdb/001_notice_service.sql:/docker-entrypoint-initdb.d/011_notice_service.sql:ro - ./services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql:/docker-entrypoint-initdb.d/012_statistics_service.sql:ro - ${DEPLOY_PLATFORM_ROOT:-../deploy-platform}/deploy/mysql/initdb/999_local_grants.sql:/docker-entrypoint-initdb.d/999_local_grants.sql:ro diff --git a/docs/flutter对接/公共跳转参数.md b/docs/flutter对接/公共跳转参数.md new file mode 100644 index 00000000..7bf4712a --- /dev/null +++ b/docs/flutter对接/公共跳转参数.md @@ -0,0 +1,212 @@ +# 公共跳转参数 + +## 地址 + +无。 + +这不是一个接口,是后台运营配置、H5 bridge、IM custom data 共用的一套 App 跳转参数。 + +## 用在哪里 + +- Admin Banner:`type` 选 `APP`,`param` 填下面的 JSON。 +- Admin 开屏配置:`type` 选 `APP`,`param` 填下面的 JSON。 +- Admin 弹窗配置:`跳转类型` 选 `APP`,使用公共跳转组件生成下面的 JSON。 +- H5 bridge:通过 `taskAction` 或其它业务 action 传下面的 JSON。 +- 全服飘窗 IM:传 `jump_type`、`jump_param` 或 `jump_payload`。 + +## 参数 + +### 钱包 + +打开钱包页。 + +```json +{"type":"wallet"} +``` + +### 指定房间 + +进入指定房间。 + +```json +{"type":"room","room_id":"ROOM_ID"} +``` + +### 随机房间 + +进入一个随机公开房间。 + +```json +{"type":"room_random"} +``` + +### 指定房内拉起窗口 + +进入指定房间后打开房内窗口。 + +```json +{"type":"room_window","room_id":"ROOM_ID","window":"gift_panel"} +``` + +### 随机房内拉起窗口 + +进入随机公开房间后打开房内窗口。 + +```json +{"type":"room_random_window","window":"gift_panel"} +``` + +### 指定房内拉起游戏 + +进入指定房间后打开指定游戏。 + +```json +{"type":"room_game","room_id":"ROOM_ID","game_id":"GAME_ID"} +``` + +### 随机房内拉起游戏 + +进入随机公开房间后打开指定游戏。 + +```json +{"type":"room_random_game","game_id":"GAME_ID"} +``` + +### Explore 游戏 H5 + +App 先切到 Explore tab,再打开对应的 H5。 + +```json +{"type":"explore_game","game_code":"game_dice"} +``` + +`game_code` 可填: + +- `game_dice`:打开 Explore 的 Dice Battle H5,对应 Flutter H5 key `game_dice`。 +- `game_rock`:打开 Explore 的 Rock Paper Scissors H5,对应 Flutter H5 key `game_rock`。 + +## window 可填值 + +### 游戏窗口 + +打开房内游戏窗口。带 `game_id` 时直接打开这个游戏;不带 `game_id` 时打开游戏菜单。 + +```json +{"type":"room_window","room_id":"ROOM_ID","window":"game_window","game_id":"GAME_ID"} +``` + +### 礼物面板 + +打开礼物面板。带 `gift_id` 时预选这个礼物。 + +```json +{"type":"room_window","room_id":"ROOM_ID","window":"gift_panel","gift_id":"GIFT_ID"} +``` + +### 领红包弹窗 + +打开领红包弹窗。带 `red_packet_no` 时打开指定红包。 + +```json +{"type":"room_window","room_id":"ROOM_ID","window":"red_packet_claim","red_packet_no":"RED_PACKET_NO"} +``` + +### 转盘窗口 + +打开房内转盘窗口。 + +```json +{"type":"room_window","room_id":"ROOM_ID","window":"wheel"} +``` + +## Admin 配置方式 + +后台公共跳转组件里: + +- `跳转类型` 选 `APP`。 +- `APP 目标` 选需要的目标。 +- 目标是 `Explore 游戏 H5` 时,继续选择 `Game Code`。 +- 保存后后台会生成 `param` JSON。 + +Explore 游戏 H5 的后台示例: + +```json +{"type":"explore_game","game_code":"game_rock"} +``` + +## 返回值 + +无。 + +这是 App 内部跳转参数,没有服务端返回值。 + +## 相关 IM + +全服飘窗使用 IM custom message。App 会解析 `jump_type`、`jump_param`、`jump_payload`。 + +### 指定 jump_payload + +```json +{ + "broadcast_type": "game_win", + "room_id": "ROOM_ID", + "game_id": "GAME_ID", + "coins": 1000, + "jump_type": "room_window", + "jump_payload": { + "room_id": "ROOM_ID", + "window": "gift_panel", + "gift_id": "GIFT_ID" + } +} +``` + +### 跳钱包 + +```json +{ + "broadcast_type": "game_win", + "coins": 1000, + "jump_type": "wallet", + "jump_payload": {} +} +``` + +### 随机房拉起游戏 + +```json +{ + "broadcast_type": "game_win", + "coins": 1000, + "jump_type": "room_random_game", + "jump_payload": { + "game_id": "GAME_ID" + } +} +``` + +### 跳 Explore 游戏 H5 + +```json +{ + "broadcast_type": "game_win", + "coins": 1000, + "jump_type": "explore_game", + "jump_payload": { + "game_code": "game_dice" + } +} +``` + +也可以把 `game_code` 放在 `jump_param`: + +```json +{ + "broadcast_type": "game_win", + "coins": 1000, + "jump_type": "explore_game", + "jump_param": "game_rock" +} +``` + +没有 `jump_type` 时,App 使用 IM 里的 `room_id` 和 `game_id` 作为默认跳转。 diff --git a/docs/flutter对接/弹窗配置Flutter对接.md b/docs/flutter对接/弹窗配置Flutter对接.md new file mode 100644 index 00000000..419b5700 --- /dev/null +++ b/docs/flutter对接/弹窗配置Flutter对接.md @@ -0,0 +1,72 @@ +# 弹窗配置 Flutter 对接 + +## 地址 + +`GET /api/v1/app/popups` + +公开接口,不需要登录。返回后台 `APP配置 -> 弹窗配置` 中当前有效的弹窗列表。 + +## 参数 + +| 参数 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `X-App-Code` | Header | 否 | App 编码,默认 `lalu`。 | +| `X-App-Package` | Header | 否 | 包名或 Bundle ID,用于服务端解析 App。 | + +示例: + +```http +GET /api/v1/app/popups +X-App-Code: lalu +``` + +## 返回值 + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_abc", + "data": { + "items": [ + { + "id": 21, + "code": "new-user-popup", + "name": "新人活动", + "image_url": "https://cdn.example.com/popup.png", + "jump_type": "h5", + "jump_url": "https://h5.example.com/new-user", + "display_period_days": 1, + "sort_order": 1, + "starts_at_ms": 1700000000000, + "ends_at_ms": 1800000000000, + "updated_at_ms": 1700000004000 + } + ], + "total": 1 + } +} +``` + +| 字段 | 说明 | +| --- | --- | +| `items` | 当前可展示的弹窗配置列表,已按 `sort_order ASC, id DESC` 排序。 | +| `code` | 弹窗编码,同一个 App 下唯一,用于 Flutter 本地频控和埋点识别。 | +| `name` | 弹窗名字。 | +| `image_url` | 弹窗图片地址,可能为空。 | +| `jump_type` | `h5` 或 `app`。 | +| `jump_url` | `jump_type=h5` 时是 H5 链接;`jump_type=app` 时由 Flutter 解释。 | +| `display_period_days` | 展示周期。`0` 表示每次打开 App 都展示;`1` 表示 1 天展示一次;`2`、`3`、`4` 同理。 | +| `starts_at_ms` / `ends_at_ms` | 弹窗生效时间。`0` 表示不限。 | + +Flutter 处理规则: + +1. 调接口后按 `items` 顺序取第一个满足本地频控的弹窗。 +2. 用 `popup_last_show:{code}:{updated_at_ms}` 记录本地最后展示时间。 +3. `display_period_days=0` 不读本地间隔,每次打开都可以展示。 +4. `display_period_days>0` 时,距离上次展示满对应天数后再展示。 +5. 用户点击弹窗时,`jump_type=h5` 打开 H5;`jump_type=app` 走 App 内路由。 + +## 相关 IM + +无。 diff --git a/docs/flutter对接/礼物墙Flutter对接.md b/docs/flutter对接/礼物墙Flutter对接.md index 87c72cdf..f1bbd931 100644 --- a/docs/flutter对接/礼物墙Flutter对接.md +++ b/docs/flutter对接/礼物墙Flutter对接.md @@ -1,12 +1,17 @@ # Flutter 礼物墙对接 -本文描述 Flutter App 查询当前登录用户礼物墙的 HTTP 接口。礼物墙事实由 `wallet-service` 在送礼扣费成功后通过异步投影聚合,客户端只展示 gateway 返回的当前投影,不从房间消息或本地缓存推算收礼数量。 +本文描述 Flutter App 查询用户礼物墙的 HTTP 接口。礼物墙事实由 `wallet-service` 在送礼扣费成功后通过异步投影聚合,客户端只展示 gateway 返回的当前投影,不从房间消息或本地缓存推算收礼数量。 ## 接口 `GET /api/v1/users/me/gift-wall` -登录接口,需要 `Authorization: Bearer `。接口固定查询当前登录用户,不接受客户端传 `user_id`。 +`GET /api/v1/users/{user_id}/gift-wall` + +登录接口,需要 `Authorization: Bearer `。 + +- `GET /api/v1/users/me/gift-wall` 查询当前登录用户。 +- `GET /api/v1/users/{user_id}/gift-wall` 查询资料卡目标用户,`user_id` 是路径参数,必须是正整数。 一致性边界:礼物墙是最终一致读模型,送礼成功后允许延迟数秒到几十秒才出现在接口结果中。客户端不要用该接口判断刚刚送礼是否成功;送礼成功状态仍以送礼接口响应和房间 IM 消息为准。 @@ -25,6 +30,12 @@ Authorization: Bearer X-App-Code: lalu ``` +```http +GET /api/v1/users/10002/gift-wall +Authorization: Bearer +X-App-Code: lalu +``` + 成功响应: ```json diff --git a/docs/openapi/gateway.swagger.yaml b/docs/openapi/gateway.swagger.yaml index e8359b26..c6c33c86 100644 --- a/docs/openapi/gateway.swagger.yaml +++ b/docs/openapi/gateway.swagger.yaml @@ -434,6 +434,21 @@ paths: $ref: "#/definitions/SplashScreenListEnvelope" "502": $ref: "#/responses/UpstreamError" + /api/v1/app/popups: + get: + tags: + - app-config + summary: 获取 App 弹窗配置 + operationId: listAppPopups + description: 公开读接口,返回后台 APP配置/弹窗配置 中当前有效的弹窗列表。客户端按列表顺序选择弹窗,并按 display_period_days 做本地展示频控。 + parameters: + responses: + "200": + description: 查询成功,`data.items` 返回当前可展示的弹窗配置。 + schema: + $ref: "#/definitions/PopupListEnvelope" + "502": + $ref: "#/responses/UpstreamError" /api/v1/im/usersig: get: tags: @@ -792,6 +807,29 @@ paths: $ref: "#/responses/Internal" "502": $ref: "#/responses/UpstreamError" + /api/v1/users/me/invite-overview: + get: + tags: + - users + summary: 查询当前用户邀请概览 + operationId: getMyInviteOverview + description: 返回当前登录用户自己的邀请码和邀请计数;该接口只服务私有 H5 分享页,不并入我的页首屏聚合摘要。 + security: + - BearerAuth: [] + parameters: + responses: + "200": + description: 查询成功,`data` 返回当前用户邀请概览。 + schema: + $ref: "#/definitions/MyInviteOverviewEnvelope" + "401": + $ref: "#/responses/Unauthorized" + "403": + $ref: "#/responses/Forbidden" + "500": + $ref: "#/responses/Internal" + "502": + $ref: "#/responses/UpstreamError" /api/v1/users/me/onboarding/complete: post: tags: @@ -3016,6 +3054,28 @@ definitions: server_time_ms: type: integer format: int64 + MyInviteOverviewData: + type: object + required: + - my_invite_code + - invite_enabled + - invite_count + - valid_invite_count + - valid_invite_threshold_coin + properties: + my_invite_code: + type: string + invite_enabled: + type: boolean + invite_count: + type: integer + format: int64 + valid_invite_count: + type: integer + format: int64 + valid_invite_threshold_coin: + type: integer + format: int64 UserProfileData: type: object properties: @@ -3293,13 +3353,7 @@ definitions: properties: key: type: string - enum: - - host-center - - manager-center - - bd-center - - bd-leader-center - - agency-center - - invite-user + description: 后台 H5 配置 key,App 只对已接入的 key 做入口展示,例如 rank、explore_game_rank、game_rock、game_dice、vip、level、task。 label: type: string url: @@ -3366,6 +3420,53 @@ definitions: total: type: integer format: int32 + PopupData: + type: object + properties: + id: + type: integer + format: uint64 + code: + type: string + description: 弹窗编码,同一个 App 下唯一,用于客户端本地频控和埋点识别。 + name: + type: string + description: 弹窗名字。 + image_url: + type: string + description: 弹窗图片 URL;为空表示无图片。 + jump_type: + type: string + enum: [h5, app] + description: h5 表示 jump_url 是 H5 链接;app 表示 jump_url 由客户端解释。 + jump_url: + type: string + display_period_days: + type: integer + format: int32 + description: 展示周期天数;0 表示每次打开都展示。 + sort_order: + type: integer + format: int32 + starts_at_ms: + type: integer + format: int64 + ends_at_ms: + type: integer + format: int64 + updated_at_ms: + type: integer + format: int64 + PopupListData: + type: object + properties: + items: + type: array + items: + $ref: "#/definitions/PopupData" + total: + type: integer + format: int32 AppBootstrapTab: type: object properties: @@ -5125,6 +5226,13 @@ definitions: properties: data: $ref: "#/definitions/MyOverviewData" + MyInviteOverviewEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/MyInviteOverviewData" UserProfileEnvelope: allOf: - $ref: "#/definitions/GatewayOKEnvelopeBase" @@ -5216,6 +5324,13 @@ definitions: properties: data: $ref: "#/definitions/SplashScreenListData" + PopupListEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/PopupListData" ResourceListEnvelope: allOf: - $ref: "#/definitions/GatewayOKEnvelopeBase" diff --git a/docs/接口清单.md b/docs/接口清单.md index f637567a..f157b13c 100644 --- a/docs/接口清单.md +++ b/docs/接口清单.md @@ -19,6 +19,7 @@ | GET | `/api/v1/app/bootstrap` | app-config | `getAppBootstrap` | 获取 App 启动配置摘要 | | GET | `/api/v1/app/h5-links` | app-config | `listAppH5Links` | 获取 App H5 入口地址 | | GET | `/api/v1/app/splash-screens` | app-config | `listSplashScreens` | 获取 App 开屏配置 | +| GET | `/api/v1/app/popups` | app-config | `listAppPopups` | 获取 App 弹窗配置 | | POST | `/api/v1/app/heartbeat` | app | `appHeartbeat` | 刷新当前 App 登录会话心跳 | | GET | `/api/v1/im/usersig` | tencent-im | `issueTencentIMUserSig` | 获取腾讯云 IM UserSig | | POST | `/api/v1/rtc/token` | tencent-rtc | `issueTencentRTCToken` | 获取腾讯 RTC 进房 UserSig | @@ -37,7 +38,9 @@ | GET | `/api/v1/users/me/identity` | users | `getMyIdentity` | 查询当前用户展示短号状态 | | GET | `/api/v1/users/me/host-identity` | users | `getMyHostIdentity` | 查询当前用户 Host/Agency/BD 身份 | | GET | `/api/v1/users/me/overview` | users | `getMyOverview` | 我的页首屏聚合摘要 | +| GET | `/api/v1/users/me/invite-overview` | users | `getMyInviteOverview` | 查询当前用户邀请码和邀请计数 | | GET | `/api/v1/users/me/gift-wall` | users | `getMyGiftWall` | 查询当前用户收到的礼物墙聚合 | +| GET | `/api/v1/users/{user_id}/gift-wall` | users | `getUserGiftWall` | 查询资料卡用户收到的礼物墙聚合 | | GET | `/api/v1/users/me/role-summary` | users | `getMyRoleSummary` | 查询当前用户角色摘要 | | POST | `/api/v1/users/me/onboarding/complete` | users | `completeMyOnboarding` | 完成注册页必填资料 | | POST | `/api/v1/users/me/profile/update` | users | `updateMyProfile` | 修改当前用户基础资料 | diff --git a/scripts/mysql/037_user_invite_valid_recharge_sources.sql b/scripts/mysql/037_user_invite_valid_recharge_sources.sql new file mode 100644 index 00000000..f56dbb68 --- /dev/null +++ b/scripts/mysql/037_user_invite_valid_recharge_sources.sql @@ -0,0 +1,6 @@ +UPDATE user_invite_validity_policies +SET eligible_recharge_types = JSON_ARRAY('coin_seller_transfer', 'google_play', 'google', 'mifapay', 'usdt_trc20'), + updated_at_ms = UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 +WHERE app_code = 'lalu' + AND region_id = 0 + AND policy_version = 'invite-valid-default-v1'; diff --git a/scripts/mysql/044_game_explore_winners.sql b/scripts/mysql/044_game_explore_winners.sql new file mode 100644 index 00000000..3f0a710f --- /dev/null +++ b/scripts/mysql/044_game_explore_winners.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS game_explore_winners ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + win_id VARCHAR(96) NOT NULL COMMENT 'Explore Square 播报 ID', + game_code VARCHAR(64) NOT NULL COMMENT 'App H5 入口 key,例如 game_dice/game_rock', + game_id VARCHAR(96) NOT NULL COMMENT '来源游戏 ID', + user_id BIGINT NOT NULL COMMENT '获胜用户 ID', + display_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '展示昵称快照', + avatar_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '展示头像快照', + coin_amount BIGINT NOT NULL COMMENT '获胜金币数', + won_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, win_id), + KEY idx_game_explore_winners_time(app_code, won_at_ms, win_id), + KEY idx_game_explore_winners_game(app_code, game_code, won_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Explore Square 游戏获胜播报表'; diff --git a/server/admin/cmd/server/main.go b/server/admin/cmd/server/main.go index 20dba1c6..49a8ce59 100644 --- a/server/admin/cmd/server/main.go +++ b/server/admin/cmd/server/main.go @@ -43,6 +43,7 @@ import ( hostagencypolicymodule "hyapp-admin-server/internal/modules/hostagencypolicy" hostorgmodule "hyapp-admin-server/internal/modules/hostorg" hostsalarysettlementmodule "hyapp-admin-server/internal/modules/hostsalarysettlement" + inviteactivityrewardmodule "hyapp-admin-server/internal/modules/inviteactivityreward" jobmodule "hyapp-admin-server/internal/modules/job" levelconfigmodule "hyapp-admin-server/internal/modules/levelconfig" luckygiftmodule "hyapp-admin-server/internal/modules/luckygift" @@ -248,6 +249,7 @@ func main() { HostAgencyPolicy: hostagencypolicymodule.New(store, walletDB, auditHandler), HostOrg: hostorgmodule.New(userclient.NewGRPC(userConn), walletclient.NewGRPC(walletConn), userDB, walletDB, sqlDB, auditHandler), HostSalarySettlement: hostsalarysettlementmodule.New(walletDB, userDB), + InviteActivityReward: inviteactivityrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), Job: jobmodule.New(store, cfg, auditHandler), LevelConfig: levelconfigmodule.New(activityclient.NewGRPC(activityConn), walletclient.NewGRPC(walletConn), auditHandler), LuckyGift: luckygiftmodule.New(activityclient.NewGRPC(activityConn), cfg.ActivityService.RequestTimeout, auditHandler), diff --git a/server/admin/docs/权限管理.md b/server/admin/docs/权限管理.md index 3cb305c4..cd2cd3dd 100644 --- a/server/admin/docs/权限管理.md +++ b/server/admin/docs/权限管理.md @@ -292,6 +292,7 @@ | `gift:create` | `button` | 从资源库选择资源新增礼物 | | `gift:update` | `button` | 编辑礼物、资源绑定和价格 | | `gift:status` | `button` | 上架、下架 | +| `gift:delete` | `button` | 删除礼物列表配置 | | `activity:view` | `menu` | 活动列表 | | `activity:create` | `button` | 新增活动 | | `activity:update` | `button` | 编辑活动 | diff --git a/server/admin/internal/integration/activityclient/client.go b/server/admin/internal/integration/activityclient/client.go index 35d37b21..77a2f5a1 100644 --- a/server/admin/internal/integration/activityclient/client.go +++ b/server/admin/internal/integration/activityclient/client.go @@ -24,6 +24,9 @@ type Client interface { GetCumulativeRechargeRewardConfig(ctx context.Context, req *activityv1.GetCumulativeRechargeRewardConfigRequest) (*activityv1.GetCumulativeRechargeRewardConfigResponse, error) UpdateCumulativeRechargeRewardConfig(ctx context.Context, req *activityv1.UpdateCumulativeRechargeRewardConfigRequest) (*activityv1.UpdateCumulativeRechargeRewardConfigResponse, error) ListCumulativeRechargeRewardGrants(ctx context.Context, req *activityv1.ListCumulativeRechargeRewardGrantsRequest) (*activityv1.ListCumulativeRechargeRewardGrantsResponse, error) + GetInviteActivityRewardConfig(ctx context.Context, req *activityv1.GetInviteActivityRewardConfigRequest) (*activityv1.GetInviteActivityRewardConfigResponse, error) + UpdateInviteActivityRewardConfig(ctx context.Context, req *activityv1.UpdateInviteActivityRewardConfigRequest) (*activityv1.UpdateInviteActivityRewardConfigResponse, error) + ListInviteActivityRewardClaims(ctx context.Context, req *activityv1.ListInviteActivityRewardClaimsRequest) (*activityv1.ListInviteActivityRewardClaimsResponse, error) GetSevenDayCheckInConfig(ctx context.Context, req *activityv1.GetSevenDayCheckInConfigRequest) (*activityv1.GetSevenDayCheckInConfigResponse, error) UpdateSevenDayCheckInConfig(ctx context.Context, req *activityv1.UpdateSevenDayCheckInConfigRequest) (*activityv1.UpdateSevenDayCheckInConfigResponse, error) ListSevenDayCheckInClaims(ctx context.Context, req *activityv1.ListSevenDayCheckInClaimsRequest) (*activityv1.ListSevenDayCheckInClaimsResponse, error) @@ -56,6 +59,7 @@ type GRPCClient struct { registrationRewardClient activityv1.AdminRegistrationRewardServiceClient firstRechargeRewardClient activityv1.AdminFirstRechargeRewardServiceClient cumulativeRechargeClient activityv1.AdminCumulativeRechargeRewardServiceClient + inviteActivityClient activityv1.AdminInviteActivityRewardServiceClient sevenDayCheckInClient activityv1.AdminSevenDayCheckInServiceClient roomTurnoverRewardClient activityv1.AdminRoomTurnoverRewardServiceClient weeklyStarClient activityv1.AdminWeeklyStarServiceClient @@ -71,6 +75,7 @@ func NewGRPC(conn grpc.ClientConnInterface) *GRPCClient { registrationRewardClient: activityv1.NewAdminRegistrationRewardServiceClient(conn), firstRechargeRewardClient: activityv1.NewAdminFirstRechargeRewardServiceClient(conn), cumulativeRechargeClient: activityv1.NewAdminCumulativeRechargeRewardServiceClient(conn), + inviteActivityClient: activityv1.NewAdminInviteActivityRewardServiceClient(conn), sevenDayCheckInClient: activityv1.NewAdminSevenDayCheckInServiceClient(conn), roomTurnoverRewardClient: activityv1.NewAdminRoomTurnoverRewardServiceClient(conn), weeklyStarClient: activityv1.NewAdminWeeklyStarServiceClient(conn), @@ -136,6 +141,18 @@ func (c *GRPCClient) ListCumulativeRechargeRewardGrants(ctx context.Context, req return c.cumulativeRechargeClient.ListCumulativeRechargeRewardGrants(ctx, req) } +func (c *GRPCClient) GetInviteActivityRewardConfig(ctx context.Context, req *activityv1.GetInviteActivityRewardConfigRequest) (*activityv1.GetInviteActivityRewardConfigResponse, error) { + return c.inviteActivityClient.GetInviteActivityRewardConfig(ctx, req) +} + +func (c *GRPCClient) UpdateInviteActivityRewardConfig(ctx context.Context, req *activityv1.UpdateInviteActivityRewardConfigRequest) (*activityv1.UpdateInviteActivityRewardConfigResponse, error) { + return c.inviteActivityClient.UpdateInviteActivityRewardConfig(ctx, req) +} + +func (c *GRPCClient) ListInviteActivityRewardClaims(ctx context.Context, req *activityv1.ListInviteActivityRewardClaimsRequest) (*activityv1.ListInviteActivityRewardClaimsResponse, error) { + return c.inviteActivityClient.ListInviteActivityRewardClaims(ctx, req) +} + func (c *GRPCClient) GetSevenDayCheckInConfig(ctx context.Context, req *activityv1.GetSevenDayCheckInConfigRequest) (*activityv1.GetSevenDayCheckInConfigResponse, error) { return c.sevenDayCheckInClient.GetSevenDayCheckInConfig(ctx, req) } diff --git a/server/admin/internal/integration/walletclient/client.go b/server/admin/internal/integration/walletclient/client.go index 22c3f8d5..2210fa6b 100644 --- a/server/admin/internal/integration/walletclient/client.go +++ b/server/admin/internal/integration/walletclient/client.go @@ -25,6 +25,7 @@ type Client interface { CreateGiftConfig(ctx context.Context, req *walletv1.CreateGiftConfigRequest) (*walletv1.GiftConfigResponse, error) UpdateGiftConfig(ctx context.Context, req *walletv1.UpdateGiftConfigRequest) (*walletv1.GiftConfigResponse, error) SetGiftConfigStatus(ctx context.Context, req *walletv1.SetGiftConfigStatusRequest) (*walletv1.GiftConfigResponse, error) + DeleteGiftConfig(ctx context.Context, req *walletv1.DeleteGiftConfigRequest) (*walletv1.GiftConfigResponse, error) 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) @@ -120,6 +121,10 @@ func (c *GRPCClient) SetGiftConfigStatus(ctx context.Context, req *walletv1.SetG return c.client.SetGiftConfigStatus(ctx, req) } +func (c *GRPCClient) DeleteGiftConfig(ctx context.Context, req *walletv1.DeleteGiftConfigRequest) (*walletv1.GiftConfigResponse, error) { + return c.client.DeleteGiftConfig(ctx, req) +} + func (c *GRPCClient) UpsertGiftTypeConfig(ctx context.Context, req *walletv1.UpsertGiftTypeConfigRequest) (*walletv1.GiftTypeConfigResponse, error) { return c.client.UpsertGiftTypeConfig(ctx, req) } diff --git a/server/admin/internal/model/models.go b/server/admin/internal/model/models.go index 5046d980..e5799db8 100644 --- a/server/admin/internal/model/models.go +++ b/server/admin/internal/model/models.go @@ -138,6 +138,27 @@ func (AppSplashScreen) TableName() string { return "admin_app_splash_screens" } +type AppPopup struct { + ID uint `gorm:"primaryKey" json:"id"` + AppCode string `gorm:"size:32;not null;default:lalu;index:idx_admin_app_popups_app_sort;uniqueIndex:uk_admin_app_popups_code" json:"appCode"` + Code string `gorm:"size:80;not null;uniqueIndex:uk_admin_app_popups_code;comment:弹窗编码" json:"code"` + Name string `gorm:"size:80;not null;comment:弹窗名称" json:"name"` + ImageURL string `gorm:"column:image_url;size:1024;not null;default:'';comment:弹窗图片 URL,可为空" json:"imageUrl"` + JumpType string `gorm:"size:16;not null;comment:跳转类型,h5 或 app" json:"jumpType"` + JumpURL string `gorm:"column:jump_url;size:2048;not null;default:'';comment:跳转地址" json:"jumpUrl"` + DisplayPeriodDays int `gorm:"column:display_period_days;not null;default:0;comment:展示周期天数,0 表示每次打开都展示" json:"displayPeriodDays"` + Status string `gorm:"size:24;index:idx_admin_app_popups_app_sort,not null;default:active" json:"status"` + SortOrder int `gorm:"index:idx_admin_app_popups_app_sort,not null;default:0" json:"sortOrder"` + StartsAtMS int64 `gorm:"column:starts_at_ms;not null;default:0;comment:生效开始时间,UTC epoch ms" json:"startsAtMs"` + EndsAtMS int64 `gorm:"column:ends_at_ms;not null;default:0;comment:生效结束时间,UTC epoch ms" json:"endsAtMs"` + CreatedAtMS int64 `gorm:"column:created_at_ms;autoCreateTime:milli" json:"createdAtMs"` + UpdatedAtMS int64 `gorm:"column:updated_at_ms;autoUpdateTime:milli" json:"updatedAtMs"` +} + +func (AppPopup) TableName() string { + return "admin_app_popups" +} + type AppVersion struct { ID uint `gorm:"primaryKey" json:"id"` AppCode string `gorm:"size:32;uniqueIndex:uk_admin_app_versions_build,not null;default:lalu" json:"appCode"` diff --git a/server/admin/internal/modules/appconfig/handler.go b/server/admin/internal/modules/appconfig/handler.go index f748a7b8..525b32b7 100644 --- a/server/admin/internal/modules/appconfig/handler.go +++ b/server/admin/internal/modules/appconfig/handler.go @@ -223,6 +223,66 @@ func (h *Handler) DeleteSplashScreen(c *gin.Context) { response.OK(c, gin.H{"deleted": true}) } +func (h *Handler) ListPopups(c *gin.Context) { + options := shared.ListOptions(c) + items, err := h.service.ListPopups(appctx.FromContext(c.Request.Context()), repository.AppPopupListOptions{ + Keyword: options.Keyword, + Status: options.Status, + }) + if err != nil { + response.ServerError(c, "获取弹窗配置失败") + return + } + response.OK(c, gin.H{"items": items, "total": len(items)}) +} + +func (h *Handler) CreatePopup(c *gin.Context) { + var request popupRequest + if err := c.ShouldBindJSON(&request); err != nil { + response.BadRequest(c, "参数不正确") + return + } + item, err := h.service.CreatePopup(appctx.FromContext(c.Request.Context()), request) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLog(c, h.audit, "create-app-popup", "admin_app_popups", "success", fmt.Sprintf("popup_id=%d", item.ID)) + response.Created(c, item) +} + +func (h *Handler) UpdatePopup(c *gin.Context) { + id, ok := popupID(c) + if !ok { + return + } + var request popupRequest + if err := c.ShouldBindJSON(&request); err != nil { + response.BadRequest(c, "参数不正确") + return + } + item, err := h.service.UpdatePopup(appctx.FromContext(c.Request.Context()), id, request) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLog(c, h.audit, "update-app-popup", "admin_app_popups", "success", fmt.Sprintf("popup_id=%d", item.ID)) + response.OK(c, item) +} + +func (h *Handler) DeletePopup(c *gin.Context) { + id, ok := popupID(c) + if !ok { + return + } + if err := h.service.DeletePopup(appctx.FromContext(c.Request.Context()), id); err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLog(c, h.audit, "delete-app-popup", "admin_app_popups", "success", fmt.Sprintf("popup_id=%d", id)) + response.OK(c, gin.H{"deleted": true}) +} + func (h *Handler) ListAppVersions(c *gin.Context) { items, err := h.service.ListAppVersions(appctx.FromContext(c.Request.Context()), repository.AppVersionListOptions{ Keyword: strings.TrimSpace(c.Query("keyword")), @@ -369,6 +429,15 @@ func splashScreenID(c *gin.Context) (uint, bool) { return uint(value), true } +func popupID(c *gin.Context) (uint, bool) { + value, err := strconv.ParseUint(c.Param("popup_id"), 10, 64) + if err != nil || value == 0 { + response.BadRequest(c, "popup id is invalid") + return 0, false + } + return uint(value), true +} + func appVersionID(c *gin.Context) (uint, bool) { value, err := strconv.ParseUint(c.Param("version_id"), 10, 64) if err != nil || value == 0 { diff --git a/server/admin/internal/modules/appconfig/request.go b/server/admin/internal/modules/appconfig/request.go index 699238ec..8aff4c3c 100644 --- a/server/admin/internal/modules/appconfig/request.go +++ b/server/admin/internal/modules/appconfig/request.go @@ -42,6 +42,19 @@ type splashScreenRequest struct { EndsAtMs int64 `json:"endsAtMs"` } +type popupRequest struct { + Code string `json:"code" binding:"required"` + Name string `json:"name" binding:"required"` + ImageURL string `json:"imageUrl"` + JumpType string `json:"jumpType" binding:"required"` + JumpURL string `json:"jumpUrl" binding:"required"` + DisplayPeriodDays int `json:"displayPeriodDays"` + Status string `json:"status" binding:"required"` + SortOrder int `json:"sortOrder"` + StartsAtMs int64 `json:"startsAtMs"` + EndsAtMs int64 `json:"endsAtMs"` +} + type appVersionRequest struct { Platform string `json:"platform" binding:"required"` Version string `json:"version" binding:"required"` diff --git a/server/admin/internal/modules/appconfig/routes.go b/server/admin/internal/modules/appconfig/routes.go index c112af9f..071421c9 100644 --- a/server/admin/internal/modules/appconfig/routes.go +++ b/server/admin/internal/modules/appconfig/routes.go @@ -32,6 +32,11 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.PUT("/admin/app-config/splash-screens/:splash_id", middleware.RequirePermission("app-config:update"), h.UpdateSplashScreen) protected.DELETE("/admin/app-config/splash-screens/:splash_id", middleware.RequirePermission("app-config:update"), h.DeleteSplashScreen) + protected.GET("/admin/app-config/popups", middleware.RequirePermission("app-config:view"), h.ListPopups) + protected.POST("/admin/app-config/popups", middleware.RequirePermission("app-config:update"), h.CreatePopup) + protected.PUT("/admin/app-config/popups/:popup_id", middleware.RequirePermission("app-config:update"), h.UpdatePopup) + protected.DELETE("/admin/app-config/popups/:popup_id", middleware.RequirePermission("app-config:update"), h.DeletePopup) + protected.GET("/admin/app-config/versions", middleware.RequirePermission("app-version:view"), h.ListAppVersions) protected.POST("/admin/app-config/versions", middleware.RequirePermission("app-version:create"), h.CreateAppVersion) protected.PUT("/admin/app-config/versions/:version_id", middleware.RequirePermission("app-version:update"), h.UpdateAppVersion) diff --git a/server/admin/internal/modules/appconfig/service.go b/server/admin/internal/modules/appconfig/service.go index a24fc063..819f63ef 100644 --- a/server/admin/internal/modules/appconfig/service.go +++ b/server/admin/internal/modules/appconfig/service.go @@ -80,6 +80,23 @@ type AppSplashScreen struct { UpdatedAtMs int64 `json:"updatedAtMs"` } +type AppPopup struct { + ID uint `json:"id"` + AppCode string `json:"appCode"` + Code string `json:"code"` + Name string `json:"name"` + ImageURL string `json:"imageUrl"` + JumpType string `json:"jumpType"` + JumpURL string `json:"jumpUrl"` + DisplayPeriodDays int `json:"displayPeriodDays"` + Status string `json:"status"` + SortOrder int `json:"sortOrder"` + StartsAtMs int64 `json:"startsAtMs"` + EndsAtMs int64 `json:"endsAtMs"` + CreatedAtMs int64 `json:"createdAtMs"` + UpdatedAtMs int64 `json:"updatedAtMs"` +} + type AppVersion struct { ID uint `json:"id"` AppCode string `json:"appCode"` @@ -311,6 +328,80 @@ func (s *AppConfigService) DeleteSplashScreen(appCode string, id uint) error { return s.store.DeleteAppSplashScreen(appctx.Normalize(appCode), id) } +func (s *AppConfigService) ListPopups(appCode string, options repository.AppPopupListOptions) ([]AppPopup, error) { + // 列表先清理已过期 active 记录,再返回后台可见数据,保证运营看到的状态和 App 实际可展示状态一致。 + options.AppCode = appctx.Normalize(appCode) + options.Status = normalizeBannerStatus(options.Status) + options.Keyword = strings.TrimSpace(options.Keyword) + if err := s.store.ExpireAppPopups(options.AppCode, time.Now().UTC().UnixMilli()); err != nil { + return nil, err + } + items, err := s.store.ListAppPopups(options) + if err != nil { + return nil, err + } + out := make([]AppPopup, 0, len(items)) + for _, item := range items { + out = append(out, appPopupFromModel(item)) + } + return out, nil +} + +func (s *AppConfigService) CreatePopup(appCode string, req popupRequest) (AppPopup, error) { + item, err := popupModelFromRequest(appctx.Normalize(appCode), req) + if err != nil { + return AppPopup{}, err + } + exists, err := s.store.HasAppPopupCode(item.AppCode, item.Code, 0) + if err != nil { + return AppPopup{}, err + } + if exists { + return AppPopup{}, errors.New("popup code already exists") + } + if err := s.store.CreateAppPopup(&item); err != nil { + return AppPopup{}, err + } + return appPopupFromModel(item), nil +} + +func (s *AppConfigService) UpdatePopup(appCode string, id uint, req popupRequest) (AppPopup, error) { + // 更新时先按 app_code 锁定原记录,再覆盖可编辑字段,避免请求体伪造租户和创建时间。 + item, err := s.store.GetAppPopup(appctx.Normalize(appCode), id) + if err != nil { + return AppPopup{}, err + } + updated, err := popupModelFromRequest(item.AppCode, req) + if err != nil { + return AppPopup{}, err + } + exists, err := s.store.HasAppPopupCode(item.AppCode, updated.Code, item.ID) + if err != nil { + return AppPopup{}, err + } + if exists { + return AppPopup{}, errors.New("popup code already exists") + } + item.Code = updated.Code + item.Name = updated.Name + item.ImageURL = updated.ImageURL + item.JumpType = updated.JumpType + item.JumpURL = updated.JumpURL + item.DisplayPeriodDays = updated.DisplayPeriodDays + item.Status = updated.Status + item.SortOrder = updated.SortOrder + item.StartsAtMS = updated.StartsAtMS + item.EndsAtMS = updated.EndsAtMS + if err := s.store.UpdateAppPopup(&item); err != nil { + return AppPopup{}, err + } + return appPopupFromModel(item), nil +} + +func (s *AppConfigService) DeletePopup(appCode string, id uint) error { + return s.store.DeleteAppPopup(appctx.Normalize(appCode), id) +} + func (s *AppConfigService) ListAppVersions(appCode string, options repository.AppVersionListOptions) ([]AppVersion, error) { options.AppCode = appctx.Normalize(appCode) options.Platform = normalizeAppPlatform(options.Platform) @@ -671,6 +762,87 @@ func appSplashScreenFromModel(item model.AppSplashScreen) AppSplashScreen { } } +func popupModelFromRequest(appCode string, req popupRequest) (model.AppPopup, error) { + item := model.AppPopup{ + AppCode: appctx.Normalize(appCode), + Code: strings.TrimSpace(req.Code), + Name: strings.TrimSpace(req.Name), + ImageURL: strings.TrimSpace(req.ImageURL), + JumpType: normalizeJumpType(req.JumpType), + JumpURL: strings.TrimSpace(req.JumpURL), + DisplayPeriodDays: req.DisplayPeriodDays, + Status: normalizeBannerStatus(req.Status), + SortOrder: req.SortOrder, + StartsAtMS: req.StartsAtMs, + EndsAtMS: req.EndsAtMs, + } + if err := validateH5Key(item.Code); err != nil { + return model.AppPopup{}, errors.New("popup code is invalid") + } + if item.Name == "" || utf8.RuneCountInString(item.Name) > 80 { + return model.AppPopup{}, errors.New("popup name is invalid") + } + if len(item.ImageURL) > 1024 { + return model.AppPopup{}, errors.New("popup image is too long") + } + if item.ImageURL != "" && containsWhitespace(item.ImageURL) { + return model.AppPopup{}, errors.New("popup image cannot contain whitespace") + } + if item.JumpType != "h5" && item.JumpType != "app" { + return model.AppPopup{}, errors.New("popup jump type is invalid") + } + if item.JumpURL == "" { + return model.AppPopup{}, errors.New("popup jump url is required") + } + if len(item.JumpURL) > 2048 { + return model.AppPopup{}, errors.New("popup jump url is too long") + } + if item.JumpType == "h5" { + // H5 弹窗必须是无空白的可打开地址;APP 类型由客户端解释路径,但同样不允许空白字符破坏路由解析。 + if err := validateH5URL(item.JumpURL); err != nil { + return model.AppPopup{}, err + } + } else if containsWhitespace(item.JumpURL) { + return model.AppPopup{}, errors.New("popup jump url cannot contain whitespace") + } + if item.DisplayPeriodDays < 0 { + return model.AppPopup{}, errors.New("popup display period is invalid") + } + if item.Status != bannerStatusActive && item.Status != bannerStatusDisabled && item.Status != bannerStatusExpired { + return model.AppPopup{}, errors.New("popup status is invalid") + } + if item.StartsAtMS < 0 || item.EndsAtMS < 0 { + return model.AppPopup{}, errors.New("popup effective time is invalid") + } + if item.StartsAtMS > 0 && item.EndsAtMS > 0 && item.StartsAtMS >= item.EndsAtMS { + return model.AppPopup{}, errors.New("popup effective time range is invalid") + } + if item.Status == bannerStatusActive && item.EndsAtMS > 0 && item.EndsAtMS <= time.Now().UTC().UnixMilli() { + // 已经结束的 active 弹窗保存为 expired,App 接口不会再下发,后台也能直接看到过期状态。 + item.Status = bannerStatusExpired + } + return item, nil +} + +func appPopupFromModel(item model.AppPopup) AppPopup { + return AppPopup{ + ID: item.ID, + AppCode: item.AppCode, + Code: item.Code, + Name: item.Name, + ImageURL: item.ImageURL, + JumpType: item.JumpType, + JumpURL: item.JumpURL, + DisplayPeriodDays: item.DisplayPeriodDays, + Status: item.Status, + SortOrder: item.SortOrder, + StartsAtMs: item.StartsAtMS, + EndsAtMs: item.EndsAtMS, + CreatedAtMs: item.CreatedAtMS, + UpdatedAtMs: item.UpdatedAtMS, + } +} + func appVersionModelFromRequest(appCode string, req appVersionRequest) (model.AppVersion, error) { item := model.AppVersion{ AppCode: appctx.Normalize(appCode), @@ -760,6 +932,10 @@ func normalizeSplashType(value string) string { return strings.ToLower(strings.TrimSpace(value)) } +func normalizeJumpType(value string) string { + return strings.ToLower(strings.TrimSpace(value)) +} + func normalizeSplashDisplayDurationMS(value int) int { // 管理端允许 0 表示“客户端默认 3 秒”,空请求和历史数据都统一补成 3000ms,避免 App 侧拿到缺省字段后出现 0 秒闪退式开屏。 if value == 0 { diff --git a/server/admin/internal/modules/appconfig/service_test.go b/server/admin/internal/modules/appconfig/service_test.go index 2c234eac..e1f77363 100644 --- a/server/admin/internal/modules/appconfig/service_test.go +++ b/server/admin/internal/modules/appconfig/service_test.go @@ -210,6 +210,95 @@ func TestAppSplashScreenFromModelMapsDTOFields(t *testing.T) { } } +func TestPopupModelFromRequestValidatesOptionalImageAndPeriod(t *testing.T) { + startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli() + endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond) + item, err := popupModelFromRequest("lalu", popupRequest{ + Code: "new-user-popup", + Name: "新人活动", + ImageURL: "", + JumpType: "h5", + JumpURL: "https://h5.example.com/new-user", + DisplayPeriodDays: 2, + Status: bannerStatusActive, + SortOrder: 4, + StartsAtMs: startsAtMs, + EndsAtMs: endsAtMs, + }) + if err != nil { + t.Fatalf("popup model should be valid: %v", err) + } + if item.AppCode != "lalu" || item.Code != "new-user-popup" || item.Name != "新人活动" || item.ImageURL != "" || item.JumpType != "h5" || item.DisplayPeriodDays != 2 || item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs { + t.Fatalf("popup model mismatch: %+v", item) + } +} + +func TestPopupModelFromRequestRejectsInvalidJumpURL(t *testing.T) { + _, err := popupModelFromRequest("lalu", popupRequest{ + Code: "activity-popup", + Name: "活动弹窗", + JumpType: "h5", + JumpURL: "https://h5.example.com/new user", + Status: bannerStatusActive, + }) + if err == nil { + t.Fatal("expected popup h5 jump url with whitespace to fail") + } +} + +func TestPopupModelFromRequestRejectsInvalidCode(t *testing.T) { + _, err := popupModelFromRequest("lalu", popupRequest{ + Code: "activity popup", + Name: "活动弹窗", + JumpType: "app", + JumpURL: "activity/detail", + Status: bannerStatusActive, + }) + if err == nil { + t.Fatal("expected popup code with whitespace to fail") + } +} + +func TestPopupModelFromRequestExpiresEndedActivePopup(t *testing.T) { + item, err := popupModelFromRequest("lalu", popupRequest{ + Code: "history-popup", + Name: "历史活动", + JumpType: "app", + JumpURL: "activity/detail", + Status: bannerStatusActive, + EndsAtMs: 1, + }) + if err != nil { + t.Fatalf("ended popup should be accepted: %v", err) + } + if item.Status != bannerStatusExpired { + t.Fatalf("ended active popup must become expired: %+v", item) + } +} + +func TestAppPopupFromModelMapsDTOFields(t *testing.T) { + item := appPopupFromModel(model.AppPopup{ + ID: 10, + AppCode: "lalu", + Code: "recharge-popup", + Name: "充值活动", + ImageURL: "https://cdn.example.com/popup.png", + JumpType: "app", + JumpURL: "recharge", + DisplayPeriodDays: 0, + Status: bannerStatusDisabled, + SortOrder: 8, + StartsAtMS: 1700000000000, + EndsAtMS: 1800000000000, + CreatedAtMS: 1690000000000, + UpdatedAtMS: 1700000002000, + }) + + if item.ID != 10 || item.Code != "recharge-popup" || item.Name != "充值活动" || item.JumpType != "app" || item.JumpURL != "recharge" || item.DisplayPeriodDays != 0 || item.UpdatedAtMs != 1700000002000 { + t.Fatalf("popup dto mismatch: %+v", item) + } +} + func bannerModel(displayScope string) model.AppBanner { return model.AppBanner{ ID: 1, diff --git a/server/admin/internal/modules/inviteactivityreward/handler.go b/server/admin/internal/modules/inviteactivityreward/handler.go new file mode 100644 index 00000000..789f7f6b --- /dev/null +++ b/server/admin/internal/modules/inviteactivityreward/handler.go @@ -0,0 +1,356 @@ +package inviteactivityreward + +import ( + "context" + "database/sql" + "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" +) + +type Handler struct { + activity activityclient.Client + userDB *sql.DB + audit shared.OperationLogger +} + +func New(activity activityclient.Client, userDB *sql.DB, audit shared.OperationLogger) *Handler { + return &Handler{activity: activity, userDB: userDB, audit: audit} +} + +type configRequest struct { + Enabled bool `json:"enabled"` + Tiers []tierDTO `json:"tiers"` +} + +type configDTO struct { + AppCode string `json:"app_code"` + Enabled bool `json:"enabled"` + Tiers []tierDTO `json:"tiers"` + UpdatedByAdminID int64 `json:"updated_by_admin_id"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type tierDTO struct { + TierID int64 `json:"tier_id"` + RewardType string `json:"reward_type"` + TierCode string `json:"tier_code"` + TierName string `json:"tier_name"` + ThresholdCoinAmount int64 `json:"threshold_coin_amount"` + ThresholdValidInviteCount int64 `json:"threshold_valid_invite_count"` + RewardCoinAmount int64 `json:"reward_coin_amount"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type claimDTO struct { + ClaimID string `json:"claim_id"` + AppCode string `json:"app_code"` + CycleKey string `json:"cycle_key"` + UserID int64 `json:"user_id"` + User *userDTO `json:"user,omitempty"` + RewardType string `json:"reward_type"` + CommandID string `json:"command_id"` + TierID int64 `json:"tier_id"` + TierCode string `json:"tier_code"` + TierName string `json:"tier_name"` + ThresholdCoinAmount int64 `json:"threshold_coin_amount"` + ThresholdValidInviteCount int64 `json:"threshold_valid_invite_count"` + ReachedValue int64 `json:"reached_value"` + RewardCoinAmount int64 `json:"reward_coin_amount"` + Status string `json:"status"` + WalletCommandID string `json:"wallet_command_id"` + WalletTransactionID string `json:"wallet_transaction_id"` + FailureReason string `json:"failure_reason"` + GrantedAtMS int64 `json:"granted_at_ms"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type userDTO struct { + UserID int64 `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + Username string `json:"username"` + Avatar string `json:"avatar"` +} + +func (h *Handler) GetConfig(c *gin.Context) { + resp, err := h.activity.GetInviteActivityRewardConfig(c.Request.Context(), &activityv1.GetInviteActivityRewardConfigRequest{Meta: h.meta(c)}) + if err != nil { + response.ServerError(c, "获取邀请活动配置失败") + return + } + response.OK(c, configFromProto(resp.GetConfig())) +} + +func (h *Handler) UpdateConfig(c *gin.Context) { + var req configRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "邀请活动配置参数不正确") + return + } + tiers := make([]*activityv1.InviteActivityRewardTier, 0, len(req.Tiers)) + for _, tier := range req.Tiers { + // admin-server 只裁剪字符串和注入操作人;档位门槛、奖励金额、最高档领取语义都由 activity-service 统一校验。 + tiers = append(tiers, &activityv1.InviteActivityRewardTier{ + TierId: tier.TierID, + RewardType: strings.TrimSpace(tier.RewardType), + TierCode: strings.TrimSpace(tier.TierCode), + TierName: strings.TrimSpace(tier.TierName), + ThresholdCoinAmount: tier.ThresholdCoinAmount, + ThresholdValidInviteCount: tier.ThresholdValidInviteCount, + RewardCoinAmount: tier.RewardCoinAmount, + Status: strings.TrimSpace(tier.Status), + SortOrder: tier.SortOrder, + }) + } + resp, err := h.activity.UpdateInviteActivityRewardConfig(c.Request.Context(), &activityv1.UpdateInviteActivityRewardConfigRequest{ + Meta: h.meta(c), + Enabled: req.Enabled, + Tiers: tiers, + OperatorAdminId: int64(middleware.CurrentUserID(c)), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + item := configFromProto(resp.GetConfig()) + shared.OperationLogWithResourceID(c, h.audit, "update-invite-activity-reward", "invite_activity_reward_configs", item.AppCode, "success", "") + response.OK(c, item) +} + +func (h *Handler) ListClaims(c *gin.Context) { + options := shared.ListOptions(c) + userID, matched, ok := h.resolveUserID(c.Request.Context(), strings.TrimSpace(options.Keyword)) + if !ok { + response.ServerError(c, "查询用户信息失败") + return + } + if options.Keyword != "" && !matched { + response.OK(c, response.Page{Items: []claimDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0}) + return + } + resp, err := h.activity.ListInviteActivityRewardClaims(c.Request.Context(), &activityv1.ListInviteActivityRewardClaimsRequest{ + Meta: h.meta(c), + Status: strings.TrimSpace(options.Status), + RewardType: strings.TrimSpace(c.Query("reward_type")), + UserId: userID, + CycleKey: strings.TrimSpace(c.Query("cycle_key")), + Page: int32(options.Page), + PageSize: int32(options.PageSize), + }) + if err != nil { + response.ServerError(c, "获取邀请活动领取记录失败") + return + } + items := make([]claimDTO, 0, len(resp.GetClaims())) + for _, claim := range resp.GetClaims() { + items = append(items, claimFromProto(claim)) + } + if err := h.fillUsers(c.Request.Context(), items); err != nil { + response.ServerError(c, "补全用户信息失败") + return + } + response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()}) +} + +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().UnixMilli(), + } +} + +func (h *Handler) resolveUserID(ctx context.Context, keyword string) (int64, bool, bool) { + if keyword == "" { + return 0, false, true + } + if h.userDB == nil { + return 0, false, true + } + appCode := appctx.FromContext(ctx) + rows, err := h.userDB.QueryContext(ctx, ` + SELECT user_id + FROM users + WHERE app_code = ? + AND ( + CAST(user_id AS CHAR) = ? + OR current_display_user_id = ? + OR username LIKE ? + ) + ORDER BY + CASE + WHEN CAST(user_id AS CHAR) = ? THEN 0 + WHEN current_display_user_id = ? THEN 1 + ELSE 2 + END, + user_id DESC + LIMIT 1`, + appCode, keyword, keyword, "%"+keyword+"%", keyword, keyword, + ) + if err != nil { + return 0, false, false + } + defer rows.Close() + if !rows.Next() { + return 0, false, rows.Err() == nil + } + var userID int64 + if err := rows.Scan(&userID); err != nil { + return 0, false, false + } + return userID, true, rows.Err() == nil +} + +func (h *Handler) fillUsers(ctx context.Context, claims []claimDTO) error { + if h.userDB == nil || len(claims) == 0 { + return nil + } + ids := collectUserIDs(claims) + if len(ids) == 0 { + return nil + } + args := append([]any{appctx.FromContext(ctx)}, int64Args(ids)...) + rows, err := h.userDB.QueryContext(ctx, ` + SELECT user_id, current_display_user_id, COALESCE(username, ''), COALESCE(avatar, '') + FROM users + WHERE app_code = ? AND user_id IN (`+placeholders(len(ids))+`)`, + args..., + ) + if err != nil { + return err + } + defer rows.Close() + users := make(map[int64]userDTO, len(ids)) + for rows.Next() { + var user userDTO + if err := rows.Scan(&user.UserID, &user.DisplayUserID, &user.Username, &user.Avatar); err != nil { + return err + } + users[user.UserID] = user + } + if err := rows.Err(); err != nil { + return err + } + for index := range claims { + if user, ok := users[claims[index].UserID]; ok { + claims[index].User = &user + continue + } + claims[index].User = &userDTO{UserID: claims[index].UserID} + } + return nil +} + +func configFromProto(config *activityv1.InviteActivityRewardConfig) configDTO { + if config == nil { + return configDTO{Tiers: []tierDTO{}} + } + tiers := make([]tierDTO, 0, len(config.GetTiers())) + for _, tier := range config.GetTiers() { + tiers = append(tiers, tierFromProto(tier)) + } + return configDTO{ + AppCode: config.GetAppCode(), + Enabled: config.GetEnabled(), + Tiers: tiers, + UpdatedByAdminID: config.GetUpdatedByAdminId(), + CreatedAtMS: config.GetCreatedAtMs(), + UpdatedAtMS: config.GetUpdatedAtMs(), + } +} + +func tierFromProto(tier *activityv1.InviteActivityRewardTier) tierDTO { + if tier == nil { + return tierDTO{} + } + return tierDTO{ + TierID: tier.GetTierId(), + RewardType: tier.GetRewardType(), + TierCode: tier.GetTierCode(), + TierName: tier.GetTierName(), + ThresholdCoinAmount: tier.GetThresholdCoinAmount(), + ThresholdValidInviteCount: tier.GetThresholdValidInviteCount(), + RewardCoinAmount: tier.GetRewardCoinAmount(), + Status: tier.GetStatus(), + SortOrder: tier.GetSortOrder(), + CreatedAtMS: tier.GetCreatedAtMs(), + UpdatedAtMS: tier.GetUpdatedAtMs(), + } +} + +func claimFromProto(claim *activityv1.InviteActivityRewardClaim) claimDTO { + if claim == nil { + return claimDTO{} + } + return claimDTO{ + ClaimID: claim.GetClaimId(), + AppCode: claim.GetAppCode(), + CycleKey: claim.GetCycleKey(), + UserID: claim.GetUserId(), + RewardType: claim.GetRewardType(), + CommandID: claim.GetCommandId(), + TierID: claim.GetTierId(), + TierCode: claim.GetTierCode(), + TierName: claim.GetTierName(), + ThresholdCoinAmount: claim.GetThresholdCoinAmount(), + ThresholdValidInviteCount: claim.GetThresholdValidInviteCount(), + ReachedValue: claim.GetReachedValue(), + RewardCoinAmount: claim.GetRewardCoinAmount(), + Status: claim.GetStatus(), + WalletCommandID: claim.GetWalletCommandId(), + WalletTransactionID: claim.GetWalletTransactionId(), + FailureReason: claim.GetFailureReason(), + GrantedAtMS: claim.GetGrantedAtMs(), + CreatedAtMS: claim.GetCreatedAtMs(), + UpdatedAtMS: claim.GetUpdatedAtMs(), + } +} + +func collectUserIDs(claims []claimDTO) []int64 { + seen := make(map[int64]struct{}, len(claims)) + ids := make([]int64, 0, len(claims)) + for _, claim := range claims { + if claim.UserID <= 0 { + continue + } + if _, ok := seen[claim.UserID]; ok { + continue + } + seen[claim.UserID] = struct{}{} + ids = append(ids, claim.UserID) + } + return ids +} + +func int64Args(ids []int64) []any { + args := make([]any, 0, len(ids)) + for _, id := range ids { + args = append(args, id) + } + return args +} + +func placeholders(count int) string { + if count <= 0 { + return "" + } + parts := make([]string, count) + for i := range parts { + parts[i] = "?" + } + return strings.Join(parts, ",") +} diff --git a/server/admin/internal/modules/inviteactivityreward/routes.go b/server/admin/internal/modules/inviteactivityreward/routes.go new file mode 100644 index 00000000..b5a78d09 --- /dev/null +++ b/server/admin/internal/modules/inviteactivityreward/routes.go @@ -0,0 +1,16 @@ +package inviteactivityreward + +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/invite-reward/config", middleware.RequirePermission("invite-activity-reward:view"), h.GetConfig) + protected.PUT("/admin/activity/invite-reward/config", middleware.RequirePermission("invite-activity-reward:update"), h.UpdateConfig) + protected.GET("/admin/activity/invite-reward/claims", middleware.RequirePermission("invite-activity-reward:view"), h.ListClaims) +} diff --git a/server/admin/internal/modules/resource/handler.go b/server/admin/internal/modules/resource/handler.go index dfe9d604..8f4cd306 100644 --- a/server/admin/internal/modules/resource/handler.go +++ b/server/admin/internal/modules/resource/handler.go @@ -512,6 +512,27 @@ func (h *Handler) UpdateGift(c *gin.Context) { response.OK(c, gift) } +func (h *Handler) DeleteGift(c *gin.Context) { + giftID := strings.TrimSpace(c.Param("gift_id")) + if giftID == "" { + response.BadRequest(c, "ID 参数不正确") + return + } + resp, err := h.wallet.DeleteGiftConfig(c.Request.Context(), &walletv1.DeleteGiftConfigRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + GiftId: giftID, + OperatorUserId: actorID(c), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + gift := giftFromProto(resp.GetGift()) + h.auditLog(c, "delete-gift", "gift_configs", gift.GiftID, "success", gift.Name) + response.OK(c, gift) +} + func (h *Handler) EnableGift(c *gin.Context) { h.setGiftStatus(c, "active") } diff --git a/server/admin/internal/modules/resource/handler_test.go b/server/admin/internal/modules/resource/handler_test.go index a6306e71..3ab62ef6 100644 --- a/server/admin/internal/modules/resource/handler_test.go +++ b/server/admin/internal/modules/resource/handler_test.go @@ -115,6 +115,26 @@ func TestUpdateMP4ResourceLayoutsIgnoresNonMP4Resource(t *testing.T) { } } +func TestDeleteGiftOnlyCallsGiftConfigDelete(t *testing.T) { + wallet := &mockResourceWallet{} + router := newResourceHandlerTestRouter(New(wallet, nil, nil, time.Second, nil)) + + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodDelete, "/admin/gifts/rose", nil) + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("delete gift status mismatch: %d %s", recorder.Code, recorder.Body.String()) + } + if len(wallet.deletedGifts) != 1 { + t.Fatalf("expected one gift delete request, got %d", len(wallet.deletedGifts)) + } + deleteReq := wallet.deletedGifts[0] + if deleteReq.GetAppCode() != "lalu" || deleteReq.GetGiftId() != "rose" || deleteReq.GetOperatorUserId() != 7 { + t.Fatalf("delete gift request mismatch: %+v", deleteReq) + } +} + func newResourceHandlerTestRouter(handler *Handler) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() @@ -126,13 +146,15 @@ func newResourceHandlerTestRouter(handler *Handler) *gin.Engine { c.Next() }) router.PUT("/admin/resources/mp4-layouts/batch", handler.UpdateMP4ResourceLayouts) + router.DELETE("/admin/gifts/:gift_id", handler.DeleteGift) return router } type mockResourceWallet struct { walletclient.Client - resources map[int64]*walletv1.Resource - updates []*walletv1.UpdateResourceRequest + resources map[int64]*walletv1.Resource + updates []*walletv1.UpdateResourceRequest + deletedGifts []*walletv1.DeleteGiftConfigRequest } func (m *mockResourceWallet) GetResource(ctx context.Context, req *walletv1.GetResourceRequest) (*walletv1.GetResourceResponse, error) { @@ -167,3 +189,14 @@ func (m *mockResourceWallet) UpdateResource(ctx context.Context, req *walletv1.U CoinPrice: req.GetCoinPrice(), }}, nil } + +func (m *mockResourceWallet) DeleteGiftConfig(ctx context.Context, req *walletv1.DeleteGiftConfigRequest) (*walletv1.GiftConfigResponse, error) { + m.deletedGifts = append(m.deletedGifts, req) + return &walletv1.GiftConfigResponse{Gift: &walletv1.GiftConfig{ + AppCode: req.GetAppCode(), + GiftId: req.GetGiftId(), + ResourceId: 11, + Status: "active", + Name: "Rose", + }}, nil +} diff --git a/server/admin/internal/modules/resource/routes.go b/server/admin/internal/modules/resource/routes.go index 1cfa2df7..9d1c396f 100644 --- a/server/admin/internal/modules/resource/routes.go +++ b/server/admin/internal/modules/resource/routes.go @@ -37,6 +37,7 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { protected.PUT("/admin/gift-types/:type_code", middleware.RequirePermission("gift:update"), h.UpdateGiftType) protected.POST("/admin/gifts", middleware.RequirePermission("gift:create"), h.CreateGift) protected.PUT("/admin/gifts/:gift_id", middleware.RequirePermission("gift:update"), h.UpdateGift) + protected.DELETE("/admin/gifts/:gift_id", middleware.RequirePermission("gift:delete"), h.DeleteGift) protected.POST("/admin/gifts/:gift_id/enable", middleware.RequirePermission("gift:status"), h.EnableGift) protected.POST("/admin/gifts/:gift_id/disable", middleware.RequirePermission("gift:status"), h.DisableGift) diff --git a/server/admin/internal/repository/app_popup_repository.go b/server/admin/internal/repository/app_popup_repository.go new file mode 100644 index 00000000..e37bfe27 --- /dev/null +++ b/server/admin/internal/repository/app_popup_repository.go @@ -0,0 +1,76 @@ +package repository + +import ( + "strings" + + "hyapp-admin-server/internal/model" +) + +type AppPopupListOptions struct { + AppCode string + Keyword string + Status string +} + +func (s *Store) ListAppPopups(options AppPopupListOptions) ([]model.AppPopup, error) { + var items []model.AppPopup + // 管理后台列表先用 app_code 做租户隔离,再叠加状态和关键字;时间窗口只改变展示资格,不影响后台可见性。 + query := s.db.Where("app_code = ?", strings.TrimSpace(options.AppCode)) + if options.Status != "" { + query = query.Where("status = ?", strings.TrimSpace(options.Status)) + } + if options.Keyword != "" { + like := "%" + strings.TrimSpace(options.Keyword) + "%" + query = query.Where("(code LIKE ? OR name LIKE ? OR jump_url LIKE ? OR image_url LIKE ?)", like, like, like, like) + } + err := query.Order("sort_order ASC, id DESC").Find(&items).Error + return items, err +} + +func (s *Store) HasAppPopupCode(appCode string, code string, excludeID uint) (bool, error) { + var count int64 + // code 是客户端频控和埋点使用的稳定标识,同一个 App 下必须唯一;更新时排除当前记录。 + query := s.db.Model(&model.AppPopup{}). + Where("app_code = ? AND code = ?", strings.TrimSpace(appCode), strings.TrimSpace(code)) + if excludeID > 0 { + query = query.Where("id <> ?", excludeID) + } + if err := query.Count(&count).Error; err != nil { + return false, err + } + return count > 0, nil +} + +func (s *Store) ExpireAppPopups(appCode string, nowMs int64) error { + appCode = strings.TrimSpace(appCode) + if appCode == "" || nowMs <= 0 { + return nil + } + // 只把已经结束的 active 弹窗转成 expired,手动关闭的配置不自动覆盖,避免运营状态被时间任务误改。 + return s.db.Model(&model.AppPopup{}). + Where("app_code = ? AND status = ? AND ends_at_ms > 0 AND ends_at_ms <= ?", appCode, "active", nowMs). + Updates(map[string]any{ + "status": "expired", + "updated_at_ms": nowMs, + }).Error +} + +func (s *Store) GetAppPopup(appCode string, id uint) (model.AppPopup, error) { + var item model.AppPopup + // app_code 和 id 同时限定,防止多 App 后台误编辑其他应用的弹窗。 + err := s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).First(&item).Error + return item, err +} + +func (s *Store) CreateAppPopup(item *model.AppPopup) error { + return s.db.Create(item).Error +} + +func (s *Store) UpdateAppPopup(item *model.AppPopup) error { + return s.db.Save(item).Error +} + +func (s *Store) DeleteAppPopup(appCode string, id uint) error { + // 弹窗配置和 Banner/开屏保持一致,后台删除使用硬删除;审计在 handler 层写操作日志。 + return s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).Delete(&model.AppPopup{}).Error +} diff --git a/server/admin/internal/repository/repository.go b/server/admin/internal/repository/repository.go index ed4a0f08..575b649c 100644 --- a/server/admin/internal/repository/repository.go +++ b/server/admin/internal/repository/repository.go @@ -65,6 +65,7 @@ func (s *Store) AutoMigrate() error { &model.AppConfig{}, &model.AppBanner{}, &model.AppSplashScreen{}, + &model.AppPopup{}, &model.AppVersion{}, &model.AppExploreTab{}, &model.HostAgencySalaryPolicy{}, diff --git a/server/admin/internal/repository/seed.go b/server/admin/internal/repository/seed.go index b824a068..d65c209c 100644 --- a/server/admin/internal/repository/seed.go +++ b/server/admin/internal/repository/seed.go @@ -56,6 +56,7 @@ var defaultPermissions = []model.Permission{ {Name: "礼物创建", Code: "gift:create", Kind: "button"}, {Name: "礼物更新", Code: "gift:update", Kind: "button"}, {Name: "礼物状态", Code: "gift:status", Kind: "button"}, + {Name: "礼物删除", Code: "gift:delete", Kind: "button"}, {Name: "表情包查看", Code: "emoji-pack:view", Kind: "menu"}, {Name: "表情包创建", Code: "emoji-pack:create", Kind: "button"}, {Name: "国家查看", Code: "country:view", Kind: "menu"}, @@ -257,8 +258,9 @@ func (s *Store) seedMenus() error { {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}, - {ParentID: &appConfigID, Title: "Explore配置", Code: "app-config-explore", Path: "/app-config/explore", Icon: "explore", PermissionCode: "app-config:view", Sort: 69, Visible: true}, - {ParentID: &appConfigID, Title: "版本管理", Code: "app-config-versions", Path: "/app-config/versions", Icon: "settings", PermissionCode: "app-version:view", Sort: 71, Visible: true}, + {ParentID: &appConfigID, Title: "弹窗配置", Code: "app-config-popups", Path: "/app-config/popups", Icon: "image", PermissionCode: "app-config:view", Sort: 69, Visible: true}, + {ParentID: &appConfigID, Title: "Explore配置", Code: "app-config-explore", Path: "/app-config/explore", Icon: "explore", PermissionCode: "app-config:view", Sort: 70, Visible: true}, + {ParentID: &appConfigID, Title: "版本管理", Code: "app-config-versions", Path: "/app-config/versions", Icon: "settings", PermissionCode: "app-version:view", Sort: 72, Visible: true}, {ParentID: &resourceID, Title: "资源列表", Code: "resource-list", Path: "/resources", Icon: "inventory", PermissionCode: "resource:view", Sort: 67, Visible: true}, {ParentID: &resourceID, Title: "道具商店", Code: "resource-shop-list", Path: "/resource-shop", Icon: "storefront", PermissionCode: "resource-shop:view", Sort: 68, Visible: true}, {ParentID: &resourceID, Title: "资源组列表", Code: "resource-group-list", Path: "/resource-groups", Icon: "category", PermissionCode: "resource-group:view", Sort: 69, Visible: true}, @@ -516,7 +518,7 @@ func defaultRolePermissionCodes(code string) []string { "resource-shop:view", "resource-shop:update", "resource-group:view", "resource-group:create", "resource-group:update", "resource-grant:view", "resource-grant:create", - "gift:view", "gift:create", "gift:update", "gift:status", + "gift:view", "gift:create", "gift:update", "gift:status", "gift:delete", "emoji-pack:view", "emoji-pack:create", "country:view", "country:create", "country:update", "country:status", "region:view", "region:create", "region:update", "region:status", @@ -613,7 +615,7 @@ func defaultRolePermissionMigrationCodes(code string) []string { "resource-shop:view", "resource-shop:update", "resource-group:view", "resource-group:create", "resource-group:update", "resource-grant:view", "resource-grant:create", - "gift:view", "gift:create", "gift:update", "gift:status", + "gift:view", "gift:create", "gift:update", "gift:status", "gift:delete", "emoji-pack:view", "emoji-pack:create", "upload:create", "country:view", "country:create", "country:update", "country:status", diff --git a/server/admin/internal/router/router.go b/server/admin/internal/router/router.go index 4397e8ed..eef9e746 100644 --- a/server/admin/internal/router/router.go +++ b/server/admin/internal/router/router.go @@ -23,6 +23,7 @@ import ( "hyapp-admin-server/internal/modules/hostagencypolicy" "hyapp-admin-server/internal/modules/hostorg" "hyapp-admin-server/internal/modules/hostsalarysettlement" + "hyapp-admin-server/internal/modules/inviteactivityreward" "hyapp-admin-server/internal/modules/job" "hyapp-admin-server/internal/modules/levelconfig" "hyapp-admin-server/internal/modules/luckygift" @@ -73,6 +74,7 @@ type Handlers struct { HostAgencyPolicy *hostagencypolicy.Handler HostOrg *hostorg.Handler HostSalarySettlement *hostsalarysettlement.Handler + InviteActivityReward *inviteactivityreward.Handler Job *job.Handler LevelConfig *levelconfig.Handler LuckyGift *luckygift.Handler @@ -137,6 +139,7 @@ func New(cfg config.Config, auth *service.AuthService, h Handlers) *gin.Engine { hostagencypolicy.RegisterRoutes(protected, h.HostAgencyPolicy) hostsalarysettlement.RegisterRoutes(protected, h.HostSalarySettlement) hostorg.RegisterRoutes(protected, h.HostOrg) + inviteactivityreward.RegisterRoutes(protected, h.InviteActivityReward) audit.RegisterRoutes(protected, h.Audit) payment.RegisterRoutes(protected, h.Payment) prettyid.RegisterRoutes(protected, h.PrettyID) diff --git a/server/admin/migrations/049_admin_app_popups.sql b/server/admin/migrations/049_admin_app_popups.sql new file mode 100644 index 00000000..f97f2c41 --- /dev/null +++ b/server/admin/migrations/049_admin_app_popups.sql @@ -0,0 +1,69 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 弹窗配置是 App 启动/回前台读取的轻量运营配置;图片可为空,展示周期由客户端本地频控执行。 +SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED); + +CREATE TABLE IF NOT EXISTS admin_app_popups ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '主键 ID', + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', + code VARCHAR(80) NOT NULL COMMENT '弹窗编码,用于客户端本地频控和埋点识别', + name VARCHAR(80) NOT NULL COMMENT '弹窗名称', + image_url VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '弹窗图片 URL,可为空', + jump_type VARCHAR(16) NOT NULL COMMENT '跳转类型:h5 或 app', + jump_url VARCHAR(2048) NOT NULL DEFAULT '' COMMENT '跳转地址', + display_period_days INT NOT NULL DEFAULT 0 COMMENT '展示周期天数,0 表示每次打开都展示', + status VARCHAR(24) NOT NULL DEFAULT 'active' COMMENT '业务状态', + sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重,数值越小越靠前', + starts_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '生效开始时间,UTC epoch ms', + ends_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '生效结束时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + UNIQUE KEY uk_admin_app_popups_code (app_code, code), + INDEX idx_admin_app_popups_app_sort (app_code, status, sort_order, id), + INDEX idx_admin_app_popups_window (app_code, status, starts_at_ms, ends_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台 App 弹窗配置表'; + +-- 开发阶段如果已经执行过旧版 049,这里补齐 code 列,并用历史 ID 生成稳定默认值,避免多个空 code 破坏唯一索引。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_app_popups' AND COLUMN_NAME = 'code') = 0, + 'ALTER TABLE admin_app_popups ADD COLUMN code VARCHAR(80) NOT NULL DEFAULT '''' COMMENT ''弹窗编码,用于客户端本地频控和埋点识别'' AFTER app_code', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +UPDATE admin_app_popups +SET code = CONCAT('popup-', id) +WHERE code = ''; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_app_popups' AND INDEX_NAME = 'uk_admin_app_popups_code') = 0, + 'ALTER TABLE admin_app_popups ADD UNIQUE KEY uk_admin_app_popups_code (app_code, code)', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) +SELECT parent.id, '弹窗配置', 'app-config-popups', '/app-config/popups', 'image', 'app-config:view', 69, TRUE, @now_ms, @now_ms +FROM admin_menus parent +WHERE parent.code = 'app-config' +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; + +UPDATE admin_menus +SET sort = 70, updated_at_ms = @now_ms +WHERE code = 'app-config-explore' AND sort < 70; + +UPDATE admin_menus +SET sort = 72, updated_at_ms = @now_ms +WHERE code = 'app-config-versions' AND sort < 72; diff --git a/server/admin/migrations/050_invite_activity_reward_navigation.sql b/server/admin/migrations/050_invite_activity_reward_navigation.sql new file mode 100644 index 00000000..3f8b10cf --- /dev/null +++ b/server/admin/migrations/050_invite_activity_reward_navigation.sql @@ -0,0 +1,59 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 邀请活动事实由 activity-service 按 UTC 月统计;后台只维护金币奖励档位并查看领取记录。 +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 + ('邀请活动查看', 'invite-activity-reward:view', 'menu', '', @now_ms, @now_ms), + ('邀请活动更新', 'invite-activity-reward: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', '', 70, 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, '邀请活动', 'invite-activity-reward', '/activities/invite-reward', 'person_add', 'invite-activity-reward: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 ('invite-activity-reward:view', 'invite-activity-reward: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 ('invite-activity-reward:view', 'invite-activity-reward: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 = 'invite-activity-reward:view'; diff --git a/server/admin/migrations/051_gift_delete_permission.sql b/server/admin/migrations/051_gift_delete_permission.sql new file mode 100644 index 00000000..708af605 --- /dev/null +++ b/server/admin/migrations/051_gift_delete_permission.sql @@ -0,0 +1,18 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +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 + ('礼物删除', 'gift:delete', 'button', '', @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE + name = VALUES(name), + kind = VALUES(kind), + description = VALUES(description), + 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 IN ('platform-admin', 'ops-admin') + AND admin_permission.code = 'gift:delete'; diff --git a/services/activity-service/configs/config.docker.yaml b/services/activity-service/configs/config.docker.yaml index de7f6690..aa2c9a21 100644 --- a/services/activity-service/configs/config.docker.yaml +++ b/services/activity-service/configs/config.docker.yaml @@ -18,6 +18,8 @@ first_recharge_reward_worker: enabled: true cumulative_recharge_reward_worker: enabled: true +invite_activity_reward_worker: + enabled: true red_packet_broadcast_worker: enabled: true task_event_worker: @@ -70,6 +72,7 @@ rocketmq: realtime_topic: "hyapp_wallet_realtime_outbox" first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" + invite_activity_consumer_group: "hyapp-activity-invite-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" task_consumer_group: "hyapp-activity-task-wallet-outbox" consumer_max_reconsume_times: 16 @@ -78,6 +81,7 @@ rocketmq: topic: "hyapp_user_outbox" consumer_group: "hyapp-activity-user-region-broadcast" task_consumer_group: "hyapp-activity-task-user-outbox" + invite_activity_consumer_group: "hyapp-activity-invite-user-outbox" consumer_max_reconsume_times: 16 consumer: room_outbox_poll_interval_ms: 1000 diff --git a/services/activity-service/configs/config.tencent.example.yaml b/services/activity-service/configs/config.tencent.example.yaml index dac44c84..9f88bcbf 100644 --- a/services/activity-service/configs/config.tencent.example.yaml +++ b/services/activity-service/configs/config.tencent.example.yaml @@ -18,6 +18,8 @@ first_recharge_reward_worker: enabled: true cumulative_recharge_reward_worker: enabled: true +invite_activity_reward_worker: + enabled: true red_packet_broadcast_worker: enabled: true task_event_worker: @@ -70,6 +72,7 @@ rocketmq: realtime_topic: "hyapp_wallet_realtime_outbox" first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" + invite_activity_consumer_group: "hyapp-activity-invite-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" task_consumer_group: "hyapp-activity-task-wallet-outbox" consumer_max_reconsume_times: 16 @@ -78,6 +81,7 @@ rocketmq: topic: "hyapp_user_outbox" consumer_group: "hyapp-activity-user-region-broadcast" task_consumer_group: "hyapp-activity-task-user-outbox" + invite_activity_consumer_group: "hyapp-activity-invite-user-outbox" consumer_max_reconsume_times: 16 consumer: room_outbox_poll_interval_ms: 1000 diff --git a/services/activity-service/configs/config.yaml b/services/activity-service/configs/config.yaml index 7f3dd793..56675cb8 100644 --- a/services/activity-service/configs/config.yaml +++ b/services/activity-service/configs/config.yaml @@ -18,6 +18,8 @@ first_recharge_reward_worker: enabled: true cumulative_recharge_reward_worker: enabled: true +invite_activity_reward_worker: + enabled: true red_packet_broadcast_worker: enabled: true task_event_worker: @@ -70,6 +72,7 @@ rocketmq: realtime_topic: "hyapp_wallet_realtime_outbox" first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" cumulative_recharge_consumer_group: "hyapp-activity-cumulative-recharge-wallet-outbox" + invite_activity_consumer_group: "hyapp-activity-invite-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" task_consumer_group: "hyapp-activity-task-wallet-outbox" consumer_max_reconsume_times: 16 @@ -78,6 +81,7 @@ rocketmq: topic: "hyapp_user_outbox" consumer_group: "hyapp-activity-user-region-broadcast" task_consumer_group: "hyapp-activity-task-user-outbox" + invite_activity_consumer_group: "hyapp-activity-invite-user-outbox" consumer_max_reconsume_times: 16 consumer: room_outbox_poll_interval_ms: 1000 diff --git a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql index aea041fe..85ede66a 100644 --- a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql +++ b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql @@ -1282,3 +1282,92 @@ CREATE TABLE IF NOT EXISTS weekly_star_settlements ( UNIQUE KEY uk_weekly_star_wallet_command (app_code, wallet_command_id), KEY idx_weekly_star_settlement_cycle (app_code, cycle_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='周星 Top 奖励结算'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_configs ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用邀请活动', + updated_by_admin_id 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) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动配置'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_tiers ( + tier_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '档位自增 ID', + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', + tier_code VARCHAR(64) NOT NULL COMMENT '档位编码', + tier_name VARCHAR(128) NOT NULL COMMENT '档位展示名称', + threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', + threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', + reward_coin_amount BIGINT NOT NULL COMMENT '奖励金币数量', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT 'active/inactive', + sort_order INT 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 (tier_id), + UNIQUE KEY uk_invite_activity_reward_tier_code (app_code, tier_code), + KEY idx_invite_activity_reward_tier_sort (app_code, reward_type, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动奖励档位'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_events ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + event_id VARCHAR(128) NOT NULL COMMENT '上游 outbox event_id', + event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInviteBecameValid', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + invited_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '被邀请人用户 ID', + inviter_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '邀请人用户 ID', + transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '充值钱包交易 ID', + command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '充值钱包命令 ID', + recharge_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '充值来源', + recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本次充值金币', + status VARCHAR(32) NOT NULL DEFAULT 'consumed' COMMENT 'consumed/skipped', + skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因', + payload_json JSON NULL COMMENT '原始上游 payload', + occurred_at_ms BIGINT NOT NULL COMMENT '事件发生时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_invite_activity_reward_event_inviter (app_code, cycle_key, inviter_user_id, occurred_at_ms), + KEY idx_invite_activity_reward_event_invited (app_code, invited_user_id, occurred_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动事件幂等表'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_progress ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', + total_recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本月邀请用户累计充值金币', + valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '本月新增有效邀请人数', + claimed_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本月已领取奖励金币', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, cycle_key, user_id), + KEY idx_invite_activity_reward_progress_recharge (app_code, cycle_key, total_recharge_coin_amount), + KEY idx_invite_activity_reward_progress_valid (app_code, cycle_key, valid_invite_count) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动用户月度进度'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_claims ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + claim_id VARCHAR(128) NOT NULL COMMENT '领取 ID', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', + command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取命令 ID', + tier_id BIGINT NOT NULL COMMENT '领取档位 ID', + tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码', + tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称', + threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', + threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', + reached_value BIGINT NOT NULL DEFAULT 0 COMMENT '领取时已达成值', + reward_coin_amount BIGINT NOT NULL COMMENT '本次实际发放金币', + status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/granted/failed', + wallet_command_id VARCHAR(128) NOT NULL COMMENT '钱包发奖命令 ID', + wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包交易 ID', + failure_reason VARCHAR(512) NOT NULL DEFAULT '' 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', + PRIMARY KEY (app_code, claim_id), + UNIQUE KEY uk_invite_activity_reward_user_tier (app_code, cycle_key, user_id, reward_type, tier_id), + UNIQUE KEY uk_invite_activity_reward_wallet_command (app_code, wallet_command_id), + KEY idx_invite_activity_reward_claim_user (app_code, cycle_key, user_id, created_at_ms), + KEY idx_invite_activity_reward_claim_status (app_code, status, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动金币领取记录'; diff --git a/services/activity-service/deploy/mysql/migrations/002_invite_activity_reward.sql b/services/activity-service/deploy/mysql/migrations/002_invite_activity_reward.sql new file mode 100644 index 00000000..74061300 --- /dev/null +++ b/services/activity-service/deploy/mysql/migrations/002_invite_activity_reward.sql @@ -0,0 +1,90 @@ +USE hyapp_activity; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_configs ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用邀请活动', + updated_by_admin_id 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) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动配置'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_tiers ( + tier_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '档位自增 ID', + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', + tier_code VARCHAR(64) NOT NULL COMMENT '档位编码', + tier_name VARCHAR(128) NOT NULL COMMENT '档位展示名称', + threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', + threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', + reward_coin_amount BIGINT NOT NULL COMMENT '奖励金币数量', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT 'active/inactive', + sort_order INT 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 (tier_id), + UNIQUE KEY uk_invite_activity_reward_tier_code (app_code, tier_code), + KEY idx_invite_activity_reward_tier_sort (app_code, reward_type, status, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动奖励档位'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_events ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + event_id VARCHAR(128) NOT NULL COMMENT '上游 outbox event_id', + event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInviteBecameValid', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + invited_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '被邀请人用户 ID', + inviter_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '邀请人用户 ID', + transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '充值钱包交易 ID', + command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '充值钱包命令 ID', + recharge_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '充值来源', + recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本次充值金币', + status VARCHAR(32) NOT NULL DEFAULT 'consumed' COMMENT 'consumed/skipped', + skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因', + payload_json JSON NULL COMMENT '原始上游 payload', + occurred_at_ms BIGINT NOT NULL COMMENT '事件发生时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_invite_activity_reward_event_inviter (app_code, cycle_key, inviter_user_id, occurred_at_ms), + KEY idx_invite_activity_reward_event_invited (app_code, invited_user_id, occurred_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动事件幂等表'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_progress ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', + total_recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本月邀请用户累计充值金币', + valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '本月新增有效邀请人数', + claimed_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本月已领取奖励金币', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, cycle_key, user_id), + KEY idx_invite_activity_reward_progress_recharge (app_code, cycle_key, total_recharge_coin_amount), + KEY idx_invite_activity_reward_progress_valid (app_code, cycle_key, valid_invite_count) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动用户月度进度'; + +CREATE TABLE IF NOT EXISTS invite_activity_reward_claims ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + claim_id VARCHAR(128) NOT NULL COMMENT '领取 ID', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', + command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取命令 ID', + tier_id BIGINT NOT NULL COMMENT '领取档位 ID', + tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码', + tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称', + threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', + threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', + reached_value BIGINT NOT NULL DEFAULT 0 COMMENT '领取时已达成值', + reward_coin_amount BIGINT NOT NULL COMMENT '本次实际发放金币', + status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/granted/failed', + wallet_command_id VARCHAR(128) NOT NULL COMMENT '钱包发奖命令 ID', + wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包交易 ID', + failure_reason VARCHAR(512) NOT NULL DEFAULT '' 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', + PRIMARY KEY (app_code, claim_id), + UNIQUE KEY uk_invite_activity_reward_user_tier (app_code, cycle_key, user_id, reward_type, tier_id), + UNIQUE KEY uk_invite_activity_reward_wallet_command (app_code, wallet_command_id), + KEY idx_invite_activity_reward_claim_user (app_code, cycle_key, user_id, created_at_ms), + KEY idx_invite_activity_reward_claim_status (app_code, status, created_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动金币领取记录'; diff --git a/services/activity-service/internal/app/app.go b/services/activity-service/internal/app/app.go index d3fce4d6..627d9f1e 100644 --- a/services/activity-service/internal/app/app.go +++ b/services/activity-service/internal/app/app.go @@ -18,6 +18,7 @@ import ( broadcastservice "hyapp/services/activity-service/internal/service/broadcast" cumulativerechargeservice "hyapp/services/activity-service/internal/service/cumulativerecharge" firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge" + inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity" luckygiftservice "hyapp/services/activity-service/internal/service/luckygift" mysqlstorage "hyapp/services/activity-service/internal/storage/mysql" ) @@ -34,6 +35,7 @@ type App struct { luckyGift *luckygiftservice.Service firstRechargeReward *firstrechargeservice.Service cumulativeRecharge *cumulativerechargeservice.Service + inviteActivityReward *inviteactivityservice.Service broadcastWorkerEnabled bool luckyGiftWorkerEnabled bool workerNodeID string @@ -110,6 +112,7 @@ func New(cfg config.Config) (*App, error) { luckyGift: services.luckyGift, firstRechargeReward: services.firstRechargeReward, cumulativeRecharge: services.cumulativeRecharge, + inviteActivityReward: services.inviteActivityReward, broadcastWorkerEnabled: cfg.Broadcast.Enabled, luckyGiftWorkerEnabled: cfg.LuckyGiftWorker.Enabled, workerNodeID: cfg.NodeID, diff --git a/services/activity-service/internal/app/grpc_registration.go b/services/activity-service/internal/app/grpc_registration.go index 27dd5735..8cc9c61d 100644 --- a/services/activity-service/internal/app/grpc_registration.go +++ b/services/activity-service/internal/app/grpc_registration.go @@ -37,4 +37,6 @@ func registerGRPCServers(server *grpc.Server, services *serviceBundle) { activityv1.RegisterAdminFirstRechargeRewardServiceServer(server, grpcserver.NewAdminFirstRechargeRewardServer(services.firstRechargeReward)) activityv1.RegisterCumulativeRechargeRewardServiceServer(server, grpcserver.NewCumulativeRechargeRewardServer(services.cumulativeRecharge)) activityv1.RegisterAdminCumulativeRechargeRewardServiceServer(server, grpcserver.NewAdminCumulativeRechargeRewardServer(services.cumulativeRecharge)) + activityv1.RegisterInviteActivityRewardServiceServer(server, grpcserver.NewInviteActivityRewardServer(services.inviteActivityReward)) + activityv1.RegisterAdminInviteActivityRewardServiceServer(server, grpcserver.NewAdminInviteActivityRewardServer(services.inviteActivityReward)) } diff --git a/services/activity-service/internal/app/mq.go b/services/activity-service/internal/app/mq.go index 7f7c669d..20320efa 100644 --- a/services/activity-service/internal/app/mq.go +++ b/services/activity-service/internal/app/mq.go @@ -63,6 +63,22 @@ func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx. } mqConsumers = append(mqConsumers, consumer) } + if cfg.InviteActivityRewardWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled { + consumer, err := newInviteActivityWalletConsumer(cfg, services) + if err != nil { + shutdownConsumers(mqConsumers) + return nil, err + } + mqConsumers = append(mqConsumers, consumer) + } + if cfg.InviteActivityRewardWorker.Enabled && cfg.RocketMQ.UserOutbox.Enabled { + consumer, err := newInviteActivityUserOutboxConsumer(cfg, services) + if err != nil { + shutdownConsumers(mqConsumers) + return nil, err + } + mqConsumers = append(mqConsumers, consumer) + } if cfg.RedPacketBroadcastWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled { consumer, err := newRedPacketWalletConsumer(cfg, services) if err != nil { @@ -171,6 +187,44 @@ func newCumulativeRechargeWalletConsumer(cfg config.Config, services *serviceBun return consumer, nil } +func newInviteActivityWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { + // 邀请活动单独使用 wallet_outbox consumer group;重放累计充值时不影响首充、累充和任务的消费进度。 + consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.InviteActivityConsumerGroup)) + if err != nil { + return nil, err + } + if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + event, ok, err := inviteActivityRechargeEventFromWalletMessage(message.Body) + if err != nil || !ok { + return err + } + return services.inviteActivityReward.ConsumeWalletRechargeEvent(appcode.WithContext(ctx, event.AppCode), event) + }); err != nil { + _ = consumer.Shutdown() + return nil, err + } + return consumer, nil +} + +func newInviteActivityUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { + // 有效邀请人数来自 user-service 已提交事实;消费失败必须让同一 outbox 重试,仓储按 event_id 保证幂等。 + consumer, err := rocketmqx.NewConsumer(userOutboxInviteActivityConsumerConfig(cfg.RocketMQ)) + if err != nil { + return nil, err + } + if err := consumer.Subscribe(cfg.RocketMQ.UserOutbox.Topic, usermq.TagUserOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + event, ok, err := inviteActivityValidInviteEventFromUserMessage(message.Body) + if err != nil || !ok { + return err + } + return services.inviteActivityReward.ConsumeValidInviteEvent(appcode.WithContext(ctx, event.AppCode), event) + }); err != nil { + _ = consumer.Shutdown() + return nil, err + } + return consumer, nil +} + func newRedPacketWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.RedPacketBroadcastConsumerGroup)) if err != nil { @@ -248,6 +302,10 @@ func userOutboxTaskConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerC return rocketMQConsumerConfig(cfg, cfg.UserOutbox.TaskConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes) } +func userOutboxInviteActivityConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { + return rocketMQConsumerConfig(cfg, cfg.UserOutbox.InviteActivityConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes) +} + func walletOutboxConsumerConfig(cfg config.RocketMQConfig, group string) rocketmqx.ConsumerConfig { return rocketMQConsumerConfig(cfg, group, cfg.WalletOutbox.ConsumerMaxReconsumeTimes) } diff --git a/services/activity-service/internal/app/services.go b/services/activity-service/internal/app/services.go index 01ce1302..63630031 100644 --- a/services/activity-service/internal/app/services.go +++ b/services/activity-service/internal/app/services.go @@ -14,6 +14,7 @@ import ( cumulativerechargeservice "hyapp/services/activity-service/internal/service/cumulativerecharge" firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge" growthservice "hyapp/services/activity-service/internal/service/growth" + inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity" luckygiftservice "hyapp/services/activity-service/internal/service/luckygift" messageservice "hyapp/services/activity-service/internal/service/message" registrationrewardservice "hyapp/services/activity-service/internal/service/registrationreward" @@ -38,6 +39,7 @@ type serviceBundle struct { luckyGift *luckygiftservice.Service firstRechargeReward *firstrechargeservice.Service cumulativeRecharge *cumulativerechargeservice.Service + inviteActivityReward *inviteactivityservice.Service broadcastPublisherAvailable bool } @@ -90,6 +92,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository, ) firstRechargeRewardSvc := firstrechargeservice.New(repository, walletClient) cumulativeRechargeSvc := cumulativerechargeservice.New(repository, walletClient) + inviteActivityRewardSvc := inviteactivityservice.New(repository, walletClient, activityclient.NewGRPCInviteAttributionSource(clients.userConn)) return &serviceBundle{ activity: activitySvc, @@ -105,6 +108,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository, luckyGift: luckyGiftSvc, firstRechargeReward: firstRechargeRewardSvc, cumulativeRecharge: cumulativeRechargeSvc, + inviteActivityReward: inviteActivityRewardSvc, broadcastPublisherAvailable: broadcastPublisher != nil, }, nil } diff --git a/services/activity-service/internal/app/user_events.go b/services/activity-service/internal/app/user_events.go new file mode 100644 index 00000000..ee23eb54 --- /dev/null +++ b/services/activity-service/internal/app/user_events.go @@ -0,0 +1,37 @@ +package app + +import ( + "encoding/json" + + "hyapp/pkg/appcode" + "hyapp/pkg/usermq" + inviteactivitydomain "hyapp/services/activity-service/internal/domain/inviteactivity" +) + +func inviteActivityValidInviteEventFromUserMessage(body []byte) (inviteactivitydomain.ValidInviteEvent, bool, error) { + message, err := usermq.DecodeUserOutboxMessage(body) + if err != nil { + return inviteactivitydomain.ValidInviteEvent{}, false, err + } + if message.EventType != inviteactivitydomain.EventTypeUserInviteBecameValid { + return inviteactivitydomain.ValidInviteEvent{}, false, nil + } + var decoded map[string]any + if err := json.Unmarshal([]byte(message.PayloadJSON), &decoded); err != nil || decoded == nil { + decoded = map[string]any{} + } + invitedUserID := firstNonZeroInt64(int64FromDecoded(decoded, "invited_user_id"), message.AggregateID) + inviterUserID := int64FromDecoded(decoded, "inviter_user_id") + if invitedUserID <= 0 || inviterUserID <= 0 { + return inviteactivitydomain.ValidInviteEvent{}, false, nil + } + return inviteactivitydomain.ValidInviteEvent{ + AppCode: appcode.Normalize(message.AppCode), + EventID: message.EventID, + EventType: message.EventType, + InviterUserID: inviterUserID, + InvitedUserID: invitedUserID, + PayloadJSON: message.PayloadJSON, + OccurredAtMS: message.OccurredAtMS, + }, true, nil +} diff --git a/services/activity-service/internal/app/wallet_events.go b/services/activity-service/internal/app/wallet_events.go index ce17ed76..1a26b1c5 100644 --- a/services/activity-service/internal/app/wallet_events.go +++ b/services/activity-service/internal/app/wallet_events.go @@ -9,6 +9,7 @@ import ( broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast" cumulativerechargedomain "hyapp/services/activity-service/internal/domain/cumulativerecharge" firstrechargedomain "hyapp/services/activity-service/internal/domain/firstrecharge" + inviteactivitydomain "hyapp/services/activity-service/internal/domain/inviteactivity" ) func firstRechargeEventFromWalletMessage(body []byte) (firstrechargedomain.RechargeEvent, bool, error) { @@ -66,6 +67,29 @@ func cumulativeRechargeEventFromWalletMessage(body []byte) (cumulativerechargedo }, true, nil } +func inviteActivityRechargeEventFromWalletMessage(body []byte) (inviteactivitydomain.RechargeEvent, bool, error) { + message, err := walletmq.DecodeWalletOutboxMessage(body) + if err != nil { + return inviteactivitydomain.RechargeEvent{}, false, err + } + if message.EventType != inviteactivitydomain.EventTypeWalletRechargeRecorded || message.AssetType != "COIN" || message.AvailableDelta <= 0 { + return inviteactivitydomain.RechargeEvent{}, false, nil + } + fields := cumulativeRechargeFactFromWalletPayload(message.PayloadJSON, message.AvailableDelta) + return inviteactivitydomain.RechargeEvent{ + AppCode: appcode.Normalize(message.AppCode), + EventID: message.EventID, + EventType: message.EventType, + TransactionID: message.TransactionID, + CommandID: message.CommandID, + InvitedUserID: message.UserID, + RechargeCoinAmount: fields.coinAmount, + RechargeType: fields.rechargeType, + PayloadJSON: message.PayloadJSON, + OccurredAtMS: message.OccurredAtMS, + }, true, nil +} + func redPacketEventFromWalletMessage(body []byte) (broadcastdomain.RedPacketWalletEvent, bool, error) { message, err := walletmq.DecodeWalletOutboxMessage(body) if err != nil { diff --git a/services/activity-service/internal/client/invite_attribution_source.go b/services/activity-service/internal/client/invite_attribution_source.go new file mode 100644 index 00000000..cb7c9078 --- /dev/null +++ b/services/activity-service/internal/client/invite_attribution_source.go @@ -0,0 +1,33 @@ +package client + +import ( + "context" + + "google.golang.org/grpc" + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/appcode" +) + +// GRPCInviteAttributionSource 从 user-service 读取被邀请用户的邀请归因。 +type GRPCInviteAttributionSource struct { + client userv1.UserServiceClient +} + +func NewGRPCInviteAttributionSource(conn *grpc.ClientConn) *GRPCInviteAttributionSource { + return &GRPCInviteAttributionSource{client: userv1.NewUserServiceClient(conn)} +} + +func (s *GRPCInviteAttributionSource) GetInviteAttribution(ctx context.Context, invitedUserID int64) (*userv1.InviteAttribution, error) { + resp, err := s.client.GetInviteAttribution(ctx, &userv1.GetInviteAttributionRequest{ + Meta: &userv1.RequestMeta{ + RequestId: "invite-activity-attribution", + Caller: "activity-service", + AppCode: appcode.FromContext(ctx), + }, + InvitedUserId: invitedUserID, + }) + if err != nil { + return nil, err + } + return resp.GetAttribution(), nil +} diff --git a/services/activity-service/internal/config/config.go b/services/activity-service/internal/config/config.go index df759920..e94de187 100644 --- a/services/activity-service/internal/config/config.go +++ b/services/activity-service/internal/config/config.go @@ -31,6 +31,8 @@ type Config struct { FirstRechargeRewardWorker FirstRechargeRewardWorkerConfig `yaml:"first_recharge_reward_worker"` // CumulativeRechargeRewardWorker 控制累充奖励对 wallet_outbox 充值事实的本地消费。 CumulativeRechargeRewardWorker CumulativeRechargeRewardWorkerConfig `yaml:"cumulative_recharge_reward_worker"` + // InviteActivityRewardWorker 控制邀请活动对 wallet/user outbox 事实的本地消费。 + InviteActivityRewardWorker InviteActivityRewardWorkerConfig `yaml:"invite_activity_reward_worker"` // RedPacketBroadcastWorker 控制红包创建事实转区域飘屏 outbox 的本地消费。 RedPacketBroadcastWorker RedPacketBroadcastWorkerConfig `yaml:"red_packet_broadcast_worker"` // TaskEventWorker 控制任务系统对 wallet/user outbox 事实的本地消费。 @@ -73,6 +75,12 @@ type CumulativeRechargeRewardWorkerConfig struct { Enabled bool `yaml:"enabled"` } +// InviteActivityRewardWorkerConfig 保存邀请活动钱包和用户事实消费策略。 +type InviteActivityRewardWorkerConfig struct { + // Enabled 控制是否启动 wallet/user outbox MQ 消费。 + Enabled bool `yaml:"enabled"` +} + // RedPacketBroadcastWorkerConfig 保存红包创建事实消费策略。 type RedPacketBroadcastWorkerConfig struct { // Enabled 控制是否启动 wallet_outbox MQ 消费。 @@ -169,6 +177,7 @@ type WalletOutboxMQConfig struct { RealtimeTopic string `yaml:"realtime_topic"` FirstRechargeConsumerGroup string `yaml:"first_recharge_consumer_group"` CumulativeRechargeConsumerGroup string `yaml:"cumulative_recharge_consumer_group"` + InviteActivityConsumerGroup string `yaml:"invite_activity_consumer_group"` RedPacketBroadcastConsumerGroup string `yaml:"red_packet_broadcast_consumer_group"` TaskConsumerGroup string `yaml:"task_consumer_group"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` @@ -176,11 +185,12 @@ type WalletOutboxMQConfig struct { // UserOutboxMQConfig 控制 activity 对 user_outbox topic 的独立消费组。 type UserOutboxMQConfig struct { - Enabled bool `yaml:"enabled"` - Topic string `yaml:"topic"` - ConsumerGroup string `yaml:"consumer_group"` - TaskConsumerGroup string `yaml:"task_consumer_group"` - ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` + Enabled bool `yaml:"enabled"` + Topic string `yaml:"topic"` + ConsumerGroup string `yaml:"consumer_group"` + TaskConsumerGroup string `yaml:"task_consumer_group"` + InviteActivityConsumerGroup string `yaml:"invite_activity_consumer_group"` + ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` } // Default 返回本地默认配置。 @@ -201,6 +211,9 @@ func Default() Config { CumulativeRechargeRewardWorker: CumulativeRechargeRewardWorkerConfig{ Enabled: false, }, + InviteActivityRewardWorker: InviteActivityRewardWorkerConfig{ + Enabled: true, + }, RedPacketBroadcastWorker: RedPacketBroadcastWorkerConfig{ Enabled: false, }, @@ -267,16 +280,18 @@ func defaultRocketMQConfig() RocketMQConfig { RealtimeTopic: "", FirstRechargeConsumerGroup: "hyapp-activity-first-recharge-wallet-outbox", CumulativeRechargeConsumerGroup: "hyapp-activity-cumulative-recharge-wallet-outbox", + InviteActivityConsumerGroup: "hyapp-activity-invite-wallet-outbox", RedPacketBroadcastConsumerGroup: "hyapp-activity-red-packet-wallet-outbox", TaskConsumerGroup: "hyapp-activity-task-wallet-outbox", ConsumerMaxReconsumeTimes: 16, }, UserOutbox: UserOutboxMQConfig{ - Enabled: false, - Topic: "hyapp_user_outbox", - ConsumerGroup: "hyapp-activity-user-region-broadcast", - TaskConsumerGroup: "hyapp-activity-task-user-outbox", - ConsumerMaxReconsumeTimes: 16, + Enabled: false, + Topic: "hyapp_user_outbox", + ConsumerGroup: "hyapp-activity-user-region-broadcast", + TaskConsumerGroup: "hyapp-activity-task-user-outbox", + InviteActivityConsumerGroup: "hyapp-activity-invite-user-outbox", + ConsumerMaxReconsumeTimes: 16, }, } } @@ -376,9 +391,12 @@ func Load(path string) (Config, error) { return Config{}, err } cfg.RocketMQ = rocketMQ - if (cfg.FirstRechargeRewardWorker.Enabled || cfg.CumulativeRechargeRewardWorker.Enabled || cfg.RedPacketBroadcastWorker.Enabled) && !cfg.RocketMQ.WalletOutbox.Enabled { + if (cfg.FirstRechargeRewardWorker.Enabled || cfg.CumulativeRechargeRewardWorker.Enabled || cfg.InviteActivityRewardWorker.Enabled || cfg.RedPacketBroadcastWorker.Enabled) && !cfg.RocketMQ.WalletOutbox.Enabled { return Config{}, errors.New("wallet outbox workers require rocketmq.wallet_outbox.enabled") } + if cfg.InviteActivityRewardWorker.Enabled && !cfg.RocketMQ.UserOutbox.Enabled { + return Config{}, errors.New("invite activity reward worker requires rocketmq.user_outbox.enabled") + } if cfg.LuckyGiftWorker.Enabled && !cfg.TencentIM.Enabled { return Config{}, errors.New("lucky gift worker requires tencent_im.enabled") } @@ -413,6 +431,9 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.WalletOutbox.CumulativeRechargeConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.CumulativeRechargeConsumerGroup); cfg.WalletOutbox.CumulativeRechargeConsumerGroup == "" { cfg.WalletOutbox.CumulativeRechargeConsumerGroup = defaults.WalletOutbox.CumulativeRechargeConsumerGroup } + if cfg.WalletOutbox.InviteActivityConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.InviteActivityConsumerGroup); cfg.WalletOutbox.InviteActivityConsumerGroup == "" { + cfg.WalletOutbox.InviteActivityConsumerGroup = defaults.WalletOutbox.InviteActivityConsumerGroup + } if cfg.WalletOutbox.RedPacketBroadcastConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.RedPacketBroadcastConsumerGroup); cfg.WalletOutbox.RedPacketBroadcastConsumerGroup == "" { cfg.WalletOutbox.RedPacketBroadcastConsumerGroup = defaults.WalletOutbox.RedPacketBroadcastConsumerGroup } @@ -431,6 +452,9 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.UserOutbox.TaskConsumerGroup = strings.TrimSpace(cfg.UserOutbox.TaskConsumerGroup); cfg.UserOutbox.TaskConsumerGroup == "" { cfg.UserOutbox.TaskConsumerGroup = defaults.UserOutbox.TaskConsumerGroup } + if cfg.UserOutbox.InviteActivityConsumerGroup = strings.TrimSpace(cfg.UserOutbox.InviteActivityConsumerGroup); cfg.UserOutbox.InviteActivityConsumerGroup == "" { + cfg.UserOutbox.InviteActivityConsumerGroup = defaults.UserOutbox.InviteActivityConsumerGroup + } if cfg.UserOutbox.ConsumerMaxReconsumeTimes <= 0 { cfg.UserOutbox.ConsumerMaxReconsumeTimes = defaults.UserOutbox.ConsumerMaxReconsumeTimes } diff --git a/services/activity-service/internal/config/config_test.go b/services/activity-service/internal/config/config_test.go index 6cfcab6e..15a53d65 100644 --- a/services/activity-service/internal/config/config_test.go +++ b/services/activity-service/internal/config/config_test.go @@ -11,8 +11,11 @@ func TestLoadLocalEnablesOutboxMQConsumers(t *testing.T) { if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || !cfg.RocketMQ.UserOutbox.Enabled { t.Fatalf("local config must enable all activity outbox MQ consumers: %+v", cfg.RocketMQ) } - if !cfg.FirstRechargeRewardWorker.Enabled || !cfg.CumulativeRechargeRewardWorker.Enabled || !cfg.RedPacketBroadcastWorker.Enabled { - t.Fatalf("local config must enable wallet outbox workers: first=%+v cumulative=%+v red_packet=%+v", cfg.FirstRechargeRewardWorker, cfg.CumulativeRechargeRewardWorker, cfg.RedPacketBroadcastWorker) + if !cfg.FirstRechargeRewardWorker.Enabled || !cfg.CumulativeRechargeRewardWorker.Enabled || !cfg.InviteActivityRewardWorker.Enabled || !cfg.RedPacketBroadcastWorker.Enabled { + t.Fatalf("local config must enable wallet outbox workers: first=%+v cumulative=%+v invite=%+v red_packet=%+v", cfg.FirstRechargeRewardWorker, cfg.CumulativeRechargeRewardWorker, cfg.InviteActivityRewardWorker, cfg.RedPacketBroadcastWorker) + } + if cfg.RocketMQ.WalletOutbox.InviteActivityConsumerGroup == "" || cfg.RocketMQ.UserOutbox.InviteActivityConsumerGroup == "" { + t.Fatalf("local config must configure invite activity consumer groups: wallet=%+v user=%+v", cfg.RocketMQ.WalletOutbox, cfg.RocketMQ.UserOutbox) } } @@ -30,4 +33,7 @@ func TestLoadTencentExampleEnablesRoomOutboxMQ(t *testing.T) { if cfg.RocketMQ.WalletOutbox.RealtimeTopic != "hyapp_wallet_realtime_outbox" { t.Fatalf("red packet worker should consume realtime wallet topic in tencent example: %+v", cfg.RocketMQ.WalletOutbox) } + if !cfg.InviteActivityRewardWorker.Enabled || cfg.RocketMQ.WalletOutbox.InviteActivityConsumerGroup == "" || cfg.RocketMQ.UserOutbox.InviteActivityConsumerGroup == "" { + t.Fatalf("tencent example must enable invite activity consumer groups: invite=%+v wallet=%+v user=%+v", cfg.InviteActivityRewardWorker, cfg.RocketMQ.WalletOutbox, cfg.RocketMQ.UserOutbox) + } } diff --git a/services/activity-service/internal/domain/inviteactivity/invite_activity.go b/services/activity-service/internal/domain/inviteactivity/invite_activity.go new file mode 100644 index 00000000..ffaf9939 --- /dev/null +++ b/services/activity-service/internal/domain/inviteactivity/invite_activity.go @@ -0,0 +1,163 @@ +package inviteactivity + +const ( + TierStatusActive = "active" + TierStatusInactive = "inactive" + + RewardTypeRecharge = "recharge" + RewardTypeValidInvite = "valid_invite" + + ClaimStatusPending = "pending" + ClaimStatusGranted = "granted" + ClaimStatusFailed = "failed" + + ReasonEligible = "eligible" + ReasonAlreadyConsumed = "already_consumed" + ReasonNoRelation = "no_invite_relation" + ReasonDuplicate = "duplicate" + + EventTypeWalletRechargeRecorded = "WalletRechargeRecorded" + EventTypeUserInviteBecameValid = "UserInviteBecameValid" +) + +// Tier 是邀请活动的金币奖励档位;不同 reward_type 使用不同门槛字段。 +type Tier struct { + TierID int64 + AppCode string + RewardType string + TierCode string + TierName string + ThresholdCoinAmount int64 + ThresholdValidInviteCount int64 + RewardCoinAmount int64 + Status string + SortOrder int32 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// Config 是当前 App 的邀请活动配置。 +type Config struct { + AppCode string + Enabled bool + Tiers []Tier + UpdatedByAdminID int64 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// Progress 是邀请人在一个 UTC 自然月内的邀请活动累计。 +type Progress struct { + AppCode string + CycleKey string + UserID int64 + TotalRechargeCoinAmount int64 + ValidInviteCount int64 + ClaimedRewardCoinAmount int64 + UpdatedAtMS int64 +} + +// Claim 是用户点击领取后创建的钱包发奖事实。 +type Claim struct { + ClaimID string + AppCode string + CycleKey string + UserID int64 + RewardType string + CommandID string + TierID int64 + TierCode string + TierName string + ThresholdCoinAmount int64 + ThresholdValidInviteCount int64 + ReachedValue int64 + RewardCoinAmount int64 + Status string + WalletCommandID string + WalletTransactionID string + FailureReason string + GrantedAtMS int64 + CreatedAtMS int64 + UpdatedAtMS int64 +} + +// RechargeEvent 是 wallet_outbox 进入邀请活动的成功充值事实。 +type RechargeEvent struct { + AppCode string + EventID string + EventType string + TransactionID string + CommandID string + InvitedUserID int64 + InviterUserID int64 + RechargeCoinAmount int64 + RechargeType string + PayloadJSON string + OccurredAtMS int64 +} + +// ValidInviteEvent 是 user_outbox 里“被邀请用户首次成为有效邀请”的事实。 +type ValidInviteEvent struct { + AppCode string + EventID string + EventType string + InvitedUserID int64 + InviterUserID int64 + PayloadJSON string + OccurredAtMS int64 +} + +// Cycle 表达 UTC 自然月周期边界。 +type Cycle struct { + Key string + StartMS int64 + EndMS int64 +} + +// StatusResult 是 App H5 页面读取的完整邀请活动状态。 +type StatusResult struct { + Config Config + Progress Progress + Claims []Claim + Cycle Cycle + ServerTimeMS int64 +} + +// ConsumeResult 表达一次事件是否被写入进度。 +type ConsumeResult struct { + Consumed bool + Reason string +} + +// ClaimQuery 是后台领取记录查询条件。 +type ClaimQuery struct { + Status string + RewardType string + UserID int64 + CycleKey string + Page int32 + PageSize int32 +} + +// LeaderboardEntry 是邀请活动月榜的一行,只统计已经达到有效邀请标准的邀请人数。 +type LeaderboardEntry struct { + RankNo int32 + UserID int64 + ValidInviteCount int64 + TotalRechargeCoinAmount int64 +} + +// LeaderboardQuery 是邀请活动月榜分页条件;CycleKey 为空时由 service 使用当前 UTC 月。 +type LeaderboardQuery struct { + CycleKey string + Page int32 + PageSize int32 +} + +// LeaderboardResult 是邀请活动月榜查询结果。 +type LeaderboardResult struct { + Entries []LeaderboardEntry + Total int64 + CycleKey string + ServerTimeMS int64 +} diff --git a/services/activity-service/internal/service/inviteactivity/service.go b/services/activity-service/internal/service/inviteactivity/service.go new file mode 100644 index 00000000..13cd8105 --- /dev/null +++ b/services/activity-service/internal/service/inviteactivity/service.go @@ -0,0 +1,455 @@ +package inviteactivity + +import ( + "context" + "fmt" + "sort" + "strings" + "time" + + "google.golang.org/grpc" + userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + domain "hyapp/services/activity-service/internal/domain/inviteactivity" +) + +const grantReason = "invite_activity_reward" + +type Repository interface { + GetInviteActivityRewardConfig(ctx context.Context) (domain.Config, bool, error) + UpdateInviteActivityRewardConfig(ctx context.Context, config domain.Config, nowMS int64) (domain.Config, error) + GetInviteActivityRewardProgress(ctx context.Context, cycleKey string, userID int64) (domain.Progress, bool, error) + ListInviteActivityRewardClaimsByUserCycle(ctx context.Context, cycleKey string, userID int64) ([]domain.Claim, error) + ConsumeInviteActivityRechargeEvent(ctx context.Context, event domain.RechargeEvent, cycle domain.Cycle, nowMS int64) (domain.ConsumeResult, error) + ConsumeInviteActivityValidInviteEvent(ctx context.Context, event domain.ValidInviteEvent, cycle domain.Cycle, nowMS int64) (domain.ConsumeResult, error) + CreateInviteActivityRewardClaim(ctx context.Context, claim domain.Claim, nowMS int64) (domain.Claim, error) + MarkInviteActivityRewardClaimGranted(ctx context.Context, claimID string, walletTransactionID string, grantedAtMS int64) (domain.Claim, error) + MarkInviteActivityRewardClaimFailed(ctx context.Context, claimID string, failureReason string, nowMS int64) error + ListInviteActivityRewardClaims(ctx context.Context, query domain.ClaimQuery) ([]domain.Claim, int64, error) + ListInviteActivityLeaderboard(ctx context.Context, query domain.LeaderboardQuery) ([]domain.LeaderboardEntry, int64, error) +} + +type WalletClient interface { + CreditInviteActivityReward(ctx context.Context, req *walletv1.CreditInviteActivityRewardRequest, opts ...grpc.CallOption) (*walletv1.CreditInviteActivityRewardResponse, error) +} + +type InviteAttributionSource interface { + GetInviteAttribution(ctx context.Context, invitedUserID int64) (*userv1.InviteAttribution, error) +} + +type Service struct { + repository Repository + wallet WalletClient + invite InviteAttributionSource + now func() time.Time +} + +func New(repository Repository, wallet WalletClient, invite InviteAttributionSource) *Service { + return &Service{repository: repository, wallet: wallet, invite: invite, now: time.Now} +} + +func (s *Service) SetClock(now func() time.Time) { + if now != nil { + s.now = now + } +} + +func (s *Service) GetStatus(ctx context.Context, userID int64) (domain.StatusResult, error) { + if err := s.requireRepository(); err != nil { + return domain.StatusResult{}, err + } + if userID <= 0 { + return domain.StatusResult{}, xerr.New(xerr.InvalidArgument, "user_id is required") + } + now := s.now().UTC() + cycle := CycleForTime(now) + result := domain.StatusResult{Cycle: cycle, ServerTimeMS: now.UnixMilli()} + + config, exists, err := s.repository.GetInviteActivityRewardConfig(ctx) + if err != nil { + return domain.StatusResult{}, err + } + if exists { + config.Tiers = activeTiers(config.Tiers) + result.Config = config + } else { + result.Config = domain.Config{AppCode: appcode.FromContext(ctx), Enabled: true} + } + progress, _, err := s.repository.GetInviteActivityRewardProgress(ctx, cycle.Key, userID) + if err != nil { + return domain.StatusResult{}, err + } + result.Progress = progress + claims, err := s.repository.ListInviteActivityRewardClaimsByUserCycle(ctx, cycle.Key, userID) + if err != nil { + return domain.StatusResult{}, err + } + result.Claims = claims + return result, nil +} + +func (s *Service) GetConfig(ctx context.Context) (domain.Config, error) { + if err := s.requireRepository(); err != nil { + return domain.Config{}, err + } + config, exists, err := s.repository.GetInviteActivityRewardConfig(ctx) + if err != nil { + return domain.Config{}, err + } + if !exists { + return domain.Config{AppCode: appcode.FromContext(ctx), Enabled: true}, nil + } + return config, nil +} + +func (s *Service) UpdateConfig(ctx context.Context, config domain.Config) (domain.Config, error) { + if err := s.requireRepository(); err != nil { + return domain.Config{}, err + } + config.AppCode = appcode.FromContext(ctx) + config.Tiers = normalizeTiers(config.Tiers) + if err := validateConfig(config); err != nil { + return domain.Config{}, err + } + return s.repository.UpdateInviteActivityRewardConfig(ctx, config, s.now().UTC().UnixMilli()) +} + +func (s *Service) ConsumeWalletRechargeEvent(ctx context.Context, event domain.RechargeEvent) error { + if err := s.requireRepository(); err != nil { + return err + } + if event.EventType != domain.EventTypeWalletRechargeRecorded { + return nil + } + event = normalizeRechargeEvent(event) + if event.RechargeCoinAmount <= 0 || event.InvitedUserID <= 0 || event.EventID == "" { + return nil + } + if s.invite == nil { + return xerr.New(xerr.Unavailable, "invite attribution source is not configured") + } + // 邀请归因只由 user-service 拥有;activity-service 按充值事实反查邀请人,不复制邀请关系表。 + attribution, err := s.invite.GetInviteAttribution(appcode.WithContext(ctx, event.AppCode), event.InvitedUserID) + if err != nil { + return err + } + if attribution == nil || !attribution.GetFound() || attribution.GetInviterUserId() <= 0 { + return nil + } + event.InviterUserID = attribution.GetInviterUserId() + cycle := CycleForTime(time.UnixMilli(event.OccurredAtMS).UTC()) + _, err = s.repository.ConsumeInviteActivityRechargeEvent(appcode.WithContext(ctx, event.AppCode), event, cycle, s.now().UTC().UnixMilli()) + return err +} + +func (s *Service) ConsumeValidInviteEvent(ctx context.Context, event domain.ValidInviteEvent) error { + if err := s.requireRepository(); err != nil { + return err + } + if event.EventType != domain.EventTypeUserInviteBecameValid || event.EventID == "" || event.InviterUserID <= 0 { + return nil + } + if event.OccurredAtMS <= 0 { + event.OccurredAtMS = s.now().UTC().UnixMilli() + } + cycle := CycleForTime(time.UnixMilli(event.OccurredAtMS).UTC()) + _, err := s.repository.ConsumeInviteActivityValidInviteEvent(appcode.WithContext(ctx, event.AppCode), event, cycle, s.now().UTC().UnixMilli()) + return err +} + +func (s *Service) Claim(ctx context.Context, userID int64, rewardType string, tierID int64, commandID string) (domain.Claim, domain.StatusResult, error) { + if err := s.requireRepository(); err != nil { + return domain.Claim{}, domain.StatusResult{}, err + } + if s.wallet == nil { + return domain.Claim{}, domain.StatusResult{}, xerr.New(xerr.Unavailable, "wallet client is not configured") + } + rewardType = normalizeRewardType(rewardType) + commandID = strings.TrimSpace(commandID) + if userID <= 0 || rewardType == "" || tierID <= 0 || commandID == "" { + return domain.Claim{}, domain.StatusResult{}, xerr.New(xerr.InvalidArgument, "claim request is incomplete") + } + status, err := s.GetStatus(ctx, userID) + if err != nil { + return domain.Claim{}, domain.StatusResult{}, err + } + if !status.Config.Enabled { + return domain.Claim{}, domain.StatusResult{}, xerr.New(xerr.Conflict, "invite activity is disabled") + } + tier, ok := findTier(status.Config.Tiers, rewardType, tierID) + if !ok { + return domain.Claim{}, domain.StatusResult{}, xerr.New(xerr.NotFound, "invite activity tier not found") + } + reached := reachedValue(status.Progress, rewardType) + if !tierReached(tier, reached) { + return domain.Claim{}, domain.StatusResult{}, xerr.New(xerr.Conflict, "invite activity tier is not reached") + } + rewardAmount, err := claimRewardAmount(status.Claims, status.Config.Tiers, tier, reached) + if err != nil { + return domain.Claim{}, domain.StatusResult{}, err + } + claim := domain.Claim{ + AppCode: appcode.FromContext(ctx), + ClaimID: fmt.Sprintf("IAR_%d_%s_%s_%d", userID, status.Cycle.Key, rewardType, tier.TierID), + CycleKey: status.Cycle.Key, + UserID: userID, + RewardType: rewardType, + CommandID: commandID, + TierID: tier.TierID, + TierCode: tier.TierCode, + TierName: tier.TierName, + ThresholdCoinAmount: tier.ThresholdCoinAmount, + ThresholdValidInviteCount: tier.ThresholdValidInviteCount, + ReachedValue: reached, + RewardCoinAmount: rewardAmount, + WalletCommandID: commandID, + } + created, err := s.repository.CreateInviteActivityRewardClaim(ctx, claim, s.now().UTC().UnixMilli()) + if err != nil { + return domain.Claim{}, domain.StatusResult{}, err + } + if created.Status == domain.ClaimStatusGranted { + refreshed, refreshErr := s.GetStatus(ctx, userID) + return created, refreshed, refreshErr + } + resp, err := s.wallet.CreditInviteActivityReward(ctx, &walletv1.CreditInviteActivityRewardRequest{ + CommandId: created.WalletCommandID, + AppCode: appcode.FromContext(ctx), + TargetUserId: userID, + Amount: created.RewardCoinAmount, + ClaimId: created.ClaimID, + RewardType: created.RewardType, + TierId: created.TierID, + TierCode: created.TierCode, + CycleKey: created.CycleKey, + ReachedValue: created.ReachedValue, + Reason: grantReason, + }) + if err != nil { + _ = s.repository.MarkInviteActivityRewardClaimFailed(ctx, created.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli()) + return domain.Claim{}, domain.StatusResult{}, err + } + updated, err := s.repository.MarkInviteActivityRewardClaimGranted(ctx, created.ClaimID, resp.GetTransactionId(), resp.GetGrantedAtMs()) + if err != nil { + return domain.Claim{}, domain.StatusResult{}, err + } + refreshed, err := s.GetStatus(ctx, userID) + return updated, refreshed, err +} + +func (s *Service) ListClaims(ctx context.Context, query domain.ClaimQuery) ([]domain.Claim, int64, error) { + if err := s.requireRepository(); err != nil { + return nil, 0, err + } + query.Status = strings.TrimSpace(query.Status) + query.RewardType = normalizeRewardType(query.RewardType) + query.CycleKey = strings.TrimSpace(query.CycleKey) + if query.Page <= 0 { + query.Page = 1 + } + if query.PageSize <= 0 { + query.PageSize = 20 + } + if query.PageSize > 100 { + query.PageSize = 100 + } + return s.repository.ListInviteActivityRewardClaims(ctx, query) +} + +func (s *Service) ListLeaderboard(ctx context.Context, query domain.LeaderboardQuery) (domain.LeaderboardResult, error) { + if err := s.requireRepository(); err != nil { + return domain.LeaderboardResult{}, err + } + now := s.now().UTC() + query.CycleKey = strings.TrimSpace(query.CycleKey) + if query.CycleKey == "" { + query.CycleKey = CycleForTime(now).Key + } + if query.Page <= 0 { + query.Page = 1 + } + if query.PageSize <= 0 { + query.PageSize = 50 + } + if query.PageSize > 100 { + query.PageSize = 100 + } + // 邀请活动榜单只对外展示“有效邀请人数”榜,累计充值金额只作为同分时的稳定排序依据和扩展字段返回。 + entries, total, err := s.repository.ListInviteActivityLeaderboard(ctx, query) + if err != nil { + return domain.LeaderboardResult{}, err + } + return domain.LeaderboardResult{ + Entries: entries, + Total: total, + CycleKey: query.CycleKey, + ServerTimeMS: now.UnixMilli(), + }, nil +} + +func CycleForTime(value time.Time) domain.Cycle { + utc := value.UTC() + start := time.Date(utc.Year(), utc.Month(), 1, 0, 0, 0, 0, time.UTC) + end := start.AddDate(0, 1, 0) + return domain.Cycle{Key: start.Format("2006-01"), StartMS: start.UnixMilli(), EndMS: end.UnixMilli() - 1} +} + +func normalizeTiers(tiers []domain.Tier) []domain.Tier { + items := make([]domain.Tier, 0, len(tiers)) + for _, tier := range tiers { + tier.RewardType = normalizeRewardType(tier.RewardType) + tier.TierCode = strings.TrimSpace(tier.TierCode) + tier.TierName = strings.TrimSpace(tier.TierName) + tier.Status = strings.ToLower(strings.TrimSpace(tier.Status)) + if tier.Status == "" { + tier.Status = domain.TierStatusActive + } + items = append(items, tier) + } + sort.SliceStable(items, func(i, j int) bool { + if items[i].RewardType == items[j].RewardType { + if items[i].SortOrder == items[j].SortOrder { + return thresholdValue(items[i]) < thresholdValue(items[j]) + } + return items[i].SortOrder < items[j].SortOrder + } + return items[i].RewardType < items[j].RewardType + }) + return items +} + +func validateConfig(config domain.Config) error { + if config.UpdatedByAdminID <= 0 { + return xerr.New(xerr.InvalidArgument, "operator_admin_id is required") + } + if config.Enabled && len(activeTiers(config.Tiers)) == 0 { + return xerr.New(xerr.InvalidArgument, "enabled invite activity requires active tiers") + } + seen := map[string]struct{}{} + for _, tier := range config.Tiers { + if tier.RewardType == "" || tier.TierCode == "" || tier.RewardCoinAmount <= 0 { + return xerr.New(xerr.InvalidArgument, "invite activity tier is incomplete") + } + if tier.RewardType == domain.RewardTypeRecharge && tier.ThresholdCoinAmount <= 0 { + return xerr.New(xerr.InvalidArgument, "recharge tier threshold is required") + } + if tier.RewardType == domain.RewardTypeValidInvite && tier.ThresholdValidInviteCount <= 0 { + return xerr.New(xerr.InvalidArgument, "valid invite tier threshold is required") + } + key := fmt.Sprintf("%s:%d", tier.RewardType, thresholdValue(tier)) + if _, ok := seen[key]; ok { + return xerr.New(xerr.InvalidArgument, "duplicate invite activity tier threshold") + } + seen[key] = struct{}{} + } + return nil +} + +func claimRewardAmount(claims []domain.Claim, tiers []domain.Tier, tier domain.Tier, reached int64) (int64, error) { + if tier.RewardType == domain.RewardTypeValidInvite { + for _, claim := range claims { + if claim.RewardType == tier.RewardType && claim.TierID == tier.TierID && claim.Status == domain.ClaimStatusGranted { + return 0, xerr.New(xerr.Conflict, "invite activity tier already claimed") + } + } + return tier.RewardCoinAmount, nil + } + highest, ok := highestReachedTier(tiers, domain.RewardTypeRecharge, reached) + if !ok || highest.TierID != tier.TierID { + return 0, xerr.New(xerr.Conflict, "only highest recharge tier can be claimed") + } + var granted int64 + for _, claim := range claims { + if claim.RewardType == domain.RewardTypeRecharge && claim.Status == domain.ClaimStatusGranted { + granted += claim.RewardCoinAmount + } + } + amount := tier.RewardCoinAmount - granted + if amount <= 0 { + return 0, xerr.New(xerr.Conflict, "invite activity reward already claimed") + } + return amount, nil +} + +func activeTiers(tiers []domain.Tier) []domain.Tier { + items := make([]domain.Tier, 0, len(tiers)) + for _, tier := range tiers { + if tier.Status == domain.TierStatusActive { + items = append(items, tier) + } + } + return items +} + +func normalizeRewardType(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case domain.RewardTypeRecharge: + return domain.RewardTypeRecharge + case domain.RewardTypeValidInvite: + return domain.RewardTypeValidInvite + default: + return "" + } +} + +func normalizeRechargeEvent(event domain.RechargeEvent) domain.RechargeEvent { + event.AppCode = appcode.Normalize(event.AppCode) + event.EventID = strings.TrimSpace(event.EventID) + event.EventType = strings.TrimSpace(event.EventType) + event.TransactionID = strings.TrimSpace(event.TransactionID) + event.CommandID = strings.TrimSpace(event.CommandID) + event.RechargeType = strings.TrimSpace(event.RechargeType) + if event.OccurredAtMS <= 0 { + event.OccurredAtMS = time.Now().UTC().UnixMilli() + } + return event +} + +func reachedValue(progress domain.Progress, rewardType string) int64 { + if rewardType == domain.RewardTypeValidInvite { + return progress.ValidInviteCount + } + return progress.TotalRechargeCoinAmount +} + +func tierReached(tier domain.Tier, reached int64) bool { + return reached >= thresholdValue(tier) +} + +func thresholdValue(tier domain.Tier) int64 { + if tier.RewardType == domain.RewardTypeValidInvite { + return tier.ThresholdValidInviteCount + } + return tier.ThresholdCoinAmount +} + +func findTier(tiers []domain.Tier, rewardType string, tierID int64) (domain.Tier, bool) { + for _, tier := range tiers { + if tier.RewardType == rewardType && tier.TierID == tierID { + return tier, true + } + } + return domain.Tier{}, false +} + +func highestReachedTier(tiers []domain.Tier, rewardType string, reached int64) (domain.Tier, bool) { + var selected domain.Tier + for _, tier := range tiers { + if tier.RewardType != rewardType || !tierReached(tier, reached) { + continue + } + if selected.TierID == 0 || thresholdValue(tier) > thresholdValue(selected) { + selected = tier + } + } + return selected, selected.TierID > 0 +} + +func (s *Service) requireRepository() error { + if s == nil || s.repository == nil { + return xerr.New(xerr.Unavailable, "invite activity repository is not configured") + } + return nil +} diff --git a/services/activity-service/internal/service/inviteactivity/service_test.go b/services/activity-service/internal/service/inviteactivity/service_test.go new file mode 100644 index 00000000..8f4a11cd --- /dev/null +++ b/services/activity-service/internal/service/inviteactivity/service_test.go @@ -0,0 +1,198 @@ +package inviteactivity + +import ( + "context" + "testing" + "time" + + "google.golang.org/grpc" + walletv1 "hyapp.local/api/proto/wallet/v1" + "hyapp/pkg/appcode" + domain "hyapp/services/activity-service/internal/domain/inviteactivity" +) + +func TestClaimRechargeOnlyHighestTierDelta(t *testing.T) { + repo := newFakeInviteActivityRepo() + repo.config = domain.Config{ + AppCode: "lalu", + Enabled: true, + Tiers: []domain.Tier{ + {TierID: 1, RewardType: domain.RewardTypeRecharge, TierCode: "r1", TierName: "80k", ThresholdCoinAmount: 80000, RewardCoinAmount: 1000, Status: domain.TierStatusActive, SortOrder: 1}, + {TierID: 2, RewardType: domain.RewardTypeRecharge, TierCode: "r2", TierName: "160k", ThresholdCoinAmount: 160000, RewardCoinAmount: 2500, Status: domain.TierStatusActive, SortOrder: 2}, + }, + } + repo.progress = domain.Progress{AppCode: "lalu", CycleKey: "2026-06", UserID: 42, TotalRechargeCoinAmount: 200000} + repo.claims = []domain.Claim{ + {ClaimID: "IAR_42_2026-06_recharge_1", AppCode: "lalu", CycleKey: "2026-06", UserID: 42, RewardType: domain.RewardTypeRecharge, TierID: 1, RewardCoinAmount: 1000, Status: domain.ClaimStatusGranted}, + } + wallet := &fakeInviteActivityWallet{} + service := New(repo, wallet, nil) + service.SetClock(func() time.Time { return time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC) }) + ctx := appcode.WithContext(context.Background(), "lalu") + + if _, _, err := service.Claim(ctx, 42, domain.RewardTypeRecharge, 1, "cmd-low"); err == nil { + t.Fatalf("expected lower recharge tier claim to be rejected") + } + + claim, status, err := service.Claim(ctx, 42, domain.RewardTypeRecharge, 2, "cmd-high") + if err != nil { + t.Fatalf("claim highest recharge tier: %v", err) + } + if claim.RewardCoinAmount != 1500 { + t.Fatalf("expected delta reward 1500, got %d", claim.RewardCoinAmount) + } + if len(wallet.requests) != 1 || wallet.requests[0].Amount != 1500 { + t.Fatalf("wallet amount mismatch: %#v", wallet.requests) + } + if len(status.Claims) != 2 { + t.Fatalf("expected refreshed status with 2 claims, got %d", len(status.Claims)) + } +} + +func TestClaimValidInviteEachReachedTier(t *testing.T) { + repo := newFakeInviteActivityRepo() + repo.config = domain.Config{ + AppCode: "lalu", + Enabled: true, + Tiers: []domain.Tier{ + {TierID: 11, RewardType: domain.RewardTypeValidInvite, TierCode: "v1", TierName: "one", ThresholdValidInviteCount: 1, RewardCoinAmount: 100, Status: domain.TierStatusActive, SortOrder: 1}, + {TierID: 12, RewardType: domain.RewardTypeValidInvite, TierCode: "v3", TierName: "three", ThresholdValidInviteCount: 3, RewardCoinAmount: 500, Status: domain.TierStatusActive, SortOrder: 2}, + }, + } + repo.progress = domain.Progress{AppCode: "lalu", CycleKey: "2026-06", UserID: 77, ValidInviteCount: 3} + wallet := &fakeInviteActivityWallet{} + service := New(repo, wallet, nil) + service.SetClock(func() time.Time { return time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC) }) + ctx := appcode.WithContext(context.Background(), "lalu") + + first, _, err := service.Claim(ctx, 77, domain.RewardTypeValidInvite, 11, "cmd-valid-1") + if err != nil { + t.Fatalf("claim first valid invite tier: %v", err) + } + second, _, err := service.Claim(ctx, 77, domain.RewardTypeValidInvite, 12, "cmd-valid-3") + if err != nil { + t.Fatalf("claim second valid invite tier: %v", err) + } + if first.RewardCoinAmount != 100 || second.RewardCoinAmount != 500 { + t.Fatalf("unexpected rewards: first=%d second=%d", first.RewardCoinAmount, second.RewardCoinAmount) + } + if len(wallet.requests) != 2 { + t.Fatalf("expected 2 wallet credits, got %d", len(wallet.requests)) + } + if _, _, err := service.Claim(ctx, 77, domain.RewardTypeValidInvite, 11, "cmd-valid-repeat"); err == nil { + t.Fatalf("expected duplicate valid invite tier claim to be rejected") + } +} + +func TestListLeaderboardDefaultsCurrentCycleAndCapsPageSize(t *testing.T) { + repo := newFakeInviteActivityRepo() + repo.leaderboardEntries = []domain.LeaderboardEntry{ + {RankNo: 1, UserID: 1001, ValidInviteCount: 3, TotalRechargeCoinAmount: 240000}, + } + service := New(repo, nil, nil) + service.SetClock(func() time.Time { return time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC) }) + ctx := appcode.WithContext(context.Background(), "lalu") + + result, err := service.ListLeaderboard(ctx, domain.LeaderboardQuery{PageSize: 1000}) + if err != nil { + t.Fatalf("list leaderboard: %v", err) + } + if result.CycleKey != "2026-06" { + t.Fatalf("expected current cycle, got %q", result.CycleKey) + } + if repo.lastLeaderboardQuery.Page != 1 || repo.lastLeaderboardQuery.PageSize != 100 { + t.Fatalf("unexpected normalized query: %#v", repo.lastLeaderboardQuery) + } + if result.Total != 1 || len(result.Entries) != 1 || result.Entries[0].UserID != 1001 { + t.Fatalf("unexpected leaderboard result: %#v", result) + } +} + +type fakeInviteActivityRepo struct { + config domain.Config + progress domain.Progress + claims []domain.Claim + leaderboardEntries []domain.LeaderboardEntry + lastLeaderboardQuery domain.LeaderboardQuery +} + +func newFakeInviteActivityRepo() *fakeInviteActivityRepo { + return &fakeInviteActivityRepo{} +} + +func (r *fakeInviteActivityRepo) GetInviteActivityRewardConfig(context.Context) (domain.Config, bool, error) { + return r.config, true, nil +} + +func (r *fakeInviteActivityRepo) UpdateInviteActivityRewardConfig(context.Context, domain.Config, int64) (domain.Config, error) { + return domain.Config{}, nil +} + +func (r *fakeInviteActivityRepo) GetInviteActivityRewardProgress(context.Context, string, int64) (domain.Progress, bool, error) { + return r.progress, true, nil +} + +func (r *fakeInviteActivityRepo) ListInviteActivityRewardClaimsByUserCycle(context.Context, string, int64) ([]domain.Claim, error) { + return append([]domain.Claim(nil), r.claims...), nil +} + +func (r *fakeInviteActivityRepo) ConsumeInviteActivityRechargeEvent(context.Context, domain.RechargeEvent, domain.Cycle, int64) (domain.ConsumeResult, error) { + return domain.ConsumeResult{}, nil +} + +func (r *fakeInviteActivityRepo) ConsumeInviteActivityValidInviteEvent(context.Context, domain.ValidInviteEvent, domain.Cycle, int64) (domain.ConsumeResult, error) { + return domain.ConsumeResult{}, nil +} + +func (r *fakeInviteActivityRepo) CreateInviteActivityRewardClaim(_ context.Context, claim domain.Claim, nowMS int64) (domain.Claim, error) { + for _, existing := range r.claims { + if existing.ClaimID == claim.ClaimID { + return existing, nil + } + } + claim.Status = domain.ClaimStatusPending + claim.CreatedAtMS = nowMS + claim.UpdatedAtMS = nowMS + r.claims = append(r.claims, claim) + return claim, nil +} + +func (r *fakeInviteActivityRepo) MarkInviteActivityRewardClaimGranted(_ context.Context, claimID string, walletTransactionID string, grantedAtMS int64) (domain.Claim, error) { + for index := range r.claims { + if r.claims[index].ClaimID != claimID { + continue + } + r.claims[index].Status = domain.ClaimStatusGranted + r.claims[index].WalletTransactionID = walletTransactionID + r.claims[index].GrantedAtMS = grantedAtMS + r.claims[index].UpdatedAtMS = grantedAtMS + r.progress.ClaimedRewardCoinAmount += r.claims[index].RewardCoinAmount + return r.claims[index], nil + } + return domain.Claim{}, nil +} + +func (r *fakeInviteActivityRepo) MarkInviteActivityRewardClaimFailed(context.Context, string, string, int64) error { + return nil +} + +func (r *fakeInviteActivityRepo) ListInviteActivityRewardClaims(context.Context, domain.ClaimQuery) ([]domain.Claim, int64, error) { + return append([]domain.Claim(nil), r.claims...), int64(len(r.claims)), nil +} + +func (r *fakeInviteActivityRepo) ListInviteActivityLeaderboard(_ context.Context, query domain.LeaderboardQuery) ([]domain.LeaderboardEntry, int64, error) { + r.lastLeaderboardQuery = query + return append([]domain.LeaderboardEntry(nil), r.leaderboardEntries...), int64(len(r.leaderboardEntries)), nil +} + +type fakeInviteActivityWallet struct { + requests []*walletv1.CreditInviteActivityRewardRequest +} + +func (w *fakeInviteActivityWallet) CreditInviteActivityReward(_ context.Context, req *walletv1.CreditInviteActivityRewardRequest, _ ...grpc.CallOption) (*walletv1.CreditInviteActivityRewardResponse, error) { + w.requests = append(w.requests, req) + return &walletv1.CreditInviteActivityRewardResponse{ + TransactionId: "wallet-" + req.GetCommandId(), + GrantedAtMs: 1781179200000, + }, nil +} diff --git a/services/activity-service/internal/storage/mysql/invite_activity_reward_migration.go b/services/activity-service/internal/storage/mysql/invite_activity_reward_migration.go new file mode 100644 index 00000000..0b6ac108 --- /dev/null +++ b/services/activity-service/internal/storage/mysql/invite_activity_reward_migration.go @@ -0,0 +1,98 @@ +package mysql + +import "context" + +func (r *Repository) ensureInviteActivityRewardTables(ctx context.Context) error { + statements := []string{ + `CREATE TABLE IF NOT EXISTS invite_activity_reward_configs ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用邀请活动', + updated_by_admin_id 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) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动配置'`, + `CREATE TABLE IF NOT EXISTS invite_activity_reward_tiers ( + tier_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '档位自增 ID', + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', + tier_code VARCHAR(64) NOT NULL COMMENT '档位编码', + tier_name VARCHAR(128) NOT NULL COMMENT '档位展示名称', + threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', + threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', + reward_coin_amount BIGINT NOT NULL COMMENT '奖励金币数量', + status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT 'active/inactive', + sort_order INT 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 (tier_id), + UNIQUE KEY uk_invite_activity_reward_tier_code (app_code, tier_code), + KEY idx_invite_activity_reward_tier_sort (app_code, reward_type, status, sort_order) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动奖励档位'`, + `CREATE TABLE IF NOT EXISTS invite_activity_reward_events ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + event_id VARCHAR(128) NOT NULL COMMENT '上游 outbox event_id', + event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInviteBecameValid', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + invited_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '被邀请人用户 ID', + inviter_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '邀请人用户 ID', + transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '充值钱包交易 ID', + command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '充值钱包命令 ID', + recharge_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '充值来源', + recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本次充值金币', + status VARCHAR(32) NOT NULL DEFAULT 'consumed' COMMENT 'consumed/skipped', + skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因', + payload_json JSON NULL COMMENT '原始上游 payload', + occurred_at_ms BIGINT NOT NULL COMMENT '事件发生时间,UTC epoch ms', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + PRIMARY KEY (app_code, event_id), + KEY idx_invite_activity_reward_event_inviter (app_code, cycle_key, inviter_user_id, occurred_at_ms), + KEY idx_invite_activity_reward_event_invited (app_code, invited_user_id, occurred_at_ms) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动事件幂等表'`, + `CREATE TABLE IF NOT EXISTS invite_activity_reward_progress ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', + total_recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本月邀请用户累计充值金币', + valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '本月新增有效邀请人数', + claimed_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '本月已领取奖励金币', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (app_code, cycle_key, user_id), + KEY idx_invite_activity_reward_progress_recharge (app_code, cycle_key, total_recharge_coin_amount), + KEY idx_invite_activity_reward_progress_valid (app_code, cycle_key, valid_invite_count) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动用户月度进度'`, + `CREATE TABLE IF NOT EXISTS invite_activity_reward_claims ( + app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', + claim_id VARCHAR(128) NOT NULL COMMENT '领取 ID', + cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', + user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', + command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取命令 ID', + tier_id BIGINT NOT NULL COMMENT '领取档位 ID', + tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码', + tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称', + threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', + threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', + reached_value BIGINT NOT NULL DEFAULT 0 COMMENT '领取时已达成值', + reward_coin_amount BIGINT NOT NULL COMMENT '本次实际发放金币', + status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/granted/failed', + wallet_command_id VARCHAR(128) NOT NULL COMMENT '钱包发奖命令 ID', + wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包交易 ID', + failure_reason VARCHAR(512) NOT NULL DEFAULT '' 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', + PRIMARY KEY (app_code, claim_id), + UNIQUE KEY uk_invite_activity_reward_user_tier (app_code, cycle_key, user_id, reward_type, tier_id), + UNIQUE KEY uk_invite_activity_reward_wallet_command (app_code, wallet_command_id), + KEY idx_invite_activity_reward_claim_user (app_code, cycle_key, user_id, created_at_ms), + KEY idx_invite_activity_reward_claim_status (app_code, status, created_at_ms) + ) 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 +} diff --git a/services/activity-service/internal/storage/mysql/invite_activity_reward_repository.go b/services/activity-service/internal/storage/mysql/invite_activity_reward_repository.go new file mode 100644 index 00000000..3803a101 --- /dev/null +++ b/services/activity-service/internal/storage/mysql/invite_activity_reward_repository.go @@ -0,0 +1,588 @@ +package mysql + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + + "hyapp/pkg/appcode" + "hyapp/pkg/idgen" + "hyapp/pkg/xerr" + domain "hyapp/services/activity-service/internal/domain/inviteactivity" +) + +func (r *Repository) GetInviteActivityRewardConfig(ctx context.Context) (domain.Config, bool, error) { + if r == nil || r.db == nil { + return domain.Config{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + config, exists, err := r.getInviteActivityRewardConfig(ctx, nil) + if err != nil || !exists { + return config, exists, err + } + tiers, err := r.listInviteActivityRewardTiers(ctx, nil, false) + if err != nil { + return domain.Config{}, false, err + } + config.Tiers = tiers + return config, true, nil +} + +func (r *Repository) UpdateInviteActivityRewardConfig(ctx context.Context, config domain.Config, nowMS int64) (domain.Config, error) { + if r == nil || r.db == nil { + return domain.Config{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return domain.Config{}, err + } + defer func() { _ = tx.Rollback() }() + + if _, err := tx.ExecContext(ctx, ` + INSERT INTO invite_activity_reward_configs (app_code, enabled, updated_by_admin_id, created_at_ms, updated_at_ms) + VALUES (?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + enabled = VALUES(enabled), + updated_by_admin_id = VALUES(updated_by_admin_id), + updated_at_ms = VALUES(updated_at_ms)`, + appcode.FromContext(ctx), config.Enabled, config.UpdatedByAdminID, nowMS, nowMS, + ); err != nil { + return domain.Config{}, err + } + if _, err := tx.ExecContext(ctx, `DELETE FROM invite_activity_reward_tiers WHERE app_code = ?`, appcode.FromContext(ctx)); err != nil { + return domain.Config{}, err + } + for i := range config.Tiers { + tier := config.Tiers[i] + tier.CreatedAtMS = nowMS + tier.UpdatedAtMS = nowMS + if tier.TierID > 0 { + if _, err := tx.ExecContext(ctx, ` + INSERT INTO invite_activity_reward_tiers ( + tier_id, app_code, reward_type, tier_code, tier_name, threshold_coin_amount, + threshold_valid_invite_count, reward_coin_amount, status, sort_order, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + tier.TierID, appcode.FromContext(ctx), tier.RewardType, tier.TierCode, tier.TierName, tier.ThresholdCoinAmount, + tier.ThresholdValidInviteCount, tier.RewardCoinAmount, tier.Status, tier.SortOrder, nowMS, nowMS, + ); err != nil { + return domain.Config{}, err + } + continue + } + result, err := tx.ExecContext(ctx, ` + INSERT INTO invite_activity_reward_tiers ( + app_code, reward_type, tier_code, tier_name, threshold_coin_amount, + threshold_valid_invite_count, reward_coin_amount, status, sort_order, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + appcode.FromContext(ctx), tier.RewardType, tier.TierCode, tier.TierName, tier.ThresholdCoinAmount, + tier.ThresholdValidInviteCount, tier.RewardCoinAmount, tier.Status, tier.SortOrder, nowMS, nowMS, + ) + if err != nil { + return domain.Config{}, err + } + id, err := result.LastInsertId() + if err != nil { + return domain.Config{}, err + } + config.Tiers[i].TierID = id + } + if err := tx.Commit(); err != nil { + return domain.Config{}, err + } + updated, exists, err := r.GetInviteActivityRewardConfig(ctx) + if err != nil { + return domain.Config{}, err + } + if !exists { + return domain.Config{}, xerr.New(xerr.NotFound, "invite activity reward config not found") + } + return updated, nil +} + +func (r *Repository) GetInviteActivityRewardProgress(ctx context.Context, cycleKey string, userID int64) (domain.Progress, bool, error) { + if r == nil || r.db == nil { + return domain.Progress{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + row := r.db.QueryRowContext(ctx, inviteActivityRewardProgressSelectSQL()+` + WHERE app_code = ? AND cycle_key = ? AND user_id = ?`, + appcode.FromContext(ctx), cycleKey, userID, + ) + progress, err := scanInviteActivityRewardProgress(row) + if errors.Is(err, sql.ErrNoRows) { + return domain.Progress{AppCode: appcode.FromContext(ctx), CycleKey: cycleKey, UserID: userID}, false, nil + } + return progress, err == nil, err +} + +func (r *Repository) ListInviteActivityRewardClaimsByUserCycle(ctx context.Context, cycleKey string, userID int64) ([]domain.Claim, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + rows, err := r.db.QueryContext(ctx, inviteActivityRewardClaimSelectSQL()+` + WHERE app_code = ? AND cycle_key = ? AND user_id = ? + ORDER BY reward_type ASC, created_at_ms ASC, claim_id ASC`, + appcode.FromContext(ctx), cycleKey, userID, + ) + if err != nil { + return nil, err + } + defer rows.Close() + claims, _, err := scanInviteActivityRewardClaims(rows, 0) + return claims, err +} + +func (r *Repository) ConsumeInviteActivityRechargeEvent(ctx context.Context, event domain.RechargeEvent, cycle domain.Cycle, nowMS int64) (domain.ConsumeResult, error) { + if r == nil || r.db == nil { + return domain.ConsumeResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return domain.ConsumeResult{}, err + } + defer func() { _ = tx.Rollback() }() + + inserted, err := insertInviteActivityRewardEvent(ctx, tx, inviteActivityRewardEventRow{ + AppCode: appcode.FromContext(ctx), + EventID: event.EventID, + EventType: event.EventType, + CycleKey: cycle.Key, + InvitedUserID: event.InvitedUserID, + InviterUserID: event.InviterUserID, + TransactionID: event.TransactionID, + CommandID: event.CommandID, + RechargeType: event.RechargeType, + RechargeCoinAmount: event.RechargeCoinAmount, + Status: "consumed", + PayloadJSON: event.PayloadJSON, + OccurredAtMS: event.OccurredAtMS, + CreatedAtMS: nowMS, + }) + if err != nil { + return domain.ConsumeResult{}, err + } + if !inserted { + return domain.ConsumeResult{Consumed: true, Reason: domain.ReasonAlreadyConsumed}, tx.Commit() + } + if err := upsertInviteActivityRechargeProgress(ctx, tx, event.InviterUserID, cycle.Key, event.RechargeCoinAmount, nowMS); err != nil { + return domain.ConsumeResult{}, err + } + return domain.ConsumeResult{Consumed: true, Reason: domain.ReasonEligible}, tx.Commit() +} + +func (r *Repository) ConsumeInviteActivityValidInviteEvent(ctx context.Context, event domain.ValidInviteEvent, cycle domain.Cycle, nowMS int64) (domain.ConsumeResult, error) { + if r == nil || r.db == nil { + return domain.ConsumeResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return domain.ConsumeResult{}, err + } + defer func() { _ = tx.Rollback() }() + + inserted, err := insertInviteActivityRewardEvent(ctx, tx, inviteActivityRewardEventRow{ + AppCode: appcode.FromContext(ctx), + EventID: event.EventID, + EventType: event.EventType, + CycleKey: cycle.Key, + InvitedUserID: event.InvitedUserID, + InviterUserID: event.InviterUserID, + Status: "consumed", + PayloadJSON: event.PayloadJSON, + OccurredAtMS: event.OccurredAtMS, + CreatedAtMS: nowMS, + }) + if err != nil { + return domain.ConsumeResult{}, err + } + if !inserted { + return domain.ConsumeResult{Consumed: true, Reason: domain.ReasonAlreadyConsumed}, tx.Commit() + } + if err := upsertInviteActivityValidProgress(ctx, tx, event.InviterUserID, cycle.Key, nowMS); err != nil { + return domain.ConsumeResult{}, err + } + return domain.ConsumeResult{Consumed: true, Reason: domain.ReasonEligible}, tx.Commit() +} + +func (r *Repository) CreateInviteActivityRewardClaim(ctx context.Context, claim domain.Claim, nowMS int64) (domain.Claim, error) { + if r == nil || r.db == nil { + return domain.Claim{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + if claim.ClaimID == "" { + claim.ClaimID = idgen.New("IAR") + } + claim.AppCode = appcode.FromContext(ctx) + claim.Status = domain.ClaimStatusPending + claim.WalletCommandID = claim.CommandID + claim.CreatedAtMS = nowMS + claim.UpdatedAtMS = nowMS + _, err := r.db.ExecContext(ctx, ` + INSERT INTO invite_activity_reward_claims ( + app_code, claim_id, cycle_key, user_id, reward_type, command_id, tier_id, tier_code, tier_name, + threshold_coin_amount, threshold_valid_invite_count, reached_value, reward_coin_amount, + status, wallet_command_id, wallet_transaction_id, failure_reason, granted_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', 0, ?, ?)`, + claim.AppCode, claim.ClaimID, claim.CycleKey, claim.UserID, claim.RewardType, claim.CommandID, claim.TierID, claim.TierCode, claim.TierName, + claim.ThresholdCoinAmount, claim.ThresholdValidInviteCount, claim.ReachedValue, claim.RewardCoinAmount, + claim.Status, claim.WalletCommandID, nowMS, nowMS, + ) + if err != nil { + if isMySQLDuplicate(err) { + existing, exists, lookupErr := r.getInviteActivityRewardClaimByUserTier(ctx, claim.CycleKey, claim.UserID, claim.RewardType, claim.TierID) + if lookupErr != nil { + return domain.Claim{}, lookupErr + } + if exists { + return existing, nil + } + } + return domain.Claim{}, err + } + return r.getInviteActivityRewardClaimByID(ctx, claim.ClaimID) +} + +func (r *Repository) MarkInviteActivityRewardClaimGranted(ctx context.Context, claimID string, walletTransactionID string, grantedAtMS int64) (domain.Claim, error) { + if r == nil || r.db == nil { + return domain.Claim{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return domain.Claim{}, err + } + defer func() { _ = tx.Rollback() }() + claim, exists, err := getInviteActivityRewardClaimByIDForUpdate(ctx, tx, appcode.FromContext(ctx), claimID) + if err != nil { + return domain.Claim{}, err + } + if !exists { + return domain.Claim{}, xerr.New(xerr.NotFound, "invite activity reward claim not found") + } + if claim.Status != domain.ClaimStatusGranted { + if _, err := tx.ExecContext(ctx, ` + UPDATE invite_activity_reward_claims + SET status = 'granted', wallet_transaction_id = ?, failure_reason = '', granted_at_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND claim_id = ?`, + walletTransactionID, grantedAtMS, grantedAtMS, appcode.FromContext(ctx), claimID, + ); err != nil { + return domain.Claim{}, err + } + if _, err := tx.ExecContext(ctx, ` + UPDATE invite_activity_reward_progress + SET claimed_reward_coin_amount = claimed_reward_coin_amount + ?, updated_at_ms = ? + WHERE app_code = ? AND cycle_key = ? AND user_id = ?`, + claim.RewardCoinAmount, grantedAtMS, appcode.FromContext(ctx), claim.CycleKey, claim.UserID, + ); err != nil { + return domain.Claim{}, err + } + } + if err := tx.Commit(); err != nil { + return domain.Claim{}, err + } + return r.getInviteActivityRewardClaimByID(ctx, claimID) +} + +func (r *Repository) MarkInviteActivityRewardClaimFailed(ctx context.Context, claimID string, failureReason string, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + _, err := r.db.ExecContext(ctx, ` + UPDATE invite_activity_reward_claims + SET status = 'failed', failure_reason = ?, updated_at_ms = ? + WHERE app_code = ? AND claim_id = ? AND status <> 'granted'`, + strings.TrimSpace(failureReason), nowMS, appcode.FromContext(ctx), claimID, + ) + return err +} + +func (r *Repository) ListInviteActivityRewardClaims(ctx context.Context, query domain.ClaimQuery) ([]domain.Claim, int64, error) { + if r == nil || r.db == nil { + return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + where := []string{"app_code = ?"} + args := []any{appcode.FromContext(ctx)} + if query.Status != "" { + where = append(where, "status = ?") + args = append(args, query.Status) + } + if query.RewardType != "" { + where = append(where, "reward_type = ?") + args = append(args, query.RewardType) + } + if query.UserID > 0 { + where = append(where, "user_id = ?") + args = append(args, query.UserID) + } + if query.CycleKey != "" { + where = append(where, "cycle_key = ?") + args = append(args, query.CycleKey) + } + if query.Page <= 0 { + query.Page = 1 + } + if query.PageSize <= 0 { + query.PageSize = 20 + } + if query.PageSize > 100 { + query.PageSize = 100 + } + countSQL := `SELECT COUNT(1) FROM invite_activity_reward_claims WHERE ` + strings.Join(where, " AND ") + var total int64 + if err := r.db.QueryRowContext(ctx, countSQL, args...).Scan(&total); err != nil { + return nil, 0, err + } + args = append(args, int(query.PageSize), int((query.Page-1)*query.PageSize)) + rows, err := r.db.QueryContext(ctx, inviteActivityRewardClaimSelectSQL()+` + WHERE `+strings.Join(where, " AND ")+` + ORDER BY created_at_ms DESC, claim_id DESC + LIMIT ? OFFSET ?`, args...) + if err != nil { + return nil, 0, err + } + defer rows.Close() + claims, _, err := scanInviteActivityRewardClaims(rows, 0) + return claims, total, err +} + +func (r *Repository) ListInviteActivityLeaderboard(ctx context.Context, query domain.LeaderboardQuery) ([]domain.LeaderboardEntry, int64, error) { + if r == nil || r.db == nil { + return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + var total int64 + if err := r.db.QueryRowContext(ctx, ` + SELECT COUNT(1) + FROM invite_activity_reward_progress + WHERE app_code = ? AND cycle_key = ? AND valid_invite_count > 0`, + appcode.FromContext(ctx), query.CycleKey, + ).Scan(&total); err != nil { + return nil, 0, err + } + if query.Page <= 0 { + query.Page = 1 + } + if query.PageSize <= 0 { + query.PageSize = 50 + } + if query.PageSize > 100 { + query.PageSize = 100 + } + // ROW_NUMBER 在完整月榜上先生成名次,再分页截取;这样第 2 页仍然返回真实全局 rank_no。 + rows, err := r.db.QueryContext(ctx, ` + SELECT rank_no, user_id, valid_invite_count, total_recharge_coin_amount + FROM ( + SELECT + ROW_NUMBER() OVER ( + ORDER BY valid_invite_count DESC, total_recharge_coin_amount DESC, updated_at_ms ASC, user_id ASC + ) AS rank_no, + user_id, + valid_invite_count, + total_recharge_coin_amount + FROM invite_activity_reward_progress + WHERE app_code = ? AND cycle_key = ? AND valid_invite_count > 0 + ) ranked + ORDER BY rank_no ASC + LIMIT ? OFFSET ?`, + appcode.FromContext(ctx), query.CycleKey, int(query.PageSize), int((query.Page-1)*query.PageSize), + ) + if err != nil { + return nil, 0, err + } + defer rows.Close() + entries := make([]domain.LeaderboardEntry, 0, query.PageSize) + for rows.Next() { + var entry domain.LeaderboardEntry + if err := rows.Scan(&entry.RankNo, &entry.UserID, &entry.ValidInviteCount, &entry.TotalRechargeCoinAmount); err != nil { + return nil, 0, err + } + entries = append(entries, entry) + } + return entries, total, rows.Err() +} + +type inviteActivityRewardEventRow struct { + AppCode string + EventID string + EventType string + CycleKey string + InvitedUserID int64 + InviterUserID int64 + TransactionID string + CommandID string + RechargeType string + RechargeCoinAmount int64 + Status string + SkipReason string + PayloadJSON string + OccurredAtMS int64 + CreatedAtMS int64 +} + +func insertInviteActivityRewardEvent(ctx context.Context, tx *sql.Tx, event inviteActivityRewardEventRow) (bool, error) { + result, err := tx.ExecContext(ctx, ` + INSERT IGNORE INTO invite_activity_reward_events ( + app_code, event_id, event_type, cycle_key, invited_user_id, inviter_user_id, transaction_id, command_id, + recharge_type, recharge_coin_amount, status, skip_reason, payload_json, occurred_at_ms, created_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULLIF(?, ''), ?, ?)`, + event.AppCode, event.EventID, event.EventType, event.CycleKey, event.InvitedUserID, event.InviterUserID, event.TransactionID, event.CommandID, + event.RechargeType, event.RechargeCoinAmount, event.Status, event.SkipReason, event.PayloadJSON, event.OccurredAtMS, event.CreatedAtMS, + ) + if err != nil { + return false, err + } + affected, err := result.RowsAffected() + return affected > 0, err +} + +func upsertInviteActivityRechargeProgress(ctx context.Context, tx *sql.Tx, userID int64, cycleKey string, coinAmount int64, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO invite_activity_reward_progress ( + app_code, cycle_key, user_id, total_recharge_coin_amount, valid_invite_count, claimed_reward_coin_amount, updated_at_ms + ) VALUES (?, ?, ?, ?, 0, 0, ?) + ON DUPLICATE KEY UPDATE + total_recharge_coin_amount = total_recharge_coin_amount + VALUES(total_recharge_coin_amount), + updated_at_ms = VALUES(updated_at_ms)`, + appcode.FromContext(ctx), cycleKey, userID, coinAmount, nowMS, + ) + return err +} + +func upsertInviteActivityValidProgress(ctx context.Context, tx *sql.Tx, userID int64, cycleKey string, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO invite_activity_reward_progress ( + app_code, cycle_key, user_id, total_recharge_coin_amount, valid_invite_count, claimed_reward_coin_amount, updated_at_ms + ) VALUES (?, ?, ?, 0, 1, 0, ?) + ON DUPLICATE KEY UPDATE + valid_invite_count = valid_invite_count + 1, + updated_at_ms = VALUES(updated_at_ms)`, + appcode.FromContext(ctx), cycleKey, userID, nowMS, + ) + return err +} + +func (r *Repository) getInviteActivityRewardConfig(ctx context.Context, tx *sql.Tx) (domain.Config, bool, error) { + queryer := queryerFromRepository(r, tx) + row := queryer.QueryRowContext(ctx, ` + SELECT app_code, enabled, updated_by_admin_id, created_at_ms, updated_at_ms + FROM invite_activity_reward_configs + WHERE app_code = ?`, appcode.FromContext(ctx)) + var config domain.Config + if err := row.Scan(&config.AppCode, &config.Enabled, &config.UpdatedByAdminID, &config.CreatedAtMS, &config.UpdatedAtMS); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return domain.Config{}, false, nil + } + return domain.Config{}, false, err + } + return config, true, nil +} + +func (r *Repository) listInviteActivityRewardTiers(ctx context.Context, tx *sql.Tx, activeOnly bool) ([]domain.Tier, error) { + queryer := queryerFromRepository(r, tx) + where := `WHERE app_code = ?` + args := []any{appcode.FromContext(ctx)} + if activeOnly { + where += ` AND status = 'active'` + } + rows, err := queryer.QueryContext(ctx, inviteActivityRewardTierSelectSQL()+` + `+where+` + ORDER BY reward_type ASC, sort_order ASC, tier_id ASC`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + tiers := []domain.Tier{} + for rows.Next() { + var tier domain.Tier + if err := rows.Scan(&tier.TierID, &tier.AppCode, &tier.RewardType, &tier.TierCode, &tier.TierName, &tier.ThresholdCoinAmount, &tier.ThresholdValidInviteCount, &tier.RewardCoinAmount, &tier.Status, &tier.SortOrder, &tier.CreatedAtMS, &tier.UpdatedAtMS); err != nil { + return nil, err + } + tiers = append(tiers, tier) + } + return tiers, rows.Err() +} + +func inviteActivityRewardTierSelectSQL() string { + return `SELECT tier_id, app_code, reward_type, tier_code, tier_name, threshold_coin_amount, threshold_valid_invite_count, reward_coin_amount, status, sort_order, created_at_ms, updated_at_ms FROM invite_activity_reward_tiers ` +} + +func inviteActivityRewardProgressSelectSQL() string { + return `SELECT app_code, cycle_key, user_id, total_recharge_coin_amount, valid_invite_count, claimed_reward_coin_amount, updated_at_ms FROM invite_activity_reward_progress ` +} + +func inviteActivityRewardClaimSelectSQL() string { + return `SELECT claim_id, app_code, cycle_key, user_id, reward_type, command_id, tier_id, tier_code, tier_name, threshold_coin_amount, threshold_valid_invite_count, reached_value, reward_coin_amount, status, wallet_command_id, wallet_transaction_id, failure_reason, granted_at_ms, created_at_ms, updated_at_ms FROM invite_activity_reward_claims ` +} + +func scanInviteActivityRewardProgress(row interface{ Scan(dest ...any) error }) (domain.Progress, error) { + var progress domain.Progress + err := row.Scan(&progress.AppCode, &progress.CycleKey, &progress.UserID, &progress.TotalRechargeCoinAmount, &progress.ValidInviteCount, &progress.ClaimedRewardCoinAmount, &progress.UpdatedAtMS) + return progress, err +} + +func scanInviteActivityRewardClaims(rows *sql.Rows, limit int) ([]domain.Claim, int64, error) { + claims := []domain.Claim{} + var count int64 + for rows.Next() { + var claim domain.Claim + if err := rows.Scan(&claim.ClaimID, &claim.AppCode, &claim.CycleKey, &claim.UserID, &claim.RewardType, &claim.CommandID, &claim.TierID, &claim.TierCode, &claim.TierName, &claim.ThresholdCoinAmount, &claim.ThresholdValidInviteCount, &claim.ReachedValue, &claim.RewardCoinAmount, &claim.Status, &claim.WalletCommandID, &claim.WalletTransactionID, &claim.FailureReason, &claim.GrantedAtMS, &claim.CreatedAtMS, &claim.UpdatedAtMS); err != nil { + return nil, 0, err + } + count++ + if limit <= 0 || len(claims) < limit { + claims = append(claims, claim) + } + } + return claims, count, rows.Err() +} + +func (r *Repository) getInviteActivityRewardClaimByID(ctx context.Context, claimID string) (domain.Claim, error) { + row := r.db.QueryRowContext(ctx, inviteActivityRewardClaimSelectSQL()+` WHERE app_code = ? AND claim_id = ?`, appcode.FromContext(ctx), claimID) + claim, err := scanInviteActivityRewardClaim(row) + if errors.Is(err, sql.ErrNoRows) { + return domain.Claim{}, xerr.New(xerr.NotFound, "invite activity reward claim not found") + } + return claim, err +} + +func (r *Repository) getInviteActivityRewardClaimByUserTier(ctx context.Context, cycleKey string, userID int64, rewardType string, tierID int64) (domain.Claim, bool, error) { + row := r.db.QueryRowContext(ctx, inviteActivityRewardClaimSelectSQL()+` + WHERE app_code = ? AND cycle_key = ? AND user_id = ? AND reward_type = ? AND tier_id = ?`, + appcode.FromContext(ctx), cycleKey, userID, rewardType, tierID, + ) + claim, err := scanInviteActivityRewardClaim(row) + if errors.Is(err, sql.ErrNoRows) { + return domain.Claim{}, false, nil + } + return claim, err == nil, err +} + +func getInviteActivityRewardClaimByIDForUpdate(ctx context.Context, tx *sql.Tx, appCode string, claimID string) (domain.Claim, bool, error) { + row := tx.QueryRowContext(ctx, inviteActivityRewardClaimSelectSQL()+` WHERE app_code = ? AND claim_id = ? FOR UPDATE`, appCode, claimID) + claim, err := scanInviteActivityRewardClaim(row) + if errors.Is(err, sql.ErrNoRows) { + return domain.Claim{}, false, nil + } + return claim, err == nil, err +} + +func scanInviteActivityRewardClaim(row interface{ Scan(dest ...any) error }) (domain.Claim, error) { + var claim domain.Claim + err := row.Scan(&claim.ClaimID, &claim.AppCode, &claim.CycleKey, &claim.UserID, &claim.RewardType, &claim.CommandID, &claim.TierID, &claim.TierCode, &claim.TierName, &claim.ThresholdCoinAmount, &claim.ThresholdValidInviteCount, &claim.ReachedValue, &claim.RewardCoinAmount, &claim.Status, &claim.WalletCommandID, &claim.WalletTransactionID, &claim.FailureReason, &claim.GrantedAtMS, &claim.CreatedAtMS, &claim.UpdatedAtMS) + return claim, err +} + +func queryerFromRepository(r *Repository, tx *sql.Tx) interface { + QueryRowContext(context.Context, string, ...any) *sql.Row + QueryContext(context.Context, string, ...any) (*sql.Rows, error) +} { + if tx != nil { + return tx + } + return r.db +} + +func inviteActivityWalletCommandID(commandID string) string { + return strings.TrimSpace(commandID) +} + +func inviteActivityClaimID(userID int64, cycleKey string, rewardType string, tierID int64) string { + return fmt.Sprintf("IAR_%d_%s_%s_%d", userID, cycleKey, rewardType, tierID) +} diff --git a/services/activity-service/internal/storage/mysql/repository.go b/services/activity-service/internal/storage/mysql/repository.go index ae076090..4c83da65 100644 --- a/services/activity-service/internal/storage/mysql/repository.go +++ b/services/activity-service/internal/storage/mysql/repository.go @@ -81,6 +81,9 @@ func (r *Repository) Migrate(ctx context.Context) error { if err := r.ensureCumulativeRechargeRewardTables(ctx); err != nil { return err } + if err := r.ensureInviteActivityRewardTables(ctx); err != nil { + return err + } if err := r.ensureTaskDefinitionMetadataColumns(ctx); err != nil { return err } diff --git a/services/activity-service/internal/transport/grpc/invite_activity_reward_server.go b/services/activity-service/internal/transport/grpc/invite_activity_reward_server.go new file mode 100644 index 00000000..eccf5fca --- /dev/null +++ b/services/activity-service/internal/transport/grpc/invite_activity_reward_server.go @@ -0,0 +1,223 @@ +package grpc + +import ( + "context" + + activityv1 "hyapp.local/api/proto/activity/v1" + "hyapp/pkg/appcode" + "hyapp/pkg/xerr" + domain "hyapp/services/activity-service/internal/domain/inviteactivity" + service "hyapp/services/activity-service/internal/service/inviteactivity" +) + +type InviteActivityRewardServer struct { + activityv1.UnimplementedInviteActivityRewardServiceServer + + svc *service.Service +} + +func NewInviteActivityRewardServer(svc *service.Service) *InviteActivityRewardServer { + return &InviteActivityRewardServer{svc: svc} +} + +func (s *InviteActivityRewardServer) GetInviteActivityRewardStatus(ctx context.Context, req *activityv1.GetInviteActivityRewardStatusRequest) (*activityv1.GetInviteActivityRewardStatusResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + status, err := s.svc.GetStatus(ctx, req.GetUserId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &activityv1.GetInviteActivityRewardStatusResponse{Status: inviteActivityRewardStatusToProto(status)}, nil +} + +func (s *InviteActivityRewardServer) ClaimInviteActivityReward(ctx context.Context, req *activityv1.ClaimInviteActivityRewardRequest) (*activityv1.ClaimInviteActivityRewardResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + claim, status, err := s.svc.Claim(ctx, req.GetUserId(), req.GetRewardType(), req.GetTierId(), req.GetCommandId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &activityv1.ClaimInviteActivityRewardResponse{Claim: inviteActivityRewardClaimToProto(claim), Status: inviteActivityRewardStatusToProto(status)}, nil +} + +func (s *InviteActivityRewardServer) ListInviteActivityLeaderboard(ctx context.Context, req *activityv1.ListInviteActivityLeaderboardRequest) (*activityv1.ListInviteActivityLeaderboardResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + result, err := s.svc.ListLeaderboard(ctx, domain.LeaderboardQuery{ + CycleKey: req.GetCycleKey(), + Page: req.GetPage(), + PageSize: req.GetPageSize(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &activityv1.ListInviteActivityLeaderboardResponse{ + Entries: make([]*activityv1.InviteActivityLeaderboardEntry, 0, len(result.Entries)), + Total: result.Total, + CycleKey: result.CycleKey, + ServerTimeMs: result.ServerTimeMS, + } + for _, entry := range result.Entries { + resp.Entries = append(resp.Entries, inviteActivityLeaderboardEntryToProto(entry)) + } + return resp, nil +} + +type AdminInviteActivityRewardServer struct { + activityv1.UnimplementedAdminInviteActivityRewardServiceServer + + svc *service.Service +} + +func NewAdminInviteActivityRewardServer(svc *service.Service) *AdminInviteActivityRewardServer { + return &AdminInviteActivityRewardServer{svc: svc} +} + +func (s *AdminInviteActivityRewardServer) GetInviteActivityRewardConfig(ctx context.Context, req *activityv1.GetInviteActivityRewardConfigRequest) (*activityv1.GetInviteActivityRewardConfigResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + config, err := s.svc.GetConfig(ctx) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &activityv1.GetInviteActivityRewardConfigResponse{Config: inviteActivityRewardConfigToProto(config)}, nil +} + +func (s *AdminInviteActivityRewardServer) UpdateInviteActivityRewardConfig(ctx context.Context, req *activityv1.UpdateInviteActivityRewardConfigRequest) (*activityv1.UpdateInviteActivityRewardConfigResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + tiers := make([]domain.Tier, 0, len(req.GetTiers())) + for _, tier := range req.GetTiers() { + tiers = append(tiers, domain.Tier{ + TierID: tier.GetTierId(), + RewardType: tier.GetRewardType(), + TierCode: tier.GetTierCode(), + TierName: tier.GetTierName(), + ThresholdCoinAmount: tier.GetThresholdCoinAmount(), + ThresholdValidInviteCount: tier.GetThresholdValidInviteCount(), + RewardCoinAmount: tier.GetRewardCoinAmount(), + Status: tier.GetStatus(), + SortOrder: tier.GetSortOrder(), + }) + } + config, err := s.svc.UpdateConfig(ctx, domain.Config{ + Enabled: req.GetEnabled(), + Tiers: tiers, + UpdatedByAdminID: req.GetOperatorAdminId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &activityv1.UpdateInviteActivityRewardConfigResponse{Config: inviteActivityRewardConfigToProto(config)}, nil +} + +func (s *AdminInviteActivityRewardServer) ListInviteActivityRewardClaims(ctx context.Context, req *activityv1.ListInviteActivityRewardClaimsRequest) (*activityv1.ListInviteActivityRewardClaimsResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + claims, total, err := s.svc.ListClaims(ctx, domain.ClaimQuery{ + Status: req.GetStatus(), + RewardType: req.GetRewardType(), + UserID: req.GetUserId(), + CycleKey: req.GetCycleKey(), + Page: req.GetPage(), + PageSize: req.GetPageSize(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &activityv1.ListInviteActivityRewardClaimsResponse{Claims: make([]*activityv1.InviteActivityRewardClaim, 0, len(claims)), Total: total} + for _, claim := range claims { + resp.Claims = append(resp.Claims, inviteActivityRewardClaimToProto(claim)) + } + return resp, nil +} + +func inviteActivityRewardStatusToProto(status domain.StatusResult) *activityv1.InviteActivityRewardStatus { + resp := &activityv1.InviteActivityRewardStatus{ + Config: inviteActivityRewardConfigToProto(status.Config), + Progress: inviteActivityRewardProgressToProto(status.Progress), + Claims: make([]*activityv1.InviteActivityRewardClaim, 0, len(status.Claims)), + CycleKey: status.Cycle.Key, + PeriodStartMs: status.Cycle.StartMS, + PeriodEndMs: status.Cycle.EndMS, + ServerTimeMs: status.ServerTimeMS, + } + for _, claim := range status.Claims { + resp.Claims = append(resp.Claims, inviteActivityRewardClaimToProto(claim)) + } + return resp +} + +func inviteActivityRewardConfigToProto(config domain.Config) *activityv1.InviteActivityRewardConfig { + resp := &activityv1.InviteActivityRewardConfig{ + AppCode: config.AppCode, + Enabled: config.Enabled, + Tiers: make([]*activityv1.InviteActivityRewardTier, 0, len(config.Tiers)), + UpdatedByAdminId: config.UpdatedByAdminID, + CreatedAtMs: config.CreatedAtMS, + UpdatedAtMs: config.UpdatedAtMS, + } + for _, tier := range config.Tiers { + resp.Tiers = append(resp.Tiers, inviteActivityRewardTierToProto(tier)) + } + return resp +} + +func inviteActivityRewardTierToProto(tier domain.Tier) *activityv1.InviteActivityRewardTier { + return &activityv1.InviteActivityRewardTier{ + TierId: tier.TierID, + RewardType: tier.RewardType, + TierCode: tier.TierCode, + TierName: tier.TierName, + ThresholdCoinAmount: tier.ThresholdCoinAmount, + ThresholdValidInviteCount: tier.ThresholdValidInviteCount, + RewardCoinAmount: tier.RewardCoinAmount, + Status: tier.Status, + SortOrder: tier.SortOrder, + CreatedAtMs: tier.CreatedAtMS, + UpdatedAtMs: tier.UpdatedAtMS, + } +} + +func inviteActivityRewardProgressToProto(progress domain.Progress) *activityv1.InviteActivityRewardProgress { + return &activityv1.InviteActivityRewardProgress{ + AppCode: progress.AppCode, + CycleKey: progress.CycleKey, + UserId: progress.UserID, + TotalRechargeCoinAmount: progress.TotalRechargeCoinAmount, + ValidInviteCount: progress.ValidInviteCount, + ClaimedRewardCoinAmount: progress.ClaimedRewardCoinAmount, + UpdatedAtMs: progress.UpdatedAtMS, + } +} + +func inviteActivityRewardClaimToProto(claim domain.Claim) *activityv1.InviteActivityRewardClaim { + if claim.ClaimID == "" { + return nil + } + return &activityv1.InviteActivityRewardClaim{ + ClaimId: claim.ClaimID, + AppCode: claim.AppCode, + CycleKey: claim.CycleKey, + UserId: claim.UserID, + RewardType: claim.RewardType, + CommandId: claim.CommandID, + TierId: claim.TierID, + TierCode: claim.TierCode, + TierName: claim.TierName, + ThresholdCoinAmount: claim.ThresholdCoinAmount, + ThresholdValidInviteCount: claim.ThresholdValidInviteCount, + ReachedValue: claim.ReachedValue, + RewardCoinAmount: claim.RewardCoinAmount, + Status: claim.Status, + WalletCommandId: claim.WalletCommandID, + WalletTransactionId: claim.WalletTransactionID, + FailureReason: claim.FailureReason, + GrantedAtMs: claim.GrantedAtMS, + CreatedAtMs: claim.CreatedAtMS, + UpdatedAtMs: claim.UpdatedAtMS, + } +} + +func inviteActivityLeaderboardEntryToProto(entry domain.LeaderboardEntry) *activityv1.InviteActivityLeaderboardEntry { + return &activityv1.InviteActivityLeaderboardEntry{ + RankNo: entry.RankNo, + UserId: entry.UserID, + ValidInviteCount: entry.ValidInviteCount, + TotalRechargeCoinAmount: entry.TotalRechargeCoinAmount, + } +} diff --git a/services/activity-service/tools/invite-activity-local-flow/main.go b/services/activity-service/tools/invite-activity-local-flow/main.go new file mode 100644 index 00000000..4943a6b8 --- /dev/null +++ b/services/activity-service/tools/invite-activity-local-flow/main.go @@ -0,0 +1,263 @@ +package main + +import ( + "context" + "database/sql" + "encoding/json" + "flag" + "fmt" + "log" + "time" + + _ "github.com/go-sql-driver/mysql" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + activityv1 "hyapp.local/api/proto/activity/v1" + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/appcode" + domain "hyapp/services/activity-service/internal/domain/inviteactivity" + inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity" + mysqlstorage "hyapp/services/activity-service/internal/storage/mysql" +) + +const ( + appCode = "lalu" + operatorAdminID = int64(900100100) + tierStatus = "active" +) + +type walletEntrySummary struct { + Count int64 `json:"count"` + Coins int64 `json:"coins"` +} + +func main() { + var ( + activityAddr = flag.String("activity_addr", "127.0.0.1:14006", "activity-service gRPC address") + activityDSN = flag.String("activity_dsn", "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_activity?parseTime=true&charset=utf8mb4&loc=UTC", "activity MySQL DSN") + walletDSN = flag.String("wallet_dsn", "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_wallet?parseTime=true&charset=utf8mb4&loc=UTC", "wallet MySQL DSN") + ) + flag.Parse() + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + ctx = appcode.WithContext(ctx, appCode) + + conn, err := grpc.DialContext(ctx, *activityAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + must("dial activity-service", err) + defer conn.Close() + adminClient := activityv1.NewAdminInviteActivityRewardServiceClient(conn) + rewardClient := activityv1.NewInviteActivityRewardServiceClient(conn) + + activityRepo, err := mysqlstorage.Open(ctx, *activityDSN) + must("open activity mysql", err) + defer activityRepo.Close() + must("migrate activity mysql", activityRepo.Migrate(ctx)) + walletDB, err := sql.Open("mysql", *walletDSN) + must("open wallet mysql", err) + defer walletDB.Close() + must("ping wallet mysql", walletDB.PingContext(ctx)) + + runID := fmt.Sprintf("iar_%d", time.Now().UnixMilli()) + inviterUserID := int64(880000000000) + time.Now().Unix()%10000000 + now := time.Now().UTC() + cycle := inviteactivityservice.CycleForTime(now) + + config := updateConfig(ctx, adminClient, runID) + rechargeTier := findTier(config.GetTiers(), domain.RewardTypeRecharge, runID+"_recharge_160k") + validTier := findTier(config.GetTiers(), domain.RewardTypeValidInvite, runID+"_valid_1") + + service := inviteactivityservice.New(activityRepo, nil, fakeInviteAttributionSource{inviterUserID: inviterUserID}) + consumeRecharge(ctx, service, runID, inviterUserID, 1, "google_play", 80000, now.Add(time.Minute)) + consumeRecharge(ctx, service, runID, inviterUserID, 2, "mifapay", 40000, now.Add(2*time.Minute)) + consumeRecharge(ctx, service, runID, inviterUserID, 3, "coin_seller_transfer", 40000, now.Add(3*time.Minute)) + consumeValidInvite(ctx, service, runID, inviterUserID, 1, now.Add(4*time.Minute)) + + beforeClaim := getStatus(ctx, rewardClient, inviterUserID) + assert(beforeClaim.GetProgress().GetTotalRechargeCoinAmount() == 160000, "invite recharge progress should be 160000 coins") + assert(beforeClaim.GetProgress().GetValidInviteCount() == 1, "valid invite progress should be 1") + leaderboard := listLeaderboard(ctx, rewardClient) + assert(len(leaderboard.GetEntries()) > 0, "leaderboard should include at least one entry") + leaderboardEntry := findLeaderboardEntry(leaderboard.GetEntries(), inviterUserID) + assert(leaderboardEntry != nil, "leaderboard should include inviter") + assert(leaderboardEntry.GetValidInviteCount() == 1, "leaderboard valid invite count should be 1") + + rechargeClaim := claim(ctx, rewardClient, inviterUserID, domain.RewardTypeRecharge, rechargeTier.GetTierId(), runID+"_claim_recharge") + validClaim := claim(ctx, rewardClient, inviterUserID, domain.RewardTypeValidInvite, validTier.GetTierId(), runID+"_claim_valid") + afterClaim := getStatus(ctx, rewardClient, inviterUserID) + assert(afterClaim.GetProgress().GetClaimedRewardCoinAmount() == 2800, "claimed reward progress should be 2800 coins") + + walletRecharge := queryWalletEntry(ctx, walletDB, rechargeClaim.GetWalletTransactionId()) + walletValid := queryWalletEntry(ctx, walletDB, validClaim.GetWalletTransactionId()) + assert(walletRecharge.Coins == 2500, "wallet recharge tier reward should credit 2500 coins") + assert(walletValid.Coins == 300, "wallet valid invite reward should credit 300 coins") + + out := map[string]any{ + "run_id": runID, + "inviter_user_id": inviterUserID, + "cycle": cycle.Key, + "progress_before_claim": map[string]int64{ + "total_recharge_coin_amount": beforeClaim.GetProgress().GetTotalRechargeCoinAmount(), + "valid_invite_count": beforeClaim.GetProgress().GetValidInviteCount(), + }, + "leaderboard_entry": map[string]any{ + "rank_no": leaderboardEntry.GetRankNo(), + "user_id": leaderboardEntry.GetUserId(), + "valid_invite_count": leaderboardEntry.GetValidInviteCount(), + "total_recharge_coin_amount": leaderboardEntry.GetTotalRechargeCoinAmount(), + }, + "claims": []map[string]any{ + {"reward_type": rechargeClaim.GetRewardType(), "tier_code": rechargeClaim.GetTierCode(), "reward_coin_amount": rechargeClaim.GetRewardCoinAmount(), "wallet_transaction_id": rechargeClaim.GetWalletTransactionId()}, + {"reward_type": validClaim.GetRewardType(), "tier_code": validClaim.GetTierCode(), "reward_coin_amount": validClaim.GetRewardCoinAmount(), "wallet_transaction_id": validClaim.GetWalletTransactionId()}, + }, + "wallet_entries": map[string]walletEntrySummary{ + "recharge": walletRecharge, + "valid_invite": walletValid, + }, + "progress_after_claim": map[string]int64{ + "claimed_reward_coin_amount": afterClaim.GetProgress().GetClaimedRewardCoinAmount(), + }, + } + encoded, _ := json.MarshalIndent(out, "", " ") + fmt.Println(string(encoded)) +} + +func updateConfig(ctx context.Context, client activityv1.AdminInviteActivityRewardServiceClient, runID string) *activityv1.InviteActivityRewardConfig { + resp, err := client.UpdateInviteActivityRewardConfig(ctx, &activityv1.UpdateInviteActivityRewardConfigRequest{ + Meta: meta(runID + "_update_config"), + Enabled: true, + OperatorAdminId: operatorAdminID, + Tiers: []*activityv1.InviteActivityRewardTier{ + {RewardType: domain.RewardTypeRecharge, TierCode: runID + "_recharge_80k", TierName: "Local recharge 80k", ThresholdCoinAmount: 80000, RewardCoinAmount: 1000, Status: tierStatus, SortOrder: 1}, + {RewardType: domain.RewardTypeRecharge, TierCode: runID + "_recharge_160k", TierName: "Local recharge 160k", ThresholdCoinAmount: 160000, RewardCoinAmount: 2500, Status: tierStatus, SortOrder: 2}, + {RewardType: domain.RewardTypeValidInvite, TierCode: runID + "_valid_1", TierName: "Local valid invite 1", ThresholdValidInviteCount: 1, RewardCoinAmount: 300, Status: tierStatus, SortOrder: 1}, + {RewardType: domain.RewardTypeValidInvite, TierCode: runID + "_valid_2", TierName: "Local valid invite 2", ThresholdValidInviteCount: 2, RewardCoinAmount: 900, Status: tierStatus, SortOrder: 2}, + }, + }) + must("update invite activity config", err) + return resp.GetConfig() +} + +func consumeRecharge(ctx context.Context, service *inviteactivityservice.Service, runID string, inviterUserID int64, index int64, rechargeType string, coins int64, occurredAt time.Time) { + err := service.ConsumeWalletRechargeEvent(ctx, domain.RechargeEvent{ + AppCode: appCode, + EventID: fmt.Sprintf("%s_recharge_%d", runID, index), + EventType: domain.EventTypeWalletRechargeRecorded, + TransactionID: fmt.Sprintf("%s_tx_%d", runID, index), + CommandID: fmt.Sprintf("%s_cmd_%d", runID, index), + InvitedUserID: inviterUserID + 100 + index, + InviterUserID: inviterUserID, + RechargeCoinAmount: coins, + RechargeType: rechargeType, + OccurredAtMS: occurredAt.UnixMilli(), + }) + must("consume invite recharge "+rechargeType, err) +} + +func consumeValidInvite(ctx context.Context, service *inviteactivityservice.Service, runID string, inviterUserID int64, index int64, occurredAt time.Time) { + err := service.ConsumeValidInviteEvent(ctx, domain.ValidInviteEvent{ + AppCode: appCode, + EventID: fmt.Sprintf("%s_valid_%d", runID, index), + EventType: domain.EventTypeUserInviteBecameValid, + InvitedUserID: inviterUserID + 200 + index, + InviterUserID: inviterUserID, + OccurredAtMS: occurredAt.UnixMilli(), + }) + must("consume valid invite", err) +} + +func getStatus(ctx context.Context, client activityv1.InviteActivityRewardServiceClient, userID int64) *activityv1.InviteActivityRewardStatus { + resp, err := client.GetInviteActivityRewardStatus(ctx, &activityv1.GetInviteActivityRewardStatusRequest{ + Meta: meta("get_status"), + UserId: userID, + }) + must("get invite activity status", err) + return resp.GetStatus() +} + +func listLeaderboard(ctx context.Context, client activityv1.InviteActivityRewardServiceClient) *activityv1.ListInviteActivityLeaderboardResponse { + resp, err := client.ListInviteActivityLeaderboard(ctx, &activityv1.ListInviteActivityLeaderboardRequest{ + Meta: meta("list_leaderboard"), + Page: 1, + PageSize: 50, + }) + must("list invite activity leaderboard", err) + return resp +} + +func claim(ctx context.Context, client activityv1.InviteActivityRewardServiceClient, userID int64, rewardType string, tierID int64, commandID string) *activityv1.InviteActivityRewardClaim { + resp, err := client.ClaimInviteActivityReward(ctx, &activityv1.ClaimInviteActivityRewardRequest{ + Meta: meta(commandID), + UserId: userID, + RewardType: rewardType, + TierId: tierID, + CommandId: commandID, + }) + must("claim invite activity reward "+rewardType, err) + return resp.GetClaim() +} + +func queryWalletEntry(ctx context.Context, db *sql.DB, transactionID string) walletEntrySummary { + var summary walletEntrySummary + err := db.QueryRowContext(ctx, ` + SELECT COUNT(*), COALESCE(SUM(available_delta), 0) + FROM wallet_entries + WHERE app_code = ? AND transaction_id = ? AND asset_type = 'COIN'`, + appCode, transactionID, + ).Scan(&summary.Count, &summary.Coins) + must("query wallet entry "+transactionID, err) + return summary +} + +func findTier(tiers []*activityv1.InviteActivityRewardTier, rewardType string, tierCode string) *activityv1.InviteActivityRewardTier { + for _, tier := range tiers { + if tier.GetRewardType() == rewardType && tier.GetTierCode() == tierCode { + return tier + } + } + log.Fatalf("tier not found: %s %s", rewardType, tierCode) + return nil +} + +func findLeaderboardEntry(entries []*activityv1.InviteActivityLeaderboardEntry, userID int64) *activityv1.InviteActivityLeaderboardEntry { + for _, entry := range entries { + if entry.GetUserId() == userID { + return entry + } + } + return nil +} + +func meta(requestID string) *activityv1.RequestMeta { + return &activityv1.RequestMeta{ + RequestId: requestID, + Caller: "invite-activity-local-flow", + AppCode: appCode, + SentAtMs: time.Now().UTC().UnixMilli(), + } +} + +func assert(ok bool, message string) { + if !ok { + log.Fatal(message) + } +} + +func must(step string, err error) { + if err != nil { + log.Fatalf("%s: %v", step, err) + } +} + +type fakeInviteAttributionSource struct { + inviterUserID int64 +} + +func (s fakeInviteAttributionSource) GetInviteAttribution(_ context.Context, invitedUserID int64) (*userv1.InviteAttribution, error) { + return &userv1.InviteAttribution{ + Found: true, + AppCode: appCode, + InvitedUserId: invitedUserID, + InviterUserId: s.inviterUserID, + }, nil +} diff --git a/services/game-service/deploy/mysql/initdata/001_self_games.sql b/services/game-service/deploy/mysql/initdata/001_self_games.sql new file mode 100644 index 00000000..c3297c4e --- /dev/null +++ b/services/game-service/deploy/mysql/initdata/001_self_games.sql @@ -0,0 +1,48 @@ +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE DATABASE IF NOT EXISTS hyapp_game DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE hyapp_game; + +-- 自研游戏数据单独维护;schema 初始化只建表,默认数据在这里保证 dice 和 rock 在新环境中同时可用。 +SET @now_ms := CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED); +SET @self_game_stakes := CAST('[{"StakeCoin":100,"Enabled":true,"SortOrder":10},{"StakeCoin":500,"Enabled":true,"SortOrder":20},{"StakeCoin":1000,"Enabled":true,"SortOrder":30},{"StakeCoin":8000,"Enabled":true,"SortOrder":40}]' AS JSON); + +INSERT INTO game_platforms ( + app_code, platform_code, platform_name, status, api_base_url, adapter_type, + callback_secret_ciphertext, callback_ip_whitelist, adapter_config, + sort_order, created_at_ms, updated_at_ms +) VALUES ( + 'lalu', 'dice', 'Dice Internal', 'active', '', 'dice_internal', + '', CAST('[]' AS JSON), CAST('{}' AS JSON), + 0, @now_ms, @now_ms +) ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; + +INSERT INTO game_catalog ( + app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url, + launch_mode, orientation, safe_height, min_coin, status, sort_order, tags, created_at_ms, updated_at_ms +) VALUES + ('lalu', 'dice', 'dice', 'dice', 'Dice', 'self', '', '', 'h5_popup', 'portrait', 0, 0, 'active', 0, CAST('["self_game"]' AS JSON), @now_ms, @now_ms), + ('lalu', 'rock', 'dice', 'rock', 'Rock Paper Scissors', 'self', '', '', 'h5_popup', 'portrait', 0, 0, 'active', 1, CAST('["self_game"]' AS JSON), @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; + +INSERT INTO game_display_rules ( + app_code, rule_id, game_id, scene, region_id, language, platform, visible, enabled, sort_order, created_at_ms, updated_at_ms +) VALUES + ('lalu', 'dice_voice_default', 'dice', 'voice_room', 0, '', '', 1, 1, 0, @now_ms, @now_ms), + ('lalu', 'rock_voice_default', 'rock', 'voice_room', 0, '', '', 1, 1, 1, @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; + +INSERT INTO game_self_game_configs ( + app_code, game_id, status, stake_options_json, fee_bps, pool_bps, + min_players, max_players, robot_enabled, robot_match_wait_ms, created_at_ms, updated_at_ms +) VALUES + ('lalu', 'dice', 'active', @self_game_stakes, 500, 100, 2, 2, 1, 1000, @now_ms, @now_ms), + ('lalu', 'rock', 'active', @self_game_stakes, 500, 100, 2, 2, 1, 1000, @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; + +INSERT INTO game_self_game_pools (app_code, game_id, balance_coin, created_at_ms, updated_at_ms) +VALUES + ('lalu', 'dice', 0, @now_ms, @now_ms), + ('lalu', 'rock', 0, @now_ms, @now_ms) +ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; diff --git a/services/game-service/deploy/mysql/initdb/001_game_service.sql b/services/game-service/deploy/mysql/initdb/001_game_service.sql index 1522744c..56b2f514 100644 --- a/services/game-service/deploy/mysql/initdb/001_game_service.sql +++ b/services/game-service/deploy/mysql/initdb/001_game_service.sql @@ -285,21 +285,6 @@ CREATE TABLE IF NOT EXISTS game_self_game_pools ( PRIMARY KEY(app_code, game_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏机器人奖池表'; --- 默认骰子配置只在新环境插入一次;后台后续修改必须保留,不用初始化脚本覆盖线上运营参数。 -SET @now_ms := CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED); -INSERT INTO game_self_game_configs ( - app_code, game_id, status, stake_options_json, fee_bps, pool_bps, - min_players, max_players, robot_enabled, robot_match_wait_ms, created_at_ms, updated_at_ms -) VALUES ( - 'lalu', 'dice', 'active', - CAST('[{"stake_coin":100,"enabled":true,"sort_order":10},{"stake_coin":500,"enabled":true,"sort_order":20},{"stake_coin":1000,"enabled":true,"sort_order":30},{"stake_coin":8000,"enabled":true,"sort_order":40}]' AS JSON), - 500, 100, 2, 2, 1, 3000, @now_ms, @now_ms -) ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; - -INSERT INTO game_self_game_pools (app_code, game_id, balance_coin, created_at_ms, updated_at_ms) -VALUES ('lalu', 'dice', 0, @now_ms, @now_ms) -ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms; - CREATE TABLE IF NOT EXISTS game_self_game_pool_transactions ( app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', adjustment_id VARCHAR(96) NOT NULL COMMENT '奖池流水 ID', @@ -329,6 +314,23 @@ CREATE TABLE IF NOT EXISTS game_self_game_robots ( KEY idx_game_robot_status(app_code, game_id, status, updated_at_ms, user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏机器人登记表'; +CREATE TABLE IF NOT EXISTS game_explore_winners ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + win_id VARCHAR(96) NOT NULL COMMENT 'Explore Square 播报 ID', + game_code VARCHAR(64) NOT NULL COMMENT 'App H5 入口 key,例如 game_dice/game_rock', + game_id VARCHAR(96) NOT NULL COMMENT '来源游戏 ID', + user_id BIGINT NOT NULL COMMENT '获胜用户 ID', + display_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '展示昵称快照', + avatar_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '展示头像快照', + coin_amount BIGINT NOT NULL COMMENT '获胜金币数', + won_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, win_id), + KEY idx_game_explore_winners_time(app_code, won_at_ms, win_id), + KEY idx_game_explore_winners_game(app_code, game_code, won_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Explore Square 游戏获胜播报表'; + CREATE TABLE IF NOT EXISTS game_callback_logs ( app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', callback_id VARCHAR(96) NOT NULL COMMENT '回调 ID', diff --git a/services/game-service/internal/domain/dice/dice.go b/services/game-service/internal/domain/dice/dice.go index f93095ef..606ad3fd 100644 --- a/services/game-service/internal/domain/dice/dice.go +++ b/services/game-service/internal/domain/dice/dice.go @@ -73,6 +73,10 @@ const ( RobotStatusActive = "active" RobotStatusDisabled = "disabled" + ExploreGameCodeDice = "game_dice" + ExploreGameCodeRock = "game_rock" + DefaultExploreWinnerPageSize = int32(20) + PoolDirectionIn = "in" PoolDirectionOut = "out" ) @@ -112,6 +116,21 @@ type Robot struct { UpdatedAtMS int64 } +// ExploreWinner 是 Explore Square 展示用的轻量获胜事实;首版用机器人池生成展示数据,后续真实结算只需要写同一张表。 +type ExploreWinner struct { + AppCode string + WinID string + GameCode string + GameID string + UserID int64 + DisplayName string + AvatarURL string + CoinAmount int64 + WonAtMS int64 + CreatedAtMS int64 + UpdatedAtMS int64 +} + // PoolAdjustment 是奖池余额变化事实;所有机器人对局和后台手工调整都必须留下流水。 type PoolAdjustment struct { AdjustmentID string diff --git a/services/game-service/internal/service/dice/service.go b/services/game-service/internal/service/dice/service.go index 177c6b1d..3e9a3591 100644 --- a/services/game-service/internal/service/dice/service.go +++ b/services/game-service/internal/service/dice/service.go @@ -48,6 +48,8 @@ type Repository interface { RegisterDiceRobots(ctx context.Context, appCode string, gameID string, userIDs []int64, adminID int64, nowMs int64) ([]dicedomain.Robot, error) SetDiceRobotStatus(ctx context.Context, appCode string, gameID string, userID int64, status string, nowMs int64) (dicedomain.Robot, error) DeleteDiceRobot(ctx context.Context, appCode string, gameID string, userID int64) error + EnsureExploreWinners(ctx context.Context, appCode string, nowMs int64) error + ListExploreWinners(ctx context.Context, appCode string, pageSize int32) ([]dicedomain.ExploreWinner, error) } // WalletClient 是 game-service 调 wallet-service 的游戏改账入口。 @@ -316,6 +318,28 @@ func (s *Service) ListRobots(ctx context.Context, appCode string, gameID string, return items, next, s.now().UnixMilli(), err } +// ListExploreWinners 给 Explore Square 提供获胜播报列表;当前版本先用现有机器人池兜底生成 20 条数据库事实。 +func (s *Service) ListExploreWinners(ctx context.Context, appCode string, pageSize int32) ([]dicedomain.ExploreWinner, int64, error) { + if s.repository == nil { + return nil, 0, xerr.New(xerr.Unavailable, "game repository is not configured") + } + app := appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, app) + nowMS := s.now().UnixMilli() + if pageSize <= 0 { + pageSize = dicedomain.DefaultExploreWinnerPageSize + } + if pageSize > 50 { + pageSize = 50 + } + // 读接口先做一次幂等补种:机器人列表是当前唯一稳定来源,写入后客户端每 5 秒轮询只需要覆盖展示列表。 + if err := s.repository.EnsureExploreWinners(ctx, app, nowMS); err != nil { + return nil, 0, err + } + items, err := s.repository.ListExploreWinners(ctx, app, pageSize) + return items, nowMS, err +} + func (s *Service) RegisterRobots(ctx context.Context, appCode string, gameID string, userIDs []int64, adminID int64) ([]dicedomain.Robot, int64, error) { if s.repository == nil { return nil, 0, xerr.New(xerr.Unavailable, "game repository is not configured") diff --git a/services/game-service/internal/service/dice/service_test.go b/services/game-service/internal/service/dice/service_test.go index 9932e862..a5e9a3b9 100644 --- a/services/game-service/internal/service/dice/service_test.go +++ b/services/game-service/internal/service/dice/service_test.go @@ -445,6 +445,14 @@ func (r *fakeDiceRepository) DeleteDiceRobot(context.Context, string, string, in return nil } +func (r *fakeDiceRepository) EnsureExploreWinners(context.Context, string, int64) error { + return nil +} + +func (r *fakeDiceRepository) ListExploreWinners(context.Context, string, int32) ([]dicedomain.ExploreWinner, error) { + return nil, nil +} + func (r *fakeDiceRepository) updateParticipant(userID int64, apply func(*dicedomain.Participant)) error { for index := range r.match.Participants { if r.match.Participants[index].UserID == userID { diff --git a/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go b/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go index c464f01a..70a437f9 100644 --- a/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go +++ b/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go @@ -2,9 +2,11 @@ package mysql import ( "context" + cryptorand "crypto/rand" "database/sql" "errors" "fmt" + "math/big" "strings" "hyapp/pkg/appcode" @@ -255,12 +257,33 @@ func (r *Repository) CancelDiceMatch(ctx context.Context, appCode string, matchI return r.GetDiceMatch(ctx, app, match.MatchID) } -// PickActiveDiceRobot 选择一个未在本局中的可用机器人;首版用更新时间排序,后续可替换成权重或 Redis 活跃池。 +// PickActiveDiceRobot 从当前游戏的可用机器人池里随机挑选一个未加入本局的机器人。 func (r *Repository) PickActiveDiceRobot(ctx context.Context, appCode string, gameID string, matchID string) (dicedomain.Robot, error) { app := appcode.Normalize(appCode) gameID = normalizeDiceGameID(gameID) + matchID = strings.TrimSpace(matchID) + var candidateCount int64 + if err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) + FROM game_self_game_robots r + WHERE r.app_code = ? AND r.game_id = ? AND r.status = ? + AND NOT EXISTS ( + SELECT 1 FROM game_dice_participants p + WHERE p.app_code = r.app_code AND p.match_id = ? AND p.user_id = r.user_id + )`, + app, gameID, dicedomain.RobotStatusActive, matchID, + ).Scan(&candidateCount); err != nil { + return dicedomain.Robot{}, err + } + if candidateCount <= 0 { + return dicedomain.Robot{}, xerr.New(xerr.NotFound, "dice robot not found") + } + randomOffset, err := randomDiceRobotOffset(candidateCount) + if err != nil { + return dicedomain.Robot{}, err + } var robot dicedomain.Robot - err := r.db.QueryRowContext(ctx, + err = r.db.QueryRowContext(ctx, `SELECT r.app_code, r.game_id, r.user_id, r.status, r.created_by_admin_id, r.created_at_ms, r.updated_at_ms FROM game_self_game_robots r WHERE r.app_code = ? AND r.game_id = ? AND r.status = ? @@ -269,8 +292,8 @@ func (r *Repository) PickActiveDiceRobot(ctx context.Context, appCode string, ga WHERE p.app_code = r.app_code AND p.match_id = ? AND p.user_id = r.user_id ) ORDER BY r.updated_at_ms ASC, r.user_id ASC - LIMIT 1`, - app, gameID, dicedomain.RobotStatusActive, strings.TrimSpace(matchID), + LIMIT 1 OFFSET ?`, + app, gameID, dicedomain.RobotStatusActive, matchID, randomOffset, ).Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -281,6 +304,16 @@ func (r *Repository) PickActiveDiceRobot(ctx context.Context, appCode string, ga return robot, nil } +func randomDiceRobotOffset(candidateCount int64) (int64, error) { + // 机器人选择不能再依赖固定排序,否则只要后台没有更新机器人状态,玩家就会反复匹配到同一个账号;这里先用候选总数限定范围, + // 再取随机 offset 保留“本局已加入机器人不再参与”的过滤语义,同时让每次补位都从全部可用机器人中抽样。 + offset, err := cryptorand.Int(cryptorand.Reader, big.NewInt(candidateCount)) + if err != nil { + return 0, err + } + return offset.Int64(), nil +} + // AdjustDicePool 用一把奖池行锁串行更新余额并写流水,防止多个机器人局并发时把余额扣成负数。 func (r *Repository) AdjustDicePool(ctx context.Context, adjustment dicedomain.PoolAdjustment) (dicedomain.PoolAdjustment, error) { if adjustment.AmountCoin <= 0 { @@ -407,6 +440,141 @@ func (r *Repository) ListDiceRobots(ctx context.Context, appCode string, gameID return robots, nextCursor, nil } +// EnsureExploreWinners 用机器人池补足 Explore Square 首批获胜事实;已有 20 条时不重写,避免轮询接口制造无意义写放大。 +func (r *Repository) EnsureExploreWinners(ctx context.Context, appCode string, nowMs int64) error { + app := appcode.Normalize(appCode) + var existing int + if err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) + FROM game_explore_winners + WHERE app_code = ?`, + app, + ).Scan(&existing); err != nil { + return err + } + if existing >= int(dicedomain.DefaultExploreWinnerPageSize) { + return nil + } + + rows, err := r.db.QueryContext(ctx, + `SELECT app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms + FROM game_self_game_robots + WHERE app_code = ? AND status = ? + ORDER BY updated_at_ms DESC, user_id DESC + LIMIT ?`, + app, dicedomain.RobotStatusActive, int(dicedomain.DefaultExploreWinnerPageSize), + ) + if err != nil { + return err + } + robots := []dicedomain.Robot{} + for rows.Next() { + var robot dicedomain.Robot + if err := rows.Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS); err != nil { + _ = rows.Close() + return err + } + robots = append(robots, robot) + } + if err := rows.Close(); err != nil { + return err + } + if len(robots) == 0 { + return nil + } + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + // 半成品数据直接重建,保证当前版本始终给客户端一组完整、按时间倒序的 20 条播报。 + if _, err := tx.ExecContext(ctx, `DELETE FROM game_explore_winners WHERE app_code = ?`, app); err != nil { + return err + } + for index := 0; index < int(dicedomain.DefaultExploreWinnerPageSize); index++ { + robot := robots[index%len(robots)] + gameCode := dicedomain.ExploreGameCodeDice + gameID := dicedomain.DefaultGameID + if index%2 == 1 { + gameCode = dicedomain.ExploreGameCodeRock + gameID = "room_rps" + } + if strings.TrimSpace(robot.GameID) != "" && gameCode == dicedomain.ExploreGameCodeDice { + gameID = strings.TrimSpace(robot.GameID) + } + wonAtMS := nowMs - int64(index)*5_000 + if _, err := tx.ExecContext(ctx, + `INSERT INTO game_explore_winners ( + app_code, win_id, game_code, game_id, user_id, display_name, avatar_url, + coin_amount, won_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + app, + fmt.Sprintf("explore_seed_%d_%02d", nowMs, index+1), + gameCode, + gameID, + robot.UserID, + "name", + "", + int64(306000), + wonAtMS, + nowMs, + nowMs, + ); err != nil { + return err + } + } + return tx.Commit() +} + +// ListExploreWinners 只返回当前 app 最新播报;客户端每次轮询直接用返回值替换旧列表。 +func (r *Repository) ListExploreWinners(ctx context.Context, appCode string, pageSize int32) ([]dicedomain.ExploreWinner, error) { + app := appcode.Normalize(appCode) + if pageSize <= 0 { + pageSize = dicedomain.DefaultExploreWinnerPageSize + } + if pageSize > 50 { + pageSize = 50 + } + rows, err := r.db.QueryContext(ctx, + `SELECT app_code, win_id, game_code, game_id, user_id, display_name, avatar_url, + coin_amount, won_at_ms, created_at_ms, updated_at_ms + FROM game_explore_winners + WHERE app_code = ? + ORDER BY won_at_ms DESC, win_id DESC + LIMIT ?`, + app, int(pageSize), + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := make([]dicedomain.ExploreWinner, 0, pageSize) + for rows.Next() { + var item dicedomain.ExploreWinner + if err := rows.Scan( + &item.AppCode, + &item.WinID, + &item.GameCode, + &item.GameID, + &item.UserID, + &item.DisplayName, + &item.AvatarURL, + &item.CoinAmount, + &item.WonAtMS, + &item.CreatedAtMS, + &item.UpdatedAtMS, + ); err != nil { + return nil, err + } + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + func (r *Repository) RegisterDiceRobots(ctx context.Context, appCode string, gameID string, userIDs []int64, adminID int64, nowMs int64) ([]dicedomain.Robot, error) { app := appcode.Normalize(appCode) gameID = normalizeDiceGameID(gameID) diff --git a/services/game-service/internal/storage/mysql/repository_test.go b/services/game-service/internal/storage/mysql/repository_test.go index 80f812b0..aa0f85e4 100644 --- a/services/game-service/internal/storage/mysql/repository_test.go +++ b/services/game-service/internal/storage/mysql/repository_test.go @@ -8,6 +8,7 @@ import ( "hyapp/internal/testutil/mysqlschema" "hyapp/pkg/appcode" + dicedomain "hyapp/services/game-service/internal/domain/dice" gamedomain "hyapp/services/game-service/internal/domain/game" ) @@ -166,6 +167,57 @@ func TestLevelEventClaimKeyQueryUsesOrderIndexWithoutFilesort(t *testing.T) { } } +func TestPickActiveDiceRobotSelectsRandomCandidateInsteadOfFixedFirstRow(t *testing.T) { + caller := mysqlschema.CallerFile(t, 1) + schema := mysqlschema.New(t, mysqlschema.Config{ + InitDBPath: mysqlschema.InitDBPath(t, caller, "../../../deploy/mysql/initdb/001_game_service.sql"), + DatabasePrefix: "hy_game_test", + }) + + ctx := appcode.WithContext(context.Background(), "lalu") + repo, err := Open(ctx, schema.DSN) + if err != nil { + t.Fatalf("open repository failed: %v", err) + } + t.Cleanup(func() { _ = repo.Close() }) + + nowMS := int64(1780542672000) + userIDs := []int64{101, 102, 103, 104} + for index, userID := range userIDs { + if _, err := repo.db.ExecContext(ctx, + `INSERT INTO game_self_game_robots ( + app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, 7, ?, ?)`, + "lalu", dicedomain.DefaultGameID, userID, dicedomain.RobotStatusActive, nowMS+int64(index), nowMS+int64(index), + ); err != nil { + t.Fatalf("insert robot %d failed: %v", userID, err) + } + } + if _, err := repo.db.ExecContext(ctx, + `INSERT INTO game_dice_participants ( + app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, joined_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, 2, ?, 100, ?, ?)`, + "lalu", "match_random_robot", int64(101), dicedomain.ParticipantTypeRobot, dicedomain.ParticipantStatusJoined, nowMS, nowMS, + ); err != nil { + t.Fatalf("insert existing robot participant failed: %v", err) + } + + picked := map[int64]bool{} + for attempt := 0; attempt < 32; attempt++ { + robot, err := repo.PickActiveDiceRobot(ctx, "lalu", dicedomain.DefaultGameID, "match_random_robot") + if err != nil { + t.Fatalf("pick active dice robot failed: %v", err) + } + if robot.UserID == 101 { + t.Fatalf("picked robot already in this match: %+v", robot) + } + picked[robot.UserID] = true + } + if len(picked) < 2 { + t.Fatalf("robot picking still behaves like fixed first-row selection, got picked user ids: %+v", picked) + } +} + func TestUpsertCatalogCreatesDefaultVoiceRoomDisplayRule(t *testing.T) { caller := mysqlschema.CallerFile(t, 1) schema := mysqlschema.New(t, mysqlschema.Config{ diff --git a/services/game-service/internal/transport/grpc/server.go b/services/game-service/internal/transport/grpc/server.go index d9ff14c0..f9df24ae 100644 --- a/services/game-service/internal/transport/grpc/server.go +++ b/services/game-service/internal/transport/grpc/server.go @@ -87,6 +87,22 @@ func (s *Server) ListRecentGames(ctx context.Context, req *gamev1.ListRecentGame return resp, nil } +func (s *Server) ListExploreWinners(ctx context.Context, req *gamev1.ListExploreWinnersRequest) (*gamev1.ListExploreWinnersResponse, error) { + if s == nil || s.diceSvc == nil { + return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "dice service is not configured")) + } + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + items, serverTimeMS, err := s.diceSvc.ListExploreWinners(ctx, req.GetMeta().GetAppCode(), req.GetPageSize()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &gamev1.ListExploreWinnersResponse{ServerTimeMs: serverTimeMS, Winners: make([]*gamev1.ExploreWinner, 0, len(items))} + for _, item := range items { + resp.Winners = append(resp.Winners, exploreWinnerToProto(item)) + } + return resp, nil +} + func (s *Server) GetBridgeScript(ctx context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) result, err := s.svc.GetBridgeScript(ctx, req.GetMeta().GetAppCode()) @@ -793,6 +809,19 @@ func diceRobotToProto(robot dicedomain.Robot) *gamev1.DiceRobot { } } +func exploreWinnerToProto(item dicedomain.ExploreWinner) *gamev1.ExploreWinner { + return &gamev1.ExploreWinner{ + WinId: item.WinID, + GameCode: item.GameCode, + GameId: item.GameID, + UserId: item.UserID, + DisplayName: item.DisplayName, + AvatarUrl: item.AvatarURL, + CoinAmount: item.CoinAmount, + WonAtMs: item.WonAtMS, + } +} + func dicePhase(match dicedomain.Match, nowMS int64) (string, int64) { switch match.Status { case dicedomain.MatchStatusCanceled: diff --git a/services/gateway-service/internal/app/app.go b/services/gateway-service/internal/app/app.go index 8ef2b653..e5924211 100644 --- a/services/gateway-service/internal/app/app.go +++ b/services/gateway-service/internal/app/app.go @@ -102,6 +102,7 @@ func New(cfg config.Config) (*App, error) { var registrationRewardClient client.RegistrationRewardClient = client.NewGRPCRegistrationRewardClient(activityConn) var firstRechargeRewardClient client.FirstRechargeRewardClient = client.NewGRPCFirstRechargeRewardClient(activityConn) var cumulativeRechargeRewardClient client.CumulativeRechargeRewardClient = client.NewGRPCCumulativeRechargeRewardClient(activityConn) + var inviteActivityRewardClient client.InviteActivityRewardClient = client.NewGRPCInviteActivityRewardClient(activityConn) var sevenDayCheckInClient client.SevenDayCheckInClient = client.NewGRPCSevenDayCheckInClient(activityConn) var luckyGiftClient client.LuckyGiftClient = client.NewGRPCLuckyGiftClient(activityConn) var roomTurnoverRewardClient client.RoomTurnoverRewardClient = client.NewGRPCRoomTurnoverRewardClient(activityConn) @@ -171,6 +172,7 @@ func New(cfg config.Config) (*App, error) { handler.SetRegistrationRewardClient(registrationRewardClient) handler.SetFirstRechargeRewardClient(firstRechargeRewardClient) handler.SetCumulativeRechargeRewardClient(cumulativeRechargeRewardClient) + handler.SetInviteActivityRewardClient(inviteActivityRewardClient) handler.SetSevenDayCheckInClient(sevenDayCheckInClient) handler.SetLuckyGiftClient(luckyGiftClient) handler.SetRoomTurnoverRewardClient(roomTurnoverRewardClient) diff --git a/services/gateway-service/internal/appconfig/mysql.go b/services/gateway-service/internal/appconfig/mysql.go index dbbc3172..c23d4192 100644 --- a/services/gateway-service/internal/appconfig/mysql.go +++ b/services/gateway-service/internal/appconfig/mysql.go @@ -45,6 +45,13 @@ const listSplashScreensSQL = ` AND (starts_at_ms = 0 OR starts_at_ms <= ?) AND (ends_at_ms = 0 OR ends_at_ms > ?) ORDER BY sort_order ASC, id DESC` +const listPopupsSQL = ` + SELECT id, app_code, code, name, image_url, jump_type, jump_url, display_period_days, sort_order, starts_at_ms, ends_at_ms, updated_at_ms + FROM admin_app_popups + WHERE app_code = ? AND status = 'active' + AND (starts_at_ms = 0 OR starts_at_ms <= ?) + AND (ends_at_ms = 0 OR ends_at_ms > ?) + ORDER BY sort_order ASC, id DESC` const latestAppVersionSQL = ` SELECT id, app_code, platform, version, build_number, force_update, download_url, COALESCE(description, ''), updated_at_ms FROM admin_app_versions @@ -130,6 +137,26 @@ type SplashScreen struct { UpdatedAtMs int64 `json:"updated_at_ms"` } +// PopupQuery 是 App 弹窗配置的公开筛选条件。 +type PopupQuery struct { + AppCode string +} + +// Popup 是 gateway 下发给 App 的弹窗配置;display_period_days=0 表示客户端每次打开都展示。 +type Popup struct { + ID uint `json:"id"` + Code string `json:"code"` + Name string `json:"name"` + ImageURL string `json:"image_url"` + JumpType string `json:"jump_type"` + JumpURL string `json:"jump_url"` + DisplayPeriodDays int `json:"display_period_days"` + SortOrder int `json:"sort_order"` + StartsAtMs int64 `json:"starts_at_ms"` + EndsAtMs int64 `json:"ends_at_ms"` + UpdatedAtMs int64 `json:"updated_at_ms"` +} + // VersionQuery 是 App 检查更新的公开筛选条件。 type VersionQuery struct { AppCode string @@ -156,6 +183,7 @@ type Reader interface { ListExploreTabs(ctx context.Context, appCode string) ([]ExploreTab, error) ListBanners(ctx context.Context, query BannerQuery) ([]Banner, error) ListSplashScreens(ctx context.Context, query SplashScreenQuery) ([]SplashScreen, error) + ListPopups(ctx context.Context, query PopupQuery) ([]Popup, error) LatestVersion(ctx context.Context, query VersionQuery) (Version, error) } @@ -367,6 +395,50 @@ func (r *MySQLReader) ListSplashScreens(ctx context.Context, query SplashScreenQ return items, nil } +// ListPopups 返回当前 App 生效窗口内的弹窗配置;展示频控由客户端按 display_period_days 本地执行。 +func (r *MySQLReader) ListPopups(ctx context.Context, query PopupQuery) ([]Popup, error) { + if r == nil || r.db == nil { + return nil, errors.New("app config reader is not configured") + } + + appCode := normalizeAppCode(query.AppCode) + nowMs := time.Now().UTC().UnixMilli() + rows, err := r.db.QueryContext(ctx, listPopupsSQL, appCode, nowMs, nowMs) + if err != nil { + return nil, err + } + defer rows.Close() + + items := make([]Popup, 0) + for rows.Next() { + var item Popup + var appCode string + var updatedAtMS int64 + if err := rows.Scan( + &item.ID, + &appCode, + &item.Code, + &item.Name, + &item.ImageURL, + &item.JumpType, + &item.JumpURL, + &item.DisplayPeriodDays, + &item.SortOrder, + &item.StartsAtMs, + &item.EndsAtMs, + &updatedAtMS, + ); err != nil { + return nil, err + } + item.UpdatedAtMs = updatedAtMS + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + // LatestVersion 返回指定 App 和平台的最高 build_number 版本;未配置时返回空版本,方便 App 端按无更新处理。 func (r *MySQLReader) LatestVersion(ctx context.Context, query VersionQuery) (Version, error) { if r == nil || r.db == nil { @@ -400,6 +472,7 @@ type StaticReader struct { ExploreTabs []ExploreTab Banners []Banner SplashScreens []SplashScreen + Popups []Popup Version Version Err error PositionAliases map[string]string @@ -457,6 +530,17 @@ func (r StaticReader) ListSplashScreens(context.Context, SplashScreenQuery) ([]S return out, nil } +// ListPopups 返回预置弹窗配置,供 HTTP 单测和无 MySQL 场景复用同一 Reader 接口。 +func (r StaticReader) ListPopups(context.Context, PopupQuery) ([]Popup, error) { + if r.Err != nil { + return nil, r.Err + } + + out := make([]Popup, len(r.Popups)) + copy(out, r.Popups) + return out, nil +} + // LatestVersion 返回预置版本配置。 func (r StaticReader) LatestVersion(_ context.Context, query VersionQuery) (Version, error) { if r.Err != nil { diff --git a/services/gateway-service/internal/client/game_client.go b/services/gateway-service/internal/client/game_client.go index a4bb6856..c408fc0c 100644 --- a/services/gateway-service/internal/client/game_client.go +++ b/services/gateway-service/internal/client/game_client.go @@ -11,6 +11,7 @@ import ( type GameClient interface { ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) ListRecentGames(ctx context.Context, req *gamev1.ListRecentGamesRequest) (*gamev1.ListGamesResponse, error) + ListExploreWinners(ctx context.Context, req *gamev1.ListExploreWinnersRequest) (*gamev1.ListExploreWinnersResponse, error) GetBridgeScript(ctx context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) GetDiceConfig(ctx context.Context, req *gamev1.GetDiceConfigRequest) (*gamev1.DiceConfigResponse, error) @@ -48,6 +49,11 @@ func (c *grpcGameClient) ListRecentGames(ctx context.Context, req *gamev1.ListRe return c.app.ListRecentGames(ctx, req) } +func (c *grpcGameClient) ListExploreWinners(ctx context.Context, req *gamev1.ListExploreWinnersRequest) (*gamev1.ListExploreWinnersResponse, error) { + // Explore Square 是 app 首页播报数据,gateway 不做本地缓存,直接读取 game-service 的获胜事实。 + return c.app.ListExploreWinners(ctx, req) +} + func (c *grpcGameClient) GetBridgeScript(ctx context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) { return c.app.GetBridgeScript(ctx, req) } diff --git a/services/gateway-service/internal/client/invite_activity_reward_client.go b/services/gateway-service/internal/client/invite_activity_reward_client.go new file mode 100644 index 00000000..45360a52 --- /dev/null +++ b/services/gateway-service/internal/client/invite_activity_reward_client.go @@ -0,0 +1,35 @@ +package client + +import ( + "context" + + "google.golang.org/grpc" + activityv1 "hyapp.local/api/proto/activity/v1" +) + +// InviteActivityRewardClient abstracts gateway reads and claims from activity-service invite reward APIs. +type InviteActivityRewardClient interface { + GetInviteActivityRewardStatus(ctx context.Context, req *activityv1.GetInviteActivityRewardStatusRequest) (*activityv1.GetInviteActivityRewardStatusResponse, error) + ClaimInviteActivityReward(ctx context.Context, req *activityv1.ClaimInviteActivityRewardRequest) (*activityv1.ClaimInviteActivityRewardResponse, error) + ListInviteActivityLeaderboard(ctx context.Context, req *activityv1.ListInviteActivityLeaderboardRequest) (*activityv1.ListInviteActivityLeaderboardResponse, error) +} + +type grpcInviteActivityRewardClient struct { + client activityv1.InviteActivityRewardServiceClient +} + +func NewGRPCInviteActivityRewardClient(conn *grpc.ClientConn) InviteActivityRewardClient { + return &grpcInviteActivityRewardClient{client: activityv1.NewInviteActivityRewardServiceClient(conn)} +} + +func (c *grpcInviteActivityRewardClient) GetInviteActivityRewardStatus(ctx context.Context, req *activityv1.GetInviteActivityRewardStatusRequest) (*activityv1.GetInviteActivityRewardStatusResponse, error) { + return c.client.GetInviteActivityRewardStatus(ctx, req) +} + +func (c *grpcInviteActivityRewardClient) ClaimInviteActivityReward(ctx context.Context, req *activityv1.ClaimInviteActivityRewardRequest) (*activityv1.ClaimInviteActivityRewardResponse, error) { + return c.client.ClaimInviteActivityReward(ctx, req) +} + +func (c *grpcInviteActivityRewardClient) ListInviteActivityLeaderboard(ctx context.Context, req *activityv1.ListInviteActivityLeaderboardRequest) (*activityv1.ListInviteActivityLeaderboardResponse, error) { + return c.client.ListInviteActivityLeaderboard(ctx, req) +} diff --git a/services/gateway-service/internal/transport/http/activityapi/handler.go b/services/gateway-service/internal/transport/http/activityapi/handler.go index 20444f76..9cc84f00 100644 --- a/services/gateway-service/internal/transport/http/activityapi/handler.go +++ b/services/gateway-service/internal/transport/http/activityapi/handler.go @@ -10,55 +10,58 @@ import ( // Handler owns activity-facing HTTP endpoints. // It keeps task and registration reward reads/writes behind activity-service, with gateway only adding auth context. type Handler struct { - taskClient client.TaskClient - growthLevelClient client.GrowthLevelClient - achievementClient client.AchievementClient - userProfileClient client.UserProfileClient - roomQueryClient client.RoomQueryClient - walletClient client.WalletClient - walletDB *sql.DB - registrationReward client.RegistrationRewardClient - firstRechargeReward client.FirstRechargeRewardClient - cumulativeRecharge client.CumulativeRechargeRewardClient - sevenDayCheckIn client.SevenDayCheckInClient - luckyGift client.LuckyGiftClient - roomTurnoverReward client.RoomTurnoverRewardClient - weeklyStar client.WeeklyStarClient + taskClient client.TaskClient + growthLevelClient client.GrowthLevelClient + achievementClient client.AchievementClient + userProfileClient client.UserProfileClient + roomQueryClient client.RoomQueryClient + walletClient client.WalletClient + walletDB *sql.DB + registrationReward client.RegistrationRewardClient + firstRechargeReward client.FirstRechargeRewardClient + cumulativeRecharge client.CumulativeRechargeRewardClient + inviteActivityReward client.InviteActivityRewardClient + sevenDayCheckIn client.SevenDayCheckInClient + luckyGift client.LuckyGiftClient + roomTurnoverReward client.RoomTurnoverRewardClient + weeklyStar client.WeeklyStarClient } type Config struct { - TaskClient client.TaskClient - GrowthLevelClient client.GrowthLevelClient - AchievementClient client.AchievementClient - UserProfileClient client.UserProfileClient - RoomQueryClient client.RoomQueryClient - WalletClient client.WalletClient - WalletDB *sql.DB - RegistrationReward client.RegistrationRewardClient - FirstRechargeReward client.FirstRechargeRewardClient - CumulativeRecharge client.CumulativeRechargeRewardClient - SevenDayCheckIn client.SevenDayCheckInClient - LuckyGift client.LuckyGiftClient - RoomTurnoverReward client.RoomTurnoverRewardClient - WeeklyStar client.WeeklyStarClient + TaskClient client.TaskClient + GrowthLevelClient client.GrowthLevelClient + AchievementClient client.AchievementClient + UserProfileClient client.UserProfileClient + RoomQueryClient client.RoomQueryClient + WalletClient client.WalletClient + WalletDB *sql.DB + RegistrationReward client.RegistrationRewardClient + FirstRechargeReward client.FirstRechargeRewardClient + CumulativeRecharge client.CumulativeRechargeRewardClient + InviteActivityReward client.InviteActivityRewardClient + SevenDayCheckIn client.SevenDayCheckInClient + LuckyGift client.LuckyGiftClient + RoomTurnoverReward client.RoomTurnoverRewardClient + WeeklyStar client.WeeklyStarClient } func New(config Config) *Handler { return &Handler{ - taskClient: config.TaskClient, - growthLevelClient: config.GrowthLevelClient, - achievementClient: config.AchievementClient, - userProfileClient: config.UserProfileClient, - roomQueryClient: config.RoomQueryClient, - walletClient: config.WalletClient, - walletDB: config.WalletDB, - registrationReward: config.RegistrationReward, - firstRechargeReward: config.FirstRechargeReward, - cumulativeRecharge: config.CumulativeRecharge, - sevenDayCheckIn: config.SevenDayCheckIn, - luckyGift: config.LuckyGift, - roomTurnoverReward: config.RoomTurnoverReward, - weeklyStar: config.WeeklyStar, + taskClient: config.TaskClient, + growthLevelClient: config.GrowthLevelClient, + achievementClient: config.AchievementClient, + userProfileClient: config.UserProfileClient, + roomQueryClient: config.RoomQueryClient, + walletClient: config.WalletClient, + walletDB: config.WalletDB, + registrationReward: config.RegistrationReward, + firstRechargeReward: config.FirstRechargeReward, + cumulativeRecharge: config.CumulativeRecharge, + inviteActivityReward: config.InviteActivityReward, + sevenDayCheckIn: config.SevenDayCheckIn, + luckyGift: config.LuckyGift, + roomTurnoverReward: config.RoomTurnoverReward, + weeklyStar: config.WeeklyStar, } } @@ -69,6 +72,9 @@ func (h *Handler) TaskHandlers() httproutes.TaskHandlers { GetRegistrationRewardEligibility: h.getRegistrationRewardEligibility, GetFirstRechargeRewardStatus: h.getFirstRechargeRewardStatus, GetCumulativeRechargeRewardStatus: h.getCumulativeRechargeRewardStatus, + GetInviteActivityRewardStatus: h.getInviteActivityRewardStatus, + ClaimInviteActivityReward: h.claimInviteActivityReward, + ListInviteActivityLeaderboard: h.listInviteActivityLeaderboard, GetSevenDayCheckInStatus: h.getSevenDayCheckInStatus, SignSevenDayCheckIn: h.signSevenDayCheckIn, ListUserLeaderboards: h.listUserLeaderboards, diff --git a/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler.go b/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler.go new file mode 100644 index 00000000..66294bf2 --- /dev/null +++ b/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler.go @@ -0,0 +1,363 @@ +package activityapi + +import ( + "net/http" + "strconv" + "strings" + + activityv1 "hyapp.local/api/proto/activity/v1" + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +type inviteActivityRewardTierData struct { + TierID int64 `json:"tier_id"` + RewardType string `json:"reward_type"` + TierCode string `json:"tier_code"` + TierName string `json:"tier_name"` + ThresholdCoinAmount int64 `json:"threshold_coin_amount"` + ThresholdValidInviteCount int64 `json:"threshold_valid_invite_count"` + RewardCoinAmount int64 `json:"reward_coin_amount"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + Reached bool `json:"reached"` + Claimable bool `json:"claimable"` + Claimed bool `json:"claimed"` +} + +type inviteActivityRewardProgressData struct { + CycleKey string `json:"cycle_key"` + UserID int64 `json:"user_id"` + TotalRechargeCoinAmount int64 `json:"total_recharge_coin_amount"` + ValidInviteCount int64 `json:"valid_invite_count"` + ClaimedRewardCoinAmount int64 `json:"claimed_reward_coin_amount"` + UpdatedAtMS int64 `json:"updated_at_ms"` + RechargeHighestTierID int64 `json:"recharge_highest_tier_id"` + RechargeClaimableTierID int64 `json:"recharge_claimable_tier_id"` + RechargeClaimableCoin int64 `json:"recharge_claimable_coin"` + ValidInviteClaimableCount int32 `json:"valid_invite_claimable_count"` +} + +type inviteActivityRewardClaimData struct { + ClaimID string `json:"claim_id"` + CycleKey string `json:"cycle_key"` + UserID int64 `json:"user_id"` + RewardType string `json:"reward_type"` + CommandID string `json:"command_id"` + TierID int64 `json:"tier_id"` + TierCode string `json:"tier_code"` + TierName string `json:"tier_name"` + ThresholdCoinAmount int64 `json:"threshold_coin_amount"` + ThresholdValidInviteCount int64 `json:"threshold_valid_invite_count"` + ReachedValue int64 `json:"reached_value"` + RewardCoinAmount int64 `json:"reward_coin_amount"` + Status string `json:"status"` + WalletCommandID string `json:"wallet_command_id"` + WalletTransactionID string `json:"wallet_transaction_id"` + FailureReason string `json:"failure_reason"` + GrantedAtMS int64 `json:"granted_at_ms"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + +type inviteActivityRewardStatusData struct { + Enabled bool `json:"enabled"` + CycleKey string `json:"cycle_key"` + Period cumulativeRechargeRewardPeriodData `json:"period"` + Tiers []inviteActivityRewardTierData `json:"tiers"` + Progress inviteActivityRewardProgressData `json:"progress"` + Claims []inviteActivityRewardClaimData `json:"claims"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type inviteActivityRewardClaimBody struct { + RewardType string `json:"reward_type"` + TierID int64 `json:"tier_id"` + CommandID string `json:"command_id"` +} + +type inviteActivityLeaderboardUserData struct { + UserID string `json:"user_id"` + DisplayUserID string `json:"display_user_id"` + Username string `json:"username"` + Avatar string `json:"avatar"` +} + +type inviteActivityLeaderboardEntryData struct { + RankNo int32 `json:"rank_no"` + UserID string `json:"user_id"` + ValidInviteCount int64 `json:"valid_invite_count"` + TotalRechargeCoinAmount int64 `json:"total_recharge_coin_amount"` + User *inviteActivityLeaderboardUserData `json:"user,omitempty"` +} + +type inviteActivityLeaderboardData struct { + Entries []inviteActivityLeaderboardEntryData `json:"entries"` + Total int64 `json:"total"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` + CycleKey string `json:"cycle_key"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +// getInviteActivityRewardStatus 返回当前用户本 UTC 月邀请活动面板;充值和有效人数都由 activity-service 已消费事实计算。 +func (h *Handler) getInviteActivityRewardStatus(writer http.ResponseWriter, request *http.Request) { + if h.inviteActivityReward == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + resp, err := h.inviteActivityReward.GetInviteActivityRewardStatus(request.Context(), &activityv1.GetInviteActivityRewardStatusRequest{ + Meta: httpkit.ActivityMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, inviteActivityRewardStatusFromProto(resp.GetStatus())) +} + +// claimInviteActivityReward 只透传当前用户、档位和 command_id;是否达标、最高档差额和发奖幂等在 activity-service 内完成。 +func (h *Handler) claimInviteActivityReward(writer http.ResponseWriter, request *http.Request) { + if h.inviteActivityReward == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body inviteActivityRewardClaimBody + if !httpkit.Decode(writer, request, &body) { + return + } + rewardType := strings.TrimSpace(body.RewardType) + commandID := strings.TrimSpace(body.CommandID) + if rewardType == "" || body.TierID <= 0 || commandID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.inviteActivityReward.ClaimInviteActivityReward(request.Context(), &activityv1.ClaimInviteActivityRewardRequest{ + Meta: httpkit.ActivityMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + RewardType: rewardType, + TierId: body.TierID, + CommandId: commandID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "claim": inviteActivityRewardClaimFromProto(resp.GetClaim()), + "status": inviteActivityRewardStatusFromProto(resp.GetStatus()), + }) +} + +// listInviteActivityLeaderboard 返回当前邀请活动有效人数月榜;头像昵称只在 gateway 出口向 user-service 批量补齐。 +func (h *Handler) listInviteActivityLeaderboard(writer http.ResponseWriter, request *http.Request) { + if h.inviteActivityReward == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + page := int32(queryInt(request, "page", 1)) + pageSize := int32(queryInt(request, "page_size", 50)) + if pageSize > 100 { + pageSize = 100 + } + resp, err := h.inviteActivityReward.ListInviteActivityLeaderboard(request.Context(), &activityv1.ListInviteActivityLeaderboardRequest{ + Meta: httpkit.ActivityMeta(request), + CycleKey: strings.TrimSpace(request.URL.Query().Get("cycle_key")), + Page: page, + PageSize: pageSize, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + users := h.resolveInviteActivityLeaderboardUsers(request, inviteActivityLeaderboardUserIDs(resp.GetEntries())) + httpkit.WriteOK(writer, request, inviteActivityLeaderboardData{ + Entries: inviteActivityLeaderboardEntriesFromProto(resp.GetEntries(), users), + Total: resp.GetTotal(), + Page: page, + PageSize: pageSize, + CycleKey: resp.GetCycleKey(), + ServerTimeMS: resp.GetServerTimeMs(), + }) +} + +func inviteActivityRewardStatusFromProto(item *activityv1.InviteActivityRewardStatus) inviteActivityRewardStatusData { + if item == nil { + return inviteActivityRewardStatusData{ + Tiers: []inviteActivityRewardTierData{}, + Claims: []inviteActivityRewardClaimData{}, + } + } + claims := make([]inviteActivityRewardClaimData, 0, len(item.GetClaims())) + grantedByTier := map[int64]bool{} + var grantedRechargeReward int64 + for _, claim := range item.GetClaims() { + converted := inviteActivityRewardClaimFromProto(claim) + claims = append(claims, converted) + if converted.Status == "granted" { + grantedByTier[converted.TierID] = true + if converted.RewardType == "recharge" { + grantedRechargeReward += converted.RewardCoinAmount + } + } + } + progress := inviteActivityRewardProgressFromProto(item.GetProgress()) + tiers := make([]inviteActivityRewardTierData, 0, len(item.GetConfig().GetTiers())) + for _, tier := range item.GetConfig().GetTiers() { + converted := inviteActivityRewardTierFromProto(tier, progress, grantedByTier) + tiers = append(tiers, converted) + if converted.RewardType == "recharge" && converted.Reached { + progress.RechargeHighestTierID = converted.TierID + // 有效人数奖励和累计充值奖励同表发放,但累计充值只能按本类型历史发放额计算最高档差额;否则先领了人数奖励会误抵扣充值档。 + if !converted.Claimed && converted.RewardCoinAmount > grantedRechargeReward { + progress.RechargeClaimableTierID = converted.TierID + progress.RechargeClaimableCoin = converted.RewardCoinAmount - grantedRechargeReward + } + } + if converted.RewardType == "valid_invite" && converted.Claimable { + progress.ValidInviteClaimableCount++ + } + } + for index := range tiers { + if tiers[index].RewardType == "recharge" { + tiers[index].Claimable = tiers[index].TierID == progress.RechargeClaimableTierID + } + } + return inviteActivityRewardStatusData{ + Enabled: item.GetConfig().GetEnabled(), + CycleKey: item.GetCycleKey(), + Period: cumulativeRechargeRewardPeriodData{ + StartMS: item.GetPeriodStartMs(), + EndMS: item.GetPeriodEndMs(), + }, + Tiers: tiers, + Progress: progress, + Claims: claims, + ServerTimeMS: item.GetServerTimeMs(), + } +} + +func (h *Handler) resolveInviteActivityLeaderboardUsers(request *http.Request, ids []int64) map[int64]inviteActivityLeaderboardUserData { + users := make(map[int64]inviteActivityLeaderboardUserData) + ids = uniquePositiveIDs(ids) + if len(ids) == 0 || h.userProfileClient == nil { + return users + } + resp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{ + Meta: httpkit.UserMeta(request, ""), + UserIds: ids, + }) + if err != nil { + return users + } + for _, item := range resp.GetUsers() { + users[item.GetUserId()] = inviteActivityLeaderboardUserData{ + UserID: strconv.FormatInt(item.GetUserId(), 10), + DisplayUserID: item.GetDisplayUserId(), + Username: item.GetUsername(), + Avatar: item.GetAvatar(), + } + } + return users +} + +func inviteActivityLeaderboardEntriesFromProto(items []*activityv1.InviteActivityLeaderboardEntry, users map[int64]inviteActivityLeaderboardUserData) []inviteActivityLeaderboardEntryData { + entries := make([]inviteActivityLeaderboardEntryData, 0, len(items)) + for _, item := range items { + if item == nil { + continue + } + entry := inviteActivityLeaderboardEntryData{ + RankNo: item.GetRankNo(), + UserID: strconv.FormatInt(item.GetUserId(), 10), + ValidInviteCount: item.GetValidInviteCount(), + TotalRechargeCoinAmount: item.GetTotalRechargeCoinAmount(), + } + if user, ok := users[item.GetUserId()]; ok { + entry.User = &user + } + entries = append(entries, entry) + } + return entries +} + +func inviteActivityLeaderboardUserIDs(items []*activityv1.InviteActivityLeaderboardEntry) []int64 { + ids := make([]int64, 0, len(items)) + for _, item := range items { + if item != nil { + ids = append(ids, item.GetUserId()) + } + } + return ids +} + +func inviteActivityRewardTierFromProto(item *activityv1.InviteActivityRewardTier, progress inviteActivityRewardProgressData, grantedByTier map[int64]bool) inviteActivityRewardTierData { + if item == nil { + return inviteActivityRewardTierData{} + } + reachedValue := progress.ValidInviteCount + threshold := item.GetThresholdValidInviteCount() + if item.GetRewardType() == "recharge" { + reachedValue = progress.TotalRechargeCoinAmount + threshold = item.GetThresholdCoinAmount() + } + reached := threshold > 0 && reachedValue >= threshold + claimed := grantedByTier[item.GetTierId()] + return inviteActivityRewardTierData{ + TierID: item.GetTierId(), + RewardType: item.GetRewardType(), + TierCode: item.GetTierCode(), + TierName: item.GetTierName(), + ThresholdCoinAmount: item.GetThresholdCoinAmount(), + ThresholdValidInviteCount: item.GetThresholdValidInviteCount(), + RewardCoinAmount: item.GetRewardCoinAmount(), + Status: item.GetStatus(), + SortOrder: item.GetSortOrder(), + Reached: reached, + Claimable: reached && !claimed, + Claimed: claimed, + } +} + +func inviteActivityRewardProgressFromProto(item *activityv1.InviteActivityRewardProgress) inviteActivityRewardProgressData { + if item == nil { + return inviteActivityRewardProgressData{} + } + return inviteActivityRewardProgressData{ + CycleKey: item.GetCycleKey(), + UserID: item.GetUserId(), + TotalRechargeCoinAmount: item.GetTotalRechargeCoinAmount(), + ValidInviteCount: item.GetValidInviteCount(), + ClaimedRewardCoinAmount: item.GetClaimedRewardCoinAmount(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} + +func inviteActivityRewardClaimFromProto(item *activityv1.InviteActivityRewardClaim) inviteActivityRewardClaimData { + if item == nil { + return inviteActivityRewardClaimData{} + } + return inviteActivityRewardClaimData{ + ClaimID: item.GetClaimId(), + CycleKey: item.GetCycleKey(), + UserID: item.GetUserId(), + RewardType: item.GetRewardType(), + CommandID: item.GetCommandId(), + TierID: item.GetTierId(), + TierCode: item.GetTierCode(), + TierName: item.GetTierName(), + ThresholdCoinAmount: item.GetThresholdCoinAmount(), + ThresholdValidInviteCount: item.GetThresholdValidInviteCount(), + ReachedValue: item.GetReachedValue(), + RewardCoinAmount: item.GetRewardCoinAmount(), + Status: item.GetStatus(), + WalletCommandID: item.GetWalletCommandId(), + WalletTransactionID: item.GetWalletTransactionId(), + FailureReason: item.GetFailureReason(), + GrantedAtMS: item.GetGrantedAtMs(), + CreatedAtMS: item.GetCreatedAtMs(), + UpdatedAtMS: item.GetUpdatedAtMs(), + } +} diff --git a/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler_test.go b/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler_test.go new file mode 100644 index 00000000..5a707891 --- /dev/null +++ b/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler_test.go @@ -0,0 +1,150 @@ +package activityapi + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + activityv1 "hyapp.local/api/proto/activity/v1" + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/appcode" +) + +func TestListInviteActivityLeaderboardEnrichesUsers(t *testing.T) { + inviteClient := &fakeInviteActivityRewardClient{ + leaderboardResp: &activityv1.ListInviteActivityLeaderboardResponse{ + Entries: []*activityv1.InviteActivityLeaderboardEntry{{ + RankNo: 1, + UserId: 1001, + ValidInviteCount: 12, + TotalRechargeCoinAmount: 880000, + }}, + Total: 1, + CycleKey: "2026-06", + ServerTimeMs: 1781179200000, + }, + } + userClient := &fakeInviteActivityUserProfileClient{ + users: map[int64]*userv1.User{ + 1001: { + UserId: 1001, + DisplayUserId: "L1001", + Username: "hanneem", + Avatar: "https://cdn.example/avatar.png", + }, + }, + } + handler := New(Config{InviteActivityReward: inviteClient, UserProfileClient: userClient}) + request := httptest.NewRequest(http.MethodGet, "/api/v1/activities/invite-reward/leaderboard?page=1&page_size=20", nil) + request = request.WithContext(appcode.WithContext(request.Context(), "lalu")) + recorder := httptest.NewRecorder() + + handler.listInviteActivityLeaderboard(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: %d body=%s", recorder.Code, recorder.Body.String()) + } + if inviteClient.lastLeaderboard == nil || inviteClient.lastLeaderboard.GetPage() != 1 || inviteClient.lastLeaderboard.GetPageSize() != 20 { + t.Fatalf("leaderboard request mismatch: %+v", inviteClient.lastLeaderboard) + } + if len(userClient.lastBatch.GetUserIds()) != 1 || userClient.lastBatch.GetUserIds()[0] != 1001 { + t.Fatalf("batch users request mismatch: %+v", userClient.lastBatch) + } + var envelope struct { + Code string `json:"code"` + Data struct { + Entries []struct { + RankNo int32 `json:"rank_no"` + UserID string `json:"user_id"` + ValidInviteCount int64 `json:"valid_invite_count"` + User struct { + Username string `json:"username"` + Avatar string `json:"avatar"` + } `json:"user"` + } `json:"entries"` + Total int64 `json:"total"` + CycleKey string `json:"cycle_key"` + } `json:"data"` + } + if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode response: %v", err) + } + if envelope.Code != "OK" || envelope.Data.Total != 1 || envelope.Data.CycleKey != "2026-06" { + t.Fatalf("response header mismatch: %+v", envelope) + } + if len(envelope.Data.Entries) != 1 || envelope.Data.Entries[0].RankNo != 1 || envelope.Data.Entries[0].UserID != "1001" || envelope.Data.Entries[0].ValidInviteCount != 12 { + t.Fatalf("entry mismatch: %+v", envelope.Data.Entries) + } + if envelope.Data.Entries[0].User.Username != "hanneem" || envelope.Data.Entries[0].User.Avatar != "https://cdn.example/avatar.png" { + t.Fatalf("user enrichment mismatch: %+v", envelope.Data.Entries[0].User) + } +} + +type fakeInviteActivityRewardClient struct { + leaderboardResp *activityv1.ListInviteActivityLeaderboardResponse + lastLeaderboard *activityv1.ListInviteActivityLeaderboardRequest +} + +func (f *fakeInviteActivityRewardClient) GetInviteActivityRewardStatus(context.Context, *activityv1.GetInviteActivityRewardStatusRequest) (*activityv1.GetInviteActivityRewardStatusResponse, error) { + return &activityv1.GetInviteActivityRewardStatusResponse{}, nil +} + +func (f *fakeInviteActivityRewardClient) ClaimInviteActivityReward(context.Context, *activityv1.ClaimInviteActivityRewardRequest) (*activityv1.ClaimInviteActivityRewardResponse, error) { + return &activityv1.ClaimInviteActivityRewardResponse{}, nil +} + +func (f *fakeInviteActivityRewardClient) ListInviteActivityLeaderboard(_ context.Context, req *activityv1.ListInviteActivityLeaderboardRequest) (*activityv1.ListInviteActivityLeaderboardResponse, error) { + f.lastLeaderboard = req + if f.leaderboardResp != nil { + return f.leaderboardResp, nil + } + return &activityv1.ListInviteActivityLeaderboardResponse{}, nil +} + +type fakeInviteActivityUserProfileClient struct { + users map[int64]*userv1.User + lastBatch *userv1.BatchGetUsersRequest +} + +func (f *fakeInviteActivityUserProfileClient) BatchGetUsers(_ context.Context, req *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error) { + f.lastBatch = req + return &userv1.BatchGetUsersResponse{Users: f.users}, nil +} + +func (f *fakeInviteActivityUserProfileClient) GetUser(context.Context, *userv1.GetUserRequest) (*userv1.GetUserResponse, error) { + return &userv1.GetUserResponse{}, nil +} + +func (f *fakeInviteActivityUserProfileClient) BusinessUserLookup(context.Context, *userv1.BusinessUserLookupRequest) (*userv1.BusinessUserLookupResponse, error) { + return &userv1.BusinessUserLookupResponse{}, nil +} + +func (f *fakeInviteActivityUserProfileClient) GetMyProfileStats(context.Context, *userv1.GetMyProfileStatsRequest) (*userv1.GetMyProfileStatsResponse, error) { + return &userv1.GetMyProfileStatsResponse{}, nil +} + +func (f *fakeInviteActivityUserProfileClient) CompleteOnboarding(context.Context, *userv1.CompleteOnboardingRequest) (*userv1.CompleteOnboardingResponse, error) { + return &userv1.CompleteOnboardingResponse{}, nil +} + +func (f *fakeInviteActivityUserProfileClient) UpdateUserProfile(context.Context, *userv1.UpdateUserProfileRequest) (*userv1.UpdateUserProfileResponse, error) { + return &userv1.UpdateUserProfileResponse{}, nil +} + +func (f *fakeInviteActivityUserProfileClient) ChangeUserCountry(context.Context, *userv1.ChangeUserCountryRequest) (*userv1.ChangeUserCountryResponse, error) { + return &userv1.ChangeUserCountryResponse{}, nil +} + +func (f *fakeInviteActivityUserProfileClient) CreateManagerUserBlock(context.Context, *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error) { + return &userv1.CreateManagerUserBlockResponse{}, nil +} + +func (f *fakeInviteActivityUserProfileClient) ListManagerUserBlocks(context.Context, *userv1.ListManagerUserBlocksRequest) (*userv1.ListManagerUserBlocksResponse, error) { + return &userv1.ListManagerUserBlocksResponse{}, nil +} + +func (f *fakeInviteActivityUserProfileClient) UnblockManagerUser(context.Context, *userv1.UnblockManagerUserRequest) (*userv1.UnblockManagerUserResponse, error) { + return &userv1.UnblockManagerUserResponse{}, nil +} diff --git a/services/gateway-service/internal/transport/http/appapi/app_config_handler.go b/services/gateway-service/internal/transport/http/appapi/app_config_handler.go index 5dcc899d..c1fbf245 100644 --- a/services/gateway-service/internal/transport/http/appapi/app_config_handler.go +++ b/services/gateway-service/internal/transport/http/appapi/app_config_handler.go @@ -72,6 +72,7 @@ func (h *Handler) getAppBootstrap(writer http.ResponseWriter, request *http.Requ "banners": "v1", "h5_links": "v1", "explore_tabs": "v1", + "popups": "v1", "resources": "v1", "gifts": "v1", }, @@ -163,6 +164,24 @@ func (h *Handler) listSplashScreens(writer http.ResponseWriter, request *http.Re httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)}) } +// listPopups 返回后台 APP配置/弹窗配置 中当前可见的弹窗列表;客户端按 display_period_days 做本地展示频控。 +func (h *Handler) listPopups(writer http.ResponseWriter, request *http.Request) { + if h.appConfigReader == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + + items, err := h.appConfigReader.ListPopups(request.Context(), appconfig.PopupQuery{ + AppCode: appcode.FromContext(request.Context()), + }) + if err != nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + + httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)}) +} + // getAppVersion 返回当前平台最新版本;App 用 build_number 判定是否需要提示升级。 func (h *Handler) getAppVersion(writer http.ResponseWriter, request *http.Request) { if h.appConfigReader == nil { diff --git a/services/gateway-service/internal/transport/http/appapi/handler.go b/services/gateway-service/internal/transport/http/appapi/handler.go index 11a7f9ee..37dd996f 100644 --- a/services/gateway-service/internal/transport/http/appapi/handler.go +++ b/services/gateway-service/internal/transport/http/appapi/handler.go @@ -15,6 +15,7 @@ type ConfigReader interface { ListExploreTabs(ctx context.Context, appCode string) ([]appconfig.ExploreTab, error) ListBanners(ctx context.Context, query appconfig.BannerQuery) ([]appconfig.Banner, error) ListSplashScreens(ctx context.Context, query appconfig.SplashScreenQuery) ([]appconfig.SplashScreen, error) + ListPopups(ctx context.Context, query appconfig.PopupQuery) ([]appconfig.Popup, error) LatestVersion(ctx context.Context, query appconfig.VersionQuery) (appconfig.Version, error) } @@ -68,6 +69,10 @@ func (h *Handler) ListSplashScreens(writer http.ResponseWriter, request *http.Re h.listSplashScreens(writer, request) } +func (h *Handler) ListPopups(writer http.ResponseWriter, request *http.Request) { + h.listPopups(writer, request) +} + func (h *Handler) GetAppVersion(writer http.ResponseWriter, request *http.Request) { h.getAppVersion(writer, request) } diff --git a/services/gateway-service/internal/transport/http/game_handler_test.go b/services/gateway-service/internal/transport/http/game_handler_test.go index b306623a..6863f1ba 100644 --- a/services/gateway-service/internal/transport/http/game_handler_test.go +++ b/services/gateway-service/internal/transport/http/game_handler_test.go @@ -101,6 +101,48 @@ func TestListRecentGamesUsesAuthenticatedUserRegionAndLimit(t *testing.T) { } } +func TestListExploreWinnersReturnsSquareItems(t *testing.T) { + gameClient := &fakeGatewayGameClient{exploreWinnersResp: &gamev1.ListExploreWinnersResponse{ + Winners: []*gamev1.ExploreWinner{{ + WinId: "win-1", + GameCode: "game_dice", + GameId: "dice", + UserId: 7001, + DisplayName: "name", + AvatarUrl: "https://cdn.example/avatar.png", + CoinAmount: 306000, + WonAtMs: 1700000000000, + }}, + ServerTimeMs: 1700000005000, + }} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/games/explore-winners?limit=20", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-App-Code", "lalu") + 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 gameClient.lastExploreWinners.GetMeta().GetActorUserId() != 42 || gameClient.lastExploreWinners.GetMeta().GetAppCode() != "lalu" || gameClient.lastExploreWinners.GetPageSize() != 20 { + t.Fatalf("ListExploreWinners request mismatch: %+v", gameClient.lastExploreWinners) + } + var envelope httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data := envelope.Data.(map[string]any) + items := data["items"].([]any) + first := items[0].(map[string]any) + if first["game_code"] != "game_dice" || first["user_id"] != "7001" || first["coin_amount"].(float64) != 306000 { + t.Fatalf("explore winner response mismatch: %+v", first) + } +} + func TestGetBridgeScriptReturnsUniversalScriptConfig(t *testing.T) { gameClient := &fakeGatewayGameClient{bridgeResp: &gamev1.GetBridgeScriptResponse{ Config: &gamev1.GameBridgeScriptConfig{ @@ -188,6 +230,45 @@ func TestDiceMatchRenewsAccessTokenWhenTokenNearExpiry(t *testing.T) { } } +func TestRPSConfigUsesRockSelfGame(t *testing.T) { + gameClient := &fakeGatewayGameClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/games/rps/config?game_id=rock", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + 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 gameClient.lastGetDiceConfig == nil || gameClient.lastGetDiceConfig.GetGameId() != "rock" { + t.Fatalf("rps config must read rock self game config, got %+v", gameClient.lastGetDiceConfig) + } +} + +func TestRPSMatchUsesRockSelfGameAndAuthenticatedRegion(t *testing.T) { + gameClient := &fakeGatewayGameClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/games/rps/match", bytes.NewReader([]byte(`{"game_id":"rock","stake_coin":8000,"gesture":"paper"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("Content-Type", "application/json") + 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 gameClient.lastMatchDice == nil || gameClient.lastMatchDice.GetGameId() != "rock" || gameClient.lastMatchDice.GetUserId() != 42 || gameClient.lastMatchDice.GetRegionId() != 1001 || gameClient.lastMatchDice.GetStakeCoin() != 8000 { + t.Fatalf("rps match request mismatch: %+v", gameClient.lastMatchDice) + } +} + func TestCreateRoomRPSChallengeUsesAuthenticatedUserAndRegion(t *testing.T) { gameClient := &fakeGatewayGameClient{roomRPSChallengeResp: &gamev1.RoomRPSChallengeResponse{ Challenge: &gamev1.RoomRPSChallenge{ @@ -398,14 +479,18 @@ func (fn roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) type fakeGatewayGameClient struct { listResp *gamev1.ListGamesResponse recentResp *gamev1.ListGamesResponse + exploreWinnersResp *gamev1.ListExploreWinnersResponse bridgeResp *gamev1.GetBridgeScriptResponse launchResp *gamev1.LaunchGameResponse callbackResp *gamev1.CallbackResponse lastList *gamev1.ListGamesRequest lastRecent *gamev1.ListRecentGamesRequest + lastExploreWinners *gamev1.ListExploreWinnersRequest lastBridge *gamev1.GetBridgeScriptRequest lastLaunch *gamev1.LaunchGameRequest lastCallback *gamev1.CallbackRequest + lastGetDiceConfig *gamev1.GetDiceConfigRequest + lastMatchDice *gamev1.MatchDiceRequest roomRPSChallengeResp *gamev1.RoomRPSChallengeResponse lastListRoomRPS *gamev1.ListRoomRPSChallengesRequest lastCreateRoomRPS *gamev1.CreateRoomRPSChallengeRequest @@ -429,6 +514,14 @@ func (f *fakeGatewayGameClient) ListRecentGames(_ context.Context, req *gamev1.L return &gamev1.ListGamesResponse{}, nil } +func (f *fakeGatewayGameClient) ListExploreWinners(_ context.Context, req *gamev1.ListExploreWinnersRequest) (*gamev1.ListExploreWinnersResponse, error) { + f.lastExploreWinners = req + if f.exploreWinnersResp != nil { + return f.exploreWinnersResp, nil + } + return &gamev1.ListExploreWinnersResponse{}, nil +} + func (f *fakeGatewayGameClient) GetBridgeScript(_ context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) { f.lastBridge = req if f.bridgeResp != nil { @@ -445,11 +538,13 @@ func (f *fakeGatewayGameClient) LaunchGame(_ context.Context, req *gamev1.Launch return &gamev1.LaunchGameResponse{}, nil } -func (f *fakeGatewayGameClient) GetDiceConfig(context.Context, *gamev1.GetDiceConfigRequest) (*gamev1.DiceConfigResponse, error) { - return &gamev1.DiceConfigResponse{}, nil +func (f *fakeGatewayGameClient) GetDiceConfig(_ context.Context, req *gamev1.GetDiceConfigRequest) (*gamev1.DiceConfigResponse, error) { + f.lastGetDiceConfig = req + return &gamev1.DiceConfigResponse{Config: &gamev1.DiceConfig{GameId: req.GetGameId()}}, nil } -func (f *fakeGatewayGameClient) MatchDice(context.Context, *gamev1.MatchDiceRequest) (*gamev1.DiceMatchResponse, error) { +func (f *fakeGatewayGameClient) MatchDice(_ context.Context, req *gamev1.MatchDiceRequest) (*gamev1.DiceMatchResponse, error) { + f.lastMatchDice = req return &gamev1.DiceMatchResponse{}, nil } diff --git a/services/gateway-service/internal/transport/http/gameapi/game_handler.go b/services/gateway-service/internal/transport/http/gameapi/game_handler.go index 728ec5f9..e628c827 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_handler.go @@ -22,6 +22,8 @@ const ( hyappAccessTokenHeader = "X-Hyapp-Access-Token" hyappTokenTypeHeader = "X-Hyapp-Token-Type" hyappExpiresInHeader = "X-Hyapp-Expires-In" + selfGameIDDice = "dice" + selfGameIDRock = "rock" ) func (h *Handler) listGames(writer http.ResponseWriter, request *http.Request) { @@ -84,6 +86,24 @@ func (h *Handler) listRecentGames(writer http.ResponseWriter, request *http.Requ httpkit.Write(writer, request, gameListDataFromProto(resp), err) } +func (h *Handler) listExploreWinners(writer http.ResponseWriter, request *http.Request) { + if h.gameClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + pageSize, ok := exploreWinnerPageSize(request) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + // Square 播报只依赖当前 App,不接受客户端传 app_code;game-service 会保证首批机器人获胜事实已落库。 + resp, err := h.gameClient.ListExploreWinners(request.Context(), &gamev1.ListExploreWinnersRequest{ + Meta: gameMeta(request), + PageSize: pageSize, + }) + httpkit.Write(writer, request, exploreWinnerListDataFromProto(resp), err) +} + func (h *Handler) getBridgeScript(writer http.ResponseWriter, request *http.Request) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -135,15 +155,23 @@ func (h *Handler) launchGame(writer http.ResponseWriter, request *http.Request) } func (h *Handler) getDiceConfig(writer http.ResponseWriter, request *http.Request) { + h.getSelfGameConfig(writer, request, selfGameIDDice, false) +} + +func (h *Handler) getRPSConfig(writer http.ResponseWriter, request *http.Request) { + h.getSelfGameConfig(writer, request, selfGameIDRock, true) +} + +func (h *Handler) getSelfGameConfig(writer http.ResponseWriter, request *http.Request, defaultGameID string, fixedGameID bool) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } - gameID := strings.TrimSpace(request.URL.Query().Get("game_id")) - if gameID == "" { - gameID = "dice" + gameID, ok := selfGameIDFromInput(writer, request, request.URL.Query().Get("game_id"), defaultGameID, fixedGameID) + if !ok { + return } - // 配置接口只返回当前 App 的可下注档位和费率,不接受客户端传 app_code。 + // 配置接口只返回当前 App 的可下注档位和费率,不接受客户端传 app_code;rps 路径固定映射 rock,避免误读 room-rps 或 dice。 resp, err := h.gameClient.GetDiceConfig(request.Context(), &gamev1.GetDiceConfigRequest{ Meta: gameMeta(request), GameId: gameID, @@ -155,6 +183,14 @@ func (h *Handler) getDiceConfig(writer http.ResponseWriter, request *http.Reques } func (h *Handler) matchDice(writer http.ResponseWriter, request *http.Request) { + h.matchSelfGame(writer, request, selfGameIDDice, false) +} + +func (h *Handler) matchRPS(writer http.ResponseWriter, request *http.Request) { + h.matchSelfGame(writer, request, selfGameIDRock, true) +} + +func (h *Handler) matchSelfGame(writer http.ResponseWriter, request *http.Request, defaultGameID string, fixedGameID bool) { if h.gameClient == nil || h.userProfileClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return @@ -163,12 +199,14 @@ func (h *Handler) matchDice(writer http.ResponseWriter, request *http.Request) { GameID string `json:"game_id"` RoomID string `json:"room_id"` StakeCoin int64 `json:"stake_coin"` + Gesture string `json:"gesture"` } if !httpkit.Decode(writer, request, &body) { return } - if strings.TrimSpace(body.GameID) == "" { - body.GameID = "dice" + gameID, ok := selfGameIDFromInput(writer, request, body.GameID, defaultGameID, fixedGameID) + if !ok { + return } if body.StakeCoin <= 0 { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") @@ -186,7 +224,7 @@ func (h *Handler) matchDice(writer http.ResponseWriter, request *http.Request) { resp, err := h.gameClient.MatchDice(request.Context(), &gamev1.MatchDiceRequest{ Meta: gameMeta(request), UserId: userID, - GameId: strings.TrimSpace(body.GameID), + GameId: gameID, RoomId: strings.TrimSpace(body.RoomID), RegionId: userResp.GetUser().GetRegionId(), StakeCoin: body.StakeCoin, @@ -195,6 +233,14 @@ func (h *Handler) matchDice(writer http.ResponseWriter, request *http.Request) { } func (h *Handler) createDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.createSelfGameMatch(writer, request, selfGameIDDice, false) +} + +func (h *Handler) createRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.createSelfGameMatch(writer, request, selfGameIDRock, true) +} + +func (h *Handler) createSelfGameMatch(writer http.ResponseWriter, request *http.Request, defaultGameID string, fixedGameID bool) { if h.gameClient == nil || h.userProfileClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return @@ -210,7 +256,11 @@ func (h *Handler) createDiceMatch(writer http.ResponseWriter, request *http.Requ if !httpkit.Decode(writer, request, &body) { return } - if strings.TrimSpace(body.GameID) == "" || body.StakeCoin <= 0 { + gameID, ok := selfGameIDFromInput(writer, request, body.GameID, defaultGameID, fixedGameID) + if !ok { + return + } + if body.StakeCoin <= 0 { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } @@ -227,7 +277,7 @@ func (h *Handler) createDiceMatch(writer http.ResponseWriter, request *http.Requ resp, err := h.gameClient.CreateDiceMatch(request.Context(), &gamev1.CreateDiceMatchRequest{ Meta: gameMeta(request), UserId: userID, - GameId: strings.TrimSpace(body.GameID), + GameId: gameID, RoomId: strings.TrimSpace(body.RoomID), RegionId: userResp.GetUser().GetRegionId(), StakeCoin: body.StakeCoin, @@ -256,6 +306,11 @@ func (h *Handler) joinDiceMatch(writer http.ResponseWriter, request *http.Reques h.writeDiceMatch(writer, request, resp, err) } +func (h *Handler) joinRPSMatch(writer http.ResponseWriter, request *http.Request) { + // 独立猜拳复用自研匹配局 ID;join 不从 body 读取 game_id,因此只需要走同一条参与者身份校验链路。 + h.joinDiceMatch(writer, request) +} + func (h *Handler) getDiceMatch(writer http.ResponseWriter, request *http.Request) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -275,6 +330,11 @@ func (h *Handler) getDiceMatch(writer http.ResponseWriter, request *http.Request h.writeDiceMatch(writer, request, resp, err) } +func (h *Handler) getRPSMatch(writer http.ResponseWriter, request *http.Request) { + // match_id 已经由 game-service 全局唯一约束,HTTP 层不再反查 game_id,避免刷新路径多一次数据库读。 + h.getDiceMatch(writer, request) +} + func (h *Handler) rollDiceMatch(writer http.ResponseWriter, request *http.Request) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -294,6 +354,11 @@ func (h *Handler) rollDiceMatch(writer http.ResponseWriter, request *http.Reques h.writeDiceMatch(writer, request, resp, err) } +func (h *Handler) rollRPSMatch(writer http.ResponseWriter, request *http.Request) { + // H5 会带 gesture 做展示态上报;当前服务端结算仍以自研匹配局事实为准,客户端不能提交结果或派奖金额。 + h.rollDiceMatch(writer, request) +} + func (h *Handler) cancelDiceMatch(writer http.ResponseWriter, request *http.Request) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -312,6 +377,11 @@ func (h *Handler) cancelDiceMatch(writer http.ResponseWriter, request *http.Requ h.writeDiceMatch(writer, request, resp, err) } +func (h *Handler) cancelRPSMatch(writer http.ResponseWriter, request *http.Request) { + // 取消只允许等待局创建者撤销,权限和状态仍在 game-service 内部用同一把 match 锁判断。 + h.cancelDiceMatch(writer, request) +} + func (h *Handler) getRoomRPSConfig(writer http.ResponseWriter, request *http.Request) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -469,6 +539,19 @@ func (h *Handler) writeRoomRPSChallenge(writer http.ResponseWriter, request *htt httpkit.Write(writer, request, data, nil) } +func selfGameIDFromInput(writer http.ResponseWriter, request *http.Request, rawGameID string, defaultGameID string, fixedGameID bool) (string, bool) { + gameID := strings.TrimSpace(rawGameID) + if gameID == "" { + gameID = defaultGameID + } + if fixedGameID && gameID != defaultGameID { + // rps 是独立 H5 入口,但当前产品只开放 rock 这一条自研猜拳 game_id;这里拒绝串到 dice 或 room-rps。 + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return "", false + } + return gameID, true +} + func normalizeRoomRPSGesture(value string) (string, bool) { // 手势在 gateway 层归一为小写英文枚举;game-service 只接收稳定值,避免 Flutter 大小写或本地化文案污染订单事实。 switch strings.ToLower(strings.TrimSpace(value)) { @@ -578,6 +661,17 @@ func recentGamePageSize(request *http.Request) (int32, bool) { return httpkit.PositiveInt32Query(request, "page_size", 4) } +func exploreWinnerPageSize(request *http.Request) (int32, bool) { + pageSize, ok := httpkit.PositiveInt32Query(request, "limit", 20) + if !ok { + return 0, false + } + if pageSize > 50 { + pageSize = 50 + } + return pageSize, true +} + func bearerAccessToken(request *http.Request) string { header := strings.TrimSpace(request.Header.Get("Authorization")) if !strings.HasPrefix(header, "Bearer ") { diff --git a/services/gateway-service/internal/transport/http/gameapi/game_view.go b/services/gateway-service/internal/transport/http/gameapi/game_view.go index 1333077c..b0115ebf 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_view.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_view.go @@ -29,6 +29,24 @@ type gameItemData struct { SortOrder int32 `json:"sort_order"` } +type exploreWinnerListData struct { + Items []exploreWinnerItemData `json:"items"` + Total int `json:"total"` + ServerTimeMS int64 `json:"server_time_ms"` +} + +type exploreWinnerItemData struct { + WinID string `json:"win_id"` + GameCode string `json:"game_code"` + GameID string `json:"game_id"` + UserID string `json:"user_id"` + UserIDNumber int64 `json:"user_id_number"` + DisplayName string `json:"display_name"` + AvatarURL string `json:"avatar_url"` + CoinAmount int64 `json:"coin_amount"` + WonAtMS int64 `json:"won_at_ms"` +} + type gameLaunchData struct { SessionID string `json:"session_id"` LaunchURL string `json:"launch_url"` @@ -229,6 +247,27 @@ func gameListDataFromProto(resp *gamev1.ListGamesResponse) gameListData { return gameListData{Games: items, ServerTimeMS: resp.GetServerTimeMs()} } +func exploreWinnerListDataFromProto(resp *gamev1.ListExploreWinnersResponse) exploreWinnerListData { + if resp == nil { + return exploreWinnerListData{} + } + items := make([]exploreWinnerItemData, 0, len(resp.GetWinners())) + for _, item := range resp.GetWinners() { + items = append(items, exploreWinnerItemData{ + WinID: item.GetWinId(), + GameCode: item.GetGameCode(), + GameID: item.GetGameId(), + UserID: int64String(item.GetUserId()), + UserIDNumber: item.GetUserId(), + DisplayName: item.GetDisplayName(), + AvatarURL: item.GetAvatarUrl(), + CoinAmount: item.GetCoinAmount(), + WonAtMS: item.GetWonAtMs(), + }) + } + return exploreWinnerListData{Items: items, Total: len(items), ServerTimeMS: resp.GetServerTimeMs()} +} + func gameLaunchDataFromProto(resp *gamev1.LaunchGameResponse) gameLaunchData { if resp == nil { return gameLaunchData{} diff --git a/services/gateway-service/internal/transport/http/gameapi/handler.go b/services/gateway-service/internal/transport/http/gameapi/handler.go index b372bfce..642b50d7 100644 --- a/services/gateway-service/internal/transport/http/gameapi/handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/handler.go @@ -39,6 +39,7 @@ func (h *Handler) Handlers() httproutes.GameHandlers { return httproutes.GameHandlers{ ListGames: h.listGames, ListRecentGames: h.listRecentGames, + ListExploreWinners: h.listExploreWinners, GetBridgeScript: h.getBridgeScript, LaunchGame: h.launchGame, GetDiceConfig: h.getDiceConfig, @@ -48,6 +49,13 @@ func (h *Handler) Handlers() httproutes.GameHandlers { GetDiceMatch: h.getDiceMatch, RollDiceMatch: h.rollDiceMatch, CancelDiceMatch: h.cancelDiceMatch, + GetRPSConfig: h.getRPSConfig, + MatchRPS: h.matchRPS, + CreateRPSMatch: h.createRPSMatch, + JoinRPSMatch: h.joinRPSMatch, + GetRPSMatch: h.getRPSMatch, + RollRPSMatch: h.rollRPSMatch, + CancelRPSMatch: h.cancelRPSMatch, GetRoomRPSConfig: h.getRoomRPSConfig, ListRoomRPSChallenges: h.listRoomRPSChallenges, CreateRoomRPSChallenge: h.createRoomRPSChallenge, @@ -67,6 +75,10 @@ func (h *Handler) ListRecentGames(writer http.ResponseWriter, request *http.Requ h.listRecentGames(writer, request) } +func (h *Handler) ListExploreWinners(writer http.ResponseWriter, request *http.Request) { + h.listExploreWinners(writer, request) +} + func (h *Handler) GetBridgeScript(writer http.ResponseWriter, request *http.Request) { h.getBridgeScript(writer, request) } @@ -103,6 +115,34 @@ func (h *Handler) CancelDiceMatch(writer http.ResponseWriter, request *http.Requ h.cancelDiceMatch(writer, request) } +func (h *Handler) GetRPSConfig(writer http.ResponseWriter, request *http.Request) { + h.getRPSConfig(writer, request) +} + +func (h *Handler) MatchRPS(writer http.ResponseWriter, request *http.Request) { + h.matchRPS(writer, request) +} + +func (h *Handler) CreateRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.createRPSMatch(writer, request) +} + +func (h *Handler) JoinRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.joinRPSMatch(writer, request) +} + +func (h *Handler) GetRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.getRPSMatch(writer, request) +} + +func (h *Handler) RollRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.rollRPSMatch(writer, request) +} + +func (h *Handler) CancelRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.cancelRPSMatch(writer, request) +} + func (h *Handler) GetRoomRPSConfig(writer http.ResponseWriter, request *http.Request) { h.getRoomRPSConfig(writer, request) } diff --git a/services/gateway-service/internal/transport/http/handler.go b/services/gateway-service/internal/transport/http/handler.go index 66b198e3..531b4b43 100644 --- a/services/gateway-service/internal/transport/http/handler.go +++ b/services/gateway-service/internal/transport/http/handler.go @@ -22,43 +22,44 @@ import ( // Handler 是 gateway 的 HTTP transport adapter。 // 它只依赖跨服务 client,不持有房间状态,也不直接实现房间命令语义。 type Handler struct { - roomClient client.RoomClient - roomGuardClient client.RoomGuardClient - roomQueryClient client.RoomQueryClient - userClient client.UserAuthClient - userIdentityClient client.UserIdentityClient - userProfileClient client.UserProfileClient - userSocialClient client.UserSocialClient - userCPClient client.UserCPClient - userDeviceClient client.UserDeviceClient - userCountryClient client.UserCountryQueryClient - userRegionClient client.UserRegionClient - userHostClient client.UserHostClient - appRegistryClient client.AppRegistryClient - walletClient client.WalletClient - messageClient client.MessageInboxClient - taskClient client.TaskClient - growthLevelClient client.GrowthLevelClient - achievementClient client.AchievementClient - registrationReward client.RegistrationRewardClient - firstRechargeReward client.FirstRechargeRewardClient - cumulativeRecharge client.CumulativeRechargeRewardClient - sevenDayCheckIn client.SevenDayCheckInClient - luckyGift client.LuckyGiftClient - roomTurnoverReward client.RoomTurnoverRewardClient - weeklyStar client.WeeklyStarClient - broadcastClient client.BroadcastClient - gameClient client.GameClient - appConfigReader appapi.ConfigReader - leaderboardWalletDB *sql.DB - tencentIM TencentIMConfig - tencentIMImporter TencentIMAccountImporter - tencentRTC TencentRTCConfig - objectUploader ObjectUploader - authRateLimitConfig AuthRateLimitConfig - authRateLimiter publicAuthRateLimiter - loginRiskConfig LoginRiskConfig - loginRiskCache loginRiskCache + roomClient client.RoomClient + roomGuardClient client.RoomGuardClient + roomQueryClient client.RoomQueryClient + userClient client.UserAuthClient + userIdentityClient client.UserIdentityClient + userProfileClient client.UserProfileClient + userSocialClient client.UserSocialClient + userCPClient client.UserCPClient + userDeviceClient client.UserDeviceClient + userCountryClient client.UserCountryQueryClient + userRegionClient client.UserRegionClient + userHostClient client.UserHostClient + appRegistryClient client.AppRegistryClient + walletClient client.WalletClient + messageClient client.MessageInboxClient + taskClient client.TaskClient + growthLevelClient client.GrowthLevelClient + achievementClient client.AchievementClient + registrationReward client.RegistrationRewardClient + firstRechargeReward client.FirstRechargeRewardClient + cumulativeRecharge client.CumulativeRechargeRewardClient + inviteActivityReward client.InviteActivityRewardClient + sevenDayCheckIn client.SevenDayCheckInClient + luckyGift client.LuckyGiftClient + roomTurnoverReward client.RoomTurnoverRewardClient + weeklyStar client.WeeklyStarClient + broadcastClient client.BroadcastClient + gameClient client.GameClient + appConfigReader appapi.ConfigReader + leaderboardWalletDB *sql.DB + tencentIM TencentIMConfig + tencentIMImporter TencentIMAccountImporter + tencentRTC TencentRTCConfig + objectUploader ObjectUploader + authRateLimitConfig AuthRateLimitConfig + authRateLimiter publicAuthRateLimiter + loginRiskConfig LoginRiskConfig + loginRiskCache loginRiskCache } // TencentIMConfig 是 gateway transport 层签发客户端腾讯云 IM 登录票据所需的配置。 @@ -232,6 +233,11 @@ func (h *Handler) SetCumulativeRechargeRewardClient(cumulativeRecharge client.Cu h.cumulativeRecharge = cumulativeRecharge } +// SetInviteActivityRewardClient 注入 activity-service 邀请活动查询和领取 client。 +func (h *Handler) SetInviteActivityRewardClient(inviteActivityReward client.InviteActivityRewardClient) { + h.inviteActivityReward = inviteActivityReward +} + // SetSevenDayCheckInClient 注入 activity-service 七日签到 client。 func (h *Handler) SetSevenDayCheckInClient(sevenDayCheckIn client.SevenDayCheckInClient) { h.sevenDayCheckIn = sevenDayCheckIn diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index c6479f37..00f5abc7 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -45,6 +45,7 @@ type AppHandlers struct { ListExploreTabs http.HandlerFunc ListAppBanners http.HandlerFunc ListSplashScreens http.HandlerFunc + ListPopups http.HandlerFunc GetAppVersion http.HandlerFunc GetResourceGroup http.HandlerFunc ListResourceShopItems http.HandlerFunc @@ -68,6 +69,7 @@ type UserHandlers struct { BatchUserProfiles http.HandlerFunc BatchRoomDisplayProfiles http.HandlerFunc GetMyOverview http.HandlerFunc + GetMyInviteOverview http.HandlerFunc GetMyGiftWall http.HandlerFunc GetMyIdentity http.HandlerFunc GetMyHostIdentity http.HandlerFunc @@ -184,6 +186,9 @@ type TaskHandlers struct { GetRegistrationRewardEligibility http.HandlerFunc GetFirstRechargeRewardStatus http.HandlerFunc GetCumulativeRechargeRewardStatus http.HandlerFunc + GetInviteActivityRewardStatus http.HandlerFunc + ClaimInviteActivityReward http.HandlerFunc + ListInviteActivityLeaderboard http.HandlerFunc GetSevenDayCheckInStatus http.HandlerFunc SignSevenDayCheckIn http.HandlerFunc ListUserLeaderboards http.HandlerFunc @@ -250,6 +255,7 @@ type VIPHandlers struct { type GameHandlers struct { ListGames http.HandlerFunc ListRecentGames http.HandlerFunc + ListExploreWinners http.HandlerFunc GetBridgeScript http.HandlerFunc LaunchGame http.HandlerFunc GetDiceConfig http.HandlerFunc @@ -259,6 +265,13 @@ type GameHandlers struct { GetDiceMatch http.HandlerFunc RollDiceMatch http.HandlerFunc CancelDiceMatch http.HandlerFunc + GetRPSConfig http.HandlerFunc + MatchRPS http.HandlerFunc + CreateRPSMatch http.HandlerFunc + JoinRPSMatch http.HandlerFunc + GetRPSMatch http.HandlerFunc + RollRPSMatch http.HandlerFunc + CancelRPSMatch http.HandlerFunc GetRoomRPSConfig http.HandlerFunc ListRoomRPSChallenges http.HandlerFunc CreateRoomRPSChallenge http.HandlerFunc @@ -343,6 +356,7 @@ func (r routes) registerAppRoutes() { r.public("/app/explore-tabs", http.MethodGet, h.ListExploreTabs) r.public("/app/banners", "", h.ListAppBanners) r.public("/app/splash-screens", http.MethodGet, h.ListSplashScreens) + r.public("/app/popups", http.MethodGet, h.ListPopups) r.public("/app/version", http.MethodGet, h.GetAppVersion) r.public("/resource-groups/{group_id}", "", h.GetResourceGroup) r.public("/resource-shop/items", http.MethodGet, h.ListResourceShopItems) @@ -367,6 +381,7 @@ func (r routes) registerUserRoutes() { r.profile("/users/profiles:batch", "", h.BatchUserProfiles) r.profile("/users/room-display-profiles:batch", http.MethodGet, h.BatchRoomDisplayProfiles) r.profile("/users/me/overview", "", h.GetMyOverview) + r.profile("/users/me/invite-overview", http.MethodGet, h.GetMyInviteOverview) r.profile("/users/me/gift-wall", http.MethodGet, h.GetMyGiftWall) r.auth("/users/me/identity", "", h.GetMyIdentity) r.profile("/users/me/host-identity", "", h.GetMyHostIdentity) @@ -483,6 +498,9 @@ func (r routes) registerTaskRoutes() { r.profile("/activities/registration-reward/eligibility", http.MethodGet, h.GetRegistrationRewardEligibility) r.profile("/activities/first-recharge-reward", http.MethodGet, h.GetFirstRechargeRewardStatus) r.profile("/activities/cumulative-recharge-reward", http.MethodGet, h.GetCumulativeRechargeRewardStatus) + r.profile("/activities/invite-reward", http.MethodGet, h.GetInviteActivityRewardStatus) + r.profile("/activities/invite-reward/leaderboard", http.MethodGet, h.ListInviteActivityLeaderboard) + r.profile("/activities/invite-reward/claim", http.MethodPost, h.ClaimInviteActivityReward) r.profile("/activities/seven-day-checkin", http.MethodGet, h.GetSevenDayCheckInStatus) r.profile("/activities/seven-day-checkin/sign", http.MethodPost, h.SignSevenDayCheckIn) r.profile("/activities/user-leaderboards", http.MethodGet, h.ListUserLeaderboards) @@ -558,6 +576,7 @@ func (r routes) registerGameRoutes() { h := r.config.Game r.profile("/games", http.MethodGet, h.ListGames) r.profile("/games/recent", http.MethodGet, h.ListRecentGames) + r.profile("/games/explore-winners", http.MethodGet, h.ListExploreWinners) r.profile("/games/bridge-script", http.MethodGet, h.GetBridgeScript) r.profile("/games/{game_id}/launch", http.MethodPost, h.LaunchGame) r.profile("/games/dice/config", http.MethodGet, h.GetDiceConfig) @@ -567,6 +586,13 @@ func (r routes) registerGameRoutes() { r.profile("/games/dice/matches/{match_id}", http.MethodGet, h.GetDiceMatch) r.profile("/games/dice/matches/{match_id}/roll", http.MethodPost, h.RollDiceMatch) r.profile("/games/dice/matches/{match_id}/cancel", http.MethodPost, h.CancelDiceMatch) + r.profile("/games/rps/config", http.MethodGet, h.GetRPSConfig) + r.profile("/games/rps/match", http.MethodPost, h.MatchRPS) + r.profile("/games/rps/matches", http.MethodPost, h.CreateRPSMatch) + r.profile("/games/rps/matches/{match_id}/join", http.MethodPost, h.JoinRPSMatch) + r.profile("/games/rps/matches/{match_id}", http.MethodGet, h.GetRPSMatch) + r.profile("/games/rps/matches/{match_id}/roll", http.MethodPost, h.RollRPSMatch) + r.profile("/games/rps/matches/{match_id}/cancel", http.MethodPost, h.CancelRPSMatch) r.profile("/games/room-rps/config", http.MethodGet, h.GetRoomRPSConfig) r.profile("/games/room-rps/challenges", http.MethodGet, h.ListRoomRPSChallenges) // 当前 ServeMux 包装按 path 注册,不能把 GET 列表和 POST 创建同时挂在 /challenges;创建动作单独放 /create,避免路由覆盖。 diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index a07e71b7..9553a6cc 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -3575,6 +3575,7 @@ type captureAppConfigReader struct { appconfig.StaticReader bannerQuery appconfig.BannerQuery splashQuery appconfig.SplashScreenQuery + popupQuery appconfig.PopupQuery } func (r *captureAppConfigReader) ListBanners(ctx context.Context, query appconfig.BannerQuery) ([]appconfig.Banner, error) { @@ -3587,6 +3588,11 @@ func (r *captureAppConfigReader) ListSplashScreens(ctx context.Context, query ap return r.StaticReader.ListSplashScreens(ctx, query) } +func (r *captureAppConfigReader) ListPopups(ctx context.Context, query appconfig.PopupQuery) ([]appconfig.Popup, error) { + r.popupQuery = query + return r.StaticReader.ListPopups(ctx, query) +} + func TestListAppBannersReturnsAdminAppConfig(t *testing.T) { handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) reader := &captureAppConfigReader{StaticReader: appconfig.StaticReader{Banners: []appconfig.Banner{ @@ -3737,6 +3743,65 @@ func TestSplashScreenJSONIncludesEmptyScopeFields(t *testing.T) { } } +func TestListPopupsReturnsAdminAppConfig(t *testing.T) { + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + reader := &captureAppConfigReader{StaticReader: appconfig.StaticReader{Popups: []appconfig.Popup{ + { + ID: 21, + Code: "new-user-popup", + Name: "新人活动", + ImageURL: "https://cdn.example.com/popup.png", + JumpType: "h5", + JumpURL: "https://h5.example.com/new-user", + DisplayPeriodDays: 2, + SortOrder: 3, + StartsAtMs: 1700000000000, + EndsAtMs: 1800000000000, + UpdatedAtMs: 1700000004000, + }, + }}} + handler.SetAppConfigReader(reader) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/app/popups", nil) + request.Header.Set("X-Request-ID", "req-app-popups") + request.Header.Set("X-App-Code", "lalu") + 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()) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data, ok := response.Data.(map[string]any) + if response.Code != httpkit.CodeOK || !ok { + t.Fatalf("unexpected envelope: %+v", response) + } + if reader.popupQuery.AppCode != "lalu" { + t.Fatalf("popup query mismatch: %+v", reader.popupQuery) + } + if data["total"].(float64) != 1 { + t.Fatalf("popup total mismatch: %+v", data) + } + items, ok := data["items"].([]any) + if !ok || len(items) != 1 { + t.Fatalf("popup items shape mismatch: %+v", data) + } + first, ok := items[0].(map[string]any) + if !ok || first["code"] != "new-user-popup" || first["name"] != "新人活动" || first["jump_type"] != "h5" || first["jump_url"] != "https://h5.example.com/new-user" { + t.Fatalf("popup item mismatch: %+v", first) + } + if first["image_url"] != "https://cdn.example.com/popup.png" || first["display_period_days"].(float64) != 2 || first["sort_order"].(float64) != 3 { + t.Fatalf("popup display fields mismatch: %+v", first) + } + if first["starts_at_ms"].(float64) != 1700000000000 || first["ends_at_ms"].(float64) != 1800000000000 || first["updated_at_ms"].(float64) != 1700000004000 { + t.Fatalf("popup metadata mismatch: %+v", first) + } +} + func TestGetAppVersionReturnsLatestVersionAndUpdateDecision(t *testing.T) { handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) handler.SetAppConfigReader(appconfig.StaticReader{Version: appconfig.Version{ @@ -4938,6 +5003,32 @@ func TestGetMyProfileUsesAuthenticatedUserID(t *testing.T) { } } +func TestGetMyInviteOverviewUsesAuthenticatedUserID(t *testing.T) { + profileClient := &fakeUserProfileClient{} + router := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient).Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/users/me/invite-overview", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-invite-overview") + 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 profileClient.lastGet == nil || profileClient.lastGet.GetUserId() != 42 { + t.Fatalf("authenticated user_id was not propagated: %+v", profileClient.lastGet) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data, ok := response.Data.(map[string]any) + if response.Code != httpkit.CodeOK || !ok || data["my_invite_code"] != "A1B2C3" || data["invite_enabled"] != true || data["invite_count"] != float64(12) || data["valid_invite_count"] != float64(3) { + t.Fatalf("invite overview response mismatch: %+v", response) + } +} + func TestMessageTabsAndInboxRoutesUseAuthenticatedUser(t *testing.T) { messageClient := &fakeMessageInboxClient{tabsResp: &activityv1.ListMessageTabsResponse{Sections: []*activityv1.MessageTabSection{ {Section: "user", Title: "用户", UnreadCount: 0, Source: "tencent_im_sdk"}, @@ -6222,6 +6313,73 @@ func TestGetMyGiftWallUsesAuthenticatedUserID(t *testing.T) { } } +func TestGetUserGiftWallUsesPathUserID(t *testing.T) { + walletClient := &fakeWalletClient{giftWallResp: &walletv1.GetUserGiftWallResponse{ + GiftKindCount: 1, + GiftTotalCount: 3, + TotalValue: 300, + TotalCoinValue: 300, + Items: []*walletv1.GiftWallItem{{ + GiftId: "castle", + GiftName: "Castle", + GiftCount: 3, + TotalValue: 300, + TotalCoinValue: 300, + }}, + }} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetWalletClient(walletClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/users/10002/gift-wall", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-user-gift-wall") + 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 walletClient.lastGiftWall == nil || walletClient.lastGiftWall.GetUserId() != 10002 || + walletClient.lastGiftWall.GetRequestId() != assertGeneratedRequestID(t, recorder, "req-user-gift-wall") { + t.Fatalf("profile gift wall request mismatch: %+v", walletClient.lastGiftWall) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + data, ok := response.Data.(map[string]any) + if response.Code != httpkit.CodeOK || !ok || data["gift_total_count"] != float64(3) || data["total_value"] != float64(300) { + t.Fatalf("profile gift wall response mismatch: %+v", response) + } +} + +func TestGetUserGiftWallRejectsInvalidPathUserID(t *testing.T) { + walletClient := &fakeWalletClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetWalletClient(walletClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodGet, "/api/v1/users/not-a-number/gift-wall", nil) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusBadRequest { + t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) + } + if walletClient.lastGiftWall != nil { + t.Fatalf("invalid user_id must not call wallet-service: %+v", walletClient.lastGiftWall) + } + var response httpkit.ResponseEnvelope + if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { + t.Fatalf("decode response failed: %v", err) + } + if response.Code != httpkit.CodeInvalidArgument { + t.Fatalf("error response mismatch: %+v", response) + } +} + func TestListCoinTransactionsForcesCoinAndDefaultsPageSize30(t *testing.T) { walletClient := &fakeWalletClient{transactionsResp: &walletv1.ListWalletTransactionsResponse{ Total: 1, diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index 49467482..e7649d05 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -9,6 +9,7 @@ import ( "hyapp/services/gateway-service/internal/transport/http/appapi" "hyapp/services/gateway-service/internal/transport/http/callbackapi" "hyapp/services/gateway-service/internal/transport/http/gameapi" + "hyapp/services/gateway-service/internal/transport/http/httpkit" "hyapp/services/gateway-service/internal/transport/http/httproutes" "hyapp/services/gateway-service/internal/transport/http/managerapi" "hyapp/services/gateway-service/internal/transport/http/messageapi" @@ -68,20 +69,21 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { UserProfileClient: h.userProfileClient, }) activityAPI := activityapi.New(activityapi.Config{ - TaskClient: h.taskClient, - GrowthLevelClient: h.growthLevelClient, - AchievementClient: h.achievementClient, - UserProfileClient: h.userProfileClient, - RoomQueryClient: h.roomQueryClient, - WalletClient: h.walletClient, - WalletDB: h.leaderboardWalletDB, - RegistrationReward: h.registrationReward, - FirstRechargeReward: h.firstRechargeReward, - CumulativeRecharge: h.cumulativeRecharge, - SevenDayCheckIn: h.sevenDayCheckIn, - LuckyGift: h.luckyGift, - RoomTurnoverReward: h.roomTurnoverReward, - WeeklyStar: h.weeklyStar, + TaskClient: h.taskClient, + GrowthLevelClient: h.growthLevelClient, + AchievementClient: h.achievementClient, + UserProfileClient: h.userProfileClient, + RoomQueryClient: h.roomQueryClient, + WalletClient: h.walletClient, + WalletDB: h.leaderboardWalletDB, + RegistrationReward: h.registrationReward, + FirstRechargeReward: h.firstRechargeReward, + CumulativeRecharge: h.cumulativeRecharge, + InviteActivityReward: h.inviteActivityReward, + SevenDayCheckIn: h.sevenDayCheckIn, + LuckyGift: h.luckyGift, + RoomTurnoverReward: h.roomTurnoverReward, + WeeklyStar: h.weeklyStar, }) gameAPI := gameapi.New(gameapi.Config{ GameClient: h.gameClient, @@ -127,6 +129,19 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { userHandlers.ListMyResources = resourceAPI.ListMyResources userHandlers.EquipMyResource = resourceAPI.EquipMyResource userHandlers.UnequipMyResource = resourceAPI.UnequipMyResource + userSocialAction := userHandlers.UserSocialAction + userHandlers.UserSocialAction = func(writer http.ResponseWriter, request *http.Request) { + // /users/{user_id}/{action...} 是资料卡动作的统一入口;gift-wall 复用这个入口可避开 ServeMux 与 by-display-user-id 路由的动态段冲突。 + if request.PathValue("action") == "gift-wall" { + if request.Method != http.MethodGet { + httpkit.WriteError(writer, request, http.StatusMethodNotAllowed, httpkit.CodeInvalidArgument, "invalid argument") + return + } + walletAPI.GetUserGiftWall(writer, request) + return + } + userSocialAction(writer, request) + } return httproutes.New(httproutes.Config{ PublicWrap: func(handler http.HandlerFunc) http.Handler { @@ -154,6 +169,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { ListExploreTabs: appAPI.ListExploreTabs, ListAppBanners: appAPI.ListAppBanners, ListSplashScreens: appAPI.ListSplashScreens, + ListPopups: appAPI.ListPopups, GetAppVersion: appAPI.GetAppVersion, GetResourceGroup: resourceAPI.GetResourceGroup, ListResourceShopItems: resourceAPI.ListResourceShopItems, diff --git a/services/gateway-service/internal/transport/http/userapi/handler.go b/services/gateway-service/internal/transport/http/userapi/handler.go index cb2331fb..5a3b0c22 100644 --- a/services/gateway-service/internal/transport/http/userapi/handler.go +++ b/services/gateway-service/internal/transport/http/userapi/handler.go @@ -54,6 +54,7 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers { return httproutes.UserHandlers{ ResolveDisplayUserID: h.resolveDisplayUserID, GetMyOverview: h.getMyOverview, + GetMyInviteOverview: h.getMyInviteOverview, GetMyIdentity: h.getMyIdentity, GetMyHostIdentity: h.getMyHostIdentity, GetMyRoleSummary: h.getMyRoleSummary, diff --git a/services/gateway-service/internal/transport/http/userapi/user_handler.go b/services/gateway-service/internal/transport/http/userapi/user_handler.go index a1f1c85a..9f63fbb0 100644 --- a/services/gateway-service/internal/transport/http/userapi/user_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/user_handler.go @@ -87,6 +87,14 @@ type inviteBindingData struct { InviterUserID string `json:"inviter_user_id,omitempty"` } +type inviteOverviewData struct { + MyInviteCode string `json:"my_invite_code"` + InviteEnabled bool `json:"invite_enabled"` + InviteCount int64 `json:"invite_count"` + ValidInviteCount int64 `json:"valid_invite_count"` + ValidInviteThresholdCoin int64 `json:"valid_invite_threshold_coin"` +} + // countryData 是注册页国家列表的公开响应投影。 // 只包含客户端选择和展示所需字段,不暴露管理端审计字段。 type countryData struct { @@ -296,6 +304,25 @@ func (h *Handler) getMyProfile(writer http.ResponseWriter, request *http.Request httpkit.WriteOK(writer, request, profileData(resp.GetUser(), 0)) } +// getMyInviteOverview 返回当前登录用户自己的邀请码和邀请计数。 +func (h *Handler) getMyInviteOverview(writer http.ResponseWriter, request *http.Request) { + if h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + + resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + + httpkit.WriteOK(writer, request, inviteOverviewDataFromProto(resp.GetUser().GetInvite())) +} + // updateMyProfile 修改当前用户的用户名、头像、性别和生日。 func (h *Handler) updateMyProfile(writer http.ResponseWriter, request *http.Request) { var body struct { @@ -577,6 +604,19 @@ func inviteBindingDataFromProto(binding *userv1.InviteBinding) *inviteBindingDat } } +func inviteOverviewDataFromProto(overview *userv1.InviteOverview) inviteOverviewData { + if overview == nil { + return inviteOverviewData{} + } + return inviteOverviewData{ + MyInviteCode: overview.GetMyInviteCode(), + InviteEnabled: overview.GetInviteEnabled(), + InviteCount: overview.GetInviteCount(), + ValidInviteCount: overview.GetValidInviteCount(), + ValidInviteThresholdCoin: overview.GetValidInviteThresholdCoin(), + } +} + func userIDString(userID int64) string { if userID > 0 { return strconv.FormatInt(userID, 10) diff --git a/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go b/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go index 690bccbe..9fc0cbe1 100644 --- a/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/app_wallet_handler.go @@ -308,16 +308,36 @@ func (h *Handler) getDiamondExchangeConfig(writer http.ResponseWriter, request * httpkit.WriteOK(writer, request, map[string]any{"rules": rules}) } -// getMyGiftWall 返回当前登录用户已经收到的礼物墙;gateway 不接受 user_id,避免越权查询他人收礼事实。 +// getMyGiftWall 返回当前登录用户已经收到的礼物墙;“我的”入口只信任 token 里的 user_id,避免客户端用参数伪造成他人。 func (h *Handler) getMyGiftWall(writer http.ResponseWriter, request *http.Request) { + h.writeGiftWall(writer, request, auth.UserIDFromContext(request.Context())) +} + +// getUserGiftWall 返回资料卡目标用户已经收到的礼物墙;资料卡本来就是公开展示面,路径 user_id 只决定展示对象,不参与鉴权身份。 +func (h *Handler) getUserGiftWall(writer http.ResponseWriter, request *http.Request) { + userID, ok := httpkit.PositiveInt64PathValue(request, "user_id") + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + + h.writeGiftWall(writer, request, userID) +} + +// writeGiftWall 统一当前用户入口和资料卡入口的响应格式;wallet-service 持有最终一致的收礼投影,gateway 只传稳定 user_id。 +func (h *Handler) writeGiftWall(writer http.ResponseWriter, request *http.Request, userID int64) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } + if userID <= 0 { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } resp, err := h.walletClient.GetUserGiftWall(request.Context(), &walletv1.GetUserGiftWallRequest{ RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), - UserId: auth.UserIDFromContext(request.Context()), + UserId: userID, }) if err != nil { httpkit.WriteRPCError(writer, request, err) diff --git a/services/gateway-service/internal/transport/http/walletapi/handler.go b/services/gateway-service/internal/transport/http/walletapi/handler.go index f9fbd602..31ed3f49 100644 --- a/services/gateway-service/internal/transport/http/walletapi/handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/handler.go @@ -89,3 +89,7 @@ func (h *Handler) VIPHandlers() httproutes.VIPHandlers { func (h *Handler) GetMyGiftWall(writer http.ResponseWriter, request *http.Request) { h.getMyGiftWall(writer, request) } + +func (h *Handler) GetUserGiftWall(writer http.ResponseWriter, request *http.Request) { + h.getUserGiftWall(writer, request) +} diff --git a/services/user-service/deploy/mysql/initdb/001_user_service.sql b/services/user-service/deploy/mysql/initdb/001_user_service.sql index f95d1480..b50d536f 100644 --- a/services/user-service/deploy/mysql/initdb/001_user_service.sql +++ b/services/user-service/deploy/mysql/initdb/001_user_service.sql @@ -369,7 +369,7 @@ INSERT INTO user_invite_validity_policies ( eligible_recharge_types, effective_from_ms, effective_to_ms, created_by_user_id, created_at_ms, updated_at_ms ) VALUES ( 'lalu', 0, 'invite-valid-default-v1', 'active', 80000, - JSON_ARRAY('coin_seller_transfer'), 0, NULL, NULL, 0, 0 + JSON_ARRAY('coin_seller_transfer', 'google_play', 'google', 'mifapay', 'usdt_trc20'), 0, NULL, NULL, 0, 0 ) ON DUPLICATE KEY UPDATE status = VALUES(status), required_recharge_coin_amount = VALUES(required_recharge_coin_amount), diff --git a/services/user-service/internal/app/app.go b/services/user-service/internal/app/app.go index 57f44d5a..8465c3d5 100644 --- a/services/user-service/internal/app/app.go +++ b/services/user-service/internal/app/app.go @@ -787,13 +787,30 @@ func roomGiftCPEventFromRoomMessage(body []byte) (cpdomain.GiftEvent, bool, erro func rechargeTypeFromWalletPayload(payload string) string { var decoded map[string]any if err := json.Unmarshal([]byte(payload), &decoded); err == nil { - if value, ok := decoded["recharge_type"].(string); ok && strings.TrimSpace(value) != "" { - return strings.TrimSpace(value) + for _, key := range []string{"recharge_type", "provider", "channel"} { + if value, ok := decoded[key].(string); ok && strings.TrimSpace(value) != "" { + return normalizeInviteRechargeType(value) + } } } return invitedomain.RechargeTypeCoinSellerTransfer } +func normalizeInviteRechargeType(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "coin_seller", "coin_seller_transfer": + return invitedomain.RechargeTypeCoinSellerTransfer + case "google", "google_play", "googleplay": + return invitedomain.RechargeTypeGooglePlay + case "mifapay", "mifa_pay": + return invitedomain.RechargeTypeMifaPay + case "usdt", "usdt_trc20", "trc20": + return invitedomain.RechargeTypeUSDTTRC20 + default: + return strings.ToLower(strings.TrimSpace(value)) + } +} + func startMQConsumers(consumers []*rocketmqx.Consumer) error { for _, consumer := range consumers { if err := consumer.Start(); err != nil { diff --git a/services/user-service/internal/domain/invite/invite.go b/services/user-service/internal/domain/invite/invite.go index 136fa159..3d75f226 100644 --- a/services/user-service/internal/domain/invite/invite.go +++ b/services/user-service/internal/domain/invite/invite.go @@ -23,6 +23,14 @@ const ( RechargeEventWalletRecorded = "WalletRechargeRecorded" // RechargeTypeCoinSellerTransfer is the current recharge fact produced by wallet-service coin seller transfer. RechargeTypeCoinSellerTransfer = "coin_seller_transfer" + // RechargeTypeGooglePlay is the recharge fact produced by Google Play Billing. + RechargeTypeGooglePlay = "google_play" + // RechargeTypeGoogle is the legacy channel value carried by some Google wallet payloads. + RechargeTypeGoogle = "google" + // RechargeTypeMifaPay is the third-party checkout provider value for MiFaPay. + RechargeTypeMifaPay = "mifapay" + // RechargeTypeUSDTTRC20 is the third-party checkout provider value for USDT TRC20. + RechargeTypeUSDTTRC20 = "usdt_trc20" ) const ( @@ -50,6 +58,20 @@ type Binding struct { InviterUserID int64 } +// Attribution 是被邀请用户到邀请人的归因快照;活动服务只依赖这个只读事实,不直接读取邀请表。 +type Attribution struct { + Found bool + AppCode string + InvitedUserID int64 + InviterUserID int64 + InviteCode string + BoundAtMS int64 + ValidityStatus string + AccumulatedRechargeCoinAmount int64 + RequiredRechargeCoinAmount int64 + ValidAtMS int64 +} + // BindCommand describes one optional invitation attribution write. type BindCommand struct { AppCode string diff --git a/services/user-service/internal/service/user/moderation_test.go b/services/user-service/internal/service/user/moderation_test.go index 00291459..82a9d4d6 100644 --- a/services/user-service/internal/service/user/moderation_test.go +++ b/services/user-service/internal/service/user/moderation_test.go @@ -8,6 +8,7 @@ import ( roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/appcode" + invitedomain "hyapp/services/user-service/internal/domain/invite" userdomain "hyapp/services/user-service/internal/domain/user" ) @@ -117,6 +118,10 @@ func (r *fakeModerationRepository) SetUserStatus(_ context.Context, command User return UserStatusPersistenceResult{User: r.user, RevokedSessionIDs: append([]string{}, r.sessionIDs...)}, nil } +func (r *fakeModerationRepository) GetInviteAttribution(context.Context, int64) (invitedomain.Attribution, error) { + return invitedomain.Attribution{}, nil +} + func (r *fakeModerationRepository) CreateManagerUserBlock(_ context.Context, command ManagerUserBlockCommand, target userdomain.User) (ManagerUserBlock, error) { return ManagerUserBlock{ BlockID: "block-1", diff --git a/services/user-service/internal/service/user/service.go b/services/user-service/internal/service/user/service.go index d69bcc96..75c08e2d 100644 --- a/services/user-service/internal/service/user/service.go +++ b/services/user-service/internal/service/user/service.go @@ -8,6 +8,7 @@ import ( roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/idgen" hostdomain "hyapp/services/user-service/internal/domain/host" + invitedomain "hyapp/services/user-service/internal/domain/invite" userdomain "hyapp/services/user-service/internal/domain/user" ) @@ -44,6 +45,8 @@ type UserRepository interface { BusinessUserLookup(ctx context.Context, keyword string, numericKeyword bool, pageSize int32) ([]userdomain.BusinessUserLookupItem, error) // BatchGetUsers 批量查询用户主状态,缺失用户不应返回占位对象。 BatchGetUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.User, error) + // GetInviteAttribution 按被邀请用户读取邀请归因快照;无关系时返回 Found=false。 + GetInviteAttribution(ctx context.Context, invitedUserID int64) (invitedomain.Attribution, error) // ListUserIDs 按稳定 user_id 游标读取低频后台任务目标用户。 ListUserIDs(ctx context.Context, filter userdomain.UserIDPageFilter) ([]int64, error) // CreateUserWithIdentity 原子创建用户和默认短号。 diff --git a/services/user-service/internal/service/user/user.go b/services/user-service/internal/service/user/user.go index 3817e5d5..d56a4e0f 100644 --- a/services/user-service/internal/service/user/user.go +++ b/services/user-service/internal/service/user/user.go @@ -6,6 +6,7 @@ import ( "hyapp/pkg/appcode" "hyapp/pkg/xerr" + invitedomain "hyapp/services/user-service/internal/domain/invite" userdomain "hyapp/services/user-service/internal/domain/user" ) @@ -35,6 +36,17 @@ func (s *Service) GetUser(ctx context.Context, userID int64) (userdomain.User, e return s.refreshExpiredUser(ctx, user, "") } +// GetInviteAttribution 按被邀请用户读取邀请关系;没有关系时返回 Found=false,调用方按普通非邀请充值处理。 +func (s *Service) GetInviteAttribution(ctx context.Context, invitedUserID int64) (invitedomain.Attribution, error) { + if invitedUserID <= 0 { + return invitedomain.Attribution{}, xerr.New(xerr.InvalidArgument, "invited_user_id is required") + } + if s.userRepository == nil { + return invitedomain.Attribution{}, xerr.New(xerr.Unavailable, "user repository is not configured") + } + return s.userRepository.GetInviteAttribution(ctx, invitedUserID) +} + // GetMyProfileStats 读取我的页顶部统计计数;统计事实必须由专门 read model 维护。 func (s *Service) GetMyProfileStats(ctx context.Context, userID int64) (userdomain.ProfileStats, error) { if userID <= 0 { diff --git a/services/user-service/internal/storage/mysql/invite/repository.go b/services/user-service/internal/storage/mysql/invite/repository.go index 603f4332..4772d7ef 100644 --- a/services/user-service/internal/storage/mysql/invite/repository.go +++ b/services/user-service/internal/storage/mysql/invite/repository.go @@ -184,6 +184,51 @@ func (r *Repository) GetOverview(ctx context.Context, appCode string, userID int return overview, nil } +// GetAttribution 返回被邀请用户的邀请归因快照;没有绑定关系是正常业务分支,调用方可直接跳过奖励累计。 +func (r *Repository) GetAttribution(ctx context.Context, appCode string, invitedUserID int64) (invitedomain.Attribution, error) { + if r == nil || r.db == nil { + return invitedomain.Attribution{}, xerr.New(xerr.Unavailable, "invite repository is not configured") + } + appCode = appcode.Normalize(appCode) + if invitedUserID <= 0 { + return invitedomain.Attribution{}, xerr.New(xerr.InvalidArgument, "invited_user_id is required") + } + var item invitedomain.Attribution + var validAt sql.NullInt64 + err := r.db.QueryRowContext(ctx, ` + SELECT r.app_code, r.invited_user_id, r.inviter_user_id, r.invite_code, r.bound_at_ms, + COALESCE(p.status, ''), COALESCE(p.accumulated_recharge_coin_amount, 0), + COALESCE(p.required_recharge_coin_amount, 0), p.valid_at_ms + FROM user_invite_relations r + LEFT JOIN user_invite_recharge_progress p + ON p.app_code = r.app_code AND p.invited_user_id = r.invited_user_id + WHERE r.app_code = ? AND r.invited_user_id = ? + LIMIT 1`, + appCode, invitedUserID, + ).Scan( + &item.AppCode, + &item.InvitedUserID, + &item.InviterUserID, + &item.InviteCode, + &item.BoundAtMS, + &item.ValidityStatus, + &item.AccumulatedRechargeCoinAmount, + &item.RequiredRechargeCoinAmount, + &validAt, + ) + if err == sql.ErrNoRows { + return invitedomain.Attribution{AppCode: appCode, InvitedUserID: invitedUserID}, nil + } + if err != nil { + return invitedomain.Attribution{}, err + } + item.Found = true + if validAt.Valid { + item.ValidAtMS = validAt.Int64 + } + return item, nil +} + // ApplyRechargeEvent consumes one wallet recharge fact and advances invite validity idempotently. func (r *Repository) ApplyRechargeEvent(ctx context.Context, event invitedomain.RechargeEvent) (invitedomain.RechargeApplyResult, error) { if r == nil || r.db == nil { @@ -194,7 +239,7 @@ func (r *Repository) ApplyRechargeEvent(ctx context.Context, event invitedomain. event.EventType = strings.TrimSpace(event.EventType) event.TransactionID = strings.TrimSpace(event.TransactionID) event.AssetType = strings.TrimSpace(event.AssetType) - event.RechargeType = strings.TrimSpace(event.RechargeType) + event.RechargeType = normalizeRechargeType(event.RechargeType) if event.RechargeType == "" { event.RechargeType = invitedomain.RechargeTypeCoinSellerTransfer } @@ -481,16 +526,47 @@ func progressForInvitedForUpdate(ctx context.Context, tx *sql.Tx, appCode string func parseEligibleTypes(raw string) []string { var types []string if err := json.Unmarshal([]byte(raw), &types); err != nil || len(types) == 0 { - return []string{invitedomain.RechargeTypeCoinSellerTransfer} + return defaultEligibleRechargeTypes() } + out := make([]string, 0, len(types)) for index := range types { - types[index] = strings.TrimSpace(types[index]) + if rechargeType := normalizeRechargeType(types[index]); rechargeType != "" { + out = append(out, rechargeType) + } } - return types + if len(out) == 0 { + return defaultEligibleRechargeTypes() + } + return out } func eligibleRechargeType(types []string, eventType string) bool { - return slices.Contains(types, eventType) + return slices.Contains(types, normalizeRechargeType(eventType)) +} + +func defaultEligibleRechargeTypes() []string { + return []string{ + invitedomain.RechargeTypeCoinSellerTransfer, + invitedomain.RechargeTypeGooglePlay, + invitedomain.RechargeTypeGoogle, + invitedomain.RechargeTypeMifaPay, + invitedomain.RechargeTypeUSDTTRC20, + } +} + +func normalizeRechargeType(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "coin_seller", "coin_seller_transfer": + return invitedomain.RechargeTypeCoinSellerTransfer + case "google", "google_play", "googleplay": + return invitedomain.RechargeTypeGooglePlay + case "mifapay", "mifa_pay": + return invitedomain.RechargeTypeMifaPay + case "usdt", "usdt_trc20", "trc20": + return invitedomain.RechargeTypeUSDTTRC20 + default: + return strings.ToLower(strings.TrimSpace(value)) + } } func inviteEventID(eventType string, aggregateID int64) string { diff --git a/services/user-service/internal/storage/mysql/invite/repository_test.go b/services/user-service/internal/storage/mysql/invite/repository_test.go index 4c691b8b..e9d79b37 100644 --- a/services/user-service/internal/storage/mysql/invite/repository_test.go +++ b/services/user-service/internal/storage/mysql/invite/repository_test.go @@ -111,6 +111,65 @@ func TestInviteRelationCountersAndRechargeValidity(t *testing.T) { } } +func TestInviteRechargeValidityAcceptsAllRechargeSources(t *testing.T) { + repo := mysqltest.NewRepository(t) + ctx := context.Background() + createUser(t, repo, 11001, "164001", 1000) + inviter, err := repo.GetUser(ctx, 11001) + if err != nil { + t.Fatalf("get inviter failed: %v", err) + } + + sources := []string{ + invitedomain.RechargeTypeCoinSellerTransfer, + invitedomain.RechargeTypeGooglePlay, + invitedomain.RechargeTypeGoogle, + invitedomain.RechargeTypeMifaPay, + invitedomain.RechargeTypeUSDTTRC20, + } + for index, source := range sources { + invitedUserID := int64(11002 + index) + createUser(t, repo, invitedUserID, "16400"+string(rune('2'+index)), int64(1100+index)) + _, err = repo.CompleteOnboarding(ctx, userdomain.CompleteOnboardingCommand{ + AppCode: "lalu", + UserID: invitedUserID, + Username: "source-user", + Avatar: "https://cdn.example/source.png", + Gender: "female", + Country: "SG", + InviteCode: inviter.InviteOverview.MyInviteCode, + RequestID: "req-invite-source-" + source, + CompletedAtMs: int64(2000 + index), + }) + if err != nil { + t.Fatalf("complete onboarding source %s failed: %v", source, err) + } + result, err := repo.Repository.InviteRepository().ApplyRechargeEvent(ctx, invitedomain.RechargeEvent{ + AppCode: "lalu", + EventID: "evt-source-" + source, + EventType: invitedomain.RechargeEventWalletRecorded, + TransactionID: "tx-source-" + source, + CommandID: "cmd-source-" + source, + InvitedUserID: invitedUserID, + AssetType: "COIN", + RechargeCoinAmount: 80000, + RechargeType: source, + OccurredAtMs: int64(3000 + index), + }) + if err != nil || !result.Consumed || !result.Valid { + t.Fatalf("source %s should count as valid recharge: result=%+v err=%v", source, result, err) + } + } + + inviter, err = repo.GetUser(ctx, 11001) + if err != nil { + t.Fatalf("get inviter after source recharge failed: %v", err) + } + if inviter.InviteOverview.ValidInviteCount != int64(len(sources)) { + t.Fatalf("valid invite count mismatch: got=%d want=%d", inviter.InviteOverview.ValidInviteCount, len(sources)) + } +} + func createUser(t *testing.T, repo *mysqltest.Repository, userID int64, displayUserID string, nowMs int64) { t.Helper() if err := repo.CreateUserWithIdentity(context.Background(), userdomain.User{ diff --git a/services/user-service/internal/storage/mysql/user/repository.go b/services/user-service/internal/storage/mysql/user/repository.go index c5b55a6f..4a94d1ec 100644 --- a/services/user-service/internal/storage/mysql/user/repository.go +++ b/services/user-service/internal/storage/mysql/user/repository.go @@ -467,6 +467,14 @@ func (r *Repository) hydrateInviteOverview(ctx context.Context, user *userdomain return nil } +// GetInviteAttribution 按被邀请用户读取邀请归因;主用户仓储只做委托,邀请表结构仍由 invite 仓储维护。 +func (r *Repository) GetInviteAttribution(ctx context.Context, invitedUserID int64) (invitedomain.Attribution, error) { + if r == nil || r.db == nil { + return invitedomain.Attribution{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + return invitestorage.New(r.db).GetAttribution(ctx, appcode.FromContext(ctx), invitedUserID) +} + func invitestorageBindCommand(command userdomain.CompleteOnboardingCommand) invitedomain.BindCommand { return invitedomain.BindCommand{ AppCode: command.AppCode, diff --git a/services/user-service/internal/testutil/mysqltest/mysqltest.go b/services/user-service/internal/testutil/mysqltest/mysqltest.go index 21a66711..42342cf2 100644 --- a/services/user-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/user-service/internal/testutil/mysqltest/mysqltest.go @@ -71,6 +71,10 @@ func (r *Repository) GetMyProfileStats(ctx context.Context, userID int64) (userd return r.Repository.UserRepository().GetMyProfileStats(ctx, userID) } +func (r *Repository) GetInviteAttribution(ctx context.Context, invitedUserID int64) (invitedomain.Attribution, error) { + return r.Repository.UserRepository().GetInviteAttribution(ctx, invitedUserID) +} + func (r *Repository) RecordProfileVisit(ctx context.Context, visitorUserID int64, targetUserID int64, nowMs int64) (bool, userdomain.ProfileStats, error) { return r.Repository.UserRepository().RecordProfileVisit(ctx, visitorUserID, targetUserID, nowMs) } diff --git a/services/user-service/internal/transport/grpc/convert.go b/services/user-service/internal/transport/grpc/convert.go index 1e2e653b..36093782 100644 --- a/services/user-service/internal/transport/grpc/convert.go +++ b/services/user-service/internal/transport/grpc/convert.go @@ -138,6 +138,21 @@ func toProtoInviteBinding(binding invitedomain.Binding) *userv1.InviteBinding { } } +func toProtoInviteAttribution(attribution invitedomain.Attribution) *userv1.InviteAttribution { + return &userv1.InviteAttribution{ + Found: attribution.Found, + AppCode: attribution.AppCode, + InvitedUserId: attribution.InvitedUserID, + InviterUserId: attribution.InviterUserID, + InviteCode: attribution.InviteCode, + BoundAtMs: attribution.BoundAtMS, + ValidityStatus: attribution.ValidityStatus, + AccumulatedRechargeCoinAmount: attribution.AccumulatedRechargeCoinAmount, + RequiredRechargeCoinAmount: attribution.RequiredRechargeCoinAmount, + ValidAtMs: attribution.ValidAtMS, + } +} + func toProtoMicLifetimeStats(stats mictimedomain.LifetimeStats) *userv1.UserMicLifetimeStats { return &userv1.UserMicLifetimeStats{ AppCode: stats.AppCode, diff --git a/services/user-service/internal/transport/grpc/server.go b/services/user-service/internal/transport/grpc/server.go index 4d98e7d5..f9d18f17 100644 --- a/services/user-service/internal/transport/grpc/server.go +++ b/services/user-service/internal/transport/grpc/server.go @@ -246,6 +246,16 @@ func (s *Server) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*user return &userv1.GetUserResponse{User: toProtoUser(user)}, nil } +// GetInviteAttribution 返回被邀请用户的邀请人归因,供活动服务按充值事实累计邀请活动奖励。 +func (s *Server) GetInviteAttribution(ctx context.Context, req *userv1.GetInviteAttributionRequest) (*userv1.GetInviteAttributionResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + attribution, err := s.userSvc.GetInviteAttribution(ctx, req.GetInvitedUserId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.GetInviteAttributionResponse{Attribution: toProtoInviteAttribution(attribution)}, nil +} + // BusinessUserLookup 是带业务场景和能力校验的用户查询入口。 func (s *Server) BusinessUserLookup(ctx context.Context, req *userv1.BusinessUserLookupRequest) (*userv1.BusinessUserLookupResponse, error) { if s.hostSvc == nil { diff --git a/services/wallet-service/internal/domain/ledger/ledger.go b/services/wallet-service/internal/domain/ledger/ledger.go index d51275ea..50082010 100644 --- a/services/wallet-service/internal/domain/ledger/ledger.go +++ b/services/wallet-service/internal/domain/ledger/ledger.go @@ -963,6 +963,29 @@ type RoomTurnoverRewardReceipt struct { GrantedAtMS int64 } +// InviteActivityRewardCommand 是 activity-service 邀请活动领奖的 COIN 入账命令。 +type InviteActivityRewardCommand struct { + AppCode string + CommandID string + TargetUserID int64 + Amount int64 + ClaimID string + RewardType string + TierID int64 + TierCode string + CycleKey string + ReachedValue int64 + Reason string +} + +// InviteActivityRewardReceipt 是邀请活动奖励入账后的稳定回执。 +type InviteActivityRewardReceipt struct { + TransactionID string + Balance AssetBalance + Amount int64 + GrantedAtMS int64 +} + // GameCoinChangeCommand 是 game-service 对钱包发起的游戏专用金币改账命令。 type GameCoinChangeCommand struct { AppCode string diff --git a/services/wallet-service/internal/service/wallet/resource.go b/services/wallet-service/internal/service/wallet/resource.go index 947b80b4..4ef4d157 100644 --- a/services/wallet-service/internal/service/wallet/resource.go +++ b/services/wallet-service/internal/service/wallet/resource.go @@ -154,6 +154,15 @@ func (s *Service) SetGiftConfigStatus(ctx context.Context, command resourcedomai return s.repository.SetGiftConfigStatus(ctx, command) } +func (s *Service) DeleteGiftConfig(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) { + if s.repository == nil { + return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + ctx = appcode.WithContext(ctx, command.AppCode) + return s.repository.DeleteGiftConfig(ctx, command) +} + func (s *Service) UpsertGiftTypeConfig(ctx context.Context, command resourcedomain.GiftTypeConfigCommand) (resourcedomain.GiftTypeConfig, error) { if s.repository == nil { return resourcedomain.GiftTypeConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") diff --git a/services/wallet-service/internal/service/wallet/service.go b/services/wallet-service/internal/service/wallet/service.go index fe3c4e6e..cfecfd50 100644 --- a/services/wallet-service/internal/service/wallet/service.go +++ b/services/wallet-service/internal/service/wallet/service.go @@ -32,6 +32,7 @@ type Repository interface { CreditTaskReward(ctx context.Context, command ledger.TaskRewardCommand) (ledger.TaskRewardReceipt, error) CreditLuckyGiftReward(ctx context.Context, command ledger.LuckyGiftRewardCommand) (ledger.LuckyGiftRewardReceipt, error) CreditRoomTurnoverReward(ctx context.Context, command ledger.RoomTurnoverRewardCommand) (ledger.RoomTurnoverRewardReceipt, error) + CreditInviteActivityReward(ctx context.Context, command ledger.InviteActivityRewardCommand) (ledger.InviteActivityRewardReceipt, error) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) GetRedPacketConfig(ctx context.Context, appCode string) (ledger.RedPacketConfig, error) UpdateRedPacketConfig(ctx context.Context, config ledger.RedPacketConfig) (ledger.RedPacketConfig, error) @@ -89,6 +90,7 @@ type Repository interface { CreateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) SetGiftConfigStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) + DeleteGiftConfig(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) UpsertGiftTypeConfig(ctx context.Context, command resourcedomain.GiftTypeConfigCommand) (resourcedomain.GiftTypeConfig, error) GrantResource(ctx context.Context, command resourcedomain.GrantResourceCommand) (resourcedomain.ResourceGrant, error) GrantResourceGroup(ctx context.Context, command resourcedomain.GrantResourceGroupCommand) (resourcedomain.ResourceGrant, error) @@ -556,6 +558,32 @@ func (s *Service) CreditRoomTurnoverReward(ctx context.Context, command ledger.R return s.repository.CreditRoomTurnoverReward(ctx, command) } +// CreditInviteActivityReward 发放邀请活动金币奖励;达标和重复领取判断属于 activity-service,钱包只负责幂等入账。 +func (s *Service) CreditInviteActivityReward(ctx context.Context, command ledger.InviteActivityRewardCommand) (ledger.InviteActivityRewardReceipt, error) { + // 钱包入口再次校验最小账务语义,避免上游 bug 生成空 claim 或 0 金额交易。 + if command.CommandID == "" || command.TargetUserID <= 0 || command.Amount <= 0 || command.ClaimID == "" || command.TierID <= 0 || command.CycleKey == "" { + return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.InvalidArgument, "invite activity reward command is incomplete") + } + command.RewardType = strings.ToLower(strings.TrimSpace(command.RewardType)) + if command.RewardType != "recharge" && command.RewardType != "valid_invite" { + return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.InvalidArgument, "invite activity reward_type is invalid") + } + command.Reason = strings.TrimSpace(command.Reason) + if command.Reason == "" { + return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required") + } + if s.repository == nil { + return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + command.ClaimID = strings.TrimSpace(command.ClaimID) + command.TierCode = strings.TrimSpace(command.TierCode) + command.CycleKey = strings.TrimSpace(command.CycleKey) + ctx = appcode.WithContext(ctx, command.AppCode) + + return s.repository.CreditInviteActivityReward(ctx, command) +} + // ApplyGameCoinChange 是游戏平台唯一的 COIN 改账入口,方向由 op_type 控制。 func (s *Service) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) { if command.CommandID == "" || command.UserID <= 0 || command.PlatformCode == "" || command.GameID == "" || command.ProviderOrderID == "" { diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index bffdafc6..c2348451 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -2429,6 +2429,89 @@ func TestGiftConfigCoercesDiamondChargeToCoinAndEffectiveRange(t *testing.T) { } } +func TestDeleteGiftConfigKeepsResourceData(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + ctx := context.Background() + + giftResource, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{ + ResourceCode: "gift_delete_keep_resource", + ResourceType: resourcedomain.TypeGift, + Name: "Delete Keep Resource", + Status: resourcedomain.StatusActive, + Grantable: true, + GrantStrategy: resourcedomain.GrantStrategyIncreaseQuantity, + UsageScopes: []string{"gift"}, + OperatorUserID: 90001, + }) + if err != nil { + t.Fatalf("create gift resource failed: %v", err) + } + gift, err := svc.CreateGiftConfig(ctx, resourcedomain.GiftConfigCommand{ + GiftID: "delete-keep-resource", + ResourceID: giftResource.ResourceID, + Status: resourcedomain.StatusActive, + Name: "Delete Keep Resource", + PriceVersion: "v1", + CoinPrice: 9, + HeatValue: 9, + OperatorUserID: 90001, + RegionIDs: []int64{0}, + }) + if err != nil { + t.Fatalf("create gift config failed: %v", err) + } + if gift.ResourceID != giftResource.ResourceID { + t.Fatalf("gift resource id mismatch: %+v", gift) + } + if got := repository.CountRows("gift_configs", "gift_id = ?", "delete-keep-resource"); got != 1 { + t.Fatalf("expected gift config before delete, got %d", got) + } + if got := repository.CountRows("wallet_gift_prices", "gift_id = ?", "delete-keep-resource"); got != 1 { + t.Fatalf("expected gift price before delete, got %d", got) + } + if got := repository.CountRows("gift_config_regions", "gift_id = ?", "delete-keep-resource"); got != 1 { + t.Fatalf("expected gift region before delete, got %d", got) + } + + deleted, err := svc.DeleteGiftConfig(ctx, resourcedomain.StatusCommand{ + StringID: "delete-keep-resource", + OperatorUserID: 90002, + }) + if err != nil { + t.Fatalf("delete gift config failed: %v", err) + } + if deleted.GiftID != "delete-keep-resource" || deleted.ResourceID != giftResource.ResourceID { + t.Fatalf("deleted gift snapshot mismatch: %+v", deleted) + } + if got := repository.CountRows("gift_configs", "gift_id = ?", "delete-keep-resource"); got != 0 { + t.Fatalf("gift config should be deleted, got %d", got) + } + if got := repository.CountRows("wallet_gift_prices", "gift_id = ?", "delete-keep-resource"); got != 0 { + t.Fatalf("gift price should be deleted with gift config, got %d", got) + } + if got := repository.CountRows("gift_config_regions", "gift_id = ?", "delete-keep-resource"); got != 0 { + t.Fatalf("gift region should be deleted with gift config, got %d", got) + } + if got := repository.CountRows("resources", "resource_id = ?", giftResource.ResourceID); got != 1 { + t.Fatalf("gift resource must stay in resource list, got %d", got) + } + if _, err := svc.GetResource(ctx, "", giftResource.ResourceID); err != nil { + t.Fatalf("gift resource should still be readable after deleting gift config: %v", err) + } + items, total, err := svc.ListGiftConfigs(ctx, resourcedomain.ListGiftConfigsQuery{ + Keyword: "delete-keep-resource", + Page: 1, + PageSize: 10, + }) + if err != nil { + t.Fatalf("list gifts after delete failed: %v", err) + } + if total != 0 || len(items) != 0 { + t.Fatalf("deleted gift should not stay in gift list total=%d items=%+v", total, items) + } +} + func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) { repository := mysqltest.NewRepository(t) svc := walletservice.New(repository) diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index f2b864f6..8750c925 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -28,6 +28,7 @@ const ( bizTypeTaskReward = "task_reward" bizTypeLuckyGiftReward = "lucky_gift_reward" bizTypeRoomTurnoverReward = "room_turnover_reward" + bizTypeInviteActivityReward = "invite_activity_reward" bizTypeCoinSellerTransfer = "coin_seller_transfer" bizTypeCoinSellerStockPurchase = "coin_seller_stock_purchase" bizTypeCoinSellerCoinCompensation = "coin_seller_coin_compensation" @@ -1261,6 +1262,82 @@ func (r *Repository) CreditRoomTurnoverReward(ctx context.Context, command ledge return receiptFromRoomTurnoverRewardMetadata(transactionID, metadata), nil } +// CreditInviteActivityReward 在同一事务内完成邀请活动 COIN 奖励入账、交易分录和钱包 outbox。 +func (r *Repository) CreditInviteActivityReward(ctx context.Context, command ledger.InviteActivityRewardCommand) (ledger.InviteActivityRewardReceipt, error) { + if r == nil || r.db == nil { + return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + + requestHash := inviteActivityRewardRequestHash(command) + if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeInviteActivityReward); err != nil || exists { + if err != nil || !exists { + return ledger.InviteActivityRewardReceipt{}, err + } + // 活动服务领取接口可能因为网络超时重试;相同 command_id 和语义摘要直接回放原始钱包回执。 + return r.receiptForInviteActivityRewardTransaction(ctx, tx, txRow.TransactionID) + } + + nowMs := time.Now().UnixMilli() + account, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetCoin, true, nowMs) + if err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + transactionID := transactionID(command.AppCode, command.CommandID) + balanceAfter := account.AvailableAmount + command.Amount + metadata := inviteActivityRewardMetadata{ + AppCode: command.AppCode, + TargetUserID: command.TargetUserID, + AssetType: ledger.AssetCoin, + Amount: command.Amount, + ClaimID: command.ClaimID, + RewardType: command.RewardType, + TierID: command.TierID, + TierCode: command.TierCode, + CycleKey: command.CycleKey, + ReachedValue: command.ReachedValue, + Reason: command.Reason, + BalanceAfter: balanceAfter, + GrantedAtMS: nowMs, + } + if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeInviteActivityReward, requestHash, command.ClaimID, metadata, nowMs); err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: command.TargetUserID, + AssetType: ledger.AssetCoin, + AvailableDelta: command.Amount, + FrozenDelta: 0, + AvailableAfter: balanceAfter, + FrozenAfter: account.FrozenAmount, + CreatedAtMS: nowMs, + }); err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetCoin, command.Amount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs), + inviteActivityRewardCreditedEvent(transactionID, command.CommandID, metadata, nowMs), + }); err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + if err := tx.Commit(); err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + + return receiptFromInviteActivityRewardMetadata(transactionID, metadata), nil +} + // ApplyGameCoinChange 在同一事务内完成游戏 COIN 改账、分录和 outbox。 func (r *Repository) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) { if r == nil || r.db == nil { @@ -2926,6 +3003,22 @@ type roomTurnoverRewardMetadata struct { GrantedAtMS int64 `json:"granted_at_ms"` } +type inviteActivityRewardMetadata struct { + AppCode string `json:"app_code"` + TargetUserID int64 `json:"target_user_id"` + AssetType string `json:"asset_type"` + Amount int64 `json:"amount"` + ClaimID string `json:"claim_id"` + RewardType string `json:"reward_type"` + TierID int64 `json:"tier_id"` + TierCode string `json:"tier_code"` + CycleKey string `json:"cycle_key"` + ReachedValue int64 `json:"reached_value"` + Reason string `json:"reason"` + BalanceAfter int64 `json:"balance_after"` + GrantedAtMS int64 `json:"granted_at_ms"` +} + type gameCoinMetadata struct { AppCode string `json:"app_code"` UserID int64 `json:"user_id"` @@ -3153,6 +3246,38 @@ func receiptFromRoomTurnoverRewardMetadata(transactionID string, metadata roomTu } } +func (r *Repository) receiptForInviteActivityRewardTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.InviteActivityRewardReceipt, error) { + var metadataJSON string + if err := tx.QueryRowContext(ctx, + `SELECT COALESCE(CAST(metadata_json AS CHAR), '{}') + FROM wallet_transactions + WHERE app_code = ? AND transaction_id = ?`, + appcode.FromContext(ctx), + transactionID, + ).Scan(&metadataJSON); err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + var metadata inviteActivityRewardMetadata + if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil { + return ledger.InviteActivityRewardReceipt{}, err + } + return receiptFromInviteActivityRewardMetadata(transactionID, metadata), nil +} + +func receiptFromInviteActivityRewardMetadata(transactionID string, metadata inviteActivityRewardMetadata) ledger.InviteActivityRewardReceipt { + return ledger.InviteActivityRewardReceipt{ + TransactionID: transactionID, + Balance: ledger.AssetBalance{ + AppCode: metadata.AppCode, + UserID: metadata.TargetUserID, + AssetType: metadata.AssetType, + AvailableAmount: metadata.BalanceAfter, + }, + Amount: metadata.Amount, + GrantedAtMS: metadata.GrantedAtMS, + } +} + func (r *Repository) receiptForGameTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.GameCoinChangeReceipt, error) { var metadataJSON string if err := tx.QueryRowContext(ctx, @@ -3509,6 +3634,34 @@ func roomTurnoverRewardCreditedEvent(transactionID string, commandID string, met } } +func inviteActivityRewardCreditedEvent(transactionID string, commandID string, metadata inviteActivityRewardMetadata, nowMs int64) walletOutboxEvent { + return walletOutboxEvent{ + EventID: eventID(transactionID, "WalletInviteActivityRewardCredited", metadata.TargetUserID, ledger.AssetCoin), + EventType: "WalletInviteActivityRewardCredited", + TransactionID: transactionID, + CommandID: commandID, + UserID: metadata.TargetUserID, + AssetType: ledger.AssetCoin, + AvailableDelta: metadata.Amount, + FrozenDelta: 0, + Payload: map[string]any{ + "transaction_id": transactionID, + "command_id": commandID, + "user_id": metadata.TargetUserID, + "claim_id": metadata.ClaimID, + "reward_type": metadata.RewardType, + "tier_id": metadata.TierID, + "tier_code": metadata.TierCode, + "cycle_key": metadata.CycleKey, + "reached_value": metadata.ReachedValue, + "amount": metadata.Amount, + "reason": metadata.Reason, + "created_at_ms": nowMs, + }, + CreatedAtMS: nowMs, + } +} + func coinSellerStockCreditedEvent(transactionID string, commandID string, metadata coinSellerStockMetadata, nowMs int64) walletOutboxEvent { eventType := "WalletCoinSellerCoinCompensated" if metadata.StockType == ledger.StockTypeUSDTPurchase { @@ -3804,6 +3957,21 @@ func roomTurnoverRewardRequestHash(command ledger.RoomTurnoverRewardCommand) str )) } +func inviteActivityRewardRequestHash(command ledger.InviteActivityRewardCommand) string { + return stableHash(fmt.Sprintf("invite_activity_reward|%s|%d|%d|%s|%s|%d|%s|%s|%d|%s", + appcode.Normalize(command.AppCode), + command.TargetUserID, + command.Amount, + strings.TrimSpace(command.ClaimID), + strings.TrimSpace(command.RewardType), + command.TierID, + strings.TrimSpace(command.TierCode), + strings.TrimSpace(command.CycleKey), + command.ReachedValue, + strings.TrimSpace(command.Reason), + )) +} + func gameBizTypeAndDelta(opType string, coinAmount int64) (string, int64, error) { switch ledger.NormalizeGameOpType(opType) { case ledger.GameOpDebit: diff --git a/services/wallet-service/internal/storage/mysql/resource_repository.go b/services/wallet-service/internal/storage/mysql/resource_repository.go index ee22de3f..515e1ca7 100644 --- a/services/wallet-service/internal/storage/mysql/resource_repository.go +++ b/services/wallet-service/internal/storage/mysql/resource_repository.go @@ -712,6 +712,58 @@ func (r *Repository) SetGiftConfigStatus(ctx context.Context, command resourcedo return gift, nil } +func (r *Repository) DeleteGiftConfig(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) { + if r == nil || r.db == nil { + return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + if strings.TrimSpace(command.StringID) == "" { + return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift_id is required") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + giftID := strings.TrimSpace(command.StringID) + nowMs := time.Now().UnixMilli() + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return resourcedomain.GiftConfig{}, err + } + defer func() { _ = tx.Rollback() }() + + // 删除礼物配置前先锁定并读取完整快照:送礼链路也会锁 gift_configs, + // 因此后台删除和正在结算的同一礼物不会交叉执行成半删状态。 + gift, err := r.getGiftConfigForUpdateTx(ctx, tx, giftID) + if err != nil { + return resourcedomain.GiftConfig{}, err + } + if _, err := tx.ExecContext(ctx, `DELETE FROM gift_config_regions WHERE app_code = ? AND gift_id = ?`, appcode.FromContext(ctx), giftID); err != nil { + return resourcedomain.GiftConfig{}, err + } + if _, err := tx.ExecContext(ctx, `DELETE FROM wallet_gift_prices WHERE app_code = ? AND gift_id = ?`, appcode.FromContext(ctx), giftID); err != nil { + return resourcedomain.GiftConfig{}, err + } + result, err := tx.ExecContext(ctx, `DELETE FROM gift_configs WHERE app_code = ? AND gift_id = ?`, appcode.FromContext(ctx), giftID) + if err != nil { + return resourcedomain.GiftConfig{}, err + } + if affected, err := result.RowsAffected(); err != nil { + return resourcedomain.GiftConfig{}, err + } else if affected == 0 { + return resourcedomain.GiftConfig{}, xerr.New(xerr.NotFound, "gift config not found") + } + + deletedGift := gift + deletedGift.Status = "deleted" + // 只删除礼物列表配置和配置附属表,不删除 resources;资源素材仍留在资源列表,可重新绑定或复用。 + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + resourceOutboxEvent("GiftConfigChanged", fmt.Sprintf("gift:%s:delete:%d", giftID, nowMs), 0, gift.ResourceID, deletedGift, nowMs), + }); err != nil { + return resourcedomain.GiftConfig{}, err + } + if err := tx.Commit(); err != nil { + return resourcedomain.GiftConfig{}, err + } + return gift, nil +} + // UpsertGiftTypeConfig 更新礼物类型 tab 展示配置;type_code 是礼物表引用的稳定业务键。 func (r *Repository) UpsertGiftTypeConfig(ctx context.Context, command resourcedomain.GiftTypeConfigCommand) (resourcedomain.GiftTypeConfig, error) { if r == nil || r.db == nil { @@ -2078,6 +2130,31 @@ func (r *Repository) getGiftConfig(ctx context.Context, giftID string) (resource return gift, nil } +func (r *Repository) getGiftConfigForUpdateTx(ctx context.Context, tx *sql.Tx, giftID string) (resourcedomain.GiftConfig, error) { + row := tx.QueryRowContext(ctx, giftConfigSelectSQL()+`WHERE gc.app_code = ? AND gc.gift_id = ? FOR UPDATE`, appcode.FromContext(ctx), giftID) + gift, err := scanGiftConfig(row) + if errors.Is(err, sql.ErrNoRows) { + return resourcedomain.GiftConfig{}, xerr.New(xerr.NotFound, "gift config not found") + } + if err != nil { + return resourcedomain.GiftConfig{}, err + } + // 删除前快照需要带当前有效价格和投放区域,便于审计和 outbox 消费者判断被删除的是哪条配置。 + price, priceErr := r.latestGiftPrice(ctx, tx, gift.GiftID, time.Now().UnixMilli()) + if priceErr == nil { + gift.PriceVersion = price.PriceVersion + gift.ChargeAssetType = price.ChargeAssetType + gift.CoinPrice = price.CoinPrice + gift.GiftPointAmount = 0 + gift.HeatValue = price.CoinPrice + } + gift.RegionIDs, err = r.listGiftConfigRegionIDs(ctx, tx, gift.GiftID) + if err != nil { + return resourcedomain.GiftConfig{}, err + } + return gift, nil +} + func (r *Repository) getGiftTypeConfigTx(ctx context.Context, tx *sql.Tx, typeCode string) (resourcedomain.GiftTypeConfig, error) { row := tx.QueryRowContext(ctx, giftTypeConfigSelectSQL()+`WHERE app_code = ? AND type_code = ?`, appcode.FromContext(ctx), typeCode) item, err := scanGiftTypeConfig(row) diff --git a/services/wallet-service/internal/transport/grpc/resource.go b/services/wallet-service/internal/transport/grpc/resource.go index e40d1009..619a7b38 100644 --- a/services/wallet-service/internal/transport/grpc/resource.go +++ b/services/wallet-service/internal/transport/grpc/resource.go @@ -206,6 +206,18 @@ func (s *Server) SetGiftConfigStatus(ctx context.Context, req *walletv1.SetGiftC return &walletv1.GiftConfigResponse{Gift: giftConfigToProto(gift)}, nil } +func (s *Server) DeleteGiftConfig(ctx context.Context, req *walletv1.DeleteGiftConfigRequest) (*walletv1.GiftConfigResponse, error) { + gift, err := s.svc.DeleteGiftConfig(ctx, resourcedomain.StatusCommand{ + AppCode: req.GetAppCode(), + StringID: req.GetGiftId(), + OperatorUserID: req.GetOperatorUserId(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.GiftConfigResponse{Gift: giftConfigToProto(gift)}, nil +} + func (s *Server) GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest) (*walletv1.ResourceGrantResponse, error) { grant, err := s.svc.GrantResource(ctx, resourcedomain.GrantResourceCommand{ AppCode: req.GetAppCode(), diff --git a/services/wallet-service/internal/transport/grpc/server.go b/services/wallet-service/internal/transport/grpc/server.go index 3993b7fa..2c599b3a 100644 --- a/services/wallet-service/internal/transport/grpc/server.go +++ b/services/wallet-service/internal/transport/grpc/server.go @@ -1052,6 +1052,40 @@ func (s *Server) CreditRoomTurnoverReward(ctx context.Context, req *walletv1.Cre }, nil } +// CreditInviteActivityReward 处理 activity-service 邀请活动奖励金币入账。 +func (s *Server) CreditInviteActivityReward(ctx context.Context, req *walletv1.CreditInviteActivityRewardRequest) (*walletv1.CreditInviteActivityRewardResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + receipt, err := s.svc.CreditInviteActivityReward(ctx, ledger.InviteActivityRewardCommand{ + AppCode: req.GetAppCode(), + CommandID: req.GetCommandId(), + TargetUserID: req.GetTargetUserId(), + Amount: req.GetAmount(), + ClaimID: req.GetClaimId(), + RewardType: req.GetRewardType(), + TierID: req.GetTierId(), + TierCode: req.GetTierCode(), + CycleKey: req.GetCycleKey(), + ReachedValue: req.GetReachedValue(), + Reason: req.GetReason(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + + return &walletv1.CreditInviteActivityRewardResponse{ + TransactionId: receipt.TransactionID, + Amount: receipt.Amount, + GrantedAtMs: receipt.GrantedAtMS, + Balance: &walletv1.AssetBalance{ + AppCode: receipt.Balance.AppCode, + AssetType: receipt.Balance.AssetType, + AvailableAmount: receipt.Balance.AvailableAmount, + FrozenAmount: receipt.Balance.FrozenAmount, + Version: receipt.Balance.Version, + }, + }, nil +} + // ApplyGameCoinChange 处理 game-service 发起的游戏专用金币改账。 func (s *Server) ApplyGameCoinChange(ctx context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode()) From b55a01c0d6c754bf3c2e8814ccbc16c3e5829ac2 Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 17:58:48 +0800 Subject: [PATCH 20/28] fix room rocket in-room reward spread --- .../internal/room/service/room_rocket.go | 80 +++++++++++++++---- .../internal/room/service/room_rocket_test.go | 13 +++ 2 files changed, 79 insertions(+), 14 deletions(-) diff --git a/services/room-service/internal/room/service/room_rocket.go b/services/room-service/internal/room/service/room_rocket.go index 79c48aeb..f2851457 100644 --- a/services/room-service/internal/room/service/room_rocket.go +++ b/services/room-service/internal/room/service/room_rocket.go @@ -1053,13 +1053,6 @@ func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, cu }) return nil } - addRandomReward := func(role string, userID int64, pool []RoomRocketRewardItem) error { - item, ok := selectRoomRocketReward(launch.RocketID, role, userID, pool) - if !ok { - return nil - } - return addRewardItem(role, userID, item) - } addAllRewards := func(role string, userID int64, pool []RoomRocketRewardItem) error { for _, item := range pool { if err := addRewardItem(role, userID, item); err != nil { @@ -1069,9 +1062,14 @@ func (s *Service) settleRoomRocketRewards(ctx context.Context, now time.Time, cu return nil } + inRoomRewards := selectRoomRocketRewardsForUsers(launch.RocketID, roomRocketRewardInRoom, inRoomUserIDs, levelCfg.InRoomRewards) for _, userID := range inRoomUserIDs { - // 在房奖励是大面积发放池,每个在线用户只按权重抽一个奖项,避免房间人数放大单次火箭成本。 - if err := addRandomReward(roomRocketRewardInRoom, userID, levelCfg.InRoomRewards); err != nil { + // 在房奖励是大面积发放池,每个在线用户只拿一个奖项;同一枚火箭内按无放回周期分配,避免配置了金币和麦位声波时整房都落到同一个奖项。 + item, ok := inRoomRewards[userID] + if !ok { + continue + } + if err := addRewardItem(roomRocketRewardInRoom, userID, item); err != nil { return nil, err } } @@ -1111,7 +1109,56 @@ func (s *Service) grantRoomRocketReward(ctx context.Context, _ time.Time, curren return resp.GetGrant().GetGrantId(), nil } +func validRoomRocketRewardPool(pool []RoomRocketRewardItem) []RoomRocketRewardItem { + valid := make([]RoomRocketRewardItem, 0, len(pool)) + for _, item := range pool { + if item.Weight <= 0 || item.ResourceGroupID <= 0 { + continue + } + valid = append(valid, item) + } + return valid +} + +func selectRoomRocketRewardsForUsers(rocketID string, role string, userIDs []int64, pool []RoomRocketRewardItem) map[int64]RoomRocketRewardItem { + assignments := make(map[int64]RoomRocketRewardItem, len(userIDs)) + if len(userIDs) == 0 { + return assignments + } + basePool := validRoomRocketRewardPool(pool) + if len(basePool) == 0 { + return assignments + } + available := append([]RoomRocketRewardItem(nil), basePool...) + cycle := 0 + for index, userID := range userIDs { + if userID <= 0 { + continue + } + if len(available) == 0 { + // 一轮内每个配置项最多发一次;房间人数超过奖池大小时再开启下一轮,让大房间仍可继续按配置重复分配。 + available = append([]RoomRocketRewardItem(nil), basePool...) + cycle++ + } + selectedIndex, ok := selectRoomRocketRewardIndex(rocketID, role, userID, available, fmt.Sprintf("cycle:%d:index:%d", cycle, index)) + if !ok { + continue + } + assignments[userID] = available[selectedIndex] + available = append(available[:selectedIndex], available[selectedIndex+1:]...) + } + return assignments +} + func selectRoomRocketReward(rocketID string, role string, userID int64, pool []RoomRocketRewardItem) (RoomRocketRewardItem, bool) { + index, ok := selectRoomRocketRewardIndex(rocketID, role, userID, pool, "") + if !ok { + return RoomRocketRewardItem{}, false + } + return pool[index], true +} + +func selectRoomRocketRewardIndex(rocketID string, role string, userID int64, pool []RoomRocketRewardItem, salt string) (int, bool) { total := int64(0) for _, item := range pool { if item.Weight > 0 && item.ResourceGroupID > 0 { @@ -1119,21 +1166,26 @@ func selectRoomRocketReward(rocketID string, role string, userID int64, pool []R } } if total <= 0 { - return RoomRocketRewardItem{}, false + return -1, false } - hash := sha256.Sum256([]byte(fmt.Sprintf("%s|%s|%d", rocketID, role, userID))) + hash := sha256.Sum256([]byte(fmt.Sprintf("%s|%s|%d|%s", rocketID, role, userID, salt))) pick := int64(binary.BigEndian.Uint64(hash[:8]) % uint64(total)) cursor := int64(0) - for _, item := range pool { + fallback := -1 + for index, item := range pool { if item.Weight <= 0 || item.ResourceGroupID <= 0 { continue } + fallback = index cursor += item.Weight if pick < cursor { - return item, true + return index, true } } - return pool[len(pool)-1], true + if fallback >= 0 { + return fallback, true + } + return -1, false } func roomRocketGrantCommandID(rocketID string, role string, userID int64, rewardItemID string) string { diff --git a/services/room-service/internal/room/service/room_rocket_test.go b/services/room-service/internal/room/service/room_rocket_test.go index bbdaaa65..ce26cbde 100644 --- a/services/room-service/internal/room/service/room_rocket_test.go +++ b/services/room-service/internal/room/service/room_rocket_test.go @@ -566,6 +566,9 @@ func TestRoomRocketLaunchStacksAllSpecialPoolsAndRandomInRoomReward(t *testing.T if countRocketRewards(rewards, igniterID, "in_room") != 1 { t.Fatalf("top1 igniter must still receive exactly one random in-room reward: %+v", rewards) } + if countRocketRewardsByRoleAndGroup(rewards, "in_room", 1001) != 1 || countRocketRewardsByRoleAndGroup(rewards, "in_room", 1101) != 1 { + t.Fatalf("in-room rewards must spread across the configured pool before repeating an item: %+v", rewards) + } if countRocketRewards(rewards, igniterID, "top1") != 2 || countRocketRewards(rewards, igniterID, "igniter") != 2 { t.Fatalf("top1 igniter must receive every configured special reward: %+v", rewards) } @@ -828,6 +831,16 @@ func countRocketRewards(rewards []*roomv1.RoomRocketRewardGrant, userID int64, r return count } +func countRocketRewardsByRoleAndGroup(rewards []*roomv1.RoomRocketRewardGrant, role string, groupID int64) int { + count := 0 + for _, reward := range rewards { + if reward.GetRewardRole() == role && reward.GetResourceGroupId() == groupID { + count++ + } + } + return count +} + func rocketRewardUserIDs(rewards []*roomv1.RoomRocketRewardGrant) []int64 { userIDs := make([]int64, 0, len(rewards)) for _, reward := range rewards { From b025ac4dda6633739d535656d6c90d5c9329f76f Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 18:07:57 +0800 Subject: [PATCH 21/28] fix room rocket in-room reward limits --- api/proto/room/v1/room.pb.go | 18 ++++- api/proto/room/v1/room.proto | 2 + .../internal/integration/roomclient/client.go | 3 + .../internal/modules/roomrocket/service.go | 9 ++- .../modules/roomrocket/service_test.go | 16 ++++ .../internal/room/service/admin_rocket.go | 1 + .../internal/room/service/repository.go | 1 + .../internal/room/service/room_rocket.go | 36 ++++++--- .../internal/room/service/room_rocket_test.go | 76 +++++++++++++++++++ 9 files changed, 146 insertions(+), 16 deletions(-) diff --git a/api/proto/room/v1/room.pb.go b/api/proto/room/v1/room.pb.go index da656982..3c395406 100644 --- a/api/proto/room/v1/room.pb.go +++ b/api/proto/room/v1/room.pb.go @@ -774,8 +774,10 @@ type RoomRocketRewardItem struct { Weight int64 `protobuf:"varint,3,opt,name=weight,proto3" json:"weight,omitempty"` DisplayName string `protobuf:"bytes,4,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` IconUrl string `protobuf:"bytes,5,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // quantity 表示该奖项在一次火箭发射中最多发给多少个在房用户;为空或 0 时兼容旧配置按 1 份处理。 + Quantity int64 `protobuf:"varint,6,opt,name=quantity,proto3" json:"quantity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RoomRocketRewardItem) Reset() { @@ -843,6 +845,13 @@ func (x *RoomRocketRewardItem) GetIconUrl() string { return "" } +func (x *RoomRocketRewardItem) GetQuantity() int64 { + if x != nil { + return x.Quantity + } + return 0 +} + // RoomRocketLevel 暴露单个等级火箭的物料、阈值和三类奖励池。 type RoomRocketLevel struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -9520,13 +9529,14 @@ const file_proto_room_v1_room_proto_rawDesc = "" + "\rcreated_at_ms\x18\x11 \x01(\x03R\vcreatedAtMs\x122\n" + "\x15wallet_transaction_id\x18\x12 \x01(\tR\x13walletTransactionId\x12,\n" + "\x12coin_balance_after\x18\x13 \x01(\x03R\x10coinBalanceAfter\x12$\n" + - "\x0etarget_user_id\x18\x14 \x01(\x03R\ftargetUserId\"\xbe\x01\n" + + "\x0etarget_user_id\x18\x14 \x01(\x03R\ftargetUserId\"\xda\x01\n" + "\x14RoomRocketRewardItem\x12$\n" + "\x0ereward_item_id\x18\x01 \x01(\tR\frewardItemId\x12*\n" + "\x11resource_group_id\x18\x02 \x01(\x03R\x0fresourceGroupId\x12\x16\n" + "\x06weight\x18\x03 \x01(\x03R\x06weight\x12!\n" + "\fdisplay_name\x18\x04 \x01(\tR\vdisplayName\x12\x19\n" + - "\bicon_url\x18\x05 \x01(\tR\aiconUrl\"\xd3\x03\n" + + "\bicon_url\x18\x05 \x01(\tR\aiconUrl\x12\x1a\n" + + "\bquantity\x18\x06 \x01(\x03R\bquantity\"\xd3\x03\n" + "\x0fRoomRocketLevel\x12\x14\n" + "\x05level\x18\x01 \x01(\x05R\x05level\x12%\n" + "\x0efuel_threshold\x18\x02 \x01(\x03R\rfuelThreshold\x12\x1b\n" + diff --git a/api/proto/room/v1/room.proto b/api/proto/room/v1/room.proto index b2e90081..7fbe5f7c 100644 --- a/api/proto/room/v1/room.proto +++ b/api/proto/room/v1/room.proto @@ -112,6 +112,8 @@ message RoomRocketRewardItem { int64 weight = 3; string display_name = 4; string icon_url = 5; + // quantity 表示该奖项在一次火箭发射中最多发给多少个在房用户;为空或 0 时兼容旧配置按 1 份处理。 + int64 quantity = 6; } // RoomRocketLevel 暴露单个等级火箭的物料、阈值和三类奖励池。 diff --git a/server/admin/internal/integration/roomclient/client.go b/server/admin/internal/integration/roomclient/client.go index a136a463..fbd69775 100644 --- a/server/admin/internal/integration/roomclient/client.go +++ b/server/admin/internal/integration/roomclient/client.go @@ -127,6 +127,7 @@ type RoomRocketRewardItem struct { RewardItemID string ResourceGroupID int64 Weight int64 + Quantity int64 DisplayName string IconURL string } @@ -623,6 +624,7 @@ func roomRocketRewardsFromProto(input []*roomv1.RoomRocketRewardItem) []RoomRock RewardItemID: reward.GetRewardItemId(), ResourceGroupID: reward.GetResourceGroupId(), Weight: reward.GetWeight(), + Quantity: reward.GetQuantity(), DisplayName: reward.GetDisplayName(), IconURL: reward.GetIconUrl(), }) @@ -637,6 +639,7 @@ func roomRocketRewardsToProto(input []RoomRocketRewardItem) []*roomv1.RoomRocket RewardItemId: reward.RewardItemID, ResourceGroupId: reward.ResourceGroupID, Weight: reward.Weight, + Quantity: reward.Quantity, DisplayName: reward.DisplayName, IconUrl: reward.IconURL, }) diff --git a/server/admin/internal/modules/roomrocket/service.go b/server/admin/internal/modules/roomrocket/service.go index ecb66b32..d96c9d8a 100644 --- a/server/admin/internal/modules/roomrocket/service.go +++ b/server/admin/internal/modules/roomrocket/service.go @@ -63,6 +63,7 @@ type RoomRocketRewardItem struct { RewardItemID string `json:"rewardItemId"` ResourceGroupID int64 `json:"resourceGroupId"` Weight int64 `json:"weight"` + Quantity int64 `json:"quantity"` DisplayName string `json:"displayName"` IconURL string `json:"iconUrl"` } @@ -246,7 +247,7 @@ func normalizeRewardPool(level int32, role string, rewards []RoomRocketRewardIte reward.RewardItemID = strings.TrimSpace(reward.RewardItemID) reward.DisplayName = strings.TrimSpace(reward.DisplayName) reward.IconURL = strings.TrimSpace(reward.IconURL) - if reward.RewardItemID == "" && reward.ResourceGroupID == 0 && reward.Weight == 0 && reward.DisplayName == "" && reward.IconURL == "" { + if reward.RewardItemID == "" && reward.ResourceGroupID == 0 && reward.Weight == 0 && reward.Quantity == 0 && reward.DisplayName == "" && reward.IconURL == "" { continue } if reward.ResourceGroupID <= 0 { @@ -255,6 +256,10 @@ func normalizeRewardPool(level int32, role string, rewards []RoomRocketRewardIte if reward.Weight <= 0 { return nil, fmt.Errorf("%w: 第 %d 级奖励权重必须大于 0", ErrInvalidArgument, level) } + if reward.Quantity <= 0 { + // 历史配置没有 quantity 字段;后台读写都按 1 份兼容,避免旧配置打开页面即失败。 + reward.Quantity = 1 + } if reward.RewardItemID == "" { reward.RewardItemID = fmt.Sprintf("level_%d_%s_%d", level, role, index+1) } @@ -382,6 +387,7 @@ func rewardsFromClient(input []roomclient.RoomRocketRewardItem) []RoomRocketRewa RewardItemID: reward.RewardItemID, ResourceGroupID: reward.ResourceGroupID, Weight: reward.Weight, + Quantity: reward.Quantity, DisplayName: reward.DisplayName, IconURL: reward.IconURL, }) @@ -396,6 +402,7 @@ func rewardsToClient(input []RoomRocketRewardItem) []roomclient.RoomRocketReward RewardItemID: reward.RewardItemID, ResourceGroupID: reward.ResourceGroupID, Weight: reward.Weight, + Quantity: reward.Quantity, DisplayName: reward.DisplayName, IconURL: reward.IconURL, }) diff --git a/server/admin/internal/modules/roomrocket/service_test.go b/server/admin/internal/modules/roomrocket/service_test.go index e2f3da1e..666c3db1 100644 --- a/server/admin/internal/modules/roomrocket/service_test.go +++ b/server/admin/internal/modules/roomrocket/service_test.go @@ -42,6 +42,22 @@ func TestNormalizeConfigDefaultsAndSortsLevels(t *testing.T) { } } +func TestNormalizeConfigKeepsRewardQuantityAndDefaultsOldItems(t *testing.T) { + config := defaultConfig("lalu") + config.Levels[0].InRoomRewards = []RoomRocketRewardItem{ + {RewardItemID: "coin", ResourceGroupID: 1001, Weight: 100, Quantity: 2}, + {RewardItemID: "mic-wave", ResourceGroupID: 1002, Weight: 100}, + } + normalized, err := normalizeConfig(config) + if err != nil { + t.Fatalf("normalizeConfig failed: %v", err) + } + rewards := normalized.Levels[0].InRoomRewards + if rewards[0].Quantity != 2 || rewards[1].Quantity != 1 { + t.Fatalf("reward quantity must be preserved and defaulted: %+v", rewards) + } +} + func TestDefaultConfigKeepsOperationalDefaults(t *testing.T) { config, err := normalizeConfig(defaultConfig("lalu")) if err != nil { diff --git a/services/room-service/internal/room/service/admin_rocket.go b/services/room-service/internal/room/service/admin_rocket.go index 22256d8f..a52f686c 100644 --- a/services/room-service/internal/room/service/admin_rocket.go +++ b/services/room-service/internal/room/service/admin_rocket.go @@ -113,6 +113,7 @@ func roomRocketRewardsFromProto(input []*roomv1.RoomRocketRewardItem) []RoomRock RewardItemID: reward.GetRewardItemId(), ResourceGroupID: reward.GetResourceGroupId(), Weight: reward.GetWeight(), + Quantity: reward.GetQuantity(), DisplayName: reward.GetDisplayName(), IconURL: reward.GetIconUrl(), }) diff --git a/services/room-service/internal/room/service/repository.go b/services/room-service/internal/room/service/repository.go index 8adf9395..8c18f4cf 100644 --- a/services/room-service/internal/room/service/repository.go +++ b/services/room-service/internal/room/service/repository.go @@ -294,6 +294,7 @@ type RoomRocketRewardItem struct { RewardItemID string `json:"rewardItemId"` ResourceGroupID int64 `json:"resourceGroupId"` Weight int64 `json:"weight"` + Quantity int64 `json:"quantity"` DisplayName string `json:"displayName"` IconURL string `json:"iconUrl"` } diff --git a/services/room-service/internal/room/service/room_rocket.go b/services/room-service/internal/room/service/room_rocket.go index f2851457..696b9803 100644 --- a/services/room-service/internal/room/service/room_rocket.go +++ b/services/room-service/internal/room/service/room_rocket.go @@ -195,6 +195,10 @@ func normalizeRoomRocketRewardPool(input []RoomRocketRewardItem) []RoomRocketRew // 后台保存层会拒绝非法奖励;运行时跳过历史脏项,避免一个奖励池配置污染送礼主链路。 continue } + if reward.Quantity <= 0 { + // 旧配置没有 quantity 字段;按 1 份兼容,避免上线后历史火箭奖励池被当成 0 份直接停发。 + reward.Quantity = 1 + } out = append(out, reward) } return out @@ -340,6 +344,7 @@ func roomRocketRewardsToProto(input []RoomRocketRewardItem) []*roomv1.RoomRocket RewardItemId: reward.RewardItemID, ResourceGroupId: reward.ResourceGroupID, Weight: reward.Weight, + Quantity: reward.Quantity, DisplayName: reward.DisplayName, IconUrl: reward.IconURL, }) @@ -1115,6 +1120,9 @@ func validRoomRocketRewardPool(pool []RoomRocketRewardItem) []RoomRocketRewardIt if item.Weight <= 0 || item.ResourceGroupID <= 0 { continue } + if item.Quantity <= 0 { + item.Quantity = 1 + } valid = append(valid, item) } return valid @@ -1129,18 +1137,16 @@ func selectRoomRocketRewardsForUsers(rocketID string, role string, userIDs []int if len(basePool) == 0 { return assignments } - available := append([]RoomRocketRewardItem(nil), basePool...) - cycle := 0 + available := expandRoomRocketRewardSlots(basePool) for index, userID := range userIDs { if userID <= 0 { continue } if len(available) == 0 { - // 一轮内每个配置项最多发一次;房间人数超过奖池大小时再开启下一轮,让大房间仍可继续按配置重复分配。 - available = append([]RoomRocketRewardItem(nil), basePool...) - cycle++ + // 在房奖励数量是单次发射的硬上限;配置总数发完后,其余在线用户本次拿空,不生成 grant 和 IM 弹窗。 + break } - selectedIndex, ok := selectRoomRocketRewardIndex(rocketID, role, userID, available, fmt.Sprintf("cycle:%d:index:%d", cycle, index)) + selectedIndex, ok := selectRoomRocketRewardIndex(rocketID, role, userID, available, fmt.Sprintf("limited:index:%d", index)) if !ok { continue } @@ -1150,12 +1156,20 @@ func selectRoomRocketRewardsForUsers(rocketID string, role string, userIDs []int return assignments } -func selectRoomRocketReward(rocketID string, role string, userID int64, pool []RoomRocketRewardItem) (RoomRocketRewardItem, bool) { - index, ok := selectRoomRocketRewardIndex(rocketID, role, userID, pool, "") - if !ok { - return RoomRocketRewardItem{}, false +func expandRoomRocketRewardSlots(pool []RoomRocketRewardItem) []RoomRocketRewardItem { + total := 0 + for _, item := range pool { + if item.Quantity > 0 { + total += int(item.Quantity) + } } - return pool[index], true + slots := make([]RoomRocketRewardItem, 0, total) + for _, item := range pool { + for count := int64(0); count < item.Quantity; count++ { + slots = append(slots, item) + } + } + return slots } func selectRoomRocketRewardIndex(rocketID string, role string, userID int64, pool []RoomRocketRewardItem, salt string) (int, bool) { diff --git a/services/room-service/internal/room/service/room_rocket_test.go b/services/room-service/internal/room/service/room_rocket_test.go index ce26cbde..d99272f0 100644 --- a/services/room-service/internal/room/service/room_rocket_test.go +++ b/services/room-service/internal/room/service/room_rocket_test.go @@ -578,6 +578,72 @@ func TestRoomRocketLaunchStacksAllSpecialPoolsAndRandomInRoomReward(t *testing.T } } +func TestRoomRocketInRoomRewardQuantityCapsWinners(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + now := &fixedRoomRocketClock{now: time.Date(2026, 5, 20, 14, 0, 0, 0, time.UTC)} + wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{ + {BillingReceiptId: "receipt-quantity", GiftPointAdded: 100, HeatValue: 100, GiftTypeCode: "normal"}, + }} + svc := newRocketTestService(t, repository, wallet, now) + config := rocketConfigForTest(100, 1_000) + config.Levels[0].InRoomRewards = []roomservice.RoomRocketRewardItem{ + {RewardItemID: "level-1-in-room-coin", ResourceGroupID: 1001, Weight: 1, Quantity: 2, DisplayName: "Coin"}, + {RewardItemID: "level-1-in-room-mic", ResourceGroupID: 1101, Weight: 1, Quantity: 3, DisplayName: "Mic Wave"}, + } + config.Levels[0].Top1Rewards = nil + config.Levels[0].IgniterRewards = nil + writeRocketConfig(t, ctx, repository, config) + + roomID := "room-rocket-quantity" + ownerID := int64(501) + igniterID := int64(502) + createRocketRoom(t, ctx, svc, roomID, ownerID, 9004) + for userID := igniterID; userID <= 507; userID++ { + joinRocketRoom(t, ctx, svc, roomID, userID) + } + + if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{ + Meta: rocketMeta(roomID, igniterID, "quantity"), + TargetType: "user", + TargetUserId: ownerID, + GiftId: "gift-quantity", + GiftCount: 1, + }); err != nil { + t.Fatalf("ignite quantity rocket failed: %v", err) + } + + now.now = now.now.Add(1100 * time.Millisecond) + if err := svc.SweepRoomRocketLaunchings(ctx); err != nil { + t.Fatalf("sweep quantity room rocket launches failed: %v", err) + } + + info, err := svc.GetRoomRocket(ctx, &roomv1.GetRoomRocketRequest{ + Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID}, + RoomId: roomID, + ViewerUserId: ownerID, + }) + if err != nil { + t.Fatalf("get quantity rocket after launch failed: %v", err) + } + rewards := info.GetRocket().GetState().GetLastRewards() + if countRocketRewardsByRole(rewards, "in_room") != 5 { + t.Fatalf("in-room rewards must stop at configured quantity 5, got %+v", rewards) + } + if countRocketRewardsByRoleAndGroup(rewards, "in_room", 1001) != 2 { + t.Fatalf("coin resource group must be granted exactly 2 times: %+v", rewards) + } + if countRocketRewardsByRoleAndGroup(rewards, "in_room", 1101) != 3 { + t.Fatalf("mic wave resource group must be granted exactly 3 times: %+v", rewards) + } + if len(wallet.grants) != 5 { + t.Fatalf("wallet must only receive 5 in-room grant calls, got %+v", wallet.grants) + } + if len(rocketRewardUserIDs(rewards)) != 5 { + t.Fatalf("only 5 users should have reward rows; remaining online users receive empty: %+v", rewards) + } +} + func TestRoomRocketUTCResetCancelsCrossDayIgnited(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) @@ -841,6 +907,16 @@ func countRocketRewardsByRoleAndGroup(rewards []*roomv1.RoomRocketRewardGrant, r return count } +func countRocketRewardsByRole(rewards []*roomv1.RoomRocketRewardGrant, role string) int { + count := 0 + for _, reward := range rewards { + if reward.GetRewardRole() == role { + count++ + } + } + return count +} + func rocketRewardUserIDs(rewards []*roomv1.RoomRocketRewardGrant) []int64 { userIDs := make([]int64, 0, len(rewards)) for _, reward := range rewards { From f4eee4db4cbd2033832380f02888291771526393 Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 18:14:04 +0800 Subject: [PATCH 22/28] =?UTF-8?q?=E9=82=80=E8=AF=B7=E6=B4=BB=E5=8A=A8?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/proto/activity/v1/activity.pb.go | 76 ++-- api/proto/activity/v1/activity.proto | 4 + api/proto/game/v1/game.pb.go | 104 +++++- api/proto/game/v1/game.proto | 9 + docs/H5自研猜拳接入接口.md | 339 ++++++++++++++++++ .../modules/inviteactivityreward/handler.go | 44 ++- .../mysql/initdb/001_activity_service.sql | 14 +- .../migrations/002_invite_activity_reward.sql | 14 +- .../003_invite_activity_per_invite_reward.sql | 25 ++ services/activity-service/internal/app/mq.go | 9 +- .../internal/app/user_events.go | 30 ++ .../domain/inviteactivity/invite_activity.go | 34 +- .../service/inviteactivity/service.go | 171 +++++++-- .../service/inviteactivity/service_test.go | 51 +++ .../mysql/invite_activity_reward_migration.go | 33 +- .../invite_activity_reward_repository.go | 79 +++- .../grpc/invite_activity_reward_server.go | 22 +- .../tools/invite-activity-local-flow/main.go | 63 +++- .../deploy/mysql/initdb/001_game_service.sql | 11 + .../game-service/internal/domain/dice/dice.go | 135 ++++++- .../internal/service/dice/service.go | 287 +++++++++++++-- .../internal/service/dice/service_test.go | 309 +++++++++++++++- .../mysql/dice_matchmaking_repository.go | 154 ++++---- .../internal/storage/mysql/dice_repository.go | 22 +- .../internal/storage/mysql/repository.go | 28 +- .../internal/transport/grpc/server.go | 39 +- .../invite_activity_reward_handler.go | 22 +- .../transport/http/game_handler_test.go | 180 +++++++++- .../http/gameapi/dice_profile_view.go | 40 +++ .../transport/http/gameapi/game_handler.go | 176 +++++++-- .../transport/http/gameapi/game_view.go | 4 + .../internal/service/wallet/service.go | 2 +- 32 files changed, 2199 insertions(+), 331 deletions(-) create mode 100644 docs/H5自研猜拳接入接口.md create mode 100644 services/activity-service/deploy/mysql/migrations/003_invite_activity_per_invite_reward.sql diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index d1db570e..c5d9190f 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -7329,15 +7329,17 @@ func (x *InviteActivityRewardTier) GetUpdatedAtMs() int64 { // InviteActivityRewardConfig 是当前 App 的邀请活动配置;关闭时保留档位但 App 不允许领取。 type InviteActivityRewardConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` - Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` - Tiers []*InviteActivityRewardTier `protobuf:"bytes,3,rep,name=tiers,proto3" json:"tiers,omitempty"` - UpdatedByAdminId int64 `protobuf:"varint,4,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"` - CreatedAtMs int64 `protobuf:"varint,5,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` - UpdatedAtMs int64 `protobuf:"varint,6,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Tiers []*InviteActivityRewardTier `protobuf:"bytes,3,rep,name=tiers,proto3" json:"tiers,omitempty"` + UpdatedByAdminId int64 `protobuf:"varint,4,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"` + CreatedAtMs int64 `protobuf:"varint,5,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,6,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + PerInviteInviterRewardCoinAmount int64 `protobuf:"varint,7,opt,name=per_invite_inviter_reward_coin_amount,json=perInviteInviterRewardCoinAmount,proto3" json:"per_invite_inviter_reward_coin_amount,omitempty"` + PerInviteInviteeRewardCoinAmount int64 `protobuf:"varint,8,opt,name=per_invite_invitee_reward_coin_amount,json=perInviteInviteeRewardCoinAmount,proto3" json:"per_invite_invitee_reward_coin_amount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *InviteActivityRewardConfig) Reset() { @@ -7412,6 +7414,20 @@ func (x *InviteActivityRewardConfig) GetUpdatedAtMs() int64 { return 0 } +func (x *InviteActivityRewardConfig) GetPerInviteInviterRewardCoinAmount() int64 { + if x != nil { + return x.PerInviteInviterRewardCoinAmount + } + return 0 +} + +func (x *InviteActivityRewardConfig) GetPerInviteInviteeRewardCoinAmount() int64 { + if x != nil { + return x.PerInviteInviteeRewardCoinAmount + } + return 0 +} + // InviteActivityRewardProgress 是邀请人在 UTC 自然月内的充值和有效邀请累计。 type InviteActivityRewardProgress struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -8313,13 +8329,15 @@ func (x *GetInviteActivityRewardConfigResponse) GetConfig() *InviteActivityRewar } type UpdateInviteActivityRewardConfigRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` - Tiers []*InviteActivityRewardTier `protobuf:"bytes,3,rep,name=tiers,proto3" json:"tiers,omitempty"` - OperatorAdminId int64 `protobuf:"varint,4,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Tiers []*InviteActivityRewardTier `protobuf:"bytes,3,rep,name=tiers,proto3" json:"tiers,omitempty"` + OperatorAdminId int64 `protobuf:"varint,4,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"` + PerInviteInviterRewardCoinAmount int64 `protobuf:"varint,5,opt,name=per_invite_inviter_reward_coin_amount,json=perInviteInviterRewardCoinAmount,proto3" json:"per_invite_inviter_reward_coin_amount,omitempty"` + PerInviteInviteeRewardCoinAmount int64 `protobuf:"varint,6,opt,name=per_invite_invitee_reward_coin_amount,json=perInviteInviteeRewardCoinAmount,proto3" json:"per_invite_invitee_reward_coin_amount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpdateInviteActivityRewardConfigRequest) Reset() { @@ -8380,6 +8398,20 @@ func (x *UpdateInviteActivityRewardConfigRequest) GetOperatorAdminId() int64 { return 0 } +func (x *UpdateInviteActivityRewardConfigRequest) GetPerInviteInviterRewardCoinAmount() int64 { + if x != nil { + return x.PerInviteInviterRewardCoinAmount + } + return 0 +} + +func (x *UpdateInviteActivityRewardConfigRequest) GetPerInviteInviteeRewardCoinAmount() int64 { + if x != nil { + return x.PerInviteInviteeRewardCoinAmount + } + return 0 +} + type UpdateInviteActivityRewardConfigResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Config *InviteActivityRewardConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` @@ -18783,14 +18815,16 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "sort_order\x18\t \x01(\x05R\tsortOrder\x12\"\n" + "\rcreated_at_ms\x18\n" + " \x01(\x03R\vcreatedAtMs\x12\"\n" + - "\rupdated_at_ms\x18\v \x01(\x03R\vupdatedAtMs\"\x8b\x02\n" + + "\rupdated_at_ms\x18\v \x01(\x03R\vupdatedAtMs\"\xad\x03\n" + "\x1aInviteActivityRewardConfig\x12\x19\n" + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x18\n" + "\aenabled\x18\x02 \x01(\bR\aenabled\x12A\n" + "\x05tiers\x18\x03 \x03(\v2+.hyapp.activity.v1.InviteActivityRewardTierR\x05tiers\x12-\n" + "\x13updated_by_admin_id\x18\x04 \x01(\x03R\x10updatedByAdminId\x12\"\n" + "\rcreated_at_ms\x18\x05 \x01(\x03R\vcreatedAtMs\x12\"\n" + - "\rupdated_at_ms\x18\x06 \x01(\x03R\vupdatedAtMs\"\xbb\x02\n" + + "\rupdated_at_ms\x18\x06 \x01(\x03R\vupdatedAtMs\x12O\n" + + "%per_invite_inviter_reward_coin_amount\x18\a \x01(\x03R perInviteInviterRewardCoinAmount\x12O\n" + + "%per_invite_invitee_reward_coin_amount\x18\b \x01(\x03R perInviteInviteeRewardCoinAmount\"\xbb\x02\n" + "\x1cInviteActivityRewardProgress\x12\x19\n" + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x1b\n" + "\tcycle_key\x18\x02 \x01(\tR\bcycleKey\x12\x17\n" + @@ -18865,12 +18899,14 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "$GetInviteActivityRewardConfigRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\"n\n" + "%GetInviteActivityRewardConfigResponse\x12E\n" + - "\x06config\x18\x01 \x01(\v2-.hyapp.activity.v1.InviteActivityRewardConfigR\x06config\"\xe6\x01\n" + + "\x06config\x18\x01 \x01(\v2-.hyapp.activity.v1.InviteActivityRewardConfigR\x06config\"\x88\x03\n" + "'UpdateInviteActivityRewardConfigRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x18\n" + "\aenabled\x18\x02 \x01(\bR\aenabled\x12A\n" + "\x05tiers\x18\x03 \x03(\v2+.hyapp.activity.v1.InviteActivityRewardTierR\x05tiers\x12*\n" + - "\x11operator_admin_id\x18\x04 \x01(\x03R\x0foperatorAdminId\"q\n" + + "\x11operator_admin_id\x18\x04 \x01(\x03R\x0foperatorAdminId\x12O\n" + + "%per_invite_inviter_reward_coin_amount\x18\x05 \x01(\x03R perInviteInviterRewardCoinAmount\x12O\n" + + "%per_invite_invitee_reward_coin_amount\x18\x06 \x01(\x03R perInviteInviteeRewardCoinAmount\"q\n" + "(UpdateInviteActivityRewardConfigResponse\x12E\n" + "\x06config\x18\x01 \x01(\v2-.hyapp.activity.v1.InviteActivityRewardConfigR\x06config\"\xfb\x01\n" + "%ListInviteActivityRewardClaimsRequest\x122\n" + diff --git a/api/proto/activity/v1/activity.proto b/api/proto/activity/v1/activity.proto index 735afeed..b16f974f 100644 --- a/api/proto/activity/v1/activity.proto +++ b/api/proto/activity/v1/activity.proto @@ -839,6 +839,8 @@ message InviteActivityRewardConfig { int64 updated_by_admin_id = 4; int64 created_at_ms = 5; int64 updated_at_ms = 6; + int64 per_invite_inviter_reward_coin_amount = 7; + int64 per_invite_invitee_reward_coin_amount = 8; } // InviteActivityRewardProgress 是邀请人在 UTC 自然月内的充值和有效邀请累计。 @@ -944,6 +946,8 @@ message UpdateInviteActivityRewardConfigRequest { bool enabled = 2; repeated InviteActivityRewardTier tiers = 3; int64 operator_admin_id = 4; + int64 per_invite_inviter_reward_coin_amount = 5; + int64 per_invite_invitee_reward_coin_amount = 6; } message UpdateInviteActivityRewardConfigResponse { diff --git a/api/proto/game/v1/game.pb.go b/api/proto/game/v1/game.pb.go index 635a7818..d282dc0c 100644 --- a/api/proto/game/v1/game.pb.go +++ b/api/proto/game/v1/game.pb.go @@ -1363,6 +1363,7 @@ type DiceParticipant struct { UpdatedAtMs int64 `protobuf:"varint,10,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` ParticipantType string `protobuf:"bytes,11,opt,name=participant_type,json=participantType,proto3" json:"participant_type,omitempty"` IsRobot bool `protobuf:"varint,12,opt,name=is_robot,json=isRobot,proto3" json:"is_robot,omitempty"` + RpsGesture string `protobuf:"bytes,13,opt,name=rps_gesture,json=rpsGesture,proto3" json:"rps_gesture,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1481,6 +1482,13 @@ func (x *DiceParticipant) GetIsRobot() bool { return false } +func (x *DiceParticipant) GetRpsGesture() string { + if x != nil { + return x.RpsGesture + } + return "" +} + type DiceMatch struct { state protoimpl.MessageState `protogen:"open.v1"` AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` @@ -3179,6 +3187,7 @@ type CreateDiceMatchRequest struct { StakeCoin int64 `protobuf:"varint,6,opt,name=stake_coin,json=stakeCoin,proto3" json:"stake_coin,omitempty"` MinPlayers int32 `protobuf:"varint,7,opt,name=min_players,json=minPlayers,proto3" json:"min_players,omitempty"` MaxPlayers int32 `protobuf:"varint,8,opt,name=max_players,json=maxPlayers,proto3" json:"max_players,omitempty"` + RpsGesture string `protobuf:"bytes,9,opt,name=rps_gesture,json=rpsGesture,proto3" json:"rps_gesture,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3269,11 +3278,20 @@ func (x *CreateDiceMatchRequest) GetMaxPlayers() int32 { return 0 } +func (x *CreateDiceMatchRequest) GetRpsGesture() string { + if x != nil { + return x.RpsGesture + } + return "" +} + type JoinDiceMatchRequest 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"` MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + GameId string `protobuf:"bytes,4,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + RpsGesture string `protobuf:"bytes,5,opt,name=rps_gesture,json=rpsGesture,proto3" json:"rps_gesture,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3329,11 +3347,26 @@ func (x *JoinDiceMatchRequest) GetMatchId() string { return "" } +func (x *JoinDiceMatchRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *JoinDiceMatchRequest) GetRpsGesture() string { + if x != nil { + return x.RpsGesture + } + return "" +} + type GetDiceMatchRequest 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"` MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + GameId string `protobuf:"bytes,4,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3389,11 +3422,20 @@ func (x *GetDiceMatchRequest) GetMatchId() string { return "" } +func (x *GetDiceMatchRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + type RollDiceMatchRequest 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"` MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + GameId string `protobuf:"bytes,4,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` + RpsGesture string `protobuf:"bytes,5,opt,name=rps_gesture,json=rpsGesture,proto3" json:"rps_gesture,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3449,6 +3491,20 @@ func (x *RollDiceMatchRequest) GetMatchId() string { return "" } +func (x *RollDiceMatchRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + +func (x *RollDiceMatchRequest) GetRpsGesture() string { + if x != nil { + return x.RpsGesture + } + return "" +} + type MatchDiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -3457,6 +3513,7 @@ type MatchDiceRequest struct { RoomId string `protobuf:"bytes,4,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` RegionId int64 `protobuf:"varint,5,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` StakeCoin int64 `protobuf:"varint,6,opt,name=stake_coin,json=stakeCoin,proto3" json:"stake_coin,omitempty"` + RpsGesture string `protobuf:"bytes,7,opt,name=rps_gesture,json=rpsGesture,proto3" json:"rps_gesture,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3533,11 +3590,19 @@ func (x *MatchDiceRequest) GetStakeCoin() int64 { return 0 } +func (x *MatchDiceRequest) GetRpsGesture() string { + if x != nil { + return x.RpsGesture + } + return "" +} + type CancelDiceMatchRequest 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"` MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` + GameId string `protobuf:"bytes,4,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3593,6 +3658,13 @@ func (x *CancelDiceMatchRequest) GetMatchId() string { return "" } +func (x *CancelDiceMatchRequest) GetGameId() string { + if x != nil { + return x.GameId + } + return "" +} + type DiceMatchResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Match *DiceMatch `protobuf:"bytes,1,opt,name=match,proto3" json:"match,omitempty"` @@ -5628,7 +5700,7 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\vorientation\x18\x04 \x01(\tR\vorientation\x12$\n" + "\x0eserver_time_ms\x18\x05 \x01(\x03R\fserverTimeMs\x12\x1f\n" + "\vsafe_height\x18\x06 \x01(\x05R\n" + - "safeHeight\"\x85\x03\n" + + "safeHeight\"\xa6\x03\n" + "\x0fDiceParticipant\x12\x17\n" + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x17\n" + "\aseat_no\x18\x02 \x01(\x05R\x06seatNo\x12\x16\n" + @@ -5646,7 +5718,9 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\rupdated_at_ms\x18\n" + " \x01(\x03R\vupdatedAtMs\x12)\n" + "\x10participant_type\x18\v \x01(\tR\x0fparticipantType\x12\x19\n" + - "\bis_robot\x18\f \x01(\bR\aisRobot\"\xe7\x06\n" + + "\bis_robot\x18\f \x01(\bR\aisRobot\x12\x1f\n" + + "\vrps_gesture\x18\r \x01(\tR\n" + + "rpsGesture\"\xe7\x06\n" + "\tDiceMatch\x12\x19\n" + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x19\n" + "\bmatch_id\x18\x02 \x01(\tR\amatchId\x12\x17\n" + @@ -5810,7 +5884,7 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\fchallenge_id\x18\x02 \x01(\tR\vchallengeId\"r\n" + "\x1dExpireRoomRPSChallengeRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12!\n" + - "\fchallenge_id\x18\x02 \x01(\tR\vchallengeId\"\x91\x02\n" + + "\fchallenge_id\x18\x02 \x01(\tR\vchallengeId\"\xb2\x02\n" + "\x16CreateDiceMatchRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x17\n" + @@ -5822,19 +5896,28 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\vmin_players\x18\a \x01(\x05R\n" + "minPlayers\x12\x1f\n" + "\vmax_players\x18\b \x01(\x05R\n" + - "maxPlayers\"z\n" + + "maxPlayers\x12\x1f\n" + + "\vrps_gesture\x18\t \x01(\tR\n" + + "rpsGesture\"\xb4\x01\n" + "\x14JoinDiceMatchRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + - "\bmatch_id\x18\x03 \x01(\tR\amatchId\"y\n" + + "\bmatch_id\x18\x03 \x01(\tR\amatchId\x12\x17\n" + + "\agame_id\x18\x04 \x01(\tR\x06gameId\x12\x1f\n" + + "\vrps_gesture\x18\x05 \x01(\tR\n" + + "rpsGesture\"\x92\x01\n" + "\x13GetDiceMatchRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + - "\bmatch_id\x18\x03 \x01(\tR\amatchId\"z\n" + + "\bmatch_id\x18\x03 \x01(\tR\amatchId\x12\x17\n" + + "\agame_id\x18\x04 \x01(\tR\x06gameId\"\xb4\x01\n" + "\x14RollDiceMatchRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + - "\bmatch_id\x18\x03 \x01(\tR\amatchId\"\xc9\x01\n" + + "\bmatch_id\x18\x03 \x01(\tR\amatchId\x12\x17\n" + + "\agame_id\x18\x04 \x01(\tR\x06gameId\x12\x1f\n" + + "\vrps_gesture\x18\x05 \x01(\tR\n" + + "rpsGesture\"\xea\x01\n" + "\x10MatchDiceRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x17\n" + @@ -5842,11 +5925,14 @@ const file_proto_game_v1_game_proto_rawDesc = "" + "\aroom_id\x18\x04 \x01(\tR\x06roomId\x12\x1b\n" + "\tregion_id\x18\x05 \x01(\x03R\bregionId\x12\x1d\n" + "\n" + - "stake_coin\x18\x06 \x01(\x03R\tstakeCoin\"|\n" + + "stake_coin\x18\x06 \x01(\x03R\tstakeCoin\x12\x1f\n" + + "\vrps_gesture\x18\a \x01(\tR\n" + + "rpsGesture\"\x95\x01\n" + "\x16CancelDiceMatchRequest\x12.\n" + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + - "\bmatch_id\x18\x03 \x01(\tR\amatchId\"i\n" + + "\bmatch_id\x18\x03 \x01(\tR\amatchId\x12\x17\n" + + "\agame_id\x18\x04 \x01(\tR\x06gameId\"i\n" + "\x11DiceMatchResponse\x12.\n" + "\x05match\x18\x01 \x01(\v2\x18.hyapp.game.v1.DiceMatchR\x05match\x12$\n" + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xbe\x03\n" + diff --git a/api/proto/game/v1/game.proto b/api/proto/game/v1/game.proto index c49d0e02..89299096 100644 --- a/api/proto/game/v1/game.proto +++ b/api/proto/game/v1/game.proto @@ -164,6 +164,7 @@ message DiceParticipant { int64 updated_at_ms = 10; string participant_type = 11; bool is_robot = 12; + string rps_gesture = 13; } message DiceMatch { @@ -356,24 +357,30 @@ message CreateDiceMatchRequest { int64 stake_coin = 6; int32 min_players = 7; int32 max_players = 8; + string rps_gesture = 9; } message JoinDiceMatchRequest { RequestMeta meta = 1; int64 user_id = 2; string match_id = 3; + string game_id = 4; + string rps_gesture = 5; } message GetDiceMatchRequest { RequestMeta meta = 1; int64 user_id = 2; string match_id = 3; + string game_id = 4; } message RollDiceMatchRequest { RequestMeta meta = 1; int64 user_id = 2; string match_id = 3; + string game_id = 4; + string rps_gesture = 5; } message MatchDiceRequest { @@ -383,12 +390,14 @@ message MatchDiceRequest { string room_id = 4; int64 region_id = 5; int64 stake_coin = 6; + string rps_gesture = 7; } message CancelDiceMatchRequest { RequestMeta meta = 1; int64 user_id = 2; string match_id = 3; + string game_id = 4; } message DiceMatchResponse { diff --git a/docs/H5自研猜拳接入接口.md b/docs/H5自研猜拳接入接口.md new file mode 100644 index 00000000..2704399f --- /dev/null +++ b/docs/H5自研猜拳接入接口.md @@ -0,0 +1,339 @@ +# H5 自研猜拳接入接口 + +本文只记录独立自研猜拳 `rock/rps` 的 H5 接口。所有地址都有 `/api/v1` 前缀,所有接口都需要 `Authorization: Bearer `。 + +成功响应统一格式: + +```json +{ + "code": "OK", + "message": "ok", + "request_id": "req_xxx", + "data": {} +} +``` + +时间字段都是 Unix epoch milliseconds。用户 ID 建议 H5 按字符串字段使用。 + +## 1. 获取游戏列表 + +地址: + +```http +GET /api/v1/games +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `scene` | query | 否 | 场景,语音房传 `voice_room`。 | +| `room_id` | query | 否 | 房间 ID。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `games` | array | 游戏列表。 | +| `games[].game_id` | string | 猜拳是 `rock`。 | +| `games[].name` | string | 游戏名称。 | +| `games[].launch_mode` | string | H5 打开方式。 | +| `games[].enabled` | bool | 是否可进入。 | +| `games[].maintenance` | bool | 是否维护中。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。 + +## 2. 创建 H5 启动会话 + +地址: + +```http +POST /api/v1/games/rock/launch +Content-Type: application/json +``` + +参数: + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `scene` | string | 否 | 场景,语音房传 `voice_room`。 | +| `room_id` | string | 否 | 房间 ID。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `session_id` | string | H5 启动会话 ID。 | +| `launch_url` | string | H5 页面地址。真实地址由后台配置,不在客户端拼。 | +| `expires_at_ms` | number | 启动会话过期时间。 | +| `orientation` | string | 页面方向。 | +| `safe_height` | number | 安全高度。 | +| `safeHeight` | number | 同 `safe_height`。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。 + +## 3. 查询猜拳配置 + +地址: + +```http +GET /api/v1/games/rps/config +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `game_id` | query | 否 | 固定是 `rock`,不传也按 `rock` 处理。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `config.game_id` | string | 固定返回 `rock`。 | +| `config.status` | string | `active` 才能玩。 | +| `config.stake_options` | array | 可下注金币档位。 | +| `config.fee_bps` | number | 平台抽水,万分比。 | +| `config.pool_bps` | number | 奖池入池,万分比。 | +| `config.min_players` | number | 最少人数。 | +| `config.max_players` | number | 最多人数。 | +| `config.robot_enabled` | bool | 是否启用机器人补位。 | +| `config.robot_match_wait_ms` | number | 等待机器人补位时间。 | +| `config.pool_balance_coin` | number | 当前奖池余额。 | +| `server_time_ms` | number | 服务端时间。 | + +`stake_options` 字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `stake_coin` | number | 下注金币。 | +| `enabled` | bool | 是否可选。 | +| `sort_order` | number | 排序。 | + +相关 IM:无。 + +## 4. 快速匹配 + +地址: + +```http +POST /api/v1/games/rps/match +Content-Type: application/json +``` + +参数: + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `stake_coin` | number | 是 | 下注金币,必须是配置里的启用档位。 | +| `gesture` | string | 是 | 本人提前选择的手势:`rock`、`paper`、`scissors`。 | +| `rps_gesture` | string | 否 | 同 `gesture`,两个字段传一个即可。 | +| `game_id` | string | 否 | 固定是 `rock`。 | +| `room_id` | string | 否 | 房间 ID。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `match` | object | 当前局详情,字段见“局详情字段”。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。H5 可以轮询详情,也可以等后续接入 IM 后按 IM 刷新。 + +## 5. 创建等待局 + +地址: + +```http +POST /api/v1/games/rps/matches +Content-Type: application/json +``` + +参数: + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `stake_coin` | number | 是 | 下注金币。 | +| `gesture` | string | 是 | 本人提前选择的手势。 | +| `rps_gesture` | string | 否 | 同 `gesture`。 | +| `game_id` | string | 否 | 固定是 `rock`。 | +| `room_id` | string | 否 | 房间 ID。 | +| `min_players` | number | 否 | 最少人数,不传用配置。 | +| `max_players` | number | 否 | 最多人数,不传用配置。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `match` | object | 当前局详情,字段见“局详情字段”。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。 + +## 6. 加入等待局 + +地址: + +```http +POST /api/v1/games/rps/matches/{match_id}/join +Content-Type: application/json +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `match_id` | path | 是 | 局 ID。 | +| `gesture` | body | 是 | 本人提前选择的手势。 | +| `rps_gesture` | body | 否 | 同 `gesture`。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `match` | object | 当前局详情,字段见“局详情字段”。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。 + +## 7. 查询局详情 + +地址: + +```http +GET /api/v1/games/rps/matches/{match_id} +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `match_id` | path | 是 | 局 ID。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `match` | object | 当前局详情,字段见“局详情字段”。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。 + +## 8. 揭晓并结算 + +地址: + +```http +POST /api/v1/games/rps/matches/{match_id}/roll +Content-Type: application/json +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `match_id` | path | 是 | 局 ID。 | +| `gesture` | body | 否 | 可以不传;如果传,必须等于创建或加入时的预选手势。 | +| `rps_gesture` | body | 否 | 同 `gesture`。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `match` | object | 已结算局详情。 | +| `server_time_ms` | number | 服务端时间。 | + +说明:猜拳不会随机出拳。服务端用双方提前选择的 `gesture` 比输赢,然后扣款、派奖和更新余额。 + +相关 IM:无。 + +## 9. 取消等待局 + +地址: + +```http +POST /api/v1/games/rps/matches/{match_id}/cancel +``` + +参数: + +| 字段 | 位置 | 必填 | 说明 | +| --- | --- | --- | --- | +| `match_id` | path | 是 | 局 ID。 | + +返回值: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `match` | object | 取消后的局详情。 | +| `server_time_ms` | number | 服务端时间。 | + +相关 IM:无。 + +## 10. 局详情字段 + +`match` 字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `match_id` | string | 局 ID。 | +| `game_id` | string | 固定是 `rock`。 | +| `room_id` | string | 房间 ID。 | +| `region_id` | number | 用户区域 ID。 | +| `min_players` | number | 最少人数。 | +| `max_players` | number | 最多人数。 | +| `current_players` | number | 当前人数。 | +| `stake_coin` | number | 本局下注金币。 | +| `round_no` | number | 当前轮次。 | +| `status` | string | `created`、`ready`、`settled`、`canceled`。 | +| `phase` | string | `waiting`、`countdown`、`settled`、`canceled`。 | +| `phase_deadline_ms` | number | 当前阶段截止时间。 | +| `result` | string | 当前用户视角结果:`win`、`lose`、`draw`。 | +| `participants` | array | 参与者列表。 | +| `join_deadline_ms` | number | 等待加入截止时间。 | +| `ready_at_ms` | number | 匹配完成时间。 | +| `settled_at_ms` | number | 结算时间。 | +| `canceled_at_ms` | number | 取消时间。 | +| `fee_bps` | number | 平台抽水。 | +| `pool_bps` | number | 奖池入池。 | +| `match_mode` | string | `human` 或 `robot`。 | +| `forced_result` | string | 机器人奖池不足时的强制结果。 | +| `pool_delta_coin` | number | 本局奖池变化。 | + +`participants` 字段: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `user_id` | string | 用户 ID,H5 优先用这个字段。 | +| `user_id_number` | number | 用户 ID 数字值。 | +| `seat_no` | number | 座位号。 | +| `status` | string | `joined`、`settled`、`canceled`。 | +| `stake_coin` | number | 下注金币。 | +| `gesture` | string | 预选手势。 | +| `rps_gesture` | string | 同 `gesture`。 | +| `result` | string | `win`、`lose`、`draw`。 | +| `payout_coin` | number | 派彩金币。 | +| `balance_after` | number | 结算后余额。 | +| `joined_at_ms` | number | 加入时间。 | +| `updated_at_ms` | number | 更新时间。 | +| `participant_type` | string | `user` 或 `robot`。 | +| `is_robot` | bool | 是否机器人。 | +| `user.display_user_id` | string | 展示 ID。 | +| `user.nickname` | string | 昵称。 | +| `user.avatar` | string | 头像。 | +| `avatar_frame` | object | 头像框资源。 | + +## 11. H5 推荐流程 + +1. App 调 `GET /api/v1/games`,看到 `game_id=rock` 后展示入口。 +2. App 调 `POST /api/v1/games/rock/launch` 打开 H5。 +3. H5 调 `GET /api/v1/games/rps/config` 展示下注档位。 +4. 用户先选择 `rock`、`paper` 或 `scissors`。 +5. H5 调 `POST /api/v1/games/rps/match` 快速匹配。 +6. 返回 `phase=countdown` 后,H5 用 `phase_deadline_ms` 做倒计时。 +7. 倒计时结束后,H5 调 `POST /api/v1/games/rps/matches/{match_id}/roll` 揭晓并结算。 +8. H5 读返回的 `participants[].gesture/result/payout_coin/balance_after` 展示结果。 + +相关 IM:当前独立猜拳没有 IM。H5 用 HTTP 返回和详情轮询刷新。 diff --git a/server/admin/internal/modules/inviteactivityreward/handler.go b/server/admin/internal/modules/inviteactivityreward/handler.go index 789f7f6b..ea66b7de 100644 --- a/server/admin/internal/modules/inviteactivityreward/handler.go +++ b/server/admin/internal/modules/inviteactivityreward/handler.go @@ -27,17 +27,21 @@ func New(activity activityclient.Client, userDB *sql.DB, audit shared.OperationL } type configRequest struct { - Enabled bool `json:"enabled"` - Tiers []tierDTO `json:"tiers"` + Enabled bool `json:"enabled"` + PerInviteInviterRewardCoinAmount int64 `json:"per_invite_inviter_reward_coin_amount"` + PerInviteInviteeRewardCoinAmount int64 `json:"per_invite_invitee_reward_coin_amount"` + Tiers []tierDTO `json:"tiers"` } type configDTO struct { - AppCode string `json:"app_code"` - Enabled bool `json:"enabled"` - Tiers []tierDTO `json:"tiers"` - UpdatedByAdminID int64 `json:"updated_by_admin_id"` - CreatedAtMS int64 `json:"created_at_ms"` - UpdatedAtMS int64 `json:"updated_at_ms"` + AppCode string `json:"app_code"` + Enabled bool `json:"enabled"` + PerInviteInviterRewardCoinAmount int64 `json:"per_invite_inviter_reward_coin_amount"` + PerInviteInviteeRewardCoinAmount int64 `json:"per_invite_invitee_reward_coin_amount"` + Tiers []tierDTO `json:"tiers"` + UpdatedByAdminID int64 `json:"updated_by_admin_id"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` } type tierDTO struct { @@ -116,10 +120,12 @@ func (h *Handler) UpdateConfig(c *gin.Context) { }) } resp, err := h.activity.UpdateInviteActivityRewardConfig(c.Request.Context(), &activityv1.UpdateInviteActivityRewardConfigRequest{ - Meta: h.meta(c), - Enabled: req.Enabled, - Tiers: tiers, - OperatorAdminId: int64(middleware.CurrentUserID(c)), + Meta: h.meta(c), + Enabled: req.Enabled, + PerInviteInviterRewardCoinAmount: req.PerInviteInviterRewardCoinAmount, + PerInviteInviteeRewardCoinAmount: req.PerInviteInviteeRewardCoinAmount, + Tiers: tiers, + OperatorAdminId: int64(middleware.CurrentUserID(c)), }) if err != nil { response.BadRequest(c, err.Error()) @@ -264,12 +270,14 @@ func configFromProto(config *activityv1.InviteActivityRewardConfig) configDTO { tiers = append(tiers, tierFromProto(tier)) } return configDTO{ - AppCode: config.GetAppCode(), - Enabled: config.GetEnabled(), - Tiers: tiers, - UpdatedByAdminID: config.GetUpdatedByAdminId(), - CreatedAtMS: config.GetCreatedAtMs(), - UpdatedAtMS: config.GetUpdatedAtMs(), + AppCode: config.GetAppCode(), + Enabled: config.GetEnabled(), + PerInviteInviterRewardCoinAmount: config.GetPerInviteInviterRewardCoinAmount(), + PerInviteInviteeRewardCoinAmount: config.GetPerInviteInviteeRewardCoinAmount(), + Tiers: tiers, + UpdatedByAdminID: config.GetUpdatedByAdminId(), + CreatedAtMS: config.GetCreatedAtMs(), + UpdatedAtMS: config.GetUpdatedAtMs(), } } diff --git a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql index 85ede66a..d1097af6 100644 --- a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql +++ b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql @@ -1286,6 +1286,8 @@ CREATE TABLE IF NOT EXISTS weekly_star_settlements ( CREATE TABLE IF NOT EXISTS invite_activity_reward_configs ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用邀请活动', + per_invite_inviter_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '每邀请一人邀请人即时奖励金币', + per_invite_invitee_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '每邀请一人被邀请人即时奖励金币', updated_by_admin_id 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', @@ -1313,7 +1315,7 @@ CREATE TABLE IF NOT EXISTS invite_activity_reward_tiers ( CREATE TABLE IF NOT EXISTS invite_activity_reward_events ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', event_id VARCHAR(128) NOT NULL COMMENT '上游 outbox event_id', - event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInviteBecameValid', + event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInvited/UserInviteBecameValid', cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', invited_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '被邀请人用户 ID', inviter_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '邀请人用户 ID', @@ -1349,11 +1351,11 @@ CREATE TABLE IF NOT EXISTS invite_activity_reward_claims ( claim_id VARCHAR(128) NOT NULL COMMENT '领取 ID', cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', - reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', - command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取命令 ID', - tier_id BIGINT NOT NULL COMMENT '领取档位 ID', - tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码', - tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite/invite_inviter/invite_invitee', + command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取或系统即时发奖命令 ID', + tier_id BIGINT NOT NULL COMMENT '领取档位 ID;即时邀请奖励使用对方用户 ID 做幂等维度', + tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码或即时奖励类型', + tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称或即时奖励名称', threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', reached_value BIGINT NOT NULL DEFAULT 0 COMMENT '领取时已达成值', diff --git a/services/activity-service/deploy/mysql/migrations/002_invite_activity_reward.sql b/services/activity-service/deploy/mysql/migrations/002_invite_activity_reward.sql index 74061300..9487d55e 100644 --- a/services/activity-service/deploy/mysql/migrations/002_invite_activity_reward.sql +++ b/services/activity-service/deploy/mysql/migrations/002_invite_activity_reward.sql @@ -3,6 +3,8 @@ USE hyapp_activity; CREATE TABLE IF NOT EXISTS invite_activity_reward_configs ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用邀请活动', + per_invite_inviter_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '每邀请一人邀请人即时奖励金币', + per_invite_invitee_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '每邀请一人被邀请人即时奖励金币', updated_by_admin_id 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', @@ -30,7 +32,7 @@ CREATE TABLE IF NOT EXISTS invite_activity_reward_tiers ( CREATE TABLE IF NOT EXISTS invite_activity_reward_events ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', event_id VARCHAR(128) NOT NULL COMMENT '上游 outbox event_id', - event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInviteBecameValid', + event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInvited/UserInviteBecameValid', cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', invited_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '被邀请人用户 ID', inviter_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '邀请人用户 ID', @@ -66,11 +68,11 @@ CREATE TABLE IF NOT EXISTS invite_activity_reward_claims ( claim_id VARCHAR(128) NOT NULL COMMENT '领取 ID', cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', - reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', - command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取命令 ID', - tier_id BIGINT NOT NULL COMMENT '领取档位 ID', - tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码', - tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite/invite_inviter/invite_invitee', + command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取或系统即时发奖命令 ID', + tier_id BIGINT NOT NULL COMMENT '领取档位 ID;即时邀请奖励使用对方用户 ID 做幂等维度', + tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码或即时奖励类型', + tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称或即时奖励名称', threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', reached_value BIGINT NOT NULL DEFAULT 0 COMMENT '领取时已达成值', diff --git a/services/activity-service/deploy/mysql/migrations/003_invite_activity_per_invite_reward.sql b/services/activity-service/deploy/mysql/migrations/003_invite_activity_per_invite_reward.sql new file mode 100644 index 00000000..8446a2ee --- /dev/null +++ b/services/activity-service/deploy/mysql/migrations/003_invite_activity_per_invite_reward.sql @@ -0,0 +1,25 @@ +USE hyapp_activity; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'invite_activity_reward_configs' + AND COLUMN_NAME = 'per_invite_inviter_reward_coin_amount') = 0, + 'ALTER TABLE invite_activity_reward_configs ADD COLUMN per_invite_inviter_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT ''每邀请一人邀请人即时奖励金币'' AFTER enabled', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'invite_activity_reward_configs' + AND COLUMN_NAME = 'per_invite_invitee_reward_coin_amount') = 0, + 'ALTER TABLE invite_activity_reward_configs ADD COLUMN per_invite_invitee_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT ''每邀请一人被邀请人即时奖励金币'' AFTER per_invite_inviter_reward_coin_amount', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/services/activity-service/internal/app/mq.go b/services/activity-service/internal/app/mq.go index 20320efa..db0aab0f 100644 --- a/services/activity-service/internal/app/mq.go +++ b/services/activity-service/internal/app/mq.go @@ -207,12 +207,19 @@ func newInviteActivityWalletConsumer(cfg config.Config, services *serviceBundle) } func newInviteActivityUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { - // 有效邀请人数来自 user-service 已提交事实;消费失败必须让同一 outbox 重试,仓储按 event_id 保证幂等。 + // 邀请绑定和有效邀请都来自 user-service 已提交事实;消费失败必须让同一 outbox 重试,仓储和钱包命令共同保证幂等。 consumer, err := rocketmqx.NewConsumer(userOutboxInviteActivityConsumerConfig(cfg.RocketMQ)) if err != nil { return nil, err } if err := consumer.Subscribe(cfg.RocketMQ.UserOutbox.Topic, usermq.TagUserOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + inviteEvent, inviteOK, err := inviteActivityInviteEventFromUserMessage(message.Body) + if err != nil { + return err + } + if inviteOK { + return services.inviteActivityReward.ConsumeInviteEvent(appcode.WithContext(ctx, inviteEvent.AppCode), inviteEvent) + } event, ok, err := inviteActivityValidInviteEventFromUserMessage(message.Body) if err != nil || !ok { return err diff --git a/services/activity-service/internal/app/user_events.go b/services/activity-service/internal/app/user_events.go index ee23eb54..2e453af2 100644 --- a/services/activity-service/internal/app/user_events.go +++ b/services/activity-service/internal/app/user_events.go @@ -35,3 +35,33 @@ func inviteActivityValidInviteEventFromUserMessage(body []byte) (inviteactivityd OccurredAtMS: message.OccurredAtMS, }, true, nil } + +func inviteActivityInviteEventFromUserMessage(body []byte) (inviteactivitydomain.InviteEvent, bool, error) { + message, err := usermq.DecodeUserOutboxMessage(body) + if err != nil { + return inviteactivitydomain.InviteEvent{}, false, err + } + if message.EventType != inviteactivitydomain.EventTypeUserInvited { + return inviteactivitydomain.InviteEvent{}, false, nil + } + var decoded map[string]any + if err := json.Unmarshal([]byte(message.PayloadJSON), &decoded); err != nil || decoded == nil { + decoded = map[string]any{} + } + invitedUserID := firstNonZeroInt64(int64FromDecoded(decoded, "invited_user_id"), message.AggregateID) + inviterUserID := int64FromDecoded(decoded, "inviter_user_id") + if invitedUserID <= 0 || inviterUserID <= 0 { + return inviteactivitydomain.InviteEvent{}, false, nil + } + return inviteactivitydomain.InviteEvent{ + AppCode: appcode.Normalize(message.AppCode), + EventID: message.EventID, + EventType: message.EventType, + InviterUserID: inviterUserID, + InvitedUserID: invitedUserID, + InviteCode: stringFromDecoded(decoded, "invite_code"), + Source: stringFromDecoded(decoded, "source"), + PayloadJSON: message.PayloadJSON, + OccurredAtMS: message.OccurredAtMS, + }, true, nil +} diff --git a/services/activity-service/internal/domain/inviteactivity/invite_activity.go b/services/activity-service/internal/domain/inviteactivity/invite_activity.go index ffaf9939..9c8a75f7 100644 --- a/services/activity-service/internal/domain/inviteactivity/invite_activity.go +++ b/services/activity-service/internal/domain/inviteactivity/invite_activity.go @@ -4,8 +4,10 @@ const ( TierStatusActive = "active" TierStatusInactive = "inactive" - RewardTypeRecharge = "recharge" - RewardTypeValidInvite = "valid_invite" + RewardTypeRecharge = "recharge" + RewardTypeValidInvite = "valid_invite" + RewardTypeInviteInviter = "invite_inviter" + RewardTypeInvitee = "invite_invitee" ClaimStatusPending = "pending" ClaimStatusGranted = "granted" @@ -17,6 +19,7 @@ const ( ReasonDuplicate = "duplicate" EventTypeWalletRechargeRecorded = "WalletRechargeRecorded" + EventTypeUserInvited = "UserInvited" EventTypeUserInviteBecameValid = "UserInviteBecameValid" ) @@ -38,12 +41,14 @@ type Tier struct { // Config 是当前 App 的邀请活动配置。 type Config struct { - AppCode string - Enabled bool - Tiers []Tier - UpdatedByAdminID int64 - CreatedAtMS int64 - UpdatedAtMS int64 + AppCode string + Enabled bool + PerInviteInviterRewardCoinAmount int64 + PerInviteInviteeRewardCoinAmount int64 + Tiers []Tier + UpdatedByAdminID int64 + CreatedAtMS int64 + UpdatedAtMS int64 } // Progress 是邀请人在一个 UTC 自然月内的邀请活动累计。 @@ -107,6 +112,19 @@ type ValidInviteEvent struct { OccurredAtMS int64 } +// InviteEvent 是 user_outbox 里“邀请关系首次绑定”的事实;它不依赖被邀请人后续是否充值达标。 +type InviteEvent struct { + AppCode string + EventID string + EventType string + InvitedUserID int64 + InviterUserID int64 + InviteCode string + Source string + PayloadJSON string + OccurredAtMS int64 +} + // Cycle 表达 UTC 自然月周期边界。 type Cycle struct { Key string diff --git a/services/activity-service/internal/service/inviteactivity/service.go b/services/activity-service/internal/service/inviteactivity/service.go index 13cd8105..a2bddbb2 100644 --- a/services/activity-service/internal/service/inviteactivity/service.go +++ b/services/activity-service/internal/service/inviteactivity/service.go @@ -2,6 +2,8 @@ package inviteactivity import ( "context" + "crypto/sha1" + "encoding/hex" "fmt" "sort" "strings" @@ -23,6 +25,7 @@ type Repository interface { GetInviteActivityRewardProgress(ctx context.Context, cycleKey string, userID int64) (domain.Progress, bool, error) ListInviteActivityRewardClaimsByUserCycle(ctx context.Context, cycleKey string, userID int64) ([]domain.Claim, error) ConsumeInviteActivityRechargeEvent(ctx context.Context, event domain.RechargeEvent, cycle domain.Cycle, nowMS int64) (domain.ConsumeResult, error) + RecordInviteActivityInviteEvent(ctx context.Context, event domain.InviteEvent, cycle domain.Cycle, status string, skipReason string, nowMS int64) (domain.ConsumeResult, error) ConsumeInviteActivityValidInviteEvent(ctx context.Context, event domain.ValidInviteEvent, cycle domain.Cycle, nowMS int64) (domain.ConsumeResult, error) CreateInviteActivityRewardClaim(ctx context.Context, claim domain.Claim, nowMS int64) (domain.Claim, error) MarkInviteActivityRewardClaimGranted(ctx context.Context, claimID string, walletTransactionID string, grantedAtMS int64) (domain.Claim, error) @@ -159,6 +162,60 @@ func (s *Service) ConsumeValidInviteEvent(ctx context.Context, event domain.Vali return err } +func (s *Service) ConsumeInviteEvent(ctx context.Context, event domain.InviteEvent) error { + if err := s.requireRepository(); err != nil { + return err + } + if event.EventType != domain.EventTypeUserInvited || event.EventID == "" || event.InviterUserID <= 0 || event.InvitedUserID <= 0 { + return nil + } + if event.OccurredAtMS <= 0 { + event.OccurredAtMS = s.now().UTC().UnixMilli() + } + event.AppCode = appcode.Normalize(event.AppCode) + event.InviteCode = strings.TrimSpace(event.InviteCode) + event.Source = strings.TrimSpace(event.Source) + cycle := CycleForTime(time.UnixMilli(event.OccurredAtMS).UTC()) + eventCtx := appcode.WithContext(ctx, event.AppCode) + + config, exists, err := s.repository.GetInviteActivityRewardConfig(eventCtx) + if err != nil { + return err + } + if !exists { + config = domain.Config{AppCode: event.AppCode, Enabled: true} + } + if !config.Enabled { + _, err := s.repository.RecordInviteActivityInviteEvent(eventCtx, event, cycle, "skipped", "invite activity is disabled", s.now().UTC().UnixMilli()) + return err + } + if config.PerInviteInviterRewardCoinAmount <= 0 && config.PerInviteInviteeRewardCoinAmount <= 0 { + _, err := s.repository.RecordInviteActivityInviteEvent(eventCtx, event, cycle, "skipped", "per invite reward is not configured", s.now().UTC().UnixMilli()) + return err + } + if s.wallet == nil { + return xerr.New(xerr.Unavailable, "wallet client is not configured") + } + // 事件表只做审计和去重观察;真正的钱包幂等落在 deterministic claim_id + wallet_command_id 上。 + // 因此 MQ 重试时即使事件已存在,也会继续复用同一 claim 重试未完成的钱包入账。 + if _, err := s.repository.RecordInviteActivityInviteEvent(eventCtx, event, cycle, "consumed", "", s.now().UTC().UnixMilli()); err != nil { + return err + } + if config.PerInviteInviterRewardCoinAmount > 0 { + claim := perInviteClaim(event, cycle, domain.RewardTypeInviteInviter, event.InviterUserID, event.InvitedUserID, config.PerInviteInviterRewardCoinAmount) + if _, err := s.grantClaim(eventCtx, claim); err != nil { + return err + } + } + if config.PerInviteInviteeRewardCoinAmount > 0 { + claim := perInviteClaim(event, cycle, domain.RewardTypeInvitee, event.InvitedUserID, event.InviterUserID, config.PerInviteInviteeRewardCoinAmount) + if _, err := s.grantClaim(eventCtx, claim); err != nil { + return err + } + } + return nil +} + func (s *Service) Claim(ctx context.Context, userID int64, rewardType string, tierID int64, commandID string) (domain.Claim, domain.StatusResult, error) { if err := s.requireRepository(); err != nil { return domain.Claim{}, domain.StatusResult{}, err @@ -166,7 +223,7 @@ func (s *Service) Claim(ctx context.Context, userID int64, rewardType string, ti if s.wallet == nil { return domain.Claim{}, domain.StatusResult{}, xerr.New(xerr.Unavailable, "wallet client is not configured") } - rewardType = normalizeRewardType(rewardType) + rewardType = normalizeTierRewardType(rewardType) commandID = strings.TrimSpace(commandID) if userID <= 0 || rewardType == "" || tierID <= 0 || commandID == "" { return domain.Claim{}, domain.StatusResult{}, xerr.New(xerr.InvalidArgument, "claim request is incomplete") @@ -206,32 +263,7 @@ func (s *Service) Claim(ctx context.Context, userID int64, rewardType string, ti RewardCoinAmount: rewardAmount, WalletCommandID: commandID, } - created, err := s.repository.CreateInviteActivityRewardClaim(ctx, claim, s.now().UTC().UnixMilli()) - if err != nil { - return domain.Claim{}, domain.StatusResult{}, err - } - if created.Status == domain.ClaimStatusGranted { - refreshed, refreshErr := s.GetStatus(ctx, userID) - return created, refreshed, refreshErr - } - resp, err := s.wallet.CreditInviteActivityReward(ctx, &walletv1.CreditInviteActivityRewardRequest{ - CommandId: created.WalletCommandID, - AppCode: appcode.FromContext(ctx), - TargetUserId: userID, - Amount: created.RewardCoinAmount, - ClaimId: created.ClaimID, - RewardType: created.RewardType, - TierId: created.TierID, - TierCode: created.TierCode, - CycleKey: created.CycleKey, - ReachedValue: created.ReachedValue, - Reason: grantReason, - }) - if err != nil { - _ = s.repository.MarkInviteActivityRewardClaimFailed(ctx, created.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli()) - return domain.Claim{}, domain.StatusResult{}, err - } - updated, err := s.repository.MarkInviteActivityRewardClaimGranted(ctx, created.ClaimID, resp.GetTransactionId(), resp.GetGrantedAtMs()) + updated, err := s.grantClaim(ctx, claim) if err != nil { return domain.Claim{}, domain.StatusResult{}, err } @@ -299,7 +331,7 @@ func CycleForTime(value time.Time) domain.Cycle { func normalizeTiers(tiers []domain.Tier) []domain.Tier { items := make([]domain.Tier, 0, len(tiers)) for _, tier := range tiers { - tier.RewardType = normalizeRewardType(tier.RewardType) + tier.RewardType = normalizeTierRewardType(tier.RewardType) tier.TierCode = strings.TrimSpace(tier.TierCode) tier.TierName = strings.TrimSpace(tier.TierName) tier.Status = strings.ToLower(strings.TrimSpace(tier.Status)) @@ -324,8 +356,11 @@ func validateConfig(config domain.Config) error { if config.UpdatedByAdminID <= 0 { return xerr.New(xerr.InvalidArgument, "operator_admin_id is required") } - if config.Enabled && len(activeTiers(config.Tiers)) == 0 { - return xerr.New(xerr.InvalidArgument, "enabled invite activity requires active tiers") + if config.PerInviteInviterRewardCoinAmount < 0 || config.PerInviteInviteeRewardCoinAmount < 0 { + return xerr.New(xerr.InvalidArgument, "per invite reward coin amount must not be negative") + } + if config.Enabled && len(activeTiers(config.Tiers)) == 0 && config.PerInviteInviterRewardCoinAmount <= 0 && config.PerInviteInviteeRewardCoinAmount <= 0 { + return xerr.New(xerr.InvalidArgument, "enabled invite activity requires active tiers or per invite rewards") } seen := map[string]struct{}{} for _, tier := range config.Tiers { @@ -347,6 +382,67 @@ func validateConfig(config domain.Config) error { return nil } +func (s *Service) grantClaim(ctx context.Context, claim domain.Claim) (domain.Claim, error) { + created, err := s.repository.CreateInviteActivityRewardClaim(ctx, claim, s.now().UTC().UnixMilli()) + if err != nil { + return domain.Claim{}, err + } + if created.Status == domain.ClaimStatusGranted { + return created, nil + } + // 钱包 command_id 与 claim_id 都是稳定值;失败重试会复用同一 claim,钱包已成功但回包丢失时会由钱包幂等回放交易。 + resp, err := s.wallet.CreditInviteActivityReward(ctx, &walletv1.CreditInviteActivityRewardRequest{ + CommandId: created.WalletCommandID, + AppCode: appcode.FromContext(ctx), + TargetUserId: created.UserID, + Amount: created.RewardCoinAmount, + ClaimId: created.ClaimID, + RewardType: created.RewardType, + TierId: created.TierID, + TierCode: created.TierCode, + CycleKey: created.CycleKey, + ReachedValue: created.ReachedValue, + Reason: grantReason, + }) + if err != nil { + _ = s.repository.MarkInviteActivityRewardClaimFailed(ctx, created.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli()) + return domain.Claim{}, err + } + return s.repository.MarkInviteActivityRewardClaimGranted(ctx, created.ClaimID, resp.GetTransactionId(), resp.GetGrantedAtMs()) +} + +func perInviteClaim(event domain.InviteEvent, cycle domain.Cycle, rewardType string, userID int64, peerUserID int64, amount int64) domain.Claim { + claimID := perInviteClaimID(event, rewardType) + return domain.Claim{ + AppCode: event.AppCode, + ClaimID: claimID, + CycleKey: cycle.Key, + UserID: userID, + RewardType: rewardType, + CommandID: "wallet_" + claimID, + TierID: peerUserID, + TierCode: rewardType, + TierName: perInviteTierName(rewardType), + ThresholdCoinAmount: 0, + ThresholdValidInviteCount: 0, + ReachedValue: 1, + RewardCoinAmount: amount, + WalletCommandID: "wallet_" + claimID, + } +} + +func perInviteClaimID(event domain.InviteEvent, rewardType string) string { + sum := sha1.Sum([]byte(fmt.Sprintf("%s|%s|%s|%d|%d", appcode.Normalize(event.AppCode), strings.TrimSpace(event.EventID), rewardType, event.InviterUserID, event.InvitedUserID))) + return fmt.Sprintf("IAR_INVITE_%s", hex.EncodeToString(sum[:])[:32]) +} + +func perInviteTierName(rewardType string) string { + if rewardType == domain.RewardTypeInvitee { + return "每邀请一人被邀请人奖励" + } + return "每邀请一人邀请人奖励" +} + func claimRewardAmount(claims []domain.Claim, tiers []domain.Tier, tier domain.Tier, reached int64) (int64, error) { if tier.RewardType == domain.RewardTypeValidInvite { for _, claim := range claims { @@ -384,6 +480,21 @@ func activeTiers(tiers []domain.Tier) []domain.Tier { } func normalizeRewardType(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case domain.RewardTypeRecharge: + return domain.RewardTypeRecharge + case domain.RewardTypeValidInvite: + return domain.RewardTypeValidInvite + case domain.RewardTypeInviteInviter: + return domain.RewardTypeInviteInviter + case domain.RewardTypeInvitee: + return domain.RewardTypeInvitee + default: + return "" + } +} + +func normalizeTierRewardType(value string) string { switch strings.ToLower(strings.TrimSpace(value)) { case domain.RewardTypeRecharge: return domain.RewardTypeRecharge diff --git a/services/activity-service/internal/service/inviteactivity/service_test.go b/services/activity-service/internal/service/inviteactivity/service_test.go index 8f4a11cd..3b615e1c 100644 --- a/services/activity-service/internal/service/inviteactivity/service_test.go +++ b/services/activity-service/internal/service/inviteactivity/service_test.go @@ -84,6 +84,53 @@ func TestClaimValidInviteEachReachedTier(t *testing.T) { } } +func TestConsumeInviteEventGrantsPerInviteRewardsIdempotently(t *testing.T) { + repo := newFakeInviteActivityRepo() + repo.config = domain.Config{ + AppCode: "lalu", + Enabled: true, + PerInviteInviterRewardCoinAmount: 700, + PerInviteInviteeRewardCoinAmount: 200, + } + wallet := &fakeInviteActivityWallet{} + service := New(repo, wallet, nil) + service.SetClock(func() time.Time { return time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC) }) + ctx := appcode.WithContext(context.Background(), "lalu") + event := domain.InviteEvent{ + AppCode: "lalu", + EventID: "evt-user-invited-1", + EventType: domain.EventTypeUserInvited, + InviterUserID: 42, + InvitedUserID: 99, + InviteCode: "ABC123", + Source: "invite_h5", + OccurredAtMS: time.Date(2026, 6, 11, 11, 0, 0, 0, time.UTC).UnixMilli(), + } + + if err := service.ConsumeInviteEvent(ctx, event); err != nil { + t.Fatalf("consume invite event: %v", err) + } + if len(wallet.requests) != 2 { + t.Fatalf("expected inviter and invitee wallet credits, got %d", len(wallet.requests)) + } + if wallet.requests[0].GetTargetUserId() != 42 || wallet.requests[0].GetAmount() != 700 || wallet.requests[0].GetRewardType() != domain.RewardTypeInviteInviter { + t.Fatalf("inviter reward mismatch: %#v", wallet.requests[0]) + } + if wallet.requests[1].GetTargetUserId() != 99 || wallet.requests[1].GetAmount() != 200 || wallet.requests[1].GetRewardType() != domain.RewardTypeInvitee { + t.Fatalf("invitee reward mismatch: %#v", wallet.requests[1]) + } + if len(repo.claims) != 2 || repo.claims[0].Status != domain.ClaimStatusGranted || repo.claims[1].Status != domain.ClaimStatusGranted { + t.Fatalf("expected 2 granted claims, got %#v", repo.claims) + } + + if err := service.ConsumeInviteEvent(ctx, event); err != nil { + t.Fatalf("consume duplicate invite event: %v", err) + } + if len(wallet.requests) != 2 || len(repo.claims) != 2 { + t.Fatalf("duplicate event should reuse existing claims: wallet=%d claims=%d", len(wallet.requests), len(repo.claims)) + } +} + func TestListLeaderboardDefaultsCurrentCycleAndCapsPageSize(t *testing.T) { repo := newFakeInviteActivityRepo() repo.leaderboardEntries = []domain.LeaderboardEntry{ @@ -140,6 +187,10 @@ func (r *fakeInviteActivityRepo) ConsumeInviteActivityRechargeEvent(context.Cont return domain.ConsumeResult{}, nil } +func (r *fakeInviteActivityRepo) RecordInviteActivityInviteEvent(context.Context, domain.InviteEvent, domain.Cycle, string, string, int64) (domain.ConsumeResult, error) { + return domain.ConsumeResult{Consumed: true, Reason: domain.ReasonEligible}, nil +} + func (r *fakeInviteActivityRepo) ConsumeInviteActivityValidInviteEvent(context.Context, domain.ValidInviteEvent, domain.Cycle, int64) (domain.ConsumeResult, error) { return domain.ConsumeResult{}, nil } diff --git a/services/activity-service/internal/storage/mysql/invite_activity_reward_migration.go b/services/activity-service/internal/storage/mysql/invite_activity_reward_migration.go index 0b6ac108..79734c7c 100644 --- a/services/activity-service/internal/storage/mysql/invite_activity_reward_migration.go +++ b/services/activity-service/internal/storage/mysql/invite_activity_reward_migration.go @@ -7,6 +7,8 @@ func (r *Repository) ensureInviteActivityRewardTables(ctx context.Context) error `CREATE TABLE IF NOT EXISTS invite_activity_reward_configs ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用邀请活动', + per_invite_inviter_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '每邀请一人邀请人即时奖励金币', + per_invite_invitee_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '每邀请一人被邀请人即时奖励金币', updated_by_admin_id 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', @@ -32,7 +34,7 @@ func (r *Repository) ensureInviteActivityRewardTables(ctx context.Context) error `CREATE TABLE IF NOT EXISTS invite_activity_reward_events ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码', event_id VARCHAR(128) NOT NULL COMMENT '上游 outbox event_id', - event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInviteBecameValid', + event_type VARCHAR(64) NOT NULL COMMENT 'WalletRechargeRecorded/UserInvited/UserInviteBecameValid', cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', invited_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '被邀请人用户 ID', inviter_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '邀请人用户 ID', @@ -66,11 +68,11 @@ func (r *Repository) ensureInviteActivityRewardTables(ctx context.Context) error claim_id VARCHAR(128) NOT NULL COMMENT '领取 ID', cycle_key VARCHAR(16) NOT NULL COMMENT 'UTC 自然月,如 2026-06', user_id BIGINT NOT NULL COMMENT '邀请人用户 ID', - reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite', - command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取命令 ID', - tier_id BIGINT NOT NULL COMMENT '领取档位 ID', - tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码', - tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称', + reward_type VARCHAR(32) NOT NULL COMMENT 'recharge/valid_invite/invite_inviter/invite_invitee', + command_id VARCHAR(128) NOT NULL COMMENT 'H5 领取或系统即时发奖命令 ID', + tier_id BIGINT NOT NULL COMMENT '领取档位 ID;即时邀请奖励使用对方用户 ID 做幂等维度', + tier_code VARCHAR(64) NOT NULL COMMENT '领取档位编码或即时奖励类型', + tier_name VARCHAR(128) NOT NULL COMMENT '领取档位名称或即时奖励名称', threshold_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币门槛', threshold_valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请人数门槛', reached_value BIGINT NOT NULL DEFAULT 0 COMMENT '领取时已达成值', @@ -94,5 +96,24 @@ func (r *Repository) ensureInviteActivityRewardTables(ctx context.Context) error return err } } + additions := []struct { + column string + sql string + }{ + {"per_invite_inviter_reward_coin_amount", `ALTER TABLE invite_activity_reward_configs ADD COLUMN per_invite_inviter_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '每邀请一人邀请人即时奖励金币' AFTER enabled`}, + {"per_invite_invitee_reward_coin_amount", `ALTER TABLE invite_activity_reward_configs ADD COLUMN per_invite_invitee_reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '每邀请一人被邀请人即时奖励金币' AFTER per_invite_inviter_reward_coin_amount`}, + } + for _, addition := range additions { + exists, err := r.columnExists(ctx, "invite_activity_reward_configs", addition.column) + if err != nil { + return err + } + if !exists { + // 邀请即时奖励必须由后台显式配置金额;老库补列默认 0,避免迁移瞬间产生非预期发奖。 + if _, err := r.db.ExecContext(ctx, addition.sql); err != nil { + return err + } + } + } return nil } diff --git a/services/activity-service/internal/storage/mysql/invite_activity_reward_repository.go b/services/activity-service/internal/storage/mysql/invite_activity_reward_repository.go index 3803a101..c3ac00b5 100644 --- a/services/activity-service/internal/storage/mysql/invite_activity_reward_repository.go +++ b/services/activity-service/internal/storage/mysql/invite_activity_reward_repository.go @@ -40,13 +40,19 @@ func (r *Repository) UpdateInviteActivityRewardConfig(ctx context.Context, confi defer func() { _ = tx.Rollback() }() if _, err := tx.ExecContext(ctx, ` - INSERT INTO invite_activity_reward_configs (app_code, enabled, updated_by_admin_id, created_at_ms, updated_at_ms) - VALUES (?, ?, ?, ?, ?) + INSERT INTO invite_activity_reward_configs ( + app_code, enabled, per_invite_inviter_reward_coin_amount, per_invite_invitee_reward_coin_amount, + updated_by_admin_id, created_at_ms, updated_at_ms + ) + VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE enabled = VALUES(enabled), + per_invite_inviter_reward_coin_amount = VALUES(per_invite_inviter_reward_coin_amount), + per_invite_invitee_reward_coin_amount = VALUES(per_invite_invitee_reward_coin_amount), updated_by_admin_id = VALUES(updated_by_admin_id), updated_at_ms = VALUES(updated_at_ms)`, - appcode.FromContext(ctx), config.Enabled, config.UpdatedByAdminID, nowMS, nowMS, + appcode.FromContext(ctx), config.Enabled, config.PerInviteInviterRewardCoinAmount, config.PerInviteInviteeRewardCoinAmount, + config.UpdatedByAdminID, nowMS, nowMS, ); err != nil { return domain.Config{}, err } @@ -170,6 +176,45 @@ func (r *Repository) ConsumeInviteActivityRechargeEvent(ctx context.Context, eve return domain.ConsumeResult{Consumed: true, Reason: domain.ReasonEligible}, tx.Commit() } +func (r *Repository) RecordInviteActivityInviteEvent(ctx context.Context, event domain.InviteEvent, cycle domain.Cycle, status string, skipReason string, nowMS int64) (domain.ConsumeResult, error) { + if r == nil || r.db == nil { + return domain.ConsumeResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return domain.ConsumeResult{}, err + } + defer func() { _ = tx.Rollback() }() + inserted, err := insertInviteActivityRewardEvent(ctx, tx, inviteActivityRewardEventRow{ + AppCode: appcode.FromContext(ctx), + EventID: event.EventID, + EventType: event.EventType, + CycleKey: cycle.Key, + InvitedUserID: event.InvitedUserID, + InviterUserID: event.InviterUserID, + CommandID: event.Source, + RechargeType: event.InviteCode, + Status: strings.TrimSpace(status), + SkipReason: strings.TrimSpace(skipReason), + PayloadJSON: event.PayloadJSON, + OccurredAtMS: event.OccurredAtMS, + CreatedAtMS: nowMS, + }) + if err != nil { + return domain.ConsumeResult{}, err + } + if err := tx.Commit(); err != nil { + return domain.ConsumeResult{}, err + } + if !inserted { + return domain.ConsumeResult{Consumed: true, Reason: domain.ReasonAlreadyConsumed}, nil + } + if strings.TrimSpace(status) == "skipped" { + return domain.ConsumeResult{Consumed: false, Reason: strings.TrimSpace(skipReason)}, nil + } + return domain.ConsumeResult{Consumed: true, Reason: domain.ReasonEligible}, nil +} + func (r *Repository) ConsumeInviteActivityValidInviteEvent(ctx context.Context, event domain.ValidInviteEvent, cycle domain.Cycle, nowMS int64) (domain.ConsumeResult, error) { if r == nil || r.db == nil { return domain.ConsumeResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") @@ -266,12 +311,7 @@ func (r *Repository) MarkInviteActivityRewardClaimGranted(ctx context.Context, c ); err != nil { return domain.Claim{}, err } - if _, err := tx.ExecContext(ctx, ` - UPDATE invite_activity_reward_progress - SET claimed_reward_coin_amount = claimed_reward_coin_amount + ?, updated_at_ms = ? - WHERE app_code = ? AND cycle_key = ? AND user_id = ?`, - claim.RewardCoinAmount, grantedAtMS, appcode.FromContext(ctx), claim.CycleKey, claim.UserID, - ); err != nil { + if err := upsertInviteActivityClaimedProgress(ctx, tx, claim.UserID, claim.CycleKey, claim.RewardCoinAmount, grantedAtMS); err != nil { return domain.Claim{}, err } } @@ -458,14 +498,31 @@ func upsertInviteActivityValidProgress(ctx context.Context, tx *sql.Tx, userID i return err } +func upsertInviteActivityClaimedProgress(ctx context.Context, tx *sql.Tx, userID int64, cycleKey string, rewardCoinAmount int64, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO invite_activity_reward_progress ( + app_code, cycle_key, user_id, total_recharge_coin_amount, valid_invite_count, claimed_reward_coin_amount, updated_at_ms + ) VALUES (?, ?, ?, 0, 0, ?, ?) + ON DUPLICATE KEY UPDATE + claimed_reward_coin_amount = claimed_reward_coin_amount + VALUES(claimed_reward_coin_amount), + updated_at_ms = VALUES(updated_at_ms)`, + appcode.FromContext(ctx), cycleKey, userID, rewardCoinAmount, nowMS, + ) + return err +} + func (r *Repository) getInviteActivityRewardConfig(ctx context.Context, tx *sql.Tx) (domain.Config, bool, error) { queryer := queryerFromRepository(r, tx) row := queryer.QueryRowContext(ctx, ` - SELECT app_code, enabled, updated_by_admin_id, created_at_ms, updated_at_ms + SELECT app_code, enabled, per_invite_inviter_reward_coin_amount, per_invite_invitee_reward_coin_amount, + updated_by_admin_id, created_at_ms, updated_at_ms FROM invite_activity_reward_configs WHERE app_code = ?`, appcode.FromContext(ctx)) var config domain.Config - if err := row.Scan(&config.AppCode, &config.Enabled, &config.UpdatedByAdminID, &config.CreatedAtMS, &config.UpdatedAtMS); err != nil { + if err := row.Scan( + &config.AppCode, &config.Enabled, &config.PerInviteInviterRewardCoinAmount, &config.PerInviteInviteeRewardCoinAmount, + &config.UpdatedByAdminID, &config.CreatedAtMS, &config.UpdatedAtMS, + ); err != nil { if errors.Is(err, sql.ErrNoRows) { return domain.Config{}, false, nil } diff --git a/services/activity-service/internal/transport/grpc/invite_activity_reward_server.go b/services/activity-service/internal/transport/grpc/invite_activity_reward_server.go index eccf5fca..10cb4e07 100644 --- a/services/activity-service/internal/transport/grpc/invite_activity_reward_server.go +++ b/services/activity-service/internal/transport/grpc/invite_activity_reward_server.go @@ -96,9 +96,11 @@ func (s *AdminInviteActivityRewardServer) UpdateInviteActivityRewardConfig(ctx c }) } config, err := s.svc.UpdateConfig(ctx, domain.Config{ - Enabled: req.GetEnabled(), - Tiers: tiers, - UpdatedByAdminID: req.GetOperatorAdminId(), + Enabled: req.GetEnabled(), + PerInviteInviterRewardCoinAmount: req.GetPerInviteInviterRewardCoinAmount(), + PerInviteInviteeRewardCoinAmount: req.GetPerInviteInviteeRewardCoinAmount(), + Tiers: tiers, + UpdatedByAdminID: req.GetOperatorAdminId(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -144,12 +146,14 @@ func inviteActivityRewardStatusToProto(status domain.StatusResult) *activityv1.I func inviteActivityRewardConfigToProto(config domain.Config) *activityv1.InviteActivityRewardConfig { resp := &activityv1.InviteActivityRewardConfig{ - AppCode: config.AppCode, - Enabled: config.Enabled, - Tiers: make([]*activityv1.InviteActivityRewardTier, 0, len(config.Tiers)), - UpdatedByAdminId: config.UpdatedByAdminID, - CreatedAtMs: config.CreatedAtMS, - UpdatedAtMs: config.UpdatedAtMS, + AppCode: config.AppCode, + Enabled: config.Enabled, + PerInviteInviterRewardCoinAmount: config.PerInviteInviterRewardCoinAmount, + PerInviteInviteeRewardCoinAmount: config.PerInviteInviteeRewardCoinAmount, + Tiers: make([]*activityv1.InviteActivityRewardTier, 0, len(config.Tiers)), + UpdatedByAdminId: config.UpdatedByAdminID, + CreatedAtMs: config.CreatedAtMS, + UpdatedAtMs: config.UpdatedAtMS, } for _, tier := range config.Tiers { resp.Tiers = append(resp.Tiers, inviteActivityRewardTierToProto(tier)) diff --git a/services/activity-service/tools/invite-activity-local-flow/main.go b/services/activity-service/tools/invite-activity-local-flow/main.go index 4943a6b8..1af6c858 100644 --- a/services/activity-service/tools/invite-activity-local-flow/main.go +++ b/services/activity-service/tools/invite-activity-local-flow/main.go @@ -14,6 +14,7 @@ import ( "google.golang.org/grpc/credentials/insecure" activityv1 "hyapp.local/api/proto/activity/v1" userv1 "hyapp.local/api/proto/user/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" domain "hyapp/services/activity-service/internal/domain/inviteactivity" inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity" @@ -34,6 +35,7 @@ type walletEntrySummary struct { func main() { var ( activityAddr = flag.String("activity_addr", "127.0.0.1:14006", "activity-service gRPC address") + walletAddr = flag.String("wallet_addr", "127.0.0.1:13004", "wallet-service gRPC address") activityDSN = flag.String("activity_dsn", "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_activity?parseTime=true&charset=utf8mb4&loc=UTC", "activity MySQL DSN") walletDSN = flag.String("wallet_dsn", "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_wallet?parseTime=true&charset=utf8mb4&loc=UTC", "wallet MySQL DSN") ) @@ -48,6 +50,10 @@ func main() { defer conn.Close() adminClient := activityv1.NewAdminInviteActivityRewardServiceClient(conn) rewardClient := activityv1.NewInviteActivityRewardServiceClient(conn) + walletConn, err := grpc.DialContext(ctx, *walletAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + must("dial wallet-service", err) + defer walletConn.Close() + walletClient := walletv1.NewWalletServiceClient(walletConn) activityRepo, err := mysqlstorage.Open(ctx, *activityDSN) must("open activity mysql", err) @@ -60,6 +66,7 @@ func main() { runID := fmt.Sprintf("iar_%d", time.Now().UnixMilli()) inviterUserID := int64(880000000000) + time.Now().Unix()%10000000 + invitedUserID := inviterUserID + 9001 now := time.Now().UTC() cycle := inviteactivityservice.CycleForTime(now) @@ -67,7 +74,14 @@ func main() { rechargeTier := findTier(config.GetTiers(), domain.RewardTypeRecharge, runID+"_recharge_160k") validTier := findTier(config.GetTiers(), domain.RewardTypeValidInvite, runID+"_valid_1") - service := inviteactivityservice.New(activityRepo, nil, fakeInviteAttributionSource{inviterUserID: inviterUserID}) + service := inviteactivityservice.New(activityRepo, walletClient, fakeInviteAttributionSource{inviterUserID: inviterUserID}) + consumeInvite(ctx, service, runID, inviterUserID, invitedUserID, now) + afterInviteInviterStatus := getStatus(ctx, rewardClient, inviterUserID) + afterInviteInviteeStatus := getStatus(ctx, rewardClient, invitedUserID) + inviterInviteClaim := findClaim(afterInviteInviterStatus.GetClaims(), domain.RewardTypeInviteInviter) + inviteeInviteClaim := findClaim(afterInviteInviteeStatus.GetClaims(), domain.RewardTypeInvitee) + assert(inviterInviteClaim.GetRewardCoinAmount() == 700, "inviter per invite reward should be 700 coins") + assert(inviteeInviteClaim.GetRewardCoinAmount() == 200, "invitee per invite reward should be 200 coins") consumeRecharge(ctx, service, runID, inviterUserID, 1, "google_play", 80000, now.Add(time.Minute)) consumeRecharge(ctx, service, runID, inviterUserID, 2, "mifapay", 40000, now.Add(2*time.Minute)) consumeRecharge(ctx, service, runID, inviterUserID, 3, "coin_seller_transfer", 40000, now.Add(3*time.Minute)) @@ -85,16 +99,21 @@ func main() { rechargeClaim := claim(ctx, rewardClient, inviterUserID, domain.RewardTypeRecharge, rechargeTier.GetTierId(), runID+"_claim_recharge") validClaim := claim(ctx, rewardClient, inviterUserID, domain.RewardTypeValidInvite, validTier.GetTierId(), runID+"_claim_valid") afterClaim := getStatus(ctx, rewardClient, inviterUserID) - assert(afterClaim.GetProgress().GetClaimedRewardCoinAmount() == 2800, "claimed reward progress should be 2800 coins") + assert(afterClaim.GetProgress().GetClaimedRewardCoinAmount() == 3500, "claimed reward progress should be 3500 coins") + walletInviteInviter := queryWalletEntry(ctx, walletDB, inviterInviteClaim.GetWalletTransactionId()) + walletInvitee := queryWalletEntry(ctx, walletDB, inviteeInviteClaim.GetWalletTransactionId()) walletRecharge := queryWalletEntry(ctx, walletDB, rechargeClaim.GetWalletTransactionId()) walletValid := queryWalletEntry(ctx, walletDB, validClaim.GetWalletTransactionId()) + assert(walletInviteInviter.Coins == 700, "wallet inviter per invite reward should credit 700 coins") + assert(walletInvitee.Coins == 200, "wallet invitee per invite reward should credit 200 coins") assert(walletRecharge.Coins == 2500, "wallet recharge tier reward should credit 2500 coins") assert(walletValid.Coins == 300, "wallet valid invite reward should credit 300 coins") out := map[string]any{ "run_id": runID, "inviter_user_id": inviterUserID, + "invited_user_id": invitedUserID, "cycle": cycle.Key, "progress_before_claim": map[string]int64{ "total_recharge_coin_amount": beforeClaim.GetProgress().GetTotalRechargeCoinAmount(), @@ -107,12 +126,16 @@ func main() { "total_recharge_coin_amount": leaderboardEntry.GetTotalRechargeCoinAmount(), }, "claims": []map[string]any{ + {"reward_type": inviterInviteClaim.GetRewardType(), "tier_code": inviterInviteClaim.GetTierCode(), "reward_coin_amount": inviterInviteClaim.GetRewardCoinAmount(), "wallet_transaction_id": inviterInviteClaim.GetWalletTransactionId()}, + {"reward_type": inviteeInviteClaim.GetRewardType(), "tier_code": inviteeInviteClaim.GetTierCode(), "reward_coin_amount": inviteeInviteClaim.GetRewardCoinAmount(), "wallet_transaction_id": inviteeInviteClaim.GetWalletTransactionId()}, {"reward_type": rechargeClaim.GetRewardType(), "tier_code": rechargeClaim.GetTierCode(), "reward_coin_amount": rechargeClaim.GetRewardCoinAmount(), "wallet_transaction_id": rechargeClaim.GetWalletTransactionId()}, {"reward_type": validClaim.GetRewardType(), "tier_code": validClaim.GetTierCode(), "reward_coin_amount": validClaim.GetRewardCoinAmount(), "wallet_transaction_id": validClaim.GetWalletTransactionId()}, }, "wallet_entries": map[string]walletEntrySummary{ - "recharge": walletRecharge, - "valid_invite": walletValid, + "invite_inviter": walletInviteInviter, + "invite_invitee": walletInvitee, + "recharge": walletRecharge, + "valid_invite": walletValid, }, "progress_after_claim": map[string]int64{ "claimed_reward_coin_amount": afterClaim.GetProgress().GetClaimedRewardCoinAmount(), @@ -124,9 +147,11 @@ func main() { func updateConfig(ctx context.Context, client activityv1.AdminInviteActivityRewardServiceClient, runID string) *activityv1.InviteActivityRewardConfig { resp, err := client.UpdateInviteActivityRewardConfig(ctx, &activityv1.UpdateInviteActivityRewardConfigRequest{ - Meta: meta(runID + "_update_config"), - Enabled: true, - OperatorAdminId: operatorAdminID, + Meta: meta(runID + "_update_config"), + Enabled: true, + PerInviteInviterRewardCoinAmount: 700, + PerInviteInviteeRewardCoinAmount: 200, + OperatorAdminId: operatorAdminID, Tiers: []*activityv1.InviteActivityRewardTier{ {RewardType: domain.RewardTypeRecharge, TierCode: runID + "_recharge_80k", TierName: "Local recharge 80k", ThresholdCoinAmount: 80000, RewardCoinAmount: 1000, Status: tierStatus, SortOrder: 1}, {RewardType: domain.RewardTypeRecharge, TierCode: runID + "_recharge_160k", TierName: "Local recharge 160k", ThresholdCoinAmount: 160000, RewardCoinAmount: 2500, Status: tierStatus, SortOrder: 2}, @@ -138,6 +163,20 @@ func updateConfig(ctx context.Context, client activityv1.AdminInviteActivityRewa return resp.GetConfig() } +func consumeInvite(ctx context.Context, service *inviteactivityservice.Service, runID string, inviterUserID int64, invitedUserID int64, occurredAt time.Time) { + err := service.ConsumeInviteEvent(ctx, domain.InviteEvent{ + AppCode: appCode, + EventID: runID + "_invite_bound_1", + EventType: domain.EventTypeUserInvited, + InvitedUserID: invitedUserID, + InviterUserID: inviterUserID, + InviteCode: runID, + Source: "invite_h5", + OccurredAtMS: occurredAt.UnixMilli(), + }) + must("consume invite bound event", err) +} + func consumeRecharge(ctx context.Context, service *inviteactivityservice.Service, runID string, inviterUserID int64, index int64, rechargeType string, coins int64, occurredAt time.Time) { err := service.ConsumeWalletRechargeEvent(ctx, domain.RechargeEvent{ AppCode: appCode, @@ -219,6 +258,16 @@ func findTier(tiers []*activityv1.InviteActivityRewardTier, rewardType string, t return nil } +func findClaim(claims []*activityv1.InviteActivityRewardClaim, rewardType string) *activityv1.InviteActivityRewardClaim { + for _, claim := range claims { + if claim.GetRewardType() == rewardType { + return claim + } + } + log.Fatalf("claim not found: %s", rewardType) + return nil +} + func findLeaderboardEntry(entries []*activityv1.InviteActivityLeaderboardEntry, userID int64) *activityv1.InviteActivityLeaderboardEntry { for _, entry := range entries { if entry.GetUserId() == userID { diff --git a/services/game-service/deploy/mysql/initdb/001_game_service.sql b/services/game-service/deploy/mysql/initdb/001_game_service.sql index 56b2f514..5ad0cc0a 100644 --- a/services/game-service/deploy/mysql/initdb/001_game_service.sql +++ b/services/game-service/deploy/mysql/initdb/001_game_service.sql @@ -153,6 +153,7 @@ CREATE TABLE IF NOT EXISTS game_dice_participants ( status VARCHAR(32) NOT NULL COMMENT '参与状态', stake_coin BIGINT NOT NULL COMMENT '下注金币', dice_points_json JSON NULL COMMENT '骰子点数', + rps_gesture VARCHAR(32) NOT NULL DEFAULT '' COMMENT '石头剪刀布预选手势', result VARCHAR(32) NOT NULL DEFAULT '' COMMENT '参与结果', payout_coin BIGINT NOT NULL DEFAULT 0 COMMENT '返奖金币', debit_order_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '下注订单 ID', @@ -241,6 +242,16 @@ PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; +-- rock 真实猜拳复用自研局表,但胜负来自参与者预选手势;旧库补空值,后续新局强制写入。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_participants' AND COLUMN_NAME = 'rps_gesture') = 0, + 'ALTER TABLE game_dice_participants ADD COLUMN rps_gesture VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''石头剪刀布预选手势'' AFTER dice_points_json', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + -- 全服等待队列按 app/game/stake/status 命中;旧库没有这个索引时,高并发匹配会退化成扫描历史局。 SET @ddl := IF( (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'game_dice_matches' AND INDEX_NAME = 'idx_game_dice_waiting') = 0, diff --git a/services/game-service/internal/domain/dice/dice.go b/services/game-service/internal/domain/dice/dice.go index 606ad3fd..674c0113 100644 --- a/services/game-service/internal/domain/dice/dice.go +++ b/services/game-service/internal/domain/dice/dice.go @@ -18,6 +18,7 @@ const ( DefaultJoinTTLMillis = int64(60_000) DefaultRobotWaitMS = int64(3_000) DefaultGameID = "dice" + SelfGameIDRock = "rock" // 首版禁止 1 人局,避免单人场景下“自己赢自己”的派奖语义不清;3 人局只是多一行 participant。 DefaultMinPlayers = int32(2) DefaultMaxPlayers = int32(2) @@ -30,6 +31,10 @@ const ( ParticipantResultLose = "lose" ParticipantResultDraw = "draw" + RPSGestureRock = "rock" + RPSGesturePaper = "paper" + RPSGestureScissors = "scissors" + MatchStatusCreated = "created" MatchStatusJoining = "joining" MatchStatusReady = "ready" @@ -116,7 +121,7 @@ type Robot struct { UpdatedAtMS int64 } -// ExploreWinner 是 Explore Square 展示用的轻量获胜事实;首版用机器人池生成展示数据,后续真实结算只需要写同一张表。 +// ExploreWinner 是 Explore Square 展示用的轻量获胜事实;只由真实游戏结算写入,读接口不能主动制造默认播报。 type ExploreWinner struct { AppCode string WinID string @@ -186,6 +191,7 @@ type Participant struct { Status string StakeCoin int64 DicePoints []int32 + RPSGesture string Result string PayoutCoin int64 DebitOrderID string @@ -330,6 +336,133 @@ func Score(points []int32) (int32, error) { return point, nil } +// IsRPSGameID 标识复用 self-game match 表的真实石头剪刀布玩法;room-rps 有独立领域模型,不走这里。 +func IsRPSGameID(gameID string) bool { + return strings.TrimSpace(gameID) == SelfGameIDRock +} + +// NormalizeRPSGesture 把 H5 输入收口为三种稳定英文枚举;后续落库、结算和响应都只使用这三个值。 +func NormalizeRPSGesture(value string) (string, bool) { + switch strings.ToLower(strings.TrimSpace(value)) { + case RPSGestureRock: + return RPSGestureRock, true + case RPSGesturePaper: + return RPSGesturePaper, true + case RPSGestureScissors: + return RPSGestureScissors, true + default: + return "", false + } +} + +// RPSGesturePoint 只给 rps 结算事实写入 dice_points_json 做幂等标记和轻量展示兜底,不参与胜负判断。 +func RPSGesturePoint(gesture string) (int32, bool) { + switch strings.TrimSpace(gesture) { + case RPSGestureRock: + return 1, true + case RPSGesturePaper: + return 2, true + case RPSGestureScissors: + return 3, true + default: + return 0, false + } +} + +// RPSWinningGestureFor 返回能击败入参的手势;机器人奖池不足强制玩家输时只改机器人预选,不改真人事实。 +func RPSWinningGestureFor(gesture string) (string, bool) { + switch strings.TrimSpace(gesture) { + case RPSGestureRock: + return RPSGesturePaper, true + case RPSGesturePaper: + return RPSGestureScissors, true + case RPSGestureScissors: + return RPSGestureRock, true + default: + return "", false + } +} + +// rpsCompare 返回 left 相对 right 的胜负:1 表示 left 赢,-1 表示 left 输,0 表示和局。 +func rpsCompare(left string, right string) (int, error) { + left = strings.TrimSpace(left) + right = strings.TrimSpace(right) + if _, ok := RPSGesturePoint(left); !ok { + return 0, xerr.New(xerr.InvalidArgument, "rps gesture is invalid") + } + if _, ok := RPSGesturePoint(right); !ok { + return 0, xerr.New(xerr.InvalidArgument, "rps gesture is invalid") + } + if left == right { + return 0, nil + } + if left == RPSGestureRock && right == RPSGestureScissors || + left == RPSGesturePaper && right == RPSGestureRock || + left == RPSGestureScissors && right == RPSGesturePaper { + return 1, nil + } + return -1, nil +} + +// SettleRPSByGestureWithPool 使用双方已经落库的预选手势结算;rock 不随机、不重选,和局直接退还双方本金并结束本局。 +func SettleRPSByGestureWithPool(participants []Participant, feeBPS int64, poolBPS int64) ([]Participant, string, error) { + // 真实石头剪刀布当前产品只开放 2 人对战;多人局没有明确胜负和分账语义,必须在规则层拒绝。 + if len(participants) != int(DefaultMinPlayers) { + return nil, "", xerr.New(xerr.Conflict, "rps match requires two participants") + } + if feeBPS < 0 || feeBPS >= 10_000 { + return nil, "", xerr.New(xerr.InvalidArgument, "dice fee_bps is invalid") + } + if poolBPS < 0 || feeBPS+poolBPS >= 10_000 { + return nil, "", xerr.New(xerr.InvalidArgument, "dice pool_bps is invalid") + } + settled := append([]Participant(nil), participants...) + sort.SliceStable(settled, func(left int, right int) bool { + return settled[left].SeatNo < settled[right].SeatNo + }) + + var totalStake int64 + for index := range settled { + // rps 的手势必须来自 match/create/join 阶段的预选事实;roll 阶段不能补造或重随机。 + gesture, ok := NormalizeRPSGesture(settled[index].RPSGesture) + if !ok { + return nil, "", xerr.New(xerr.InvalidArgument, "rps gesture is invalid") + } + if settled[index].StakeCoin <= 0 { + return nil, "", xerr.New(xerr.InvalidArgument, "dice stake_coin is invalid") + } + point, _ := RPSGesturePoint(gesture) + settled[index].RPSGesture = gesture + settled[index].DicePoints = []int32{point} + settled[index].Result = ParticipantResultLose + settled[index].PayoutCoin = 0 + totalStake += settled[index].StakeCoin + } + + compare, err := rpsCompare(settled[0].RPSGesture, settled[1].RPSGesture) + if err != nil { + return nil, "", err + } + if compare == 0 { + // 预选手势相同就是本局和局;双方已扣本金后各退回自己的 stake_coin,不进入平台费或奖池抽成。 + for index := range settled { + settled[index].Result = ParticipantResultDraw + settled[index].PayoutCoin = settled[index].StakeCoin + } + return settled, ParticipantResultDraw, nil + } + + winnerIndex := 0 + if compare < 0 { + winnerIndex = 1 + } + // 非和局沿用 self-game 经济模型:总奖池先扣平台费和入池比例,剩余整数金额一次性给赢家。 + payoutPool := totalStake * (10_000 - feeBPS - poolBPS) / 10_000 + settled[winnerIndex].Result = ParticipantResultWin + settled[winnerIndex].PayoutCoin = payoutPool + return settled, ParticipantResultWin, nil +} + // SettleByHighestScore 使用单骰最高点数结算;全部平局只标记 draw,由 service 保持同一局继续重投。 func SettleByHighestScore(participants []Participant, feeBPS int64) ([]Participant, string, error) { return SettleByHighestScoreWithPool(participants, feeBPS, 0) diff --git a/services/game-service/internal/service/dice/service.go b/services/game-service/internal/service/dice/service.go index 3e9a3591..1640deac 100644 --- a/services/game-service/internal/service/dice/service.go +++ b/services/game-service/internal/service/dice/service.go @@ -25,7 +25,7 @@ type Repository interface { MarkOrderFailed(ctx context.Context, appCode string, orderID string, status string, code string, message string, nowMs int64) error CreateDiceMatch(ctx context.Context, match dicedomain.Match) (dicedomain.Match, error) - JoinDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) + JoinDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, rpsGesture string, nowMs int64) (dicedomain.Match, error) GetDiceMatch(ctx context.Context, appCode string, matchID string) (dicedomain.Match, error) ClaimDiceMatchForRoll(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) SaveDiceRolls(ctx context.Context, match dicedomain.Match, nowMs int64) (dicedomain.Match, error) @@ -39,8 +39,8 @@ type Repository interface { GetDiceConfig(ctx context.Context, appCode string, gameID string) (dicedomain.Config, error) ListSelfGameConfigs(ctx context.Context, appCode string) ([]dicedomain.Config, error) UpsertDiceConfig(ctx context.Context, config dicedomain.Config, nowMs int64) (dicedomain.Config, error) - FindAndJoinWaitingDiceMatch(ctx context.Context, appCode string, gameID string, userID int64, stakeCoin int64, nowMs int64) (dicedomain.Match, error) - JoinDiceRobot(ctx context.Context, appCode string, matchID string, robotUserID int64, forcedResult string, nowMs int64) (dicedomain.Match, error) + FindAndJoinWaitingDiceMatch(ctx context.Context, appCode string, gameID string, userID int64, stakeCoin int64, rpsGesture string, nowMs int64) (dicedomain.Match, error) + JoinDiceRobot(ctx context.Context, appCode string, matchID string, robotUserID int64, forcedResult string, rpsGesture string, nowMs int64) (dicedomain.Match, error) CancelDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) PickActiveDiceRobot(ctx context.Context, appCode string, gameID string, matchID string) (dicedomain.Robot, error) AdjustDicePool(ctx context.Context, adjustment dicedomain.PoolAdjustment) (dicedomain.PoolAdjustment, error) @@ -48,7 +48,7 @@ type Repository interface { RegisterDiceRobots(ctx context.Context, appCode string, gameID string, userIDs []int64, adminID int64, nowMs int64) ([]dicedomain.Robot, error) SetDiceRobotStatus(ctx context.Context, appCode string, gameID string, userID int64, status string, nowMs int64) (dicedomain.Robot, error) DeleteDiceRobot(ctx context.Context, appCode string, gameID string, userID int64) error - EnsureExploreWinners(ctx context.Context, appCode string, nowMs int64) error + RecordExploreWinner(ctx context.Context, winner dicedomain.ExploreWinner) error ListExploreWinners(ctx context.Context, appCode string, pageSize int32) ([]dicedomain.ExploreWinner, error) } @@ -83,36 +83,43 @@ type CreateMatchCommand struct { StakeCoin int64 MinPlayers int32 MaxPlayers int32 + RPSGesture string } type JoinMatchCommand struct { - AppCode string - RequestID string - UserID int64 - MatchID string + AppCode string + RequestID string + UserID int64 + MatchID string + GameID string + RPSGesture string } type GetMatchCommand struct { AppCode string UserID int64 MatchID string + GameID string } type RollMatchCommand struct { - AppCode string - RequestID string - UserID int64 - MatchID string + AppCode string + RequestID string + UserID int64 + MatchID string + GameID string + RPSGesture string } type MatchCommand struct { - AppCode string - RequestID string - UserID int64 - GameID string - RoomID string - RegionID int64 - StakeCoin int64 + AppCode string + RequestID string + UserID int64 + GameID string + RoomID string + RegionID int64 + StakeCoin int64 + RPSGesture string } type CancelMatchCommand struct { @@ -120,6 +127,7 @@ type CancelMatchCommand struct { RequestID string UserID int64 MatchID string + GameID string } // New 创建骰子游戏用例层;随机数、时间和费率都集中在这里,便于测试和后续按房间配置扩展。 @@ -173,6 +181,10 @@ func (s *Service) CreateMatch(ctx context.Context, command CreateMatchCommand) ( if err != nil { return dicedomain.Match{}, 0, err } + rpsGesture, err := normalizeRPSGestureForGame(game.GameID, command.RPSGesture, true) + if err != nil { + return dicedomain.Match{}, 0, err + } if config.Status != dicedomain.ConfigStatusActive { return dicedomain.Match{}, 0, xerr.New(xerr.Conflict, "dice game config is disabled") } @@ -226,6 +238,7 @@ func (s *Service) CreateMatch(ctx context.Context, command CreateMatchCommand) ( SeatNo: 1, Status: dicedomain.ParticipantStatusJoined, StakeCoin: command.StakeCoin, + RPSGesture: rpsGesture, JoinedAtMS: nowMS, UpdatedAtMS: nowMS, }}, @@ -318,7 +331,7 @@ func (s *Service) ListRobots(ctx context.Context, appCode string, gameID string, return items, next, s.now().UnixMilli(), err } -// ListExploreWinners 给 Explore Square 提供获胜播报列表;当前版本先用现有机器人池兜底生成 20 条数据库事实。 +// ListExploreWinners 给 Explore Square 提供真实获胜播报列表;读接口只读已有结算事实,不生成默认 seed 数据。 func (s *Service) ListExploreWinners(ctx context.Context, appCode string, pageSize int32) ([]dicedomain.ExploreWinner, int64, error) { if s.repository == nil { return nil, 0, xerr.New(xerr.Unavailable, "game repository is not configured") @@ -332,10 +345,6 @@ func (s *Service) ListExploreWinners(ctx context.Context, appCode string, pageSi if pageSize > 50 { pageSize = 50 } - // 读接口先做一次幂等补种:机器人列表是当前唯一稳定来源,写入后客户端每 5 秒轮询只需要覆盖展示列表。 - if err := s.repository.EnsureExploreWinners(ctx, app, nowMS); err != nil { - return nil, 0, err - } items, err := s.repository.ListExploreWinners(ctx, app, pageSize) return items, nowMS, err } @@ -400,11 +409,15 @@ func (s *Service) Match(ctx context.Context, command MatchCommand) (dicedomain.M if !dicedomain.StakeEnabled(config, command.StakeCoin) { return dicedomain.Match{}, 0, xerr.New(xerr.InvalidArgument, "dice stake_coin is not configured") } + rpsGesture, err := normalizeRPSGestureForGame(config.GameID, command.RPSGesture, true) + if err != nil { + return dicedomain.Match{}, 0, err + } if err := s.ensureCoinBalance(ctx, command.RequestID, app, command.UserID, command.StakeCoin); err != nil { return dicedomain.Match{}, 0, err } nowMS := s.now().UnixMilli() - match, err := s.repository.FindAndJoinWaitingDiceMatch(ctx, app, config.GameID, command.UserID, command.StakeCoin, nowMS) + match, err := s.repository.FindAndJoinWaitingDiceMatch(ctx, app, config.GameID, command.UserID, command.StakeCoin, rpsGesture, nowMS) if err == nil { return s.attachRobotIfDue(ctx, command.RequestID, match, nowMS) } @@ -422,6 +435,7 @@ func (s *Service) Match(ctx context.Context, command MatchCommand) (dicedomain.M StakeCoin: command.StakeCoin, MinPlayers: config.MinPlayers, MaxPlayers: config.MaxPlayers, + RPSGesture: rpsGesture, }) } @@ -436,8 +450,19 @@ func (s *Service) JoinMatch(ctx context.Context, command JoinMatchCommand) (dice app := appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, app) nowMS := s.now().UnixMilli() + matchForJoin, err := s.expectedMatch(ctx, app, command.MatchID, command.GameID) + if err != nil { + return dicedomain.Match{}, 0, err + } + rpsGesture, err := normalizeRPSGestureForGame(matchForJoin.GameID, command.RPSGesture, true) + if err != nil { + return dicedomain.Match{}, 0, err + } + if err := validateRPSParticipantGesture(matchForJoin, command.UserID, rpsGesture); err != nil { + return dicedomain.Match{}, 0, err + } // 加入只修改参与者事实;如果用户重复点击 join,仓储按唯一参与者返回已有局快照。 - match, err := s.repository.JoinDiceMatch(ctx, app, strings.TrimSpace(command.MatchID), command.UserID, nowMS) + match, err := s.repository.JoinDiceMatch(ctx, app, strings.TrimSpace(command.MatchID), command.UserID, rpsGesture, nowMS) if err != nil { return dicedomain.Match{}, 0, err } @@ -459,6 +484,9 @@ func (s *Service) GetMatch(ctx context.Context, command GetMatchCommand) (dicedo if err != nil { return dicedomain.Match{}, 0, err } + if err := expectedGameIDMatches(command.GameID, match); err != nil { + return dicedomain.Match{}, 0, err + } return s.attachRobotIfDue(ctx, "", match, nowMS) } @@ -473,6 +501,9 @@ func (s *Service) CancelMatch(ctx context.Context, command CancelMatchCommand) ( app := appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, app) nowMS := s.now().UnixMilli() + if err := s.ensureMatchGame(ctx, app, command.MatchID, command.GameID); err != nil { + return dicedomain.Match{}, 0, err + } match, err := s.repository.CancelDiceMatch(ctx, app, strings.TrimSpace(command.MatchID), command.UserID, nowMS) return match, nowMS, err } @@ -491,6 +522,17 @@ func (s *Service) RollMatch(ctx context.Context, command RollMatchCommand) (dice app := appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, app) nowMS := s.now().UnixMilli() + matchForRoll, err := s.expectedMatch(ctx, app, command.MatchID, command.GameID) + if err != nil { + return dicedomain.Match{}, 0, err + } + rpsGesture, err := normalizeRPSGestureForGame(matchForRoll.GameID, command.RPSGesture, false) + if err != nil { + return dicedomain.Match{}, 0, err + } + if err := validateOptionalRPSRollGesture(matchForRoll, command.UserID, rpsGesture); err != nil { + return dicedomain.Match{}, 0, err + } // ClaimDiceMatchForRoll 用 match 行锁把局切到 settling;锁提交后才调用钱包,减少数据库锁持有时间。 match, err := s.repository.ClaimDiceMatchForRoll(ctx, app, strings.TrimSpace(command.MatchID), command.UserID, nowMS) @@ -504,6 +546,9 @@ func (s *Service) RollMatch(ctx context.Context, command RollMatchCommand) (dice if len(match.Participants) < int(match.MinPlayers) { return dicedomain.Match{}, 0, xerr.New(xerr.Conflict, "dice match does not have enough participants") } + if err := validateOptionalRPSRollGesture(match, command.UserID, rpsGesture); err != nil { + return dicedomain.Match{}, 0, err + } normalizeMatchEconomicSnapshot(&match) for index := range match.Participants { @@ -533,7 +578,29 @@ func (s *Service) RollMatch(ctx context.Context, command RollMatchCommand) (dice } } - if !dicedomain.HasRolls(match.Participants) { + if dicedomain.IsRPSGameID(match.GameID) { + if !dicedomain.HasRolls(match.Participants) { + // rock 的胜负只来自 match/create/join 阶段已经保存的 rps_gesture;roll 阶段只负责扣款后把固定结果落库。 + settledParticipants, result, err := dicedomain.SettleRPSByGestureWithPool(match.Participants, int64(match.FeeBPS), int64(match.PoolBPS)) + if err != nil { + return dicedomain.Match{}, 0, err + } + match.Participants = settledParticipants + match.Result = result + match, err = s.repository.SaveDiceRolls(ctx, match, nowMS) + if err != nil { + return dicedomain.Match{}, 0, err + } + } else if match.Result == "" { + // 兼容 rps 结果点数已写入但 match.result 空的半完成状态,仍按手势重新推导同一份派奖/退款金额。 + settledParticipants, result, err := dicedomain.SettleRPSByGestureWithPool(match.Participants, int64(match.FeeBPS), int64(match.PoolBPS)) + if err != nil { + return dicedomain.Match{}, 0, err + } + match.Participants = settledParticipants + match.Result = result + } + } else if !dicedomain.HasRolls(match.Participants) { // 只有首次结算可以随机点数;点数一旦落库,所有后续重试必须复用旧点数,避免改变输赢结果。 if err := s.rollParticipants(match.Participants); err != nil { return dicedomain.Match{}, 0, err @@ -615,10 +682,132 @@ func (s *Service) RollMatch(ctx context.Context, command RollMatchCommand) (dice if err := s.repository.MarkDiceMatchSettled(ctx, app, match.MatchID, match.Result, nowMS); err != nil { return dicedomain.Match{}, 0, err } + // Square 播报是结算后的附属展示事实,只记录真人赢家且用 match_id+user_id 做幂等键;写失败不能让已经完成的钱包结算回滚成失败局。 + _ = s.recordExploreWinners(ctx, match, nowMS) saved, err := s.repository.GetDiceMatch(ctx, app, match.MatchID) return saved, nowMS, err } +func (s *Service) recordExploreWinners(ctx context.Context, match dicedomain.Match, nowMS int64) error { + gameCode := exploreGameCodeForMatch(match.GameID) + if gameCode == "" { + return nil + } + for _, participant := range match.Participants { + if participant.ParticipantType == dicedomain.ParticipantTypeRobot { + // 机器人只是匹配补位和奖池对手,不应该出现在 Explore Square 的“用户获胜”播报里。 + continue + } + if participant.Result != dicedomain.ParticipantResultWin || participant.PayoutCoin <= 0 { + // 输家、和局和没有派奖金额的半完成状态都不能生成获胜播报,避免客户端展示假的中奖金额。 + continue + } + winner := dicedomain.ExploreWinner{ + AppCode: match.AppCode, + WinID: fmt.Sprintf("self_%s_%d", match.MatchID, participant.UserID), + GameCode: gameCode, + GameID: strings.TrimSpace(match.GameID), + UserID: participant.UserID, + CoinAmount: participant.PayoutCoin, + WonAtMS: nowMS, + CreatedAtMS: nowMS, + UpdatedAtMS: nowMS, + } + if err := s.repository.RecordExploreWinner(ctx, winner); err != nil { + return err + } + } + return nil +} + +func exploreGameCodeForMatch(gameID string) string { + switch strings.TrimSpace(gameID) { + case "", dicedomain.DefaultGameID: + return dicedomain.ExploreGameCodeDice + case dicedomain.SelfGameIDRock: + return dicedomain.ExploreGameCodeRock + default: + return "" + } +} + +func (s *Service) ensureMatchGame(ctx context.Context, appCode string, matchID string, expectedGameID string) error { + _, err := s.expectedMatch(ctx, appCode, matchID, expectedGameID) + return err +} + +func (s *Service) expectedMatch(ctx context.Context, appCode string, matchID string, expectedGameID string) (dicedomain.Match, error) { + expectedGameID = strings.TrimSpace(expectedGameID) + if expectedGameID == "" { + return s.repository.GetDiceMatch(ctx, appCode, strings.TrimSpace(matchID)) + } + match, err := s.repository.GetDiceMatch(ctx, appCode, strings.TrimSpace(matchID)) + if err != nil { + return dicedomain.Match{}, err + } + if err := expectedGameIDMatches(expectedGameID, match); err != nil { + return dicedomain.Match{}, err + } + return match, nil +} + +func expectedGameIDMatches(expectedGameID string, match dicedomain.Match) error { + expectedGameID = strings.TrimSpace(expectedGameID) + if expectedGameID == "" { + return nil + } + if strings.TrimSpace(match.GameID) == expectedGameID { + return nil + } + // dice 和 rps 共用自研 match 表,HTTP 路径必须用 game_id 再切一刀;返回 not found,避免向客户端泄露其它游戏局存在。 + return xerr.New(xerr.NotFound, "self game match not found") +} + +func normalizeRPSGestureForGame(gameID string, rawGesture string, required bool) (string, error) { + if !dicedomain.IsRPSGameID(gameID) { + return "", nil + } + if strings.TrimSpace(rawGesture) == "" && !required { + return "", nil + } + gesture, ok := dicedomain.NormalizeRPSGesture(rawGesture) + if !ok { + return "", xerr.New(xerr.InvalidArgument, "rps gesture is invalid") + } + return gesture, nil +} + +func validateRPSParticipantGesture(match dicedomain.Match, userID int64, gesture string) error { + if !dicedomain.IsRPSGameID(match.GameID) { + return nil + } + if strings.TrimSpace(gesture) == "" { + return xerr.New(xerr.InvalidArgument, "rps gesture is invalid") + } + for _, participant := range match.Participants { + if participant.UserID != userID { + continue + } + selected, ok := dicedomain.NormalizeRPSGesture(participant.RPSGesture) + if !ok { + return xerr.New(xerr.Conflict, "rps selected gesture is missing") + } + if selected != gesture { + // 同一个用户重复 join/roll 只能确认原手势,不能通过重试把已经排队的拳换掉。 + return xerr.New(xerr.Conflict, "rps gesture does not match selected gesture") + } + return nil + } + return nil +} + +func validateOptionalRPSRollGesture(match dicedomain.Match, userID int64, gesture string) error { + if !dicedomain.IsRPSGameID(match.GameID) || strings.TrimSpace(gesture) == "" { + return nil + } + return validateRPSParticipantGesture(match, userID, gesture) +} + func (s *Service) configForMatch(ctx context.Context, appCode string, gameID string) (dicedomain.Config, error) { config, err := s.repository.GetDiceConfig(ctx, appCode, strings.TrimSpace(gameID)) if err == nil { @@ -689,13 +878,55 @@ func (s *Service) attachRobotIfDue(ctx context.Context, requestID string, match if config.PoolBalanceCoin < requiredPool { forcedResult = dicedomain.ForcedResultPlayerLose } - joined, err := s.repository.JoinDiceRobot(ctx, match.AppCode, match.MatchID, robot.UserID, forcedResult, nowMS) + robotRPSGesture := "" + if dicedomain.IsRPSGameID(match.GameID) { + robotRPSGesture, err = s.robotRPSGestureForMatch(match, forcedResult) + if err != nil { + return dicedomain.Match{}, 0, err + } + } + joined, err := s.repository.JoinDiceRobot(ctx, match.AppCode, match.MatchID, robot.UserID, forcedResult, robotRPSGesture, nowMS) if err != nil { return dicedomain.Match{}, 0, err } return joined, nowMS, nil } +func (s *Service) robotRPSGestureForMatch(match dicedomain.Match, forcedResult string) (string, error) { + var humanGesture string + for _, participant := range match.Participants { + if participant.ParticipantType == dicedomain.ParticipantTypeRobot { + continue + } + gesture, ok := dicedomain.NormalizeRPSGesture(participant.RPSGesture) + if !ok { + return "", xerr.New(xerr.InvalidArgument, "rps gesture is invalid") + } + humanGesture = gesture + break + } + if humanGesture == "" { + return "", xerr.New(xerr.Conflict, "rps match does not have human participant") + } + if forcedResult == dicedomain.ForcedResultPlayerLose { + // 奖池不足时不能随机出玩家赢的结果;机器人只选择能击败真人预选手势的那一拳。 + gesture, ok := dicedomain.RPSWinningGestureFor(humanGesture) + if !ok { + return "", xerr.New(xerr.InvalidArgument, "rps gesture is invalid") + } + return gesture, nil + } + index, err := s.randomInt(3) + if err != nil { + return "", err + } + gestures := []string{dicedomain.RPSGestureRock, dicedomain.RPSGesturePaper, dicedomain.RPSGestureScissors} + if index < 0 || index >= len(gestures) { + return "", xerr.New(xerr.InvalidArgument, "random rps gesture is invalid") + } + return gestures[index], nil +} + func (s *Service) forceHumanLose(participants []dicedomain.Participant) { for index := range participants { if participants[index].ParticipantType == dicedomain.ParticipantTypeRobot { diff --git a/services/game-service/internal/service/dice/service_test.go b/services/game-service/internal/service/dice/service_test.go index a5e9a3b9..8b6118d6 100644 --- a/services/game-service/internal/service/dice/service_test.go +++ b/services/game-service/internal/service/dice/service_test.go @@ -75,6 +75,16 @@ func TestRollMatchDebitsRollsAndPaysWinner(t *testing.T) { if repo.levelDebitOrders != 2 { t.Fatalf("debit orders should still feed game order success path, got %d", repo.levelDebitOrders) } + if len(repo.exploreWinners) != 1 { + t.Fatalf("explore winner records = %d, want 1", len(repo.exploreWinners)) + } + exploreWinner := repo.exploreWinners[0] + if exploreWinner.GameCode != dicedomain.ExploreGameCodeDice || exploreWinner.GameID != "dice" || exploreWinner.UserID != 101 || exploreWinner.CoinAmount != 188 { + t.Fatalf("explore winner mismatch: %+v", exploreWinner) + } + if exploreWinner.WinID == "" || exploreWinner.WonAtMS != 1700000000000 { + t.Fatalf("explore winner identity/time mismatch: %+v", exploreWinner) + } } func TestRollMatchRequiresMinimumParticipantsBeforeWalletDebit(t *testing.T) { @@ -171,6 +181,285 @@ func TestRollMatchDrawRerollsUntilWinner(t *testing.T) { } } +func TestRPSRollUsesPreselectedGesturesWithoutRandomRoll(t *testing.T) { + repo := newFakeDiceRepository() + configureFakeRock(repo) + wallet := &fakeDiceWallet{balances: map[int64]int64{101: 1000, 202: 1000}} + svc := New(Config{FeeBPS: 500}, repo, wallet) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + svc.randomInt = func(max int) (int, error) { + t.Fatalf("rock settlement must not call randomInt, max=%d", max) + return 0, nil + } + + created, _, err := svc.CreateMatch(context.Background(), CreateMatchCommand{ + AppCode: "lalu", + RequestID: "req-create-rps", + UserID: 101, + GameID: "rock", + StakeCoin: 100, + MinPlayers: 2, + MaxPlayers: 2, + RPSGesture: "rock", + }) + if err != nil { + t.Fatalf("CreateMatch failed: %v", err) + } + if _, _, err := svc.JoinMatch(context.Background(), JoinMatchCommand{AppCode: "lalu", RequestID: "req-join-rps", UserID: 202, MatchID: created.MatchID, GameID: "rock", RPSGesture: "scissors"}); err != nil { + t.Fatalf("JoinMatch failed: %v", err) + } + + settled, _, err := svc.RollMatch(context.Background(), RollMatchCommand{AppCode: "lalu", RequestID: "req-roll-rps", UserID: 101, MatchID: created.MatchID, GameID: "rock", RPSGesture: "rock"}) + if err != nil { + t.Fatalf("RollMatch failed: %v", err) + } + if settled.Status != dicedomain.MatchStatusSettled || settled.Result != dicedomain.ParticipantResultWin { + t.Fatalf("rock match should settle with winner: %+v", settled) + } + winner := participantByUser(settled.Participants, 101) + loser := participantByUser(settled.Participants, 202) + if winner.RPSGesture != "rock" || len(winner.DicePoints) != 1 || winner.DicePoints[0] != 1 || winner.Result != dicedomain.ParticipantResultWin || winner.PayoutCoin != 188 || winner.BalanceAfter != 1088 { + t.Fatalf("winner rps settlement mismatch: %+v", winner) + } + if loser.RPSGesture != "scissors" || len(loser.DicePoints) != 1 || loser.DicePoints[0] != 3 || loser.Result != dicedomain.ParticipantResultLose || loser.PayoutCoin != 0 || loser.BalanceAfter != 900 { + t.Fatalf("loser rps settlement mismatch: %+v", loser) + } + if wallet.calls != 3 { + t.Fatalf("rock win should debit both and pay winner, wallet calls=%d", wallet.calls) + } + if len(repo.exploreWinners) != 1 || repo.exploreWinners[0].GameCode != dicedomain.ExploreGameCodeRock || repo.exploreWinners[0].UserID != 101 { + t.Fatalf("rock explore winner mismatch: %+v", repo.exploreWinners) + } +} + +func TestRPSDrawRefundsBothAndSettles(t *testing.T) { + repo := newFakeDiceRepository() + configureFakeRock(repo) + wallet := &fakeDiceWallet{balances: map[int64]int64{101: 1000, 202: 1000}} + svc := New(Config{FeeBPS: 500}, repo, wallet) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + svc.randomInt = func(max int) (int, error) { + t.Fatalf("rock draw settlement must not call randomInt, max=%d", max) + return 0, nil + } + + created, _, err := svc.CreateMatch(context.Background(), CreateMatchCommand{ + AppCode: "lalu", + RequestID: "req-create-rps-draw", + UserID: 101, + GameID: "rock", + StakeCoin: 100, + MinPlayers: 2, + MaxPlayers: 2, + RPSGesture: "rock", + }) + if err != nil { + t.Fatalf("CreateMatch failed: %v", err) + } + if _, _, err := svc.JoinMatch(context.Background(), JoinMatchCommand{AppCode: "lalu", RequestID: "req-join-rps-draw", UserID: 202, MatchID: created.MatchID, GameID: "rock", RPSGesture: "rock"}); err != nil { + t.Fatalf("JoinMatch failed: %v", err) + } + + settled, _, err := svc.RollMatch(context.Background(), RollMatchCommand{AppCode: "lalu", RequestID: "req-roll-rps-draw", UserID: 101, MatchID: created.MatchID, GameID: "rock"}) + if err != nil { + t.Fatalf("RollMatch failed: %v", err) + } + if settled.Status != dicedomain.MatchStatusSettled || settled.Result != dicedomain.ParticipantResultDraw || settled.RoundNo != 1 { + t.Fatalf("rock draw should settle without reroll: %+v", settled) + } + for _, participant := range settled.Participants { + if participant.Result != dicedomain.ParticipantResultDraw || participant.PayoutCoin != participant.StakeCoin || participant.BalanceAfter != 1000 || participant.RPSGesture != "rock" { + t.Fatalf("draw participant should be refunded: %+v", participant) + } + } + if wallet.calls != 4 { + t.Fatalf("rock draw should debit and refund both users, wallet calls=%d", wallet.calls) + } + if len(repo.exploreWinners) != 0 { + t.Fatalf("draw must not record explore winners: %+v", repo.exploreWinners) + } +} + +func TestRPSRequiresGestureBeforeMatchmaking(t *testing.T) { + tests := []struct { + name string + call func(*Service) (dicedomain.Match, int64, error) + }{ + { + name: "create", + call: func(svc *Service) (dicedomain.Match, int64, error) { + return svc.CreateMatch(context.Background(), CreateMatchCommand{AppCode: "lalu", UserID: 101, GameID: "rock", StakeCoin: 100, MinPlayers: 2, MaxPlayers: 2}) + }, + }, + { + name: "match", + call: func(svc *Service) (dicedomain.Match, int64, error) { + return svc.Match(context.Background(), MatchCommand{AppCode: "lalu", UserID: 101, GameID: "rock", StakeCoin: 100}) + }, + }, + { + name: "join", + call: func(svc *Service) (dicedomain.Match, int64, error) { + return svc.JoinMatch(context.Background(), JoinMatchCommand{AppCode: "lalu", UserID: 202, MatchID: "match-rock-1", GameID: "rock"}) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + repo := newFakeDiceRepository() + configureFakeRock(repo) + repo.match = dicedomain.Match{ + AppCode: "lalu", + MatchID: "match-rock-1", + GameID: "rock", + PlatformCode: "dice", + ProviderGameID: "rock", + MinPlayers: 2, + MaxPlayers: 2, + CurrentPlayers: 1, + StakeCoin: 100, + Status: dicedomain.MatchStatusCreated, + Participants: []dicedomain.Participant{{ + AppCode: "lalu", + MatchID: "match-rock-1", + UserID: 101, + SeatNo: 1, + Status: dicedomain.ParticipantStatusJoined, + StakeCoin: 100, + RPSGesture: "paper", + }}, + } + wallet := &fakeDiceWallet{balances: map[int64]int64{101: 1000, 202: 1000}} + svc := New(Config{}, repo, wallet) + + _, _, err := tt.call(svc) + + if !xerr.IsCode(err, xerr.InvalidArgument) { + t.Fatalf("%s should reject missing rps gesture, got %v", tt.name, err) + } + if wallet.calls != 0 { + t.Fatalf("%s should reject before wallet mutation, calls=%d", tt.name, wallet.calls) + } + }) + } +} + +func TestRPSRollRejectsGestureMismatchBeforeMutation(t *testing.T) { + repo := newFakeDiceRepository() + configureFakeRock(repo) + repo.match = dicedomain.Match{ + AppCode: "lalu", + MatchID: "match-rock-1", + GameID: "rock", + PlatformCode: "dice", + ProviderGameID: "rock", + MinPlayers: 2, + MaxPlayers: 2, + CurrentPlayers: 2, + StakeCoin: 100, + Status: dicedomain.MatchStatusReady, + Participants: []dicedomain.Participant{ + {AppCode: "lalu", MatchID: "match-rock-1", UserID: 101, SeatNo: 1, Status: dicedomain.ParticipantStatusJoined, StakeCoin: 100, RPSGesture: "rock"}, + {AppCode: "lalu", MatchID: "match-rock-1", UserID: 202, SeatNo: 2, Status: dicedomain.ParticipantStatusJoined, StakeCoin: 100, RPSGesture: "paper"}, + }, + } + wallet := &fakeDiceWallet{balances: map[int64]int64{101: 1000, 202: 1000}} + svc := New(Config{}, repo, wallet) + + _, _, err := svc.RollMatch(context.Background(), RollMatchCommand{AppCode: "lalu", UserID: 101, MatchID: "match-rock-1", GameID: "rock", RPSGesture: "scissors"}) + + if !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("roll should reject changed rps gesture, got %v", err) + } + if repo.match.Status != dicedomain.MatchStatusReady || wallet.calls != 0 { + t.Fatalf("gesture mismatch must not claim roll or call wallet: match=%+v wallet=%d", repo.match, wallet.calls) + } +} + +func TestExploreGameCodeForMatchUsesSelfGameIDs(t *testing.T) { + tests := []struct { + gameID string + want string + }{ + {gameID: "dice", want: dicedomain.ExploreGameCodeDice}, + {gameID: "rock", want: dicedomain.ExploreGameCodeRock}, + {gameID: "room_rps", want: ""}, + {gameID: "ocean_hunt", want: ""}, + } + for _, tt := range tests { + if got := exploreGameCodeForMatch(tt.gameID); got != tt.want { + t.Fatalf("exploreGameCodeForMatch(%q) = %q, want %q", tt.gameID, got, tt.want) + } + } +} + +func TestSelfGameOperationsRejectWrongGameIDBeforeMutation(t *testing.T) { + tests := []struct { + name string + call func(*Service) (dicedomain.Match, int64, error) + }{ + { + name: "join", + call: func(svc *Service) (dicedomain.Match, int64, error) { + return svc.JoinMatch(context.Background(), JoinMatchCommand{AppCode: "lalu", UserID: 202, MatchID: "match-rock-1", GameID: "dice"}) + }, + }, + { + name: "get", + call: func(svc *Service) (dicedomain.Match, int64, error) { + return svc.GetMatch(context.Background(), GetMatchCommand{AppCode: "lalu", UserID: 101, MatchID: "match-rock-1", GameID: "dice"}) + }, + }, + { + name: "roll", + call: func(svc *Service) (dicedomain.Match, int64, error) { + return svc.RollMatch(context.Background(), RollMatchCommand{AppCode: "lalu", UserID: 101, MatchID: "match-rock-1", GameID: "dice"}) + }, + }, + { + name: "cancel", + call: func(svc *Service) (dicedomain.Match, int64, error) { + return svc.CancelMatch(context.Background(), CancelMatchCommand{AppCode: "lalu", UserID: 101, MatchID: "match-rock-1", GameID: "dice"}) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + repo := newFakeDiceRepository() + repo.match = dicedomain.Match{ + AppCode: "lalu", + MatchID: "match-rock-1", + GameID: "rock", + PlatformCode: "dice", + ProviderGameID: "rock", + MinPlayers: 2, + MaxPlayers: 2, + CurrentPlayers: 1, + StakeCoin: 100, + Status: dicedomain.MatchStatusCreated, + Participants: []dicedomain.Participant{{ + AppCode: "lalu", + MatchID: "match-rock-1", + UserID: 101, + SeatNo: 1, + Status: dicedomain.ParticipantStatusJoined, + StakeCoin: 100, + }}, + } + svc := New(Config{}, repo, &fakeDiceWallet{balances: map[int64]int64{101: 1000, 202: 1000}}) + + _, _, err := tt.call(svc) + + if !xerr.IsCode(err, xerr.NotFound) { + t.Fatalf("%s should hide cross-game match as not found, got %v", tt.name, err) + } + if repo.match.Status != dicedomain.MatchStatusCreated || len(repo.match.Participants) != 1 { + t.Fatalf("%s must not mutate mismatched game match: %+v", tt.name, repo.match) + } + }) + } +} + type fakeDiceRepository struct { game gamedomain.LaunchableGame match dicedomain.Match @@ -178,6 +467,7 @@ type fakeDiceRepository struct { poolBalance int64 orders map[string]gamedomain.GameOrder levelDebitOrders int + exploreWinners []dicedomain.ExploreWinner } func newFakeDiceRepository() *fakeDiceRepository { @@ -209,6 +499,13 @@ func newFakeDiceRepository() *fakeDiceRepository { } } +func configureFakeRock(repo *fakeDiceRepository) { + repo.game.CatalogItem.GameID = "rock" + repo.game.CatalogItem.ProviderGameID = "rock" + repo.game.CatalogItem.GameName = "Rock Paper Scissors" + repo.config.GameID = "rock" +} + func (r *fakeDiceRepository) GetLaunchableGame(context.Context, string, string) (gamedomain.LaunchableGame, error) { return r.game, nil } @@ -252,7 +549,7 @@ func (r *fakeDiceRepository) CreateDiceMatch(_ context.Context, match dicedomain return cloneDiceMatch(r.match), nil } -func (r *fakeDiceRepository) JoinDiceMatch(_ context.Context, _ string, _ string, userID int64, nowMs int64) (dicedomain.Match, error) { +func (r *fakeDiceRepository) JoinDiceMatch(_ context.Context, _ string, _ string, userID int64, rpsGesture string, nowMs int64) (dicedomain.Match, error) { for _, participant := range r.match.Participants { if participant.UserID == userID { return cloneDiceMatch(r.match), nil @@ -266,6 +563,7 @@ func (r *fakeDiceRepository) JoinDiceMatch(_ context.Context, _ string, _ string SeatNo: seatNo, Status: dicedomain.ParticipantStatusJoined, StakeCoin: r.match.StakeCoin, + RPSGesture: strings.TrimSpace(rpsGesture), JoinedAtMS: nowMs, UpdatedAtMS: nowMs, }) @@ -305,6 +603,7 @@ func (r *fakeDiceRepository) SaveDiceRolls(_ context.Context, match dicedomain.M for index := range r.match.Participants { if r.match.Participants[index].UserID == source.UserID { r.match.Participants[index].DicePoints = append([]int32(nil), source.DicePoints...) + r.match.Participants[index].RPSGesture = source.RPSGesture r.match.Participants[index].Result = source.Result r.match.Participants[index].PayoutCoin = source.PayoutCoin r.match.Participants[index].UpdatedAtMS = nowMs @@ -322,6 +621,7 @@ func (r *fakeDiceRepository) SaveDiceDrawForReroll(_ context.Context, match dice for index := range r.match.Participants { if r.match.Participants[index].UserID == source.UserID { r.match.Participants[index].DicePoints = append([]int32(nil), source.DicePoints...) + r.match.Participants[index].RPSGesture = source.RPSGesture r.match.Participants[index].Result = dicedomain.ParticipantResultDraw r.match.Participants[index].PayoutCoin = 0 r.match.Participants[index].UpdatedAtMS = nowMs @@ -402,11 +702,11 @@ func (r *fakeDiceRepository) UpsertDiceConfig(_ context.Context, config dicedoma return config, nil } -func (r *fakeDiceRepository) FindAndJoinWaitingDiceMatch(context.Context, string, string, int64, int64, int64) (dicedomain.Match, error) { +func (r *fakeDiceRepository) FindAndJoinWaitingDiceMatch(context.Context, string, string, int64, int64, string, int64) (dicedomain.Match, error) { return dicedomain.Match{}, xerr.New(xerr.NotFound, "waiting dice match not found") } -func (r *fakeDiceRepository) JoinDiceRobot(context.Context, string, string, int64, string, int64) (dicedomain.Match, error) { +func (r *fakeDiceRepository) JoinDiceRobot(context.Context, string, string, int64, string, string, int64) (dicedomain.Match, error) { return dicedomain.Match{}, xerr.New(xerr.NotFound, "dice robot not found") } @@ -445,7 +745,8 @@ func (r *fakeDiceRepository) DeleteDiceRobot(context.Context, string, string, in return nil } -func (r *fakeDiceRepository) EnsureExploreWinners(context.Context, string, int64) error { +func (r *fakeDiceRepository) RecordExploreWinner(_ context.Context, winner dicedomain.ExploreWinner) error { + r.exploreWinners = append(r.exploreWinners, winner) return nil } diff --git a/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go b/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go index 70a437f9..831b3a7a 100644 --- a/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go +++ b/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go @@ -8,6 +8,7 @@ import ( "fmt" "math/big" "strings" + "time" "hyapp/pkg/appcode" "hyapp/pkg/xerr" @@ -41,7 +42,7 @@ func (r *Repository) GetDiceConfig(ctx context.Context, appCode string, gameID s return dicedomain.NormalizeConfig(config) } -// ListSelfGameConfigs 当前只有 dice 一个自研游戏;仍返回数组,方便后台后续追加更多自研游戏。 +// ListSelfGameConfigs 返回 dice、rock 等自研游戏配置;后台列表直接以配置表为准,避免依赖第三方 catalog。 func (r *Repository) ListSelfGameConfigs(ctx context.Context, appCode string) ([]dicedomain.Config, error) { app := appcode.Normalize(appCode) rows, err := r.db.QueryContext(ctx, @@ -129,7 +130,7 @@ func (r *Repository) UpsertDiceConfig(ctx context.Context, config dicedomain.Con } // FindAndJoinWaitingDiceMatch 在全服等待队列里原子抢一个真人局;没有命中时返回 NotFound,让 service 创建新局。 -func (r *Repository) FindAndJoinWaitingDiceMatch(ctx context.Context, appCode string, gameID string, userID int64, stakeCoin int64, nowMs int64) (dicedomain.Match, error) { +func (r *Repository) FindAndJoinWaitingDiceMatch(ctx context.Context, appCode string, gameID string, userID int64, stakeCoin int64, rpsGesture string, nowMs int64) (dicedomain.Match, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { return dicedomain.Match{}, err @@ -172,7 +173,7 @@ func (r *Repository) FindAndJoinWaitingDiceMatch(ctx context.Context, appCode st if match.MatchMode == "" { match.MatchMode = dicedomain.MatchModeHuman } - joined, err := insertDiceParticipantLocked(ctx, tx, match, userID, dicedomain.ParticipantTypeUser, nowMs) + joined, err := insertDiceParticipantLocked(ctx, tx, match, userID, dicedomain.ParticipantTypeUser, rpsGesture, nowMs) if err != nil { return dicedomain.Match{}, err } @@ -186,7 +187,7 @@ func (r *Repository) FindAndJoinWaitingDiceMatch(ctx context.Context, appCode st } // JoinDiceRobot 把一个已登记真实 App 用户作为机器人补位;机器人只参与局事实,不触发钱包扣款。 -func (r *Repository) JoinDiceRobot(ctx context.Context, appCode string, matchID string, robotUserID int64, forcedResult string, nowMs int64) (dicedomain.Match, error) { +func (r *Repository) JoinDiceRobot(ctx context.Context, appCode string, matchID string, robotUserID int64, forcedResult string, rpsGesture string, nowMs int64) (dicedomain.Match, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { return dicedomain.Match{}, err @@ -204,7 +205,7 @@ func (r *Repository) JoinDiceRobot(ctx context.Context, appCode string, matchID if match.CurrentPlayers >= match.MaxPlayers { return dicedomain.Match{}, xerr.New(xerr.Conflict, "dice match is full") } - joined, err := insertDiceParticipantLocked(ctx, tx, match, robotUserID, dicedomain.ParticipantTypeRobot, nowMs) + joined, err := insertDiceParticipantLocked(ctx, tx, match, robotUserID, dicedomain.ParticipantTypeRobot, rpsGesture, nowMs) if err != nil { return dicedomain.Match{}, err } @@ -440,91 +441,57 @@ func (r *Repository) ListDiceRobots(ctx context.Context, appCode string, gameID return robots, nextCursor, nil } -// EnsureExploreWinners 用机器人池补足 Explore Square 首批获胜事实;已有 20 条时不重写,避免轮询接口制造无意义写放大。 -func (r *Repository) EnsureExploreWinners(ctx context.Context, appCode string, nowMs int64) error { - app := appcode.Normalize(appCode) - var existing int - if err := r.db.QueryRowContext(ctx, - `SELECT COUNT(*) - FROM game_explore_winners - WHERE app_code = ?`, +// RecordExploreWinner 写入真实结算后的 Square 播报事实;win_id 由 match_id+user_id 派生,重试结算只会覆盖同一条记录。 +func (r *Repository) RecordExploreWinner(ctx context.Context, winner dicedomain.ExploreWinner) error { + app := appcode.Normalize(winner.AppCode) + winID := strings.TrimSpace(winner.WinID) + gameCode := strings.TrimSpace(winner.GameCode) + gameID := strings.TrimSpace(winner.GameID) + if winID == "" || gameCode == "" || gameID == "" || winner.UserID <= 0 || winner.CoinAmount <= 0 { + return xerr.New(xerr.InvalidArgument, "explore winner is incomplete") + } + nowMs := winner.UpdatedAtMS + if nowMs <= 0 { + nowMs = winner.WonAtMS + } + if nowMs <= 0 { + nowMs = time.Now().UnixMilli() + } + createdAtMS := winner.CreatedAtMS + if createdAtMS <= 0 { + createdAtMS = nowMs + } + wonAtMS := winner.WonAtMS + if wonAtMS <= 0 { + wonAtMS = nowMs + } + _, err := r.db.ExecContext(ctx, + `INSERT INTO game_explore_winners ( + app_code, win_id, game_code, game_id, user_id, display_name, avatar_url, + coin_amount, won_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + game_code = VALUES(game_code), + game_id = VALUES(game_id), + user_id = VALUES(user_id), + display_name = VALUES(display_name), + avatar_url = VALUES(avatar_url), + coin_amount = VALUES(coin_amount), + won_at_ms = VALUES(won_at_ms), + updated_at_ms = VALUES(updated_at_ms)`, app, - ).Scan(&existing); err != nil { - return err - } - if existing >= int(dicedomain.DefaultExploreWinnerPageSize) { - return nil - } - - rows, err := r.db.QueryContext(ctx, - `SELECT app_code, game_id, user_id, status, created_by_admin_id, created_at_ms, updated_at_ms - FROM game_self_game_robots - WHERE app_code = ? AND status = ? - ORDER BY updated_at_ms DESC, user_id DESC - LIMIT ?`, - app, dicedomain.RobotStatusActive, int(dicedomain.DefaultExploreWinnerPageSize), + winID, + gameCode, + gameID, + winner.UserID, + strings.TrimSpace(winner.DisplayName), + strings.TrimSpace(winner.AvatarURL), + winner.CoinAmount, + wonAtMS, + createdAtMS, + nowMs, ) - if err != nil { - return err - } - robots := []dicedomain.Robot{} - for rows.Next() { - var robot dicedomain.Robot - if err := rows.Scan(&robot.AppCode, &robot.GameID, &robot.UserID, &robot.Status, &robot.CreatedByAdminID, &robot.CreatedAtMS, &robot.UpdatedAtMS); err != nil { - _ = rows.Close() - return err - } - robots = append(robots, robot) - } - if err := rows.Close(); err != nil { - return err - } - if len(robots) == 0 { - return nil - } - - tx, err := r.db.BeginTx(ctx, nil) - if err != nil { - return err - } - defer func() { _ = tx.Rollback() }() - // 半成品数据直接重建,保证当前版本始终给客户端一组完整、按时间倒序的 20 条播报。 - if _, err := tx.ExecContext(ctx, `DELETE FROM game_explore_winners WHERE app_code = ?`, app); err != nil { - return err - } - for index := 0; index < int(dicedomain.DefaultExploreWinnerPageSize); index++ { - robot := robots[index%len(robots)] - gameCode := dicedomain.ExploreGameCodeDice - gameID := dicedomain.DefaultGameID - if index%2 == 1 { - gameCode = dicedomain.ExploreGameCodeRock - gameID = "room_rps" - } - if strings.TrimSpace(robot.GameID) != "" && gameCode == dicedomain.ExploreGameCodeDice { - gameID = strings.TrimSpace(robot.GameID) - } - wonAtMS := nowMs - int64(index)*5_000 - if _, err := tx.ExecContext(ctx, - `INSERT INTO game_explore_winners ( - app_code, win_id, game_code, game_id, user_id, display_name, avatar_url, - coin_amount, won_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - app, - fmt.Sprintf("explore_seed_%d_%02d", nowMs, index+1), - gameCode, - gameID, - robot.UserID, - "name", - "", - int64(306000), - wonAtMS, - nowMs, - nowMs, - ); err != nil { - return err - } - } - return tx.Commit() + return err } // ListExploreWinners 只返回当前 app 最新播报;客户端每次轮询直接用返回值替换旧列表。 @@ -549,7 +516,7 @@ func (r *Repository) ListExploreWinners(ctx context.Context, appCode string, pag return nil, err } defer rows.Close() - items := make([]dicedomain.ExploreWinner, 0, pageSize) + items := make([]dicedomain.ExploreWinner, 0, int(pageSize)) for rows.Next() { var item dicedomain.ExploreWinner if err := rows.Scan( @@ -650,7 +617,7 @@ func (r *Repository) DeleteDiceRobot(ctx context.Context, appCode string, gameID return nil } -func insertDiceParticipantLocked(ctx context.Context, tx *sql.Tx, match dicedomain.Match, userID int64, participantType string, nowMs int64) (dicedomain.Match, error) { +func insertDiceParticipantLocked(ctx context.Context, tx *sql.Tx, match dicedomain.Match, userID int64, participantType string, rpsGesture string, nowMs int64) (dicedomain.Match, error) { var seatNo int32 if err := tx.QueryRowContext(ctx, `SELECT COALESCE(MAX(seat_no), 0) + 1 @@ -662,11 +629,11 @@ func insertDiceParticipantLocked(ctx context.Context, tx *sql.Tx, match dicedoma } if _, err := tx.ExecContext(ctx, `INSERT INTO game_dice_participants ( - app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, result, + app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, rps_gesture, result, payout_coin, debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), '', 0, '', '', '', 0, ?, ?)`, + ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), ?, '', 0, '', '', '', 0, ?, ?)`, match.AppCode, match.MatchID, userID, normalizeParticipantType(participantType), seatNo, - dicedomain.ParticipantStatusJoined, match.StakeCoin, "[]", nowMs, nowMs, + dicedomain.ParticipantStatusJoined, match.StakeCoin, "[]", strings.TrimSpace(rpsGesture), nowMs, nowMs, ); err != nil { return dicedomain.Match{}, err } @@ -682,6 +649,7 @@ func insertDiceParticipantLocked(ctx context.Context, tx *sql.Tx, match dicedoma SeatNo: seatNo, Status: dicedomain.ParticipantStatusJoined, StakeCoin: match.StakeCoin, + RPSGesture: strings.TrimSpace(rpsGesture), JoinedAtMS: nowMs, UpdatedAtMS: nowMs, }) diff --git a/services/game-service/internal/storage/mysql/dice_repository.go b/services/game-service/internal/storage/mysql/dice_repository.go index db8a3ad5..e8dea804 100644 --- a/services/game-service/internal/storage/mysql/dice_repository.go +++ b/services/game-service/internal/storage/mysql/dice_repository.go @@ -42,14 +42,14 @@ func (r *Repository) CreateDiceMatch(ctx context.Context, match dicedomain.Match return dicedomain.Match{}, err } for _, participant := range match.Participants { - // dice_points_json 创建阶段固定写 [],后续 SaveDiceRolls 才允许写真实点数。 + // 创建阶段只保存用户的座位和预选手势;dice_points_json 仍等到 roll/settlement 阶段才写入不可变结果标记。 if _, err := tx.ExecContext(ctx, `INSERT INTO game_dice_participants ( - app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, result, - payout_coin, debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), ?, ?, ?, ?, ?, ?, ?, ?)`, + app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, rps_gesture, result, + payout_coin, debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), ?, ?, ?, ?, ?, ?, ?, ?, ?)`, match.AppCode, match.MatchID, participant.UserID, normalizeParticipantType(participant.ParticipantType), participant.SeatNo, participant.Status, participant.StakeCoin, - dicePointsJSON(participant.DicePoints), participant.Result, participant.PayoutCoin, participant.DebitOrderID, + dicePointsJSON(participant.DicePoints), strings.TrimSpace(participant.RPSGesture), participant.Result, participant.PayoutCoin, participant.DebitOrderID, participant.PayoutOrderID, participant.RefundOrderID, participant.BalanceAfter, participant.JoinedAtMS, participant.UpdatedAtMS, ); err != nil { return dicedomain.Match{}, err @@ -63,7 +63,7 @@ func (r *Repository) CreateDiceMatch(ctx context.Context, match dicedomain.Match } // JoinDiceMatch 只在 match 行锁内分配座位和增加人数;外部钱包调用不会持有这把锁。 -func (r *Repository) JoinDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, nowMs int64) (dicedomain.Match, error) { +func (r *Repository) JoinDiceMatch(ctx context.Context, appCode string, matchID string, userID int64, rpsGesture string, nowMs int64) (dicedomain.Match, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { return dicedomain.Match{}, err @@ -115,10 +115,10 @@ func (r *Repository) JoinDiceMatch(ctx context.Context, appCode string, matchID } if _, err := tx.ExecContext(ctx, `INSERT INTO game_dice_participants ( - app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, result, + app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, dice_points_json, rps_gesture, result, payout_coin, debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), '', 0, '', '', '', 0, ?, ?)`, - app, match.MatchID, userID, dicedomain.ParticipantTypeUser, seatNo, dicedomain.ParticipantStatusJoined, match.StakeCoin, "[]", nowMs, nowMs, + ) VALUES (?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), ?, '', 0, '', '', '', 0, ?, ?)`, + app, match.MatchID, userID, dicedomain.ParticipantTypeUser, seatNo, dicedomain.ParticipantStatusJoined, match.StakeCoin, "[]", strings.TrimSpace(rpsGesture), nowMs, nowMs, ); err != nil { return dicedomain.Match{}, err } @@ -432,7 +432,7 @@ func queryDiceMatch(ctx context.Context, q diceQuerier, appCode string, matchID func queryDiceParticipants(ctx context.Context, q diceQuerier, appCode string, matchID string, forUpdate bool) ([]dicedomain.Participant, error) { // participants 始终按座位排序,HTTP 响应和补偿重放都能得到稳定顺序。 query := `SELECT app_code, match_id, user_id, participant_type, seat_no, status, stake_coin, - COALESCE(CAST(dice_points_json AS CHAR), '[]'), result, payout_coin, + COALESCE(CAST(dice_points_json AS CHAR), '[]'), rps_gesture, result, payout_coin, debit_order_id, payout_order_id, refund_order_id, balance_after, joined_at_ms, updated_at_ms FROM game_dice_participants WHERE app_code = ? AND match_id = ? @@ -450,7 +450,7 @@ func queryDiceParticipants(ctx context.Context, q diceQuerier, appCode string, m for rows.Next() { var participant dicedomain.Participant var pointsJSON string - if err := rows.Scan(&participant.AppCode, &participant.MatchID, &participant.UserID, &participant.ParticipantType, &participant.SeatNo, &participant.Status, &participant.StakeCoin, &pointsJSON, &participant.Result, &participant.PayoutCoin, &participant.DebitOrderID, &participant.PayoutOrderID, &participant.RefundOrderID, &participant.BalanceAfter, &participant.JoinedAtMS, &participant.UpdatedAtMS); err != nil { + if err := rows.Scan(&participant.AppCode, &participant.MatchID, &participant.UserID, &participant.ParticipantType, &participant.SeatNo, &participant.Status, &participant.StakeCoin, &pointsJSON, &participant.RPSGesture, &participant.Result, &participant.PayoutCoin, &participant.DebitOrderID, &participant.PayoutOrderID, &participant.RefundOrderID, &participant.BalanceAfter, &participant.JoinedAtMS, &participant.UpdatedAtMS); err != nil { return nil, err } participant.ParticipantType = normalizeParticipantType(participant.ParticipantType) diff --git a/services/game-service/internal/storage/mysql/repository.go b/services/game-service/internal/storage/mysql/repository.go index a50613a8..8a3478ee 100644 --- a/services/game-service/internal/storage/mysql/repository.go +++ b/services/game-service/internal/storage/mysql/repository.go @@ -188,10 +188,11 @@ func (r *Repository) Migrate(ctx context.Context) error { match_id VARCHAR(96) NOT NULL, user_id BIGINT NOT NULL, seat_no INT NOT NULL, - status VARCHAR(32) NOT NULL, - stake_coin BIGINT NOT NULL, - dice_points_json JSON NULL, - result VARCHAR(32) NOT NULL DEFAULT '', + status VARCHAR(32) NOT NULL, + stake_coin BIGINT NOT NULL, + dice_points_json JSON NULL, + rps_gesture VARCHAR(32) NOT NULL DEFAULT '', + result VARCHAR(32) NOT NULL DEFAULT '', payout_coin BIGINT NOT NULL DEFAULT 0, debit_order_id VARCHAR(96) NOT NULL DEFAULT '', payout_order_id VARCHAR(96) NOT NULL DEFAULT '', @@ -204,6 +205,22 @@ func (r *Repository) Migrate(ctx context.Context) error { KEY idx_game_dice_participant_user(app_code, user_id, updated_at_ms), KEY idx_game_dice_participant_status(app_code, match_id, status, seat_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS game_explore_winners ( + app_code VARCHAR(32) NOT NULL, + win_id VARCHAR(96) NOT NULL, + game_code VARCHAR(64) NOT NULL, + game_id VARCHAR(96) NOT NULL, + user_id BIGINT NOT NULL, + display_name VARCHAR(128) NOT NULL DEFAULT '', + avatar_url VARCHAR(512) NOT NULL DEFAULT '', + coin_amount BIGINT NOT NULL, + won_at_ms BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY(app_code, win_id), + KEY idx_game_explore_winners_time(app_code, won_at_ms, win_id), + KEY idx_game_explore_winners_game(app_code, game_code, won_at_ms) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, `CREATE TABLE IF NOT EXISTS game_callback_logs ( app_code VARCHAR(32) NOT NULL, callback_id VARCHAR(96) NOT NULL, @@ -307,6 +324,9 @@ func (r *Repository) Migrate(ctx context.Context) error { if err := r.ensureColumn(ctx, "game_orders", "region_id", "region_id BIGINT NOT NULL DEFAULT 0 AFTER room_id"); err != nil { return err } + if err := r.ensureColumn(ctx, "game_dice_participants", "rps_gesture", "rps_gesture VARCHAR(32) NOT NULL DEFAULT '' AFTER dice_points_json"); err != nil { + return err + } if err := r.ensureColumn(ctx, "game_outbox", "worker_id", "worker_id VARCHAR(128) NOT NULL DEFAULT '' AFTER status"); err != nil { return err } diff --git a/services/game-service/internal/transport/grpc/server.go b/services/game-service/internal/transport/grpc/server.go index f9df24ae..7eb760cc 100644 --- a/services/game-service/internal/transport/grpc/server.go +++ b/services/game-service/internal/transport/grpc/server.go @@ -162,13 +162,14 @@ func (s *Server) MatchDice(ctx context.Context, req *gamev1.MatchDiceRequest) (* } ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) match, serverTimeMS, err := s.diceSvc.Match(ctx, diceservice.MatchCommand{ - AppCode: req.GetMeta().GetAppCode(), - RequestID: req.GetMeta().GetRequestId(), - UserID: req.GetUserId(), - GameID: req.GetGameId(), - RoomID: req.GetRoomId(), - RegionID: req.GetRegionId(), - StakeCoin: req.GetStakeCoin(), + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + GameID: req.GetGameId(), + RoomID: req.GetRoomId(), + RegionID: req.GetRegionId(), + StakeCoin: req.GetStakeCoin(), + RPSGesture: req.GetRpsGesture(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -192,6 +193,7 @@ func (s *Server) CreateDiceMatch(ctx context.Context, req *gamev1.CreateDiceMatc StakeCoin: req.GetStakeCoin(), MinPlayers: req.GetMinPlayers(), MaxPlayers: req.GetMaxPlayers(), + RPSGesture: req.GetRpsGesture(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -206,10 +208,12 @@ func (s *Server) JoinDiceMatch(ctx context.Context, req *gamev1.JoinDiceMatchReq // user_id 由 gateway 从登录态注入;transport 不允许用 actor_user_id 推导业务用户,避免调用方混淆身份字段。 ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) match, serverTimeMS, err := s.diceSvc.JoinMatch(ctx, diceservice.JoinMatchCommand{ - AppCode: req.GetMeta().GetAppCode(), - RequestID: req.GetMeta().GetRequestId(), - UserID: req.GetUserId(), - MatchID: req.GetMatchId(), + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + MatchID: req.GetMatchId(), + GameID: req.GetGameId(), + RPSGesture: req.GetRpsGesture(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -227,6 +231,7 @@ func (s *Server) GetDiceMatch(ctx context.Context, req *gamev1.GetDiceMatchReque AppCode: req.GetMeta().GetAppCode(), UserID: req.GetUserId(), MatchID: req.GetMatchId(), + GameID: req.GetGameId(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -241,10 +246,12 @@ func (s *Server) RollDiceMatch(ctx context.Context, req *gamev1.RollDiceMatchReq // roll 是唯一会触发钱包改账的骰子 RPC;gRPC 层不拆分扣款/派奖,避免客户端绕过局状态机。 ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) match, serverTimeMS, err := s.diceSvc.RollMatch(ctx, diceservice.RollMatchCommand{ - AppCode: req.GetMeta().GetAppCode(), - RequestID: req.GetMeta().GetRequestId(), - UserID: req.GetUserId(), - MatchID: req.GetMatchId(), + AppCode: req.GetMeta().GetAppCode(), + RequestID: req.GetMeta().GetRequestId(), + UserID: req.GetUserId(), + MatchID: req.GetMatchId(), + GameID: req.GetGameId(), + RPSGesture: req.GetRpsGesture(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -262,6 +269,7 @@ func (s *Server) CancelDiceMatch(ctx context.Context, req *gamev1.CancelDiceMatc RequestID: req.GetMeta().GetRequestId(), UserID: req.GetUserId(), MatchID: req.GetMatchId(), + GameID: req.GetGameId(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -738,6 +746,7 @@ func diceParticipantToProto(participant dicedomain.Participant) *gamev1.DicePart UpdatedAtMs: participant.UpdatedAtMS, ParticipantType: participant.ParticipantType, IsRobot: participant.ParticipantType == dicedomain.ParticipantTypeRobot, + RpsGesture: participant.RPSGesture, } } diff --git a/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler.go b/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler.go index 66294bf2..84b00d95 100644 --- a/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler.go +++ b/services/gateway-service/internal/transport/http/activityapi/invite_activity_reward_handler.go @@ -62,13 +62,15 @@ type inviteActivityRewardClaimData struct { } type inviteActivityRewardStatusData struct { - Enabled bool `json:"enabled"` - CycleKey string `json:"cycle_key"` - Period cumulativeRechargeRewardPeriodData `json:"period"` - Tiers []inviteActivityRewardTierData `json:"tiers"` - Progress inviteActivityRewardProgressData `json:"progress"` - Claims []inviteActivityRewardClaimData `json:"claims"` - ServerTimeMS int64 `json:"server_time_ms"` + Enabled bool `json:"enabled"` + PerInviteInviterRewardCoinAmount int64 `json:"per_invite_inviter_reward_coin_amount"` + PerInviteInviteeRewardCoinAmount int64 `json:"per_invite_invitee_reward_coin_amount"` + CycleKey string `json:"cycle_key"` + Period cumulativeRechargeRewardPeriodData `json:"period"` + Tiers []inviteActivityRewardTierData `json:"tiers"` + Progress inviteActivityRewardProgressData `json:"progress"` + Claims []inviteActivityRewardClaimData `json:"claims"` + ServerTimeMS int64 `json:"server_time_ms"` } type inviteActivityRewardClaimBody struct { @@ -226,8 +228,10 @@ func inviteActivityRewardStatusFromProto(item *activityv1.InviteActivityRewardSt } } return inviteActivityRewardStatusData{ - Enabled: item.GetConfig().GetEnabled(), - CycleKey: item.GetCycleKey(), + Enabled: item.GetConfig().GetEnabled(), + PerInviteInviterRewardCoinAmount: item.GetConfig().GetPerInviteInviterRewardCoinAmount(), + PerInviteInviteeRewardCoinAmount: item.GetConfig().GetPerInviteInviteeRewardCoinAmount(), + CycleKey: item.GetCycleKey(), Period: cumulativeRechargeRewardPeriodData{ StartMS: item.GetPeriodStartMs(), EndMS: item.GetPeriodEndMs(), diff --git a/services/gateway-service/internal/transport/http/game_handler_test.go b/services/gateway-service/internal/transport/http/game_handler_test.go index 6863f1ba..89d33409 100644 --- a/services/gateway-service/internal/transport/http/game_handler_test.go +++ b/services/gateway-service/internal/transport/http/game_handler_test.go @@ -141,6 +141,9 @@ func TestListExploreWinnersReturnsSquareItems(t *testing.T) { if first["game_code"] != "game_dice" || first["user_id"] != "7001" || first["coin_amount"].(float64) != 306000 { t.Fatalf("explore winner response mismatch: %+v", first) } + if first["display_name"] != "user-7001" || first["avatar_url"] != "https://cdn.example/avatar.png" { + t.Fatalf("explore winner response mismatch: %+v", first) + } } func TestGetBridgeScriptReturnsUniversalScriptConfig(t *testing.T) { @@ -264,11 +267,166 @@ func TestRPSMatchUsesRockSelfGameAndAuthenticatedRegion(t *testing.T) { if recorder.Code != http.StatusOK { t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) } - if gameClient.lastMatchDice == nil || gameClient.lastMatchDice.GetGameId() != "rock" || gameClient.lastMatchDice.GetUserId() != 42 || gameClient.lastMatchDice.GetRegionId() != 1001 || gameClient.lastMatchDice.GetStakeCoin() != 8000 { + if gameClient.lastMatchDice == nil || gameClient.lastMatchDice.GetGameId() != "rock" || gameClient.lastMatchDice.GetUserId() != 42 || gameClient.lastMatchDice.GetRegionId() != 1001 || gameClient.lastMatchDice.GetStakeCoin() != 8000 || gameClient.lastMatchDice.GetRpsGesture() != "paper" { t.Fatalf("rps match request mismatch: %+v", gameClient.lastMatchDice) } } +func TestRPSCreateUsesPreselectedGesture(t *testing.T) { + gameClient := &fakeGatewayGameClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/games/rps/matches", bytes.NewReader([]byte(`{"game_id":"rock","stake_coin":500,"gesture":"Scissors"}`))) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("Content-Type", "application/json") + 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 gameClient.lastCreateDice == nil || gameClient.lastCreateDice.GetGameId() != "rock" || gameClient.lastCreateDice.GetRpsGesture() != "scissors" || gameClient.lastCreateDice.GetRegionId() != 1001 { + t.Fatalf("rps create request mismatch: %+v", gameClient.lastCreateDice) + } +} + +func TestRPSMatchOperationsCarryRockBoundary(t *testing.T) { + gameClient := &fakeGatewayGameClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + token := "Bearer " + signGatewayToken(t, "secret", 42) + + // rps 的后续操作路径没有 body 里的 game_id 可依赖,必须由 gateway 根据路由固定传 rock 给 game-service 做 match 归属复核。 + requests := []struct { + method string + path string + body string + check func() + }{ + { + method: http.MethodPost, + path: "/api/v1/games/rps/matches/match-rock-1/join", + body: `{"gesture":"scissors"}`, + check: func() { + if gameClient.lastJoinDice == nil || gameClient.lastJoinDice.GetGameId() != "rock" || gameClient.lastJoinDice.GetRpsGesture() != "scissors" { + t.Fatalf("rps join must carry rock game_id: %+v", gameClient.lastJoinDice) + } + }, + }, + { + method: http.MethodGet, + path: "/api/v1/games/rps/matches/match-rock-1", + check: func() { + if gameClient.lastGetDice == nil || gameClient.lastGetDice.GetGameId() != "rock" { + t.Fatalf("rps get must carry rock game_id: %+v", gameClient.lastGetDice) + } + }, + }, + { + method: http.MethodPost, + path: "/api/v1/games/rps/matches/match-rock-1/roll", + body: `{"gesture":"paper","game_id":"dice"}`, + check: func() { + if gameClient.lastRollDice == nil || gameClient.lastRollDice.GetGameId() != "rock" || gameClient.lastRollDice.GetRpsGesture() != "paper" { + t.Fatalf("rps roll must ignore body game_id and carry rock: %+v", gameClient.lastRollDice) + } + }, + }, + { + method: http.MethodPost, + path: "/api/v1/games/rps/matches/match-rock-1/cancel", + check: func() { + if gameClient.lastCancelDice == nil || gameClient.lastCancelDice.GetGameId() != "rock" { + t.Fatalf("rps cancel must carry rock game_id: %+v", gameClient.lastCancelDice) + } + }, + }, + } + for _, item := range requests { + body := io.Reader(bytes.NewReader(nil)) + if item.body != "" { + body = bytes.NewReader([]byte(item.body)) + } + request := httptest.NewRequest(item.method, item.path, body) + request.Header.Set("Authorization", token) + request.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("%s %s status mismatch: got %d body=%s", item.method, item.path, recorder.Code, recorder.Body.String()) + } + item.check() + } +} + +func TestDiceMatchOperationsCarryDiceBoundary(t *testing.T) { + gameClient := &fakeGatewayGameClient{} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + token := "Bearer " + signGatewayToken(t, "secret", 42) + + requests := []struct { + method string + path string + check func() + }{ + { + method: http.MethodPost, + path: "/api/v1/games/dice/matches/match-dice-1/join", + check: func() { + if gameClient.lastJoinDice == nil || gameClient.lastJoinDice.GetGameId() != "dice" { + t.Fatalf("dice join must carry dice game_id: %+v", gameClient.lastJoinDice) + } + }, + }, + { + method: http.MethodGet, + path: "/api/v1/games/dice/matches/match-dice-1", + check: func() { + if gameClient.lastGetDice == nil || gameClient.lastGetDice.GetGameId() != "dice" { + t.Fatalf("dice get must carry dice game_id: %+v", gameClient.lastGetDice) + } + }, + }, + { + method: http.MethodPost, + path: "/api/v1/games/dice/matches/match-dice-1/roll", + check: func() { + if gameClient.lastRollDice == nil || gameClient.lastRollDice.GetGameId() != "dice" { + t.Fatalf("dice roll must carry dice game_id: %+v", gameClient.lastRollDice) + } + }, + }, + { + method: http.MethodPost, + path: "/api/v1/games/dice/matches/match-dice-1/cancel", + check: func() { + if gameClient.lastCancelDice == nil || gameClient.lastCancelDice.GetGameId() != "dice" { + t.Fatalf("dice cancel must carry dice game_id: %+v", gameClient.lastCancelDice) + } + }, + }, + } + for _, item := range requests { + request := httptest.NewRequest(item.method, item.path, nil) + request.Header.Set("Authorization", token) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("%s %s status mismatch: got %d body=%s", item.method, item.path, recorder.Code, recorder.Body.String()) + } + item.check() + } +} + func TestCreateRoomRPSChallengeUsesAuthenticatedUserAndRegion(t *testing.T) { gameClient := &fakeGatewayGameClient{roomRPSChallengeResp: &gamev1.RoomRPSChallengeResponse{ Challenge: &gamev1.RoomRPSChallenge{ @@ -491,6 +649,11 @@ type fakeGatewayGameClient struct { lastCallback *gamev1.CallbackRequest lastGetDiceConfig *gamev1.GetDiceConfigRequest lastMatchDice *gamev1.MatchDiceRequest + lastCreateDice *gamev1.CreateDiceMatchRequest + lastJoinDice *gamev1.JoinDiceMatchRequest + lastGetDice *gamev1.GetDiceMatchRequest + lastRollDice *gamev1.RollDiceMatchRequest + lastCancelDice *gamev1.CancelDiceMatchRequest roomRPSChallengeResp *gamev1.RoomRPSChallengeResponse lastListRoomRPS *gamev1.ListRoomRPSChallengesRequest lastCreateRoomRPS *gamev1.CreateRoomRPSChallengeRequest @@ -548,23 +711,28 @@ func (f *fakeGatewayGameClient) MatchDice(_ context.Context, req *gamev1.MatchDi return &gamev1.DiceMatchResponse{}, nil } -func (f *fakeGatewayGameClient) CreateDiceMatch(context.Context, *gamev1.CreateDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { +func (f *fakeGatewayGameClient) CreateDiceMatch(_ context.Context, req *gamev1.CreateDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + f.lastCreateDice = req return &gamev1.DiceMatchResponse{}, nil } -func (f *fakeGatewayGameClient) JoinDiceMatch(context.Context, *gamev1.JoinDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { +func (f *fakeGatewayGameClient) JoinDiceMatch(_ context.Context, req *gamev1.JoinDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + f.lastJoinDice = req return &gamev1.DiceMatchResponse{}, nil } -func (f *fakeGatewayGameClient) GetDiceMatch(context.Context, *gamev1.GetDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { +func (f *fakeGatewayGameClient) GetDiceMatch(_ context.Context, req *gamev1.GetDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + f.lastGetDice = req return &gamev1.DiceMatchResponse{}, nil } -func (f *fakeGatewayGameClient) RollDiceMatch(context.Context, *gamev1.RollDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { +func (f *fakeGatewayGameClient) RollDiceMatch(_ context.Context, req *gamev1.RollDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + f.lastRollDice = req return &gamev1.DiceMatchResponse{}, nil } -func (f *fakeGatewayGameClient) CancelDiceMatch(context.Context, *gamev1.CancelDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { +func (f *fakeGatewayGameClient) CancelDiceMatch(_ context.Context, req *gamev1.CancelDiceMatchRequest) (*gamev1.DiceMatchResponse, error) { + f.lastCancelDice = req return &gamev1.DiceMatchResponse{}, nil } diff --git a/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go b/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go index 2420f7ff..e6d2d0c6 100644 --- a/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go +++ b/services/gateway-service/internal/transport/http/gameapi/dice_profile_view.go @@ -57,6 +57,46 @@ func (h *Handler) enrichRoomRPSChallenges(request *http.Request, challenges []*r } } +// enrichExploreWinners 只补 Square 播报展示资料;game_code、金额和时间仍以 game-service 的获胜事实为准。 +func (h *Handler) enrichExploreWinners(request *http.Request, winners []exploreWinnerItemData) { + if h == nil || len(winners) == 0 { + return + } + userIDs := exploreWinnerUserIDs(winners) + users := h.diceUsersByID(request, userIDs) + for index := range winners { + userID := winners[index].UserIDNumber + if userID <= 0 { + continue + } + if user, ok := users[userID]; ok { + // user-service 是昵称头像的唯一事实来源;game-service 表里的 display_name/avatar_url 只是播报创建时的兜底快照。 + if user.Nickname != "" { + winners[index].DisplayName = user.Nickname + } + if user.Avatar != "" { + winners[index].AvatarURL = user.Avatar + } + } + } +} + +func exploreWinnerUserIDs(winners []exploreWinnerItemData) []int64 { + seen := make(map[int64]struct{}, len(winners)) + out := make([]int64, 0, len(winners)) + for _, winner := range winners { + if winner.UserIDNumber <= 0 { + continue + } + if _, ok := seen[winner.UserIDNumber]; ok { + continue + } + seen[winner.UserIDNumber] = struct{}{} + out = append(out, winner.UserIDNumber) + } + return out +} + func roomRPSChallengeUserIDs(challenges []*roomRPSChallengeItemData) []int64 { seen := make(map[int64]struct{}, len(challenges)*2) out := make([]int64, 0, len(challenges)*2) diff --git a/services/gateway-service/internal/transport/http/gameapi/game_handler.go b/services/gateway-service/internal/transport/http/gameapi/game_handler.go index e628c827..bbd684d8 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_handler.go @@ -1,6 +1,7 @@ package gameapi import ( + "bytes" "io" "log/slog" "net/http" @@ -101,7 +102,10 @@ func (h *Handler) listExploreWinners(writer http.ResponseWriter, request *http.R Meta: gameMeta(request), PageSize: pageSize, }) - httpkit.Write(writer, request, exploreWinnerListDataFromProto(resp), err) + data := exploreWinnerListDataFromProto(resp) + // 获胜事实归 game-service,头像昵称归 user-service;资料补全失败时仍返回游戏事实,Flutter 用 user_id 和本地图做兜底。 + h.enrichExploreWinners(request, data.Items) + httpkit.Write(writer, request, data, err) } func (h *Handler) getBridgeScript(writer http.ResponseWriter, request *http.Request) { @@ -196,10 +200,11 @@ func (h *Handler) matchSelfGame(writer http.ResponseWriter, request *http.Reques return } var body struct { - GameID string `json:"game_id"` - RoomID string `json:"room_id"` - StakeCoin int64 `json:"stake_coin"` - Gesture string `json:"gesture"` + GameID string `json:"game_id"` + RoomID string `json:"room_id"` + StakeCoin int64 `json:"stake_coin"` + Gesture string `json:"gesture"` + RPSGesture string `json:"rps_gesture"` } if !httpkit.Decode(writer, request, &body) { return @@ -212,6 +217,10 @@ func (h *Handler) matchSelfGame(writer http.ResponseWriter, request *http.Reques httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } + rpsGesture, ok := selfGameRPSGestureFromInput(writer, request, gameID, firstRPSGestureInput(body.Gesture, body.RPSGesture), true) + if !ok { + return + } userID := auth.UserIDFromContext(request.Context()) userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ Meta: httpkit.UserMeta(request, ""), @@ -222,12 +231,13 @@ func (h *Handler) matchSelfGame(writer http.ResponseWriter, request *http.Reques return } resp, err := h.gameClient.MatchDice(request.Context(), &gamev1.MatchDiceRequest{ - Meta: gameMeta(request), - UserId: userID, - GameId: gameID, - RoomId: strings.TrimSpace(body.RoomID), - RegionId: userResp.GetUser().GetRegionId(), - StakeCoin: body.StakeCoin, + Meta: gameMeta(request), + UserId: userID, + GameId: gameID, + RoomId: strings.TrimSpace(body.RoomID), + RegionId: userResp.GetUser().GetRegionId(), + StakeCoin: body.StakeCoin, + RpsGesture: rpsGesture, }) h.writeDiceMatch(writer, request, resp, err) } @@ -252,6 +262,8 @@ func (h *Handler) createSelfGameMatch(writer http.ResponseWriter, request *http. StakeCoin int64 `json:"stake_coin"` MinPlayers int32 `json:"min_players"` MaxPlayers int32 `json:"max_players"` + Gesture string `json:"gesture"` + RPSGesture string `json:"rps_gesture"` } if !httpkit.Decode(writer, request, &body) { return @@ -264,6 +276,10 @@ func (h *Handler) createSelfGameMatch(writer http.ResponseWriter, request *http. httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } + rpsGesture, ok := selfGameRPSGestureFromInput(writer, request, gameID, firstRPSGestureInput(body.Gesture, body.RPSGesture), true) + if !ok { + return + } userID := auth.UserIDFromContext(request.Context()) userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ Meta: httpkit.UserMeta(request, ""), @@ -283,11 +299,20 @@ func (h *Handler) createSelfGameMatch(writer http.ResponseWriter, request *http. StakeCoin: body.StakeCoin, MinPlayers: body.MinPlayers, MaxPlayers: body.MaxPlayers, + RpsGesture: rpsGesture, }) h.writeDiceMatch(writer, request, resp, err) } func (h *Handler) joinDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.joinSelfGameMatch(writer, request, selfGameIDDice) +} + +func (h *Handler) joinRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.joinSelfGameMatch(writer, request, selfGameIDRock) +} + +func (h *Handler) joinSelfGameMatch(writer http.ResponseWriter, request *http.Request, expectedGameID string) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return @@ -297,21 +322,42 @@ func (h *Handler) joinDiceMatch(writer http.ResponseWriter, request *http.Reques httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } + rpsGesture := "" + if expectedGameID == selfGameIDRock { + var body struct { + Gesture string `json:"gesture"` + RPSGesture string `json:"rps_gesture"` + } + if !httpkit.Decode(writer, request, &body) { + return + } + var ok bool + rpsGesture, ok = selfGameRPSGestureFromInput(writer, request, expectedGameID, firstRPSGestureInput(body.Gesture, body.RPSGesture), true) + if !ok { + return + } + } // join 不接受 body 里的 user_id 或 stake_coin;参与者身份来自 JWT,押注金额来自 match 当前事实。 resp, err := h.gameClient.JoinDiceMatch(request.Context(), &gamev1.JoinDiceMatchRequest{ Meta: gameMeta(request), UserId: auth.UserIDFromContext(request.Context()), MatchId: matchID, + // rps 入口复用 self-game match 表,但必须把路径期望的 game_id 传给 game-service 复核,避免拿 dice match_id 串到 rock 流程。 + GameId: expectedGameID, + RpsGesture: rpsGesture, }) h.writeDiceMatch(writer, request, resp, err) } -func (h *Handler) joinRPSMatch(writer http.ResponseWriter, request *http.Request) { - // 独立猜拳复用自研匹配局 ID;join 不从 body 读取 game_id,因此只需要走同一条参与者身份校验链路。 - h.joinDiceMatch(writer, request) +func (h *Handler) getDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.getSelfGameMatch(writer, request, selfGameIDDice) } -func (h *Handler) getDiceMatch(writer http.ResponseWriter, request *http.Request) { +func (h *Handler) getRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.getSelfGameMatch(writer, request, selfGameIDRock) +} + +func (h *Handler) getSelfGameMatch(writer http.ResponseWriter, request *http.Request, expectedGameID string) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return @@ -326,16 +372,21 @@ func (h *Handler) getDiceMatch(writer http.ResponseWriter, request *http.Request Meta: gameMeta(request), UserId: auth.UserIDFromContext(request.Context()), MatchId: matchID, + // 查询同样带上路径 game_id,防止 rps 页面仅凭 match_id 读到 dice 局详情并继续轮询。 + GameId: expectedGameID, }) h.writeDiceMatch(writer, request, resp, err) } -func (h *Handler) getRPSMatch(writer http.ResponseWriter, request *http.Request) { - // match_id 已经由 game-service 全局唯一约束,HTTP 层不再反查 game_id,避免刷新路径多一次数据库读。 - h.getDiceMatch(writer, request) +func (h *Handler) rollDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.rollSelfGameMatch(writer, request, selfGameIDDice) } -func (h *Handler) rollDiceMatch(writer http.ResponseWriter, request *http.Request) { +func (h *Handler) rollRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.rollSelfGameMatch(writer, request, selfGameIDRock) +} + +func (h *Handler) rollSelfGameMatch(writer http.ResponseWriter, request *http.Request, expectedGameID string) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return @@ -345,21 +396,42 @@ func (h *Handler) rollDiceMatch(writer http.ResponseWriter, request *http.Reques httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } + rpsGesture := "" + if expectedGameID == selfGameIDRock { + var body struct { + Gesture string `json:"gesture"` + RPSGesture string `json:"rps_gesture"` + } + if !decodeOptionalJSONBody(writer, request, &body) { + return + } + var ok bool + rpsGesture, ok = selfGameRPSGestureFromInput(writer, request, expectedGameID, firstRPSGestureInput(body.Gesture, body.RPSGesture), false) + if !ok { + return + } + } // roll 不接收骰子点数、结果或派奖金额;这些全部由 game-service 服务端随机和结算。 resp, err := h.gameClient.RollDiceMatch(request.Context(), &gamev1.RollDiceMatchRequest{ Meta: gameMeta(request), UserId: auth.UserIDFromContext(request.Context()), MatchId: matchID, + // roll 是资金入口,必须在 game-service 抢结算锁前验证当前路径只能操作自己的自研游戏局。 + GameId: expectedGameID, + RpsGesture: rpsGesture, }) h.writeDiceMatch(writer, request, resp, err) } -func (h *Handler) rollRPSMatch(writer http.ResponseWriter, request *http.Request) { - // H5 会带 gesture 做展示态上报;当前服务端结算仍以自研匹配局事实为准,客户端不能提交结果或派奖金额。 - h.rollDiceMatch(writer, request) +func (h *Handler) cancelDiceMatch(writer http.ResponseWriter, request *http.Request) { + h.cancelSelfGameMatch(writer, request, selfGameIDDice) } -func (h *Handler) cancelDiceMatch(writer http.ResponseWriter, request *http.Request) { +func (h *Handler) cancelRPSMatch(writer http.ResponseWriter, request *http.Request) { + h.cancelSelfGameMatch(writer, request, selfGameIDRock) +} + +func (h *Handler) cancelSelfGameMatch(writer http.ResponseWriter, request *http.Request, expectedGameID string) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return @@ -373,15 +445,12 @@ func (h *Handler) cancelDiceMatch(writer http.ResponseWriter, request *http.Requ Meta: gameMeta(request), UserId: auth.UserIDFromContext(request.Context()), MatchId: matchID, + // 取消等待局也要校验 game_id,否则用户可以从 rps 路径取消自己创建的 dice 等待局。 + GameId: expectedGameID, }) h.writeDiceMatch(writer, request, resp, err) } -func (h *Handler) cancelRPSMatch(writer http.ResponseWriter, request *http.Request) { - // 取消只允许等待局创建者撤销,权限和状态仍在 game-service 内部用同一把 match 锁判断。 - h.cancelDiceMatch(writer, request) -} - func (h *Handler) getRoomRPSConfig(writer http.ResponseWriter, request *http.Request) { if h.gameClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -552,6 +621,57 @@ func selfGameIDFromInput(writer http.ResponseWriter, request *http.Request, rawG return gameID, true } +func firstRPSGestureInput(gesture string, rpsGesture string) string { + if strings.TrimSpace(gesture) != "" { + return gesture + } + return rpsGesture +} + +func selfGameRPSGestureFromInput(writer http.ResponseWriter, request *http.Request, gameID string, rawGesture string, required bool) (string, bool) { + if gameID != selfGameIDRock { + return "", true + } + if strings.TrimSpace(rawGesture) == "" && !required { + return "", true + } + gesture, ok := normalizeSelfRPSGesture(rawGesture) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return "", false + } + return gesture, true +} + +func normalizeSelfRPSGesture(value string) (string, bool) { + // self-rps 的手势在入 game-service 前统一成小写英文枚举;dice 路径不会调用这里,避免污染骰子玩法。 + switch strings.ToLower(strings.TrimSpace(value)) { + case "rock", "paper", "scissors": + return strings.ToLower(strings.TrimSpace(value)), true + default: + return "", false + } +} + +func decodeOptionalJSONBody(writer http.ResponseWriter, request *http.Request, out any) bool { + if request.Body == nil { + return true + } + raw, err := io.ReadAll(request.Body) + if err != nil { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidJSON, "invalid request body") + return false + } + if strings.TrimSpace(string(raw)) == "" { + return true + } + if err := httpkit.DecodeJSON(bytes.NewReader(raw), out); err != nil { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidJSON, "invalid request body") + return false + } + return true +} + func normalizeRoomRPSGesture(value string) (string, bool) { // 手势在 gateway 层归一为小写英文枚举;game-service 只接收稳定值,避免 Flutter 大小写或本地化文案污染订单事实。 switch strings.ToLower(strings.TrimSpace(value)) { diff --git a/services/gateway-service/internal/transport/http/gameapi/game_view.go b/services/gateway-service/internal/transport/http/gameapi/game_view.go index b0115ebf..f64540a6 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_view.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_view.go @@ -169,6 +169,8 @@ type diceParticipantItemData struct { Status string `json:"status"` StakeCoin int64 `json:"stake_coin"` DicePoints []int32 `json:"dice_points"` + RPSGesture string `json:"rps_gesture"` + Gesture string `json:"gesture"` Result string `json:"result"` PayoutCoin int64 `json:"payout_coin"` BalanceAfter int64 `json:"balance_after"` @@ -408,6 +410,8 @@ func diceParticipantItemDataFromProto(participant *gamev1.DiceParticipant) diceP Status: participant.GetStatus(), StakeCoin: participant.GetStakeCoin(), DicePoints: participant.GetDicePoints(), + RPSGesture: participant.GetRpsGesture(), + Gesture: participant.GetRpsGesture(), Result: participant.GetResult(), PayoutCoin: participant.GetPayoutCoin(), BalanceAfter: participant.GetBalanceAfter(), diff --git a/services/wallet-service/internal/service/wallet/service.go b/services/wallet-service/internal/service/wallet/service.go index cfecfd50..b9536db3 100644 --- a/services/wallet-service/internal/service/wallet/service.go +++ b/services/wallet-service/internal/service/wallet/service.go @@ -565,7 +565,7 @@ func (s *Service) CreditInviteActivityReward(ctx context.Context, command ledger return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.InvalidArgument, "invite activity reward command is incomplete") } command.RewardType = strings.ToLower(strings.TrimSpace(command.RewardType)) - if command.RewardType != "recharge" && command.RewardType != "valid_invite" { + if command.RewardType != "recharge" && command.RewardType != "valid_invite" && command.RewardType != "invite_inviter" && command.RewardType != "invite_invitee" { return ledger.InviteActivityRewardReceipt{}, xerr.New(xerr.InvalidArgument, "invite activity reward_type is invalid") } command.Reason = strings.TrimSpace(command.Reason) From f995d806100f9d38ab972967a8f4b9e552e2a4ff Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 18:33:30 +0800 Subject: [PATCH 23/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=BB=E6=84=8F?= =?UTF-8?q?=E9=80=81=E7=A4=BC=E5=A2=9E=E5=8A=A0=E4=BA=B2=E5=AF=86=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/user-service/internal/app/app.go | 6 +- .../user-service/internal/app/app_test.go | 49 ++++++++++ .../internal/storage/mysql/cp/repository.go | 8 +- .../storage/mysql/cp/repository_test.go | 95 +++++++++++++++++++ 4 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 services/user-service/internal/app/app_test.go diff --git a/services/user-service/internal/app/app.go b/services/user-service/internal/app/app.go index 8465c3d5..c6c74621 100644 --- a/services/user-service/internal/app/app.go +++ b/services/user-service/internal/app/app.go @@ -758,10 +758,8 @@ func roomGiftCPEventFromRoomMessage(body []byte) (cpdomain.GiftEvent, bool, erro if err := proto.Unmarshal(envelope.GetBody(), &sent); err != nil { return cpdomain.GiftEvent{}, false, err } - // 只有明确标记 cp_relation_type 的礼物才进入关系逻辑;普通礼物仍由其他读模型消费。 - if strings.TrimSpace(sent.GetCpRelationType()) == "" { - return cpdomain.GiftEvent{}, false, nil - } + // CP worker 必须接收所有送礼事实:没有 active 关系时仓储层只让 CP/兄弟/姐妹礼物生成申请; + // 已有 active 关系时,普通、幸运和超级幸运礼物都按 wallet 已结算的 GiftValue 增加亲密值。 return cpdomain.GiftEvent{ AppCode: appcode.Normalize(envelope.GetAppCode()), EventID: envelope.GetEventId(), diff --git a/services/user-service/internal/app/app_test.go b/services/user-service/internal/app/app_test.go new file mode 100644 index 00000000..ae21b35f --- /dev/null +++ b/services/user-service/internal/app/app_test.go @@ -0,0 +1,49 @@ +package app + +import ( + "testing" + + "google.golang.org/protobuf/proto" + roomeventsv1 "hyapp.local/api/proto/events/room/v1" + "hyapp/pkg/roommq" +) + +func TestRoomGiftCPEventFromRoomMessageIncludesNormalGift(t *testing.T) { + body, err := proto.Marshal(&roomeventsv1.RoomGiftSent{ + SenderUserId: 1001, + TargetUserId: 1002, + GiftId: "rose", + GiftCount: 1, + GiftValue: 100, + BillingReceiptId: "receipt-normal", + CommandId: "cmd-normal", + GiftTypeCode: "normal", + GiftName: "Rose", + }) + if err != nil { + t.Fatalf("marshal room gift sent failed: %v", err) + } + encoded, err := roommq.EncodeRoomOutboxMessage(&roomeventsv1.EventEnvelope{ + AppCode: "lalu", + EventId: "room_evt_normal_gift", + EventType: "RoomGiftSent", + RoomId: "room-1", + RoomVersion: 7, + OccurredAtMs: 1700000000000, + Body: body, + }) + if err != nil { + t.Fatalf("encode room outbox message failed: %v", err) + } + + event, ok, err := roomGiftCPEventFromRoomMessage(encoded) + if err != nil { + t.Fatalf("convert room gift event failed: %v", err) + } + if !ok { + t.Fatal("normal gifts must reach CP consumer so active relationships can gain intimacy") + } + if event.CPRelationType != "" || event.GiftTypeCode != "normal" || event.GiftValue != 100 || event.TargetUserID != 1002 { + t.Fatalf("normal gift event mismatch: %+v", event) + } +} diff --git a/services/user-service/internal/storage/mysql/cp/repository.go b/services/user-service/internal/storage/mysql/cp/repository.go index 39be9169..17469a51 100644 --- a/services/user-service/internal/storage/mysql/cp/repository.go +++ b/services/user-service/internal/storage/mysql/cp/repository.go @@ -514,7 +514,7 @@ func (r *Repository) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEv return cpdomain.ConsumeResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") } relationType := normalizeRelationType(event.CPRelationType) - if relationType == "" || event.SenderUserID <= 0 || event.TargetUserID <= 0 || event.SenderUserID == event.TargetUserID { + if event.SenderUserID <= 0 || event.TargetUserID <= 0 || event.SenderUserID == event.TargetUserID { return cpdomain.ConsumeResult{Consumed: false}, nil } if strings.TrimSpace(event.EventID) == "" { @@ -540,7 +540,7 @@ func (r *Repository) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEv return cpdomain.ConsumeResult{Consumed: false}, tx.Commit() } userAID, userBID := orderedPair(event.SenderUserID, event.TargetUserID) - // 已有 active 关系时,任何关系礼物都只增加这对关系的亲密值,不再生成新的申请。 + // 已有 active 关系时,任意礼物都只增加这对关系的亲密值;GiftValue 已由 wallet-service 按普通/幸运/超级幸运比例结算。 if relationship, found, err := lockActiveRelationshipTx(ctx, tx, appCode, userAID, userBID); err != nil { return cpdomain.ConsumeResult{}, err } else if found { @@ -556,6 +556,10 @@ func (r *Repository) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEv } return cpdomain.ConsumeResult{Consumed: true, Relationship: updated}, tx.Commit() } + if relationType == "" { + // 普通礼物在双方还没有 active 关系时不能创建申请;这类事件不写消费位点,后续若 MQ 重投仍然是无副作用 no-op。 + return cpdomain.ConsumeResult{Consumed: false}, tx.Commit() + } // 没有 active 关系时,CP/兄弟/姐妹礼物只生成 pending 申请;真正建立关系必须由 B 显式同意。 if err := ensurePairHasNoActiveRelationshipTx(ctx, tx, appCode, userAID, userBID, event.TargetUserID); err != nil { if err := insertConsumptionTx(ctx, tx, appCode, event.EventID, "skipped", "", "", "existing_relationship", nowMs); err != nil { diff --git a/services/user-service/internal/storage/mysql/cp/repository_test.go b/services/user-service/internal/storage/mysql/cp/repository_test.go index 4504bf6f..5b04dfa9 100644 --- a/services/user-service/internal/storage/mysql/cp/repository_test.go +++ b/services/user-service/internal/storage/mysql/cp/repository_test.go @@ -176,6 +176,101 @@ func TestListActiveIntimacyLeaderboardEntriesOrdersAndIncludesBothUsers(t *testi } } +func TestConsumeGiftEventAddsIntimacyForNormalGiftWhenRelationshipExists(t *testing.T) { + schema := mysqlschema.New(t) + repo := New(schema.DB) + ctx := appcode.WithContext(context.Background(), appcode.Default) + nowMS := int64(1700000000000) + + seedCPTestUser(t, schema.DB, 4201, "Normal Gift Alice", nowMS) + seedCPTestUser(t, schema.DB, 4202, "Normal Gift Bob", nowMS) + seedCPLevelRules(t, schema.DB, cpdomain.RelationTypeCP, []int64{0, 100, 300}, nowMS) + seedActiveCPRelationship(t, schema.DB, "normal_gift_cp", cpdomain.RelationTypeCP, 4201, 4202, 90, 1, nowMS) + + result, err := repo.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{ + AppCode: appcode.Default, + EventID: "room_evt_normal_gift_cp", + RoomID: "room-normal", + RoomVersion: 3, + OccurredAtMS: nowMS + 1, + SenderUserID: 4201, + TargetUserID: 4202, + GiftID: "rose", + GiftCount: 1, + GiftValue: 37, + BillingReceiptID: "receipt-normal-gift-cp", + CommandID: "cmd-normal-gift-cp", + GiftTypeCode: "normal", + }, nowMS+2) + if err != nil { + t.Fatalf("consume normal gift for active cp failed: %v", err) + } + if !result.Consumed || result.Relationship.RelationshipID != "normal_gift_cp" { + t.Fatalf("normal gift must update existing relationship: %+v", result) + } + if result.Relationship.IntimacyValue != 127 || result.Relationship.Level != 2 { + t.Fatalf("relationship intimacy/level mismatch: %+v", result.Relationship) + } + + var status string + if err := schema.DB.QueryRowContext(ctx, ` + SELECT status + FROM user_cp_gift_event_consumption + WHERE app_code = ? AND room_event_id = ?`, + appcode.Default, "room_evt_normal_gift_cp", + ).Scan(&status); err != nil { + t.Fatalf("read gift consumption failed: %v", err) + } + if status != "relationship_intimacy_added" { + t.Fatalf("normal gift consumption status mismatch: %s", status) + } +} + +func TestConsumeGiftEventIgnoresNormalGiftWithoutRelationship(t *testing.T) { + schema := mysqlschema.New(t) + repo := New(schema.DB) + ctx := appcode.WithContext(context.Background(), appcode.Default) + nowMS := int64(1700000000000) + + seedCPTestUser(t, schema.DB, 4301, "No CP Alice", nowMS) + seedCPTestUser(t, schema.DB, 4302, "No CP Bob", nowMS) + + result, err := repo.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{ + AppCode: appcode.Default, + EventID: "room_evt_normal_no_cp", + RoomID: "room-normal", + RoomVersion: 3, + OccurredAtMS: nowMS + 1, + SenderUserID: 4301, + TargetUserID: 4302, + GiftID: "rose", + GiftCount: 1, + GiftValue: 37, + BillingReceiptID: "receipt-normal-no-cp", + CommandID: "cmd-normal-no-cp", + GiftTypeCode: "normal", + }, nowMS+2) + if err != nil { + t.Fatalf("consume normal gift without cp failed: %v", err) + } + if result.Consumed { + t.Fatalf("normal gift without active relationship must be ignored: %+v", result) + } + + var applicationCount int64 + if err := schema.DB.QueryRowContext(ctx, ` + SELECT COUNT(*) + FROM user_cp_applications + WHERE app_code = ? AND requester_user_id = ? AND target_user_id = ?`, + appcode.Default, 4301, 4302, + ).Scan(&applicationCount); err != nil { + t.Fatalf("count cp applications failed: %v", err) + } + if applicationCount != 0 { + t.Fatalf("normal gift without relationship must not create application, got %d", applicationCount) + } +} + func TestBreakRelationshipPrepareConfirmIsIdempotent(t *testing.T) { schema := mysqlschema.New(t) repo := New(schema.DB) From 811178252fdc5a6e381072ffff89da3990cb6392 Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 18:40:20 +0800 Subject: [PATCH 24/28] =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/domain/task/task.go | 7 +++ .../internal/service/task/service.go | 22 +++++++- .../internal/service/game/service.go | 51 ++++++++++++++++++- .../internal/service/game/service_test.go | 46 +++++++++++++++-- 4 files changed, 120 insertions(+), 6 deletions(-) diff --git a/services/activity-service/internal/domain/task/task.go b/services/activity-service/internal/domain/task/task.go index 87146c20..ada8175b 100644 --- a/services/activity-service/internal/domain/task/task.go +++ b/services/activity-service/internal/domain/task/task.go @@ -13,13 +13,20 @@ const ( AudienceNewbie = "newbie" ActionNone = "none" + ActionRoom = "room" ActionRoomRandom = "room_random" + ActionRoomWindow = "room_window" + ActionRoomRandomWindow = "room_random_window" + ActionRoomGame = "room_game" ActionRoomRandomGame = "room_random_game" + ActionExploreGame = "explore_game" ActionWallet = "wallet" ActionGiftPanel = "gift_panel" ActionGiftPanelSpecific = "gift_panel_specific" MetricGameSpendCoin = "game_spend_coin" + MetricGameDicePlayCount = "game_dice_play_count" + MetricGameRockPlayCount = "game_rock_play_count" MetricGiftSpendCoin = "gift_spend_coin" MetricGiftSendCount = "gift_send_count" MetricLuckyGiftSpendCoin = "lucky_gift_spend_coin" diff --git a/services/activity-service/internal/service/task/service.go b/services/activity-service/internal/service/task/service.go index e50f2be0..7a2f223c 100644 --- a/services/activity-service/internal/service/task/service.go +++ b/services/activity-service/internal/service/task/service.go @@ -535,10 +535,20 @@ func normalizeActionType(value string) string { switch strings.ToLower(strings.TrimSpace(value)) { case "", taskdomain.ActionNone: return taskdomain.ActionNone + case taskdomain.ActionRoom: + return taskdomain.ActionRoom case taskdomain.ActionRoomRandom: return taskdomain.ActionRoomRandom + case taskdomain.ActionRoomWindow: + return taskdomain.ActionRoomWindow + case taskdomain.ActionRoomRandomWindow: + return taskdomain.ActionRoomRandomWindow + case taskdomain.ActionRoomGame: + return taskdomain.ActionRoomGame case taskdomain.ActionRoomRandomGame: return taskdomain.ActionRoomRandomGame + case taskdomain.ActionExploreGame: + return taskdomain.ActionExploreGame case taskdomain.ActionWallet: return taskdomain.ActionWallet case taskdomain.ActionGiftPanel: @@ -552,7 +562,17 @@ func normalizeActionType(value string) string { func validActionType(value string) bool { switch value { - case taskdomain.ActionNone, taskdomain.ActionRoomRandom, taskdomain.ActionRoomRandomGame, taskdomain.ActionWallet, taskdomain.ActionGiftPanel, taskdomain.ActionGiftPanelSpecific: + case taskdomain.ActionNone, + taskdomain.ActionRoom, + taskdomain.ActionRoomRandom, + taskdomain.ActionRoomWindow, + taskdomain.ActionRoomRandomWindow, + taskdomain.ActionRoomGame, + taskdomain.ActionRoomRandomGame, + taskdomain.ActionExploreGame, + taskdomain.ActionWallet, + taskdomain.ActionGiftPanel, + taskdomain.ActionGiftPanelSpecific: return true default: return false diff --git a/services/game-service/internal/service/game/service.go b/services/game-service/internal/service/game/service.go index 1f92ef4a..8f243f5c 100644 --- a/services/game-service/internal/service/game/service.go +++ b/services/game-service/internal/service/game/service.go @@ -26,6 +26,13 @@ import ( const defaultScene = gamedomain.SceneVoiceRoom +const ( + selfGameIDDice = "dice" + selfGameIDRock = "rock" + taskMetricGameDicePlayed = "game_dice_play_count" + taskMetricGameRockPlayed = "game_rock_play_count" +) + // Repository 隔离 game-service 持久化事实,订单和 session 不能只放内存。 type Repository interface { Ping(ctx context.Context) error @@ -697,7 +704,38 @@ func (s *Service) ProcessLevelEventOutboxBatch(ctx context.Context, runID string _ = s.repository.MarkLevelEventFailed(eventCtx, event.EventID, "task returned "+taskResp.GetStatus(), nextRetry, s.now().UnixMilli()) continue } - // 只有成长和任务两条链路都达到终态才标记 delivered,防止“游戏等级已加、任务未加”的半完成 outbox。 + if playMetric := selfGamePlayCountMetric(event.GameID); playMetric != "" { + // dice/rock 的“玩 1 局”是独立每日任务口径,不能复用扣币金额;仍使用同一条扣费 outbox 派生,保证每个真人扣款参与者最多累计 1 次。 + playResp, relayErr := s.activity.ConsumeTaskEvent(eventCtx, &activityv1.ConsumeTaskEventRequest{ + Meta: &activityv1.RequestMeta{ + RequestId: event.EventID + ":task:play_count", + Caller: "game-service", + SentAtMs: s.now().UnixMilli(), + AppCode: event.AppCode, + }, + EventId: event.EventID + ":task:play_count", + EventType: "SelfGamePlayed", + SourceService: "game-service", + UserId: event.UserID, + MetricType: playMetric, + Value: 1, + OccurredAtMs: event.OccurredAtMS, + DimensionsJson: event.PayloadJSON, + }) + if relayErr != nil { + failure++ + nextRetry := s.now().Add(time.Minute).UnixMilli() + _ = s.repository.MarkLevelEventFailed(eventCtx, event.EventID, xerr.MessageOf(relayErr), nextRetry, s.now().UnixMilli()) + continue + } + if playResp.GetStatus() != "consumed" && playResp.GetStatus() != "skipped" && playResp.GetStatus() != "duplicate" { + failure++ + nextRetry := s.now().Add(time.Minute).UnixMilli() + _ = s.repository.MarkLevelEventFailed(eventCtx, event.EventID, "play task returned "+playResp.GetStatus(), nextRetry, s.now().UnixMilli()) + continue + } + } + // 只有成长、扣币任务和自研游戏局数任务都达到终态才标记 delivered,防止“游戏等级已加、每日任务未加”的半完成 outbox。 if err := s.repository.MarkLevelEventDelivered(eventCtx, event.EventID, s.now().UnixMilli()); err != nil { failure++ continue @@ -707,6 +745,17 @@ func (s *Service) ProcessLevelEventOutboxBatch(ctx context.Context, runID string return int32(len(events)), processed, success, failure, len(events) == batchSize, nil } +func selfGamePlayCountMetric(gameID string) string { + switch strings.TrimSpace(gameID) { + case selfGameIDDice: + return taskMetricGameDicePlayed + case selfGameIDRock: + return taskMetricGameRockPlayed + default: + return "" + } +} + func normalizeScene(scene string) string { scene = strings.ToLower(strings.TrimSpace(scene)) if scene == "" { diff --git a/services/game-service/internal/service/game/service_test.go b/services/game-service/internal/service/game/service_test.go index 8b2a5db0..b4df1926 100644 --- a/services/game-service/internal/service/game/service_test.go +++ b/services/game-service/internal/service/game/service_test.go @@ -1653,6 +1653,42 @@ func TestProcessLevelEventOutboxBatchRelaysGameSpend(t *testing.T) { } } +func TestProcessLevelEventOutboxBatchRelaysSelfGamePlayCount(t *testing.T) { + repo := &fakeRepository{levelEvents: []gamedomain.LevelEventOutbox{{ + AppCode: "lalu", + EventID: "game_level:dice_order_1", + OrderID: "dice_order_1", + UserID: 42, + GameID: "dice", + ProviderOrderID: "dice_match_1:42:debit", + ProviderRoundID: "dice_match_1", + CoinAmount: 100, + WalletTransactionID: "wtx-dice-1", + PayloadJSON: `{"game_id":"dice","match_id":"dice_match_1"}`, + OccurredAtMS: 1700000000000, + }}} + activity := &fakeActivity{status: "consumed"} + svc := New(Config{}, repo, &fakeWallet{}, &fakeUser{}, activity) + svc.now = func() time.Time { return time.UnixMilli(1700000001000) } + + _, _, success, failure, _, err := svc.ProcessLevelEventOutboxBatch(context.Background(), "run_1", "worker_1", 100, 30*time.Second) + if err != nil { + t.Fatalf("ProcessLevelEventOutboxBatch failed: %v", err) + } + if success != 1 || failure != 0 { + t.Fatalf("batch result mismatch: success=%d failure=%d", success, failure) + } + if len(activity.taskRequests) != 2 { + t.Fatalf("task request count mismatch: %d", len(activity.taskRequests)) + } + if activity.taskRequests[0].GetMetricType() != "game_spend_coin" || activity.taskRequests[0].GetValue() != 100 { + t.Fatalf("game spend task mismatch: %+v", activity.taskRequests[0]) + } + if activity.taskRequests[1].GetMetricType() != "game_dice_play_count" || activity.taskRequests[1].GetValue() != 1 || activity.taskRequests[1].GetEventId() != "game_level:dice_order_1:task:play_count" { + t.Fatalf("play count task mismatch: %+v", activity.taskRequests[1]) + } +} + type fakeRepository struct { launchable gamedomain.LaunchableGame platform gamedomain.Platform @@ -1846,10 +1882,11 @@ func (f *fakeWallet) GetBalances(context.Context, *walletv1.GetBalancesRequest) } type fakeActivity struct { - status string - taskStatus string - last *activityv1.ConsumeLevelEventRequest - lastTask *activityv1.ConsumeTaskEventRequest + status string + taskStatus string + last *activityv1.ConsumeLevelEventRequest + lastTask *activityv1.ConsumeTaskEventRequest + taskRequests []*activityv1.ConsumeTaskEventRequest } func (f *fakeActivity) ConsumeLevelEvent(_ context.Context, req *activityv1.ConsumeLevelEventRequest, _ ...grpc.CallOption) (*activityv1.ConsumeLevelEventResponse, error) { @@ -1863,6 +1900,7 @@ func (f *fakeActivity) ConsumeLevelEvent(_ context.Context, req *activityv1.Cons func (f *fakeActivity) ConsumeTaskEvent(_ context.Context, req *activityv1.ConsumeTaskEventRequest, _ ...grpc.CallOption) (*activityv1.ConsumeTaskEventResponse, error) { f.lastTask = req + f.taskRequests = append(f.taskRequests, req) status := f.taskStatus if status == "" { status = "consumed" From eea564ac56cb33530fa4cdb42a0062cd2ed12dec Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 19:12:50 +0800 Subject: [PATCH 25/28] fix cp invite requester room notice --- .../modules/cpnotice/mysql_repository.go | 2 +- .../internal/modules/cpnotice/service.go | 19 ++++++++++++++++++- .../internal/modules/cpnotice/service_test.go | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/services/notice-service/internal/modules/cpnotice/mysql_repository.go b/services/notice-service/internal/modules/cpnotice/mysql_repository.go index f950e120..364ef07a 100644 --- a/services/notice-service/internal/modules/cpnotice/mysql_repository.go +++ b/services/notice-service/internal/modules/cpnotice/mysql_repository.go @@ -195,7 +195,7 @@ func validateDelivery(delivery CPDelivery) error { if isTencentIMC2CChannel(delivery.Channel) && delivery.SenderUserID <= 0 { return xerr.New(xerr.InvalidArgument, "cp c2c delivery sender_user_id is required") } - if (delivery.Channel == channelTencentIMRoom || delivery.Channel == channelTencentIMRegion) && strings.TrimSpace(delivery.GroupID) == "" { + if (delivery.Channel == channelTencentIMRoom || delivery.Channel == channelTencentIMRoomSender || delivery.Channel == channelTencentIMRegion) && strings.TrimSpace(delivery.GroupID) == "" { return xerr.New(xerr.InvalidArgument, "cp group delivery group_id is required") } return nil diff --git a/services/notice-service/internal/modules/cpnotice/service.go b/services/notice-service/internal/modules/cpnotice/service.go index a3635cff..e327db18 100644 --- a/services/notice-service/internal/modules/cpnotice/service.go +++ b/services/notice-service/internal/modules/cpnotice/service.go @@ -25,6 +25,7 @@ const ( channelTencentIMC2C = "tencent_im_c2c" channelTencentIMC2CAcceptor = "tencent_im_c2c_acceptor" channelTencentIMRoom = "tencent_im_room" + channelTencentIMRoomSender = "tencent_im_room_sender" channelTencentIMRegion = "tencent_im_region" noticeTypeApplicationCreated = "cp_application_created" @@ -109,7 +110,7 @@ func (s *Service) publishDelivery(ctx context.Context, delivery CPDelivery, opti Ext: "cp_relation_notice", PayloadJSON: delivery.PayloadJSON, }) - case channelTencentIMRoom: + case channelTencentIMRoom, channelTencentIMRoomSender: err = s.publisher.PublishGroupCustomMessage(publishCtx, tencentim.CustomGroupMessage{ GroupID: delivery.GroupID, EventID: delivery.EventID, @@ -214,6 +215,8 @@ func deliveriesFromMessage(message usermq.UserOutboxMessage, groupIDPrefix strin } roomPayload := cloneJSONMap(basePayload) roomPayload["recipient_user_id"] = strconv.FormatInt(application.Target.UserID, 10) + requesterRoomPayload := cloneJSONMap(basePayload) + requesterRoomPayload["recipient_user_id"] = strconv.FormatInt(application.Requester.UserID, 10) return []CPDelivery{ { AppCode: appCode, @@ -237,6 +240,20 @@ func deliveriesFromMessage(message usermq.UserOutboxMessage, groupIDPrefix strin PayloadJSON: mustJSON(roomPayload), CreatedAtMS: message.OccurredAtMS, }, + { + // 创建申请时 target 需要接收态按钮,requester 也需要同一房间上下文里的等待态弹窗。 + // notice_delivery_events 的主键包含 channel,同一 event_id 不能复用 tencent_im_room, + // 因此发起人房间通知用独立内部 channel,发布边界仍走腾讯 IM 群自定义消息。 + AppCode: appCode, + EventID: message.EventID, + EventType: message.EventType, + Channel: channelTencentIMRoomSender, + NoticeType: noticeTypeApplicationCreated, + TargetUserID: application.Requester.UserID, + GroupID: application.RoomID, + PayloadJSON: mustJSON(requesterRoomPayload), + CreatedAtMS: message.OccurredAtMS, + }, }, nil case eventApplicationAccepted: application, err := applicationFromObject(root) diff --git a/services/notice-service/internal/modules/cpnotice/service_test.go b/services/notice-service/internal/modules/cpnotice/service_test.go index ae837d8d..80afac4d 100644 --- a/services/notice-service/internal/modules/cpnotice/service_test.go +++ b/services/notice-service/internal/modules/cpnotice/service_test.go @@ -11,7 +11,7 @@ import ( "hyapp/pkg/usermq" ) -func TestProcessApplicationCreatedPublishesC2CAndRoomIM(t *testing.T) { +func TestProcessApplicationCreatedPublishesC2CAndBothRoomIMs(t *testing.T) { repo := &fakeRepository{} publisher := &fakePublisher{} service := New(Config{NodeID: "notice-test"}, repo, publisher) @@ -31,10 +31,18 @@ func TestProcessApplicationCreatedPublishesC2CAndRoomIM(t *testing.T) { if len(publisher.userMessages) != 1 || publisher.userMessages[0].FromAccount != "1001" || publisher.userMessages[0].ToAccount != "1002" { t.Fatalf("expected target c2c message, got %+v", publisher.userMessages) } - if len(publisher.groupMessages) != 1 || publisher.groupMessages[0].GroupID != "room-100" { - t.Fatalf("expected room group message, got %+v", publisher.groupMessages) + if len(publisher.groupMessages) != 2 || publisher.groupMessages[0].GroupID != "room-100" || publisher.groupMessages[1].GroupID != "room-100" { + t.Fatalf("expected target and requester room group messages, got %+v", publisher.groupMessages) } - if len(repo.delivered) != 2 || repo.delivered[0].Channel != channelTencentIMC2C || repo.delivered[1].Channel != channelTencentIMRoom { + targetRoomPayload := mustObject(string(publisher.groupMessages[0].PayloadJSON)) + if targetRoomPayload["recipient_user_id"] != "1002" { + t.Fatalf("target room payload mismatch: %+v", targetRoomPayload) + } + requesterRoomPayload := mustObject(string(publisher.groupMessages[1].PayloadJSON)) + if requesterRoomPayload["recipient_user_id"] != "1001" { + t.Fatalf("requester room payload mismatch: %+v", requesterRoomPayload) + } + if len(repo.delivered) != 3 || repo.delivered[0].Channel != channelTencentIMC2C || repo.delivered[1].Channel != channelTencentIMRoom || repo.delivered[2].Channel != channelTencentIMRoomSender { t.Fatalf("delivery channels mismatch: %+v", repo.delivered) } } From cc84e14860e059648c4be0fab1cb7052b4c6f6a0 Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 19:24:51 +0800 Subject: [PATCH 26/28] =?UTF-8?q?=E9=A6=96=E5=85=85=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/gamemq/messages.go | 28 +- .../mysql/dice_matchmaking_repository.go | 51 +- .../internal/storage/mysql/dice_repository.go | 179 ++++++- .../configs/config.docker.yaml | 3 + .../configs/config.tencent.example.yaml | 3 + services/gateway-service/configs/config.yaml | 3 + services/gateway-service/internal/app/app.go | 2 + .../internal/client/statistics_client.go | 81 +++ .../gateway-service/internal/config/config.go | 18 + .../first_recharge_reward_handler.go | 8 +- .../first_recharge_reward_handler_test.go | 33 ++ .../transport/http/gameapi/handler.go | 4 + .../http/gameapi/self_game_report_handler.go | 95 ++++ .../internal/transport/http/handler.go | 6 + .../transport/http/httproutes/router.go | 2 + .../internal/transport/http/router.go | 1 + .../mysql/initdb/001_statistics_service.sql | 123 +++++ .../statistics-service/internal/app/app.go | 131 ++++- .../internal/app/local_flow_test.go | 161 ++++++ .../internal/app/query_http.go | 102 ++++ .../internal/storage/mysql/query.go | 415 ++++++++++++++++ .../internal/storage/mysql/repository.go | 465 ++++++++++++++++++ 22 files changed, 1897 insertions(+), 17 deletions(-) create mode 100644 services/gateway-service/internal/client/statistics_client.go create mode 100644 services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler_test.go create mode 100644 services/gateway-service/internal/transport/http/gameapi/self_game_report_handler.go diff --git a/pkg/gamemq/messages.go b/pkg/gamemq/messages.go index c74a4e61..2b3060cf 100644 --- a/pkg/gamemq/messages.go +++ b/pkg/gamemq/messages.go @@ -11,6 +11,14 @@ const ( MessageTypeGameOutboxEvent = "game_outbox_event" TagGameOutboxEvent = "game_outbox_event" + + EventTypeGameOrderSettled = "GameOrderSettled" + EventTypeSelfGameMatchCreated = "SelfGameMatchCreated" + EventTypeSelfGameMatchReady = "SelfGameMatchReady" + EventTypeSelfGameMatchSettled = "SelfGameMatchSettled" + EventTypeSelfGameMatchCanceled = "SelfGameMatchCanceled" + EventTypeSelfGameMatchFailed = "SelfGameMatchFailed" + EventTypeSelfGamePoolAdjusted = "SelfGamePoolAdjusted" ) // GameOutboxMessage is the MQ representation of one committed game_outbox fact. @@ -35,14 +43,16 @@ func EncodeGameOutboxMessage(message GameOutboxMessage) ([]byte, error) { if strings.TrimSpace(message.AppCode) == "" || strings.TrimSpace(message.EventID) == "" || strings.TrimSpace(message.EventType) == "" || - strings.TrimSpace(message.OrderID) == "" || - message.UserID <= 0 || strings.TrimSpace(message.GameID) == "" || - strings.TrimSpace(message.OpType) == "" || - message.CoinAmount <= 0 || message.OccurredAtMS <= 0 { return nil, errors.New("game outbox message is incomplete") } + if message.EventType == EventTypeGameOrderSettled && (strings.TrimSpace(message.OrderID) == "" || + message.UserID <= 0 || + strings.TrimSpace(message.OpType) == "" || + message.CoinAmount <= 0) { + return nil, errors.New("game order outbox message is incomplete") + } if strings.TrimSpace(message.PayloadJSON) == "" { message.PayloadJSON = "{}" } @@ -61,14 +71,16 @@ func DecodeGameOutboxMessage(body []byte) (GameOutboxMessage, error) { if strings.TrimSpace(message.AppCode) == "" || strings.TrimSpace(message.EventID) == "" || strings.TrimSpace(message.EventType) == "" || - strings.TrimSpace(message.OrderID) == "" || - message.UserID <= 0 || strings.TrimSpace(message.GameID) == "" || - strings.TrimSpace(message.OpType) == "" || - message.CoinAmount <= 0 || message.OccurredAtMS <= 0 { return GameOutboxMessage{}, errors.New("game outbox message is incomplete") } + if message.EventType == EventTypeGameOrderSettled && (strings.TrimSpace(message.OrderID) == "" || + message.UserID <= 0 || + strings.TrimSpace(message.OpType) == "" || + message.CoinAmount <= 0) { + return GameOutboxMessage{}, errors.New("game order outbox message is incomplete") + } if strings.TrimSpace(message.PayloadJSON) == "" { message.PayloadJSON = "{}" } diff --git a/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go b/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go index 831b3a7a..140db81f 100644 --- a/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go +++ b/services/game-service/internal/storage/mysql/dice_matchmaking_repository.go @@ -4,6 +4,7 @@ import ( "context" cryptorand "crypto/rand" "database/sql" + "encoding/json" "errors" "fmt" "math/big" @@ -11,6 +12,7 @@ import ( "time" "hyapp/pkg/appcode" + "hyapp/pkg/gamemq" "hyapp/pkg/xerr" dicedomain "hyapp/services/game-service/internal/domain/dice" ) @@ -252,6 +254,13 @@ func (r *Repository) CancelDiceMatch(ctx context.Context, appCode string, matchI ); err != nil { return dicedomain.Match{}, err } + match.Status = dicedomain.MatchStatusCanceled + match.CanceledAtMS = nowMs + match.UpdatedAtMS = nowMs + match.Participants = participants + if err := insertSelfGameMatchOutbox(ctx, tx, gamemq.EventTypeSelfGameMatchCanceled, match, nowMs); err != nil { + return dicedomain.Match{}, err + } if err := tx.Commit(); err != nil { return dicedomain.Match{}, err } @@ -385,6 +394,34 @@ func (r *Repository) AdjustDicePool(ctx context.Context, adjustment dicedomain.P return dicedomain.PoolAdjustment{}, err } } + payload, err := json.Marshal(map[string]any{ + "adjustment_id": adjustment.AdjustmentID, + "game_id": adjustment.GameID, + "match_id": adjustment.MatchID, + "user_id": adjustment.UserID, + "direction": adjustment.Direction, + "amount_coin": adjustment.AmountCoin, + "reason": adjustment.Reason, + "balance_after": adjustment.BalanceAfter, + "created_by": adjustment.CreatedBy, + "created_at_ms": adjustment.CreatedAtMS, + }) + if err != nil { + return dicedomain.PoolAdjustment{}, err + } + // 奖池调整和奖池流水同事务写入 outbox;统计服务消费 owner fact 后才能同时得到入池、出池和余额快照。 + if _, err := tx.ExecContext(ctx, ` + INSERT INTO game_outbox ( + app_code, event_id, event_type, order_id, user_id, platform_code, game_id, + op_type, coin_amount, payload_json, status, attempts, next_retry_at_ms, + created_at_ms, updated_at_ms, published_at_ms + ) VALUES (?, ?, ?, '', ?, 'self', ?, '', 0, CAST(? AS JSON), 'pending', 0, 0, ?, ?, 0) + ON DUPLICATE KEY UPDATE updated_at_ms = VALUES(updated_at_ms)`, + adjustment.AppCode, gamemq.EventTypeSelfGamePoolAdjusted+":"+adjustment.AdjustmentID, gamemq.EventTypeSelfGamePoolAdjusted, + adjustment.UserID, adjustment.GameID, string(payload), adjustment.CreatedAtMS, adjustment.CreatedAtMS, + ); err != nil { + return dicedomain.PoolAdjustment{}, err + } if err := tx.Commit(); err != nil { return dicedomain.PoolAdjustment{}, err } @@ -672,7 +709,19 @@ func updateDiceMatchAfterJoinLocked(ctx context.Context, tx *sql.Tx, match diced match.CurrentPlayers, nextStatus, readyAtMS, strings.TrimSpace(matchMode), strings.TrimSpace(forcedResult), nowMs, match.AppCode, match.MatchID, ) - return err + if err != nil { + return err + } + match.Status = nextStatus + match.ReadyAtMS = readyAtMS + match.MatchMode = strings.TrimSpace(matchMode) + match.ForcedResult = strings.TrimSpace(forcedResult) + match.UpdatedAtMS = nowMs + if nextStatus == dicedomain.MatchStatusReady { + // ready 事件只用 INSERT IGNORE 写一次;真人 join 和机器人补位可能都走这里,幂等键保证不会重复统计匹配成功局。 + return insertSelfGameMatchOutbox(ctx, tx, gamemq.EventTypeSelfGameMatchReady, match, nowMs) + } + return nil } func normalizeDiceGameID(gameID string) string { diff --git a/services/game-service/internal/storage/mysql/dice_repository.go b/services/game-service/internal/storage/mysql/dice_repository.go index e8dea804..8185fd3b 100644 --- a/services/game-service/internal/storage/mysql/dice_repository.go +++ b/services/game-service/internal/storage/mysql/dice_repository.go @@ -8,6 +8,7 @@ import ( "strings" "hyapp/pkg/appcode" + "hyapp/pkg/gamemq" "hyapp/pkg/xerr" dicedomain "hyapp/services/game-service/internal/domain/dice" ) @@ -55,6 +56,9 @@ func (r *Repository) CreateDiceMatch(ctx context.Context, match dicedomain.Match return dicedomain.Match{}, err } } + if err := insertSelfGameMatchOutbox(ctx, tx, gamemq.EventTypeSelfGameMatchCreated, match, match.CreatedAtMS); err != nil { + return dicedomain.Match{}, err + } if err := tx.Commit(); err != nil { return dicedomain.Match{}, err } @@ -139,6 +143,20 @@ func (r *Repository) JoinDiceMatch(ctx context.Context, appCode string, matchID ); err != nil { return dicedomain.Match{}, err } + match.CurrentPlayers = nextPlayers + match.Status = nextStatus + match.ReadyAtMS = readyAtMS + match.UpdatedAtMS = nowMs + participants, err := queryDiceParticipants(ctx, tx, app, match.MatchID, false) + if err != nil { + return dicedomain.Match{}, err + } + match.Participants = participants + if nextStatus == dicedomain.MatchStatusReady { + if err := insertSelfGameMatchOutbox(ctx, tx, gamemq.EventTypeSelfGameMatchReady, match, nowMs); err != nil { + return dicedomain.Match{}, err + } + } if err := tx.Commit(); err != nil { return dicedomain.Match{}, err } @@ -394,19 +412,176 @@ func (r *Repository) MarkDiceMatchSettled(ctx context.Context, appCode string, m ); err != nil { return err } + match, err := queryDiceMatch(ctx, tx, app, strings.TrimSpace(matchID), true) + if err != nil { + return err + } + participants, err := queryDiceParticipants(ctx, tx, app, match.MatchID, true) + if err != nil { + return err + } + match.Participants = participants + if err := insertSelfGameMatchOutbox(ctx, tx, gamemq.EventTypeSelfGameMatchSettled, match, nowMs); err != nil { + return err + } return tx.Commit() } func (r *Repository) MarkDiceMatchFailed(ctx context.Context, appCode string, matchID string, result string, nowMs int64) error { - _, err := r.db.ExecContext(ctx, + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + app := appcode.Normalize(appCode) + if _, err := tx.ExecContext(ctx, `UPDATE game_dice_matches SET status = ?, result = ?, updated_at_ms = ? WHERE app_code = ? AND match_id = ?`, - dicedomain.MatchStatusFailed, strings.TrimSpace(result), nowMs, appcode.Normalize(appCode), strings.TrimSpace(matchID), + dicedomain.MatchStatusFailed, strings.TrimSpace(result), nowMs, app, strings.TrimSpace(matchID), + ); err != nil { + return err + } + match, err := queryDiceMatch(ctx, tx, app, strings.TrimSpace(matchID), true) + if err != nil { + return err + } + participants, err := queryDiceParticipants(ctx, tx, app, match.MatchID, true) + if err != nil { + return err + } + match.Participants = participants + if err := insertSelfGameMatchOutbox(ctx, tx, gamemq.EventTypeSelfGameMatchFailed, match, nowMs); err != nil { + return err + } + return tx.Commit() +} + +func insertSelfGameMatchOutbox(ctx context.Context, tx *sql.Tx, eventType string, match dicedomain.Match, nowMs int64) error { + match.AppCode = appcode.Normalize(match.AppCode) + match.GameID = strings.TrimSpace(match.GameID) + if match.AppCode == "" || match.MatchID == "" || match.GameID == "" || nowMs <= 0 { + return xerr.New(xerr.InvalidArgument, "self game match event is incomplete") + } + payload := selfGameMatchOutboxPayload(match, nowMs) + raw, err := json.Marshal(payload) + if err != nil { + return err + } + userID := int64(0) + if len(match.Participants) > 0 { + userID = match.Participants[0].UserID + } + // 自研局级事件没有单笔订单语义,order_id/op_type/coin_amount 只保留空值;统计服务按 event_type 解析 payload。 + _, err = tx.ExecContext(ctx, ` + INSERT IGNORE INTO game_outbox ( + app_code, event_id, event_type, order_id, user_id, platform_code, game_id, op_type, + coin_amount, payload_json, status, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, '', ?, ?, ?, '', 0, CAST(? AS JSON), 'pending', ?, ?)`, + match.AppCode, eventType+":"+match.MatchID, eventType, userID, strings.TrimSpace(match.PlatformCode), + match.GameID, string(raw), nowMs, nowMs, ) return err } +func selfGameMatchOutboxPayload(match dicedomain.Match, nowMs int64) map[string]any { + participants := make([]map[string]any, 0, len(match.Participants)) + userStakeCoin, robotStakeCoin, payoutCoin, refundCoin := int64(0), int64(0), int64(0), int64(0) + userParticipants, robotParticipants, winners, losers, draws := int64(0), int64(0), int64(0), int64(0), int64(0) + for _, participant := range match.Participants { + isRobot := participant.ParticipantType == dicedomain.ParticipantTypeRobot + if isRobot { + robotParticipants++ + robotStakeCoin += participant.StakeCoin + } else { + userParticipants++ + userStakeCoin += participant.StakeCoin + } + switch participant.Result { + case dicedomain.ParticipantResultWin: + winners++ + case dicedomain.ParticipantResultLose: + losers++ + case dicedomain.ParticipantResultDraw: + draws++ + if !isRobot { + refundCoin += participant.PayoutCoin + } + } + if !isRobot && participant.Result != dicedomain.ParticipantResultDraw { + payoutCoin += participant.PayoutCoin + } + netWinCoin := participant.PayoutCoin + if !isRobot { + netWinCoin -= participant.StakeCoin + } + participants = append(participants, map[string]any{ + "user_id": participant.UserID, + "participant_type": participant.ParticipantType, + "seat_no": participant.SeatNo, + "status": participant.Status, + "stake_coin": participant.StakeCoin, + "dice_points": participant.DicePoints, + "rps_gesture": strings.TrimSpace(participant.RPSGesture), + "result": strings.TrimSpace(participant.Result), + "payout_coin": participant.PayoutCoin, + "net_win_coin": netWinCoin, + "joined_at_ms": participant.JoinedAtMS, + "updated_at_ms": participant.UpdatedAtMS, + }) + } + waitMS, durationMS := int64(0), int64(0) + if match.ReadyAtMS > 0 && match.CreatedAtMS > 0 { + waitMS = match.ReadyAtMS - match.CreatedAtMS + } + finishedAtMS := match.SettledAtMS + if finishedAtMS == 0 { + finishedAtMS = match.CanceledAtMS + } + if finishedAtMS > 0 && match.CreatedAtMS > 0 { + durationMS = finishedAtMS - match.CreatedAtMS + } + return map[string]any{ + "match_id": match.MatchID, + "game_id": strings.TrimSpace(match.GameID), + "platform_code": strings.TrimSpace(match.PlatformCode), + "provider_game_id": strings.TrimSpace(match.ProviderGameID), + "room_id": strings.TrimSpace(match.RoomID), + "region_id": match.RegionID, + "stake_coin": match.StakeCoin, + "min_players": match.MinPlayers, + "max_players": match.MaxPlayers, + "current_players": match.CurrentPlayers, + "status": strings.TrimSpace(match.Status), + "result": strings.TrimSpace(match.Result), + "match_mode": strings.TrimSpace(match.MatchMode), + "forced_result": strings.TrimSpace(match.ForcedResult), + "fee_bps": match.FeeBPS, + "pool_bps": match.PoolBPS, + "pool_delta_coin": match.PoolDeltaCoin, + "user_participants": userParticipants, + "robot_participants": robotParticipants, + "winner_count": winners, + "loser_count": losers, + "draw_count": draws, + "user_stake_coin": userStakeCoin, + "robot_stake_coin": robotStakeCoin, + "payout_coin": payoutCoin, + "refund_coin": refundCoin, + "platform_profit_coin": userStakeCoin - payoutCoin - refundCoin, + "join_deadline_ms": match.JoinDeadlineMS, + "ready_at_ms": match.ReadyAtMS, + "created_at_ms": match.CreatedAtMS, + "updated_at_ms": match.UpdatedAtMS, + "settled_at_ms": match.SettledAtMS, + "canceled_at_ms": match.CanceledAtMS, + "wait_ms": waitMS, + "duration_ms": durationMS, + "participants": participants, + "event_recorded_at_ms": nowMs, + } +} + func queryDiceMatch(ctx context.Context, q diceQuerier, appCode string, matchID string, forUpdate bool) (dicedomain.Match, error) { // forUpdate 只给状态推进路径使用;读路径不加锁,避免普通刷新阻塞 join/roll。 query := `SELECT app_code, match_id, game_id, platform_code, provider_game_id, room_id, region_id, diff --git a/services/gateway-service/configs/config.docker.yaml b/services/gateway-service/configs/config.docker.yaml index a6b24894..d2d0be9b 100644 --- a/services/gateway-service/configs/config.docker.yaml +++ b/services/gateway-service/configs/config.docker.yaml @@ -34,6 +34,9 @@ user_service_addr: "user-service:13005" wallet_service_addr: "wallet-service:13004" activity_service_addr: "activity-service:13006" game_service_addr: "game-service:13008" +statistics: + base_url: "http://statistics-service:13010" + timeout: "2s" grpc_client: # Docker 本地也使用生产同形态的 deadline、keepalive 和窄重试策略。 default_timeout: "5s" diff --git a/services/gateway-service/configs/config.tencent.example.yaml b/services/gateway-service/configs/config.tencent.example.yaml index 57011b7d..d12d2655 100644 --- a/services/gateway-service/configs/config.tencent.example.yaml +++ b/services/gateway-service/configs/config.tencent.example.yaml @@ -38,6 +38,9 @@ user_service_addr: "user-service.internal:13005" wallet_service_addr: "wallet-service.internal:13004" activity_service_addr: "activity-service.internal:13006" game_service_addr: "game-service.internal:13008" +statistics: + base_url: "http://statistics-service.internal:13010" + timeout: "2s" grpc_client: # 线上通过内网 CLB/DNS 访问内部服务;重试只覆盖瞬时 UNAVAILABLE,避免放大业务副作用。 default_timeout: "5s" diff --git a/services/gateway-service/configs/config.yaml b/services/gateway-service/configs/config.yaml index 243d2a3b..8601f69f 100644 --- a/services/gateway-service/configs/config.yaml +++ b/services/gateway-service/configs/config.yaml @@ -34,6 +34,9 @@ user_service_addr: "127.0.0.1:13005" wallet_service_addr: "127.0.0.1:13004" activity_service_addr: "127.0.0.1:13006" game_service_addr: "127.0.0.1:13008" +statistics: + base_url: "http://127.0.0.1:13010" + timeout: "2s" grpc_client: # gateway 内部 gRPC 默认 deadline 包含一次 UNAVAILABLE 重试;业务长调用应在 handler 上下文显式收紧或放宽。 default_timeout: "5s" diff --git a/services/gateway-service/internal/app/app.go b/services/gateway-service/internal/app/app.go index e5924211..c0cacb5d 100644 --- a/services/gateway-service/internal/app/app.go +++ b/services/gateway-service/internal/app/app.go @@ -109,6 +109,7 @@ func New(cfg config.Config) (*App, error) { var weeklyStarClient client.WeeklyStarClient = client.NewGRPCWeeklyStarClient(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) appConfigReader, err := openAppConfigReader(cfg.AppConfig) if err != nil { _ = roomConn.Close() @@ -179,6 +180,7 @@ func New(cfg config.Config) (*App, error) { handler.SetWeeklyStarClient(weeklyStarClient) handler.SetBroadcastClient(broadcastClient) handler.SetGameClient(gameClient) + handler.SetStatisticsClient(statisticsClient) handler.SetLeaderboardWalletDB(leaderboardWalletDB) if appConfigReader != nil { handler.SetAppConfigReader(appConfigReader) diff --git a/services/gateway-service/internal/client/statistics_client.go b/services/gateway-service/internal/client/statistics_client.go new file mode 100644 index 00000000..0b3c3d6f --- /dev/null +++ b/services/gateway-service/internal/client/statistics_client.go @@ -0,0 +1,81 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +type SelfGameH5Event struct { + AppCode string `json:"app_code"` + EventID string `json:"event_id"` + EventName string `json:"event_name"` + GameID string `json:"game_id"` + Page string `json:"page"` + SessionID string `json:"session_id"` + MatchID string `json:"match_id"` + UserID int64 `json:"user_id"` + CountryID int64 `json:"country_id"` + RegionID int64 `json:"region_id"` + Language string `json:"language"` + ClientVersion string `json:"client_version"` + H5Version string `json:"h5_version"` + EntrySource string `json:"entry_source"` + DurationMS int64 `json:"duration_ms"` + Success bool `json:"success"` + ErrorCode string `json:"error_code"` + OccurredAtMS int64 `json:"occurred_at_ms"` +} + +type StatisticsClient interface { + ReportSelfGameH5Event(ctx context.Context, event SelfGameH5Event) error +} + +type HTTPStatisticsClient struct { + baseURL string + httpClient *http.Client +} + +func NewHTTPStatisticsClient(baseURL string, timeout time.Duration) *HTTPStatisticsClient { + baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/") + if baseURL == "" { + return nil + } + if timeout <= 0 { + timeout = 2 * time.Second + } + return &HTTPStatisticsClient{ + baseURL: baseURL, + httpClient: &http.Client{Timeout: timeout}, + } +} + +func (c *HTTPStatisticsClient) ReportSelfGameH5Event(ctx context.Context, event SelfGameH5Event) error { + if c == nil || c.httpClient == nil || c.baseURL == "" { + return fmt.Errorf("statistics client is not configured") + } + body, err := json.Marshal(event) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/internal/v1/statistics/self-game/events", bytes.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + resp, err := c.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return nil + } + payload, _ := io.ReadAll(io.LimitReader(resp.Body, 512)) + return fmt.Errorf("statistics report failed: status=%d body=%s", resp.StatusCode, strings.TrimSpace(string(payload))) +} diff --git a/services/gateway-service/internal/config/config.go b/services/gateway-service/internal/config/config.go index 05c3d678..96e52ed5 100644 --- a/services/gateway-service/internal/config/config.go +++ b/services/gateway-service/internal/config/config.go @@ -26,6 +26,7 @@ type Config struct { WalletServiceAddr string `yaml:"wallet_service_addr"` ActivityServiceAddr string `yaml:"activity_service_addr"` GameServiceAddr string `yaml:"game_service_addr"` + Statistics StatisticsConfig `yaml:"statistics"` GRPCClient GRPCClientConfig `yaml:"grpc_client"` AuthRateLimit AuthRateLimitConfig `yaml:"auth_rate_limit"` LoginRisk LoginRiskConfig `yaml:"login_risk"` @@ -122,6 +123,12 @@ type LeaderboardConfig struct { WalletMySQLDSN string `yaml:"wallet_mysql_dsn"` } +// StatisticsConfig 描述 gateway 转发客户端 H5 埋点到 statistics-service 的内部 HTTP 入口。 +type StatisticsConfig struct { + BaseURL string `yaml:"base_url"` + Timeout time.Duration `yaml:"timeout"` +} + // AuthRateLimitConfig 控制 gateway public auth 入口的 Redis 频控。 type AuthRateLimitConfig struct { // Enabled 为 false 时关闭 gateway auth 入口频控。 @@ -273,6 +280,10 @@ func Default() Config { WalletServiceAddr: "127.0.0.1:13004", ActivityServiceAddr: "127.0.0.1:13006", GameServiceAddr: "127.0.0.1:13008", + Statistics: StatisticsConfig{ + BaseURL: "http://127.0.0.1:13010", + Timeout: 2 * time.Second, + }, GRPCClient: GRPCClientConfig{ DefaultTimeout: 5 * time.Second, ConnectTimeout: 2 * time.Second, @@ -384,6 +395,7 @@ func (cfg *Config) Normalize() error { cfg.WalletServiceAddr = strings.TrimSpace(cfg.WalletServiceAddr) cfg.ActivityServiceAddr = strings.TrimSpace(cfg.ActivityServiceAddr) cfg.GameServiceAddr = strings.TrimSpace(cfg.GameServiceAddr) + cfg.Statistics.BaseURL = strings.TrimRight(strings.TrimSpace(cfg.Statistics.BaseURL), "/") if cfg.WalletServiceAddr == "" { cfg.WalletServiceAddr = "127.0.0.1:13004" } @@ -393,6 +405,12 @@ func (cfg *Config) Normalize() error { if cfg.GameServiceAddr == "" { cfg.GameServiceAddr = "127.0.0.1:13008" } + if cfg.Statistics.BaseURL == "" { + cfg.Statistics.BaseURL = "http://127.0.0.1:13010" + } + if cfg.Statistics.Timeout <= 0 { + cfg.Statistics.Timeout = 2 * time.Second + } runtimeGRPC := cfg.GRPCClient.Runtime() cfg.GRPCClient = GRPCClientConfig{ DefaultTimeout: runtimeGRPC.DefaultTimeout, diff --git a/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler.go b/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler.go index 4d0cef8f..6debeb97 100644 --- a/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler.go +++ b/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler.go @@ -30,6 +30,7 @@ type firstRechargeRewardItemData struct { ResourceType string `json:"resource_type"` ResourceName string `json:"resource_name"` CoverURL string `json:"cover_url"` + AssetURL string `json:"asset_url"` EffectURL string `json:"effect_url"` DurationMS int64 `json:"duration_ms"` Quantity int64 `json:"quantity"` @@ -196,8 +197,11 @@ func firstRechargeRewardItemsFromGroup(group checkinResourceGroupData) []firstRe if item.Resource != nil { reward.ResourceType = item.Resource.ResourceType reward.ResourceName = item.Resource.Name - reward.CoverURL = firstRechargeFirstNonEmpty(item.Resource.AssetURL, item.Resource.PreviewURL, item.Resource.AnimationURL) - reward.EffectURL = firstRechargeFirstNonEmpty(item.Resource.AnimationURL, item.Resource.PreviewURL, item.Resource.AssetURL) + // 首充弹窗格子只展示静态封面,点击后才播放资源资产;因此优先把 preview_url 下发为 cover_url, + // asset_url 保持资源主资产原值,effect_url 继续兜底旧客户端使用的动画预览字段。 + reward.CoverURL = firstRechargeFirstNonEmpty(item.Resource.PreviewURL, item.Resource.AssetURL, item.Resource.AnimationURL) + reward.AssetURL = firstRechargeFirstNonEmpty(item.Resource.AssetURL, item.Resource.AnimationURL) + reward.EffectURL = firstRechargeFirstNonEmpty(item.Resource.AnimationURL, item.Resource.AssetURL, item.Resource.PreviewURL) } else { reward.ResourceType = firstRechargeWalletAssetType(item.WalletAssetType) reward.ResourceName = firstRechargeWalletAssetType(item.WalletAssetType) diff --git a/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler_test.go b/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler_test.go new file mode 100644 index 00000000..100af070 --- /dev/null +++ b/services/gateway-service/internal/transport/http/activityapi/first_recharge_reward_handler_test.go @@ -0,0 +1,33 @@ +package activityapi + +import "testing" + +func TestFirstRechargeRewardItemsSplitCoverImageAndPreviewAsset(t *testing.T) { + items := firstRechargeRewardItemsFromGroup(checkinResourceGroupData{ + Items: []checkinResourceGroupItemData{{ + ResourceID: 88001, + Resource: &checkinResourceData{ + ResourceID: 88001, + ResourceType: "vehicle", + Name: "Rocket Car", + PreviewURL: "https://cdn.example.test/first-recharge/rocket-cover.png", + AssetURL: "https://cdn.example.test/first-recharge/rocket-preview.mp4", + }, + Quantity: 1, + DurationMS: 604800000, + }}, + }) + + if len(items) != 1 { + t.Fatalf("reward item count = %d", len(items)) + } + if items[0].CoverURL != "https://cdn.example.test/first-recharge/rocket-cover.png" { + t.Fatalf("cover_url should stay static image, got %q", items[0].CoverURL) + } + if items[0].AssetURL != "https://cdn.example.test/first-recharge/rocket-preview.mp4" { + t.Fatalf("asset_url should carry mp4 preview asset, got %q", items[0].AssetURL) + } + if items[0].EffectURL != "https://cdn.example.test/first-recharge/rocket-preview.mp4" { + t.Fatalf("effect_url should keep old preview fallback, got %q", items[0].EffectURL) + } +} diff --git a/services/gateway-service/internal/transport/http/gameapi/handler.go b/services/gateway-service/internal/transport/http/gameapi/handler.go index 642b50d7..e9377b53 100644 --- a/services/gateway-service/internal/transport/http/gameapi/handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/handler.go @@ -14,6 +14,7 @@ type Handler struct { userAuthClient client.UserAuthClient userProfileClient client.UserProfileClient walletClient client.WalletClient + statisticsClient client.StatisticsClient // 热游固定回调地址先进入 hyapp gateway;chatapp3 老 token 需要原样转给 chatapp3,hyapp token 继续落到 game-service。 hotgameCompatHTTPClient *http.Client } @@ -23,6 +24,7 @@ type Config struct { UserAuthClient client.UserAuthClient UserProfileClient client.UserProfileClient WalletClient client.WalletClient + StatisticsClient client.StatisticsClient } func New(config Config) *Handler { @@ -31,6 +33,7 @@ func New(config Config) *Handler { userAuthClient: config.UserAuthClient, userProfileClient: config.UserProfileClient, walletClient: config.WalletClient, + statisticsClient: config.StatisticsClient, hotgameCompatHTTPClient: hotgameCompatHTTPClient(), } } @@ -56,6 +59,7 @@ func (h *Handler) Handlers() httproutes.GameHandlers { GetRPSMatch: h.getRPSMatch, RollRPSMatch: h.rollRPSMatch, CancelRPSMatch: h.cancelRPSMatch, + ReportSelfGameH5Event: h.reportSelfGameH5Event, GetRoomRPSConfig: h.getRoomRPSConfig, ListRoomRPSChallenges: h.listRoomRPSChallenges, CreateRoomRPSChallenge: h.createRoomRPSChallenge, diff --git a/services/gateway-service/internal/transport/http/gameapi/self_game_report_handler.go b/services/gateway-service/internal/transport/http/gameapi/self_game_report_handler.go new file mode 100644 index 00000000..31508664 --- /dev/null +++ b/services/gateway-service/internal/transport/http/gameapi/self_game_report_handler.go @@ -0,0 +1,95 @@ +package gameapi + +import ( + "net/http" + "strings" + "time" + + userv1 "hyapp.local/api/proto/user/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/client" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +type selfGameH5EventRequest struct { + EventID string `json:"event_id"` + EventName string `json:"event_name"` + GameID string `json:"game_id"` + Page string `json:"page"` + SessionID string `json:"session_id"` + MatchID string `json:"match_id"` + Language string `json:"language"` + ClientVersion string `json:"client_version"` + H5Version string `json:"h5_version"` + EntrySource string `json:"entry_source"` + DurationMS int64 `json:"duration_ms"` + Success bool `json:"success"` + ErrorCode string `json:"error_code"` + OccurredAtMS int64 `json:"occurred_at_ms"` +} + +func (h *Handler) reportSelfGameH5Event(writer http.ResponseWriter, request *http.Request) { + if h.statisticsClient == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body selfGameH5EventRequest + if err := httpkit.DecodeJSON(request.Body, &body); err != nil { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidJSON, "invalid request body") + return + } + body.EventID = strings.TrimSpace(body.EventID) + body.EventName = strings.TrimSpace(body.EventName) + body.GameID = strings.TrimSpace(body.GameID) + if body.EventID == "" || body.EventName == "" || body.GameID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + userID := auth.UserIDFromContext(request.Context()) + userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: userID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + if body.OccurredAtMS <= 0 { + body.OccurredAtMS = time.Now().UTC().UnixMilli() + } + language := strings.TrimSpace(body.Language) + if language == "" { + language = requestLanguage(request) + } + clientVersion := strings.TrimSpace(body.ClientVersion) + if clientVersion == "" { + clientVersion = httpkit.FirstHeader(request, "X-App-Version", "X-Client-Version") + } + // H5 上报只信任客户端的行为事实和耗时;用户、App、国家和区域由 gateway 根据登录态补齐,避免前端伪造维度污染统计口径。 + err = h.statisticsClient.ReportSelfGameH5Event(request.Context(), client.SelfGameH5Event{ + AppCode: appcode.FromContext(request.Context()), + EventID: body.EventID, + EventName: body.EventName, + GameID: body.GameID, + Page: body.Page, + SessionID: body.SessionID, + MatchID: body.MatchID, + UserID: userID, + CountryID: userResp.GetUser().GetCountryId(), + RegionID: userResp.GetUser().GetRegionId(), + Language: language, + ClientVersion: clientVersion, + H5Version: body.H5Version, + EntrySource: body.EntrySource, + DurationMS: body.DurationMS, + Success: body.Success, + ErrorCode: body.ErrorCode, + OccurredAtMS: body.OccurredAtMS, + }) + if err != nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + httpkit.WriteOK(writer, request, map[string]any{"ok": true}) +} diff --git a/services/gateway-service/internal/transport/http/handler.go b/services/gateway-service/internal/transport/http/handler.go index 531b4b43..17d178c8 100644 --- a/services/gateway-service/internal/transport/http/handler.go +++ b/services/gateway-service/internal/transport/http/handler.go @@ -50,6 +50,7 @@ type Handler struct { weeklyStar client.WeeklyStarClient broadcastClient client.BroadcastClient gameClient client.GameClient + statisticsClient client.StatisticsClient appConfigReader appapi.ConfigReader leaderboardWalletDB *sql.DB tencentIM TencentIMConfig @@ -268,6 +269,11 @@ func (h *Handler) SetGameClient(gameClient client.GameClient) { h.gameClient = gameClient } +// SetStatisticsClient 注入 statistics-service 内部 HTTP client;gateway 只转发 H5 埋点,不在入口层做统计聚合。 +func (h *Handler) SetStatisticsClient(statisticsClient client.StatisticsClient) { + h.statisticsClient = statisticsClient +} + // SetLeaderboardWalletDB 注入 App 榜单只读钱包库连接。 func (h *Handler) SetLeaderboardWalletDB(db *sql.DB) { h.leaderboardWalletDB = db diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index 00f5abc7..f1e557ed 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -272,6 +272,7 @@ type GameHandlers struct { GetRPSMatch http.HandlerFunc RollRPSMatch http.HandlerFunc CancelRPSMatch http.HandlerFunc + ReportSelfGameH5Event http.HandlerFunc GetRoomRPSConfig http.HandlerFunc ListRoomRPSChallenges http.HandlerFunc CreateRoomRPSChallenge http.HandlerFunc @@ -593,6 +594,7 @@ func (r routes) registerGameRoutes() { r.profile("/games/rps/matches/{match_id}", http.MethodGet, h.GetRPSMatch) r.profile("/games/rps/matches/{match_id}/roll", http.MethodPost, h.RollRPSMatch) r.profile("/games/rps/matches/{match_id}/cancel", http.MethodPost, h.CancelRPSMatch) + r.profile("/games/self/report-events", http.MethodPost, h.ReportSelfGameH5Event) r.profile("/games/room-rps/config", http.MethodGet, h.GetRoomRPSConfig) r.profile("/games/room-rps/challenges", http.MethodGet, h.ListRoomRPSChallenges) // 当前 ServeMux 包装按 path 注册,不能把 GET 列表和 POST 创建同时挂在 /challenges;创建动作单独放 /create,避免路由覆盖。 diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index e7649d05..04fd7d2c 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -90,6 +90,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { UserAuthClient: h.userClient, UserProfileClient: h.userProfileClient, WalletClient: h.walletClient, + StatisticsClient: h.statisticsClient, }) resourceAPI := resourceapi.New(resourceapi.Config{ WalletClient: h.walletClient, diff --git a/services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql b/services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql index b4b707e6..e998a2ce 100644 --- a/services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql +++ b/services/statistics-service/deploy/mysql/initdb/001_statistics_service.sql @@ -134,3 +134,126 @@ CREATE TABLE IF NOT EXISTS stat_game_day_players ( PRIMARY KEY (app_code, stat_day, country_id, platform_code, game_id, user_id), KEY idx_stat_game_player_region (app_code, stat_day, region_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游戏日参与用户去重表'; + +CREATE TABLE IF NOT EXISTS stat_self_game_h5_event_day ( + app_code VARCHAR(32) NOT NULL, + stat_day DATE NOT NULL, + country_id BIGINT NOT NULL DEFAULT 0, + region_id BIGINT NOT NULL DEFAULT 0, + game_id VARCHAR(96) NOT NULL, + event_name VARCHAR(64) NOT NULL, + language VARCHAR(16) NOT NULL DEFAULT '', + client_version VARCHAR(64) NOT NULL DEFAULT '', + h5_version VARCHAR(64) NOT NULL DEFAULT '', + entry_source VARCHAR(64) NOT NULL DEFAULT '', + event_count BIGINT NOT NULL DEFAULT 0, + user_count BIGINT NOT NULL DEFAULT 0, + success_count BIGINT NOT NULL DEFAULT 0, + error_count BIGINT NOT NULL DEFAULT 0, + duration_ms_sum BIGINT NOT NULL DEFAULT 0, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, country_id, region_id, game_id, event_name, language, client_version, h5_version, entry_source), + KEY idx_stat_self_game_h5_day (app_code, stat_day, game_id, event_name), + KEY idx_stat_self_game_h5_region (app_code, stat_day, region_id, game_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏 H5 事件日统计表'; + +CREATE TABLE IF NOT EXISTS stat_self_game_h5_event_users ( + app_code VARCHAR(32) NOT NULL, + stat_day DATE NOT NULL, + game_id VARCHAR(96) NOT NULL, + event_name VARCHAR(64) NOT NULL, + user_id BIGINT NOT NULL, + first_seen_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, game_id, event_name, user_id), + KEY idx_stat_self_game_h5_users_day (app_code, stat_day, game_id, event_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏 H5 事件用户去重表'; + +CREATE TABLE IF NOT EXISTS stat_self_game_match_day ( + app_code VARCHAR(32) NOT NULL, + stat_day DATE NOT NULL, + country_id BIGINT NOT NULL DEFAULT 0, + region_id BIGINT NOT NULL DEFAULT 0, + game_id VARCHAR(96) NOT NULL, + stake_coin BIGINT NOT NULL DEFAULT 0, + created_matches BIGINT NOT NULL DEFAULT 0, + matched_matches BIGINT NOT NULL DEFAULT 0, + settled_matches BIGINT NOT NULL DEFAULT 0, + canceled_matches BIGINT NOT NULL DEFAULT 0, + failed_matches BIGINT NOT NULL DEFAULT 0, + human_matches BIGINT NOT NULL DEFAULT 0, + robot_matches BIGINT NOT NULL DEFAULT 0, + human_participants BIGINT NOT NULL DEFAULT 0, + robot_participants BIGINT NOT NULL DEFAULT 0, + winner_count BIGINT NOT NULL DEFAULT 0, + loser_count BIGINT NOT NULL DEFAULT 0, + draw_count BIGINT NOT NULL DEFAULT 0, + user_stake_coin BIGINT NOT NULL DEFAULT 0, + robot_stake_coin BIGINT NOT NULL DEFAULT 0, + payout_coin BIGINT NOT NULL DEFAULT 0, + refund_coin BIGINT NOT NULL DEFAULT 0, + platform_profit_coin BIGINT NOT NULL DEFAULT 0, + pool_delta_coin BIGINT NOT NULL DEFAULT 0, + forced_player_lose_matches BIGINT NOT NULL DEFAULT 0, + wait_ms_sum BIGINT NOT NULL DEFAULT 0, + duration_ms_sum BIGINT NOT NULL DEFAULT 0, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, country_id, region_id, game_id, stake_coin), + KEY idx_stat_self_game_match_day (app_code, stat_day, game_id), + KEY idx_stat_self_game_match_region (app_code, stat_day, region_id, game_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏对局日统计表'; + +CREATE TABLE IF NOT EXISTS stat_self_game_pool_day ( + app_code VARCHAR(32) NOT NULL, + stat_day DATE NOT NULL, + game_id VARCHAR(96) NOT NULL, + direction VARCHAR(16) NOT NULL DEFAULT '', + reason VARCHAR(64) NOT NULL DEFAULT '', + transaction_count BIGINT NOT NULL DEFAULT 0, + in_coin BIGINT NOT NULL DEFAULT 0, + out_coin BIGINT NOT NULL DEFAULT 0, + balance_after_coin BIGINT NOT NULL DEFAULT 0, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, game_id, direction, reason), + KEY idx_stat_self_game_pool_day (app_code, stat_day, game_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏奖池日统计表'; + +CREATE TABLE IF NOT EXISTS stat_self_game_participant_day ( + app_code VARCHAR(32) NOT NULL, + stat_day DATE NOT NULL, + country_id BIGINT NOT NULL DEFAULT 0, + region_id BIGINT NOT NULL DEFAULT 0, + game_id VARCHAR(96) NOT NULL, + stake_coin BIGINT NOT NULL DEFAULT 0, + participant_type VARCHAR(32) NOT NULL DEFAULT 'user', + result VARCHAR(32) NOT NULL DEFAULT '', + rps_gesture VARCHAR(32) NOT NULL DEFAULT '', + dice_point INT NOT NULL DEFAULT 0, + participant_count BIGINT NOT NULL DEFAULT 0, + payout_coin BIGINT NOT NULL DEFAULT 0, + net_win_coin BIGINT NOT NULL DEFAULT 0, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, country_id, region_id, game_id, stake_coin, participant_type, result, rps_gesture, dice_point), + KEY idx_stat_self_game_participant_day (app_code, stat_day, game_id), + KEY idx_stat_self_game_participant_region (app_code, stat_day, region_id, game_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏参与者结果分布日统计表'; + +CREATE TABLE IF NOT EXISTS stat_self_game_user_day ( + app_code VARCHAR(32) NOT NULL, + stat_day DATE NOT NULL, + country_id BIGINT NOT NULL DEFAULT 0, + region_id BIGINT NOT NULL DEFAULT 0, + game_id VARCHAR(96) NOT NULL, + user_id BIGINT NOT NULL, + match_count BIGINT NOT NULL DEFAULT 0, + win_count BIGINT NOT NULL DEFAULT 0, + lose_count BIGINT NOT NULL DEFAULT 0, + draw_count BIGINT NOT NULL DEFAULT 0, + cancel_count BIGINT NOT NULL DEFAULT 0, + stake_coin BIGINT NOT NULL DEFAULT 0, + payout_coin BIGINT NOT NULL DEFAULT 0, + net_win_coin BIGINT NOT NULL DEFAULT 0, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, game_id, user_id), + KEY idx_stat_self_game_user_win (app_code, stat_day, game_id, net_win_coin), + KEY idx_stat_self_game_user_region (app_code, stat_day, region_id, game_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自研游戏用户风险日统计表'; diff --git a/services/statistics-service/internal/app/app.go b/services/statistics-service/internal/app/app.go index 83a5f2c3..32d67553 100644 --- a/services/statistics-service/internal/app/app.go +++ b/services/statistics-service/internal/app/app.go @@ -196,11 +196,21 @@ func newConsumers(cfg config.Config, repo *mysqlstorage.Repository) ([]*rocketmq return nil, err } if err := add(cfg.RocketMQ.GameOutbox, gamemq.TagGameOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { - event, ok, err := gameOrderEvent(message.Body) - if err != nil || !ok { - return err + if event, ok, err := gameOrderEvent(message.Body); err != nil || ok { + if err != nil || !ok { + return err + } + return repo.ConsumeGameOrder(appcode.WithContext(ctx, event.AppCode), event) } - return repo.ConsumeGameOrder(appcode.WithContext(ctx, event.AppCode), event) + match, ok, err := selfGameMatchEvent(message.Body) + if err != nil || !ok { + pool, poolOK, poolErr := selfGamePoolAdjustmentEvent(message.Body) + if poolErr != nil || !poolOK { + return poolErr + } + return repo.ConsumeSelfGamePoolAdjustment(appcode.WithContext(ctx, pool.AppCode), pool) + } + return repo.ConsumeSelfGameMatch(appcode.WithContext(ctx, match.AppCode), match) }); err != nil { shutdownConsumers(consumers) return nil, err @@ -431,6 +441,119 @@ func gameOrderEvent(body []byte) (mysqlstorage.GameOrderEvent, bool, error) { }, true, nil } +func selfGameMatchEvent(body []byte) (mysqlstorage.SelfGameMatchEvent, bool, error) { + message, err := gamemq.DecodeGameOutboxMessage(body) + if err != nil { + return mysqlstorage.SelfGameMatchEvent{}, false, err + } + switch message.EventType { + case gamemq.EventTypeSelfGameMatchCreated, + gamemq.EventTypeSelfGameMatchReady, + gamemq.EventTypeSelfGameMatchSettled, + gamemq.EventTypeSelfGameMatchCanceled, + gamemq.EventTypeSelfGameMatchFailed: + default: + return mysqlstorage.SelfGameMatchEvent{}, false, nil + } + payload := mysqlstorage.DecodeJSON(message.PayloadJSON) + event := mysqlstorage.SelfGameMatchEvent{ + AppCode: message.AppCode, + EventID: message.EventID, + EventType: message.EventType, + MatchID: firstNonEmpty(mysqlstorage.String(payload, "match_id"), message.OrderID), + GameID: firstNonEmpty(mysqlstorage.String(payload, "game_id"), message.GameID), + PlatformCode: firstNonEmpty(mysqlstorage.String(payload, "platform_code"), message.PlatformCode), + RoomID: mysqlstorage.String(payload, "room_id"), + RegionID: mysqlstorage.Int64(payload, "region_id"), + StakeCoin: mysqlstorage.Int64(payload, "stake_coin"), + Status: mysqlstorage.String(payload, "status"), + Result: mysqlstorage.String(payload, "result"), + MatchMode: mysqlstorage.String(payload, "match_mode"), + ForcedResult: mysqlstorage.String(payload, "forced_result"), + UserParticipants: mysqlstorage.Int64(payload, "user_participants"), + RobotParticipants: mysqlstorage.Int64(payload, "robot_participants"), + WinnerCount: mysqlstorage.Int64(payload, "winner_count"), + LoserCount: mysqlstorage.Int64(payload, "loser_count"), + DrawCount: mysqlstorage.Int64(payload, "draw_count"), + UserStakeCoin: mysqlstorage.Int64(payload, "user_stake_coin"), + RobotStakeCoin: mysqlstorage.Int64(payload, "robot_stake_coin"), + PayoutCoin: mysqlstorage.Int64(payload, "payout_coin"), + RefundCoin: mysqlstorage.Int64(payload, "refund_coin"), + PlatformProfitCoin: mysqlstorage.Int64(payload, "platform_profit_coin"), + PoolDeltaCoin: mysqlstorage.Int64(payload, "pool_delta_coin"), + WaitMS: mysqlstorage.Int64(payload, "wait_ms"), + DurationMS: mysqlstorage.Int64(payload, "duration_ms"), + Participants: selfGameParticipants(payload["participants"]), + OccurredAtMS: message.OccurredAtMS, + } + return event, true, nil +} + +func selfGamePoolAdjustmentEvent(body []byte) (mysqlstorage.SelfGamePoolAdjustmentEvent, bool, error) { + message, err := gamemq.DecodeGameOutboxMessage(body) + if err != nil { + return mysqlstorage.SelfGamePoolAdjustmentEvent{}, false, err + } + if message.EventType != gamemq.EventTypeSelfGamePoolAdjusted { + return mysqlstorage.SelfGamePoolAdjustmentEvent{}, false, nil + } + payload := mysqlstorage.DecodeJSON(message.PayloadJSON) + return mysqlstorage.SelfGamePoolAdjustmentEvent{ + AppCode: message.AppCode, + EventID: message.EventID, + GameID: firstNonEmpty(mysqlstorage.String(payload, "game_id"), message.GameID), + Direction: mysqlstorage.String(payload, "direction"), + Reason: mysqlstorage.String(payload, "reason"), + AmountCoin: mysqlstorage.Int64(payload, "amount_coin"), + BalanceAfter: mysqlstorage.Int64(payload, "balance_after"), + OccurredAtMS: message.OccurredAtMS, + }, true, nil +} + +func selfGameParticipants(raw any) []mysqlstorage.SelfGameMatchParticipantEvent { + values, ok := raw.([]any) + if !ok { + return nil + } + participants := make([]mysqlstorage.SelfGameMatchParticipantEvent, 0, len(values)) + for _, value := range values { + payload, ok := value.(map[string]any) + if !ok { + continue + } + participants = append(participants, mysqlstorage.SelfGameMatchParticipantEvent{ + UserID: mysqlstorage.Int64(payload, "user_id"), + ParticipantType: mysqlstorage.String(payload, "participant_type"), + StakeCoin: mysqlstorage.Int64(payload, "stake_coin"), + DicePoints: int64Slice(payload["dice_points"]), + RPSGesture: mysqlstorage.String(payload, "rps_gesture"), + Result: mysqlstorage.String(payload, "result"), + PayoutCoin: mysqlstorage.Int64(payload, "payout_coin"), + NetWinCoin: mysqlstorage.Int64(payload, "net_win_coin"), + }) + } + return participants +} + +func int64Slice(raw any) []int64 { + values, ok := raw.([]any) + if !ok { + return nil + } + out := make([]int64, 0, len(values)) + for _, value := range values { + switch typed := value.(type) { + case float64: + out = append(out, int64(typed)) + case int64: + out = append(out, typed) + case int: + out = append(out, int64(typed)) + } + } + return out +} + func startConsumers(consumers []*rocketmqx.Consumer) error { for _, consumer := range consumers { if err := consumer.Start(); err != nil { diff --git a/services/statistics-service/internal/app/local_flow_test.go b/services/statistics-service/internal/app/local_flow_test.go index f981acb6..13c53cb5 100644 --- a/services/statistics-service/internal/app/local_flow_test.go +++ b/services/statistics-service/internal/app/local_flow_test.go @@ -10,6 +10,7 @@ import ( "google.golang.org/protobuf/proto" roomeventsv1 "hyapp.local/api/proto/events/room/v1" "hyapp/internal/testutil/mysqlschema" + "hyapp/pkg/appcode" "hyapp/pkg/gamemq" "hyapp/pkg/roommq" "hyapp/pkg/walletmq" @@ -201,6 +202,166 @@ func TestLocalStatisticsDataScreenFlow(t *testing.T) { } } +func TestLocalSelfGameStatisticsFlow(t *testing.T) { + ctx := context.Background() + _, file, _, _ := runtime.Caller(0) + schema := mysqlschema.New(t, mysqlschema.Config{ + InitDBPath: mysqlschema.InitDBPath(t, file, "../../deploy/mysql/initdb/001_statistics_service.sql"), + DatabasePrefix: "hy_self_game_stats", + }) + repo, err := mysqlstorage.Open(ctx, schema.DSN) + if err != nil { + t.Fatalf("open statistics repository failed: %v", err) + } + t.Cleanup(func() { _ = repo.Close() }) + + occurredAt := time.Date(2026, 6, 11, 12, 0, 0, 0, time.UTC).UnixMilli() + dayStart := time.Date(2026, 6, 11, 0, 0, 0, 0, time.UTC).UnixMilli() + const ( + appCode = "lalu" + regionID = int64(210) + userID = int64(42) + ) + appCtx := appcode.WithContext(ctx, appCode) + for _, eventName := range []string{"page_open", "match_click", "match_success", "settlement_show", "play_again"} { + if err := repo.ConsumeSelfGameH5Event(appCtx, mysqlstorage.SelfGameH5Event{ + AppCode: appCode, + EventID: "h5:" + eventName, + EventName: eventName, + GameID: "dice", + UserID: userID, + RegionID: regionID, + H5Version: "dice-h5-test", + EntrySource: "room", + DurationMS: 20, + Success: true, + OccurredAtMS: occurredAt, + }); err != nil { + t.Fatalf("consume h5 event %s failed: %v", eventName, err) + } + } + + body, err := gamemq.EncodeGameOutboxMessage(gamemq.GameOutboxMessage{ + AppCode: appCode, + EventID: "SelfGameMatchSettled:dice_match_1", + EventType: gamemq.EventTypeSelfGameMatchSettled, + OrderID: "", + UserID: userID, + PlatformCode: "self", + GameID: "dice", + PayloadJSON: mustJSON(t, map[string]any{ + "match_id": "dice_match_1", + "game_id": "dice", + "platform_code": "self", + "region_id": regionID, + "stake_coin": int64(500), + "status": "settled", + "result": "win", + "match_mode": "robot", + "forced_result": "", + "user_participants": int64(1), + "robot_participants": int64(1), + "winner_count": int64(1), + "loser_count": int64(1), + "draw_count": int64(0), + "user_stake_coin": int64(500), + "robot_stake_coin": int64(500), + "payout_coin": int64(900), + "refund_coin": int64(0), + "platform_profit_coin": int64(100), + "pool_delta_coin": int64(50), + "wait_ms": int64(1200), + "duration_ms": int64(4800), + "participants": []map[string]any{ + { + "user_id": userID, + "participant_type": "user", + "stake_coin": int64(500), + "dice_points": []int64{6}, + "result": "win", + "payout_coin": int64(900), + "net_win_coin": int64(400), + }, + { + "user_id": int64(0), + "participant_type": "robot", + "stake_coin": int64(500), + "dice_points": []int64{2}, + "result": "lose", + "payout_coin": int64(0), + "net_win_coin": int64(-500), + }, + }, + }), + OccurredAtMS: occurredAt, + }) + if err != nil { + t.Fatalf("encode self game match failed: %v", err) + } + match, ok, err := selfGameMatchEvent(body) + if err != nil || !ok { + t.Fatalf("decode self game match failed: ok=%v err=%v", ok, err) + } + if err := repo.ConsumeSelfGameMatch(appCtx, match); err != nil { + t.Fatalf("consume self game match failed: %v", err) + } + poolBody, err := gamemq.EncodeGameOutboxMessage(gamemq.GameOutboxMessage{ + AppCode: appCode, + EventID: "SelfGamePoolAdjusted:pool_adjust_1", + EventType: gamemq.EventTypeSelfGamePoolAdjusted, + UserID: userID, + PlatformCode: "self", + GameID: "dice", + PayloadJSON: mustJSON(t, map[string]any{ + "adjustment_id": "pool_adjust_1", + "game_id": "dice", + "match_id": "dice_match_1", + "user_id": userID, + "direction": "in", + "amount_coin": int64(50), + "reason": "match_pool_in", + "balance_after": int64(1050), + }), + OccurredAtMS: occurredAt, + }) + if err != nil { + t.Fatalf("encode self game pool failed: %v", err) + } + pool, ok, err := selfGamePoolAdjustmentEvent(poolBody) + if err != nil || !ok { + t.Fatalf("decode self game pool failed: ok=%v err=%v", ok, err) + } + if err := repo.ConsumeSelfGamePoolAdjustment(appCtx, pool); err != nil { + t.Fatalf("consume self game pool failed: %v", err) + } + + overview, err := repo.QuerySelfGameOverview(ctx, mysqlstorage.SelfGameOverviewQuery{ + AppCode: appCode, + StartMS: dayStart, + EndMS: dayStart + int64(24*time.Hour/time.Millisecond), + GameID: "dice", + RegionID: regionID, + }) + if err != nil { + t.Fatalf("query self game overview failed: %v", err) + } + if overview.Conversion.PageToMatchClickRate != 1 || overview.Conversion.MatchClickToSuccessRate != 1 || overview.Conversion.SuccessToSettlementRate != 1 || overview.Conversion.SettlementToPlayAgainRate != 1 { + t.Fatalf("self game funnel mismatch: %+v", overview.Conversion) + } + if overview.MatchTotals.SettledMatches != 1 || overview.MatchTotals.RobotMatches != 1 || overview.MatchTotals.UserStakeCoin != 500 || overview.MatchTotals.PayoutCoin != 900 || overview.MatchTotals.PlatformProfitCoin != 100 || overview.MatchTotals.PoolDeltaCoin != 50 { + t.Fatalf("self game match totals mismatch: %+v", overview.MatchTotals) + } + if len(overview.StakeBreakdown) != 1 || overview.StakeBreakdown[0].StakeCoin != 500 || overview.StakeBreakdown[0].PlatformProfitCoin != 100 { + t.Fatalf("self game stake breakdown mismatch: %+v", overview.StakeBreakdown) + } + if len(overview.PoolStats) != 1 || overview.PoolStats[0].InCoin != 50 || overview.PoolStats[0].BalanceAfterCoin != 1050 { + t.Fatalf("self game pool stats mismatch: %+v", overview.PoolStats) + } + if len(overview.UserRisk) != 1 || overview.UserRisk[0].UserID != userID || overview.UserRisk[0].WinCount != 1 || overview.UserRisk[0].NetWinCoin != 400 { + t.Fatalf("self game user risk mismatch: %+v", overview.UserRisk) + } +} + func walletMessage(t *testing.T, message walletmq.WalletOutboxMessage) []byte { t.Helper() body, err := walletmq.EncodeWalletOutboxMessage(message) diff --git a/services/statistics-service/internal/app/query_http.go b/services/statistics-service/internal/app/query_http.go index 81a5897b..716be60e 100644 --- a/services/statistics-service/internal/app/query_http.go +++ b/services/statistics-service/internal/app/query_http.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "hyapp/pkg/appcode" mysqlstorage "hyapp/services/statistics-service/internal/storage/mysql" ) @@ -31,6 +32,8 @@ func newQueryHTTPServer(addr string, repo *mysqlstorage.Repository) (*queryHTTPS s := &queryHTTPServer{repo: repo, listener: listener} mux := http.NewServeMux() mux.HandleFunc("/internal/v1/statistics/overview", s.overview) + mux.HandleFunc("/internal/v1/statistics/self-game/events", s.selfGameEvents) + mux.HandleFunc("/internal/v1/statistics/self-games/overview", s.selfGameOverview) s.server = &http.Server{Handler: mux, ReadHeaderTimeout: 2 * time.Second} return s, nil } @@ -80,6 +83,105 @@ func (s *queryHTTPServer) overview(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, overview) } +type selfGameH5EventRequest struct { + AppCode string `json:"app_code"` + EventID string `json:"event_id"` + EventName string `json:"event_name"` + GameID string `json:"game_id"` + Page string `json:"page"` + SessionID string `json:"session_id"` + MatchID string `json:"match_id"` + UserID int64 `json:"user_id"` + CountryID int64 `json:"country_id"` + RegionID int64 `json:"region_id"` + Language string `json:"language"` + ClientVersion string `json:"client_version"` + H5Version string `json:"h5_version"` + EntrySource string `json:"entry_source"` + DurationMS int64 `json:"duration_ms"` + Success bool `json:"success"` + ErrorCode string `json:"error_code"` + OccurredAtMS int64 `json:"occurred_at_ms"` +} + +func (s *queryHTTPServer) selfGameEvents(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + writeJSON(w, http.StatusMethodNotAllowed, map[string]any{"error": "method not allowed"}) + return + } + defer r.Body.Close() + var body selfGameH5EventRequest + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid json"}) + return + } + body.EventID = strings.TrimSpace(body.EventID) + body.EventName = strings.TrimSpace(body.EventName) + if body.EventID == "" || body.EventName == "" { + writeJSON(w, http.StatusBadRequest, map[string]any{"error": "event_id and event_name are required"}) + return + } + if body.OccurredAtMS <= 0 { + body.OccurredAtMS = time.Now().UTC().UnixMilli() + } + app := appcode.Normalize(body.AppCode) + if app == "" { + app = "lalu" + } + // 内部 ingest 只承接 gateway 已解析过的用户、App 和维度字段;statistics-service 只做幂等聚合,不反查业务库。 + err := s.repo.ConsumeSelfGameH5Event(appcode.WithContext(r.Context(), app), mysqlstorage.SelfGameH5Event{ + AppCode: app, + EventID: body.EventID, + EventName: body.EventName, + GameID: body.GameID, + Page: body.Page, + SessionID: body.SessionID, + MatchID: body.MatchID, + UserID: body.UserID, + CountryID: body.CountryID, + RegionID: body.RegionID, + Language: body.Language, + ClientVersion: body.ClientVersion, + H5Version: body.H5Version, + EntrySource: body.EntrySource, + DurationMS: body.DurationMS, + Success: body.Success, + ErrorCode: body.ErrorCode, + OccurredAtMS: body.OccurredAtMS, + }) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]any{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, map[string]any{"ok": true}) +} + +func (s *queryHTTPServer) selfGameOverview(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + startMS := parseInt64(query.Get("start_ms")) + endMS := parseInt64(query.Get("end_ms")) + if startMS <= 0 { + now := time.Now().UTC() + startMS = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).UnixMilli() + } + if endMS <= startMS { + endMS = startMS + 24*time.Hour.Milliseconds() + } + overview, err := s.repo.QuerySelfGameOverview(r.Context(), mysqlstorage.SelfGameOverviewQuery{ + AppCode: query.Get("app_code"), + StartMS: startMS, + EndMS: endMS, + GameID: query.Get("game_id"), + CountryID: parseInt64(query.Get("country_id")), + RegionID: parseInt64(query.Get("region_id")), + }) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]any{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, overview) +} + func parseInt64(value string) int64 { parsed, _ := strconv.ParseInt(strings.TrimSpace(value), 10, 64) return parsed diff --git a/services/statistics-service/internal/storage/mysql/query.go b/services/statistics-service/internal/storage/mysql/query.go index 7544bc03..1475f361 100644 --- a/services/statistics-service/internal/storage/mysql/query.go +++ b/services/statistics-service/internal/storage/mysql/query.go @@ -155,6 +155,144 @@ type CountryBreakdown struct { ARPPUUSDMinor int64 `json:"arppu_usd_minor"` } +type SelfGameOverviewQuery struct { + AppCode string + StartMS int64 + EndMS int64 + GameID string + CountryID int64 + RegionID int64 +} + +type SelfGameOverview struct { + AppCode string `json:"app_code"` + StartMS int64 `json:"start_ms"` + EndMS int64 `json:"end_ms"` + GameID string `json:"game_id"` + CountryID int64 `json:"country_id"` + RegionID int64 `json:"region_id"` + Events []SelfGameEventStat `json:"events"` + MatchTotals SelfGameMatchTotals `json:"match_totals"` + StakeBreakdown []SelfGameStakeStat `json:"stake_breakdown"` + PoolStats []SelfGamePoolStat `json:"pool_stats"` + ParticipantDistribution []SelfGameParticipantStat `json:"participant_distribution"` + UserRisk []SelfGameUserRiskStat `json:"user_risk"` + Funnel []SelfGameFunnelStep `json:"funnel"` + Conversion SelfGameConversion `json:"conversion"` +} + +type SelfGameEventStat struct { + GameID string `json:"game_id"` + EventName string `json:"event_name"` + EventCount int64 `json:"event_count"` + UserCount int64 `json:"user_count"` + SuccessCount int64 `json:"success_count"` + ErrorCount int64 `json:"error_count"` + DurationMSSum int64 `json:"duration_ms_sum"` + AvgDurationMS int64 `json:"avg_duration_ms"` +} + +type SelfGameMatchTotals struct { + CreatedMatches int64 `json:"created_matches"` + MatchedMatches int64 `json:"matched_matches"` + SettledMatches int64 `json:"settled_matches"` + CanceledMatches int64 `json:"canceled_matches"` + FailedMatches int64 `json:"failed_matches"` + HumanMatches int64 `json:"human_matches"` + RobotMatches int64 `json:"robot_matches"` + HumanParticipants int64 `json:"human_participants"` + RobotParticipants int64 `json:"robot_participants"` + WinnerCount int64 `json:"winner_count"` + LoserCount int64 `json:"loser_count"` + DrawCount int64 `json:"draw_count"` + UserStakeCoin int64 `json:"user_stake_coin"` + RobotStakeCoin int64 `json:"robot_stake_coin"` + PayoutCoin int64 `json:"payout_coin"` + RefundCoin int64 `json:"refund_coin"` + PlatformProfitCoin int64 `json:"platform_profit_coin"` + PoolDeltaCoin int64 `json:"pool_delta_coin"` + ForcedPlayerLoseMatches int64 `json:"forced_player_lose_matches"` + WaitMSSum int64 `json:"wait_ms_sum"` + DurationMSSum int64 `json:"duration_ms_sum"` + AvgWaitMS int64 `json:"avg_wait_ms"` + AvgDurationMS int64 `json:"avg_duration_ms"` + CancelRate float64 `json:"cancel_rate"` + FailRate float64 `json:"fail_rate"` + RobotMatchRate float64 `json:"robot_match_rate"` + PlatformProfitRate float64 `json:"platform_profit_rate"` +} + +type SelfGameStakeStat struct { + GameID string `json:"game_id"` + StakeCoin int64 `json:"stake_coin"` + CreatedMatches int64 `json:"created_matches"` + MatchedMatches int64 `json:"matched_matches"` + SettledMatches int64 `json:"settled_matches"` + CanceledMatches int64 `json:"canceled_matches"` + FailedMatches int64 `json:"failed_matches"` + HumanParticipants int64 `json:"human_participants"` + RobotParticipants int64 `json:"robot_participants"` + UserStakeCoin int64 `json:"user_stake_coin"` + RobotStakeCoin int64 `json:"robot_stake_coin"` + PayoutCoin int64 `json:"payout_coin"` + RefundCoin int64 `json:"refund_coin"` + PlatformProfitCoin int64 `json:"platform_profit_coin"` + PoolDeltaCoin int64 `json:"pool_delta_coin"` + PlatformProfitRate float64 `json:"platform_profit_rate"` +} + +type SelfGamePoolStat struct { + GameID string `json:"game_id"` + Direction string `json:"direction"` + Reason string `json:"reason"` + TransactionCount int64 `json:"transaction_count"` + InCoin int64 `json:"in_coin"` + OutCoin int64 `json:"out_coin"` + BalanceAfterCoin int64 `json:"balance_after_coin"` +} + +type SelfGameParticipantStat struct { + GameID string `json:"game_id"` + StakeCoin int64 `json:"stake_coin"` + ParticipantType string `json:"participant_type"` + Result string `json:"result"` + RPSGesture string `json:"rps_gesture"` + DicePoint int64 `json:"dice_point"` + ParticipantCount int64 `json:"participant_count"` + PayoutCoin int64 `json:"payout_coin"` + NetWinCoin int64 `json:"net_win_coin"` + WinRate float64 `json:"win_rate"` +} + +type SelfGameUserRiskStat struct { + GameID string `json:"game_id"` + UserID int64 `json:"user_id"` + MatchCount int64 `json:"match_count"` + WinCount int64 `json:"win_count"` + LoseCount int64 `json:"lose_count"` + DrawCount int64 `json:"draw_count"` + CancelCount int64 `json:"cancel_count"` + StakeCoin int64 `json:"stake_coin"` + PayoutCoin int64 `json:"payout_coin"` + NetWinCoin int64 `json:"net_win_coin"` + WinRate float64 `json:"win_rate"` + CancelRate float64 `json:"cancel_rate"` +} + +type SelfGameFunnelStep struct { + EventName string `json:"event_name"` + EventCount int64 `json:"event_count"` + UserCount int64 `json:"user_count"` +} + +type SelfGameConversion struct { + PageToMatchClickRate float64 `json:"page_to_match_click_rate"` + MatchClickToSuccessRate float64 `json:"match_click_to_success_rate"` + SuccessToSettlementRate float64 `json:"success_to_settlement_rate"` + SettlementToPlayAgainRate float64 `json:"settlement_to_play_again_rate"` + PageToSettlementRate float64 `json:"page_to_settlement_rate"` +} + func (r *Repository) QueryOverview(ctx context.Context, query OverviewQuery) (Overview, error) { app := appcode.Normalize(query.AppCode) if app == "" { @@ -233,6 +371,57 @@ func (r *Repository) QueryOverview(ctx context.Context, query OverviewQuery) (Ov return overview, nil } +func (r *Repository) QuerySelfGameOverview(ctx context.Context, query SelfGameOverviewQuery) (SelfGameOverview, error) { + startMS, endMS := normalizeRange(query.StartMS, query.EndMS) + app := appcode.Normalize(query.AppCode) + if app == "" { + app = "lalu" + } + startDay := statDay(startMS) + endDay := statDay(endMS - 1) + gameID := normalizeShortText(query.GameID) + overview := SelfGameOverview{ + AppCode: app, + StartMS: startMS, + EndMS: endMS, + GameID: gameID, + CountryID: query.CountryID, + RegionID: query.RegionID, + } + events, err := r.querySelfGameEvents(ctx, app, startDay, endDay, gameID, query.CountryID, query.RegionID) + if err != nil { + return SelfGameOverview{}, err + } + overview.Events = events + overview.Funnel, overview.Conversion = buildSelfGameFunnel(events) + totals, err := r.querySelfGameMatchTotals(ctx, app, startDay, endDay, gameID, query.CountryID, query.RegionID) + if err != nil { + return SelfGameOverview{}, err + } + overview.MatchTotals = totals + stakes, err := r.querySelfGameStakeBreakdown(ctx, app, startDay, endDay, gameID, query.CountryID, query.RegionID) + if err != nil { + return SelfGameOverview{}, err + } + overview.StakeBreakdown = stakes + pools, err := r.querySelfGamePools(ctx, app, startDay, endDay, gameID) + if err != nil { + return SelfGameOverview{}, err + } + overview.PoolStats = pools + participants, err := r.querySelfGameParticipantDistribution(ctx, app, startDay, endDay, gameID, query.CountryID, query.RegionID) + if err != nil { + return SelfGameOverview{}, err + } + overview.ParticipantDistribution = participants + risk, err := r.querySelfGameUserRisk(ctx, app, startDay, endDay, gameID, query.CountryID, query.RegionID) + if err != nil { + return SelfGameOverview{}, err + } + overview.UserRisk = risk + return overview, nil +} + func (r *Repository) queryAppDayOverviewTotals(ctx context.Context, app, startDay, endDay string, countryID int64, regionID int64) (Overview, error) { args := []any{app, startDay, endDay} filter := "" @@ -701,6 +890,232 @@ func sumLuckyGiftPools(pools []LuckyGiftPoolStat) (int64, int64) { return turnover, payout } +func (r *Repository) querySelfGameEvents(ctx context.Context, app, startDay, endDay, gameID string, countryID int64, regionID int64) ([]SelfGameEventStat, error) { + filter, args := selfGameFilter(app, startDay, endDay, gameID, countryID, regionID) + rows, err := r.db.QueryContext(ctx, ` + SELECT game_id, event_name, COALESCE(SUM(event_count),0), COALESCE(SUM(user_count),0), + COALESCE(SUM(success_count),0), COALESCE(SUM(error_count),0), COALESCE(SUM(duration_ms_sum),0) + FROM stat_self_game_h5_event_day + WHERE `+filter+` + GROUP BY game_id, event_name + ORDER BY game_id, event_name`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []SelfGameEventStat{} + for rows.Next() { + var item SelfGameEventStat + if err := rows.Scan(&item.GameID, &item.EventName, &item.EventCount, &item.UserCount, &item.SuccessCount, &item.ErrorCount, &item.DurationMSSum); err != nil { + return nil, err + } + item.AvgDurationMS = div(item.DurationMSSum, item.EventCount) + out = append(out, item) + } + return out, rows.Err() +} + +func (r *Repository) querySelfGameMatchTotals(ctx context.Context, app, startDay, endDay, gameID string, countryID int64, regionID int64) (SelfGameMatchTotals, error) { + filter, args := selfGameFilter(app, startDay, endDay, gameID, countryID, regionID) + row := r.db.QueryRowContext(ctx, ` + SELECT COALESCE(SUM(created_matches),0), COALESCE(SUM(matched_matches),0), COALESCE(SUM(settled_matches),0), + COALESCE(SUM(canceled_matches),0), COALESCE(SUM(failed_matches),0), COALESCE(SUM(human_matches),0), + COALESCE(SUM(robot_matches),0), COALESCE(SUM(human_participants),0), COALESCE(SUM(robot_participants),0), + COALESCE(SUM(winner_count),0), COALESCE(SUM(loser_count),0), COALESCE(SUM(draw_count),0), + COALESCE(SUM(user_stake_coin),0), COALESCE(SUM(robot_stake_coin),0), COALESCE(SUM(payout_coin),0), + COALESCE(SUM(refund_coin),0), COALESCE(SUM(platform_profit_coin),0), COALESCE(SUM(pool_delta_coin),0), + COALESCE(SUM(forced_player_lose_matches),0), COALESCE(SUM(wait_ms_sum),0), COALESCE(SUM(duration_ms_sum),0) + FROM stat_self_game_match_day + WHERE `+filter, args...) + var totals SelfGameMatchTotals + if err := row.Scan(&totals.CreatedMatches, &totals.MatchedMatches, &totals.SettledMatches, &totals.CanceledMatches, &totals.FailedMatches, &totals.HumanMatches, &totals.RobotMatches, &totals.HumanParticipants, &totals.RobotParticipants, &totals.WinnerCount, &totals.LoserCount, &totals.DrawCount, &totals.UserStakeCoin, &totals.RobotStakeCoin, &totals.PayoutCoin, &totals.RefundCoin, &totals.PlatformProfitCoin, &totals.PoolDeltaCoin, &totals.ForcedPlayerLoseMatches, &totals.WaitMSSum, &totals.DurationMSSum); err != nil { + return SelfGameMatchTotals{}, err + } + applySelfGameMatchDerived(&totals) + return totals, nil +} + +func (r *Repository) querySelfGameStakeBreakdown(ctx context.Context, app, startDay, endDay, gameID string, countryID int64, regionID int64) ([]SelfGameStakeStat, error) { + filter, args := selfGameFilter(app, startDay, endDay, gameID, countryID, regionID) + rows, err := r.db.QueryContext(ctx, ` + SELECT game_id, stake_coin, COALESCE(SUM(created_matches),0), COALESCE(SUM(matched_matches),0), + COALESCE(SUM(settled_matches),0), COALESCE(SUM(canceled_matches),0), COALESCE(SUM(failed_matches),0), + COALESCE(SUM(human_participants),0), COALESCE(SUM(robot_participants),0), COALESCE(SUM(user_stake_coin),0), + COALESCE(SUM(robot_stake_coin),0), COALESCE(SUM(payout_coin),0), COALESCE(SUM(refund_coin),0), + COALESCE(SUM(platform_profit_coin),0), COALESCE(SUM(pool_delta_coin),0) + FROM stat_self_game_match_day + WHERE `+filter+` + GROUP BY game_id, stake_coin + ORDER BY game_id, stake_coin`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []SelfGameStakeStat{} + for rows.Next() { + var item SelfGameStakeStat + if err := rows.Scan(&item.GameID, &item.StakeCoin, &item.CreatedMatches, &item.MatchedMatches, &item.SettledMatches, &item.CanceledMatches, &item.FailedMatches, &item.HumanParticipants, &item.RobotParticipants, &item.UserStakeCoin, &item.RobotStakeCoin, &item.PayoutCoin, &item.RefundCoin, &item.PlatformProfitCoin, &item.PoolDeltaCoin); err != nil { + return nil, err + } + item.PlatformProfitRate = ratio(item.PlatformProfitCoin, item.UserStakeCoin+item.RobotStakeCoin) + out = append(out, item) + } + return out, rows.Err() +} + +func (r *Repository) querySelfGamePools(ctx context.Context, app, startDay, endDay, gameID string) ([]SelfGamePoolStat, error) { + filter := "app_code = ? AND stat_day BETWEEN ? AND ?" + args := []any{app, startDay, endDay} + if gameID != "" { + filter += " AND game_id = ?" + args = append(args, gameID) + } + rows, err := r.db.QueryContext(ctx, ` + SELECT game_id, direction, reason, COALESCE(SUM(transaction_count),0), + COALESCE(SUM(in_coin),0), COALESCE(SUM(out_coin),0), COALESCE(MAX(balance_after_coin),0) + FROM stat_self_game_pool_day + WHERE `+filter+` + GROUP BY game_id, direction, reason + ORDER BY game_id, direction, reason`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []SelfGamePoolStat{} + for rows.Next() { + var item SelfGamePoolStat + if err := rows.Scan(&item.GameID, &item.Direction, &item.Reason, &item.TransactionCount, &item.InCoin, &item.OutCoin, &item.BalanceAfterCoin); err != nil { + return nil, err + } + out = append(out, item) + } + return out, rows.Err() +} + +func (r *Repository) querySelfGameParticipantDistribution(ctx context.Context, app, startDay, endDay, gameID string, countryID int64, regionID int64) ([]SelfGameParticipantStat, error) { + filter, args := selfGameFilter(app, startDay, endDay, gameID, countryID, regionID) + rows, err := r.db.QueryContext(ctx, ` + SELECT game_id, stake_coin, participant_type, result, rps_gesture, dice_point, + COALESCE(SUM(participant_count),0), COALESCE(SUM(payout_coin),0), COALESCE(SUM(net_win_coin),0) + FROM stat_self_game_participant_day + WHERE `+filter+` + GROUP BY game_id, stake_coin, participant_type, result, rps_gesture, dice_point + ORDER BY game_id, stake_coin, participant_type, result, rps_gesture, dice_point`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SelfGameParticipantStat{} + totals := map[string]int64{} + for rows.Next() { + var item SelfGameParticipantStat + if err := rows.Scan(&item.GameID, &item.StakeCoin, &item.ParticipantType, &item.Result, &item.RPSGesture, &item.DicePoint, &item.ParticipantCount, &item.PayoutCoin, &item.NetWinCoin); err != nil { + return nil, err + } + key := item.GameID + "\x00" + item.ParticipantType + "\x00" + item.RPSGesture + if item.DicePoint > 0 { + key = item.GameID + "\x00" + item.ParticipantType + "\x00" + string(rune(item.DicePoint)) + } + totals[key] += item.ParticipantCount + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, err + } + for index := range items { + key := items[index].GameID + "\x00" + items[index].ParticipantType + "\x00" + items[index].RPSGesture + if items[index].DicePoint > 0 { + key = items[index].GameID + "\x00" + items[index].ParticipantType + "\x00" + string(rune(items[index].DicePoint)) + } + if items[index].Result == "win" { + items[index].WinRate = ratio(items[index].ParticipantCount, totals[key]) + } + } + return items, nil +} + +func (r *Repository) querySelfGameUserRisk(ctx context.Context, app, startDay, endDay, gameID string, countryID int64, regionID int64) ([]SelfGameUserRiskStat, error) { + filter, args := selfGameFilter(app, startDay, endDay, gameID, countryID, regionID) + rows, err := r.db.QueryContext(ctx, ` + SELECT game_id, user_id, COALESCE(SUM(match_count),0), COALESCE(SUM(win_count),0), + COALESCE(SUM(lose_count),0), COALESCE(SUM(draw_count),0), COALESCE(SUM(cancel_count),0), + COALESCE(SUM(stake_coin),0), COALESCE(SUM(payout_coin),0), COALESCE(SUM(net_win_coin),0) + FROM stat_self_game_user_day + WHERE `+filter+` + GROUP BY game_id, user_id + ORDER BY SUM(match_count) DESC, SUM(net_win_coin) DESC, SUM(cancel_count) DESC + LIMIT 100`, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []SelfGameUserRiskStat{} + for rows.Next() { + var item SelfGameUserRiskStat + if err := rows.Scan(&item.GameID, &item.UserID, &item.MatchCount, &item.WinCount, &item.LoseCount, &item.DrawCount, &item.CancelCount, &item.StakeCoin, &item.PayoutCoin, &item.NetWinCoin); err != nil { + return nil, err + } + item.WinRate = ratio(item.WinCount, item.MatchCount) + item.CancelRate = ratio(item.CancelCount, item.MatchCount+item.CancelCount) + out = append(out, item) + } + return out, rows.Err() +} + +func selfGameFilter(app, startDay, endDay, gameID string, countryID int64, regionID int64) (string, []any) { + filter := "app_code = ? AND stat_day BETWEEN ? AND ?" + args := []any{app, startDay, endDay} + if gameID != "" { + filter += " AND game_id = ?" + args = append(args, gameID) + } + if countryID > 0 { + filter += " AND country_id = ?" + args = append(args, countryID) + } + if regionID > 0 { + filter += " AND region_id = ?" + args = append(args, regionID) + } + return filter, args +} + +func applySelfGameMatchDerived(totals *SelfGameMatchTotals) { + if totals == nil { + return + } + totals.AvgWaitMS = div(totals.WaitMSSum, totals.MatchedMatches+totals.SettledMatches+totals.CanceledMatches+totals.FailedMatches) + totals.AvgDurationMS = div(totals.DurationMSSum, totals.SettledMatches+totals.CanceledMatches+totals.FailedMatches) + totals.CancelRate = ratio(totals.CanceledMatches, totals.CreatedMatches) + totals.FailRate = ratio(totals.FailedMatches, totals.CreatedMatches) + totals.RobotMatchRate = ratio(totals.RobotMatches, totals.SettledMatches) + totals.PlatformProfitRate = ratio(totals.PlatformProfitCoin, totals.UserStakeCoin+totals.RobotStakeCoin) +} + +func buildSelfGameFunnel(events []SelfGameEventStat) ([]SelfGameFunnelStep, SelfGameConversion) { + order := []string{"page_open", "match_click", "match_success", "settlement_show", "play_again"} + byEvent := map[string]SelfGameFunnelStep{} + for _, event := range events { + step := byEvent[event.EventName] + step.EventName = event.EventName + step.EventCount += event.EventCount + step.UserCount += event.UserCount + byEvent[event.EventName] = step + } + funnel := make([]SelfGameFunnelStep, 0, len(order)) + for _, name := range order { + funnel = append(funnel, byEvent[name]) + funnel[len(funnel)-1].EventName = name + } + return funnel, SelfGameConversion{ + PageToMatchClickRate: ratio(byEvent["match_click"].UserCount, byEvent["page_open"].UserCount), + MatchClickToSuccessRate: ratio(byEvent["match_success"].UserCount, byEvent["match_click"].UserCount), + SuccessToSettlementRate: ratio(byEvent["settlement_show"].UserCount, byEvent["match_success"].UserCount), + SettlementToPlayAgainRate: ratio(byEvent["play_again"].UserCount, byEvent["settlement_show"].UserCount), + PageToSettlementRate: ratio(byEvent["settlement_show"].UserCount, byEvent["page_open"].UserCount), + } +} + func normalizeRange(startMS, endMS int64) (int64, int64) { if startMS <= 0 { now := time.Now().UTC() diff --git a/services/statistics-service/internal/storage/mysql/repository.go b/services/statistics-service/internal/storage/mysql/repository.go index 721b758b..8db79657 100644 --- a/services/statistics-service/internal/storage/mysql/repository.go +++ b/services/statistics-service/internal/storage/mysql/repository.go @@ -18,6 +18,7 @@ const ( SourceWallet = "wallet" SourceRoom = "room" SourceGame = "game" + SourceH5 = "h5" ) // Repository owns the statistics read model. Online queries must use aggregate @@ -171,6 +172,74 @@ func (r *Repository) Migrate(ctx context.Context) error { PRIMARY KEY (app_code, stat_day, country_id, platform_code, game_id, user_id), KEY idx_stat_game_player_region (app_code, stat_day, region_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS stat_self_game_h5_event_day ( + app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, country_id BIGINT NOT NULL DEFAULT 0, + region_id BIGINT NOT NULL DEFAULT 0, game_id VARCHAR(96) NOT NULL, event_name VARCHAR(64) NOT NULL, + language VARCHAR(16) NOT NULL DEFAULT '', client_version VARCHAR(64) NOT NULL DEFAULT '', + h5_version VARCHAR(64) NOT NULL DEFAULT '', entry_source VARCHAR(64) NOT NULL DEFAULT '', + event_count BIGINT NOT NULL DEFAULT 0, user_count BIGINT NOT NULL DEFAULT 0, + success_count BIGINT NOT NULL DEFAULT 0, error_count BIGINT NOT NULL DEFAULT 0, + duration_ms_sum BIGINT NOT NULL DEFAULT 0, updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, country_id, region_id, game_id, event_name, language, client_version, h5_version, entry_source), + KEY idx_stat_self_game_h5_day (app_code, stat_day, game_id, event_name), + KEY idx_stat_self_game_h5_region (app_code, stat_day, region_id, game_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS stat_self_game_h5_event_users ( + app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, game_id VARCHAR(96) NOT NULL, + event_name VARCHAR(64) NOT NULL, user_id BIGINT NOT NULL, first_seen_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, game_id, event_name, user_id), + KEY idx_stat_self_game_h5_users_day (app_code, stat_day, game_id, event_name) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS stat_self_game_match_day ( + app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, country_id BIGINT NOT NULL DEFAULT 0, + region_id BIGINT NOT NULL DEFAULT 0, game_id VARCHAR(96) NOT NULL, stake_coin BIGINT NOT NULL DEFAULT 0, + created_matches BIGINT NOT NULL DEFAULT 0, matched_matches BIGINT NOT NULL DEFAULT 0, + settled_matches BIGINT NOT NULL DEFAULT 0, canceled_matches BIGINT NOT NULL DEFAULT 0, + failed_matches BIGINT NOT NULL DEFAULT 0, human_matches BIGINT NOT NULL DEFAULT 0, + robot_matches BIGINT NOT NULL DEFAULT 0, human_participants BIGINT NOT NULL DEFAULT 0, + robot_participants BIGINT NOT NULL DEFAULT 0, winner_count BIGINT NOT NULL DEFAULT 0, + loser_count BIGINT NOT NULL DEFAULT 0, draw_count BIGINT NOT NULL DEFAULT 0, + user_stake_coin BIGINT NOT NULL DEFAULT 0, robot_stake_coin BIGINT NOT NULL DEFAULT 0, + payout_coin BIGINT NOT NULL DEFAULT 0, refund_coin BIGINT NOT NULL DEFAULT 0, + platform_profit_coin BIGINT NOT NULL DEFAULT 0, pool_delta_coin BIGINT NOT NULL DEFAULT 0, + forced_player_lose_matches BIGINT NOT NULL DEFAULT 0, wait_ms_sum BIGINT NOT NULL DEFAULT 0, + duration_ms_sum BIGINT NOT NULL DEFAULT 0, updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, country_id, region_id, game_id, stake_coin), + KEY idx_stat_self_game_match_day (app_code, stat_day, game_id), + KEY idx_stat_self_game_match_region (app_code, stat_day, region_id, game_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS stat_self_game_pool_day ( + app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, game_id VARCHAR(96) NOT NULL, + direction VARCHAR(16) NOT NULL DEFAULT '', reason VARCHAR(64) NOT NULL DEFAULT '', + transaction_count BIGINT NOT NULL DEFAULT 0, in_coin BIGINT NOT NULL DEFAULT 0, + out_coin BIGINT NOT NULL DEFAULT 0, balance_after_coin BIGINT NOT NULL DEFAULT 0, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, game_id, direction, reason), + KEY idx_stat_self_game_pool_day (app_code, stat_day, game_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS stat_self_game_participant_day ( + app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, country_id BIGINT NOT NULL DEFAULT 0, + region_id BIGINT NOT NULL DEFAULT 0, game_id VARCHAR(96) NOT NULL, stake_coin BIGINT NOT NULL DEFAULT 0, + participant_type VARCHAR(32) NOT NULL DEFAULT 'user', result VARCHAR(32) NOT NULL DEFAULT '', + rps_gesture VARCHAR(32) NOT NULL DEFAULT '', dice_point INT NOT NULL DEFAULT 0, + participant_count BIGINT NOT NULL DEFAULT 0, payout_coin BIGINT NOT NULL DEFAULT 0, + net_win_coin BIGINT NOT NULL DEFAULT 0, updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, country_id, region_id, game_id, stake_coin, participant_type, result, rps_gesture, dice_point), + KEY idx_stat_self_game_participant_day (app_code, stat_day, game_id), + KEY idx_stat_self_game_participant_region (app_code, stat_day, region_id, game_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, + `CREATE TABLE IF NOT EXISTS stat_self_game_user_day ( + app_code VARCHAR(32) NOT NULL, stat_day DATE NOT NULL, country_id BIGINT NOT NULL DEFAULT 0, + region_id BIGINT NOT NULL DEFAULT 0, game_id VARCHAR(96) NOT NULL, user_id BIGINT NOT NULL, + match_count BIGINT NOT NULL DEFAULT 0, win_count BIGINT NOT NULL DEFAULT 0, + lose_count BIGINT NOT NULL DEFAULT 0, draw_count BIGINT NOT NULL DEFAULT 0, + cancel_count BIGINT NOT NULL DEFAULT 0, stake_coin BIGINT NOT NULL DEFAULT 0, + payout_coin BIGINT NOT NULL DEFAULT 0, net_win_coin BIGINT NOT NULL DEFAULT 0, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (app_code, stat_day, game_id, user_id), + KEY idx_stat_self_game_user_win (app_code, stat_day, game_id, net_win_coin), + KEY idx_stat_self_game_user_region (app_code, stat_day, region_id, game_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, } for _, statement := range statements { if _, err := r.db.ExecContext(ctx, statement); err != nil { @@ -340,6 +409,80 @@ type GameOrderEvent struct { OccurredAtMS int64 } +type SelfGameH5Event struct { + AppCode string + EventID string + EventName string + GameID string + Page string + SessionID string + MatchID string + UserID int64 + CountryID int64 + RegionID int64 + Language string + ClientVersion string + H5Version string + EntrySource string + DurationMS int64 + Success bool + ErrorCode string + OccurredAtMS int64 +} + +type SelfGameMatchParticipantEvent struct { + UserID int64 + ParticipantType string + StakeCoin int64 + DicePoints []int64 + RPSGesture string + Result string + PayoutCoin int64 + NetWinCoin int64 +} + +type SelfGameMatchEvent struct { + AppCode string + EventID string + EventType string + MatchID string + GameID string + PlatformCode string + RoomID string + RegionID int64 + StakeCoin int64 + Status string + Result string + MatchMode string + ForcedResult string + UserParticipants int64 + RobotParticipants int64 + WinnerCount int64 + LoserCount int64 + DrawCount int64 + UserStakeCoin int64 + RobotStakeCoin int64 + PayoutCoin int64 + RefundCoin int64 + PlatformProfitCoin int64 + PoolDeltaCoin int64 + WaitMS int64 + DurationMS int64 + Participants []SelfGameMatchParticipantEvent + OccurredAtMS int64 +} + +type SelfGamePoolAdjustmentEvent struct { + AppCode string + EventID string + GameID string + Direction string + Reason string + AmountCoin int64 + BalanceAfter int64 + OccurredAtMS int64 +} + func (r *Repository) ConsumeUserRegistered(ctx context.Context, event UserRegisteredEvent) error { return r.withEvent(ctx, SourceUser, event.EventID, "UserRegistered", func(tx *sql.Tx, nowMS int64) error { day := statDay(event.OccurredAtMS) @@ -558,6 +701,274 @@ func (r *Repository) ConsumeGameOrder(ctx context.Context, event GameOrderEvent) }) } +func (r *Repository) ConsumeSelfGameH5Event(ctx context.Context, event SelfGameH5Event) error { + eventName := normalizeShortText(event.EventName) + if eventName == "" { + eventName = "unknown" + } + return r.withEvent(ctx, SourceH5, event.EventID, eventName, func(tx *sql.Tx, nowMS int64) error { + day := statDay(event.OccurredAtMS) + countryID, regionID := normalizeDimension(event.CountryID, event.RegionID) + gameID := normalizeShortText(event.GameID) + if gameID == "" { + gameID = "unknown" + } + language := normalizeShortText(event.Language) + clientVersion := normalizeShortText(event.ClientVersion) + h5Version := normalizeShortText(event.H5Version) + entrySource := normalizeShortText(event.EntrySource) + if err := ensureSelfGameH5EventDay(ctx, tx, event.AppCode, day, countryID, regionID, gameID, eventName, language, clientVersion, h5Version, entrySource, nowMS); err != nil { + return err + } + userDelta := int64(0) + if event.UserID > 0 { + affected, err := insertUnique(ctx, tx, ` + INSERT IGNORE INTO stat_self_game_h5_event_users (app_code, stat_day, game_id, event_name, user_id, first_seen_at_ms) + VALUES (?, ?, ?, ?, ?, ?) + `, appcode.Normalize(event.AppCode), day, gameID, eventName, event.UserID, event.OccurredAtMS) + if err != nil { + return err + } + userDelta = affected + } + successDelta, errorDelta := int64(0), int64(0) + if event.Success { + successDelta = 1 + } + if normalizeShortText(event.ErrorCode) != "" { + errorDelta = 1 + } + // H5 事件按 event_id 做幂等;PV 用事件次数,UV 用同日同玩法同事件的用户去重,接口耗时只累加客户端实际传入的 duration。 + _, err := tx.ExecContext(ctx, ` + UPDATE stat_self_game_h5_event_day + SET event_count = event_count + 1, + user_count = user_count + ?, + success_count = success_count + ?, + error_count = error_count + ?, + duration_ms_sum = duration_ms_sum + ?, + updated_at_ms = ? + WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ? + AND game_id = ? AND event_name = ? AND language = ? AND client_version = ? + AND h5_version = ? AND entry_source = ? + `, userDelta, successDelta, errorDelta, normalizeID(event.DurationMS), nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID, gameID, eventName, language, clientVersion, h5Version, entrySource) + return err + }) +} + +func (r *Repository) ConsumeSelfGameMatch(ctx context.Context, event SelfGameMatchEvent) error { + return r.withEvent(ctx, SourceGame, event.EventID, event.EventType, func(tx *sql.Tx, nowMS int64) error { + day := statDay(event.OccurredAtMS) + countryID, regionID := normalizeDimension(0, event.RegionID) + gameID := normalizeShortText(event.GameID) + if gameID == "" { + gameID = "unknown" + } + stakeCoin := normalizeID(event.StakeCoin) + if err := ensureSelfGameMatchDay(ctx, tx, event.AppCode, day, countryID, regionID, gameID, stakeCoin, nowMS); err != nil { + return err + } + created, matched, settled, canceled, failed := selfGameMatchCounters(event.EventType) + humanMatch, robotMatch := int64(0), int64(0) + humanParticipants, robotParticipants := int64(0), int64(0) + userStake, robotStake, payout, refund, platformProfit, poolDelta := int64(0), int64(0), int64(0), int64(0), int64(0), int64(0) + winners, losers, draws := int64(0), int64(0), int64(0) + forcedLose, waitMS, durationMS := int64(0), int64(0), int64(0) + if settled > 0 { + if event.UserParticipants > 0 { + humanMatch = 1 + } + if event.RobotParticipants > 0 { + robotMatch = 1 + } + humanParticipants = normalizeID(event.UserParticipants) + robotParticipants = normalizeID(event.RobotParticipants) + winners = normalizeID(event.WinnerCount) + losers = normalizeID(event.LoserCount) + draws = normalizeID(event.DrawCount) + userStake = normalizeID(event.UserStakeCoin) + robotStake = normalizeID(event.RobotStakeCoin) + payout = normalizeID(event.PayoutCoin) + refund = normalizeID(event.RefundCoin) + platformProfit = event.PlatformProfitCoin + poolDelta = event.PoolDeltaCoin + waitMS = normalizeID(event.WaitMS) + durationMS = normalizeID(event.DurationMS) + if strings.EqualFold(strings.TrimSpace(event.ForcedResult), "player_lose") { + forcedLose = 1 + } + } + if canceled > 0 || failed > 0 || matched > 0 { + waitMS = normalizeID(event.WaitMS) + durationMS = normalizeID(event.DurationMS) + } + // 对局状态事件只按 match 级事实累加一次;明细用户维度只在结算/取消时落表,避免创建和 ready 阶段重复计算下注与输赢。 + if _, err := tx.ExecContext(ctx, ` + UPDATE stat_self_game_match_day + SET created_matches = created_matches + ?, + matched_matches = matched_matches + ?, + settled_matches = settled_matches + ?, + canceled_matches = canceled_matches + ?, + failed_matches = failed_matches + ?, + human_matches = human_matches + ?, + robot_matches = robot_matches + ?, + human_participants = human_participants + ?, + robot_participants = robot_participants + ?, + winner_count = winner_count + ?, + loser_count = loser_count + ?, + draw_count = draw_count + ?, + user_stake_coin = user_stake_coin + ?, + robot_stake_coin = robot_stake_coin + ?, + payout_coin = payout_coin + ?, + refund_coin = refund_coin + ?, + platform_profit_coin = platform_profit_coin + ?, + pool_delta_coin = pool_delta_coin + ?, + forced_player_lose_matches = forced_player_lose_matches + ?, + wait_ms_sum = wait_ms_sum + ?, + duration_ms_sum = duration_ms_sum + ?, + updated_at_ms = ? + WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ? + AND game_id = ? AND stake_coin = ? + `, created, matched, settled, canceled, failed, humanMatch, robotMatch, humanParticipants, robotParticipants, winners, losers, draws, userStake, robotStake, payout, refund, platformProfit, poolDelta, forcedLose, waitMS, durationMS, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID, gameID, stakeCoin); err != nil { + return err + } + if settled > 0 { + for _, participant := range event.Participants { + if err := consumeSelfGameSettledParticipant(ctx, tx, event, participant, day, countryID, regionID, gameID, stakeCoin, nowMS); err != nil { + return err + } + } + } + if canceled > 0 { + for _, participant := range event.Participants { + if participant.UserID <= 0 || !strings.EqualFold(strings.TrimSpace(participant.ParticipantType), "user") { + continue + } + if err := ensureSelfGameUserDay(ctx, tx, event.AppCode, day, countryID, regionID, gameID, participant.UserID, nowMS); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, ` + UPDATE stat_self_game_user_day + SET cancel_count = cancel_count + 1, updated_at_ms = ? + WHERE app_code = ? AND stat_day = ? AND game_id = ? AND user_id = ? + `, nowMS, appcode.Normalize(event.AppCode), day, gameID, participant.UserID); err != nil { + return err + } + } + } + return nil + }) +} + +func (r *Repository) ConsumeSelfGamePoolAdjustment(ctx context.Context, event SelfGamePoolAdjustmentEvent) error { + return r.withEvent(ctx, SourceGame, event.EventID, "SelfGamePoolAdjusted", func(tx *sql.Tx, nowMS int64) error { + day := statDay(event.OccurredAtMS) + gameID := normalizeShortText(event.GameID) + if gameID == "" { + gameID = "unknown" + } + direction := normalizeShortText(event.Direction) + reason := normalizeShortText(event.Reason) + if err := ensureSelfGamePoolDay(ctx, tx, event.AppCode, day, gameID, direction, reason, nowMS); err != nil { + return err + } + inCoin, outCoin := int64(0), int64(0) + switch direction { + case "in": + inCoin = normalizeID(event.AmountCoin) + case "out": + outCoin = normalizeID(event.AmountCoin) + } + _, err := tx.ExecContext(ctx, ` + UPDATE stat_self_game_pool_day + SET transaction_count = transaction_count + 1, + in_coin = in_coin + ?, + out_coin = out_coin + ?, + balance_after_coin = ?, + updated_at_ms = ? + WHERE app_code = ? AND stat_day = ? AND game_id = ? AND direction = ? AND reason = ? + `, inCoin, outCoin, event.BalanceAfter, nowMS, appcode.Normalize(event.AppCode), day, gameID, direction, reason) + return err + }) +} + +func consumeSelfGameSettledParticipant(ctx context.Context, tx *sql.Tx, event SelfGameMatchEvent, participant SelfGameMatchParticipantEvent, day string, countryID int64, regionID int64, gameID string, stakeCoin int64, nowMS int64) error { + participantType := normalizeShortText(participant.ParticipantType) + if participantType == "" { + participantType = "user" + } + result := normalizeShortText(participant.Result) + gesture := normalizeShortText(participant.RPSGesture) + points := participant.DicePoints + if len(points) == 0 { + points = []int64{0} + } + for _, point := range points { + dicePoint := int64(0) + if point > 0 { + dicePoint = point + } + if err := ensureSelfGameParticipantDay(ctx, tx, event.AppCode, day, countryID, regionID, gameID, stakeCoin, participantType, result, gesture, dicePoint, nowMS); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, ` + UPDATE stat_self_game_participant_day + SET participant_count = participant_count + 1, + payout_coin = payout_coin + ?, + net_win_coin = net_win_coin + ?, + updated_at_ms = ? + WHERE app_code = ? AND stat_day = ? AND country_id = ? AND region_id = ? + AND game_id = ? AND stake_coin = ? AND participant_type = ? AND result = ? + AND rps_gesture = ? AND dice_point = ? + `, normalizeID(participant.PayoutCoin), participant.NetWinCoin, nowMS, appcode.Normalize(event.AppCode), day, countryID, regionID, gameID, stakeCoin, participantType, result, gesture, dicePoint); err != nil { + return err + } + } + if participant.UserID <= 0 || participantType != "user" { + return nil + } + if err := ensureSelfGameUserDay(ctx, tx, event.AppCode, day, countryID, regionID, gameID, participant.UserID, nowMS); err != nil { + return err + } + win, lose, draw := int64(0), int64(0), int64(0) + switch result { + case "win": + win = 1 + case "lose": + lose = 1 + case "draw": + draw = 1 + } + _, err := tx.ExecContext(ctx, ` + UPDATE stat_self_game_user_day + SET match_count = match_count + 1, + win_count = win_count + ?, + lose_count = lose_count + ?, + draw_count = draw_count + ?, + stake_coin = stake_coin + ?, + payout_coin = payout_coin + ?, + net_win_coin = net_win_coin + ?, + updated_at_ms = ? + WHERE app_code = ? AND stat_day = ? AND game_id = ? AND user_id = ? + `, win, lose, draw, normalizeID(participant.StakeCoin), normalizeID(participant.PayoutCoin), participant.NetWinCoin, nowMS, appcode.Normalize(event.AppCode), day, gameID, participant.UserID) + return err +} + +func selfGameMatchCounters(eventType string) (created, matched, settled, canceled, failed int64) { + switch strings.TrimSpace(eventType) { + case "SelfGameMatchCreated": + created = 1 + case "SelfGameMatchReady": + matched = 1 + case "SelfGameMatchSettled": + settled = 1 + case "SelfGameMatchCanceled": + canceled = 1 + case "SelfGameMatchFailed": + failed = 1 + } + return created, matched, settled, canceled, failed +} + func (r *Repository) withEvent(ctx context.Context, source string, eventID string, eventType string, apply func(tx *sql.Tx, nowMS int64) error) error { var lastErr error for attempt := 0; attempt < 5; attempt++ { @@ -685,6 +1096,56 @@ func ensureLuckyGiftPoolDay(ctx context.Context, tx *sql.Tx, app string, day str return err } +func ensureSelfGameH5EventDay(ctx context.Context, tx *sql.Tx, app string, day string, countryID int64, regionID int64, gameID string, eventName string, language string, clientVersion string, h5Version string, entrySource string, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO stat_self_game_h5_event_day (app_code, stat_day, country_id, region_id, game_id, event_name, language, client_version, h5_version, entry_source, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + updated_at_ms = VALUES(updated_at_ms) + `, appcode.Normalize(app), day, countryID, regionID, gameID, eventName, language, clientVersion, h5Version, entrySource, nowMS) + return err +} + +func ensureSelfGameMatchDay(ctx context.Context, tx *sql.Tx, app string, day string, countryID int64, regionID int64, gameID string, stakeCoin int64, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO stat_self_game_match_day (app_code, stat_day, country_id, region_id, game_id, stake_coin, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + updated_at_ms = VALUES(updated_at_ms) + `, appcode.Normalize(app), day, countryID, regionID, gameID, stakeCoin, nowMS) + return err +} + +func ensureSelfGamePoolDay(ctx context.Context, tx *sql.Tx, app string, day string, gameID string, direction string, reason string, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO stat_self_game_pool_day (app_code, stat_day, game_id, direction, reason, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + updated_at_ms = VALUES(updated_at_ms) + `, appcode.Normalize(app), day, gameID, direction, reason, nowMS) + return err +} + +func ensureSelfGameParticipantDay(ctx context.Context, tx *sql.Tx, app string, day string, countryID int64, regionID int64, gameID string, stakeCoin int64, participantType string, result string, gesture string, dicePoint int64, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO stat_self_game_participant_day (app_code, stat_day, country_id, region_id, game_id, stake_coin, participant_type, result, rps_gesture, dice_point, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + updated_at_ms = VALUES(updated_at_ms) + `, appcode.Normalize(app), day, countryID, regionID, gameID, stakeCoin, participantType, result, gesture, dicePoint, nowMS) + return err +} + +func ensureSelfGameUserDay(ctx context.Context, tx *sql.Tx, app string, day string, countryID int64, regionID int64, gameID string, userID int64, nowMS int64) error { + _, err := tx.ExecContext(ctx, ` + INSERT INTO stat_self_game_user_day (app_code, stat_day, country_id, region_id, game_id, user_id, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + updated_at_ms = VALUES(updated_at_ms) + `, appcode.Normalize(app), day, countryID, regionID, gameID, userID, nowMS) + return err +} + func insertUnique(ctx context.Context, tx *sql.Tx, query string, args ...any) (int64, error) { result, err := tx.ExecContext(ctx, query, args...) if err != nil { @@ -708,6 +1169,10 @@ func normalizeID(id int64) int64 { return id } +func normalizeShortText(value string) string { + return strings.TrimSpace(value) +} + func normalizeDimension(countryID int64, regionID int64) (int64, int64) { return normalizeID(countryID), normalizeID(regionID) } From 8628de3dbd4d98b3f7918d3123825d0fc491fd6b Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 19:31:39 +0800 Subject: [PATCH 27/28] fix game room rps im config --- .../game-service/configs/config.docker.yaml | 10 ++ .../configs/config.tencent.example.yaml | 10 ++ services/game-service/configs/config.yaml | 10 ++ services/game-service/internal/app/app.go | 23 +++-- .../game-service/internal/config/config.go | 98 +++++++++++++++++-- .../internal/config/config_test.go | 41 ++++++++ .../internal/service/roomrps/service.go | 41 +------- 7 files changed, 178 insertions(+), 55 deletions(-) diff --git a/services/game-service/configs/config.docker.yaml b/services/game-service/configs/config.docker.yaml index a43f577d..0f6689fb 100644 --- a/services/game-service/configs/config.docker.yaml +++ b/services/game-service/configs/config.docker.yaml @@ -8,6 +8,16 @@ wallet_service_addr: "wallet-service:13004" user_service_addr: "user-service:13005" activity_service_addr: "activity-service:13006" launch_session_ttl: "15m" +tencent_im: + # Docker/testbox 联调房内猜拳 PK 房间群消息;必须和 gateway/room 使用同一个 IM 应用。 + enabled: true + sdk_app_id: 20036101 + secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c" + admin_identifier: "900100100" + admin_user_sig_ttl: "24h" + endpoint: "adminapisgp.im.qcloud.com" + group_type: "ChatRoom" + request_timeout: "5s" rocketmq: enabled: true name_servers: ["rocketmq-namesrv:9876"] diff --git a/services/game-service/configs/config.tencent.example.yaml b/services/game-service/configs/config.tencent.example.yaml index 3e00bcb8..2c4093d7 100644 --- a/services/game-service/configs/config.tencent.example.yaml +++ b/services/game-service/configs/config.tencent.example.yaml @@ -8,6 +8,16 @@ wallet_service_addr: "wallet-service:13004" user_service_addr: "user-service:13005" activity_service_addr: "10.2.1.16:13006" launch_session_ttl: "15m" +tencent_im: + # 房内猜拳发起、应战、揭晓和结算事件都走腾讯 IM 房间群自定义消息。 + enabled: true + sdk_app_id: 1400000000 + secret_key: "TENCENT_IM_SECRET_KEY" + admin_identifier: "TENCENT_IM_ADMIN_IDENTIFIER" + admin_user_sig_ttl: "24h" + endpoint: "adminapisgp.im.qcloud.com" + group_type: "ChatRoom" + request_timeout: "5s" rocketmq: enabled: true name_servers: [] diff --git a/services/game-service/configs/config.yaml b/services/game-service/configs/config.yaml index 7cf66f6a..c99e1a7c 100644 --- a/services/game-service/configs/config.yaml +++ b/services/game-service/configs/config.yaml @@ -8,6 +8,16 @@ wallet_service_addr: "127.0.0.1:13004" user_service_addr: "127.0.0.1:13005" activity_service_addr: "127.0.0.1:13006" launch_session_ttl: "15m" +tencent_im: + # 本地联调房内猜拳 PK 房间群消息;必须和 gateway/room 使用同一个 IM 应用。 + enabled: true + sdk_app_id: 20036101 + secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c" + admin_identifier: "900100100" + admin_user_sig_ttl: "24h" + endpoint: "adminapisgp.im.qcloud.com" + group_type: "ChatRoom" + request_timeout: "5s" rocketmq: enabled: true name_servers: ["127.0.0.1:19876"] diff --git a/services/game-service/internal/app/app.go b/services/game-service/internal/app/app.go index 71e738fa..c41d6ba9 100644 --- a/services/game-service/internal/app/app.go +++ b/services/game-service/internal/app/app.go @@ -98,14 +98,21 @@ func New(cfg config.Config) (*App, error) { svc := gameservice.New(gameservice.Config{LaunchSessionTTL: cfg.LaunchSessionTTL}, repo, walletClient, userClient, client.NewActivityGrowthClient(activityConn)) // 骰子服务复用同一个 MySQL repository 和 wallet client;局事实走 dice 表,金币事实仍走 game_orders + wallet-service。 diceSvc := diceservice.New(diceservice.Config{}, repo, walletClient) - roomRPSPublisher, err := roomrpsservice.NewTencentIMPublisherFromEnv() - if err != nil { - _ = listener.Close() - _ = activityConn.Close() - _ = userConn.Close() - _ = walletConn.Close() - _ = repo.Close() - return nil, err + var roomRPSPublisher roomrpsservice.Publisher + if cfg.TencentIM.Enabled { + // 房内 RPS 的“发起 PK”需要同步投递腾讯 IM 房间群消息;启动阶段先创建 REST client,避免运行中才发现配置缺失。 + roomRPSPublisher, err = roomrpsservice.NewTencentIMPublisher(cfg.TencentIM.RESTConfig()) + if err != nil { + _ = listener.Close() + _ = activityConn.Close() + _ = userConn.Close() + _ = walletConn.Close() + _ = repo.Close() + return nil, err + } + } else { + // 显式关闭时保留 HTTP/RPC 功能,便于纯本地测试;测试服和线上必须在 YAML 中打开,否则用户看不到房内 PK。 + logx.Warn(context.Background(), "room_rps_im_disabled", slog.String("reason", "tencent_im.enabled=false")) } // 房内猜拳独立于骰子和独立猜拳:配置、挑战状态机和 IM 事件全部由 room-rps service 承接,transport 只做 RPC 适配。 roomRPSSvc := roomrpsservice.New(roomrpsservice.Config{}, userClient, roomRPSPublisher) diff --git a/services/game-service/internal/config/config.go b/services/game-service/internal/config/config.go index 02f23856..5596e5f5 100644 --- a/services/game-service/internal/config/config.go +++ b/services/game-service/internal/config/config.go @@ -3,11 +3,13 @@ package config import ( "errors" "fmt" + "net/http" "strings" "time" "hyapp/pkg/configx" "hyapp/pkg/logx" + "hyapp/pkg/tencentim" ) // Config 描述 game-service 启动所需的最小依赖。 @@ -17,15 +19,58 @@ type Config struct { Environment string `yaml:"environment"` GRPCAddr string `yaml:"grpc_addr"` // HealthHTTPAddr 是给 CLB/发布脚本探活使用的 HTTP 监听地址,不承载业务流量。 - HealthHTTPAddr string `yaml:"health_http_addr"` - MySQLDSN string `yaml:"mysql_dsn"` - WalletServiceAddr string `yaml:"wallet_service_addr"` - UserServiceAddr string `yaml:"user_service_addr"` - ActivityServiceAddr string `yaml:"activity_service_addr"` - LaunchSessionTTL time.Duration `yaml:"launch_session_ttl"` - RocketMQ RocketMQConfig `yaml:"rocketmq"` - OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"` - Log logx.Config `yaml:"log"` + HealthHTTPAddr string `yaml:"health_http_addr"` + MySQLDSN string `yaml:"mysql_dsn"` + WalletServiceAddr string `yaml:"wallet_service_addr"` + UserServiceAddr string `yaml:"user_service_addr"` + ActivityServiceAddr string `yaml:"activity_service_addr"` + LaunchSessionTTL time.Duration `yaml:"launch_session_ttl"` + // TencentIM 是房内猜拳向房间群投递 PK 事件的腾讯 IM REST 配置;缺配置时必须显式关闭,不能靠环境变量隐式漂移。 + TencentIM TencentIMConfig `yaml:"tencent_im"` + RocketMQ RocketMQConfig `yaml:"rocketmq"` + OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"` + Log logx.Config `yaml:"log"` +} + +// TencentIMConfig 描述 game-service 发房内 RPS 群自定义消息需要的服务端 IM 配置。 +type TencentIMConfig struct { + // Enabled 控制房内 RPS 是否向腾讯 IM 房间群投递事件;开启后缺少密钥或管理员账号应启动失败。 + Enabled bool `yaml:"enabled"` + // SDKAppID 是腾讯云 IM 应用 ID,必须和 gateway 签发 UserSig 使用同一个应用。 + SDKAppID int64 `yaml:"sdk_app_id"` + // SecretKey 只用于服务端签 admin UserSig,不能出现在响应或业务日志里。 + SecretKey string `yaml:"secret_key"` + // AdminIdentifier 是服务端 REST 发房间 PK 消息的管理员账号。 + AdminIdentifier string `yaml:"admin_identifier"` + // AdminUserSigTTL 控制 REST 调用签名有效期,默认 24h。 + AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"` + // Endpoint 是腾讯 IM REST 地域入口,例如 adminapisgp.im.qcloud.com。 + Endpoint string `yaml:"endpoint"` + // GroupType 是房间群类型;房内 RPS 复用房间群,默认 ChatRoom。 + GroupType string `yaml:"group_type"` + // RequestTimeout 是单次腾讯 IM REST 调用预算,避免创建 PK 时无限等待外部服务。 + RequestTimeout time.Duration `yaml:"request_timeout"` +} + +// RESTConfig 收敛为共享腾讯 IM client 配置,调用方不用重复处理默认超时和群类型。 +func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig { + timeout := c.RequestTimeout + if timeout <= 0 { + timeout = 5 * time.Second + } + ttl := c.AdminUserSigTTL + if ttl <= 0 { + ttl = 24 * time.Hour + } + return tencentim.RESTConfig{ + SDKAppID: c.SDKAppID, + SecretKey: c.SecretKey, + AdminIdentifier: c.AdminIdentifier, + AdminUserSigTTL: ttl, + Endpoint: c.Endpoint, + GroupType: c.GroupType, + HTTPClient: &http.Client{Timeout: timeout}, + } } type RocketMQConfig struct { @@ -68,7 +113,14 @@ func Default() Config { UserServiceAddr: "127.0.0.1:13005", ActivityServiceAddr: "127.0.0.1:13006", LaunchSessionTTL: 15 * time.Minute, - RocketMQ: defaultRocketMQConfig(), + TencentIM: TencentIMConfig{ + AdminIdentifier: "administrator", + AdminUserSigTTL: 24 * time.Hour, + Endpoint: tencentim.DefaultEndpoint, + GroupType: tencentim.DefaultGroupType, + RequestTimeout: 5 * time.Second, + }, + RocketMQ: defaultRocketMQConfig(), OutboxWorker: OutboxWorkerConfig{ Enabled: false, PollInterval: time.Second, @@ -138,6 +190,10 @@ func (cfg *Config) Normalize() error { if cfg.LaunchSessionTTL <= 0 { cfg.LaunchSessionTTL = 15 * time.Minute } + cfg.normalizeTencentIM() + if cfg.TencentIM.Enabled && (cfg.TencentIM.SDKAppID <= 0 || cfg.TencentIM.SecretKey == "" || cfg.TencentIM.AdminIdentifier == "") { + return errors.New("tencent_im enabled requires sdk_app_id, secret_key and admin_identifier") + } cfg.RocketMQ = normalizeRocketMQConfig(cfg.RocketMQ) if cfg.OutboxWorker.PollInterval <= 0 { cfg.OutboxWorker.PollInterval = time.Second @@ -162,6 +218,28 @@ func (cfg *Config) Normalize() error { return nil } +func (cfg *Config) normalizeTencentIM() { + cfg.TencentIM.SecretKey = strings.TrimSpace(cfg.TencentIM.SecretKey) + cfg.TencentIM.AdminIdentifier = strings.TrimSpace(cfg.TencentIM.AdminIdentifier) + if cfg.TencentIM.AdminIdentifier == "" { + cfg.TencentIM.AdminIdentifier = "administrator" + } + cfg.TencentIM.Endpoint = strings.TrimSpace(cfg.TencentIM.Endpoint) + if cfg.TencentIM.Endpoint == "" { + cfg.TencentIM.Endpoint = tencentim.DefaultEndpoint + } + cfg.TencentIM.GroupType = strings.TrimSpace(cfg.TencentIM.GroupType) + if cfg.TencentIM.GroupType == "" { + cfg.TencentIM.GroupType = tencentim.DefaultGroupType + } + if cfg.TencentIM.AdminUserSigTTL <= 0 { + cfg.TencentIM.AdminUserSigTTL = 24 * time.Hour + } + if cfg.TencentIM.RequestTimeout <= 0 { + cfg.TencentIM.RequestTimeout = 5 * time.Second + } +} + func defaultRocketMQConfig() RocketMQConfig { return RocketMQConfig{ Enabled: false, diff --git a/services/game-service/internal/config/config_test.go b/services/game-service/internal/config/config_test.go index acddd405..9b0b498e 100644 --- a/services/game-service/internal/config/config_test.go +++ b/services/game-service/internal/config/config_test.go @@ -25,3 +25,44 @@ func TestLoadConfigsKeepActivityServiceAddr(t *testing.T) { }) } } + +func TestLoadConfigsEnableRoomRPSIM(t *testing.T) { + tests := []struct { + name string + path string + }{ + {name: "local", path: "../../configs/config.yaml"}, + {name: "docker", path: "../../configs/config.docker.yaml"}, + {name: "tencent", path: "../../configs/config.tencent.example.yaml"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg, err := Load(tt.path) + if err != nil { + t.Fatalf("Load %s config failed: %v", tt.name, err) + } + if !cfg.TencentIM.Enabled { + t.Fatalf("room-rps tencent_im must be enabled in %s config", tt.name) + } + if cfg.TencentIM.SDKAppID <= 0 || cfg.TencentIM.SecretKey == "" || cfg.TencentIM.AdminIdentifier == "" { + t.Fatalf("room-rps tencent_im config is incomplete: sdk_app_id_set=%t secret_key_set=%t admin_identifier_set=%t", cfg.TencentIM.SDKAppID > 0, cfg.TencentIM.SecretKey != "", cfg.TencentIM.AdminIdentifier != "") + } + if cfg.TencentIM.Endpoint != "adminapisgp.im.qcloud.com" { + t.Fatalf("room-rps tencent_im endpoint mismatch: %q", cfg.TencentIM.Endpoint) + } + }) + } +} + +func TestNormalizeRejectsEnabledIncompleteTencentIM(t *testing.T) { + cfg := Default() + cfg.TencentIM.Enabled = true + cfg.TencentIM.SDKAppID = 0 + cfg.TencentIM.SecretKey = "" + cfg.TencentIM.AdminIdentifier = "" + + if err := cfg.Normalize(); err == nil { + t.Fatal("Normalize should reject enabled incomplete tencent_im config") + } +} diff --git a/services/game-service/internal/service/roomrps/service.go b/services/game-service/internal/service/roomrps/service.go index d17a047c..43d8c75b 100644 --- a/services/game-service/internal/service/roomrps/service.go +++ b/services/game-service/internal/service/roomrps/service.go @@ -6,8 +6,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "net/http" - "os" "sort" "strconv" "strings" @@ -101,33 +99,10 @@ func New(config Config, user UserClient, publisher Publisher) *Service { } } -// NewTencentIMPublisherFromEnv 按环境变量创建真实腾讯 IM publisher。 -// 密钥、账号和地域入口都属于敏感或部署差异配置,因此这里只读取环境,不在 game-service 默认配置里伪造。 -func NewTencentIMPublisherFromEnv() (Publisher, error) { - sdkAppID, _ := strconv.ParseInt(strings.TrimSpace(os.Getenv("TENCENT_IM_SDK_APP_ID")), 10, 64) - secretKey := strings.TrimSpace(os.Getenv("TENCENT_IM_SECRET_KEY")) - adminIdentifier := strings.TrimSpace(os.Getenv("TENCENT_IM_ADMIN_IDENTIFIER")) - if sdkAppID <= 0 || secretKey == "" || adminIdentifier == "" { - // 未配置 IM 时不阻断本地服务启动;真实 IM smoke 会显式带环境变量,并要求事件投递成功。 - return nil, nil - } - ttl := time.Hour - if raw := strings.TrimSpace(os.Getenv("TENCENT_IM_ADMIN_USERSIG_TTL")); raw != "" { - parsed, err := time.ParseDuration(raw) - if err != nil || parsed <= 0 { - return nil, fmt.Errorf("TENCENT_IM_ADMIN_USERSIG_TTL is invalid") - } - ttl = parsed - } - return tencentim.NewRESTClient(tencentim.RESTConfig{ - SDKAppID: sdkAppID, - SecretKey: secretKey, - AdminIdentifier: adminIdentifier, - AdminUserSigTTL: ttl, - Endpoint: envOr("TENCENT_IM_ENDPOINT", tencentim.DefaultEndpoint), - GroupType: tencentim.DefaultGroupType, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }) +// NewTencentIMPublisher 创建真实腾讯 IM publisher。 +// game-service 的房内 PK 发起接口是同步返回给客户端的入口,因此 IM 配置必须来自已校验的 YAML,而不是进程环境变量的隐式副作用。 +func NewTencentIMPublisher(config tencentim.RESTConfig) (Publisher, error) { + return tencentim.NewRESTClient(config) } func (s *Service) GetConfig(ctx context.Context, app string) (*gamev1.RoomRPSConfig, int64, error) { @@ -768,11 +743,3 @@ func newChallengeID(app string) string { } return fmt.Sprintf("rps_%s_%d_%s", appcode.Normalize(app), time.Now().UnixMilli(), hex.EncodeToString(entropy[:])) } - -func envOr(key string, fallback string) string { - value := strings.TrimSpace(os.Getenv(key)) - if value == "" { - return fallback - } - return value -} From 88551cd96d33d73aa013a5976dfdba7ffe3de16f Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 11 Jun 2026 19:37:21 +0800 Subject: [PATCH 28/28] =?UTF-8?q?=E5=B9=82=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/app/local_flow_test.go | 33 ++++ .../internal/storage/mysql/query.go | 101 +++++++++++ .../internal/storage/mysql/cp/repository.go | 4 +- .../storage/mysql/cp/repository_test.go | 163 ++++++++++++++++++ 4 files changed, 300 insertions(+), 1 deletion(-) diff --git a/services/statistics-service/internal/app/local_flow_test.go b/services/statistics-service/internal/app/local_flow_test.go index 13c53cb5..01a66db1 100644 --- a/services/statistics-service/internal/app/local_flow_test.go +++ b/services/statistics-service/internal/app/local_flow_test.go @@ -334,6 +334,33 @@ func TestLocalSelfGameStatisticsFlow(t *testing.T) { if err := repo.ConsumeSelfGamePoolAdjustment(appCtx, pool); err != nil { t.Fatalf("consume self game pool failed: %v", err) } + for _, dayOffset := range []int{1, 7} { + if err := repo.ConsumeSelfGameMatch(appCtx, mysqlstorage.SelfGameMatchEvent{ + AppCode: appCode, + EventID: "SelfGameMatchSettled:dice_retention_" + string(rune('0'+dayOffset)), + EventType: gamemq.EventTypeSelfGameMatchSettled, + MatchID: "dice_retention", + GameID: "dice", + RegionID: regionID, + StakeCoin: 500, + UserParticipants: 1, + WinnerCount: 1, + UserStakeCoin: 500, + PayoutCoin: 900, + Participants: []mysqlstorage.SelfGameMatchParticipantEvent{{ + UserID: userID, + ParticipantType: "user", + StakeCoin: 500, + DicePoints: []int64{5}, + Result: "win", + PayoutCoin: 900, + NetWinCoin: 400, + }}, + OccurredAtMS: time.UnixMilli(occurredAt).AddDate(0, 0, dayOffset).UnixMilli(), + }); err != nil { + t.Fatalf("consume retention self game match day+%d failed: %v", dayOffset, err) + } + } overview, err := repo.QuerySelfGameOverview(ctx, mysqlstorage.SelfGameOverviewQuery{ AppCode: appCode, @@ -360,6 +387,12 @@ func TestLocalSelfGameStatisticsFlow(t *testing.T) { if len(overview.UserRisk) != 1 || overview.UserRisk[0].UserID != userID || overview.UserRisk[0].WinCount != 1 || overview.UserRisk[0].NetWinCoin != 400 { t.Fatalf("self game user risk mismatch: %+v", overview.UserRisk) } + if overview.Retention.CohortUsers != 1 || overview.Retention.Day1Users != 1 || overview.Retention.Day7Users != 1 || overview.Retention.Day1Rate != 1 || overview.Retention.Day7Rate != 1 { + t.Fatalf("self game retention mismatch: %+v", overview.Retention) + } + if len(overview.MetricDefinitions) == 0 { + t.Fatalf("self game metric definitions missing") + } } func walletMessage(t *testing.T, message walletmq.WalletOutboxMessage) []byte { diff --git a/services/statistics-service/internal/storage/mysql/query.go b/services/statistics-service/internal/storage/mysql/query.go index 1475f361..dd3dd21f 100644 --- a/services/statistics-service/internal/storage/mysql/query.go +++ b/services/statistics-service/internal/storage/mysql/query.go @@ -177,6 +177,8 @@ type SelfGameOverview struct { PoolStats []SelfGamePoolStat `json:"pool_stats"` ParticipantDistribution []SelfGameParticipantStat `json:"participant_distribution"` UserRisk []SelfGameUserRiskStat `json:"user_risk"` + Retention SelfGameRetention `json:"retention"` + MetricDefinitions []MetricDefinition `json:"metric_definitions"` Funnel []SelfGameFunnelStep `json:"funnel"` Conversion SelfGameConversion `json:"conversion"` } @@ -279,6 +281,19 @@ type SelfGameUserRiskStat struct { CancelRate float64 `json:"cancel_rate"` } +type SelfGameRetention struct { + CohortUsers int64 `json:"cohort_users"` + Day1Users int64 `json:"day1_users"` + Day7Users int64 `json:"day7_users"` + Day1Rate float64 `json:"day1_rate"` + Day7Rate float64 `json:"day7_rate"` +} + +type MetricDefinition struct { + Metric string `json:"metric"` + Definition string `json:"definition"` +} + type SelfGameFunnelStep struct { EventName string `json:"event_name"` EventCount int64 `json:"event_count"` @@ -419,6 +434,12 @@ func (r *Repository) QuerySelfGameOverview(ctx context.Context, query SelfGameOv return SelfGameOverview{}, err } overview.UserRisk = risk + retention, err := r.querySelfGameRetention(ctx, app, startDay, endDay, gameID, query.CountryID, query.RegionID) + if err != nil { + return SelfGameOverview{}, err + } + overview.Retention = retention + overview.MetricDefinitions = selfGameMetricDefinitions() return overview, nil } @@ -1062,6 +1083,63 @@ func (r *Repository) querySelfGameUserRisk(ctx context.Context, app, startDay, e return out, rows.Err() } +func (r *Repository) querySelfGameRetention(ctx context.Context, app, startDay, endDay, gameID string, countryID int64, regionID int64) (SelfGameRetention, error) { + cohortFilter, cohortArgs := selfGameUserDayFilter(app, startDay, endDay, gameID, countryID, regionID) + returnFilter, returnArgs := selfGameUserDayFilter(app, addStatDays(startDay, 1), addStatDays(endDay, 7), gameID, countryID, regionID) + args := make([]any, 0, len(cohortArgs)+len(returnArgs)*2) + args = append(args, cohortArgs...) + args = append(args, returnArgs...) + args = append(args, returnArgs...) + row := r.db.QueryRowContext(ctx, ` + SELECT COUNT(1), + COALESCE(SUM(CASE WHEN d1.user_id IS NULL THEN 0 ELSE 1 END), 0), + COALESCE(SUM(CASE WHEN d7.user_id IS NULL THEN 0 ELSE 1 END), 0) + FROM ( + SELECT DISTINCT stat_day, user_id + FROM stat_self_game_user_day + WHERE `+cohortFilter+` + ) cohort + LEFT JOIN ( + SELECT DISTINCT stat_day, user_id + FROM stat_self_game_user_day + WHERE `+returnFilter+` + ) d1 ON d1.user_id = cohort.user_id AND d1.stat_day = DATE_ADD(cohort.stat_day, INTERVAL 1 DAY) + LEFT JOIN ( + SELECT DISTINCT stat_day, user_id + FROM stat_self_game_user_day + WHERE `+returnFilter+` + ) d7 ON d7.user_id = cohort.user_id AND d7.stat_day = DATE_ADD(cohort.stat_day, INTERVAL 7 DAY)`, args...) + var retention SelfGameRetention + if err := row.Scan(&retention.CohortUsers, &retention.Day1Users, &retention.Day7Users); err != nil { + return SelfGameRetention{}, err + } + retention.Day1Rate = ratio(retention.Day1Users, retention.CohortUsers) + retention.Day7Rate = ratio(retention.Day7Users, retention.CohortUsers) + return retention, nil +} + +func selfGameUserDayFilter(app, startDay, endDay, gameID string, countryID int64, regionID int64) (string, []any) { + filter := "app_code = ? AND match_count > 0" + args := []any{app} + if startDay != "" && endDay != "" { + filter += " AND stat_day BETWEEN ? AND ?" + args = append(args, startDay, endDay) + } + if gameID != "" { + filter += " AND game_id = ?" + args = append(args, gameID) + } + if countryID > 0 { + filter += " AND country_id = ?" + args = append(args, countryID) + } + if regionID > 0 { + filter += " AND region_id = ?" + args = append(args, regionID) + } + return filter, args +} + func selfGameFilter(app, startDay, endDay, gameID string, countryID int64, regionID int64) (string, []any) { filter := "app_code = ? AND stat_day BETWEEN ? AND ?" args := []any{app, startDay, endDay} @@ -1146,6 +1224,14 @@ func previousStatDayRange(startDay, endDay string) (string, string) { return previousStart.Format("2006-01-02"), previousEnd.Format("2006-01-02") } +func addStatDays(day string, offset int) string { + parsed, err := time.Parse("2006-01-02", day) + if err != nil { + return day + } + return parsed.AddDate(0, 0, offset).Format("2006-01-02") +} + func div(numerator, denominator int64) int64 { if denominator <= 0 { return 0 @@ -1172,3 +1258,18 @@ func ratio(numerator, denominator int64) float64 { } return float64(numerator) / float64(denominator) } + +func selfGameMetricDefinitions() []MetricDefinition { + return []MetricDefinition{ + {Metric: "game_pv", Definition: "H5 page_open event_count;同一用户多次打开会重复计数。"}, + {Metric: "game_uv", Definition: "H5 page_open user_count;同一 UTC 日、同一 game_id、同一用户只计一次。"}, + {Metric: "match_click_users", Definition: "H5 match_click user_count;用户点击匹配按钮后上报。"}, + {Metric: "match_success_users", Definition: "H5 match_success user_count;匹配接口成功返回有效 match_id 后上报。"}, + {Metric: "settlement_users", Definition: "H5 settlement_show user_count;结算页展示时上报。"}, + {Metric: "retention_cohort_users", Definition: "统计窗口内有已结算自研游戏对局的真实用户;只统计 stat_self_game_user_day.match_count > 0。"}, + {Metric: "day1_retention", Definition: "cohort 用户在第 1 个 UTC 统计日再次有已结算自研游戏对局;game_id、country_id、region_id 筛选会同时作用于 cohort 和回访日。"}, + {Metric: "day7_retention", Definition: "cohort 用户在第 7 个 UTC 统计日再次有已结算自研游戏对局;game_id、country_id、region_id 筛选会同时作用于 cohort 和回访日。"}, + {Metric: "platform_profit_coin", Definition: "结算事件快照的 user_stake_coin + robot_stake_coin - payout_coin - refund_coin;与奖池入池/出池流水分表展示。"}, + {Metric: "pool_stats", Definition: "game-service 奖池调整流水聚合;in_coin/out_coin 是入池/出池金额,balance_after_coin 是当天同方向同原因聚合行内最新余额快照。"}, + } +} diff --git a/services/user-service/internal/storage/mysql/cp/repository.go b/services/user-service/internal/storage/mysql/cp/repository.go index 17469a51..0a17beb0 100644 --- a/services/user-service/internal/storage/mysql/cp/repository.go +++ b/services/user-service/internal/storage/mysql/cp/repository.go @@ -10,6 +10,7 @@ import ( "time" "hyapp/pkg/appcode" + "hyapp/pkg/idgen" "hyapp/pkg/xerr" cpdomain "hyapp/services/user-service/internal/domain/cp" ) @@ -1144,7 +1145,8 @@ func insertOutboxTx(ctx context.Context, tx *sql.Tx, appCode string, eventType s if err != nil { return err } - eventID := fmt.Sprintf("%s:%s:%d:%d", aggregateType, eventType, aggregateID, nowMs) + // event_id 是 user_outbox 的投递事实主键,必须按每条通知唯一生成;同一个目标用户同毫秒内可能收到多条 CP 申请/结果通知。 + eventID := idgen.New("uout") // outbox 与业务状态同事务提交;RocketMQ 发布失败只影响异步 IM/统计,不回滚已经同意的关系。 _, err = tx.ExecContext(ctx, ` INSERT INTO user_outbox ( diff --git a/services/user-service/internal/storage/mysql/cp/repository_test.go b/services/user-service/internal/storage/mysql/cp/repository_test.go index 5b04dfa9..6f83eba2 100644 --- a/services/user-service/internal/storage/mysql/cp/repository_test.go +++ b/services/user-service/internal/storage/mysql/cp/repository_test.go @@ -271,6 +271,169 @@ func TestConsumeGiftEventIgnoresNormalGiftWithoutRelationship(t *testing.T) { } } +func TestConsumeGiftEventCreatesNewApplicationAfterRejected(t *testing.T) { + schema := mysqlschema.New(t) + repo := New(schema.DB) + ctx := appcode.WithContext(context.Background(), appcode.Default) + nowMS := int64(1700000000000) + + seedCPTestUser(t, schema.DB, 4401, "Invite Alice", nowMS) + seedCPTestUser(t, schema.DB, 4402, "Invite Bob", nowMS) + + // 第一次 CP 礼物只生成 pending 申请;目标用户拒绝后,这条申请必须保持终态,不允许后续礼物回写成 pending。 + first, err := repo.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{ + AppCode: appcode.Default, + EventID: "room_evt_cp_invite_1", + RoomID: "room-cp", + RoomVersion: 3, + OccurredAtMS: nowMS + 1, + SenderUserID: 4401, + TargetUserID: 4402, + GiftID: "cp_gift", + GiftCount: 1, + GiftValue: 37, + BillingReceiptID: "receipt-cp-invite-1", + CommandID: "cmd-cp-invite-1", + CPRelationType: cpdomain.RelationTypeCP, + GiftName: "CP Gift", + }, nowMS+2) + if err != nil { + t.Fatalf("consume first cp gift failed: %v", err) + } + if !first.Consumed || first.Application.Status != cpdomain.ApplicationStatusPending { + t.Fatalf("first cp gift must create pending application: %+v", first) + } + if _, err := repo.RejectApplication(ctx, 4402, first.Application.ApplicationID, "declined", nowMS+3); err != nil { + t.Fatalf("reject first cp application failed: %v", err) + } + + // 第二次送礼来自新的 room outbox event,应创建新的 pending 申请,不能复用或覆盖旧 rejected 申请。 + second, err := repo.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{ + AppCode: appcode.Default, + EventID: "room_evt_cp_invite_2", + RoomID: "room-cp", + RoomVersion: 4, + OccurredAtMS: nowMS + 4, + SenderUserID: 4401, + TargetUserID: 4402, + GiftID: "cp_gift", + GiftCount: 1, + GiftValue: 37, + BillingReceiptID: "receipt-cp-invite-2", + CommandID: "cmd-cp-invite-2", + CPRelationType: cpdomain.RelationTypeCP, + GiftName: "CP Gift", + }, nowMS+5) + if err != nil { + t.Fatalf("consume second cp gift failed: %v", err) + } + if !second.Consumed || second.Application.Status != cpdomain.ApplicationStatusPending { + t.Fatalf("second cp gift must create pending application: %+v", second) + } + if second.Application.ApplicationID == first.Application.ApplicationID { + t.Fatalf("rejected application must not be reused: first=%s second=%s", first.Application.ApplicationID, second.Application.ApplicationID) + } + + statuses := map[string]string{} + rows, err := schema.DB.QueryContext(ctx, ` + SELECT application_id, status + FROM user_cp_applications + WHERE app_code = ? AND requester_user_id = ? AND target_user_id = ? + ORDER BY created_at_ms ASC`, + appcode.Default, 4401, 4402, + ) + if err != nil { + t.Fatalf("list cp applications failed: %v", err) + } + defer rows.Close() + for rows.Next() { + var applicationID string + var status string + if err := rows.Scan(&applicationID, &status); err != nil { + t.Fatalf("scan cp application status failed: %v", err) + } + statuses[applicationID] = status + } + if err := rows.Err(); err != nil { + t.Fatalf("iterate cp application statuses failed: %v", err) + } + if statuses[first.Application.ApplicationID] != cpdomain.ApplicationStatusRejected { + t.Fatalf("first application must remain rejected: %+v", statuses) + } + if statuses[second.Application.ApplicationID] != cpdomain.ApplicationStatusPending { + t.Fatalf("second application must remain pending: %+v", statuses) + } +} + +func TestConsumeGiftEventWritesDistinctOutboxEventsInSameMillisecond(t *testing.T) { + schema := mysqlschema.New(t) + repo := New(schema.DB) + ctx := appcode.WithContext(context.Background(), appcode.Default) + nowMS := int64(1700000000000) + + seedCPTestUser(t, schema.DB, 4501, "Outbox Alice", nowMS) + seedCPTestUser(t, schema.DB, 4502, "Outbox Bob", nowMS) + + // 同一 pending 申请被后续礼物刷新时仍会产生新的通知事实;event_id 不能只按目标用户和毫秒拼接,否则 IM 会被主键碰撞吞掉。 + first, err := repo.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{ + AppCode: appcode.Default, + EventID: "room_evt_cp_outbox_1", + RoomID: "room-cp-outbox", + RoomVersion: 3, + OccurredAtMS: nowMS + 1, + SenderUserID: 4501, + TargetUserID: 4502, + GiftID: "cp_gift", + GiftCount: 1, + GiftValue: 37, + BillingReceiptID: "receipt-cp-outbox-1", + CommandID: "cmd-cp-outbox-1", + CPRelationType: cpdomain.RelationTypeCP, + GiftName: "CP Gift", + }, nowMS+2) + if err != nil { + t.Fatalf("consume first cp outbox gift failed: %v", err) + } + second, err := repo.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{ + AppCode: appcode.Default, + EventID: "room_evt_cp_outbox_2", + RoomID: "room-cp-outbox", + RoomVersion: 4, + OccurredAtMS: nowMS + 2, + SenderUserID: 4501, + TargetUserID: 4502, + GiftID: "cp_gift", + GiftCount: 1, + GiftValue: 37, + BillingReceiptID: "receipt-cp-outbox-2", + CommandID: "cmd-cp-outbox-2", + CPRelationType: cpdomain.RelationTypeCP, + GiftName: "CP Gift", + }, nowMS+2) + if err != nil { + t.Fatalf("consume second cp outbox gift failed: %v", err) + } + if first.Application.ApplicationID != second.Application.ApplicationID { + t.Fatalf("same pending application should be refreshed before a decision: first=%s second=%s", first.Application.ApplicationID, second.Application.ApplicationID) + } + + var outboxCount int64 + if err := schema.DB.QueryRowContext(ctx, ` + SELECT COUNT(*) + FROM user_outbox + WHERE app_code = ? + AND event_type = ? + AND aggregate_type = 'cp_application' + AND aggregate_id = ?`, + appcode.Default, cpdomain.EventTypeApplicationCreated, 4502, + ).Scan(&outboxCount); err != nil { + t.Fatalf("count cp outbox events failed: %v", err) + } + if outboxCount != 2 { + t.Fatalf("same-millisecond cp notifications must keep two outbox rows, got %d", outboxCount) + } +} + func TestBreakRelationshipPrepareConfirmIsIdempotent(t *testing.T) { schema := mysqlschema.New(t) repo := New(schema.DB)