This commit is contained in:
ray zhou
2026-06-29 14:51:55 +08:00
parent 225fb2bd28
commit 2dd9f17da9
319 changed files with 29461 additions and 9412 deletions

View File

@@ -0,0 +1,236 @@
---
name: 全局活动轮次
overview: Lucky Rewards 活动配置与轮次均改为全平台唯一:`lucky_reward_config``lucky_reward_tier_config``lucky_reward_cycle` 移除 `source`;转盘奖项已迁至 `reward_pool_item`(无 sourcelegacy `lucky_reward_prize_config` 确认删除C 端仍用 `lucky_reward_player.source` 记录用户注册渠道。
todos:
- id: ddl-migration
content: 新增 lucky_reward_drop_source.sqlconfig/tier/cycle 删 source确认 legacy prize_config 已 DROP同步 lucky_reward.sql 与需求文档
status: completed
- id: config-model-service
content: LuckyRewardConfigModel 改为 findEnabled() 单例ConfigService requireEnabledConfig/resolveActiveCycle/createNextCycle 去 source 参数
status: completed
- id: cycle-model-rollover
content: LuckyRewardCycleModel 全局 findActive/maxCycleNoCycleRolloverService 去 sourceTierConfig 去 source
status: completed
- id: console-callers
content: Logic/Service 配置查询改 findEnabled();轮次查询改 findActive()player 写入仍保留 resolvedSource
status: completed
- id: admin-crud
content: slot_admin Config/Tier Logic/Validate/Controller 去 source 字段;主配置改为单例校验
status: completed
- id: tests-verify
content: 更新集成测试 seed全局 config+cycle补跨渠道同 config/cycle 用例phpunit + verify-slot-backend.sh
status: completed
isProject: false
---
# Lucky Rewards 配置与轮次全局化(不区分渠道)
## 目标与边界
| 维度 | 变更后 |
| --- | --- |
| `lucky_reward_config` | **全平台唯一**主配置,**移除 `source` 列**(表内仅保留一行或业务层强制单例) |
| `lucky_reward_tier_config` | **移除 `source` 列**,仅通过 `config_id` 关联主配置 |
| `lucky_reward_prize_config` | **已废弃**;奖项 SSOT 为 **`reward_pool` + `reward_pool_item`**`pool_code=lucky_reward`**本身无 `source`**)。若环境仍残留 legacy 表,执行 DROP**无需**对其 ALTER 删列 |
| `lucky_reward_cycle` | **全平台唯一**进行中轮次,**移除 `source` 列** |
| `lucky_reward_player.source` 等业务表 | **保留**,表示**用户注册/归属渠道**,供后台统计筛选,与活动配置无关 |
| wheel 短码 `biz_id=cycle_id` | **不变**,全渠道共用同一 `cycle_id` |
```mermaid
flowchart TB
subgraph before [现状]
ConfigA[config source=A]
ConfigB[config source=B]
ConfigA --> CycleA[cycle source=A]
ConfigB --> CycleB[cycle source=B]
end
subgraph after [改造后]
GlobalConfig[单一 lucky_reward_config]
GlobalCycle[单一 active cycle]
PlayerA[player source=渠道A]
PlayerB[player source=渠道B]
GlobalConfig --> GlobalCycle
GlobalCycle --> PlayerA
GlobalCycle --> PlayerB
end
```
与 [`00_整体技术方案.md`](docs/requirements/lucky_rewards/00_整体技术方案.md) 一致cycle 表本无 `source`;配置亦改为全局单套参数。
---
## 1. DDL 与数据迁移
新增 [`backend/slot_admin/db/lucky_reward_drop_source.sql`](backend/slot_admin/db/lucky_reward_drop_source.sql)(合并原 cycle 脚本,一次迁移三表):
### 1.1 `lucky_reward_config`
1. **合并多行配置**(若存在按渠道多行)
- 保留 **`source='default'``status=1` 的行**;若无 default 启用行则保留 `id` 最小且 `status=1` 的行
- 将其余行的 `lucky_reward_tier_config.config_id` **重指向**保留行
- 删除其余 config 行
2. **改表**
- `DROP INDEX idx_source`
- `DROP COLUMN source`
### 1.2 `lucky_reward_tier_config`
- `DROP COLUMN source`(已通过 `config_id` 关联列冗余admin Tier Logic/Validate 同步去 source 字段)
### 1.3 `lucky_reward_prize_config`legacy
> 现状:转盘奖项已迁移至 [`reward_pool.sql`](backend/slot_admin/db/reward_pool.sql) 的 `reward_pool_item`console 通过 [`LuckyRewardConfigService::listLuckyRewardPoolItems()`](slot_console/app/service/luckyReward/LuckyRewardConfigService.php) 读 `pool_code=lucky_reward`**无渠道维度**。
迁移步骤(写入 `lucky_reward_drop_source.sql` 末尾或独立段落):
1.**`lucky_reward_prize_config` 表仍存在** 且尚未迁移:先跑 [`reward_pool_migrate_lucky_reward_prize.sql`](backend/slot_admin/db/reward_pool_migrate_lucky_reward_prize.sql)(迁移脚本内 `@config_id` 改为取合并后的**唯一** config 行,不再 `WHERE source='default'`
2. 验证 `reward_pool_item` 数据完整后,执行 [`reward_pool_drop_legacy_prize_config.sql`](backend/slot_admin/db/reward_pool_drop_legacy_prize_config.sql)`DROP TABLE IF EXISTS lucky_reward_prize_config`
3. **无 PHP Model/Logic 改动**(已无 `LuckyRewardPrizeConfig*` 代码路径;后台菜单已指向 `RewardPoolItemController`
文档同步:[`04_slot_console_活动主流程方案.md`](docs/requirements/lucky_rewards/04_slot_console_活动主流程方案.md) 中将 `lucky_reward_prize_config` 表述改为 `reward_pool_item`
### 1.4 `lucky_reward_cycle`
1. 合并重复 `status=1`:保留 `id` 最大一行,其余改 `STATUS_ENDED=2`(不合并 player 行)
2. `DROP INDEX idx_source_status``DROP COLUMN source``ADD KEY idx_status (status)`
### 1.5 同步基线 DDL
- [`backend/slot_admin/db/lucky_reward.sql`](backend/slot_admin/db/lucky_reward.sql) — config / tier / cycle 去掉 `source`**不包含**已废弃的 prize_config 建表
- [`docs/requirements/lucky_rewards/02_管理后台方案.md`](docs/requirements/lucky_rewards/02_管理后台方案.md) §2.1 / §2.3 / §2.4(去掉「用 source 区分渠道」§2.2 已说明 prize 在 reward_pool
---
## 2. Model 层
### 2.1 [`LuckyRewardConfigModel`](slot_console/app/model/common/LuckyRewardConfigModel.php)console + admin 同步)
- 删除 `@property string $source`
- 删除 `findEnabledBySourceWithFallback(string $source)`
- 新增 `findEnabled(): ?self``status=STATUS_ENABLED``order id asc`,取唯一启用配置
- admin Model 去掉 search 按 source 的 scope若有
### 2.2 [`LuckyRewardTierConfigModel`](slot_console/app/model/common/LuckyRewardTierConfigModel.php)
- 删除 `@property string $source`
- `listEnabledByConfigId(int $configId)` 不变
### 2.3 [`LuckyRewardCycleModel`](slot_console/app/model/common/LuckyRewardCycleModel.php)
- 删除 `source``findActiveBySource` / `maxCycleNo(string $source)`
- 新增 `findActive(): ?self``maxCycleNo(): int`
---
## 3. Service 层slot_console
### 3.1 [`LuckyRewardConfigService`](slot_console/app/service/luckyReward/LuckyRewardConfigService.php)
| 方法 | 改造 |
| --- | --- |
| `requireEnabledConfig()` | 无参;内部 `findEnabled()`,未开启抛 `BusinessException` |
| `resolveActiveCycle(LuckyRewardConfigModel $activityConfig)` | 去掉 `$source`;查 `findActive()`;创建下一轮调 `createNextCycle($activityConfig, ...)` |
| `createNextCycle(LuckyRewardConfigModel $activityConfig, ?int $baseTimestamp = null)` | 去掉 `$source``cycle_days` 直接取自传入的**唯一** `$activityConfig`;不写 cycle.source事务 + `FOR UPDATE` 防并发双 active |
### 3.2 [`LuckyRewardCycleRolloverService`](slot_console/app/service/luckyReward/LuckyRewardCycleRolloverService.php)
- `rolloverExpiredCycle`:去掉 `$expiredCycle->source`
- 开启下一轮:`findActive()` 为空且 `findEnabled()` 非空时 `createNextCycle($activityConfig, $nowTimestamp)`
---
## 4. Logic / 其它调用点slot_console
**配置读取**:所有 `findEnabledBySourceWithFallback($resolvedSource)` / `requireEnabledConfig($source)` 改为无参全局读取。
**轮次读取**:所有 `findActiveBySource(...)` 改为 `findActive()`
**仍保留 `resolveSource($uid, $source)` 的场景**(仅写业务行渠道归属,**不**用于查配置):
- `lucky_reward_player.source`
- `lucky_reward_spin_grant.source`
- `lucky_reward_helper.source`
- `lucky_reward_spin_record.source`
- 日志 / MQ 回调 `channel_code` 字段
| 文件 | 改动要点 |
| --- | --- |
| [`LuckyRewardLogic.php`](slot_console/app/api/logic/LuckyRewardLogic.php) | enter/spin/openBox/toBalance配置无参轮次 `findActive()`player 写入仍用 `$resolvedSource` |
| [`LuckyRewardInviteLogic.php`](slot_console/app/api/logic/LuckyRewardInviteLogic.php) | invite_bind、弹窗、claim配置/轮次全局helper/player 仍写用户渠道 |
| [`LuckyRewardSupportInvitePendingService.php`](slot_console/app/service/luckyReward/LuckyRewardSupportInvitePendingService.php) | flush 时查全局 config + active cycle |
| [`ShareService.php`](slot_console/app/service/ShareService.php) | `resolveOrCreateWheelShareRow``requireEnabledConfig()` + `resolveActiveCycle($config)` |
Controller 仍向 Logic 传 `$request->userEntity->source`(供 player 归属Logic 内部不再用它查 config。
---
## 5. 管理后台slot_admin
| 文件 | 改动 |
| --- | --- |
| [`LuckyRewardConfigLogic`](backend/slot_admin/app/game/logic/LuckyRewardConfigLogic.php) | 去掉 `source` 写入与 `ensureSourceUnique`;新增 **单例校验**(全表仅允许 1 行update 除外) |
| [`LuckyRewardConfigValidate`](backend/slot_admin/app/game/validate/LuckyRewardConfigValidate.php) | save/update scene 移除 `source` |
| [`LuckyRewardConfigController`](backend/slot_admin/app/game/controller/LuckyRewardConfigController.php) | index 去掉 source 筛选options 返回 `id,status`(无 source |
| [`LuckyRewardTierConfigLogic`](backend/slot_admin/app/game/logic/LuckyRewardTierConfigLogic.php) | 去掉 `source` 写入与列表 `config_source` 展示 |
| [`LuckyRewardTierConfigValidate`](backend/slot_admin/app/game/validate/LuckyRewardTierConfigValidate.php) | 移除 `source` |
| Model PHPDoc | Config / Tier / Cycle 三表同步 |
[`LuckyRewardStatsLogic`](backend/slot_admin/app/game/logic/LuckyRewardStatsLogic.php)**不变** — 统计仍可按 `lucky_reward_player.source` 筛用户渠道。
**前端**(若 [`slot_admin_vue`](backend/slot_admin_vue) 有 Lucky Reward 配置页渠道字段):移除主配置/分层表单中的「渠道」输入与列表列(本计划标注为 admin 联调项,改 PHP 后前端需同步)。
---
## 6. 测试
[`LuckyRewardDbTestCase::seedEnabledActivity`](slot_console/tests/Integration/LuckyRewardDbTestCase.php)
- 不再按 `TEST_SOURCE` 创建独立 config改为 **更新全局唯一 config 为 ENABLED**(或 insert 仅当表空)
- cycle seed 不写 `source`;先 `findActive()` 结束旧 active
- player / helper 测试行仍可写 `source => TEST_SOURCE` 模拟用户渠道
更新所有集成测试中的 `findEnabledBySourceWithFallback` / `findActiveBySource` / config 按 source 查询。
**新增用例**:两渠道用户(不同 `player.source`)进入活动,得到相同 `config` 参数与 `cycle_id`
---
## 7. 部署顺序
1. 执行 `lucky_reward_drop_source.sql`(合并 config + 删列)
2. **同批发布** slot_console + slot_admin删列后旧代码会 SQL 报错)
3. slot_admin_vue 去渠道字段(可与后端同批或紧随其后)
---
## 8. 风险与验收
| 风险 | 处理 |
| --- | --- |
| 线上多渠道各有 config / active cycle | 迁移脚本合并为单行 config + 单行 active cycle |
| 非 default 渠道 config 有差异化参数 | 迁移保留 default 启用行;运营需事先确认 default 参数为 SSOT |
| 并发创建双 active cycle | `createNextCycle` 事务 + 行锁 |
| 后台误建第二条 config | Logic 单例校验拦截 |
**验收清单**
- 全渠道用户 `enter` 读到同一套 target_amount / cycle_days / tier
- 全渠道同一 `cycle_id`;轮转后仅一条新 active
- 后台主配置 CRUD 无渠道字段,不可新增第二条
- 统计页仍可按 **玩家 source** 筛选
- `phpunit` Lucky Rewards 集成测 + `verify-slot-backend.sh` PASS
---
## 涉及文件汇总
**DDL**`lucky_reward_drop_source.sql``lucky_reward.sql``reward_pool_migrate_lucky_reward_prize.sql`(改 config 选取逻辑)、`reward_pool_drop_legacy_prize_config.sql`、需求文档
**slot_console**`LuckyRewardConfigModel``LuckyRewardTierConfigModel``LuckyRewardCycleModel``LuckyRewardConfigService``LuckyRewardCycleRolloverService``LuckyRewardLogic``LuckyRewardInviteLogic``LuckyRewardSupportInvitePendingService``ShareService`、集成测试
**slot_admin**`LuckyRewardConfigModel``LuckyRewardTierConfigModel``LuckyRewardCycleModel``LuckyRewardConfigLogic``LuckyRewardTierConfigLogic`、对应 Validate/Controller
**无需改动(已无 source**`reward_pool` / `reward_pool_item``RewardPoolItemLogic`(转盘奖项 CRUD
**保留 source 不改**`lucky_reward_player` 及 spin/helper/record/grant 业务表;`LuckyRewardStatsLogic` 玩家渠道筛选