18 lines
19 KiB
JSON
18 lines
19 KiB
JSON
{"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()` | 84–163 | 消费 `console_bus`;`type=Recharge` 时动态加载 `RechargeEvent` |\n| `/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php` | `handle()` | 26–120 | 调用 `FreeCreditsLogic::handleRecharge` |\n\n`EventBus` 对未知 type 的约定:`\\\\app\\\\command\\\\event\\\\{type}Event` → `Recharge` → `RechargeEvent`(142–152 行)。\n\n### 业务编排:`FreeCreditsLogic`\n\n| 文件 | 方法 | 行号 | 作用 |\n|------|------|------|------|\n| `/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php` | `handleRecharge()` | 158–186 | 首充定格 + 档位推进主逻辑 |\n| 同上 | `freezeFirstRecharge()` | 402–445 | 写玩家/档位 + 调钱包冻结 |\n| 同上 | `calcFrozenAmount()` | 47–50 | `max(balanceAfter - rechargeAmount, 0)` |\n| 同上 | `advanceByRecharge()` | 494–530 | 非首充或已定格后的档位解锁 |\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()` | 271–272 | 支付成功 → `PayMqService::sendRecharge` |\n| `/Users/ray/Documents/project/www/slot/slot_pay/app/command/EventRecharge.php` | `updateOrder()` | 177–205 | `walletService->recharge(...)` 先入账 |\n| `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php` | `recharge()` | 698–729 | `inc()` 后 `sendConsoleBus('Recharge', ...)` |\n| `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php` | `sendConsoleBus()` | 996–1011 | 投递 `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` | 160–162 | 直接 return |\n| 无生效活动配置 | 165–168 | return |\n| 非首充且无 `player` | 179–181 | return |\n| 首充但 `frozenAmount <= 0` 或已有 `frozen_amount_qf > 0` | 175–177 | 不调用 `freezeFirstRecharge` |\n| 已定格玩家 | 183–185 | 仅 `advanceByRecharge` |\n\n首充判定在 `RechargeEvent`:`recharge_count <= 0`(67–70 行),且在 **递增 `recharge_count` 之前** 调用定格(90 行之后才更新统计)。\n\n### `freezeFirstRecharge` 失败\n\n- `Db::transaction` 内若 `freeCreditsFreeze` HTTP 抛错 → **console 活动库整段回滚**(玩家/档位不落库)。\n- `RechargeEvent` 外层 `try/catch`(73–82 行):只 `logger()->error`,**不阻断**后续统计、黑名单、代理首充通知。\n\n### 钱包 `freeCreditsFreeze` 失败\n\n| 场景 | 文件:行 | 行为 |\n|------|---------|------|\n| `fee <= 0` | `WalletLogic.php:589–591` | 返回 `false` |\n| 余额不足 | `594–596` | `WalletException` |\n| 乐观锁冲突 | `603–604, 609–610` | `maxRetry` 最多 3 次 |\n| 其它错误 | `622–625` | rollback 后抛出 |\n\n`freeCreditsFreeze` **不会**再发 `sendConsoleBus`(与 `freeCreditsClaim` 不同)。\n\n### 充值入账失败\n\n在 `EventRecharge::updateOrder`(193–196 行)抛异常,订单不 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()` 184–187 → `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`(779–810 行)\n- `recharge()` 里统计、`createTask`、`sendConsoleBus` 在 `inc()` 之后的 `try` 中,**失败只记日志,不回滚入账**(725–727 行)\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** 调 wallet;wallet 成功但 console commit 失败 → 存在钱包已扣、活动库无记录的风险(无分布式事务)\n\n### C. 钱包定格(独立事务)\n\n- `freeCreditsFreeze()`:`maxRetry` + `startTrans` → `minus` + `addLog` → `commit`(580–626 行)\n- 与 console 事务 **分离**\n\n### D. 档位推进 `advanceByRecharge`\n\n- 无 `Db::transaction`;多次 `save()`(494–530 行)\n\n### E. `RechargeEvent` 统计块\n\n- 独立 `try/catch`(84–118 行);与 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` 408–411 行)。\n\n[REDACTED]"}]}} |