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

18 lines
19 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>\nInvestigate \"首充定格\" (first deposit freeze) logic in slot_console, especially recharge event handling and fund/balance operations. Find:\n1. Where recharge events are processed for free credits freeze\n2. What happens on success vs failure\n3. Whether balance deduction happens before/after event processing\n4. Any transaction boundaries\n\nSearch in /Users/ray/Documents/project/www/slot/slot_console and related services. Return file paths, key method names, and code flow with line references.\n</user_query>"}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"Tracing the first-deposit freeze flow in slot_console and related services.\n\n[REDACTED]"},{"type":"tool_use","name":"updateCurrentStep","input":{"current_step":"Searching recharge freeze logic"}},{"type":"tool_use","name":"Grep","input":{"pattern":"首充|定格|freeze|FreeCredits|free_credits|first.*deposit|firstDeposit","path":"/Users/ray/Documents/project/www/slot/slot_console","-i":true}},{"type":"tool_use","name":"Grep","input":{"pattern":"recharge|充值","path":"/Users/ray/Documents/project/www/slot/slot_console","glob":"**/*FreeCredits*"}},{"type":"tool_use","name":"Grep","input":{"pattern":"recharge|Recharge","path":"/Users/ray/Documents/project/www/slot/slot_console","glob":"**/*.php","head_limit":80}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php","offset":140,"limit":320}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze|FreeCreditsFreeze|free_credits_freeze","path":"/Users/ray/Documents/project/www/slot","glob":"**/*.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"RechargeEvent","path":"/Users/ray/Documents/project/www/slot/slot_console"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"RechargeEvent|recharge.*success|充值成功","path":"/Users/ray/Documents/project/www/slot/slot_console","glob":"**/*.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_lib/src/services/WalletService.php","offset":170,"limit":50}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":560,"limit":120}},{"type":"tool_use","name":"Grep","input":{"pattern":"wallet_amount|biz_id|recharge","path":"/Users/ray/Documents/project/www/slot/slot_pay","glob":"**/*.php","head_limit":40}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"new RechargeEvent|RechargeEvent::","path":"/Users/ray/Documents/project/www/slot","glob":"**/*.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_pay/app/command/EventRecharge.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"sendConsoleBus|recharge|wallet_amount","path":"/Users/ray/Documents/project/www/slot/slot_wallet","glob":"**/*.php","head_limit":50}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":690,"limit":90}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":990,"limit":30}},{"type":"tool_use","name":"Grep","input":{"pattern":"RechargeEvent|case 'Recharge'|'Recharge'","path":"/Users/ray/Documents/project/www/slot/slot_console","glob":"**/*.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"sendConsoleBusEvent|Recharge","path":"/Users/ray/Documents/project/www/slot/slot_lib","glob":"**/*.php","head_limit":40}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"RechargeEvent|ConsoleBus|console.*bus|QUEUE_CONSOLE","path":"/Users/ray/Documents/project/www/slot/slot_console","glob":"**/*.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze|WALLET_TYPE_FREE_CREDITS","path":"/Users/ray/Documents/project/www/slot/slot_wallet","glob":"**/*.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php","offset":1,"limit":95}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php","offset":580,"limit":90}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze","path":"/Users/ray/Documents/project/www/slot/slot_wallet","glob":"**/*"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_lib/src/services/WalletService.php","offset":1,"limit":100}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php","offset":486,"limit":55}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/tests/Support/FreeCreditsLogicHarness.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze|freeCreditsClaim","path":"/Users/ray/Documents/project/www/slot/slot_wallet"}},{"type":"tool_use","name":"Grep","input":{"pattern":"biz_id|幂等","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","head_limit":30}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_lib/src/services/WalletService.php","offset":91,"limit":80}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze|function update","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":1,"limit":120}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/controller/WalletController.php","offset":25,"limit":40}},{"type":"tool_use","name":"Grep","input":{"pattern":"addLog|biz_id","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","head_limit":25}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":850,"limit":50}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"create\\(|duplicate|biz_id","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/WalletLogService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/FreeCreditsFreezeDev.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/WalletLogService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":764,"limit":50}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"sendRecharge|PayMqService","path":"/Users/ray/Documents/project/www/slot/slot_pay","glob":"**/*.php","head_limit":30}},{"type":"tool_use","name":"Grep","input":{"pattern":"handleRecharge|FreeCredits","path":"/Users/ray/Documents/project/www/slot/slot_console/app","glob":"**/*.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_pay/app/entity/RechargeOrderEntity.php","offset":230,"limit":60}},{"type":"tool_use","name":"Grep","input":{"pattern":"sendRecharge|notify|callback","path":"/Users/ray/Documents/project/www/slot/slot_pay/app","glob":"**/*Recharge*","head_limit":25}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":896,"limit":40}},{"type":"tool_use","name":"updateCurrentStep","input":{"current_step":"Summarizing freeze flow findings","final_summary":"首充定格由 slot_pay 先入账钱包,再经 MQ 触发 slot_console 的 `RechargeEvent` → `FreeCreditsLogic::handleRecharge`;定格扣款在充值之后,通过 `freezeFirstRecharge` 内的独立 DB 事务 + 钱包 RPC `freeCreditsFreeze` 完成。console 与 wallet 为两段事务,定格失败只记日志不阻断统计。","completed_subtitle":"Mapped first-deposit freeze flow"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"# 首充定格First Deposit Freeze代码流调研\n\n## 端到端时序\n\n```mermaid\nsequenceDiagram\n participant Pay as slot_pay\n participant Wallet as slot_wallet\n participant MQ as RabbitMQ console_bus\n participant Console as slot_console\n\n Pay->>Pay: RechargeOrderEntity.notifySuccess\n Pay->>MQ: PayMqService.sendRecharge(orderId)\n Pay->>Pay: EventRecharge.updateOrder\n Pay->>Wallet: WalletService.recharge (HTTP)\n Wallet->>Wallet: WalletLogic.recharge → inc() 入账\n Wallet->>MQ: sendConsoleBus('Recharge')\n MQ->>Console: EventBus.deal\n Console->>Console: RechargeEvent.handle\n Console->>Console: FreeCreditsLogic.handleRecharge\n Console->>Wallet: freeCreditsFreeze (HTTP, 扣减定格金额)\n```\n\n**结论:充值入账发生在定格扣款之前**定格是充值成功后的异步MQ编排不是支付回调里同步做的。\n\n---\n\n## 1. 充值事件在哪里处理 Free Credits 定格?\n\n### 入口Console 总线 → `RechargeEvent`\n\n| 文件 | 方法 | 行号 | 作用 |\n|------|------|------|------|\n| `/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php` | `deal()` | 84163 | 消费 `console_bus``type=Recharge` 时动态加载 `RechargeEvent` |\n| `/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php` | `handle()` | 26120 | 调用 `FreeCreditsLogic::handleRecharge` |\n\n`EventBus` 对未知 type 的约定:`\\\\app\\\\command\\\\event\\\\{type}Event` → `Recharge` → `RechargeEvent`142152 行)。\n\n### 业务编排:`FreeCreditsLogic`\n\n| 文件 | 方法 | 行号 | 作用 |\n|------|------|------|------|\n| `/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php` | `handleRecharge()` | 158186 | 首充定格 + 档位推进主逻辑 |\n| 同上 | `freezeFirstRecharge()` | 402445 | 写玩家/档位 + 调钱包冻结 |\n| 同上 | `calcFrozenAmount()` | 4750 | `max(balanceAfter - rechargeAmount, 0)` |\n| 同上 | `advanceByRecharge()` | 494530 | 非首充或已定格后的档位解锁 |\n\n`RechargeEvent` 调用片段:\n\n```73:79:/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php\n try {\n (new FreeCreditsLogic())->handleRecharge(\n intval($busEntity->uid),\n intval($data['wallet_amount'] ?? $amount),\n strval($data['biz_id'] ?? ''),\n $isFirstRecharge\n );\n```\n\n### MQ 来源:钱包充值成功后投递\n\n| 文件 | 方法 | 行号 |\n|------|------|------|\n| `/Users/ray/Documents/project/www/slot/slot_pay/app/entity/RechargeOrderEntity.php` | `notifyStatus()` | 271272 | 支付成功 → `PayMqService::sendRecharge` |\n| `/Users/ray/Documents/project/www/slot/slot_pay/app/command/EventRecharge.php` | `updateOrder()` | 177205 | `walletService->recharge(...)` 先入账 |\n| `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php` | `recharge()` | 698729 | `inc()` 后 `sendConsoleBus('Recharge', ...)` |\n| `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php` | `sendConsoleBus()` | 9961011 | 投递 `wallet_amount`、`biz_id`、`amount` |\n\nPay 侧先入账,再发 Pay MQ钱包 `recharge()` 在入账后再发 Console MQ。\n\n---\n\n## 2. 成功 vs 失败行为\n\n### `FreeCreditsLogic::handleRecharge` 静默跳过(非异常)\n\n| 条件 | 行号 | 行为 |\n|------|------|------|\n| `uid <= 0` 或 `walletAmount <= 0` | 160162 | 直接 return |\n| 无生效活动配置 | 165168 | return |\n| 非首充且无 `player` | 179181 | return |\n| 首充但 `frozenAmount <= 0` 或已有 `frozen_amount_qf > 0` | 175177 | 不调用 `freezeFirstRecharge` |\n| 已定格玩家 | 183185 | 仅 `advanceByRecharge` |\n\n首充判定在 `RechargeEvent``recharge_count <= 0`6770 行),且在 **递增 `recharge_count` 之前** 调用定格90 行之后才更新统计)。\n\n### `freezeFirstRecharge` 失败\n\n- `Db::transaction` 内若 `freeCreditsFreeze` HTTP 抛错 → **console 活动库整段回滚**(玩家/档位不落库)。\n- `RechargeEvent` 外层 `try/catch`7382 行):只 `logger()->error`**不阻断**后续统计、黑名单、代理首充通知。\n\n### 钱包 `freeCreditsFreeze` 失败\n\n| 场景 | 文件:行 | 行为 |\n|------|---------|------|\n| `fee <= 0` | `WalletLogic.php:589591` | 返回 `false` |\n| 余额不足 | `594596` | `WalletException` |\n| 乐观锁冲突 | `603604, 609610` | `maxRetry` 最多 3 次 |\n| 其它错误 | `622625` | rollback 后抛出 |\n\n`freeCreditsFreeze` **不会**再发 `sendConsoleBus`(与 `freeCreditsClaim` 不同)。\n\n### 充值入账失败\n\n在 `EventRecharge::updateOrder`193196 行)抛异常,订单不 finish**不会**走到 Console `Recharge` 总线。\n\n---\n\n## 3. 余额扣减相对事件处理的顺序\n\n| 阶段 | 时机 | 操作 |\n|------|------|------|\n| ① | Pay `EventRecharge` 同步 | `WalletService::recharge` → `WalletLogic::recharge` → `inc()` **增加** deposit/withdraw |\n| ② | 同上,`inc()` commit 后 | `sendConsoleBus('Recharge')` |\n| ③ | Console MQ 异步 | `handleRecharge` 读 **充值后** `wallet->balance` |\n| ④ | 首充且需定格 | `calcFrozenAmount(balance, walletAmount)` ≈ 充值前免费余额 |\n| ⑤ | `freezeFirstRecharge` 末尾 | `freeCreditsFreeze` **扣减**定格金额 |\n\n定格公式\n\n```47:49:/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php\n public static function calcFrozenAmount(int $balanceAfterRecharge, int $rechargeAmount): int\n {\n return max($balanceAfterRecharge - $rechargeAmount, 0);\n```\n\n钱包扣减实现先 deposit 后 withdraw\n\n```598:611:/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php\n $deposit = min($wallet->deposit_balance, $this->requestDTO->fee);\n $withdraw = $this->requestDTO->fee - $deposit;\n $model->startTrans();\n try {\n $ok = $walletService->minus($withdraw, $deposit, 0, $wallet->version, $this->requestDTO->currency);\n // ...\n $ok = $this->addLog($this->requestDTO->fee * -1, $balance, WalletLogModel::BIZ_TYPE_FREE_CREDITS_FREEZE, WalletLogModel::WALLET_TYPE_GIFT);\n```\n\nRPC 封装:`/Users/ray/Documents/project/www/slot/slot_lib/src/services/WalletService.php` `freeCreditsFreeze()` 184187 → `POST api/wallet/update`。\n\n幂等`biz_id = free_credits_freeze:{orderId}`442 行);应用层用 `frozen_amount_qf > 0` 防重复定格。\n\n---\n\n## 4. 事务边界\n\n### A. 钱包充值(定格之前)\n\n- `WalletLogic::inc()``WalletAccountModel::startTrans` → `inc` + `addLog` → `commit`779810 行)\n- `recharge()` 里统计、`createTask`、`sendConsoleBus` 在 `inc()` 之后的 `try` 中,**失败只记日志,不回滚入账**725727 行)\n\n### B. Console 定格(跨库 + RPC\n\n```416:444:/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php\n return Db::transaction(function () use (...) {\n // 1. create/update FreeCreditsPlayerModel\n // 2. createPackages(...)\n $walletService->freeCreditsFreeze($frozenAmount, 'free_credits_freeze:' . $orderId);\n return $player;\n });\n```\n\n- **单库事务**`free_credits_player` / `free_credits_package`\n- **事务内同步 HTTP** 调 walletwallet 成功但 console commit 失败 → 存在钱包已扣、活动库无记录的风险(无分布式事务)\n\n### C. 钱包定格(独立事务)\n\n- `freeCreditsFreeze()``maxRetry` + `startTrans` → `minus` + `addLog` → `commit`580626 行)\n- 与 console 事务 **分离**\n\n### D. 档位推进 `advanceByRecharge`\n\n- 无 `Db::transaction`;多次 `save()`494530 行)\n\n### E. `RechargeEvent` 统计块\n\n- 独立 `try/catch`84118 行);与 Free Credits **解耦**\n\n---\n\n## 5. 相关文件速查\n\n| 层级 | 路径 |\n|------|------|\n| Console MQ 消费 | `slot_console/app/command/EventBus.php` |\n| 充值事件 | `slot_console/app/command/event/RechargeEvent.php` |\n| 定格编排 | `slot_console/app/api/logic/FreeCreditsLogic.php` |\n| 活动配置 | `slot_console/app/model/common/RechargeGiftConfigModel.php` |\n| 玩家/档位表 | `FreeCreditsPlayerModel.php`, `FreeCreditsPackageModel.php` |\n| Pay 充值消费者 | `slot_pay/app/command/EventRecharge.php` |\n| 钱包入账/总线 | `slot_wallet/app/api/logic/WalletLogic.php` (`recharge`, `freeCreditsFreeze`, `sendConsoleBus`) |\n| 钱包 API | `slot_wallet/app/api/controller/WalletController.php` `update()` |\n| 客户端 RPC | `slot_lib/src/services/WalletService.php` |\n| 联调命令 | `slot_console/app/command/FreeCreditsFreezeDev.php` |\n| 单测 | `slot_console/tests/Unit/FreeCreditsHandleRechargeTest.php`, `tests/Integration/FreeCreditsFirstRechargeFreezeTest.php` |\n\n---\n\n## 6. 要点摘要\n\n1. **定格唯一生产路径**Console MQ `Recharge` → `RechargeEvent` → `FreeCreditsLogic::handleRecharge` → `freezeFirstRecharge`。\n2. **充值先于定格**Pay/Wallet 先 `recharge` 入账,再通过 MQ 触发定格扣款。\n3. **定格失败**:活动库事务回滚;`RechargeEvent` 吞异常,统计仍继续。\n4. **两阶段事务**Console DB 与 Wallet DB 分开;定格 RPC 嵌在 console 事务末尾。\n5. **首充标志**UserTag `statistics_info.recharge_count <= 0`(定格前读取);钱包 `wallet.r` 用于累计充值解锁第一档(`freezeFirstRecharge` 408411 行)。\n\n[REDACTED]"}]}}