15 lines
15 KiB
JSON
15 lines
15 KiB
JSON
{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\n请在仓库 /Users/ray/Documents/project/www/ray/slot-wallet 只读探索当前“派奖(win/payout)逻辑”的主实现方式,重点找:Controller入口、Logic编排、Service处理、Model落库。请返回:1) 关键文件路径列表;2) 每个文件里与派奖相关的方法名;3) 真实流程链路(从接口入参到账变落库);4) 任何与PRD不一致或风险点(若有)。不要做修改。\n</user_query>"}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"正在只读检索仓库中与派奖(win/payout)相关的控制器、逻辑、服务与模型。\n\n[REDACTED]"},{"type":"tool_use","name":"updateCurrentStep","input":{"current_step":"Searching win/payout code paths","final_summary":"","completed_subtitle":""}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"payout|派奖|win(Settle|Out)?|settleWin|win_settle|slots.*win","glob":"*.php","-i":true}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app","pattern":"Win|Payout|Settle","glob":"*Controller*.php"}},{"type":"tool_use","name":"Glob","input":{"target_directory":"/Users/ray/Documents/project/www/ray/slot-wallet","glob_pattern":"**/*Win*.php"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api","pattern":"function win|->win\\(|WalletLogic.*win","glob":"*.php"}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/Wallet*Controller*.php","target_directory":"/Users/ray/Documents/project/www/ray/slot-wallet"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","offset":580,"limit":400}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/WinService.php"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/controller/WalletController.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/dto/request/wallet/WalletUpdateRequestDTO.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletAccountModel.php","offset":200,"limit":120}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletBetFundingModel.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletFundLotModel.php","offset":180,"limit":120}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","limit":120,"offset":1}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/validator/Wallet2Validator.php"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service","pattern":"create\\(|WalletLogService","glob":"*WalletLog*"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"BIZ_TYPE_WIN|'win'.*type|type.*win","glob":"*.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service/WalletLogService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service/WalletService.php","limit":50}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/doc/wallet.md","limit":80}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/doc/win.md","limit":150}},{"type":"tool_use","name":"Grep","input":{"glob":"*.php","path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"->inc\\(|WalletLogic.*inc"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletLogModel.php","limit":80}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"wallet_game_round"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","offset":970,"limit":50}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"limit":200,"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/BetService.php"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","pattern":"function bet\\("}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","offset":380,"limit":200}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"cacheBetSplitByRound"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletLogModel.php","pattern":"findByBizIdAndType"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"limit":80,"offset":160,"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"glob":"*.php","path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"minus\\(|SCENE.*minus|BIZ_TYPE.*minus"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"updateCurrentStep","input":{"completed_subtitle":"","current_step":"Summarizing payout flow and PRD gaps","final_summary":""}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"## 1) 关键文件(绝对路径)\n\n| 分层 | 路径 |\n|------|------|\n| Controller | `/Users/ray/Documents/project/www/ray/slot-wallet/app/api/controller/WalletController.php` |\n| Validate | `/Users/ray/Documents/project/www/ray/slot-wallet/app/validator/Wallet2Validator.php` |\n| DTO | `/Users/ray/Documents/project/www/ray/slot-wallet/app/api/dto/request/wallet/WalletUpdateRequestDTO.php` |\n| Logic | `/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php` |\n| Service(派奖核心) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/WinService.php` |\n| Service(账户封装) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/WalletService.php` |\n| Service(流水) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/WalletLogService.php` |\n| Model(聚合账户) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletAccountModel.php` |\n| Model(下注拆分 / Final 派奖绑定) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletBetFundingModel.php` |\n| Model(Lot / 派奖回流) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletFundLotModel.php` |\n| Model(流水 / 幂等查询) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletLogModel.php` |\n| 上游下注(写入 funding) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/BetService.php` |\n| PRD 对照 | `/Users/ray/Documents/project/www/ray/slot-wallet/doc/win.md`,`/Users/ray/Documents/project/www/ray/slot-wallet/doc/wallet.md` |\n\n---\n\n## 2) 各文件内与派奖相关的方法 / 常量\n\n- **`WalletController.php`**: `update()` — 统一入口,`type=win` 时走 Logic。\n- **`WalletLogic.php`**: `run()`;`ACTION_METHOD_MAP` 将 `win` 映射到 `win()`;**`win()`**(主路径);私有 **`addLog()`**;**`inc()`** 内对 `type === win` 的分支(Redis 按比例拆分 + `WalletService::win`);**`getWinAllocationByRound()`**、**`cacheBetSplitByRound()`**(与旧下注路径配套)。\n- **`WinService.php`**: **`execute()`**;**`allocatePayout()`**;**`finalizeLotAfterSettlement()`**;**`addPayoutToRemaining` 的调用方**;**`reloadLockedWallet()`**;**`writeWalletLog()`**;**`buildWinRemark()`**;**`indexLotsById()`**。\n- **`WalletService.php`**: **`win()`** → `WalletAccountModel::incWinBalance`。\n- **`WalletAccountModel.php`**: **`incWinBalance()`**;派奖后 Lot 终态里还会用到 **`moveBonusToWithdraw()`**、**`moveDepositToWithdraw()`**(在 `WinService` 内)。\n- **`WalletBetFundingModel.php`**: **`lockUnallocatedFundingByRound()`**;**`markFundingFinalAllocated()`**;下注侧 **`insertFundingRows()`**;**`countUnallocatedFundingByLot()`**。\n- **`WalletFundLotModel.php`**: **`lockLotsByIds()`**;**`addPayoutToRemaining()`**;**`completeLot()`**;**`updateLotStatus()`**。\n- **`WalletLogModel.php`**: **`BIZ_TYPE_WIN`**;**`findByBizIdAndType()`**(幂等);底层插入由 `WalletLogService` 调 **`insertData`**。\n- **`WalletLogService.php`**: **`create()`**。\n- **`Wallet2Validator.php`**: **`checkFeeAndBonus()`**(允许 `type=win` 且 `fee=0`);`checkRoundIdWhenBet` **仅强制 bet 传 `round_id`**。\n- **`BetService.php`**: **`execute()`** — 下注时写入 **`wallet_bet_funding`**,供 Final 派奖按 round 锁定分配。\n\n---\n\n## 3) 真实流程链路(从接口到落库)\n\n1. **HTTP**:`WalletController::update()` 读 POST,`Wallet2Validator::SCENE_UPDATE` 校验后构造 **`WalletUpdateRequestDTO`**(字段含 `type`、`uid`、`fee`(本局总派奖)、`biz_id`、`round_id`、`currency`、`trace_id` 等)。\n2. **编排**:`WalletLogic::run()` 根据 `type === 'win'` 调用 **`WalletLogic::win()`**(带 `maxRetry`)。\n3. **事务与锁**:`startTrans` → **`WalletAccountModel::lockWallet`** 锁住聚合账户行。\n4. **幂等**:**`WalletLogModel::findByBizIdAndType($biz_id, 'win')`**;若已存在则回滚事务并返回当前余额视图(不再改账)。\n5. **派奖核心 `WinService::execute()`**(仍在同一事务内):\n - 校验 **`round_id` 非空**,**`fee >= 0`**。\n - **`WalletBetFundingModel::lockUnallocatedFundingByRound`**:按 `currency + round_id` 行锁所有 **`is_final_allocated=0` 且 `status=BET_PLACED`** 的 funding(若无则报错「未找到待结算的下注资金拆分」)。\n - **`allocatePayout`**:按各 funding `bet_amount` 比例 floor 分配总派奖,余数按下注额从大到小、`id` 升序补齐(与 `doc/win.md` 5.4 描述一致)。\n - 对每条 funding:**`markFundingFinalAllocated`**(写 `allocated_payout_amount`、`is_final_allocated=1`、`status=FINAL_ALLOCATED`)。\n - 若 **`fee > 0`**:**`WalletAccountModel::incWinBalance`**(可提 / 锁定存款 / **Bonus 进 `frozen_bonus`**),必要时 **`reloadLockedWallet`**。\n - 按 Lot 汇总派奖:**`WalletFundLotModel::addPayoutToRemaining`**(把该 Lot 分到的派奖加回 `remaining_amount`)。\n - **`finalizeLotAfterSettlement`**:按 Lot 上是否还有未最终分配的 funding、打码进度、`remaining_amount` 等,可能触发 **`moveBonusToWithdraw` / `moveDepositToWithdraw` / `completeLot` / `updateLotStatus(PlayedOut)`**,并通过 **`writeWalletLog`** 写入 **`bonus_converted` / `deposit_unlocked` / `bonus_played_out`** 等流水。\n6. **主 win 流水**:回到 `WalletLogic::win()`,**`addLog` → `WalletLogService::create` → `WalletLogModel::insertData`**,`biz_type=win`,`amount=fee`,`remark` 为 JSON(`round_id`、按桶汇总、`allocations`)。\n7. **提交与旁路**:`commit` → **`sendConsoleBus('win', fee)`** → `run()` 里删钱包缓存。\n\n**上游依赖**:同一 `round_id` 上必须先有 **`BetService::execute`** 写入的 **`wallet_bet_funding`** 行;否则 Final 派奖无法锁定到 funding。\n\n---\n\n## 4) 与 PRD 的差异或风险点\n\n- **`wallet_game_round` / `wallet_game_round_event`**:`doc/win.md` 要求 Round 内多次派奖只记事件、累加 `payout_amount`,Final 才分配;当前仓库 **PHP 侧未见这两张表的实现**,中间派奖若由其他服务承接,需保证与钱包侧「单次 Final、总额 `fee`」契约一致,否则容易出现认知割裂或对账缺口。\n- **「多次派奖事件」vs 本实现**:`WinService` 在 **一次 `type=win` 调用** 就把该 round 下 **所有未分配 funding 标为 Final**,并把 **`fee` 当作 Round 总派奖**。**同一 `round_id` 第二次成功的 `win` 请求会因无未分配 funding 而失败**。这与 PRD「只在 Final Settled 分配」一致,但 **对接方若误把每次小派奖都调 `win`** 会直接踩坑。\n- **校验层**:`Wallet2Validator` **未强制 `win` 传 `round_id`**(仅 `bet` 强制);**`WinService` 运行时才报错**,属于体验/契约问题而非资金安全盲区。\n- **遗留双轨**:`WalletLogic::inc()` + **`getWinAllocationByRound`(Redis)** 与 **`WinService`(DB funding)** 是两套归属逻辑;当前 **`run()` 的 `win` 不会走 `inc()`**,但若未来有人绕过 `run()` 调用 `inc()` 且 `type=win`,可能与 funding 模型 **不一致**(Redis 与 DB 拆单不同步风险)。\n- **`fee=0` 的 Final**:校验允许 **`win` 且 `fee=0`**;`WinService` 仍会 **将 funding 全部标记为 Final 分配(金额为 0)** 并跑 Lot 终态逻辑——符合「输光/零派奖收官」时可接受,但对接方需明确这是 **终局结算语义**,不是「无操作」。\n\n---\n\n**结论**:当前主实现是 **`WalletController::update` → `WalletLogic::win` → `WinService::execute`**,账变依赖 **`WalletAccountModel::incWinBalance`**、**`WalletBetFundingModel` 终态字段**、**`WalletFundLotModel::addPayoutToRemaining` 及后续 Lot 完结/转化**,主流水为 **`wallet_log` `biz_type=win`**,并可能追加 **`bonus_converted` / `deposit_unlocked` / `bonus_played_out`** 等子流水。\n\n[REDACTED]"}]}} |