syntax = "proto3"; package hyapp.luckygift.v1; option go_package = "hyapp.local/api/proto/luckygift/v1;luckygiftv1"; // RequestMeta 是 lucky-gift-service 的内部追踪元信息。 // request_id 只用于链路排查,不能被任何业务流程当成幂等键使用。 message RequestMeta { string request_id = 1; string caller = 2; string gateway_node_id = 3; int64 sent_at_ms = 4; string app_code = 5; } // LuckyGiftMeta 是 HyApp 内部房间送礼扣费成功后的抽奖事实输入。 // 金额、目标用户和 command_id 必须来自 wallet-service 已落账的 receipt,不能从客户端 payload 推导。 message LuckyGiftMeta { RequestMeta meta = 1; string command_id = 2; int64 user_id = 3; string device_id = 4; string room_id = 5; string anchor_id = 6; string gift_id = 7; int64 coin_spent = 8; int64 paid_at_ms = 9; string pool_id = 10; int64 target_user_id = 11; int32 gift_count = 12; int64 visible_region_id = 13; int64 country_id = 14; // sender_* 是 room-service 在送礼入口已经确认的展示快照,只进入中奖事实和 IM,不能参与抽奖或账务判断。 string sender_name = 15; string sender_avatar = 16; string sender_display_user_id = 17; string sender_pretty_display_user_id = 18; // 充值分层与充值后短时加权必须由调用方传入已确认的用户事实;幸运礼物服务不跨库回查用户或支付明细。 int64 recharge_7d_coins = 19; int64 recharge_30d_coins = 20; int64 last_recharged_at_ms = 21; // gift_income_coins 是 wallet 已实际入账给收礼人的收益金币;dynamic_v3 用它校验规则主播拆账,缺失或比例不一致均拒绝开奖。 int64 gift_income_coins = 22; // user_registered_at_ms 是 user-service 在送礼入口返回的账号创建事实快照。 // dynamic_v3 只用它判断闭窗时账号是否已满 48 小时;缺失时普通开奖继续,但补偿大奖资格 fail-close。 int64 user_registered_at_ms = 23; } message LuckyGiftRuleTier { string stage = 1; string tier_id = 2; int64 multiplier_ppm = 3; int64 base_weight_ppm = 4; bool high_water_only = 5; bool enabled = 6; } message LuckyGiftRuleStage { string stage = 1; repeated LuckyGiftRuleTier tiers = 2; // 两个充值门槛共同决定 dynamic_v3 分层;novice 固定为 0/0,后续阶段逐维单调递增。 int64 min_recharge_7d_coins = 3; int64 min_recharge_30d_coins = 4; } message LuckyGiftRuleConfig { string app_code = 1; string pool_id = 2; int64 rule_version = 3; bool enabled = 4; int64 target_rtp_ppm = 5; int64 pool_rate_ppm = 6; int64 settlement_window_wager = 7; int64 control_band_ppm = 8; int64 gift_price_reference = 9; int64 novice_max_equivalent_draws = 10; int64 normal_max_equivalent_draws = 11; int64 effective_from_ms = 12; int64 created_by_admin_id = 13; int64 created_at_ms = 14; repeated LuckyGiftRuleStage stages = 15; // strategy_version 隔离历史 fixed_v2 与动态算法 dynamic_v3,避免新增约束破坏已经发布的旧规则。 string strategy_version = 16; int64 profit_rate_ppm = 17; int64 anchor_rate_ppm = 18; int64 initial_pool_coins = 19; int64 loss_streak_guarantee = 20; int64 low_watermark_coins = 21; int64 low_water_nonzero_factor_ppm = 22; int64 high_watermark_coins = 23; int64 high_water_nonzero_factor_ppm = 24; int64 recharge_boost_window_ms = 25; int64 recharge_boost_factor_ppm = 26; repeated int64 jackpot_multiplier_ppms = 27; int64 jackpot_global_rtp_max_ppm = 28; int64 jackpot_user_round_rtp_max_ppm = 29; int64 jackpot_user_48h_rtp_max_ppm = 30; // 用户在同一应用、奖池和 UTC 自然日每累计消费满该金币数,生成一次可跨日保留的消费大奖资格。 int64 jackpot_spend_threshold_coins = 31; int64 max_jackpot_hits_per_user_day = 32; int64 max_single_payout = 33; int64 user_hourly_payout_cap = 34; int64 user_daily_payout_cap = 35; int64 device_daily_payout_cap = 36; int64 room_hourly_payout_cap = 37; int64 anchor_daily_payout_cap = 38; // 用户严格滚动 24 小时 RTP 超过此值时,普通非零奖档按下方因子降权;0 表示关闭该策略。 int64 user_24h_rtp_threshold_ppm = 39; // 触发用户 24 小时 RTP 降权后保留的普通中奖权重;1000000 表示保持原概率。 int64 user_24h_ordinary_win_factor_ppm = 40; } message CheckLuckyGiftRequest { RequestMeta meta = 1; int64 user_id = 2; string room_id = 3; string gift_id = 4; string pool_id = 5; } message CheckLuckyGiftResponse { bool enabled = 1; string reason = 2; string gift_id = 3; int64 gift_price = 4; int64 rule_version = 5; int64 target_rtp_ppm = 6; string experience_pool = 7; string pool_id = 8; // strategy_version 由 lucky-gift owner 返回并由 room saga 固化,用于滚动发布时区分 fixed_v2 兼容与 dynamic_v3 强校验。 string strategy_version = 9; } message LuckyGiftDrawResult { string draw_id = 1; string command_id = 2; string gift_id = 3; int64 rule_version = 4; string experience_pool = 5; string selected_tier_id = 6; int64 base_reward_coins = 7; int64 effective_reward_coins = 8; string reward_status = 9; int64 rtp_window_index = 10; int64 gift_rtp_window_index = 11; int64 global_base_rtp_ppm = 12; int64 gift_base_rtp_ppm = 13; bool stage_feedback = 14; bool high_multiplier = 15; int64 created_at_ms = 16; string pool_id = 17; int64 multiplier_ppm = 18; string wallet_transaction_id = 19; int64 coin_balance_after = 20; int64 user_id = 21; string external_user_id = 22; string app_code = 23; repeated LuckyGiftHit hits = 24; } message ExecuteLuckyGiftDrawRequest { LuckyGiftMeta lucky_gift = 1; } message ExecuteLuckyGiftDrawResponse { LuckyGiftDrawResult result = 1; } message BatchExecuteLuckyGiftDrawRequest { repeated LuckyGiftMeta lucky_gifts = 1; } message BatchExecuteLuckyGiftDrawResponse { repeated LuckyGiftDrawResult results = 1; } // ExternalGiftDrawRequest 是外部 App 接入的抽奖输入。 // App 调用前必须已经完成自己的扣费,lucky-gift-service 只校验金额事实并返回中奖结果。 message ExternalGiftDrawRequest { RequestMeta meta = 1; string app_code = 3; string external_user_id = 4; string request_id = 5; int64 gift_count = 6; int64 unit_amount = 7; int64 total_amount = 8; string currency = 9; int64 paid_at_ms = 10; string metadata_json = 11; string pool_id = 12; // dynamic_v3 必填的稳定设备作用域。luck-gateway 不独立证明其真实性,调用方必须在自己的认证/签名边界内绑定该值;fixed_v2 允许缺失。 string device_id = 13; // user_registered_at_ms 由外部 App 按自己的用户主数据提供;dynamic_v3 缺失或晚于支付时间时补偿大奖资格 fail-close。 int64 user_registered_at_ms = 14; } message ExternalGiftDrawResponse { string draw_id = 1; string request_id = 2; string app_code = 4; string external_user_id = 5; int64 gift_count = 6; int64 unit_amount = 7; int64 total_amount = 8; int64 reward_amount = 9; int64 multiplier_ppm = 10; string reward_status = 11; int64 rule_version = 12; int64 created_at_ms = 13; } message GetLuckyGiftConfigRequest { RequestMeta meta = 1; string pool_id = 2; } message GetLuckyGiftConfigResponse { LuckyGiftRuleConfig config = 1; } message UpsertLuckyGiftConfigRequest { RequestMeta meta = 1; LuckyGiftRuleConfig config = 2; int64 operator_admin_id = 3; } message UpsertLuckyGiftConfigResponse { LuckyGiftRuleConfig config = 1; } message ListLuckyGiftConfigsRequest { RequestMeta meta = 1; } message ListLuckyGiftConfigsResponse { repeated LuckyGiftRuleConfig configs = 1; } message ListLuckyGiftDrawsRequest { RequestMeta meta = 1; string gift_id = 2; int64 user_id = 3; string room_id = 4; string status = 5; int32 page = 6; int32 page_size = 7; string pool_id = 8; string external_user_id = 10; bool external_only = 11; } message ListLuckyGiftDrawsResponse { repeated LuckyGiftDrawResult draws = 1; int64 total = 2; } message LuckyGiftDrawSummary { string pool_id = 1; int64 total_draws = 2; int64 unique_users = 3; int64 unique_rooms = 4; int64 total_spent_coins = 5; int64 total_reward_coins = 6; int64 base_reward_coins = 7; int64 actual_rtp_ppm = 8; int64 pending_draws = 9; int64 granted_draws = 10; int64 failed_draws = 11; } message LuckyGiftPoolBalance { string app_code = 1; string pool_id = 2; int64 balance = 3; int64 reserve_floor = 4; int64 available_balance = 5; int64 total_in = 6; int64 total_out = 7; bool materialized = 8; int64 updated_at_ms = 9; string strategy_version = 10; int64 manual_credit_total = 11; int64 manual_debit_total = 12; } message GetLuckyGiftDrawSummaryRequest { RequestMeta meta = 1; string gift_id = 2; int64 user_id = 3; string room_id = 4; string status = 5; string pool_id = 6; string external_user_id = 8; bool external_only = 9; } message GetLuckyGiftDrawSummaryResponse { LuckyGiftDrawSummary summary = 1; } message ListLuckyGiftPoolBalancesRequest { RequestMeta meta = 1; string pool_id = 2; string strategy_version = 3; } message ListLuckyGiftPoolBalancesResponse { repeated LuckyGiftPoolBalance pools = 1; } service LuckyGiftService { rpc CheckLuckyGift(CheckLuckyGiftRequest) returns (CheckLuckyGiftResponse); rpc ExecuteLuckyGiftDraw(ExecuteLuckyGiftDrawRequest) returns (ExecuteLuckyGiftDrawResponse); rpc BatchExecuteLuckyGiftDraw(BatchExecuteLuckyGiftDrawRequest) returns (BatchExecuteLuckyGiftDrawResponse); rpc ExecuteExternalGiftDraw(ExternalGiftDrawRequest) returns (ExternalGiftDrawResponse); } service AdminLuckyGiftService { rpc GetLuckyGiftConfig(GetLuckyGiftConfigRequest) returns (GetLuckyGiftConfigResponse); rpc UpsertLuckyGiftConfig(UpsertLuckyGiftConfigRequest) returns (UpsertLuckyGiftConfigResponse); rpc ListLuckyGiftConfigs(ListLuckyGiftConfigsRequest) returns (ListLuckyGiftConfigsResponse); rpc ListLuckyGiftDraws(ListLuckyGiftDrawsRequest) returns (ListLuckyGiftDrawsResponse); rpc GetLuckyGiftDrawSummary(GetLuckyGiftDrawSummaryRequest) returns (GetLuckyGiftDrawSummaryResponse); rpc ListLuckyGiftPoolBalances(ListLuckyGiftPoolBalancesRequest) returns (ListLuckyGiftPoolBalancesResponse); rpc AdjustLuckyGiftPoolBalance(AdjustLuckyGiftPoolBalanceRequest) returns (AdjustLuckyGiftPoolBalanceResponse); rpc ListLuckyGiftUserProfiles(ListLuckyGiftUserProfilesRequest) returns (ListLuckyGiftUserProfilesResponse); rpc GetLuckyGiftUserProfile(GetLuckyGiftUserProfileRequest) returns (GetLuckyGiftUserProfileResponse); } // LuckyGiftHit 只返回批量逐份开奖中实际中奖的精简位置,不复制全部审计明细。 // 声明追加在文件尾以保持既有 protobuf message index 稳定。 message LuckyGiftHit { int32 gift_index = 1; string draw_id = 2; string selected_tier_id = 3; int64 multiplier_ppm = 4; int64 reward_coins = 5; } // 调账消息也只追加在文件尾,保持所有既有 protobuf message index 稳定。 // adjustment_id 是业务幂等键;RequestMeta.request_id 只做链路追踪,不能替代资金幂等语义。 message AdjustLuckyGiftPoolBalanceRequest { RequestMeta meta = 1; string pool_id = 2; string strategy_version = 3; string adjustment_id = 4; string direction = 5; int64 amount_coins = 6; string reason = 7; int64 operator_admin_id = 8; } message LuckyGiftPoolAdjustment { string adjustment_id = 1; string app_code = 2; string pool_id = 3; string strategy_version = 4; string direction = 5; int64 amount_coins = 6; string reason = 7; int64 operator_admin_id = 8; int64 balance_before = 9; int64 balance_after = 10; int64 created_at_ms = 11; } message AdjustLuckyGiftPoolBalanceResponse { LuckyGiftPoolAdjustment adjustment = 1; LuckyGiftPoolBalance pool = 2; bool idempotent_replay = 3; } // 用户画像协议追加在文件尾,避免改变既有 message index。外部策略哈希只存在于 owner 数据库, // 本协议永远只下发 external_user_id,防止运营误把不可逆内部键当成外部用户身份。 message LuckyGiftUserProfileWindow { int64 draws = 1; int64 wager_coins = 2; int64 payout_coins = 3; int64 net_coins = 4; int64 rtp_ppm = 5; bool has_rtp = 6; } message LuckyGiftUserProfile { string app_code = 1; string pool_id = 2; string identity_type = 3; int64 internal_user_id = 4; string external_user_id = 5; int64 rule_version = 6; string strategy_version = 7; string stage = 8; int64 paid_draws = 9; int64 equivalent_draws = 10; int64 loss_streak = 11; int64 pending_spend_jackpot_tokens = 12; int64 guarantee_draws_remaining = 13; bool downweight_active = 14; int64 downweight_factor_ppm = 15; LuckyGiftUserProfileWindow one_hour = 16; LuckyGiftUserProfileWindow twenty_four_hours = 17; LuckyGiftUserProfileWindow forty_eight_hours = 18; LuckyGiftUserProfileWindow lifetime = 19; int64 base_reward_coins = 20; int64 ordinary_win_count = 21; int64 high_multiplier_ordinary_win_count = 22; int64 rtp_compensation_jackpot_count = 23; int64 cumulative_spend_jackpot_count = 24; int64 guarantee_hit_count = 25; int64 zero_draw_count = 26; int64 granted_draw_count = 27; int64 pending_draw_count = 28; int64 failed_draw_count = 29; int64 max_multiplier_ppm = 30; int64 max_reward_coins = 31; int64 first_draw_at_ms = 32; int64 last_draw_at_ms = 33; int64 aggregated_at_ms = 34; int64 data_lag_ms = 35; int64 user_24h_rtp_threshold_ppm = 36; } message ListLuckyGiftUserProfilesRequest { RequestMeta meta = 1; string pool_id = 2; string identity_type = 3; int64 internal_user_id = 4; string external_user_id = 5; string stage = 6; string status = 7; string sort_by = 8; string sort_direction = 9; int32 page = 10; int32 page_size = 11; // cursor 是 owner service 生成的不透明 keyset 游标;page 仅保留旧客户端协议兼容,服务端不再执行 OFFSET。 string cursor = 12; } message ListLuckyGiftUserProfilesResponse { repeated LuckyGiftUserProfile profiles = 1; int64 total = 2; int64 snapshot_at_ms = 3; string next_cursor = 4; bool has_more = 5; } message GetLuckyGiftUserProfileRequest { RequestMeta meta = 1; string pool_id = 2; string identity_type = 3; int64 internal_user_id = 4; string external_user_id = 5; int32 jackpot_page = 6; int32 jackpot_page_size = 7; // jackpot_cursor 由上一页 next_jackpot_cursor 原样返回;jackpot_page 仅保留协议兼容。 string jackpot_cursor = 8; } message LuckyGiftUserMultiplierStat { string hit_type = 1; int64 multiplier_ppm = 2; int64 draw_count = 3; int64 wager_coins = 4; int64 payout_coins = 5; } message LuckyGiftUserJackpotCondition { string name = 1; int64 numerator = 2; int64 denominator = 3; int64 ratio_ppm = 4; int64 limit_ppm = 5; int64 factor_ppm = 6; bool passed = 7; string reason = 8; } message LuckyGiftUserJackpotHit { string draw_id = 1; string request_id = 2; int64 rule_version = 3; string stage = 4; string mechanism = 5; string reason_code = 6; string reason_summary = 7; string tier_id = 8; int64 multiplier_ppm = 9; int64 wager_coins = 10; int64 payout_coins = 11; int64 occurred_at_ms = 12; repeated LuckyGiftUserJackpotCondition conditions = 13; // event_key 只标识这条画像大奖事实,供前端稳定追加去重;不包含用户策略键或用户哈希。 string event_key = 14; } message GetLuckyGiftUserProfileResponse { LuckyGiftUserProfile profile = 1; repeated LuckyGiftUserMultiplierStat multiplier_distribution = 2; repeated LuckyGiftUserJackpotHit jackpot_hits = 3; int64 jackpot_hit_total = 4; int64 snapshot_at_ms = 5; int32 jackpot_page = 6; int32 jackpot_page_size = 7; string next_jackpot_cursor = 8; bool jackpot_has_more = 9; }