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,160 @@
---
name: InviteBind 规范与弹窗
overview: 重构 `LuckyRewardInviteLogic::handleInviteBind` 以符合 php-clean-code 分层写法并在有效助力成功时可靠入队邀请人「Claim Now」弹窗在线 WS 即时推送 + 离线/游戏中 HomeEvent 补弹),对齐 PRD 4.2 与子需求 F/D 文档。
todos:
- id: invite-bind-context-dto
content: 新增 InviteBindProcessContextDTOfetchUserProfile 改为返回 LuckyRewardUserProfileEntity
status: completed
- id: refactor-handle-invite-bind
content: 拆分 handleInviteBind 为编排 + private 步骤(校验/上下文/事务/弹窗/日志)
status: completed
- id: inviter-popup-prd-42
content: notifyInviterInviteSuccess成功必入队补全 PRD 4.2 payload去掉 spin_reward>0 早退
status: completed
- id: tests-docs-verify
content: 增强 InviteBind 集成测 payload 断言;更新 05 文档;跑 verify + phpunit
status: completed
isProject: false
---
# LuckyRewardInviteLogic handleInviteBind 重构与邀请人弹窗
## 现状与问题
[`handleInviteBind`](slot_console/app/api/logic/LuckyRewardInviteLogic.php) 约 160 行,单方法内堆叠:参数校验、活动/轮次解析、幂等判断、事务写 helper/grant/player、弹窗入队、日志。
**编码规范偏差(对照 `php-clean-code`**
| 问题 | 现状 |
|------|------|
| 方法过长 | `handleInviteBind` 远超 50 行建议 |
| 编排不清晰 | 校验 / 发奖 / 弹窗混在一个方法 |
| 返回值不规范 | `fetchUserProfile()` 返回 `array{nickname,avatar}`,同模块已有 [`LuckyRewardUserProfileEntity`](slot_console/app/entity/luckyReward/LuckyRewardUserProfileEntity.php)[`LuckyRewardLogic`](slot_console/app/api/logic/LuckyRewardLogic.php) 已使用) |
| 弹窗条件偏窄 | [`enqueueInviterSuccessPopup`](slot_console/app/api/logic/LuckyRewardInviteLogic.php) 在 `inviterSpinReward <= 0` 时直接 return有效助力成功但配置为 0 时不入队 |
**弹窗能力已存在但未在编排层显式表达:**
- 成功路径已调用 `enqueueInviterSuccessPopup` → [`PendingPopupService::enqueue`](slot_console/app/service/PendingPopupService.php) → 写 `user_pending_popup` + [`WsService::notifyClientPOP`](slot_console/app/service/WsService.php)(在线即时推送)
- 离线/游戏中:`HomeEvent::appendLuckyRewardHomePopups` 从待弹队列补弹([`05 邀请方案 §5`](docs/requirements/lucky_rewards/05_slot_console_邀请助力与弹窗方案.md)、[`07 待弹窗中心`](docs/requirements/lucky_rewards/07_用户定向弹窗通知机制方案.md)
- 集成测已断言 pending 记录存在([`LuckyRewardInviteBindIntegrationTest`](slot_console/tests/Integration/LuckyRewardInviteBindIntegrationTest.php)),但未覆盖「成功必入队 + payload 结构」
PRD [4.2 邀请成功](docs/requirements/转盘活动-功能需求文档.md):被邀请人注册成功后,**邀请人**收到 LUCKY YOU / 1 Free Spin / Claim Now仅回大厅弹、游戏内禁弹由客户端忽略 WS + HomeEvent flush 保证)。
```mermaid
sequenceDiagram
participant Agent as slot_agent_MQ
participant Logic as handleInviteBind
participant DB as s_common
participant Popup as PendingPopupService
participant WS as WsService
participant Home as HomeEvent
Agent->>Logic: invite_bind callback
Logic->>DB: helper + grant + player
Logic->>Popup: enqueue inviter popup
Popup->>DB: user_pending_popup
Popup->>WS: notifyClientPOP
Note over WS: 在线大厅即时弹
Home->>Popup: fetchPending on home
Popup->>WS: 补弹离线/回大厅
```
---
## 目标结构
### `handleInviteBind` 编排public读流程
```php
public function handleInviteBind(InviteBindCallbackDTO $callbackDto): InviteBindResultEntity
{
$earlyResult = $this->resolveInviteBindSkipResult($callbackDto);
if ($earlyResult !== null) {
return $earlyResult;
}
$processContext = $this->resolveInviteBindProcessContext($callbackDto);
if ($processContext instanceof InviteBindResultEntity) {
return $processContext; // activity closed 等
}
if ($this->isInviteHelperAlreadyProcessed($processContext->cycleId, $callbackDto->inviteeUid)) {
return $this->buildInviteBindResult(STATUS_ALREADY_PROCESSED, ...);
}
$this->persistInviteHelperInTransaction($callbackDto, $processContext);
$this->notifyInviterInviteSuccess($callbackDto, $processContext);
$this->logInviteBindSuccess($callbackDto, $processContext);
return $this->buildInviteBindResult(STATUS_SUCCESS, ...);
}
```
### 新增编排上下文 DTO
[`InviteBindProcessContextDTO`](slot_console/app/dto/luckyReward/InviteBindProcessContextDTO.php)readonly承载本用例多步共享字段避免 private 方法重复挂标量:
- `resolvedSource`, `cycleId`, `inviterSpinReward`, `inviteeSpinReward`
- `inviterBizId`, `inviteeBizId`, `popupBizId`
### 拆分 private 方法(均有中文 PHPDoc
| 方法 | 职责 |
|------|------|
| `resolveInviteBindSkipResult` | 参数非法 / 自邀 / 老用户 / 非 wheel 来源 → early return Entity |
| `resolveInviteBindProcessContext` | 解析活动配置与活跃轮次;失败返回 `InviteBindResultEntity` |
| `isInviteHelperAlreadyProcessed` | 封装 `LuckyRewardHelperModel::findByCycleAndInvitee` |
| `persistInviteHelperInTransaction` | 事务:写 helper、发 inviter/invitee grant、更新 player |
| `createInviteHelperRecord` | 写 helper 行 |
| `grantInviterSpinForHelper` | inviter grant + player spin/valid_invite_count |
| `grantInviteeSpinForHelper` | invitee grant + player spin |
| `notifyInviterInviteSuccess` | **PRD 4.2**:有效助力成功后入队 + WS委托 `PendingPopupService` |
| `logInviteBindSuccess` | 成功业务日志 |
### 弹窗行为调整PRD 4.2
- **成功即入队**`notifyInviterInviteSuccess``STATUS_SUCCESS` 路径**始终**调用(去掉 `inviterSpinReward <= 0` 早退);`payload.spin_reward` 仍传配置值(可为 0
- **payload 对齐前端**(在现有字段基础上补全,不破坏已对接字段):
- 保留:`invitee_uid`, `invitee_nickname`, `invitee_avatar`, `spin_reward`, `cycle_id`
- 新增PRD 文案,便于客户端少写死):`title`=`LUCKY YOU!`, `content`=`You've received 1 Free Spin`, `button_text`=`Claim Now`, `priority`
- **在线推送**:继续复用 `PendingPopupService::enqueue`(内部已 WSLogic 不重复调 `WsService`
- **离线/游戏中**:依赖已有 `user_pending_popup` + `HomeEvent::appendLuckyRewardHomePopups`,无需 Logic 判断在线
### 用户资料返回 Entity
- `fetchUserProfile(int $uid): LuckyRewardUserProfileEntity` 替换 array 返回
- `enqueueInviterSuccessPopup` / `buildSupportInvitePopup` 同步改用 Entity 字段(后者可顺带改,范围小)
---
## 测试
| 用例 | 文件 |
|------|------|
| 现有 early-return 单测 | [`LuckyRewardInviteLogicUnitTest`](slot_console/tests/Unit/LuckyRewardInviteLogicUnitTest.php) 保持 |
| 集成:成功写 helper + pending popup | 已有;**增强**断言 `popup_type``payload``title/content/spin_reward` |
| 集成:第二次回调幂等不入队新 popup | 已有 `already_processed`;确认 popup 仍仅 1 条 |
运行:`RUN_DB_TESTS=1``LuckyRewardInviteBindIntegrationTest` + 现有 Unit 测。
---
## 文档
- 更新 [`docs/requirements/lucky_rewards/05_slot_console_邀请助力与弹窗方案.md`](docs/requirements/lucky_rewards/05_slot_console_邀请助力与弹窗方案.md) §5 payload 示例(若新增 title/content 字段)
- 无需改 plan 文件
---
## 不在本次范围
- `buildSupportInvitePopup` 改返回 Entity可选后续本次仅改其用到的 `fetchUserProfile`
- 服务端精确判断「是否在大厅/是否在线」07 文档一期不做,依赖 WS + home flush
- slot_hub / 客户端弹窗 UI 实现
---
## 自检
- `verify-slot-backend.sh` + docker `php -l`
- 对照 Logic §3/§8方法长度、中文 PHPDoc、无 Logic 返回 array、编排 DTO 不双份拷贝