33 lines
47 KiB
JSON
33 lines
47 KiB
JSON
{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\nwallet服,增加一次性脚本,将所有的充值用户写入到共享Redis里面的一个集合里面,这个集合的名字,通过center服获取的。center有个sharerediskey.php里面定义共享Redis相关的Key.wallet服,后面充值都要把这个用户uid加入集合里面。\n</user_query>"}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"调研 wallet 充值流程、center 共享 Redis key 定义及现有脚本模式。\n\n[REDACTED]"},{"type":"tool_use","name":"Task","input":{"description":"Explore center sharerediskey","prompt":"Explore slot_center for sharerediskey.php and how shared Redis keys are defined and consumed. Find any existing recharge-user set keys. Return file paths, key names, and how other services read these keys from center.","subagent_type":"explore","readonly":true}},{"type":"tool_use","name":"Task","input":{"description":"Explore wallet recharge flow","prompt":"Explore slot_wallet for: (1) recharge/deposit logic where user gets credited, (2) existing one-time scripts or webman commands, (3) shared Redis usage patterns, (4) how wallet gets config from center. Return relevant file paths, class names, and code patterns for adding uid to Redis set on recharge.","subagent_type":"explore","readonly":true}},{"type":"tool_use","name":"Grep","input":{"pattern":"sharerediskey|ShareRedisKey","path":"/Users/ray/Documents/project/www/slot","output_mode":"content","head_limit":40}},{"type":"tool_use","name":"Grep","input":{"pattern":"充值|recharge|deposit","path":"/Users/ray/Documents/project/www/slot/slot_wallet","glob":"**/*.{php,md}","output_mode":"files_with_matches","head_limit":30}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_center/config/sharerediskey.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/RedisKeyManagerService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/.cursor/plans/每日返水需求文档_20a03147.plan.md","limit":120}},{"type":"tool_use","name":"Grep","input":{"pattern":"recharged|充值用户|recharge.*set|sAdd","path":"/Users/ray/Documents/project/www/slot","glob":"**/*.{php,md}","head_limit":30}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"recharged|充值用户|total_deposit","path":"/Users/ray/.cursor/plans","glob":"**/*","head_limit":30}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/model/multi/WalletStatModel.php","limit":80}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/command/CleanupWalletLog.php","limit":80}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":760,"limit":90}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/config/redis.php"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/service/user/UserRegisterEventService.php","offset":100,"limit":40}},{"type":"tool_use","name":"Grep","input":{"pattern":"wallet_stat|total_deposit","path":"/Users/ray/Documents/project/www/slot/slot_wallet","glob":"**/*","head_limit":35}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/model/multi/BaseMultiModel.php","limit":100}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/.cursor/plans/每日返水需求文档_20a03147.plan.md","offset":120,"limit":100}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"total_deposit|wallet_stat","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app","head_limit":40}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/ShardService.php","limit":120}},{"type":"tool_use","name":"Grep","input":{"pattern":"recharged|user:recharge|充值.*set","path":"/Users/ray/.cursor/plans/每日返水需求文档_20a03147.plan.md"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/command/InitDB.php","limit":100}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"database_num|table_num|wallet_\\d","path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/command","head_limit":30}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/wallet/RechargeExchangeService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/ShareConfigService.php","limit":60}},{"type":"tool_use","name":"Grep","input":{"pattern":"SISMEMBER|sIsMember|sCard|sAdd","path":"/Users/ray/Documents/project/www/slot","glob":"**/*.php","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/command/InitDB.php","offset":115,"limit":40}},{"type":"tool_use","name":"Grep","input":{"pattern":"type.*recharge|rechargeSign|BIZ_TYPE_RECHARGE","path":"/Users/ray/Documents/project/www/slot/slot_wallet","glob":"**/*.php","head_limit":25}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"total_deposit","path":"/Users/ray/Documents/project/www/slot","glob":"**/*.php","head_limit":30}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/command/Test.php","offset":145,"limit":50}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/config/app.php","limit":80}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"已理清充值流程、共享 Redis 配置与分片表结构;正在生成实现计划。\n\n[REDACTED]"},{"type":"tool_use","name":"AskQuestion","input":{"title":"充值用户集合 — 确认两点","questions":[{"id":"key_name","prompt":"center sharerediskey 中新增 key 的命名(Redis 实际 key 为 user:recharged)是否可接受?","options":[{"id":"userRecharged","label":"userRecharged => user:recharged(推荐,与 user:source / user:profit 风格一致)"},{"id":"custom","label":"我有别的命名,稍后说明"}]},{"id":"recharge_sign","prompt":"rechargeSign(签到购,会累加 total_deposit 但不走 inc 入账)是否也算「充值用户」写入集合?","options":[{"id":"yes","label":"是,recharge 与 rechargeSign 都写入"},{"id":"recharge_only","label":"仅 type=recharge 写入"}]}]}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"CreatePlan","input":{"name":"充值用户 Redis 集合","overview":"在 center 注册共享 Redis key `userRecharged`,wallet 侧一次性脚本从分片 `wallet_stat` 回填历史充值用户,并在 `recharge` / `rechargeSign` 成功后实时 SADD 到共享 Redis 集合。","plan":"# Wallet 充值用户共享 Redis 集合\n\n## 目标\n\n维护一个**全局、持久**的共享 Redis Set,成员为「曾成功充值」的用户 uid(口径:`wallet_stat.total_deposit > 0`,与 [每日返水需求](file:///Users/ray/.cursor/plans/每日返水需求文档_20a03147.plan.md) 中「充值用户」一致)。供后续活动(如每日返水结算)用 `SISMEMBER` 快速判断,避免逐用户查库。\n\n你已确认:\n- Key:`userRecharged` → Redis `user:recharged`\n- `recharge` 与 `rechargeSign` 均写入集合\n\n---\n\n## 架构与数据流\n\n```mermaid\nflowchart LR\n subgraph center [slot_center]\n Sharerediskey[\"config/sharerediskey.php\"]\n ShareConfigAPI[\"/innerapi/share-config/all\"]\n end\n subgraph wallet [slot_wallet]\n BackfillCmd[\"BackfillRechargedUidSet command\"]\n WalletLogic[\"WalletLogic::recharge / rechargeSign\"]\n RechargedSvc[\"RechargedUidSetService\"]\n StatDB[\"wallet_XX.wallet_stat_YY\"]\n end\n ShareRedis[\"Redis connection share\"]\n Sharerediskey --> ShareConfigAPI\n ShareConfigAPI --> RechargedSvc\n BackfillCmd --> StatDB\n BackfillCmd --> RechargedSvc\n WalletLogic --> RechargedSvc\n RechargedSvc --> ShareRedis\n```\n\n---\n\n## 1. Center:注册共享 Key\n\n**文件**:[`slot_center/config/sharerediskey.php`](slot_center/config/sharerediskey.php)\n\n在数组末尾增加(含注释):\n\n```php\n'userRecharged' => 'user:recharged', // set:曾充值用户 uid(wallet 维护)\n```\n\n该文件已通过 [`ShareConfigController::all()`](slot_center/app/innerapi/controller/ShareConfigController.php) 下发为 `sharerediskey.*`,各服务经 `ShareConfigService::get('sharerediskey.userRecharged')` 读取。\n\n**部署顺序**:先发布 center,再发布 wallet(否则 wallet 会落到 fallback 默认值,key 不一致)。\n\n---\n\n## 2. Wallet:Key 解析与写入封装\n\n### 2.1 `RedisKeyManagerService`\n\n**文件**:[`slot_wallet/app/service/RedisKeyManagerService.php`](slot_wallet/app/service/RedisKeyManagerService.php)\n\n新增方法(与现有 `sourceRealDataKey` 一致,从 center 取 key):\n\n```php\npublic static function getUserRechargedSetKey(): string\n{\n return ShareConfigService::get('sharerediskey.userRecharged', 'user:recharged');\n}\n```\n\n### 2.2 新建 `RechargedUidSetService`\n\n**路径**:`slot_wallet/app/service/wallet/RechargedUidSetService.php`\n\n职责(公共能力,避免 Logic 直接拼 Redis):\n\n- `add(int $uid): void` — `Redis::connection('share')->sAdd($key, (string)$uid)`\n- key 来自 `RedisKeyManagerService::getUserRechargedSetKey()`\n- 失败只 `LoggerService::error`,不抛异常(与 `WalletLogic::recharge()` 内 `sendConsoleBus` / `createTask` 等非阻断副作用一致)\n- **不设 expire**(成员为永久「曾充值」标记;`SADD` 幂等)\n\n参考:[`slot_risk`](slot_risk/app/service/api/BlackUserService.php) 对 `connection('share')->sAdd` 的用法;[`config/redis.php`](slot_wallet/config/redis.php) 已配置 `share` 连接。\n\n---\n\n## 3. 充值链路:实时写入\n\n**文件**:[`slot_wallet/app/api/logic/WalletLogic.php`](slot_wallet/app/api/logic/WalletLogic.php)\n\n在以下两处、`total_deposit` 递增且 `RechargeExchangeService::recharge()` 之后,调用 `RechargedUidSetService::add($uid)`:\n\n| 方法 | 约行号 | 说明 |\n|------|--------|------|\n| `recharge()` | ~782 后 | 正常支付充值 |\n| `rechargeSign()` | ~820 后 | 签到购,累加统计 |\n\n放在现有 `try/catch` 块内即可,无需单独 try。\n\n**不扩展范围**:全库仅这两处递增 `total_deposit`(已 grep 确认),无需 hook `inc()` 或其它 type。\n\n---\n\n## 4. 一次性回填脚本\n\n### 4.1 Webman Command\n\n**路径**:`slot_wallet/app/command/BackfillRechargedUidSet.php`\n\n- `$defaultName = 'backfillRechargedUidSet'`\n- 结构参考 [`CleanupWalletLog.php`](slot_wallet/app/command/CleanupWalletLog.php):`LoggerService::initGenerateTraceId`、stdout 汇总、异常返回 `FAILURE`\n\n**选项**:\n\n| Option | 作用 |\n|--------|------|\n| `--dry-run` | 只统计 uid 数量,不写 Redis |\n| `--rebuild` | 执行前 `DEL` 目标 set(全量重建;默认增量 SADD,可重复跑) |\n| `--batch-size` | 每批 `SADD` 成员数,默认 500 |\n\n### 4.2 扫描逻辑\n\n按 [`InitDB::initWalletStat`](slot_wallet/app/command/InitDB.php) 的分片循环:\n\n```php\nfor ($i = 1; $i <= $shardInfo['database_num']; $i++) {\n $db = sprintf('wallet_%02d', $i);\n for ($j = 1; $j <= $shardInfo['table_num']; $j++) {\n $table = \"{$db}.wallet_stat_\" . sprintf('%02d', $j);\n // SELECT DISTINCT uid FROM {$table} WHERE total_deposit > 0\n }\n}\n```\n\n- 使用 `DISTINCT uid`(表主键 `(uid, currency)`,多币种各一行,同一 uid 只入 set 一次)\n- 分批 `sAdd($key, ...$uids)` 写入 `connection('share')`\n- 输出:每表行数、总 uid 数、最终 `SCARD`\n\n### 4.3 执行命令(Docker)\n\n```bash\ndocker exec -w /app/www/slot/slot_wallet php82 php webman backfillRechargedUidSet --rebuild\n# 预检\ndocker exec -w /app/www/slot/slot_wallet php82 php webman backfillRechargedUidSet --dry-run\n```\n\n**建议上线步骤**:\n\n1. 发布 center(`userRecharged` 配置)\n2. 发布 wallet(Service + Logic + Command)\n3. 低峰执行 `backfillRechargedUidSet --rebuild`(或先 `--dry-run` 核对数量)\n4. 之后新充值由 Logic 自动 `SADD`\n\n---\n\n## 5. 测试(wallet 仓库)\n\n| 项 | 方式 |\n|----|------|\n| `RechargedUidSetService` | 单元测试 mock `Redis::connection('share')`,或集成测试用真实 share Redis(若 CI 有) |\n| `WalletLogic` | 在现有 [`WalletControllerUpdateTest`](slot_wallet/tests/Integration/WalletControllerUpdateTest.php) / recharge 相关用例后断言 set 含 uid(可选,依赖 share Redis) |\n| Command | 本地 `--dry-run` + 小环境 `--rebuild` 后 `SCARD` 抽样 `SISMEMBER` |\n\n至少保证:Service 在 key 存在时调用 `sAdd` 且不抛异常;Command SQL 与分片配置可读。\n\n---\n\n## 6. 不在本次范围(供后续)\n\n- **消费方**(如 `slot_console` 每日返水 cron)改为 `SISMEMBER`:需在对应服务增加 `ShareConfigService::get('sharerediskey.userRecharged')` + `connection('share')`,本次仅 wallet 写入。\n- center 本地 [`ShareRedisKeyManagerService`](slot_center/app/service/ShareRedisKeyManagerService.php) 可不增方法(wallet 直接用 `RedisKeyManagerService` + `ShareConfigService`,与现有 `sourceReal` 一致)。\n\n---\n\n## 涉及文件一览\n\n| 仓库 | 文件 | 变更 |\n|------|------|------|\n| slot_center | `config/sharerediskey.php` | +1 key |\n| slot_wallet | `app/service/RedisKeyManagerService.php` | +`getUserRechargedSetKey()` |\n| slot_wallet | `app/service/wallet/RechargedUidSetService.php` | 新建 |\n| slot_wallet | `app/api/logic/WalletLogic.php` | recharge / rechargeSign 各 +1 行调用 |\n| slot_wallet | `app/command/BackfillRechargedUidSet.php` | 新建一次性脚本 |\n","todos":[{"id":"center-key","content":"slot_center sharerediskey.php 增加 userRecharged => user:recharged"},{"id":"wallet-service","content":"wallet:RedisKeyManagerService + RechargedUidSetService(share Redis SADD)"},{"id":"wallet-hook","content":"WalletLogic::recharge / rechargeSign 成功后调用 RechargedUidSetService::add"},{"id":"backfill-cmd","content":"新建 backfillRechargedUidSet 命令:扫 wallet_stat 分片、--dry-run/--rebuild/--batch-size"},{"id":"tests","content":"补充 Service/集成测试或 dry-run 验证说明"}]}}]}}
|
||
{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\nWallet 充值用户共享 Redis 集合\n\nImplement the plan as specified, it is attached for your reference. Do NOT edit the plan file itself.\n\nTo-do's from the plan have already been created. Do not create them again. Mark them as in_progress as you work, starting with the first one. Don't stop until you have completed all the to-dos.\n</user_query>"}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"开始按计划实现:先将第一个待办标为进行中,并着手修改 center 与 wallet 相关代码。\n\n[REDACTED]"},{"type":"tool_use","name":"TodoWrite","input":{"todos":[{"id":"center-key","content":"slot_center sharerediskey.php 增加 userRecharged => user:recharged","status":"in_progress"}],"merge":true}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_center/config/sharerediskey.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/RedisKeyManagerService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","limit":50}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/tests/**/*Test.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_wallet"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"StrReplace","input":{"path":"/Users/ray/Documents/project/www/slot/slot_center/config/sharerediskey.php","old_string":" 'userProfit' => 'user:profit:', // 后面接日期hash结构并设定过期时间为7天,如:user:profit:20250807\n];","new_string":" 'userProfit' => 'user:profit:', // 后面接日期hash结构并设定过期时间为7天,如:user:profit:20250807\n 'userRecharged' => 'user:recharged', // set:曾充值用户 uid(wallet 维护)\n];"}},{"type":"tool_use","name":"StrReplace","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/RedisKeyManagerService.php","old_string":" public static function getRechargeExchangeKey(int $uid )\n {\n return self::RECHARGE_EXCHANGE.($uid%10);\n }\n\n}","new_string":" public static function getRechargeExchangeKey(int $uid )\n {\n return self::RECHARGE_EXCHANGE.($uid%10);\n }\n\n /**\n * 曾充值用户 uid 集合 key(共享 Redis set)\n */\n public static function getUserRechargedSetKey(): string\n {\n return ShareConfigService::get('sharerediskey.userRecharged', 'user:recharged');\n }\n\n}"}},{"type":"tool_use","name":"Write","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/service/wallet/RechargedUidSetService.php","contents":"<?php\n\nnamespace app\\service\\wallet;\n\nuse app\\service\\LoggerService;\nuse app\\service\\RedisKeyManagerService;\nuse support\\Redis;\nuse Throwable;\n\nclass RechargedUidSetService\n{\n public static function add(int $uid): void\n {\n if ($uid <= 0) {\n return;\n }\n\n try {\n $key = RedisKeyManagerService::getUserRechargedSetKey();\n Redis::connection('share')->sAdd($key, (string) $uid);\n } catch (Throwable $e) {\n LoggerService::error(__METHOD__, [\n 'uid' => $uid,\n 'message' => $e->getMessage(),\n ]);\n }\n }\n}\n"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","offset":765,"limit":70}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/tests/Unit/WalletFreeCreditInitBusTest.php","limit":50}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"StrReplace","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","old_string":"use app\\service\\wallet\\RechargeExchangeService;","new_string":"use app\\service\\wallet\\RechargedUidSetService;\nuse app\\service\\wallet\\RechargeExchangeService;"}},{"type":"tool_use","name":"StrReplace","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","old_string":" $service = new RechargeExchangeService($this->requestDTO->uid);\n $service->recharge($this->requestDTO->recharge);\n $this->sendConsoleBus('Recharge', $this->requestDTO->recharge);\n $sourceType = WagerTaskModel::SOURCE_TYPE_ACTIVITY_RECHARGE;","new_string":" $service = new RechargeExchangeService($this->requestDTO->uid);\n $service->recharge($this->requestDTO->recharge);\n RechargedUidSetService::add($this->requestDTO->uid);\n $this->sendConsoleBus('Recharge', $this->requestDTO->recharge);\n $sourceType = WagerTaskModel::SOURCE_TYPE_ACTIVITY_RECHARGE;"}},{"type":"tool_use","name":"StrReplace","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php","old_string":" $service = new RechargeExchangeService($this->requestDTO->uid);\n $service->recharge($this->requestDTO->recharge);\n $this->addTransactionRecord('recharge', $this->requestDTO->fee, $balance, 1);","new_string":" $service = new RechargeExchangeService($this->requestDTO->uid);\n $service->recharge($this->requestDTO->recharge);\n RechargedUidSetService::add($this->requestDTO->uid);\n $this->addTransactionRecord('recharge', $this->requestDTO->fee, $balance, 1);"}},{"type":"tool_use","name":"Write","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/command/BackfillRechargedUidSet.php","contents":"<?php\n\nnamespace app\\command;\n\nuse app\\service\\LoggerService;\nuse app\\service\\RedisKeyManagerService;\nuse support\\Redis;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse think\\facade\\Db;\nuse Throwable;\n\nclass BackfillRechargedUidSet extends Command\n{\n protected static $defaultName = 'backfillRechargedUidSet';\n protected static $defaultDescription = '回填曾充值用户 uid 到共享 Redis 集合';\n\n private const DEFAULT_BATCH_SIZE = 500;\n\n protected function configure()\n {\n $this->addOption('dry-run', null, InputOption::VALUE_NONE, '只统计数量,不写 Redis');\n $this->addOption('rebuild', null, InputOption::VALUE_NONE, '执行前删除目标 set 后全量重建');\n $this->addOption('batch-size', null, InputOption::VALUE_OPTIONAL, '每批 SADD 成员数', self::DEFAULT_BATCH_SIZE);\n }\n\n protected function execute(InputInterface $input, OutputInterface $output): int\n {\n LoggerService::initGenerateTraceId('backfill_recharged_uid_set:');\n\n $dryRun = (bool) $input->getOption('dry-run');\n $rebuild = (bool) $input->getOption('rebuild');\n $batchSize = max(1, (int) ($input->getOption('batch-size') ?: self::DEFAULT_BATCH_SIZE));\n\n try {\n $key = RedisKeyManagerService::getUserRechargedSetKey();\n $output->writeln('redis key: ' . $key);\n\n if ($rebuild && !$dryRun) {\n Redis::connection('share')->del($key);\n $output->writeln('deleted existing set (--rebuild)');\n }\n\n $shardInfo = config('app.shard');\n $databaseNum = (int) $shardInfo['database_num'];\n $tableNum = (int) $shardInfo['table_num'];\n\n $totalUids = 0;\n $totalAdded = 0;\n $redis = Redis::connection('share');\n\n for ($i = 1; $i <= $databaseNum; $i++) {\n $db = sprintf('wallet_%02d', $i);\n for ($j = 1; $j <= $tableNum; $j++) {\n $table = \"{$db}.wallet_stat_\" . sprintf('%02d', $j);\n $uids = Db::query(\n \"SELECT DISTINCT uid FROM `{$table}` WHERE total_deposit > 0\"\n );\n $uidList = array_map(static fn ($row) => (int) $row['uid'], $uids);\n $count = count($uidList);\n $totalUids += $count;\n\n if ($count === 0) {\n continue;\n }\n\n $output->writeln(\"{$table}: {$count} uids\");\n\n if ($dryRun) {\n continue;\n }\n\n foreach (array_chunk($uidList, $batchSize) as $chunk) {\n $members = array_map(static fn (int $uid) => (string) $uid, $chunk);\n $redis->sAdd($key, ...$members);\n $totalAdded += count($members);\n }\n }\n }\n\n $output->writeln('distinct uid rows scanned: ' . $totalUids);\n if ($dryRun) {\n $output->writeln('dry-run finished, no redis writes');\n return self::SUCCESS;\n }\n\n $output->writeln('sadd operations (member count): ' . $totalAdded);\n $output->writeln('set cardinality (scard): ' . $redis->sCard($key));\n\n return self::SUCCESS;\n } catch (Throwable $e) {\n LoggerService::error(__METHOD__, $e);\n $output->writeln('backfill failed: ' . $e->getMessage());\n return self::FAILURE;\n }\n }\n}\n"}},{"type":"tool_use","name":"TodoWrite","input":{"todos":[{"id":"center-key","content":"slot_center sharerediskey.php 增加 userRecharged => user:recharged","status":"completed"},{"id":"wallet-service","content":"wallet:RedisKeyManagerService + RechargedUidSetService(share Redis SADD)","status":"completed"},{"id":"wallet-hook","content":"WalletLogic::recharge / rechargeSign 成功后调用 RechargedUidSetService::add","status":"completed"},{"id":"backfill-cmd","content":"新建 backfillRechargedUidSet 命令:扫 wallet_stat 分片、--dry-run/--rebuild/--batch-size","status":"completed"},{"id":"tests","content":"补充 Service/集成测试或 dry-run 验证说明","status":"in_progress"}],"merge":true}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"pattern":"Redis::|ShareConfigService","path":"/Users/ray/Documents/project/www/slot/slot_wallet/tests","head_limit":20}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/phpunit.xml"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/tests/bootstrap.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/tests/Support/WalletTestConfig.php"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/tests/Integration/WalletDbTestCase.php","limit":60}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"修复回填命令的 SQL 表名引用,并添加测试。\n\n[REDACTED]"},{"type":"tool_use","name":"StrReplace","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/command/BackfillRechargedUidSet.php","old_string":" $table = \"{$db}.wallet_stat_\" . sprintf('%02d', $j);\n $uids = Db::query(\n \"SELECT DISTINCT uid FROM `{$table}` WHERE total_deposit > 0\"\n );","new_string":" $tableName = 'wallet_stat_' . sprintf('%02d', $j);\n $uids = Db::query(\n \"SELECT DISTINCT uid FROM `{$db}`.`{$tableName}` WHERE total_deposit > 0\"\n );"}},{"type":"tool_use","name":"StrReplace","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/command/BackfillRechargedUidSet.php","old_string":" $output->writeln(\"{$table}: {$count} uids\");","new_string":" $output->writeln(\"{$db}.{$tableName}: {$count} uids\");"}},{"type":"tool_use","name":"Write","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/tests/Unit/RechargedUidSetServiceTest.php","contents":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit;\n\nuse app\\service\\RedisKeyManagerService;\nuse app\\service\\wallet\\RechargedUidSetService;\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class RechargedUidSetServiceTest extends TestCase\n{\n public function testAddIgnoresNonPositiveUid(): void\n {\n RechargedUidSetService::add(0);\n RechargedUidSetService::add(-1);\n $this->addToAssertionCount(1);\n }\n\n public function testGetUserRechargedSetKeyReturnsNonEmptyString(): void\n {\n $key = RedisKeyManagerService::getUserRechargedSetKey();\n $this->assertNotSame('', $key);\n $this->assertStringContainsString('recharged', $key);\n }\n}\n"}},{"type":"tool_use","name":"Write","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet/tests/Integration/RechargedUidSetIntegrationTest.php","contents":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Integration;\n\nuse app\\service\\RedisKeyManagerService;\nuse app\\service\\wallet\\RechargedUidSetService;\nuse PHPUnit\\Framework\\TestCase;\nuse support\\Redis;\n\n/**\n * 共享 Redis 集成测试(可选):\n * RUN_SHARE_REDIS_TESTS=1 ./vendor/bin/phpunit tests/Integration/RechargedUidSetIntegrationTest.php\n */\nfinal class RechargedUidSetIntegrationTest extends TestCase\n{\n private const TEST_UID = 999000001;\n\n protected function setUp(): void\n {\n if (!getenv('RUN_SHARE_REDIS_TESTS')) {\n $this->markTestSkipped('Set RUN_SHARE_REDIS_TESTS=1 to run share Redis integration tests.');\n }\n\n try {\n Redis::connection('share')->ping();\n } catch (\\Throwable $e) {\n $this->markTestSkipped('Share Redis unavailable: ' . $e->getMessage());\n }\n }\n\n protected function tearDown(): void\n {\n if (!getenv('RUN_SHARE_REDIS_TESTS')) {\n return;\n }\n\n try {\n $key = RedisKeyManagerService::getUserRechargedSetKey();\n Redis::connection('share')->sRem($key, (string) self::TEST_UID);\n } catch (\\Throwable) {\n // ignore cleanup errors\n }\n }\n\n public function testAddPutsUidInShareRedisSet(): void\n {\n $key = RedisKeyManagerService::getUserRechargedSetKey();\n $redis = Redis::connection('share');\n\n RechargedUidSetService::add(self::TEST_UID);\n\n $this->assertTrue((bool) $redis->sIsMember($key, (string) self::TEST_UID));\n }\n}\n"}}]}}
|
||
{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\nImplement the plan as specified, it is attached for your reference. Do NOT edit the plan file itself.\n\nTo-do's from the plan have already been created. Do not create them again. Mark them as in_progress as you work, starting with the first one. Don't stop until you have completed all the to-dos.\n</user_query>"}]}}
|
||
{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\n这个也是每日返水的需求分支里面。因此在console服,创建一个该需求的上线文档,方便后期持续更新\n</user_query>"}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"在 slot_console 创建每日返水上线文档前,先查看 console 仓库的文档惯例与相关实现状态。\n\n[REDACTED]"},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/*.{md,MD}","target_directory":"/Users/ray/Documents/project/www/slot/slot_console"}},{"type":"tool_use","name":"Grep","input":{"pattern":"daily.?rebate|每日返水|userRecharged|recharged","path":"/Users/ray/Documents/project/www/slot","glob":"**/*","head_limit":40}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/.cursor/plans/每日返水需求文档_20a03147.plan.md","limit":220}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/doc/首充前免费余额定格与分档释放需求部署说明.md","limit":120}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/.cursor/plans/每日返水需求文档_20a03147.plan.md","offset":220,"limit":120}},{"type":"tool_use","name":"Grep","input":{"pattern":"userRecharged|RechargedUid|backfillRecharged","path":"/Users/ray/Documents/project/www/slot","head_limit":30}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/doc/bankrupt_260324.md","limit":80}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/.cursor/plans/每日返水需求文档_20a03147.plan.md","offset":330,"limit":80}},{"type":"tool_use","name":"TodoWrite","input":{"todos":[{"id":"tests","content":"补充 Service/集成测试或 dry-run 验证说明","status":"completed"}],"merge":true}}]}}
|
||
{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\n中断了吗?\n</user_query>"}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"上次在创建 console 上线文档时被中断了。正在补全该文档。\n\n[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/doc/bankrupt_260324.md","limit":60}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/.cursor/plans/每日返水需求文档_20a03147.plan.md","offset":320,"limit":90}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Write","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/doc/daily_rebate_deploy.md","contents":"# 每日返水(Daily Rebate)上线文档\n\n> **维护说明**:本文档随需求分支持续更新。产品规格见 Cursor 计划「每日返水需求文档」;充值用户 Redis 集合见「充值用户 Redis 集合」计划。 \n> **最后更新**:2026-05-27\n\n---\n\n## 1. 需求摘要\n\n| 项 | 说明 |\n| --- | --- |\n| 活动 | 对已充值用户,按自然日有效下注(`user_profit_daily.bet`)累进分段计算返水 |\n| 领取窗口 | D 日下注 → D+1 日 00:00 起可领 → D+2 日 00:00 前未领过期(24h) |\n| 充值用户判定 | 结算时用共享 Redis Set `user:recharged`(`SISMEMBER`);wallet 维护集合 |\n| 时区 | `Asia/Shanghai`(与 center 一致) |\n\n---\n\n## 2. 上线顺序(强制)\n\n按依赖关系发布,**不可颠倒**:\n\n| 顺序 | 服务 | 说明 |\n| --- | --- | --- |\n| 1 | **slot_center** | `sharerediskey.userRecharged` 等共享 key |\n| 2 | **slot_wallet** | 充值写入集合 + 一次性回填脚本 |\n| 3 | **slot_lib** | `TRANSACTION_TYPE_DAILY_REBATE` 等常量(领取入账时) |\n| 4 | **slot_console** | 表结构、结算/过期定时任务、用户 API |\n| 5 | **backend/slot_admin** + **slot_admin_vue** | 档位配置、综合统计 |\n| 6 | **slot_gateway**(若经网关转发) | 路由 / sdk Client |\n| 7 | **slot_pwa**(前端) | 三态 UI、领取 |\n\n---\n\n## 3. 分支进度总览\n\n在对应项完成后将 `[ ]` 改为 `[x]` 并注明日期。\n\n### 3.1 基础设施(充值用户 Redis)\n\n- [x] **center** `config/sharerediskey.php` 增加 `userRecharged` => `user:recharged`\n- [x] **wallet** `RechargedUidSetService` + `WalletLogic::recharge` / `rechargeSign` 实时 `SADD`\n- [x] **wallet** 命令 `backfillRechargedUidSet`(历史回填)\n- [ ] **console** 结算任务使用 `SISMEMBER` 判断充值用户(待每日返水 Command 实现)\n\n### 3.2 每日返水主体\n\n- [ ] **DB** `daily_rebate_record`、`daily_rebate_tier_config`(`s_common`)\n- [ ] **console** `RebateCalcService` + 单元测试\n- [ ] **console** `dailyRebateSettle` / `dailyRebateExpire` 定时任务\n- [ ] **console** `GET/POST /api/daily-rebate/*` 用户 API\n- [ ] **wallet** 领取入账 + 流水类型 65\n- [ ] **slot_lib** `Consts::TRANSACTION_TYPE_DAILY_REBATE`\n- [ ] **admin** 档位 CRUD + 综合统计页\n- [ ] **PWA** 锁定 / 可领 / 7 日表 UI\n\n---\n\n## 4. 各服务操作明细\n\n### 4.1 slot_center\n\n**变更**\n\n- `config/sharerediskey.php`:\n ```php\n 'userRecharged' => 'user:recharged', // set:曾充值用户 uid(wallet 维护)\n ```\n\n**验证**\n\n- `GET {centerApiHost}/innerapi/share-config/all` 响应含 `sharerediskey.userRecharged`\n\n---\n\n### 4.2 slot_wallet\n\n**变更**\n\n| 文件 | 作用 |\n| --- | --- |\n| `app/service/wallet/RechargedUidSetService.php` | 共享 Redis `SADD` |\n| `app/service/RedisKeyManagerService.php` | `getUserRechargedSetKey()` |\n| `app/api/logic/WalletLogic.php` | `recharge` / `rechargeSign` 成功后写入集合 |\n| `app/command/BackfillRechargedUidSet.php` | 一次性历史回填 |\n\n**环境**\n\n- 需配置 `SHARE_REDIS_*`(`config/redis.php` 连接名 `share`)\n- 需能访问 `CENTER_API_HOST`(`ShareConfigService` 拉取 key)\n\n**一次性回填(低峰执行)**\n\n```bash\n# 预检:只统计,不写 Redis\ndocker exec -w /app/www/slot/slot_wallet php82 php webman backfillRechargedUidSet --dry-run\n\n# 全量重建(会先 DEL 目标 set)\ndocker exec -w /app/www/slot/slot_wallet php82 php webman backfillRechargedUidSet --rebuild\n\n# 可选:调整批次大小,默认 500\ndocker exec -w /app/www/slot/slot_wallet php82 php webman backfillRechargedUidSet --rebuild --batch-size=1000\n```\n\n**数据来源**:各分片 `wallet_XX.wallet_stat_YY` 中 `total_deposit > 0` 的 `DISTINCT uid`。\n\n**验证**\n\n```bash\n# 共享 Redis(按环境连接)\nredis-cli -h <share_host> -p <share_port> SCARD user:recharged\nredis-cli SISMEMBER user:recharged <测试充值 uid>\n```\n\n**单测**\n\n```bash\ndocker exec -w /app/www/slot/slot_wallet php82 ./vendor/bin/phpunit tests/Unit/RechargedUidSetServiceTest.php\n# 可选共享 Redis 集成测\nRUN_SHARE_REDIS_TESTS=1 docker exec -w /app/www/slot/slot_wallet php82 ./vendor/bin/phpunit tests/Integration/RechargedUidSetIntegrationTest.php\n```\n\n---\n\n### 4.3 slot_console\n\n**待实现(规格占位,落地后请更新本节)**\n\n#### SQL(`s_common`)\n\n> 以下 DDL 为需求规格草案,以评审后 `db/` 或迁移脚本为准。\n\n```sql\n-- daily_rebate_tier_config:累进档位\n-- daily_rebate_record:用户每日返水记录,唯一 (uid, stat_date)\n```\n\n表字段与索引见需求文档 §4.2 / §4.3。\n\n#### 定时任务\n\n| 命令名 | 建议 cron | 职责 |\n| --- | --- | --- |\n| `dailyRebateSettle` | 每日 00:05 | 结算昨日:读 `user_profit_daily` + `SISMEMBER user:recharged` → upsert `claimable` |\n| `dailyRebateExpire` | 每小时或 00:10 | `claimable` 且 `now >= expire_at` → `expired` |\n\n#### 用户 API(建议路径)\n\n| 方法 | 路径 | 说明 |\n| --- | --- | --- |\n| GET | `/api/daily-rebate/info` | 锁定态 / 可领 / 7 日明细、倒计时 |\n| POST | `/api/daily-rebate/claim` | 领取指定 `stat_date`(默认昨天) |\n\n#### 充值用户判断(结算时)\n\n```php\n$key = ShareConfigService::get('sharerediskey.userRecharged', 'user:recharged');\n$isRecharged = (bool) Redis::connection('share')->sIsMember($key, (string) $uid);\n```\n\n**勿**在结算循环内逐 uid 查 `wallet_stat`(性能差);集合由 wallet 充值链路与回填脚本维护。\n\n#### .env / 配置\n\n上线后在此补充,例如:\n\n```ini\n# DAILY_REBATE_SETTLE_CRON=5 0 * * *\n```\n\n---\n\n### 4.4 slot_lib\n\n- [ ] `Consts.php` 增加 `TRANSACTION_TYPE_DAILY_REBATE = 65`(以最终实现为准)\n\n---\n\n### 4.5 slot_admin / slot_admin_vue\n\n- [ ] 菜单:活动配置 → 每日返水档位(`daily_rebate_tier_config` CRUD)\n- [ ] 菜单:综合统计 → 每日返水统计(参考 FreeCreditsStats)\n- [ ] 种子数据:默认四档累进比例($0–1k 3% 等)\n\n---\n\n### 4.6 slot_pwa / gateway\n\n- [ ] 活动页三态:锁定(未充值)/ Play Now / Claim\n- [ ] 打开弹窗强制刷新当日 pending\n- [ ] gateway 转发 console `/api/daily-rebate/*`(若适用)\n\n---\n\n## 5. 上线检查清单\n\n### 5.1 发布前\n\n- [ ] center 已发布且 share-config 含 `userRecharged`\n- [ ] wallet 已发布;`backfillRechargedUidSet --dry-run` 数量与预期接近\n- [ ] 共享 Redis `SCARD user:recharged` 合理\n- [ ] console 表结构、种子档位、cron 已配置\n- [ ] 结算任务 dry-run / 单用户抽样验证返水金额(如 $2000 → $50)\n\n### 5.2 发布日\n\n1. 执行 wallet 回填:`backfillRechargedUidSet --rebuild`(若 center+wallet 已上线但未回填)\n2. 发布 console / admin / 前端\n3. 启用 `dailyRebateSettle`、`dailyRebateExpire`\n4. 观察首日凌晨结算日志与 `daily_rebate_record` 行数\n\n### 5.3 发布后\n\n- [ ] 充值用户 D 日下注,D+1 可领、24h 内入账正确\n- [ ] 未充值用户仅锁定态,`claim` 失败\n- [ ] 过期任务将超时行置 `expired`\n- [ ] 后台统计筛选/汇总与公式一致\n\n---\n\n## 6. 回滚说明\n\n| 组件 | 回滚 |\n| --- | --- |\n| 前端 / API | 下线入口或关闭活动配置 |\n| console cron | 停用 `dailyRebateSettle` / `dailyRebateExpire` |\n| `user:recharged` 集合 | 一般无需删;wallet 回滚后新充值不再写入,旧数据不影响其它业务 |\n| DB | 保留表数据;勿随意删 `daily_rebate_record`(已领审计) |\n\n---\n\n## 7. 监控与排障\n\n| 现象 | 排查 |\n| --- | --- |\n| 应参与用户无返水记录 | `SISMEMBER user:recharged <uid>`;昨日 `user_profit_daily.bet` |\n| 集合人数偏少 | 是否执行 wallet 回填;充值是否走 `recharge` / `rechargeSign` |\n| 结算重复 | 任务应对 `(uid, stat_date)` upsert,查唯一索引 |\n| 领取失败 | wallet 入账日志、`status=claimable` 与 `expire_at` |\n\n---\n\n## 8. 变更记录\n\n| 日期 | 作者 | 说明 |\n| --- | --- | --- |\n| 2026-05-27 | — | 初版:上线顺序、充值用户 Redis 分支(center/wallet)、每日返水待办占位 |\n| | | |\n\n---\n\n## 9. 相关文档\n\n- 产品规格:Cursor 计划「每日返水需求文档」\n- 充值用户集合:Cursor 计划「充值用户 Redis 集合」\n- 参考实现:`doc/首充前免费余额定格与分档释放需求部署说明.md`(文档结构)\n"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"是的,**上次在写 console 上线文档时被中断了**(读文件/更新 todo 超时)。代码侧(center + wallet 充值用户 Redis 集合)此前已按计划落地;文档这次已补全。\n\n已在 **slot_console** 创建可持续更新的上线文档:\n\n**[`slot_console/doc/daily_rebate_deploy.md`](file:///Users/ray/Documents/project/www/slot/slot_console/doc/daily_rebate_deploy.md)**\n\n文档主要内容:\n\n1. **上线顺序**:center → wallet → lib → console → admin → gateway/PWA \n2. **分支进度**:充值用户 Redis 已勾选完成;每日返水主体待办占位 \n3. **wallet 回填命令**、Redis 校验、单测命令 \n4. **console 待实现**:表结构、cron、API、`SISMEMBER` 用法说明 \n5. **发布前/发布日/发布后检查清单**、回滚与排障 \n6. **变更记录表**:后续可在 §8 追加更新 \n\n之后每完成一块(例如 `dailyRebateSettle`、admin 档位页),在 §3 勾选并在 §8 记一行即可。若要我把该文档链到 `README.md` 或补具体 DDL 草稿,可以说一下。\n\n[REDACTED]"}]}}
|
||
{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\n前期简单点,你应该写上线步骤以及上线流程\n</user_query>"}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"简化上线文档,聚焦上线步骤与流程。\n\n[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/doc/daily_rebate_deploy.md"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Write","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/doc/daily_rebate_deploy.md","contents":"# 每日返水 — 上线流程与步骤\n\n> 随分支持续补充。产品细节见「每日返水需求文档」计划。\n\n---\n\n## 一、上线流程(总览)\n\n```\n阶段 A:充值用户 Redis(前置,可单独先发)\n center 发布 → wallet 发布 → 执行回填脚本 → 验证 Redis\n\n阶段 B:每日返水功能(依赖阶段 A)\n 执行 SQL → 发布 lib / wallet / console → 配置 cron\n → 发布 admin + 前端 → 冒烟验收\n\n阶段 C:首日观察\n 次日 00:05 看结算任务 → 抽查可领/过期\n```\n\n**服务发布顺序(阶段 B 内勿乱序)**\n\n`center` → `wallet` → `slot_lib` → `slot_console` → `slot_admin` + `slot_admin_vue` → `slot_pwa`(经 gateway 则一并发)\n\n---\n\n## 二、阶段 A:充值用户 Redis(当前可做)\n\n### 步骤 1 — 发布 center\n\n- 上线 `slot_center`(含 `sharerediskey.userRecharged` = `user:recharged`)\n- 确认:`/innerapi/share-config/all` 能拿到 `userRecharged`\n\n### 步骤 2 — 发布 wallet\n\n- 上线 `slot_wallet`(充值 / 签到购成功后自动写入共享 Redis 集合)\n\n### 步骤 3 — 历史数据回填(低峰一次)\n\n```bash\n# 先看数量\ndocker exec -w /app/www/slot/slot_wallet php82 php webman backfillRechargedUidSet --dry-run\n\n# 全量写入\ndocker exec -w /app/www/slot/slot_wallet php82 php webman backfillRechargedUidSet --rebuild\n```\n\n### 步骤 4 — 验证\n\n- Redis:`SCARD user:recharged` 数量合理\n- 抽一个已充值 uid:`SISMEMBER user:recharged <uid>` 为 1\n- 新充值一笔后,该 uid 仍在集合中\n\n**阶段 A 完成标准**:center + wallet 已上线,回填已跑,集合可查。\n\n---\n\n## 三、阶段 B:每日返水功能(待开发完成后执行)\n\n### 步骤 1 — 数据库\n\n- 在 `s_common` 执行迁移(表名以最终 SQL 为准):\n - `daily_rebate_tier_config`(档位)\n - `daily_rebate_record`(用户每日记录)\n- 导入默认档位种子数据\n\n### 步骤 2 — 按顺序发布代码\n\n| 顺序 | 服务 | 要点 |\n| --- | --- | --- |\n| 1 | slot_lib | 领取流水类型常量 |\n| 2 | slot_wallet | 领取入账 |\n| 3 | slot_console | API、结算/过期命令 |\n| 4 | slot_admin + vue | 档位配置、统计页 |\n| 5 | slot_pwa | 活动页 |\n\n### 步骤 3 — 配置定时任务(console)\n\n| 任务 | 建议时间 | 作用 |\n| --- | --- | --- |\n| `dailyRebateSettle` | 每天 00:05 | 结算昨日返水 |\n| `dailyRebateExpire` | 每天 00:10 或每小时 | 过期未领取 |\n\n### 步骤 4 — 冒烟\n\n- 未充值用户:活动页为锁定,不能领\n- 已充值用户:昨日有下注 → 今日可领 → 领取到账\n- 后台:档位可改;统计列表有数据\n\n---\n\n## 四、阶段 C:上线后首日\n\n1. 看 `dailyRebateSettle` 日志是否成功\n2. 抽查几条 `daily_rebate_record` 金额(如 $2000 下注 → $50 返水)\n3. 看 `dailyRebateExpire` 是否把超时单置为过期\n\n---\n\n## 五、变更记录\n\n| 日期 | 说明 |\n| --- | --- |\n| 2026-05-27 | 简化为上线流程 + 分阶段步骤 |\n| | |\n"}}]}}
|
||
{"role":"assistant","message":{"content":[{"type":"text","text":"已把 [`slot_console/doc/daily_rebate_deploy.md`](file:///Users/ray/Documents/project/www/slot/slot_console/doc/daily_rebate_deploy.md) 改成以**上线流程**和**分阶段步骤**为主,去掉了冗长的检查表、排障和代码细节。\n\n结构现在是:\n\n1. **上线流程总览** — 三阶段:A 充值用户 Redis → B 每日返水功能 → C 首日观察,以及服务发布顺序 \n2. **阶段 A** — 4 步:发 center → 发 wallet → 回填脚本 → 验证(当前可做) \n3. **阶段 B** — 4 步:SQL → 按序发版 → 配 cron → 冒烟(功能做完再做) \n4. **阶段 C** — 上线后首日要看什么 \n5. **变更记录** — 方便以后追加\n\n后面每完成一块,只要在对应阶段下补一两行即可。若要再缩成「一张 checklist」,也可以说一声。\n\n[REDACTED]"}]}} |