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,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 '业务IDwheel填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 复用/新建**,非永久一条
- 过期短码:注册 OKLucky 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 拦截)