16 lines
19 KiB
JSON
16 lines
19 KiB
JSON
{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\nInvestigate wallet balance operations related to \"首充定格\" / free credits freeze in slot_wallet. Find:\n1. How total balance is decreased during freeze\n2. Interaction with console service\n3. What happens if console callback/event fails\n4. Transaction and rollback behavior\n\nSearch in /Users/ray/Documents/project/www/slot/slot_wallet and /Users/ray/Documents/project/www/slot/slot_lib. Return file paths, key methods, and the fund operation sequence.\n</user_query>"}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"Investigating wallet freeze flow for first-deposit free credits — searching slot_wallet and slot_lib for freeze, console, and balance logic.\n\n[REDACTED]"},{"type":"tool_use","name":"updateCurrentStep","input":{"current_step":"Searching freeze balance flow"}},{"type":"tool_use","name":"Grep","input":{"pattern":"首充|定格|freeze|FreeCredits|free_credits|freeCredits","path":"/Users/ray/Documents/project/www/slot/slot_wallet","-i":true}},{"type":"tool_use","name":"Grep","input":{"pattern":"首充|定格|freeze|FreeCredits|free_credits|freeCredits","path":"/Users/ray/Documents/project/www/slot/slot_lib","-i":true}},{"type":"tool_use","name":"Grep","input":{"pattern":"console.*freeze|freeze.*console|FreeCredits","path":"/Users/ray/Documents/project/www/slot/slot_wallet","-i":true}}]}}
|
||
{"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":560,"limit":200}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_lib/src/services/WalletService.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze|sendConsoleBus|FREE_CREDITS","path":"/Users/ray/Documents/project/www/slot/slot_wallet"}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze|freeCreditsClaim","path":"/Users/ray/Documents/project/www/slot/slot_console"}}]}}
|
||
{"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":1,"limit":120}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":960,"limit":60}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze|getBalance|function update|maxRetry","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_console/app/api/logic/FreeCreditsLogic.php","offset":400,"limit":80}}]}}
|
||
{"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":170,"limit":100}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":895,"limit":70}},{"type":"tool_use","name":"Grep","input":{"pattern":"function minus|function inc|function wallet","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.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/service/WalletService.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"function addLog|biz_id|idempotent","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"handleRecharge|freezeFirstRecharge","path":"/Users/ray/Documents/project/www/slot/slot_console"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/controller/WalletController.php","offset":25,"limit":40}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php","offset":140,"limit":100}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":870,"limit":35}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"decBalance|incBalance|biz_id","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"function create|duplicate|uniq|已存在","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/model/multi/WalletAccountModel.php","offset":70,"limit":140}},{"type":"tool_use","name":"Grep","input":{"pattern":"RechargeEvent|QUEUE_CONSOLE|CONSOLE_BUS","path":"/Users/ray/Documents/project/www/slot/slot_console"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/scripts/dev_free_credits_freeze.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_console/app/command/EventBus.php","offset":1,"limit":120}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsFreeze|freeCreditsClaim|Recharge","path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"Recharge|RechargeEvent|freeCredits","path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php"}},{"type":"tool_use","name":"Grep","input":{"pattern":"insertData|Duplicate|1062|uniq_uid_bizid","path":"/Users/ray/Documents/project/www/slot/slot_wallet"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php","offset":115,"limit":100}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/model/SelfBaseModel.php","offset":20,"limit":40}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/tests/Unit/FreeCreditsHandleRechargeTest.php","offset":60,"limit":40}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsClaim|freeCreditsFreeze","path":"/Users/ray/Documents/project/www/slot/slot_console/app/command"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/freeCredits*.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_console"}},{"type":"tool_use","name":"Grep","input":{"pattern":"freeCreditsClaim","path":"/Users/ray/Documents/project/www/slot/slot_console"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"postAction|function update","path":"/Users/ray/Documents/project/www/slot/slot_lib/src/basic/BaseApiService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/model/multi/WalletLogModel.php","offset":35,"limit":25}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_lib/src/basic/BaseApiService.php","offset":20,"limit":50}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php","offset":220,"limit":30}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"updateCurrentStep","input":{"current_step":"Compiling freeze flow report","final_summary":"首充定格在 slot_wallet 内通过 `freeCreditsFreeze` 从 deposit+withdraw 扣减总额(先 deposit 后 withdraw),单库事务 + 乐观锁重试;**定格本身不向 Console 发 MQ**。Console 在充值成功 MQ(`Recharge`)后异步调用 `FreeCreditsLogic::freezeFirstRecharge` 发起 RPC;MQ/定格失败仅记日志且消息仍 ack,存在与充值已入账但未定格的窗口;Console DB 与钱包非分布式事务,存在跨服务不一致风险。","completed_subtitle":"Mapped freeze balance flow"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"## Executive summary\n\n**首充定格(freeze)** is a wallet RPC (`freeCreditsFreeze`) that deducts **deposit + withdraw** (not bonus). It runs **after** first recharge credits the wallet, triggered **asynchronously** by Console via the `Recharge` MQ bus. **Freeze does not call `sendConsoleBus`**. Console/event failures are mostly **log-only**; wallet and console DB are **not** one distributed transaction.\n\n---\n\n## 1. How total balance is decreased during freeze\n\n### Entry & routing\n\n| Layer | File | Method |\n|-------|------|--------|\n| HTTP client (lib) | `/Users/ray/Documents/project/www/slot/slot_lib/src/services/WalletService.php` | `freeCreditsFreeze()` → `update(WALLET_TYPE_FREE_CREDITS_FREEZE, ...)` |\n| API | `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/controller/WalletController.php` | `update()` |\n| Logic | `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php` | `run()` → `freeCreditsFreeze()` |\n\n`WalletLogic::run()` dispatches by `requestDTO->type` (`freeCreditsFreeze`), then clears wallet cache.\n\n### Balance definition & deduction split\n\n```178:181:/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php\n private function getBalance(WalletAccountModel $model): int\n {\n return $model->withdraw_balance + $model->deposit_balance;\n }\n```\n\nIn `freeCreditsFreeze()`:\n\n1. Pre-check: `getBalance($wallet) >= fee` (else `WALLET_BALANCE_NOT_ENOUGH`).\n2. Split (deposit first, same pattern as bet):\n - `deposit = min(deposit_balance, fee)`\n - `withdraw = fee - deposit`\n3. DB update: `WalletService::minus($withdraw, $deposit, 0, version, currency)` → `WalletAccountModel::decBalance()` (optimistic `version`, column guards).\n4. Log: `addLog(-fee, balance - fee, BIZ_TYPE_FREE_CREDITS_FREEZE, WALLET_TYPE_GIFT)`.\n\n**Bonus is untouched.** “Total balance” visible to the user = `deposit_balance + withdraw_balance` only.\n\n### Persistence\n\n- Account: `/Users/ray/Documents/project/www/slot/slot_wallet/app/model/multi/WalletAccountModel.php` — `decBalance()`\n- Log biz type: `/Users/ray/Documents/project/www/slot/slot_wallet/app/model/multi/WalletLogModel.php` — `BIZ_TYPE_FREE_CREDITS_FREEZE`\n- Idempotency key: `(uid, biz_id, biz_type)` unique — `biz_id` e.g. `free_credits_freeze:{orderId}`\n\n---\n\n## 2. Interaction with Console service\n\n### Trigger chain (async, not synchronous with freeze RPC)\n\n```mermaid\nsequenceDiagram\n participant Pay as Pay/Recharge caller\n participant Wallet as slot_wallet\n participant MQ as RabbitMQ console_bus\n participant Console as slot_console EventBus\n participant FC as FreeCreditsLogic\n\n Pay->>Wallet: recharge (inc deposit)\n Wallet->>Wallet: commit + addLog\n Wallet->>MQ: sendConsoleBus('Recharge')\n MQ->>Console: RechargeEvent::handle\n Console->>FC: handleRecharge(uid, wallet_amount, orderId, isFirstRecharge)\n FC->>FC: calcFrozenAmount(balance - recharge)\n FC->>FC: freezeFirstRecharge (console DB txn)\n FC->>Wallet: HTTP freeCreditsFreeze (RPC)\n```\n\n### Console orchestration\n\n| File | Role |\n|------|------|\n| `/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php` | Consumes `Recharge` bus; calls `FreeCreditsLogic::handleRecharge()` |\n| `/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php` | `handleRecharge()`, `freezeFirstRecharge()`, `calcFrozenAmount()` |\n| `/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php` | Routes `Recharge` → `RechargeEvent` (dynamic `RechargeEvent` class) |\n\n**Frozen amount formula** (`FreeCreditsLogic::calcFrozenAmount`):\n\n`frozenAmount = max(balanceAfterRecharge - rechargeAmount, 0)` \n= pre-recharge “free” balance (千分位).\n\n**`freezeFirstRecharge()`** (inside `Db::transaction`):\n\n1. Upsert `free_credits_player` (`frozen_amount_qf`, first cash, status, …)\n2. `createPackages()` (first_cash + release tiers)\n3. **`$walletService->freeCreditsFreeze($frozenAmount, 'free_credits_freeze:' . $orderId)`** — HTTP to wallet **inside** console txn (not 2PC)\n\n### Wallet → Console direction\n\n- **`freeCreditsFreeze`**: no `sendConsoleBus` (no outbound MQ from wallet for freeze).\n- **`recharge`**: `sendConsoleBus('Recharge', recharge)` → drives freeze downstream.\n- **`freeCreditsClaim`**: after commit, `sendConsoleBus('freeCreditsClaim', fee)` + `createTask()` (claim path; separate from freeze).\n\n`sendConsoleBus` implementation: `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php` — `sendConsoleBus()` → `EXCHANGE_CONSOLE` / `QUEUE_CONSOLE_BUS`.\n\n---\n\n## 3. What happens if Console callback/event fails\n\n### A. `Recharge` MQ / `RechargeEvent` fails (freeze never runs)\n\n```73:82:/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php\n try {\n (new FreeCreditsLogic())->handleRecharge(...);\n } catch (\\Throwable $e) {\n logger()->error('FreeCredits handleRecharge failed: ' . $e->getMessage(), ['uid' => $busEntity->uid]);\n }\n```\n\n- Exception is **swallowed** (logged only).\n- **`EventBus::deal()` always `$message->ack()`** even on handler errors — **no automatic retry** from ack behavior shown.\n- **Wallet recharge already committed**; user keeps full balance; **no freeze**, no console player/packages.\n\n### B. `freezeFirstRecharge` wallet RPC fails\n\n- Console `Db::transaction` **rolls back** player + packages.\n- Wallet unchanged (RPC failed or never committed).\n\n### C. Wallet freeze succeeds, Console DB commit fails\n\n- Wallet deduction **committed** (separate service/txn).\n- Console player/packages **rolled back** → **cross-service inconsistency** (money gone, no Free Credits state). No compensating rollback in wallet from this path.\n\n### D. Duplicate / retry\n\n- Wallet: same `biz_id` → unique on `wallet_log` → insert fails → wallet txn **rollback** (balance restore).\n- Console: `frozen_amount_qf <= 0` guard skips re-freeze if player already frozen.\n\n### E. `freeCreditsClaim` bus (related, not freeze)\n\n- Wallet fires `sendConsoleBus('freeCreditsClaim', …)` post-commit; failures **only logged** (same pattern as `recharge` side effects).\n- No `freeCreditsClaimEvent` class in console — EventBus default path likely logs **未知消息**; does not undo wallet claim.\n\n---\n\n## 4. Transaction and rollback behavior\n\n### slot_wallet `freeCreditsFreeze` (local ACID)\n\n```600:625:/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php\n $model->startTrans();\n try {\n $ok = $walletService->minus(...);\n ...\n $ok = $this->addLog($this->requestDTO->fee * -1, $balance, ...);\n ...\n $model->commit();\n return new WalletEntity([...]);\n } catch (Throwable $e) {\n $model->rollback();\n throw $e;\n }\n```\n\n- Wrapped in **`maxRetry()`** (default 3): retries on `WALLET_CONFLICT_ERROR` / version conflict; other `WalletException` codes fail fast.\n- `decBalance` / `addLog` must both return `1` or throws `retry` → rollback.\n- **No post-commit side effects** for freeze (unlike `freeCreditsClaim` / `recharge`).\n\n### slot_console `freezeFirstRecharge`\n\n- ThinkPHP `Db::transaction` around player + packages + wallet HTTP.\n- Wallet HTTP is **not** enrolled in that transaction → **best-effort distributed flow**, not XA.\n\n### `freeCreditsClaim` (contrast)\n\n- Wallet: txn commit first; `createTask` + `sendConsoleBus` in outer try/catch — **failures do not roll back wallet**.\n- Console `claim()`: on wallet failure, package status reverted to `READY`.\n\n---\n\n## Fund operation sequence (end-to-end)\n\n| Step | Service | Operation |\n|------|---------|-----------|\n| 1 | Pay / caller | `WalletService::recharge()` → wallet `recharge()` |\n| 2 | slot_wallet | `inc(0, fee, 0)` + recharge logs; **commit** |\n| 3 | slot_wallet | MQ `Recharge` (async) |\n| 4 | slot_console | `RechargeEvent` → `handleRecharge` |\n| 5 | slot_console | `frozenAmount = balance - walletAmount` |\n| 6 | slot_console | DB: player + packages |\n| 7 | slot_wallet | `freeCreditsFreeze`: `minus(withdraw, deposit)` + negative log; **commit** |\n| 8 | slot_console | DB commit (if step 7 OK) |\n\nLater **claim** (not freeze): Console `freeCreditsClaim` RPC → wallet `inc(0, fee)` + log → post-commit wager task + optional bus.\n\n---\n\n## Key file index\n\n**slot_wallet**\n\n- `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php` — `freeCreditsFreeze`, `freeCreditsClaim`, `recharge`, `sendConsoleBus`, `maxRetry`, `addLog`\n- `/Users/ray/Documents/project/www/slot/slot_wallet/app/api/controller/WalletController.php` — `update()`\n- `/Users/ray/Documents/project/www/slot/slot_wallet/app/service/WalletService.php` — `minus` / `inc`\n- `/Users/ray/Documents/project/www/slot/slot_wallet/app/model/multi/WalletAccountModel.php` — `decBalance` / `incBalance`\n- `/Users/ray/Documents/project/www/slot/slot_wallet/app/model/multi/WalletLogModel.php` — biz types\n- `/Users/ray/Documents/project/www/slot/slot_wallet/scripts/dev_free_credits_freeze.php` — local freeze test script\n\n**slot_lib**\n\n- `/Users/ray/Documents/project/www/slot/slot_lib/src/services/WalletService.php` — `WALLET_TYPE_FREE_CREDITS_FREEZE`, `freeCreditsFreeze()`, `freeCreditsClaim()`\n\n**slot_console** (orchestration; outside your two roots but part of the flow)\n\n- `/Users/ray/Documents/project/www/slot/slot_console/app/api/logic/FreeCreditsLogic.php`\n- `/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php`\n- `/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php`\n\n[REDACTED]"}]}} |