Files
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

25 lines
35 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>\nwww/slot 项目新增「Lucky Rewards 转盘活动」,涉及:用户进入游戏自动注册、邀请链接(invite_code/share_url)、注册时透传落地归因(landing_token/landing_scene)、绑定手机/账号激活校验、首次充值判断。请【彻底】调研 slot_user 服务。仓库路径:/Users/ray/Documents/project/www/slot/slot_user\n\n请重点回答并给出具体文件路径与代码引用\n\n1. 用户注册流程:自动注册(进入游戏自动建号)在哪里实现?注册 Service/Logic 的入口与关键方法(如 AbstractRegisterService)。\n2. 分享码 share_url 体系origin 字段(user_agent / red_packet / user_self)如何生成与解析invite_code 如何关联用户?给文件路径。\n3. 注册时如何把 invite_code 透传、写 invite_uid、并通知 slot_agent (MQ invite_bind)?现有 notifyAgentInviteBind / shouldNotifyAgentByInviteOrigin 在哪。\n4. 「绑定手机号」「账号激活」状态在用户域如何表示与校验?其它服务如何查询某用户是否已绑定手机/是否新注册用户/是否充值用户?走 slot_sdk 还是 innerapi给示例。\n5. 「首次充值」标志在哪个服务/表slot_console 要判断用户是否已首充,应该查谁?\n6. 是否已有 landing_token / landing_scene 之类落地归因透传机制?没有的话,注册链路中最适合扩展的位置在哪。\n7. slot_user 对外 innerapi 提供哪些用户信息查询接口(给2-3个示例)。\n\n输出结构化包含文件路径、类名/方法名、简短代码引用。不要修改任何文件。\n</user_query>"}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"正在系统调研 slot_user 服务,先从注册流程、分享码与邀请绑定相关代码入手。\n\n[REDACTED]"},{"type":"tool_use","name":"UpdateCurrentStep","input":{"current_step":"Exploring slot_user registration flow"}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/*Register*","target_directory":"/Users/ray/Documents/project/www/slot/slot_user"}},{"type":"tool_use","name":"Grep","input":{"pattern":"AbstractRegisterService|autoRegister|自动注册","path":"/Users/ray/Documents/project/www/slot/slot_user"}},{"type":"tool_use","name":"Grep","input":{"pattern":"invite_code|share_url|invite_uid|invite_bind","path":"/Users/ray/Documents/project/www/slot/slot_user"}},{"type":"tool_use","name":"Grep","input":{"pattern":"notifyAgentInviteBind|shouldNotifyAgentByInviteOrigin|landing_token|landing_scene","path":"/Users/ray/Documents/project/www/slot/slot_user"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"origin|user_agent|red_packet|user_self","glob":"**/*Share*"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"bind.*phone|phone.*bind|mobile.*bind|激活|is_active|account.*activ","-i":true}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"first.*recharge|首充|first_recharge|has_recharge|recharge","-i":true}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"landing_token|landing_scene|归因"}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/innerapi/**/*.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_user"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/register/AbstractRegisterService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/register/ImeiRegisterService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/register/RegisterFactoryService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/model/ShareUrlModel.php"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"ImeiRegister|RegisterFactory|register\\("}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/api/controller/UserController.php","limit":150}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/innerapi/controller/UserController.php"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"first_pay|STATE_ACTIVE|state.*激活|mobile_bind|bindMobile"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot","pattern":"share_url|ShareUrl|short_code|createShare","glob":"**/slot_user/**"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot","pattern":"createUserShareUrl|ShareUrlModel|share_url","glob":"**/*.php","head_limit":60}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/login/ImeiLoginService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/validator/UserValidator.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/UserService.php","limit":250}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/route*.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_user"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/config/route.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/login/LoginService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/TouristBindService.php"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot","pattern":"first_pay_date|first_pay|首充"}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/user/**/*.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_sdk"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_sdk/src/service/user/UserService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_sdk/src/service/user/UserClient.php"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"innerapi|Route::","glob":"**/*"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/cache/UserInfoCache.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/model/shard/UserShardModel.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot","pattern":"type.*imei|TYPE_IMEI|imei_register|/user/register","head_limit":40}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/service/ShareService.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/register/MobileRegisterService.php"}},{"type":"tool_use","name":"Read","input":{"offset":150,"limit":250,"path":"/Users/ray/Documents/project/www/slot/slot_user/app/api/controller/UserController.php"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_wallet","pattern":"total_deposit|first_deposit|hasDeposit","head_limit":30}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot","pattern":"invite_code|secret.*register|register.*invite","glob":"**/*.{php,js,vue,ts}","head_limit":50}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot","pattern":"activeState|isBind|bindingMobile|UserClient","glob":"**/*.php","head_limit":40}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"invite_code|secret"}},{"type":"tool_use","name":"Glob","input":{"glob_pattern":"**/config/**/*.php","target_directory":"/Users/ray/Documents/project/www/slot/slot_user"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"SemanticSearch","input":{"query":"Where does client or game server call user register with imei type for auto registration?","target_directories":["/Users/ray/Documents/project/www/slot"],"num_results":15}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/service/user/UserService.php","limit":120}},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":30,"path":"/Users/ray/Documents/project/www/slot/slot_sdk","pattern":"total_deposit|getStatistics|first_pay"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/register/NameRegisterService.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"glob":"**/*","head_limit":40,"path":"/Users/ray/Documents/project/www/slot/slot_pwa","pattern":"/user/register|user/register"}},{"type":"tool_use","name":"Read","input":{"limit":80,"offset":480,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/entity/mq/RegisterEntity.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/entity/mq/InviteBindEntity.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_sdk/src/service/wallet/WalletService.php","limit":80}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_pwa","pattern":"register|imei","glob":"**/*.{php,js,vue,ts}","head_limit":40}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console","pattern":"first_pay_date|total_deposit|首充|firstRecharge","glob":"**/*.php","head_limit":40}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/innerapi/controller/VipController.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/UserDataService.php"}},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"checkActiveState|sendActiveNotify|STATE_ACTIVE|state.*0"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"limit":180,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RechargeEvent.php"}},{"type":"tool_use","name":"Read","input":{"limit":120,"offset":180,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/UserController.php"}},{"type":"tool_use","name":"Read","input":{"limit":80,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/GiftController.php"}},{"type":"tool_use","name":"Grep","input":{"glob":"**/*","head_limit":30,"path":"/Users/ray/Documents/project/www/slot","pattern":"api/user/register|/register.*type.*imei"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/service/AssignmentObjectService.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"head_limit":30,"path":"/Users/ray/Documents/project/www/slot","pattern":"user/register|TYPE_IMEI|type.*imei","glob":"**/*.{vue,js,ts,tsx}"}},{"type":"tool_use","name":"Read","input":{"limit":80,"offset":180,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/GiftController.php"}},{"type":"tool_use","name":"Read","input":{"limit":80,"offset":285,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/UserController.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/entity/mq/MQBusEntity.php"}},{"type":"tool_use","name":"Grep","input":{"head_limit":25,"path":"/Users/ray/Documents/project/www/slot/slot_user","pattern":"fbp|fbc|fbclid|secret|from"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":35,"path":"/Users/ray/Documents/project/www/slot","pattern":"first_pay_date"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/service/user/UserRegisterEventService.php"}},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":30,"path":"/Users/ray/Documents/project/www/slot/slot_lib","pattern":"register|imei|auto.*login"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_console/app/model/ShareUrlModel.php"}},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":20,"path":"/Users/ray/Documents/project/www/slot","pattern":"innerapi/user/isBind|isBind"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":30,"path":"/Users/ray/Documents/project/www/slot/slot_console","pattern":"user_register|TYPE_USER_REGISTER"}},{"type":"tool_use","name":"Read","input":{"limit":120,"path":"/Users/ray/Documents/project/www/slot/slot_lib/src/entity/user/UserInfoEntity.php"}},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":25,"path":"/Users/ray/Documents/project/www/slot","pattern":"register.*imei|ImeiRegister|type=.imei"}},{"type":"tool_use","name":"Read","input":{"limit":100,"path":"/Users/ray/Documents/project/www/slot/docs/requirements/lucky_rewards/01_slot_agent邀请回调对接方案.md"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"limit":150,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/EventBus.php"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/command/EventRegister.php"}},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":25,"path":"/Users/ray/Documents/project/www/slot/slot_wallet","pattern":"statistics|getStatistics|innerapi.*wallet"}},{"type":"tool_use","name":"Read","input":{"limit":80,"offset":1180,"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Grep","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/innerapi","pattern":"public function"}},{"type":"tool_use","name":"Read","input":{"limit":80,"path":"/Users/ray/Documents/project/www/slot/slot_pwa/app/service/UserGameDailyService.php"}},{"type":"tool_use","name":"Read","input":{"limit":80,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/command/event/RegisterEvent.php"}},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":20,"path":"/Users/ray/Documents/project/www/slot/slot_console","pattern":"total_deposit.*0|isFirstRecharge|hasRecharge|已充值"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"limit":50,"path":"/Users/ray/Documents/project/www/slot/slot_wallet/app/api/controller/WalletController.php","offset":155}},{"type":"tool_use","name":"Grep","input":{"glob":"**/*.php","head_limit":15,"path":"/Users/ray/Documents/project/www/slot/slot_console","pattern":"WalletClient|getStatistics"}},{"type":"tool_use","name":"Read","input":{"path":"/Users/ray/Documents/project/www/slot/slot_user/app/entity/referral/ReferralShareInfoRequestEntity.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"Read","input":{"limit":50,"offset":140,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/UserController.php"}},{"type":"tool_use","name":"Read","input":{"limit":50,"offset":400,"path":"/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/UserController.php"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"[REDACTED]"},{"type":"tool_use","name":"UpdateCurrentStep","input":{"current_step":"Compiling slot_user research report","final_summary":"已对 slot_user 注册、分享码、邀请绑定 MQ、手机/激活状态、首充口径与 innerapi 接口完成调研;`landing_token`/`landing_scene` 在 user 域尚未实现,文档建议在 `AbstractRegisterService` 扩展。","completed_subtitle":"Completed slot_user codebase survey"}}]}}
{"role":"assistant","message":{"content":[{"type":"text","text":"# slot_user 调研报告Lucky Rewards 转盘活动)\n\n---\n\n## 1. 用户注册流程:自动注册(进入游戏建号)\n\n### 结论\n\n「进入游戏自动建号」对应 **游客 IMEI 注册**:客户端调 `POST /api/user/register``type=imei`。`ImeiRegisterService` 采用 **注册-or-登录** 模式——IMEI 已存在则走 `ImeiLoginService` 登录,否则新建用户。\n\n### 调用链\n\n| 层级 | 路径 | 类/方法 |\n|------|------|---------|\n| HTTP 入口 | `/Users/ray/Documents/project/www/slot/slot_user/app/api/controller/UserController.php` | `register()` |\n| 工厂 | `/Users/ray/Documents/project/www/slot/slot_user/app/service/register/RegisterFactoryService.php` | `createRegisterService()` |\n| 游客自动建号 | `/Users/ray/Documents/project/www/slot/slot_user/app/service/register/ImeiRegisterService.php` | `run()` / `check()` |\n| 抽象基类 | `/Users/ray/Documents/project/www/slot/slot_user/app/service/register/AbstractRegisterService.php` | `initUserModel()`、`resolveInviteContext()`、`notify()`、`notifyExtend()` |\n| 其它注册方式 | `NameRegisterService.php`、`MobileRegisterService.php` | 用户名 / 手机号注册 |\n\n### 关键代码\n\n**Controller 入口**(按 `type` 选 Service\n\n```71:100:/Users/ray/Documents/project/www/slot/slot_user/app/api/controller/UserController.php\n public function register(Request $request)\n {\n // ...\n $type = $post['type'] ?? UserValidator::TYPE_NAME;\n // ...\n $registerService = RegisterFactoryService::createRegisterService($type, $post);\n try {\n $model = $registerService->run();\n```\n\n**Factory 映射**`imei` → `ImeiRegisterService`\n\n```14:29:/Users/ray/Documents/project/www/slot/slot_user/app/service/register/RegisterFactoryService.php\n public static function createRegisterService(string $type, array $data)\n {\n switch ($type) {\n case UserValidator::TYPE_NAME:\n $registerService = new NameRegisterService($data);\n break;\n case UserValidator::TYPE_IMEI:\n $registerService = new ImeiRegisterService($data);\n break;\n case UserValidator::TYPE_MOBILE:\n $registerService = new MobileRegisterService($data);\n break;\n```\n\n**Imei 注册-or-登录**\n\n```90:97:/Users/ray/Documents/project/www/slot/slot_user/app/service/register/ImeiRegisterService.php\n public function run(): ?UserShardModel\n {\n $this->checkMax();\n if (!$this->check()) {\n $login = new ImeiLoginService(['name' => $this->imei, 'password' => '', 'source' => $this->source, 'ip' => $this->ip]);\n $userModel = $login->run();\n return $userModel;\n }\n```\n\n**校验参数**IMEI 注册必填 `imei,type,source``invite_code` 不在 validator scene 里,但 POST 有值时会被 `AssignmentObjectService` 注入):\n\n```73:74:/Users/ray/Documents/project/www/slot/slot_user/app/validator/UserValidator.php\n self::SCENE_IMEI_REGISTER => ['imei', 'type', 'source',],\n```\n\n**注册后 MQ**\n- 内部:`QUEUE_USER_REGISTER` → `EventRegister` 调 PWA 创建游戏玩家\n- 外部:`EXCHANGE_CONSOLE` / `TYPE_USER_REGISTER` → slot_console 发注册奖励等\n\n---\n\n## 2. share_url / invite_code / origin 体系\n\n### 数据真源\n\n| 项 | 说明 |\n|----|------|\n| 表 | `s_common.share_url` |\n| Model | `/Users/ray/Documents/project/www/slot/slot_user/app/model/ShareUrlModel.php` |\n| 邀请码 | `short_code` 字段,对外称 `invite_code` |\n| 邀请人 | `creator_id`(玩家 UID |\n| origin | `red_packet` / `user_self` / `user_agent` |\n\n```19:19:/Users/ray/Documents/project/www/slot/slot_user/app/model/ShareUrlModel.php\n * @property string $origin 来源(red_packet[红包]/user_self[自发]/user_agent[代理])\n```\n\n### origin 如何生成\n\n**生成在 slot_console**(不在 slot_user\n\n- `/Users/ray/Documents/project/www/slot/slot_console/app/service/ShareService.php` → `createUrl($from, $type)`\n- 入口:`/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/GiftController.php` → `createShareUrl()`\n- 常量:`SHARE_FROM_RED_PACKET`、`SHARE_FROM_USER_SELF`、`SHARE_FROM_USER_AGENT`\n\n```66:88:/Users/ray/Documents/project/www/slot/slot_console/app/service/ShareService.php\n public function createUrl($from = self::SHARE_FROM_RED_PACKET, string $type = 'web'): false|array\n {\n $model = ShareUrlModel::isPlayerCreated($this->userEntity->uid, $from);\n // ...\n $model->origin = $from;\n $model->creator_type = ShareUrlModel::CREATOR_TYPE_PLAYER;\n $model->short_code = $this->generateShareShortCode($codeLength);\n $data = [\n 'organization' => $this->userEntity->organization,\n 'invite_uid' => $this->userEntity->uid,\n 'from' => $from,\n ];\n $model->code = AESLibService::encrypt(...);\n```\n\n返回给前端`secret`AES 加密 payload、`code`short_code、`land_url` 等。\n\n### origin / invite_code 如何解析(注册时)\n\n`/Users/ray/Documents/project/www/slot/slot_user/app/service/register/AbstractRegisterService.php`\n\n1. **新链路**POST `invite_code` → `ShareUrlModel::findByShortCode()` → `creator_id` 为 `invite_uid``origin` 写入 `invite_share_origin`\n2. **旧链路**POST `secret` → AES 解密 → 取 `invite_uid`、`from`\n\n```136:157:/Users/ray/Documents/project/www/slot/slot_user/app/service/register/AbstractRegisterService.php\n protected function resolveInviteUidByInviteCode(): void\n {\n $inviteCode = trim($this->invite_code);\n // ...\n $shareRow = ShareUrlModel::findByShortCode($inviteCode);\n // ...\n $this->invite_uid = $inviterUid;\n $this->invite_share_origin = strval($shareRow->origin ?? '');\n if (empty($this->from)) {\n $this->from = $this->invite_share_origin !== '' ? $this->invite_share_origin : 'invite_code';\n }\n }\n```\n\n写入用户表`user.origin = $this->from``user.invite_uid = $this->invite_uid``initUserModel()`)。\n\n### 注意\n\nslot_user 的 `referralShareInfo` innerapi **已注释**,但 slot_sdk 仍封装了 `referralShareInfo()`——当前分享码创建以 **slot_console ShareService** 为准。\n\n---\n\n## 3. invite_code 透传 → invite_uid → slot_agent MQ\n\n### 注册时自动绑定\n\n| 步骤 | 位置 | 行为 |\n|------|------|------|\n| 透传 | `AbstractRegisterService::__construct` + `AssignmentObjectService` | POST 的 `invite_code`/`secret` 注入 public 属性 |\n| 解析 | `resolveInviteContext()` | invite_code 优先于 secret |\n| 写库 | `initUserModel()` | `invite_uid`、`origin` |\n| 关系树 | `insertShare()` | 写 `user_family`parent_id = invite_uid |\n| 通知 agent | `notifyAgentInviteBind()` | 条件:`shouldNotifyAgentByInviteOrigin()` |\n\n**写 invite_uid**\n\n```318:320:/Users/ray/Documents/project/www/slot/slot_user/app/service/register/AbstractRegisterService.php\n $userModel->source = $this->source;\n $userModel->origin = $this->from;\n $userModel->invite_uid = $this->invite_uid;\n```\n\n**notifyAgentInviteBind / shouldNotifyAgentByInviteOrigin**(均在 `AbstractRegisterService.php`\n\n```245:284:/Users/ray/Documents/project/www/slot/slot_user/app/service/register/AbstractRegisterService.php\n protected function notifyAgentInviteBind(int $inviteeUid): void\n {\n // ...\n $bindEntity = new InviteBindEntity([\n 'inviter_uid' => intval($this->invite_uid),\n 'invitee_uid' => $inviteeUid,\n 'invite_code' => trim($this->invite_code),\n 'bind_type' => 'auto',\n ]);\n $agentMqService = RabbitMqService::getInstance(MQKeyManagerService::EXCHANGE_AGENT, MQKeyManagerService::QUEUE_AGENT_BUS);\n // type = MQBusEntity::TYPE_INVITE_BIND\n }\n\n protected function shouldNotifyAgentByInviteOrigin(): bool\n {\n $origin = trim(strtolower($this->invite_share_origin));\n if ($origin === '') {\n $origin = trim(strtolower($this->from));\n }\n return $origin === 'user_agent';\n }\n```\n\n**仅 `origin === user_agent` 才发 agent MQ**`red_packet` / `user_self` 注册会写 `invite_uid`,但不通知 slot_agent。\n\n各 RegisterService 在 `invite_uid > 0` 时调用(例:`ImeiRegisterService.php` 128133 行)。\n\n### 注册后手动绑定\n\n`/Users/ray/Documents/project/www/slot/slot_user/app/innerapi/controller/UserController.php` → `referralBind()` \n写 `user_family` + `invite_uid`,成功后同样发 `EXCHANGE_AGENT` / `TYPE_INVITE_BIND`bind_type=`manual`)。\n\nslot_console 封装:`/Users/ray/Documents/project/www/slot/slot_console/app/api/controller/UserController.php` → `bindingInviteCode()` → slot_sdk `referralBindByRequest()`。\n\n---\n\n## 4. 绑定手机 / 账号激活 / 跨服务查询\n\n### 用户域字段与语义\n\n| 概念 | 存储/表示 | 校验方式 |\n|------|-----------|----------|\n| **绑定手机** | 分片表 `user.phone` + `user_account_mapping`type=phone | `phone` 非空;`UserService::checkMobile()` |\n| **账号激活** | `user.state`0=未激活1=已激活(`UserShardModel::STATE_ACTIVE` | `state == 1` |\n| **游客绑账号** | `password` 非空 → Redis `is_bind_name=true` | `TouristBindService::checkPasswd()`innerapi `isBind` |\n| **新注册用户** | `create_at` / UserTag `regTime` | 对比注册时间或 MQ 注册事件 |\n| **是否充值** | **不在 slot_user** | 查 slot_wallet |\n\n```195:195:/Users/ray/Documents/project/www/slot/slot_user/app/command/userDB.php\n `state` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态 0:未激活,1:已激活,10:禁止登录',\n```\n\n### 绑定手机\n\n- C 端:`/Users/ray/Documents/project/www/slot/slot_user/app/api/controller/UserController.php` → `bindingMobile()`\n- innerapi`/Users/ray/Documents/project/www/slot/slot_user/app/innerapi/controller/UserController.php` → `bindingMobile()`\n- 逻辑:`UserService::updateMobile()` + 短信校验(`UserValidator::SCENE_MOBILE_BIND`\n\n### 账号激活\n\n- innerapi`activeState()` → `state=1` + `sendActiveNotify()`MQ `TYPE_USER_ACTIVATE`\n- **触发时机**:用户首次在游戏下注时,`slot_pwa` 的 `UserGameDailyService::activeUser()` 经 slot_sdk 调 `activeState`slot_console `EventBus::checkActiveState()` 在下注事件里也会调\n\n### 其它服务如何查\n\n**推荐 slot_sdk → innerapi**(跨服务标准方式):\n\n```php\nuse slotsdk\\Config as SDKConfig;\nuse slotsdk\\service\\user\\UserClient;\n\n$config = new SDKConfig([\n 'host' => ShareConfigService::get('userApiHost'),\n 'headers' => ['server-name' => config('app.server_name')],\n]);\n$userInfo = (new UserClient($config))->service()->info($uid);\n// $userInfo['phone'] — 是否绑手机\n// $userInfo['state'] — 是否激活1=已激活)\n// $userInfo['create_at'] / regTime — 注册时间\n// $userInfo['invite_uid']\n// $userInfo['is_bind_name'] — 是否游客已绑用户名密码\n```\n\n**绑定手机innerapi**\n\n```php\n(new UserClient($config))->service()->bindingMobile($uid, $mobile, $code);\n```\n\n**激活用户**\n\n```php\n(new UserClient($config))->service()->activeState($uid);\n```\n\n**是否充值** → 查 **slot_wallet**(非 slot_user\n\n```php\nuse slotsdk\\service\\wallet\\WalletClient;\n\n$stats = (new WalletClient($walletConfig))->service()->getStatistics([$uid], $currency);\n$hasRecharged = ($stats[$uid]->total_deposit ?? 0) > 0;\n```\n\n**isBind游客是否绑密码**:仅有 innerapi `POST innerapi/user/isBind`**slot_sdk 未封装**;需 InnerCurl 或补 SDK。\n\n**区分「绑手机」vs「isBind」**Lucky Rewards 文档里的「绑定手机/账号激活」应对应 `phone` + `state`,不是 `isBind`(后者是游客设密码)。\n\n---\n\n## 5. 「首次充值」标志:查谁?\n\n### 结论slot_console 判断首充)\n\n| 优先级 | 服务 | 字段/口径 | 可靠性 |\n|--------|------|-----------|--------|\n| **首选** | **slot_wallet** | `wallet_stat.total_deposit`(入账前 `<= 0` 即首充) | 钱包真源 |\n| 辅助 | slot_console UserTag | `statistics_info.recharge_count <= 0` | RechargeEvent 维护 |\n| 邀请域 | slot_agent | `ref_first_deposit_event` | 裂变奖励 |\n| **不推荐** | slot_user | `user.first_pay_date` | VIP 0→1 时写入,**不是充值真源** |\n\n**wallet 首充判断**`/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php`\n\n```1208:1215:/Users/ray/Documents/project/www/slot/slot_wallet/app/api/logic/WalletLogic.php\n $statModel = new WalletStatModel($this->requestDTO->uid);\n $stat = $statModel->getInfo($this->requestDTO->currency);\n $totalDeposit = intval($stat['total_deposit'] ?? 0);\n return [\n 'is_first_recharge' => $totalDeposit <= 0,\n ];\n```\n\n**slot_console 侧示例**(已有用法):\n\n```php\n$client = \\app\\service\\WalletService::getWalletClient();\n$stats = $client->service()->getStatistics([$uid], $currency);\n$isFirstRecharge = intval($stats[$uid]->total_deposit ?? 0) <= 0;\n```\n\n接口`POST /api/wallet/statistics`WalletClient->getStatistics。\n\n---\n\n## 6. landing_token / landing_scene 落地归因\n\n### 现状\n\n在 **slot_user 全仓库 grep 无 `landing_token` / `landing_scene`**。\n\n现有近似机制\n\n| 机制 | 位置 | 用途 |\n|------|------|------|\n| `secret` AES payload | 注册 POST | invite_uid + from |\n| `invite_code` | 注册 POST | 邀请码 |\n| `fbp/fbc/fbclid/ua` | `AbstractRegisterService` | FB 广告 → `UserTag``insertADTag` |\n| `user.origin` | 用户分片表 | 分享来源red_packet 等) |\n\nLucky Rewards 方案文档(`/Users/ray/Documents/project/www/slot/docs/requirements/lucky_rewards/01_slot_agent邀请回调对接方案.md`)明确:**落地归因由 slot_console Redis/DB 记录**slot_user 侧「注册时透传 landing_token可选」**尚未实现**。\n\n### 最适合扩展的位置slot_user\n\n1. **`AbstractRegisterService`**:新增 `landing_token` / `landing_scene` public 属性(与 `invite_code` 同级constructor 自动注入)\n2. **`initUserModel()` 或 `insertADTag()`**:持久化到 `UserTagModel` / Redis与 ad 标签同模式)\n3. **`RegisterEntity` + `notifyExtend()`**:随 `TYPE_USER_REGISTER` 带给 slot_console便于注册即归因\n4. **备选**:写入 `user.origin` 扩展 JSON需评估与现有 origin 语义冲突)\n\n---\n\n## 7. slot_user innerapi 用户信息查询(示例)\n\nWebman 默认路由:`/innerapi/{controller}/{action}`。\n\n| 接口 | 方法 | 用途 |\n|------|------|------|\n| `POST innerapi/user/info` | `UserController::info()` | 全量用户信息Redis 缓存) |\n| `POST innerapi/user/getTag` | `getTag()` | 按 key 读 UserTag |\n| `POST innerapi/user/activeState` | `activeState()` | 激活state→1 |\n| `POST innerapi/user/bindingMobile` | `bindingMobile()` | 绑手机 |\n| `POST innerapi/user/isBind` | `isBind()` | 游客是否已设密码 |\n| `POST innerapi/user/referralBind` | `referralBind()` | 手动绑邀请码 |\n| `POST innerapi/user/referralFriends` | `referralFriends()` | 直属邀请列表 |\n| `POST innerapi/user/addExp` | `addExp()` | VIP 经验 |\n| `POST innerapi/vip/config` | `VipController::config()` | VIP 配置 |\n\n**示例 1 — 查用户信息**\n\n```60:70:/Users/ray/Documents/project/www/slot/slot_user/app/innerapi/controller/UserController.php\n public function info(Request $request)\n {\n $post = $request->post();\n // ...\n $info = UserService::getInfo($post['uid']);\n return $op->ok($info);\n```\n\n**示例 2 — 查 Tag含 ad/统计扩展字段)**\n\n```97:108:/Users/ray/Documents/project/www/slot/slot_user/app/innerapi/controller/UserController.php\n public function getTag(Request $request)\n {\n // ...\n $info = $userInfoCache->getInfo($keys);\n return $op->ok($info);\n```\n\n**示例 3 — 激活状态**\n\n```180:201:/Users/ray/Documents/project/www/slot/slot_user/app/innerapi/controller/UserController.php\n public function activeState(Request $request)\n {\n // ...\n if ($state == UserShardModel::STATE_ACTIVE) {\n return $op->ok();\n }\n $userService->updateInfo('state', UserShardModel::STATE_ACTIVE);\n $userService->sendActiveNotify();\n```\n\n---\n\n## Lucky Rewards 对接要点slot_user 侧)\n\n```mermaid\nsequenceDiagram\n participant Client as 客户端\n participant User as slot_user\n participant DB as share_url/user\n participant AgentMQ as agent_bus\n\n Client->>User: POST /api/user/register type=imei + invite_code\n User->>DB: resolveInviteContext → invite_uid\n User->>DB: initUserModel + insertShare\n alt origin=user_agent\n User->>AgentMQ: invite_bind (auto)\n end\n User->>Client: token + uid\n```\n\n- **invite_code 真源**`s_common.share_url.short_code`,由 slot_console `ShareService` 创建 \n- **有效助力 agent 通知**:仅 `origin=user_agent` 自动发 MQLucky Rewards 还需 console 侧落地归因 \n- **首充校验**:查 slot_wallet `total_deposit`,勿用 `user.first_pay_date` \n- **绑手机 + 激活**`info.phone` + `info.state==1`;激活可在首次下注后异步完成 \n\n如需我在 Agent 模式下实现 `landing_token` 透传或补全 `referralShareInfo` innerapi可切换模式继续。\n\n[REDACTED]"}]}}
{"type":"turn_ended","status":"success"}