Files
ray zhou f71a5c59af ok
2026-05-29 11:21:40 +08:00

15 lines
18 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{"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|Payout|派奖|settleRound|win|Win|award|settlement","glob":"*.php","head_limit":80}},{"type":"tool_use","name":"Glob","input":{"target_directory":"/Users/ray/Documents/project/www/ray/slot-wallet","glob_pattern":"**/*Win*.php"}},{"type":"tool_use","name":"Glob","input":{"target_directory":"/Users/ray/Documents/project/www/ray/slot-wallet","glob_pattern":"**/*Settlement*.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","pattern":"WinService|wallet.*win|biz_type_win|BIZ_TYPE_WIN|updateWallet.*win","glob":"*.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/WinService.php"}},{"type":"tool_use","name":"Glob","input":{"target_directory":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api","glob_pattern":"**/*Wallet*.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/logic/WalletLogic.php","offset":1,"limit":120}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","offset":560,"limit":280}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"getWinAllocationByRound"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletAccountModel.php","offset":220,"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":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletFundLotModel.php","pattern":"function addPayout|function completeLot|function lockLots"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"limit":120,"offset":200,"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletFundLotModel.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","offset":830,"limit":200}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/BetService.php","pattern":"funding|insertFunding|round_id"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/validator/Wallet2Validator.php","limit":120}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"->inc\\(|function inc\\(|BIZ_TYPE_WIN","glob":"*.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/service/wallet/BetService.php","limit":200}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/doc/win.md","limit":120}}]}}
{"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|WalletGameRound"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","limit":160,"offset":430}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service/WalletService.php","limit":80}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/dto/request/WalletWinRequestDTO.php"}}]}}
{"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":"Glob","input":{"glob_pattern":"**/WalletLog*.php","target_directory":"/Users/ray/Documents/project/www/ray/slot-wallet/app"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"limit":120,"offset":160,"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletLogModel.php","limit":100}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/service/WalletLogService.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":"->minus\\(|function minus\\("}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"walletLogic.*minus|minus\\(\\)|'minus'"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app","pattern":"notifyClient2"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"updateCurrentStep","input":{"completed_subtitle":"Mapped win flow and files","current_step":"Report win/payout architecture","final_summary":"主入口为 `WalletController::update` → `WalletLogic::win` → `WinService::execute`(按 `wallet_bet_funding` 比例分配、`incWinBalance` / `addPayoutToRemaining`、Lot 终态);`BetService::execute` 下注写 funding 是前提。仓库内未见 `wallet_game_round` 模型;`WalletLogic::inc` 中基于 Redis 的 win 分配逻辑在 `run()` 映射下不会被 `type=win` 调用(残留路径)。`WalletWinRequestDTO` 未被引用。"}}]}}
{"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| 校验 | `/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| 下注写 fundingwin 前置) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/BetService.php` |\n| 聚合账户 Model | `/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletAccountModel.php` |\n| funding 明细 Model | `/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletBetFundingModel.php` |\n| Lot Model | `/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| 流水写入 | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/WalletLogService.php` |\n| 聚合账户封装(含 `win` | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/WalletService.php` |\n| 异步通知 | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/WalletMQService.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说明`/Users/ray/Documents/project/www/ray/slot-wallet/app/api/dto/request/WalletWinRequestDTO.php` 存在,但在 `app/` 下无引用,属于未接入的 DTO。\n\n---\n\n## 2) 各文件与派奖相关的方法名\n\n- **`WalletController.php`**`update`(统一 `WalletUpdateRequestDTO` + `WalletLogic::run`\n- **`Wallet2Validator.php`**`checkFeeAndBonus``type=win` 时允许 `fee=0 && bonus=0`)、`checkType`、`checkRoundIdWhenBet`win 不强校验 `round_id`,见下)\n- **`WalletUpdateRequestDTO.php`**:字段承载 `type/fee/biz_id/round_id/...`(无单独方法)\n- **`WalletLogic.php`**`run` → `ACTION_METHOD_MAP` 将 `win` 映射到 `win`**`win`****`addLog`****`getWinAllocationByRound`**、`cacheBetSplitByRound`(与旧 `minus`/通用 `inc` 相关,见风险)\n- **`WinService.php`****`execute`**、**`allocatePayout`**、**`finalizeLotAfterSettlement`**、**`reloadLockedWallet`**、**`writeWalletLog`**、**`buildWinRemark`**、`indexLotsById`\n- **`BetService.php`****`execute`**(插 `wallet_bet_funding`)、`buildRemark` 等与下注remark\n- **`WalletBetFundingModel.php`****`lockUnallocatedFundingByRound`**、**`markFundingFinalAllocated`**、`countUnallocatedFundingByLot`、`insertFundingRows`\n- **`WalletAccountModel.php`****`incWinBalance`**、`moveBonusToWithdraw`、`moveDepositToWithdraw`、`lockWallet` 等\n- **`WalletFundLotModel.php`****`lockLotsByIds`**、**`addPayoutToRemaining`**、`completeLot`、`updateLotStatus`、`consumeForBet`(下注侧)\n- **`WalletLogModel.php`**`BIZ_TYPE_WIN`、**`findByBizIdAndType`**\n- **`WalletLogService.php`****`create`**\n- **`WalletService.php`****`win`**(调 `WalletAccountModel::incWinBalance`\n- **`WalletMQService.php`****`notifyClient2`**\n\n---\n\n## 3) 真实流程链路(接口入参 → 账变落库)\n\n以下为当前 **`type = win`** 的实际路径(与其它 `update` 业务共用同一 Controller。\n\n1. **HTTP**`WalletController::update` 校验 `Wallet2Validator::SCENE_UPDATE`,用 POST 组装 `WalletUpdateRequestDTO`(含 `uid, currency, biz_id, fee, type=win, round_id` 等),调用 `WalletLogic::run`;成功后删缓存并发 `WalletMQService::notifyClient2`。\n\n2. **`WalletLogic::run`**:按 `type` 找到方法名 **`win`**,执行后 `WalletService::deleteWalletCache`。\n\n3. **`WalletLogic::win`**(事务 + 乐观冲突重试 `maxRetry`\n - `lockWallet` 锁定聚合账户;\n - **幂等**`WalletLogModel::findByBizIdAndType(biz_id, 'win')` 已存在则 rollback 并返回当前余额形状(不写 funding、不加钱\n - 构造 **`WinService::execute`**。\n\n4. **`WinService::execute`**(核心业务):\n - 校验 `round_id`、`fee >= 0`\n - **`WalletBetFundingModel::lockUnallocatedFundingByRound`**:锁定该 `round_id` 下 `is_final_allocated=0` 且 `STATUS_BET_PLACED` 的 funding\n - **`allocatePayout`**:按各 funding **`bet_amount` 占比** 分配 **`requestDTO->fee`**(整除 + 余数按出资额大到小顺位 +1与 PRD 「按出资分配」一致思路;\n - 对每条 funding **`markFundingFinalAllocated`**\n - 按 `fund_type` 汇总到 withdraw / deposit / **`frozen_bonus`bonus 派奖记在 `frozen_bonus` 桶)**\n - 若 `fee > 0`**`WalletAccountModel::incWinBalance`** 一次更新三桶;\n - 对 Bonus/Deposit funding 聚合到 **lot****`WalletFundLotModel::addPayoutToRemaining`**(按 Lot 回补 `remaining_amount`Withdrawable 出资不关联 Lot只进可提现桶\n - **`finalizeLotAfterSettlement`**:若该 Lot 已无未结算 funding则按 `current_wager` / `required_wager` / `remaining_amount` / `lot_type` 做 Bonus→可提现、Deposit 解锁、Completed、Bonus PlayedOut 等,并可能 **`writeWalletLog`** 写 `bonus_converted` / `deposit_unlocked` / `bonus_played_out`\n - 返回 `balance_after`、`remark`JSONround、三桶汇总、逐 funding 分配)、最新 `wallet`。\n\n5. **回到 `WalletLogic::win`**`addLog` 写入 **`biz_type=win`** 的 **`wallet_log`**(金额 `fee``balance_after``remark` 为上面 JSON`commit``sendConsoleBus('win')`;返回 `WalletEntity`。\n\n**前置依赖**:同一 `round_id` 必须先走 **`WalletLogic::bet` → `BetService::execute`**,否则 `WinService` 会因没有 funding 抛 **「未找到待结算的下注资金拆分」**。`BetService` 在同一事务链路里 **`insertFundingRows`** 写入 `wallet_bet_funding`,并 **`consumeForBet`** 更新 Lot、`decBalance` 更新聚合账户。\n\n---\n\n## 4) 与 PRD / 文档不一致或需注意的风险点\n\n1. **`doc/win.md` 中的 Round 事件表**:文档要求中间派奖累积在 **`wallet_game_round` / `wallet_game_round_event`**(多次 payout 不显式回流,仅 Final 分配)。当前 **`app/` 下未发现对应 Model/写入**;实现上是一次请求携带 **Round 总派奖 `fee`**,直接在 **`WinService` 做 Final 分配**。若产品上仍存在「多次中间派奖事件」且不汇总后再调钱包,会与文档事件模型不一致(需确认上游是否在游戏服务汇总后再调 `win`)。\n\n2. **校验层对 `win` 的 `round_id`**`Wallet2Validator::checkRoundIdWhenBet` 仅强制 **bet** 必填 `round_id`**win 为空时由 `WinService` 报错**,而不是校验阶段拦截;行为正确但接口错误反馈阶段偏后。\n\n3. **遗留两套「下注出资拆分」语义**\n - 新链路:`BetService` + **`wallet_bet_funding`**(与当前 `WinService` 对齐)。\n - 旧代码:`WalletLogic::minus` 使用 **`available_bonus`/聚合扣减** + **`cacheBetSplitByRound`**`WalletLogic::inc` 里对 **`type=win`** 的 **`getWinAllocationByRound`** 读 **Redis** 按比例加钱。**`WalletLogic::run` 已将 `win` 固定路由到 `win()`,不再走 `inc()`**,因此这段 Redis 分配对「标准 `update` + `win`」应已**不参与**;但若存在历史/旁路直接调 `inc()` 且 `type=win`,会与 `WinService` 的 DB funding 逻辑**分裂**,属于维护风险(双实现)。\n\n4. **`WinService::allocatePayout` 在 `totalBetAmount<=0` 时返回空分配**:若出现异常 funding`bet_amount` 全为 0可能导致后续 `markFundingFinalAllocated` 等逻辑与金额为 0 的边界异常;依赖数据下注路径保证 `bet_amount>0`。\n\n5. **`WalletWinRequestDTO`** 未被使用:若接入方误认为有独立 win 接口 DTO容易与 **`WalletUpdateRequestDTO`** 混淆。\n\n以上为只读梳理结论未改动任何文件。\n\n---\n\n代码锚点便于你对照\n\n```32:59:/Users/ray/Documents/project/www/ray/slot-wallet/app/api/controller/WalletController.php\n public function update(Request $request)\n {\n $op = OutputService::getInstance();\n $post = $request->post();\n\n $validate = new Wallet2Validator();\n // ...\n $requestDTO = new WalletUpdateRequestDTO($post);\n try{\n $result = $this->walletLogic->run($requestDTO);\n // ...\n WalletMQService::notifyClient2($requestDTO->uid,$requestDTO->currency,$result);\n```\n\n```609:675:/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php\n public function win()\n {\n return $this->maxRetry(function () {\n // ...\n $duplicated = $walletLogModel->findByBizIdAndType($this->requestDTO->biz_id, WalletLogModel::BIZ_TYPE_WIN);\n if (!is_null($duplicated)) {\n // ...\n }\n\n $winService = new WinService($this->requestDTO->uid);\n $winResult = $winService->execute($this->requestDTO, $wallet);\n\n $ok = $this->addLog(\n $this->requestDTO->fee,\n $winResult['balance_after'],\n WalletLogModel::BIZ_TYPE_WIN,\n WalletLogModel::WALLET_TYPE_GAME,\n $winResult['remark']\n );\n // ...\n });\n }\n```\n\n```42:119:/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/WinService.php\n public function execute(WalletUpdateRequestDTO $requestDTO, WalletAccountModel $wallet): array\n {\n // ...\n $fundings = $this->walletBetFundingModel->lockUnallocatedFundingByRound(\n $requestDTO->currency,\n $requestDTO->round_id\n );\n // ...\n $allocations = $this->allocatePayout($fundings, $requestDTO->fee);\n // ...\n $updated = $this->walletAccountModel->incWinBalance(\n $totals['withdraw'],\n $totals['deposit'],\n $totals['bonus'],\n (int)$wallet->version,\n $requestDTO->currency\n );\n // ...\n $updated = $this->walletFundLotModel->addPayoutToRemaining(\n $lotId,\n $payoutAmount,\n (int)$lot['version']\n );\n```\n\n[REDACTED]"}]}}