--- todos: - id: "phase0-prep" content: "Phase 0:确认 minus() 死代码、补 PHPDoc、更新 README 入口索引" status: pending - id: "phase1-base-query-register" content: "Phase 1:新建 AbstractWalletMutationLogic + WalletQueryLogic + WalletRegisterLogic,WalletLogic 委托 register/query" status: pending - id: "phase2-bet-win" content: "Phase 2:新建 WalletBetWinLogic,迁移 bet/win 及 Redis round 辅助逻辑" status: pending - id: "phase3-recharge" content: "Phase 3:新建 WalletRechargeLogic,迁移 inc/recharge/reward 与 Trial Pool 充值链" status: pending - id: "phase4-withdraw-trial" content: "Phase 4:新建 WalletWithdrawLogic + WalletTrialLogic,修复 switchBonus 路由(如需要)" status: pending isProject: false --- # WalletLogic 分阶段拆分方案 ## 现状诊断 [`slot-wallet/app/api/logic/WalletLogic.php`](slot-wallet/app/api/logic/WalletLogic.php) 当前 **1885 行**,承担了钱包服务几乎全部资金用例编排: | 域 | 主要方法 | 约行数 | 已有下沉 | |---|---|---|---| | 路由门面 | `run`, `runRechargeOrder`, `ACTION_METHOD_MAP` | ~80 | — | | 注册 | `register`, `initWalletWithoutMoney` | ~120 | [`RegisterService`](slot-wallet/app/service/wallet/RegisterService.php) | | 下注/派奖 | `bet`, `win`, pending win Redis, bet split | ~450 | [`BetService`](slot-wallet/app/service/wallet/BetService.php), [`WinService`](slot-wallet/app/service/wallet/WinService.php) | | 入账/充值 | `inc`, `recharge`, `rechargeSign`, `reward`, Trial Pool | ~400 | [`RechargeFundLotService`](slot-wallet/app/service/wallet/RechargeFundLotService.php) | | 提现/Bonus 转换 | `withdraw*`, `rollbackWithdraw`, `bonusToDeposit`, `switchBonus` | ~350 | [`WithdrawTaskService`](slot-wallet/app/service/wallet/WithdrawTaskService.php) | | Trial 体验金 | `grantTrial`, `expireTrial` | ~250 | — | | 查询 | `query`, `queryBizLog` (static) | ~70 | — | | 公共能力 | `addLog`, `maxRetry`, `sendConsoleBus`, `getBalance` 等 | ~200 | — | **问题**:单文件混合 6+ 业务域,事务边界、幂等、Redis 辅助状态交织,Review 和改动风险都高。 **已有良好先例**:同目录 [`GrantLogic`](slot-wallet/app/api/logic/GrantLogic.php)、[`PlayerTaskLogic`](slot-wallet/app/api/logic/PlayerTaskLogic.php) 已是「一用例一 Logic」;核心扣款/派奖细节已在 Service 层,Logic 层再拆是合理下一步。 **额外发现**(非本次必做):`minus()` 未出现在 `ACTION_METHOD_MAP` 且无调用方,疑似历史遗留,可在 Phase 0 确认后删除。 --- ## 目标架构 ```mermaid flowchart TB subgraph controller [Controller 不变] WC[WalletController] end subgraph facade [门面层] WL[WalletLogic] end subgraph domain [子 Logic 按域] WQ[WalletQueryLogic] WRg[WalletRegisterLogic] WBw[WalletBetWinLogic] WRe[WalletRechargeLogic] WWd[WalletWithdrawLogic] WTr[WalletTrialLogic] end subgraph shared [共享] Base[AbstractWalletMutationLogic] end subgraph service [已有 Service 不动] BS[BetService] WS[WinService] RS[RegisterService] end WC --> WL WL --> WRg & WBw & WRe & WWd & WTr GrantLogic --> WQ WL --> WQ WRg & WBw & WRe & WWd & WTr --> Base WBw --> BS & WS WRg --> RS ``` **原则**(对齐 [`backend-layering`](.cursor/rules/backend-layering.mdc)): - **Logic 拆用例、Service 保公共能力**:不再新建「只做 Model 转发」的 Service。 - **WalletLogic 只做分发 + 缓存清理**:保留 `run()` / `runRechargeOrder()` 对外签名;`ACTION_METHOD_MAP` 改为指向子 Logic 方法。 - **Controller 不改**:继续只注入 `WalletLogic`(你已确认)。 - **共享状态收敛**:子 Logic 继承抽象基类,统一持有 `WalletUpdateRequestDTO`;`rechargeOrderDto` 仅留在 `WalletRechargeLogic`。 --- ## 共享基类设计 新建 [`slot-wallet/app/api/logic/AbstractWalletMutationLogic.php`](slot-wallet/app/api/logic/AbstractWalletMutationLogic.php): ```php abstract class AbstractWalletMutationLogic { protected WalletUpdateRequestDTO $requestDTO; /** 绑定本次 mutation 请求上下文 */ protected function bindRequest(WalletUpdateRequestDTO $requestDTO): void { ... } /** 写 wallet_log、PWA 流水、乐观锁重试、Console Bus 等 */ protected function addLog(...): int { ... } protected function addTransactionRecord(...): void { ... } protected function getBalance(WalletAccountModel $model): int { ... } protected function maxRetry(callable $callback, int $retryCount = 3): WalletEntity { ... } protected function sendConsoleBus(string $type, int $amount): void { ... } protected function initWalletWithoutMoney(): ?WalletAccountModel { ... } } ``` - 从现有 `WalletLogic` **原样迁移**上述 protected/private 方法,避免行为变化。 - 子 Logic 方法签名改为显式接收 DTO 或在门面 `bindRequest()` 后调用,**不再依赖 `$this->requestDTO` 隐式全局状态跨文件散落**。 --- ## 子 Logic 划分 | 新类 | 职责 | 从 WalletLogic 迁出的方法 | |---|---|---| | `WalletQueryLogic` | 只读查询 | `query`, `queryBizLog` | | `WalletRegisterLogic` | 注册赠金 | `register` | | `WalletBetWinLogic` | 游戏下注/派奖编排 | `bet`, `win`, `executeTrialBet/Win`, `executeFreeSpinPaidBet`, pending win / bet split Redis, `getWinAllocationByRound`, `buildBetSplitRemark` | | `WalletRechargeLogic` | 入账/充值 | `inc`, `executeIncWithTransaction`, `recharge`, `rechargeSign`, `reward`, Trial Pool 扣减链, `resolveRechargeOrderDtoForLots`, `insertRechargeFundLotsIfNeeded` | | `WalletWithdrawLogic` | 提现与 Bonus 桶转换 | `withdraw`, `withdrawFrozen`, `rollbackWithdraw`, `doneWithdraw`, `bonusToDeposit`, `switchBonus` | | `WalletTrialLogic` | Trial 体验金生命周期 | `grantTrial`, `expireTrial`, `resolveTrialAmount`, `makeTrialPoolBizId`, `buildTrialPoolRemark`, `deductTrialPoolInRechargeTransaction`(若仍被 Recharge 调用则保留在 Recharge 或抽 `TrialPoolSupport` trait) | **Trial Pool 边界**:`planTrialPoolDeductionForRecharge` / `finalizeTrialPoolAfterRechargeInc` 与充值事务强耦合,建议 **Phase 3 随 RechargeLogic 一起迁**;Trial 发放/过期独立进 `WalletTrialLogic`。 --- ## 门面 WalletLogic(拆分后 ~150 行) ```php class WalletLogic { public function __construct( private WalletRegisterLogic $registerLogic, private WalletBetWinLogic $betWinLogic, private WalletRechargeLogic $rechargeLogic, private WalletWithdrawLogic $withdrawLogic, private WalletTrialLogic $trialLogic, ) {} public function run(WalletUpdateRequestDTO $requestDTO) { /* 分发 + deleteWalletCache */ } public function runRechargeOrder(WalletRechargeRequestDTO $dto) { /* 委托 rechargeLogic */ } // 兼容 GrantLogic / 外部 static 调用 public static function query(...) { return WalletQueryLogic::query(...); } public static function queryBizLog(...) { return WalletQueryLogic::queryBizLog(...); } } ``` `ACTION_METHOD_MAP` 改为 **Logic 实例 + 方法名** 或 **callable 数组**,例如: ```php WalletLogModel::BIZ_TYPE_BET => [$this->betWinLogic, 'bet'], ``` Webman DI 需在 [`config/`](slot-wallet/config/) 或现有容器绑定中注册子 Logic(若项目无自动注入,门面构造函数内 `new` 亦可,与当前 `GrantLogic` 风格一致)。 --- ## 分阶段实施(你已选 phased + facade) ### Phase 0:准备(低风险) - 为 `WalletLogic` 现有 public 方法补全/核对中文 PHPDoc(拆分时会移动代码,趁此对齐 [`php-clean-code`](.cursor/rules/php-clean-code.mdc))。 - 确认 `minus()` 无调用后删除(或加 `@deprecated` 注释待下一版删)。 - 更新 [`README.md`](slot-wallet/README.md)「实现入口」一节,列出子 Logic 索引。 ### Phase 1:Query + Register + 基类 - 新建 `AbstractWalletMutationLogic`、`WalletQueryLogic`、`WalletRegisterLogic`。 - `WalletLogic::query/queryBizLog` 改为委托;[`GrantLogic`](slot-wallet/app/api/logic/GrantLogic.php) 可继续 `WalletLogic::query()` 不动。 - `register` 从 WalletLogic 迁出;`run()` 分发到 `WalletRegisterLogic`。 - **验收**:注册接口、余额查询、Grant 通知链路回归。 ### Phase 2:BetWin(最高复杂度) - 新建 `WalletBetWinLogic`,迁移 `bet`/`win` 及 Redis round 状态方法。 - `WalletController` 的 `bet`/`win` 专用入口仍走 `walletLogic->run()`,行为不变。 - **注意**:`win()` 内对 `$requestDTO->fee` 的临时修改必须在 BetWinLogic 内用 `try/finally` 保持,避免泄漏到其他域。 - **验收**:下注/派奖、Trial 阶段 bet/win、FreeSpin 代付、中间派奖 `is_end=0`、幂等 hit。 ### Phase 3:Recharge / Inc - 新建 `WalletRechargeLogic`,迁移 `inc` 全链路 + `recharge`/`rechargeSign`/`reward`。 - `runRechargeOrder` 委托给 RechargeLogic(含 `rechargeOrderDto` 生命周期)。 - **验收**:充值主链路、首充 Trial Pool 扣减、Fund Lot 落库、签到充值 `sign`。 ### Phase 4:Withdraw + Trial - 新建 `WalletWithdrawLogic`、`WalletTrialLogic`。 - 补齐 `ACTION_METHOD_MAP` 中缺失的 `switchBonus` 映射(若 slot-sdk 仍在调 `type=switchBonus`,当前可能未路由到方法——拆分时可一并修复)。 - **验收**:提现冻结/成功/失败回滚、Bonus 转 Deposit、Trial 发放/过期。 --- ## 不建议的做法 - **不要把子用例再拆成 Logic 型 Service**(如 `WithdrawLogicService`),违反分层红线。 - **不要一次性改 Controller 注入多个 Logic**,你已选 facade,保持对外稳定。 - **不要把 BetService/WinService 的业务编排再搬回 Logic 后又复制一份**——子 Logic 只做事务、幂等、流水、MQ,核心算法继续调用现有 Service。 --- ## 预期收益 - 单文件从 ~1900 行降至 **门面 ~150 行 + 各子 Logic 200–500 行**。 - 改动 bet 不再误触 recharge 事务代码,Review 范围清晰。 - 与 `GrantLogic` 风格统一,新人可按域定位代码。 - 对外 API、`WalletLogic::run()` 签名、SDK 调用路径 **零破坏**。 --- ## 风险与缓解 | 风险 | 缓解 | |---|---| | 拆分引入行为回归 | 每 Phase 独立 PR;优先复制代码再删旧代码,不做顺手重构 | | `$requestDTO` 可变状态 | 基类 `bindRequest()` + 子 Logic 不跨用例复用实例 | | Webman 构造注入 | 若无 DI,Phase 1 先在 WalletLogic 内 `new` 子 Logic,后续再接线 | | Trial Pool 跨 Recharge/Trial | Phase 3 完成 Recharge 后再动 Trial;必要时抽 `TrialPoolDeductionSupport` trait 供 Recharge 专用 | --- ## 涉及文件 **新建**(均在 `slot-wallet/app/api/logic/`): - `AbstractWalletMutationLogic.php` - `WalletQueryLogic.php` - `WalletRegisterLogic.php` - `WalletBetWinLogic.php` - `WalletRechargeLogic.php` - `WalletWithdrawLogic.php` - `WalletTrialLogic.php` **修改**: - [`WalletLogic.php`](slot-wallet/app/api/logic/WalletLogic.php) — 瘦身为门面 - [`README.md`](slot-wallet/README.md) — 更新入口索引(可选 `.cursor/rules/slot-wallet-prd-and-money.mdc` 一行指向) **不改**: - [`WalletController.php`](slot-wallet/app/api/controller/WalletController.php) - 现有 `app/service/wallet/*Service.php`