hyapp-server/docs/幸运礼物重构第三步-链路接入与上线验收.md
2026-05-27 22:12:16 +08:00

211 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 幸运礼物重构第三步:链路接入与上线验收
本文档定义幸运礼物重构第三步。第三步把 v2 配置和 v2 抽奖引擎接回房间送礼主链路完成返奖、IM、后台审计、灰度上线和旧逻辑删除。当前不兼容旧代码切换完成后旧幸运礼物抽奖入口、旧规则表和旧模拟器都应下线。
## 目标
| 目标 | 说明 |
| --- | --- |
| 主链路接入 | `room-service` 扣费后调用 v2 抽奖 |
| 返奖一致 | `activity_outbox``wallet-service` 入账,钱包 outbox 通知余额 |
| 房间表现一致 | `lucky_gift_drawn` IM 和区域播报由 outbox 补偿 |
| 后台可审计 | 后台能查 draw、候选表、控制模式、RTP、奖池和风控快照 |
| 可灰度回滚 | 按 `pool_id/room_id/region` 控制 v2 开关 |
| 删除旧逻辑 | 验收完成后移除旧 v1 抽奖和旧配置入口 |
## 主链路
```text
Client
-> gateway POST /api/v1/rooms/gift/send
-> room-service 校验房间和目标
-> wallet-service DebitGift
-> room-service 调 activity-service ExecuteLuckyGiftDrawV2
-> activity-service 本地事务写 draw + outbox
-> room-service 落 Room Cell 礼物、热度、榜单
-> gateway 返回 lucky_gift
-> activity worker 返奖入账
-> activity worker 投递 lucky_gift_drawn IM
-> wallet/notice 投递 WalletBalanceChanged
```
## 接口要求
### App 送礼请求
幸运礼物必须传明确 `pool_id`
```json
{
"room_id": "room_1",
"command_id": "cmd_001",
"target_type": "user",
"target_user_ids": [10002],
"gift_id": "lucky_star_100",
"gift_count": 1,
"pool_id": "lucky_100"
}
```
不再支持“礼物类型是 lucky 但不传 pool_id 时走 default”的隐式路径。
### 送礼响应
```json
{
"lucky_gift": {
"enabled": true,
"draw_id": "lucky_draw_xxx",
"command_id": "cmd_001",
"pool_id": "lucky_100",
"rule_version": 12,
"stage": "normal",
"control_mode": "normal",
"selected_tier_id": "normal_0_5x",
"multiplier_ppm": 500000,
"base_reward_coins": 50,
"activity_subsidy_coins": 0,
"effective_reward_coins": 50,
"reward_status": "pending",
"created_at_ms": 1779259000000
}
}
```
客户端规则:
| 场景 | 处理 |
| --- | --- |
| `reward_status=pending` | 展示中奖结果和“奖励处理中”,不本地加余额 |
| 收到 `WalletBalanceChanged` | 更新余额 |
| 收到同一 `draw_id` 的 IM | 去重,不重复播放 |
| `lucky_gift` 为空 | 按普通礼物处理 |
## outbox 要求
抽奖事务只写事实和 outbox
| outbox | 作用 |
| --- | --- |
| `LuckyGiftRewardCredit` | 调钱包返奖 |
| `LuckyGiftDrawnRoomIM` | 投递房间开奖结果 |
| `LuckyGiftBigWinBroadcast` | 投递区域或全站播报 |
顺序要求:
1. 先返奖入账。
2. 再投递房间开奖结果。
3. 最后标记 draw 为 `granted`
如果返奖已成功但 IM 失败:
| 状态 | 处理 |
| --- | --- |
| draw | 不能标记 `failed`,避免覆盖账务事实 |
| outbox | 继续重试 IM |
| 后台 | 展示“返奖成功,表现补偿中” |
## 后台审计
后台必须支持:
| 页面 | 能力 |
| --- | --- |
| 抽奖列表 | 按 `pool_id/user_id/room_id/status/control_mode` 查询 |
| 抽奖详情 | 展示候选表、过滤原因、权重、随机落点 |
| RTP 看板 | 展示目标 RTP、实际 RTP、控制模式分布 |
| 奖池看板 | 展示 platform / room / gift 水位 |
| 风控看板 | 展示用户、设备、房间、主播过滤次数 |
| outbox 看板 | 展示 pending/retryable/failed |
抽奖详情必须能回答:
```text
为什么这次是 0x
为什么 100x 被过滤?
为什么进入 boost/cooldown
这次随机落点在哪里?
返奖是否到账?
房间 IM 是否投递?
```
## 灰度策略
| 阶段 | 范围 | 要求 |
| --- | --- | --- |
| shadow | 只计算 v2不返奖、不发 IM | 和模拟结果接近 |
| internal | 内部房间、小额礼物 | outbox 和钱包稳定 |
| room whitelist | 白名单房间 | 用户体验指标正常 |
| region canary | 单区域小流量 | RTP 和投诉稳定 |
| full | 全量开放 | 旧逻辑可删除 |
灰度开关:
| 开关 | 作用 |
| --- | --- |
| `lucky_gift_v2_enabled` | 全局 v2 开关 |
| `pool_v2_enabled` | 单奖池开关 |
| `room_whitelist` | 房间白名单 |
| `region_whitelist` | 区域白名单 |
| `disable_high_tier` | 临时关闭高倍 |
| `reward_credit_enabled` | 钱包事故时暂停返奖 dispatchdraw 保持 pending |
## 监控指标
| 指标 | 告警 |
| --- | --- |
| `base_rtp` | 超出目标波动带 |
| `effective_rtp` | 高于预算预期 |
| `zero_rate` | 高于配置预期 |
| `zero_streak_p95` | 超过体验阈值 |
| `control_mode_boost_ratio` | 长期过高说明概率或奖池不健康 |
| `control_mode_cooldown_ratio` | 长期过高说明派奖过多 |
| `pool_balance_min` | 接近 reserve |
| `risk_filter_count` | 过高说明风控在替代概率控制 |
| `outbox_pending_count` | 堆积 |
| `reward_credit_latency` | 到账延迟 |
| `draw_error_count` | 抽奖事务失败 |
## 旧逻辑删除
全量稳定后删除:
| 旧内容 | 删除条件 |
| --- | --- |
| 旧 `lucky_gift_rules` 写入口 | v2 后台发布稳定 |
| 旧 `multiplier_ppms -> tiers(weight=0)` 归一化 | v2 引擎全量 |
| 旧 `selectLuckyCandidate` 自动权重模型 | v2 引擎全量 |
| 旧 `tools/lucky-gift-v2-demo` 写死倍率模拟 | 新模拟器覆盖 |
| 空 `pool_id` 默认奖池路径 | App 全部传明确 `pool_id` |
## 实施任务
1. 增加 `ExecuteLuckyGiftDrawV2` 内部接口。
2. `room-service` 送礼链路改调 v2。
3. gateway 响应增加 `stage/control_mode`
4. activity outbox 拆分返奖、房间 IM、区域播报事件。
5. 后台增加抽奖详情和候选表审计。
6. 增加灰度开关和监控指标。
7. shadow draw 跑满一个结算窗口。
8. 灰度切流。
9. 全量后删除旧逻辑。
## 验收标准
| 验收项 | 标准 |
| --- | --- |
| App 参数 | 幸运礼物不传 `pool_id` 必须拒绝,不走 default |
| 扣费顺序 | 钱包扣费成功后才调用 v2 抽奖 |
| 事务边界 | 抽奖事务内不调用钱包返奖或 IM |
| 同步响应 | `lucky_gift` 返回 `stage/control_mode/multiplier_ppm/reward_status` |
| 余额一致 | 客户端余额只由 `WalletBalanceChanged` 或余额查询更新 |
| outbox 幂等 | 重试不会重复返奖、重复 IM 可由 `event_id/draw_id` 去重 |
| 后台审计 | 任一 draw 可查看完整候选表和过滤原因 |
| shadow 验证 | shadow 结果的 RTP、命中率、连续 0 分布与模拟报告一致 |
| 灰度指标 | 灰度期间 base RTP 在控制带内outbox 无持续堆积 |
| 体验指标 | 100 金币礼物 `zero_streak_p95` 不超过配置阈值 |
| 删除旧逻辑 | 全量稳定后旧配置入口、旧抽奖引擎、旧默认奖池路径已删除 |
| 全量测试 | `make test` 通过 |
第三步完成后,幸运礼物 v2 才算真正上线。前两步只完成规则和引擎,不能直接对外开放。