ok
This commit is contained in:
172
plans/转盘分享周期绑定_d7eeeacf.plan.md
Normal file
172
plans/转盘分享周期绑定_d7eeeacf.plan.md
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
name: 转盘分享周期绑定
|
||||
overview: 推荐在 `share_url` 增加通用 `biz_type` + `biz_id`(转盘绑定 `lucky_reward_cycle.id`),按当前轮次复用/新建短码;过期校验以 `lucky_reward_cycle.end_at` 为真源,invite_bind 侧对过期轮次返回 skipped 而不影响注册。
|
||||
todos:
|
||||
- id: ddl-share-url-biz
|
||||
content: share_url 增加 biz_type/biz_id + 唯一键;同步 slot_console/slot_user Model PHPDoc
|
||||
status: completed
|
||||
- id: share-service-wheel-reuse
|
||||
content: ShareService wheel 分支:按 active cycle_id 复用或新建短码
|
||||
status: completed
|
||||
- id: invite-bind-expired-check
|
||||
content: LuckyRewardInviteLogic invite_bind 校验 share 绑定 cycle 未过期且匹配 active cycle,返回 skipped_expired_cycle
|
||||
status: completed
|
||||
- id: docs-tests
|
||||
content: 更新 05 文档;单测/集成测覆盖同轮复用、跨轮新建、过期跳过
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 转盘分享链接与活动轮次绑定方案
|
||||
|
||||
## 结论(直接回答你的二选一)
|
||||
|
||||
**推荐:`biz_type` + `biz_id`(转盘 `biz_id = cycle_id`),不推荐单独加 `expire_at` 作为唯一方案。**
|
||||
|
||||
| 方案 | 优点 | 缺点 |
|
||||
|------|------|------|
|
||||
| **仅 `expire_at`** | 查询简单(`expire_at > now`) | 与 `lucky_reward_cycle.end_at` **双份真源**,易漂移;`user_agent`/`red_packet` 等 origin 语义不同,全局 `expire_at` 难统一 |
|
||||
| **`biz_type` + `biz_id`(推荐)** | 与业务轮次一一对应;**有效期以 `lucky_reward_cycle.end_at` 为准**;同轮次内复用、新轮次新建;可扩展其它活动 | 需改 `ShareService` 查询与唯一键;invite_bind 需多一步校验 |
|
||||
|
||||
可选:**可冗余 `expire_at = cycle.end_at` 仅作索引加速**,但判定仍应 join/查 `lucky_reward_cycle`,不以 `share_url.expire_at` 为 SSOT。
|
||||
|
||||
---
|
||||
|
||||
## 现状问题
|
||||
|
||||
[`ShareService::createUrl`](slot_console/app/service/ShareService.php) 通过 [`ShareUrlModel::isPlayerCreated($uid, $from)`](slot_console/app/model/ShareUrlModel.php) 按 `(creator_id, origin)` **永久复用**一条记录:
|
||||
|
||||
```php
|
||||
$model = ShareUrlModel::isPlayerCreated($this->userEntity->uid, $from);
|
||||
if ($model === false) { /* 新建 */ }
|
||||
```
|
||||
|
||||
[`LuckyRewardInviteLogic::buildInviteLink`](slot_console/app/api/logic/LuckyRewardInviteLogic.php) 每次调 `createUrl(SHARE_FROM_WHEEL)`,**新 cycle 开始后仍返回旧短码**。
|
||||
|
||||
注册侧 [`slot_user` AbstractRegisterService](slot_user/app/service/register/AbstractRegisterService.php) 只校验 `short_code` 存在,**不校验轮次**;console `handleInviteBind` 直接用**当前 active cycle** 发奖,导致**旧链接可能计入新轮次**(与「每轮转盘有有效期」不符)。
|
||||
|
||||
你已确认过期策略:**注册/agent 关系照常;Lucky Rewards 不计助力**。
|
||||
|
||||
---
|
||||
|
||||
## 推荐数据模型
|
||||
|
||||
在 [`s_common.share_url`](slot_user/app/model/ShareUrlModel.php) 增加:
|
||||
|
||||
```sql
|
||||
ALTER TABLE share_url
|
||||
ADD COLUMN biz_type varchar(32) NOT NULL DEFAULT '' COMMENT '业务类型,wheel填lucky_reward_cycle',
|
||||
ADD COLUMN biz_id bigint NOT NULL DEFAULT 0 COMMENT '业务ID,wheel填lucky_reward_cycle.id';
|
||||
|
||||
-- wheel:同一用户同一轮次唯一;其它 origin 保持 biz 为空
|
||||
ADD UNIQUE KEY uk_creator_origin_biz (creator_id, creator_type, origin, biz_type, biz_id);
|
||||
```
|
||||
|
||||
语义约定:
|
||||
|
||||
| origin | biz_type | biz_id | 行为 |
|
||||
|--------|----------|--------|------|
|
||||
| `user_agent` / `red_packet` / `user_self` | `''` | `0` | 现网永久短码,不变 |
|
||||
| `wheel` | `lucky_reward_cycle` | `cycle.id` | **每轮一条**,同轮复用 |
|
||||
|
||||
`code` 加密 JSON 可冗余写入 `cycle_id` 便于排查,但**复用查询走 DB 列**,不走解密。
|
||||
|
||||
---
|
||||
|
||||
## 核心流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as ActivityPage
|
||||
participant Console as ShareService
|
||||
participant Cycle as lucky_reward_cycle
|
||||
participant Share as share_url
|
||||
participant User as slot_user_register
|
||||
participant Logic as LuckyRewardInviteLogic
|
||||
|
||||
Client->>Console: invite-link / createShareUrl from=wheel
|
||||
Console->>Cycle: resolveActiveCycle
|
||||
Console->>Share: find uid+wheel+biz(cycle_id)
|
||||
alt 当前轮次已有短码
|
||||
Share-->>Console: 复用 short_code
|
||||
else 新轮次或无记录
|
||||
Console->>Share: insert 新 short_code + biz_id
|
||||
end
|
||||
Console-->>Client: code + land_url
|
||||
|
||||
User->>Share: invite_code 查 short_code
|
||||
User->>Logic: invite_bind share_origin=wheel
|
||||
Logic->>Share: invite_code -> biz_id(cycle_id)
|
||||
Logic->>Cycle: 校验 cycle 仍进行中且 end_at>now
|
||||
alt cycle 已结束或不匹配 active
|
||||
Logic-->>User: skipped_expired_cycle
|
||||
else
|
||||
Logic-->>User: 正常助力流程
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实现要点(按服务)
|
||||
|
||||
### 1. slot_console — 分享创建
|
||||
|
||||
改动 [`ShareService::createUrl`](slot_console/app/service/ShareService.php):
|
||||
|
||||
- **`from !== wheel`**:保持 `isPlayerCreated(uid, from)`(biz 为空)。
|
||||
- **`from === wheel`**:
|
||||
1. `LuckyRewardConfigService::requireEnabledConfig` + `resolveActiveCycle`
|
||||
2. `ShareUrlModel::findByPlayerBiz($uid, 'wheel', 'lucky_reward_cycle', $cycleId)`
|
||||
3. 命中则复用;否则新建并写入 `biz_type/biz_id`
|
||||
4. (可选)冗余 `expire_at = cycle.end_at` 仅索引
|
||||
|
||||
同步改 [`GiftController::createShareUrl`](slot_console/app/api/controller/GiftController.php) 与 [`LuckyRewardInviteLogic::buildInviteLink`](slot_console/app/api/logic/LuckyRewardInviteLogic.php) 路径(均走 ShareService,一处改即可)。
|
||||
|
||||
[`ShareUrlModel`](slot_console/app/model/ShareUrlModel.php) + [`slot_user/ShareUrlModel`](slot_user/app/model/ShareUrlModel.php) 补 `@property` 与新查询方法。
|
||||
|
||||
### 2. slot_console — invite_bind 过期校验
|
||||
|
||||
在 [`LuckyRewardInviteLogic::handleInviteBind`](slot_console/app/api/logic/LuckyRewardInviteLogic.php) 有效助力判定链中(`skipped_not_wheel` 之后)增加:
|
||||
|
||||
1. 用 `invite_code` 查 `share_url`(console 可读 `share_url` 或经 slot_user innerapi;优先 console 直读同库 `s_common.share_url`)
|
||||
2. 若 `origin=wheel` 且 `biz_type=lucky_reward_cycle`:
|
||||
- 查 `lucky_reward_cycle` by `biz_id`
|
||||
- 若 `status=已结束` 或 `end_at <= now()` → 返回新 status **`skipped_expired_cycle`**(注册/agent 不受影响,仅不发 Spin)
|
||||
- 若 `biz_id != 当前 active cycle.id` → 同上(旧轮短码不计新轮助力)
|
||||
|
||||
在 [`InviteBindResultEntity`](slot_console/app/entity/luckyReward/InviteBindResultEntity.php) 增加常量 + 文档表更新([05 文档](docs/requirements/lucky_rewards/05_slot_console_邀请助力与弹窗方案.md) §3 判定表)。
|
||||
|
||||
### 3. slot_user — 无需阻断注册
|
||||
|
||||
按你的选择:**不在注册阶段 reject invite_code**。`AbstractRegisterService` 可不改,或仅补日志(`share_url.biz_id` + cycle 状态)便于排障。
|
||||
|
||||
### 4. 文档
|
||||
|
||||
更新 [05_slot_console_邀请助力与弹窗方案.md](docs/requirements/lucky_rewards/05_slot_console_邀请助力与弹窗方案.md) §2.1:
|
||||
|
||||
- wheel 短码 **按 cycle 复用/新建**,非永久一条
|
||||
- 过期短码:注册 OK,Lucky Rewards `skipped_expired_cycle`
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
- **Unit**:`ShareService` wheel 分支 — mock cycle,`findByPlayerBiz` 命中复用 / 新 cycle 新建
|
||||
- **Integration**:同一 uid 同 cycle 两次 `invite-link` 返回相同 `code`;模拟新 cycle 返回不同 `code`
|
||||
- **Integration**:旧 cycle 短码 + invite_bind → `skipped_expired_cycle`,无 helper/grant
|
||||
|
||||
---
|
||||
|
||||
## 为何不选「只加 expire_at」
|
||||
|
||||
1. **真源重复**:cycle 结束时间已在 [`lucky_reward_cycle.end_at`](backend/slot_admin/db/lucky_reward.sql);单独 `expire_at` 需与轮转任务同步,易不一致。
|
||||
2. **无法表达「属于哪一轮」**:仅有 `expire_at` 难以区分「同用户第 N 轮 vs 第 N+1 轮」历史短码;`biz_id=cycle_id` 可精确归因与审计。
|
||||
3. **影响面**:`expire_at` 若加在全局 `share_url`,需约定 `user_agent` 等为 0/NULL;而 `biz_id` 对非 wheel 恒为 0,更清晰。
|
||||
|
||||
---
|
||||
|
||||
## 不在本次范围
|
||||
|
||||
- 修改 `user_agent` 分享逻辑
|
||||
- 前端 Copy Link 缓存策略(后端保证同 cycle 幂等即可)
|
||||
- 删除历史 cycle 的 `share_url` 行(可保留作审计;过期靠 invite_bind 拦截)
|
||||
Reference in New Issue
Block a user