25 lines
35 KiB
JSON
25 lines
35 KiB
JSON
{"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` 128–133 行)。\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` 自动发 MQ;Lucky 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"}
|