211 lines
6.9 KiB
Markdown
211 lines
6.9 KiB
Markdown
# 幸运礼物重构第三步:链路接入与上线验收
|
||
|
||
本文档定义幸运礼物重构第三步。第三步把 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` | 钱包事故时暂停返奖 dispatch,draw 保持 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 才算真正上线。前两步只完成规则和引擎,不能直接对外开放。
|