# 幸运礼物重构第一步:配置契约与单位 本文档定义幸运礼物重构第一步。第一步只解决配置、单位、规则版本和后台发布校验,不实现新抽奖算法。当前处于开发阶段,不兼容旧代码,不保留旧的 `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` 通过 | 第一步完成后,系统只具备“正确配置和发布规则”的能力,不要求新抽奖引擎上线。