157 lines
6.5 KiB
Markdown
157 lines
6.5 KiB
Markdown
# 幸运礼物重构第一步:配置契约与单位
|
||
|
||
本文档定义幸运礼物重构第一步。第一步只解决配置、单位、规则版本和后台发布校验,不实现新抽奖算法。当前处于开发阶段,不兼容旧代码,不保留旧的 `target_rtp_ppm=95`、旧 `tiers.weight=0` 自动权重模型,也不继续把 `lucky_gift_rules.gift_id` 当作奖池 ID 使用。
|
||
|
||
## 目标
|
||
|
||
| 目标 | 说明 |
|
||
| --- | --- |
|
||
| 消灭单位歧义 | 后台展示百分比和倍数,服务端持久化 ppm |
|
||
| 明确规则 owner | `activity-service` 拥有幸运礼物规则、奖档、RTP 参数和版本 |
|
||
| 按阶段配置体验 | `novice/normal/advanced` 各自拥有独立奖档概率 |
|
||
| 发布前校验 | 配置无法满足成本或体验边界时拒绝发布 |
|
||
| 为第二步抽奖引擎铺路 | 输出可直接被 v2 抽奖引擎读取的规则快照 |
|
||
|
||
## 删除旧模型
|
||
|
||
| 旧模型 | 新处理 |
|
||
| --- | --- |
|
||
| 后台直接提交 `target_rtp_ppm=95` | 改为后台提交 `target_rtp_percent=95`,服务端转换为 `950000` |
|
||
| 后台提交倍数 `0.3` 后直接存 `0.3` | 服务端保存 `multiplier_ppm=300000` |
|
||
| `lucky_gift_rules.gift_id` 表示奖池 | 新表使用显式 `pool_id` |
|
||
| 单一 `multiplier_ppms` 生成三个阶段相同奖档 | 三个阶段分别配置奖档和权重 |
|
||
| `tiers.weight=0`,运行时自动权重 | 后台必须配置基础概率,抽奖引擎只做控制调整 |
|
||
|
||
## 新配置字段
|
||
|
||
### 规则主表
|
||
|
||
建议新建 `lucky_gift_rule_versions`。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `app_code` | string | App 编码 |
|
||
| `pool_id` | string | 幸运礼物奖池 ID |
|
||
| `rule_version` | int64 | 规则版本,每次发布递增 |
|
||
| `enabled` | bool | 是否启用 |
|
||
| `target_rtp_ppm` | int64 | 基础返奖目标,`95% = 950000` |
|
||
| `pool_rate_ppm` | int64 | 每笔扣费进入基础奖池比例 |
|
||
| `settlement_window_wager` | int64 | RTP 结算窗口流水,单位金币 |
|
||
| `control_band_ppm` | int64 | 正常波动带,`10000 = ±1%` |
|
||
| `gift_price_reference` | int64 | 配置参考礼物价格 |
|
||
| `effective_from_ms` | int64 | 生效时间,UTC epoch ms |
|
||
| `created_by_admin_id` | int64 | 发布人 |
|
||
| `created_at_ms` | int64 | 创建时间 |
|
||
|
||
### 阶段奖档表
|
||
|
||
建议新建 `lucky_gift_stage_tiers`。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `app_code` | string | App 编码 |
|
||
| `pool_id` | string | 奖池 ID |
|
||
| `rule_version` | int64 | 规则版本 |
|
||
| `stage` | string | `novice/normal/advanced` |
|
||
| `tier_id` | string | 奖档 ID |
|
||
| `multiplier_ppm` | int64 | 倍率,`1x = 1000000` |
|
||
| `base_weight_ppm` | int64 | 正常模式基础概率 |
|
||
| `reward_source` | string | `base_rtp/activity_subsidy/presentation_only` |
|
||
| `high_water_only` | bool | 是否高水位才开放 |
|
||
| `broadcast_level` | string | `none/room/region/global` |
|
||
| `enabled` | bool | 是否启用 |
|
||
|
||
## 后台输入格式
|
||
|
||
后台对运营展示和提交应使用易懂单位:
|
||
|
||
```json
|
||
{
|
||
"pool_id": "lucky_100",
|
||
"enabled": true,
|
||
"target_rtp_percent": 95,
|
||
"pool_rate_percent": 96,
|
||
"settlement_window_wager": 1000000,
|
||
"control_band_percent": 1,
|
||
"gift_price_reference": 100,
|
||
"stages": [
|
||
{
|
||
"stage": "novice",
|
||
"tiers": [
|
||
{"tier_id": "novice_none", "multiplier": 0, "probability_percent": 30},
|
||
{"tier_id": "novice_0_3x", "multiplier": 0.3, "probability_percent": 22},
|
||
{"tier_id": "novice_0_5x", "multiplier": 0.5, "probability_percent": 18}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
服务端保存前转换:
|
||
|
||
```text
|
||
target_rtp_percent=95 -> target_rtp_ppm=950000
|
||
pool_rate_percent=96 -> pool_rate_ppm=960000
|
||
control_band_percent=1 -> control_band_ppm=10000
|
||
multiplier=0.3 -> multiplier_ppm=300000
|
||
probability_percent=22 -> base_weight_ppm=220000
|
||
```
|
||
|
||
## 发布校验
|
||
|
||
| 校验项 | 规则 | 失败处理 |
|
||
| --- | --- | --- |
|
||
| RTP 单位 | `100000 <= target_rtp_ppm <= 990000` | 拒绝发布 |
|
||
| 入池比例 | `pool_rate_ppm >= target_rtp_ppm` 且 `pool_rate_ppm <= 1000000` | 拒绝发布 |
|
||
| 波动带 | `0 < control_band_ppm <= 50000` | 拒绝发布 |
|
||
| 参考价格 | `gift_price_reference > 0` | 拒绝发布 |
|
||
| 阶段完整 | 必须有 `novice/normal/advanced` 三个阶段 | 拒绝发布 |
|
||
| 阶段概率 | 每个阶段 `sum(base_weight_ppm) == 1000000` | 拒绝发布 |
|
||
| 倍率合法 | `multiplier_ppm >= 0` | 拒绝发布 |
|
||
| 基础奖档 | 每个阶段至少有一个 `base_rtp` 奖档 | 拒绝发布 |
|
||
| 未中奖档 | 每个阶段必须显式配置 `0x`,除非标记为“每抽必返” | 拒绝发布或强确认 |
|
||
| 高倍档 | `high_water_only=true` 或有明确单次上限 | 拒绝发布 |
|
||
|
||
## API 调整
|
||
|
||
后台接口只暴露业务单位:
|
||
|
||
| 接口 | 行为 |
|
||
| --- | --- |
|
||
| `GET /api/v1/admin/activity/lucky-gifts/config?pool_id=...` | 返回百分比、倍数和阶段奖档 |
|
||
| `PUT /api/v1/admin/activity/lucky-gifts/config?pool_id=...` | 发布新版本,服务端转换 ppm 并校验 |
|
||
| `GET /api/v1/admin/activity/lucky-gifts/configs` | 列出所有奖池和当前版本 |
|
||
|
||
内部 gRPC 可以继续使用 ppm,但字段名必须明确:
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `target_rtp_ppm` | 内部字段,后台 HTTP 不直接要求运营填写 |
|
||
| `pool_rate_ppm` | 内部字段 |
|
||
| `multiplier_ppm` | 内部字段 |
|
||
| `base_weight_ppm` | 内部字段 |
|
||
|
||
## 实施任务
|
||
|
||
1. 新增 v2 proto:规则、阶段、奖档、发布请求、读取响应。
|
||
2. 新增 MySQL v2 表。
|
||
3. 改后台 lucky gift handler,只接收百分比和倍数。
|
||
4. 在 `activity-service` 增加配置转换和发布校验。
|
||
5. 删除或旁路旧 `multiplier_ppms -> tiers(weight=0)` 归一化模型。
|
||
6. 增加配置单元测试和 schema 初始化。
|
||
|
||
## 验收标准
|
||
|
||
| 验收项 | 标准 |
|
||
| --- | --- |
|
||
| 单位转换 | 后台提交 `target_rtp_percent=95` 后,DB 保存 `target_rtp_ppm=950000` |
|
||
| 倍率转换 | 后台提交 `multiplier=0.3` 后,DB 保存 `multiplier_ppm=300000` |
|
||
| 概率转换 | 后台提交 `probability_percent=22` 后,DB 保存 `base_weight_ppm=220000` |
|
||
| 错误 RTP | 后台提交等价 `target_rtp_ppm=95` 的配置必须被拒绝 |
|
||
| 阶段概率 | 任一阶段概率和不是 100% 时必须被拒绝 |
|
||
| 旧字段隔离 | 新规则表不再使用 `gift_id` 表示 `pool_id` |
|
||
| 版本不可变 | 发布新配置只新增 `rule_version`,不覆盖历史版本 |
|
||
| 测试 | `go test ./services/activity-service/internal/service/luckygift ./services/activity-service/internal/storage/mysql ./server/admin/internal/modules/luckygift` 通过 |
|
||
|
||
第一步完成后,系统只具备“正确配置和发布规则”的能力,不要求新抽奖引擎上线。
|