# 幸运礼物线上落地文档 本文档定义当前幸运礼物在 `activity-service` 的线上落地边界、后台参数、抽奖事务和验证方式。幸运礼物只拥有活动决策、RTP 窗口、奖池和抽奖事实;金币扣费和返奖入账仍归 `wallet-service`,房间状态和房间表现仍归 `room-service`。 ## Current Scope 本次落地包含: | Scope | Status | Notes | | --- | --- | --- | | `LuckyGiftService.CheckLuckyGift` | done | 房间送礼前检查礼物是否启用,并返回当前体验池 | | `LuckyGiftService.ExecuteLuckyGiftDraw` | done | 扣费成功后执行抽奖决策,写抽奖事实和 outbox | | `AdminLuckyGiftService.GetLuckyGiftConfig` | done | 后台按 `pool_id` 读取幸运礼物奖池规则;未配置时返回默认草稿 | | `AdminLuckyGiftService.UpsertLuckyGiftConfig` | done | 后台按 `pool_id` 发布独立幸运礼物奖池规则版本 | | `AdminLuckyGiftService.ListLuckyGiftDraws` | done | 后台审计抽奖记录 | | 三层基础奖池 | done | platform / room / gift | | RTP 双窗口 | done | global + gift | | 风控计数 | done | user hour/day、device day、room hour、anchor day | | 房间气氛池 | done | 独立预算,不污染基础 RTP | | 活动预算池 | done | 独立补贴,不污染基础 RTP | | 返奖入账 | done | `activity_outbox` worker 调 `wallet-service.CreditLuckyGiftReward`,钱包 outbox 继续触发余额私信 | | 房间中奖 IM | done | `activity_outbox` worker 投递腾讯云 IM 群消息 `lucky_gift_drawn` | | App 检查接口 | done | gateway 暴露 `POST /api/v1/activities/lucky-gifts/check` | `activity-service` 抽奖事务内仍不调用钱包、房间 RPC 或腾讯云 IM。抽奖事务只写事实和 `activity_outbox`;事务外由 lucky gift worker 抢占 outbox,先幂等返奖,再投递房间中奖 IM,最后标记 draw 和 outbox 状态。 ## Call Flow ```text room-service SendGift 1. CheckLuckyGift(meta, user_id, room_id, gift_id, pool_id) 2. wallet-service 扣费成功 3. ExecuteLuckyGiftDraw(command_id, user_id, target_user_id, gift_count, device_id, room_id, anchor_id, gift_id, pool_id, coin_spent, paid_at_ms) 4. activity-service 本地事务写 draw fact + outbox 5. lucky gift worker 幂等调用 wallet-service.CreditLuckyGiftReward 6. wallet-service 写 WalletBalanceChanged,notice-service 投递私信余额通知 7. lucky gift worker 投递房间群 IM lucky_gift_drawn 8. activity-service 标记 reward_status=granted、outbox=delivered ``` `command_id` 是抽奖幂等键。重复 `ExecuteLuckyGiftDraw` 会返回同一条 `lucky_draw_records`,不会重复消耗 RTP、奖池或预算。 后台按 `pool_id` 维护多份互相独立的幸运礼物奖池规则,实际抽奖仍记录用户发送的真实 `gift_id`。后台不暴露单抽消耗;服务端保存固定内部换算单位,不同幸运礼物实际消耗不同,抽奖事务按实际 `coin_spent` 等比缩放基础奖档,并按实际 `coin_spent` 累计 RTP 目标和奖池入账。同一个 `gift_id` 可以请求不同 `pool_id`,不同 `pool_id` 的 RTP 窗口、基础奖池、房间气氛池、活动预算、用户阶段和风控计数互不共享。 ## Transaction Boundary `ExecuteLuckyGiftDraw` 使用一个 MySQL 本地事务,事务内必须完成: 1. 按 `command_id` 锁定幂等记录。 2. 锁定 `lucky_gift_rules` 中 `pool_id` 对应规则。 3. 锁定或创建用户体验状态 `lucky_user_states`。 4. 锁定或创建全站 RTP 窗口、幸运礼物整体子窗口。 5. 锁定或创建平台、房间、幸运礼物整体三层基础奖池。 6. 锁定或创建房间气氛池。 7. 锁定或创建活动总预算桶和 UTC 日预算桶。 8. 锁定用户、设备、房间、主播风控计数。 9. 抽前过滤所有不可支付候选。 10. 执行一次服务端安全随机。 11. 更新 RTP 窗口、奖池、预算、风控计数、用户状态。 12. 插入 `lucky_draw_records`。 13. 必要时插入 `activity_outbox`。 事务内不得调用 `wallet-service`、`room-service` 或腾讯云 IM,避免外部 RPC 卡住奖池行锁。 ## RTP Accounting 基础返奖只使用 `base_reward_coins`: ```text base_rtp = sum(base_reward_coins) / sum(coin_spent) ``` 用户可见返奖使用: ```text effective_reward_coins = base_reward_coins + room_atmosphere_reward_coins + activity_subsidy_coins ``` 房间气氛池和活动预算只能提高 `effective_rtp`,不能用于补平基础 RTP。 ## Admin Parameters 后台参数分为 6 类。 后台 HTTP 入口: | Method | Path | Permission | Meaning | | --- | --- | --- | --- | | `GET` | `/api/v1/admin/activity/lucky-gifts/config?pool_id=pool_95` | `lucky-gift:view` | 读取指定奖池规则;未配置返回默认草稿 | | `PUT` | `/api/v1/admin/activity/lucky-gifts/config?pool_id=pool_95` | `lucky-gift:update` | 发布指定奖池的新规则版本 | | `GET` | `/api/v1/admin/activity/lucky-gifts/draws` | `lucky-gift:view` | 查询抽奖审计记录 | | Group | Fields | Meaning | | --- | --- | --- | | RTP 成本 | `target_rtp_ppm`, `pool_rate_ppm`, `global_window_draws`, `gift_window_draws` | 控制基础 RTP 和收敛窗口 | | 三层池 | `platform_pool_weight_ppm`, `room_pool_weight_ppm`, `gift_pool_weight_ppm`, `initial_*_pool`, `*_reserve` | 控制平台、房间、礼物各自承担的基础返奖能力 | | 倍率配置 | `multiplier_ppms`, generated `tiers` | 运营只配置用户可能命中的倍率;服务按 RTP 窗口、水位和风控自动生成实时概率 | | 体验池 | `novice_draw_limit`, `intermediate_draw_limit` | 控制新手/中级/高级阶段,不直接决定成本 | | 高倍 | `high_multiplier`, `high_water_pool_multiple`, `large_tier_enabled` | 控制 100x/500x 等高倍倍率是否可进入候选 | | 风控 | `max_single_payout`, `user_hourly_payout_cap`, `user_daily_payout_cap`, `device_daily_payout_cap`, `room_hourly_payout_cap`, `anchor_daily_payout_cap` | 控制单用户、设备、房间、主播关联风险敞口 | | 娱乐补贴 | `room_atmosphere_rate_ppm`, `room_atmosphere_initial`, `room_atmosphere_reserve`, `activity_budget`, `activity_daily_limit` | 控制房间爆点和活动补贴 | 调整方向: | Goal | Primary Change | | --- | --- | | 降成本 | 降 `target_rtp_ppm`,降补贴预算,提高高水位倍数 | | 增刺激 | 增加高倍倍率,增房间气氛和活动预算 | | 新手更友好 | 增加 0.2x/0.5x/1x 等小倍率,延长 `novice_draw_limit` | | 防套利 | 降低用户/设备/房间/主播上限,关闭高倍 | | RTP 更稳 | 缩小 RTP 窗口 | | 体验更自然 | 放大 RTP 窗口,但接受短周期波动 | ## Data Tables | Table | Owner Data | | --- | --- | | `lucky_gift_rules` | 按 `pool_id` 保存幸运礼物奖池规则和后台可调参数 | | `lucky_rtp_windows` | 按 `pool_id` 保存 RTP 控制窗口 | | `lucky_pools` | 按 `pool_id` 保存 platform / room / gift 三层基础奖池 | | `lucky_room_atmosphere_pools` | 按 `pool_id + room_id` 保存房间气氛预算 | | `lucky_activity_budgets` | 按 `pool_id` 保存活动补贴预算;`budget_day=__total__` 控总额,UTC 日期行控日释放 | | `lucky_user_states` | 用户在单个 `pool_id` 内的累计付费抽数 | | `lucky_risk_counters` | 按 `pool_id` 保存风控窗口计数 | | `lucky_draw_records` | 每次抽奖事实和审计快照 | ## Verification Proto 变更后必须运行: ```bash PATH=/tmp/codex-protoc/bin:$HOME/go/bin:$PATH make proto ``` 服务编译验证: ```bash go test ./services/activity-service/... ``` 模型验证: ```bash go run ./tools/lucky-gift-v2-demo \ -users 1000 \ -devices 900 \ -rooms 50 \ -draws-min 0 \ -draws-max 100000 \ -unique-draws \ -base-rtp 95 \ -initial 1000000000 \ -cost 500 \ -global-window 100000 \ -gift-window 100000 ``` 验收口径: | Check | Requirement | | --- | --- | | `100000` paid draws | 基础 RTP 偏差 `< 1%` | | `1000000` paid draws | 基础 RTP 偏差 `< 0.5%` | | lucky gift RTP | 幸运礼物整体子窗口独立收敛 | | pool safety | 三层池不低于 reserve | | subsidy isolation | 房间气氛/活动补贴不进入基础 RTP | | idempotency | 重复 command_id 返回同一 draw | ## Rollout 建议上线顺序: 1. 后台创建规则草稿,`enabled=false`。 2. 跑模型模拟并审核参数。 3. 发布 `enabled=true` 到全局幸运礼物配置。 4. 只开放内部房间或白名单房间。 5. 观察 `base_rtp`、`effective_rtp`、pool balance、risk reject、outbox pending。 6. 稳定后逐步开放更多房间和礼物。 必须保留的开关: | Switch | Effect | | --- | --- | | `enabled=false` | 全站幸运礼物停止抽奖 | | `large_tier_enabled=false` | 关闭高倍奖档 | | `activity_budget=0` | 关闭活动补贴 | | `room_atmosphere_rate_ppm=0` | 关闭房间气氛补贴 | | 降低各风控 cap | 快速压缩风险敞口 |