Files
cursor/plans/全局活动轮次_0e1173f0.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

237 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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` 玩家渠道筛选