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

157
plans/Plan-0abba57d.plan.md Normal file
View File

@@ -0,0 +1,157 @@
<!-- 0abba57d-3d88-4972-ad6e-96d899cbee65 -->
---
todos:
- id: "expiry-service-mark-only"
content: "TrialBalanceExpiryService新增 markTrialExpiredIfNeeded / isTrialPlayBlocked / ensureCanLaunchGame停止对外清零编排"
status: pending
- id: "ws-connect"
content: "TrialBalanceExpiryConnectService仅打 tag + 弹窗,移除 clearExpiredTrialBalanceIfNeeded"
status: pending
- id: "entry-logic"
content: "TrialRewardEntryLogic移除全路径清零修正 trial_balance_expired 判定"
status: pending
- id: "game-launch-guard"
content: "GameLogic + GameController::login体验过期未充值时 BusinessException 拦截"
status: pending
- id: "constants-copy"
content: "TrialRewardEntryConstants更新弹窗与 launch 拒绝文案"
status: pending
- id: "verify"
content: "docker php -l + slot-backend-completion-report 自检"
status: pending
isProject: false
---
# 体验到期:仅标识、不清余额、禁止进游戏
## 背景与现状
当前 [`TrialBalanceExpiryConnectService`](slot-console/app/service/trial/TrialBalanceExpiryConnectService.php) 在 WS 连接时:
1. 调用 `clearExpiredTrialBalanceIfNeeded` → wallet `expireTrialBalance` + activity 删 `trial_reward_record`
2. 打 user tag `trial_bonus_expired`
3. 推送弹窗文案写「Trial balance has been cleared」
[`TrialRewardEntryLogic`](slot-console/app/api/logic/TrialRewardEntryLogic.php) 在 entry/status/withdraw-click 三条路径同样调用清零。
这与需求文档 [`03_wallet_fund_flow.md`](docs/requirements/trial_withdrawal/03_wallet_fund_flow.md)「不再产生 `TRIAL_BONUS_EXPIRE`」不一致。wallet 侧 [`isTrialPhase`](slot-wallet/app/service/WalletService.php) 在超期后已返回 `false`,体验金留在 `frozen_bonus` 但无法用于试玩下注;需在 **console 启动接口** 显式拦截。
## 目标行为
```mermaid
flowchart TD
subgraph wsConnect [WS连接事件]
A[未充值且体验期已过] --> B[打 user tag trial_bonus_expired]
B --> C[推送 WS 弹窗一次]
A --> D[不调用 wallet expireTrialBalance]
A --> E[不调用 activity trial-balance/expire]
end
subgraph gameLaunch [game/launch 与 game/login]
F[未充值且体验期已过] --> G[拒绝启动 BusinessException]
H[已充值或仍在体验期内] --> I[正常 launch]
end
```
| 场景 | 余额 | activity 记录 | user tag | 进游戏 |
|------|------|---------------|----------|--------|
| 体验期内、未充值 | 保留 | 保留 | 无 | 允许 |
| 体验过期、未充值 | **保留 frozen_bonus** | **保留** | 标记过期 | **拒绝** |
| 已充值 | 按首充规则 | 按首充规则 | 不处理 | 允许 |
## 实现步骤
### 1. 收敛「到期处理」到仅标识(`TrialBalanceExpiryService`
文件:[`slot-console/app/service/trial/TrialBalanceExpiryService.php`](slot-console/app/service/trial/TrialBalanceExpiryService.php)
- 新增 `markTrialExpiredIfNeeded(int $uid, string $source, string $currency, int $organization): bool`
- 已充值 → 直接返回 `false`
- `resolveWalletSnapshot` + `resolveTrialBalanceExpiry` 未过期 → `false`
- 已过期 → 仅 `markTrialBonusExpiredUserTag($uid)`,返回 `true`
- **不**调用 `expireTrialBalance`、**不**调用 `markActivityTrialBalanceExpired`
- 新增 `isTrialPlayBlocked(int $uid, string $source, string $currency, int $organization): bool`(供 launch 守卫复用):
- 逻辑:`!has_recharged && resolveTrialBalanceExpiry(...).expired`
- 新增 `ensureCanLaunchGame(...)`:当 `isTrialPlayBlocked` 为真时 `throw new BusinessException(...)`
- `clearExpiredTrialBalanceIfNeeded` 保留方法体但 **删除所有调用方**(避免误用);类注释改为「到期判定与标识」,去掉「清零」表述
- `markActivityTrialBalanceExpired` 可保留 private 方法(历史兼容),但本需求下不再被调用
### 2. 改写 WS 连接处理
文件:[`slot-console/app/service/trial/TrialBalanceExpiryConnectService.php`](slot-console/app/service/trial/TrialBalanceExpiryConnectService.php)
- 删除 `clearExpiredTrialBalanceIfNeeded``wasBalanceCleared` / `hadNoTrialBalance` 分支
- 流程简化为:
1. user tag 已标记 → return幂等防重复弹窗
2. 已充值 → return
3. 未过期 → return
4. `markTrialBonusExpiredUserTag` + `WsService::notifyClientPOP` + info 日志
- 更新类 PHPDoc「检测试玩到期打 user tag、推送弹窗不清零余额
### 3. 入口 Logic 停止清零并修正 `trial_balance_expired`
文件:[`slot-console/app/api/logic/TrialRewardEntryLogic.php`](slot-console/app/api/logic/TrialRewardEntryLogic.php)
-`buildEntryPayload` / `buildStatusPayload` / `resolveWithdrawClick` 中:
- 移除 `clearExpiredTrialBalanceIfNeeded` 调用及 `trialBalanceBeforeClear` 相关刷新逻辑
- `trial_balance_expired` 改为:`$trialExpiry['expired'] && !$walletSnapshot['has_recharged']`(不再依赖 `bonus_amount <= 0`
- 删除 private `clearExpiredTrialBalanceIfNeeded` 方法
- 移除对 [`TrialExpiredBalanceClearCommand`](slot-console/app/api/dto/TrialExpiredBalanceClearCommand.php) 的 useDTO 可保留文件以免大范围删除,但无引用)
### 4. game/launch 与 game/login 拦截
文件:[`slot-console/app/api/logic/GameLogic.php`](slot-console/app/api/logic/GameLogic.php)
- 构造注入或懒加载 `TrialBalanceExpiryService`
- `launch()` 开头调用 `ensureCanLaunchGame($dto->uid, source, currency, organization)`
- source/currency/organization 从 `GameLaunchDTO``userEntity` 读取(与 wallet 快照现有参数对齐)
- [`GameController::login`](slot-console/app/api/controller/GameController.php) 在调用 `SlotPlatformService::login` 前同样守卫deprecated 路径仍可能被 `launchByGameId` 使用)
错误处理:抛 `Webman\Exception\BusinessException`,消息使用新常量;[`api/exception/Handler`](slot-console/app/api/exception/Handler.php) 已注册,前端 `useGameLaunch` 会 toast 该 message。
### 5. 常量与文案
文件:[`slot-console/app/constants/TrialRewardEntryConstants.php`](slot-console/app/constants/TrialRewardEntryConstants.php)
- 更新 `POPUP_MESSAGE_TRIAL_BALANCE_EXPIRED`去掉「balance has been cleared」改为体验期结束、需充值后才能继续游戏英文与产品口径一致
- 新增 `GAME_LAUNCH_BLOCKED_MESSAGE`launch 拒绝文案,可与弹窗语义一致)
`CLEAR_SOURCE_*` 常量暂保留activity 接口仍存在,仅本链路不再调用)。
### 6. 不改动的服务
- **slot-wallet**:不删 `expireTrialBalance` API历史兼容本需求不再调用
- **slot-activity**`trial-balance/expire` innerapi 保留,本需求不再调用
- **slot-pwa**:按你的选择不拦截
- **lobby**:依赖 launch API 错误 message 即可,无需必改
## 关键代码对照
WS 连接当前清零调用(将删除):
```65:75:slot-console/app/service/trial/TrialBalanceExpiryConnectService.php
$clearOutcome = $this->trialBalanceExpiryService->clearExpiredTrialBalanceIfNeeded(
new TrialExpiredBalanceClearCommand(
$uid,
$channelSource,
$currency,
$organization,
$walletSnapshot,
[]
),
TrialRewardEntryConstants::CLEAR_SOURCE_WS_CONNECT
);
```
入口当前过期判定(将修改):
```112:112:slot-console/app/api/logic/TrialRewardEntryLogic.php
'trial_balance_expired' => $walletSnapshot['bonus_amount'] <= 0 && $trialExpiry['expired'],
```
## 验证要点
1. 模拟未充值用户、`wallet.created_at` 超过配置 `validity_hours`WS 连接一次 → user tag 写入、收到弹窗、**bonus 余额不变**、无 `TRIAL_BONUS_EXPIRE` 流水
2. 再次 WS 连接 → 无重复弹窗tag 幂等)
3. 同用户调用 `POST /api/game/launch` → BusinessException不返回 url
4. 同用户 `GET /api/trial-reward/entry` → `trial_balance_expired=true``trial_balance` 仍 > 0
5. 已充值用户超体验窗口 → launch 正常、WS 不弹窗
6. 收尾跑 `slot-backend-completion-report` 与 verify 脚本