19 lines
17 KiB
JSON
19 lines
17 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":"\n\n","completed_subtitle":"\n\n"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"payout|派奖|settleWin|winSettle|award|prize|Payout","glob":"*.php","-i":true}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app","pattern":"function .*[Ww]in|credit.*bet|BetWin|wallet.*win","glob":"*.php"}},{"type":"tool_use","name":"Glob","input":{"target_directory":"/Users/ray/Documents/project/www/ray/slot-wallet","glob_pattern":"**/api/**/*Wallet*.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/controller","pattern":"win|WinService|WalletWin"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/controller/WalletController.php","limit":120}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","offset":550,"limit":450}},{"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","offset":115}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/api/logic/WalletLogic.php","limit":200}},{"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/model/multi/WalletAccountModel.php","offset":230,"limit":100}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet","pattern":"WalletWinRequestDTO|SCENE_WIN|BIZ_TYPE_WIN"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/validator/Wallet2Validator.php","limit":120}},{"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/WalletBetFundingModel.php"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/doc/wallet.md","pattern":"派奖|Final|Round|wallet_bet_funding"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletFundLotModel.php","limit":280}},{"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","pattern":"generateWinTask|WagerTask.*win"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/doc/wallet.md","limit":120}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/doc/wallet.md","pattern":"派奖|§12|最终结算|Funding"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/doc/wallet.md","offset":460,"limit":120}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/ray/slot-wallet/doc/win.md","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":"wallet_game_round"}},{"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":"->inc\\("}}]}}
|
||
{"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.*inc|'inc'\\)|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":"walletLogic|WalletLogic"}}]}}
|
||
{"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"}}]}}
|
||
{"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":380,"offset":188}}]}}
|
||
{"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\\("}}]}}
|
||
{"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":"sendConsoleBus\\('win'|win.*createTask"}},{"type":"tool_use","name":"Read","input":{"limit":80,"path":"/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletLogModel.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/model/multi/WalletLogModel.php","pattern":"findByBizIdAndType","-A":25}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"updateCurrentStep","input":{"completed_subtitle":"Mapped win flow and files","current_step":"Reporting findings to user","final_summary":"派奖主路径为 `WalletController::update` + `WalletLogic::run`(`type=win`)→ `WalletLogic::win` → `WinService::execute`,配合 `BetService` 下注写入的 `wallet_bet_funding`。PRD 文案中的 `wallet_game_round`/`wallet_game_round_event` 与“多次派奖只累加、最终再分配”在本次代码中未见实现;Redis 下注比例缓存与 `minus`/`inc(win)` 分支为遗留路径。"}}]}}
|
||
{"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| Validator | `/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 | `/Users/ray/Documents/project/www/ray/slot-wallet/app/model/multi/WalletBetFundingModel.php` |\n| 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| Entity(流水) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/entity/mq/WalletLogEntity.php` |\n| 下注与 funding(派奖前置) | `/Users/ray/Documents/project/www/ray/slot-wallet/app/service/wallet/BetService.php` |\n| (文档/口径) | `/Users/ray/Documents/project/www/ray/slot-wallet/doc/wallet.md`、`/Users/ray/Documents/project/www/ray/slot-wallet/doc/win.md` |\n\n顺带:`WalletValidator`/`WalletWinRequestDTO` 中存在 `SCENE_WIN` 等与「独立 win 校验」相关内容,但与当前 **`Wallet2Validator` + `update` + `run()`** 主链路无直接引用(更像历史或未完成对接)。\n\n---\n\n## 2)各文件里与「派奖 / Final Settlement」相关的方法(按职责)\n\n- **`WalletController.php`**:`update()` — POST 校验后组装 `WalletUpdateRequestDTO`,调用 `WalletLogic::run()`;派奖走的是 **`type === 'win'`** 这一条,而不是单独 `win` Action。\n- **`Wallet2Validator.php`**:`checkFeeAndBonus()`(允许 `type=win` 且 `fee=0`、`bonus=0`)、`checkType()`(含 `BIZ_TYPE_WIN`)。\n- **`WalletLogic.php`**:`ACTION_METHOD_MAP` 将 `win` → `win()`;`run()`;**`win()`**;私有 **`addLog()`**;**`getWinAllocationByRound()`**、**`cacheBetSplitByRound()`**(与旧 Redis 下注比例、`inc()` 里 win 分支相关,见下文风险)。\n- **`WinService.php`**:**`execute()`**;**`allocatePayout()`**;**`finalizeLotAfterSettlement()`**;**`reloadLockedWallet()`**;**`writeWalletLog()`**;**`buildWinRemark()`**;**`indexLotsById()`**。\n- **`WalletService.php`**:**`win()`** → `WalletAccountModel::incWinBalance()`。\n- **`WalletAccountModel.php`**:**`incWinBalance()`**;派奖后置里会用 **`moveBonusToWithdraw`**、**`moveDepositToWithdraw`**(由 `finalizeLotAfterSettlement` 间接触发)。\n- **`WalletBetFundingModel.php`**:**`lockUnallocatedFundingByRound()`**;**`markFundingFinalAllocated()`**;**`countUnallocatedFundingByLot()`**;下注侧 **`insertFundingRows()`**(在 `BetService` 内调用)。\n- **`WalletFundLotModel.php`**:**`lockLotsByIds()`**;**`addPayoutToRemaining()`**;**`completeLot()`** / **`updateLotStatus()`** 等。\n- **`WalletLogService.php`**:**`create()`**。\n- **`BetService.php`**:**`execute()`** — 下注扣减 + 写入 **`wallet_bet_funding`**,是 `WinService` 按比例派奖的前提。\n\n---\n\n## 3)真实流程链路(从接口入参到账变落库)\n\n**约定**:HTTP 经由 Webman 自动路由到 **`WalletController::update`**(类名与方法名推导为 `/wallet/update` 一类路径,与本问题无关不写死 URL)。\n\n1. **入参**:`uid`、`currency`、`biz_id`(幂等键)、`type='win'`、`fee`(本 Round **总派奖**,一次结算口径)、`round_id`、`source`、`organization`、`bonus`、`trace_id` 等 → `Wallet2Validator::SCENE_update` → **`WalletUpdateRequestDTO`**。\n2. **`WalletLogic::run()`**:根据 `type` 调用 **`win()`**;成功后 **`WalletService::deleteWalletCache`**。\n3. **`WalletLogic::win()`**(`maxRetry` 包一层) \n - 初始化/锁定 **`wallet_account`**(`lockWallet`)。 \n - **幂等**:`WalletLogModel::findByBizIdAndType(biz_id, 'win')` 已存在则直接返回当前余额视图,不再改账。 \n - 调 **`WinService::execute($requestDTO, $wallet)`**(同一事务内)。\n4. **`WinService::execute()`**(PRD 注释为 Round Final Settlement) \n - 要求 **`round_id` 非空**;`fee < 0` 拒绝。 \n - **`WalletBetFundingModel::lockUnallocatedFundingByRound`**:锁定该 `round_id` 下仍未 `is_final_allocated` 的 funding(须先有 **`BetService::execute`** 写入的行)。 \n - **`allocatePayout`**:按各 funding `bet_amount` 占 Round 总下注比例分配 **整局总派奖 `fee`**,余数按「下注额大优先、同额 id 升序」补到 funding(与 `doc/win.md` 5.4 描述一致)。 \n - 对每条 funding:**`markFundingFinalAllocated`**(写 `allocated_payout_amount`、`is_final_allocated`、`status`)。 \n - 若 `fee > 0`:**`WalletAccountModel::incWinBalance`** 把汇总后的 withdraw / deposit(locked) / bonus(frozen_bonus) 派奖记入聚合账户(乐观锁 `version`)。 \n - 按 **`lot_id`** 汇总派奖:**`WalletFundLotModel::addPayoutToRemaining`** 把对应派奖回流到各 Lot `remaining_amount`。Withdraw 出资 **`lot_id=0`**,只记聚合 bucket,不写 Lot。 \n - **`finalizeLotAfterSettlement`**:若某 Lot 下已无未结算 funding,则按 **`current_wager` / `required_wager` / `remaining_amount` / `lot_type`** 做 **Bonus→可提现**、**Deposit 解锁→可提现**、**Completed / PlayedOut** 等;必要时 **`writeWalletLog`** 写 **`bonus_converted` / `deposit_unlocked` / `bonus_played_out`**。 \n - 返回拆分汇总、`balance_after`、`remark`、`wallet`。 \n5. **回到 `WalletLogic::win()`**: \n - **`addLog`**(经 **`WalletLogService::create`**)写 **`biz_type=win`** 的 **`wallet_log`**(金额 `fee`、`biz_id`、`trace_id`、备注中含 `round_id` 与各桶汇总等)。 \n - `commit`、`sendConsoleBus('win')`、返回 **`WalletEntity`**。\n\n**下注侧 prerequisite**(与派奖可追溯性强相关):`**WalletLogic::bet()`** → **`BetService::execute()`** → `wallet_bet_funding` 多行拆分 + 聚合 **`decBalance`** + Lot **`consumeForBet`**。\n\n---\n\n## 4)与 PRD 不一致或需注意的风险点\n\n1. **`wallet_game_round` / `wallet_game_round_event`**:在 `doc/win.md`、`doc/wallet.md` 中描述为「多次派奖只记事件并累加 `payout_amount`,FinalSettle 时再分配」。**仓库 PHP 代码中无这两张表的读写**;本次实现是:**一次 `type=win` 请求带上 Round 总派奖 `fee`,当场完成 funding 分配 + 聚合/Lot 落库**。若上游仍有多段派奖事件,需在**网关或游戏引擎侧先汇总**,再调用本接口一次 —— 与文档「每段派奖单独写 event 表」的落库模型不一致。\n2. **「单次中间派奖不替代 Round 结论」**:代码层面没有做「禁止 `fee > 0` 的多次调用」的约束;防重依赖 **`biz_id` 粒度**。若上游对**同一 Round** 多次带不同 `biz_id` 且不同 `fee` 调 win,可能造成**多头分配**。**产品/接入方必须把「最终结算」收敛为单笔 win(或等价幂等策略)**,否则与 PRD 的 Final Settlement 单点语义冲突。\n3. **Legacy 路径**:存在 **`minus()`(旧下注:聚合桶比例 + Redis `cacheBetSplitByRound`)** 且 **全仓库未发现 `minus()` 被调用**;`**inc()`** 内含 **`type === win`** 时用 Redis 比例的 **`getWinAllocationByRound`**,但 **`run()`** 对已把 `win` 映射到 **`win()`**,**不会走 `inc()` 的 win 分支**。这些是**死代码或迁移残留**,易造成读者误以为派奖仍走 Redis 比例。\n4. **校验**:`Wallet2Validator` **只强制 bet 时 `round_id` 必填**,**win 的 `round_id` 仅靠 `WinService` 运行时校验**;若在网关漏传,会得到业务错误而非校验阶段失败(体验/契约问题)。\n5. **流水完整性**:聚合派奖有一条 **`biz_type=win`** 流水;Lot 解锁/转化/PlayedOut 在 `WinService` 里可能额外写 **`WalletLogService::create`**。与 PRD「Round 须有可追溯 `round_id`/`lot_id`」相比,主 win 流水的 `remark` 为 JSON(含 `round_id`);需确认 **`WalletLogEntity` 表字段**是否单独存 `round_id`(若审计强依赖独立列,需自行对照 DDL)。\n\n---\n\n**简要结论**:当前「派奖」主实现即 **`update` → `WalletLogic::win` → `WinService`**,以 **`wallet_bet_funding` 为准做比例拆分**,更新 **`wallet_account` + `wallet_fund_lot` + `wallet_log`**;与 **`doc/win.md` 描述的 Round 事件表、`wallet_game_round*` 全流程**在本仓库尚未对齐,应以「单次 Final Settlement 请求」理解现有实现边界。\n\n[REDACTED]"}]}} |