161 lines
8.0 KiB
Markdown
161 lines
8.0 KiB
Markdown
---
|
||
name: InviteBind 规范与弹窗
|
||
overview: 重构 `LuckyRewardInviteLogic::handleInviteBind` 以符合 php-clean-code 分层写法,并在有效助力成功时可靠入队邀请人「Claim Now」弹窗(在线 WS 即时推送 + 离线/游戏中 HomeEvent 补弹),对齐 PRD 4.2 与子需求 F/D 文档。
|
||
todos:
|
||
- id: invite-bind-context-dto
|
||
content: 新增 InviteBindProcessContextDTO;fetchUserProfile 改为返回 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`(内部已 WS);Logic 不重复调 `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 不双份拷贝
|