--- name: Lucky Reward 奖池迁移 overview: 采用全平台统一的 reward_pool / reward_pool_item,以 pool_code = lucky_reward 承载大转盘手动 Spin 奖项;外层仅 prize_type 表达类型,lucky_reward 业务参数(含 unlockInviteCount)一律进 config;逐步替换 lucky_reward_prize_config。 todos: - id: ddl-seed content: s_common 建 reward_pool / reward_pool_item(含 prize_type 外层字段),seed pool_code=lucky_reward,编写从 lucky_reward_prize_config 的迁移脚本 status: completed - id: console-draw content: slot_console:RewardPoolItemModel + ConfigService/DrawService/LuckyRewardLogic 改读 lucky_reward 奖池,Draw 读 prize_type 列 status: completed - id: admin-crud content: slot_admin:替换 LuckyRewardPrizeConfig CRUD 为 lucky_reward 奖池项管理,更新菜单权限 status: completed - id: test-docs-cleanup content: 单测/集成测、需求文档更新、删除旧 Model 与 lucky_reward_prize_config 表 status: completed isProject: false --- # Lucky Reward 奖池方案评估与迁移计划 ## 结论:`pool_code = lucky_reward` 可行,推荐做 **方向正确**:把「可复用奖池」与「活动专用抽奖/入账」拆开,比继续扩 [`lucky_reward_prize_config`](docs/requirements/lucky_rewards/02_管理后台方案.md) 更适合后续其它活动复用同一套表结构。 **与你已确认的前提对齐**: - 全平台 **一个** 奖池:`pool_code = 'lucky_reward'` - **替换** 现有 [`lucky_reward_prize_config`](slot_console/app/model/common/LuckyRewardPrizeConfigModel.php)(非并存长期双写) - **外层仅 `prize_type` 表达奖项类型**;lucky_reward 业务参数(含开放条件)**一律进 `config`**,避免个别字段外置导致表结构膨胀 ```mermaid flowchart LR subgraph admin [slot_admin] PoolCRUD[RewardPoolItem CRUD] end subgraph db [s_common] RP[reward_pool pool_code=lucky_reward] RPI["reward_pool_item prize_type + config"] end subgraph console [slot_console] Draw[LuckyRewardDrawService] Spin[LuckyRewardLogic.executeManualSpin] end PoolCRUD --> RPI RP --> RPI RPI --> Draw Draw --> Spin ``` --- ## 核心设计:`prize_type` 外层 + 业务参数全进 `config` **字段分层原则**: | 层级 | 字段 | 说明 | | --- | --- | --- | | 表通用列 | `pool_id`、`pool_code`、`item_code`、`item_name`、`prize_type`、`weight`、`display_text`、`icon_url`、`status`、`sort` | 奖池项元数据,各 pool 共用 | | `config` JSON | lucky_reward 及后续各 pool 的**业务参数** | 含开放条件、金额区间等;**禁止**再拆个别业务字段到表列 | | 禁止 | 表列 `unlock_invite_count` 等 lucky_reward 专用字段 | 与「通用奖池表 + pool 级 config 约定」冲突 | ### 1. 表字段调整(相对你初版 DDL) 原 DDL 中的 `reward_type` **改为 `prize_type`**(TINYINT,奖项命中语义)。不同 `pool_code` 可约定不同枚举;`lucky_reward` 池定义如下: | prize_type | 含义 | 抽奖 | 入账行为(与现网一致) | | --- | --- | --- | --- | | 1 | 随机金额 | 参与权重 | My Amount 增加(封顶至 target) | | 2 | 1x Spin | 参与权重 | spin_available +1 | | 3 | Cash Out | 参与权重 | playerStatus → 可提取 | | 4 | Jackpot 展示 | **不参与**(weight=0 或 Draw 过滤) | 无入账,仅转盘 UI 展示 | 常量命名沿用现有 [`LuckyRewardPrizeConfigModel::PRIZE_TYPE_*`](slot_console/app/model/common/LuckyRewardPrizeConfigModel.php),迁移到 `RewardPoolItemModel`(或共用常量类),保证 [`lucky_reward_spin_record.prize_type`](docs/requirements/lucky_rewards/02_管理后台方案.md) 与 [`DrawResultEntity::prizeType`](slot_console/app/entity/luckyReward/DrawResultEntity.php) **数值不变**。 ### 2. `lucky_reward` 池 `config` 结构(SSOT) **禁止** config 内写 `prizeType`(类型只看外层列)。按 `prize_type` 校验必填/可选字段(admin Validate + 文档): **各类型共有可选字段**(参与抽奖的类型 1/2/3 常用): | config 字段 | 说明 | | --- | --- | | `unlockInviteCount` | 开放命中所需有效邀请人数,默认 0(无需) | **各类型专有字段**: | prize_type | 专有 config 字段 | 说明 | | --- | --- | --- | | 1 随机金额 | `amountMinQf`, `amountMaxQf` | 千分位区间,min ≤ max | | 2 1x Spin | — | 可无专有字段 | | 3 Cash Out | — | 可无专有字段;开放条件用 `unlockInviteCount` | | 4 Jackpot | `displayAmountQf` | 展示用金额(千分位) | 示例: ```json // prize_type = 1,需 3 个有效邀请后才开放 { "unlockInviteCount": 3, "amountMinQf": 100, "amountMaxQf": 500 } // prize_type = 3,Cash Out 需 5 个邀请 { "unlockInviteCount": 5 } // prize_type = 4 { "displayAmountQf": 6666000 } ``` ### 3. 外层列(不进 config) 仅保留奖池项**通用**列,不含 lucky_reward 业务参数: | 字段 | 说明 | | --- | --- | | `weight` | 抽取权重 | | `display_text` / `icon_url` / `sort` | 前端展示与排序 | `reward_pool_item` 建议 DDL 片段(相对初版变更点): ```sql `prize_type` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '奖项类型,lucky_reward池见 PRIZE_TYPE_* 常量', `config` JSON NOT NULL COMMENT 'pool 业务参数,lucky_reward见文档 SSOT', -- 删除 reward_type;不新增 unlock_invite_count 等 pool 专用列 ``` ### 4. 全平台单池 所有 source 共用 `pool_code = lucky_reward` 一套转盘格子和权重;[`LuckyRewardConfigService::listEnabledPrizeConfigs($configId)`](slot_console/app/service/luckyReward/LuckyRewardConfigService.php) 改为按 `pool_code` 加载,**不再依赖** `lucky_reward_config.id`。 活动 per-source 配置(开关、周期、分层开宝箱、目标金额)不变。 ### 5. DDL 其它细节 | 项 | 建议 | | --- | --- | | 主键 | 确认 ID 策略:若无雪花发号,补 AUTO_INCREMENT | | 时间字段 | Model 层映射 `created_at`/`updated_at` | | 库 | **s_common** | | 索引 | item 表增加 `KEY idx_pool_code_status (pool_code, status)`、`KEY idx_prize_type (prize_type)` | ### 6. 常量约定 ```php /** Lucky Rewards 大转盘手动 Spin 奖池编码 */ public const POOL_CODE_LUCKY_REWARD = 'lucky_reward'; ``` `prize_type` 常量(中文注释必填)与现网 1/2/3/4 对齐。 --- ## 迁移与实现步骤 ### Phase 1 — 表结构与数据迁移 1. 在 `s_common` 执行 `reward_pool`、`reward_pool_item` DDL(`prize_type` + `config`,无 `reward_type`、无 `unlock_invite_count` 列) 2. Seed:`pool_code = lucky_reward`,`pool_name = Lucky Rewards Wheel`,`status = 1` 3. 迁移 `lucky_reward_prize_config` → `reward_pool_item`: - `prize_type` ← 旧 `prize_type`(直迁) - `weight`、`sort`、`status` 直迁 - `config`:写入 `unlockInviteCount`(← 旧 `unlock_invite_count`)+ 类型专有字段(amountMinQf/MaxQf 或 displayAmountQf) - `item_code`:建议 `lr_prize_{old_id}` 4. 验证通过后 drop 旧表(或先 rename `_bak`) ### Phase 2 — slot_console 读新池抽奖 | 文件 | 变更 | | --- | --- | | 新建 `RewardPoolModel` / `RewardPoolItemModel` | `listEnabledByPoolCode()` | | [`LuckyRewardConfigService`](slot_console/app/service/luckyReward/LuckyRewardConfigService.php) | `listLuckyRewardPoolItems()`,固定 `POOL_CODE_LUCKY_REWARD` | | [`LuckyRewardDrawService`](slot_console/app/service/luckyReward/LuckyRewardDrawService.php) | 读列 `prize_type`;过滤读 `config.unlockInviteCount`(缺省 0);随机金额读 `config.amountMinQf/MaxQf`;Jackpot(4) 仍排除 | | [`DrawResultEntity`](slot_console/app/entity/luckyReward/DrawResultEntity.php) | 增加 `poolItemId`;`prizeType` 来自 item 的 `prize_type` 列 | | [`LuckyRewardLogic::executeManualSpin`](slot_console/app/api/logic/LuckyRewardLogic.php) | 切换数据源;spin_record.prize_type 仍写 Draw 结果 | **行为不变**:随机封顶、+1 Spin、Cash Out 改状态、spin_record 写入。 ### Phase 3 — slot_admin 后台替换 CRUD | 现状 | 目标 | | --- | --- | | [`LuckyRewardPrizeConfigController`](backend/slot_admin/app/game/controller/LuckyRewardPrizeConfigController.php) 等 | Lucky Reward 奖池项管理(scoped `pool_code=lucky_reward`) | | Validate | 校验外层 `prize_type`、按类型校验 config(含 `unlockInviteCount`、金额区间)、权重 | | [`menu-lucky-rewards.sql`](backend/slot_admin/db/menu-lucky-rewards.sql) | 路由指向新 Controller | 表单:美元展示 → 写入 config 内 `_qf`;`unlockInviteCount` 表单项写入 config;`prize_type` 下拉与现网四类一致。 ### Phase 4 — 文档、测试、清理 - 更新 [`02_管理后台方案.md`](docs/requirements/lucky_rewards/02_管理后台方案.md):`reward_pool_item.prize_type` 枚举 + config 结构 SSOT - 更新 [`LuckyRewardDrawServiceTest`](slot_console/tests/Unit/LuckyRewardDrawServiceTest.php) fixture:pool item 含 `prize_type` 列 + 精简 config - 删除 `LuckyRewardPrizeConfigModel` 及旧表引用 --- ## 风险与规避 | 风险 | 规避 | | --- | --- | | 运营迁移窗口改旧表 | 迁移前冻结编辑;脚本完成后立即切读新表 | | Jackpot 无 C 端拉取 | 后台仍配 prize_type=4;后续如需动态转盘再补接口 | | 其它 pool_code 的 prize_type 语义冲突 | 按 pool 文档约定枚举;通用表结构复用,语义由 pool_code + 文档定义 | | config 误写 prizeType | Validate 拒绝;迁移脚本不写入 prizeType | | 业务字段外置导致表膨胀 | 约定:除 `prize_type` 外 lucky_reward 参数一律 config;其它 pool 同理 | --- ## 总体评价 - **赞成** `pool_code = lucky_reward` 作为大转盘唯一奖池编码。 - **采纳**:类型语义 **外层 `prize_type`**;**`unlockInviteCount` 等业务参数进 config**,与通用奖池表设计一致。 - 其它活动复用 `reward_pool_item` 时,同一 `prize_type` 数值在不同 pool 可代表不同含义,需在各自 pool 文档中定义(lucky_reward 池先用 1/2/3/4 四态)。 确认本计划后,按 Phase 1→4 落地。