This commit is contained in:
ray zhou
2026-06-29 14:51:55 +08:00
parent 225fb2bd28
commit 2dd9f17da9
319 changed files with 29461 additions and 9412 deletions

View File

@@ -0,0 +1,279 @@
<!-- 09c08dbd-5e66-4d08-b29e-090a18ddb10d -->
---
todos:
- id: "user-is-new-user"
content: "slot-userAbstractRegisterService 增加 isNewUser 标志Mobile/Imei/Name RegisterService 赋值UserController::register 返回 is_new_user"
status: pending
- id: "console-ggame-query"
content: "slot-consoleGGame 新增按 game_code 查可展示单条游戏方法"
status: pending
- id: "console-recommend-logic"
content: "slot-consoleOnboardingRecommendLogiccenter 取池 → 随机 → g_game 组装 → 降级)"
status: pending
- id: "console-recommend-api"
content: "slot-consoleOnboardingController + GET /api/onboarding/recommend-game"
status: pending
- id: "update-docs"
content: "更新 register_popup_game_entry 文档:前端 is_new_user 条件请求 recommend-game 链路"
status: pending
- id: "lobby-auth-types"
content: "lobbyAuthResponse 增加 is_new_userauth.service 注册后按标志决定是否拉 recommend-game"
status: pending
- id: "lobby-onboarding-api"
content: "lobby新增 api/console/onboarding.api.ts 调用 GET /slot-console/api/onboarding/recommend-game"
status: pending
- id: "lobby-onboarding-popup"
content: "lobby新用户注册成功后展示 OnboardingGamePopup倒计时 + LET'S PLAY + 关闭)"
status: pending
isProject: false
---
# is_new_user + console recommend-game 方案
## 方案评价
该方案**推荐采用**,理由:
- 符合 SSOT [`register_popup_game_entry.md`](docs/requirements/register_popup_game_entry.md) 服务边界user 管身份console 管展示聚合
- 前端仍直连 `slot-user` 注册,无需改注册入口
- `MobileRegisterService` / `ImeiRegisterService` 已有「账号存在则走登录」逻辑,正好需要 `is_new_user` 区分
- 老用户误走 register 接口时不会触发 onboarding 弹窗
- 注册主链路不受 center/console 降级影响(推荐游戏失败只影响弹窗)
```mermaid
sequenceDiagram
participant FE as lobby_PWA
participant User as slot_user
participant Console as slot_console
participant Center as slot_center
participant GGame as g_game
FE->>User: POST register
alt 新用户创建
User-->>FE: token + uid + is_new_user=true
FE->>Console: GET recommend-game (Bearer token)
Console->>Center: innerapi onboarding/channel-game
Center-->>Console: game_codes
Console->>GGame: 按 game_code 查可展示游戏
Console-->>FE: onboarding payload
else 老用户登录回退
User-->>FE: token + uid + is_new_user=false
Note over FE: 不请求 recommend-game
end
```
---
## Part 1slot-user 增加 `is_new_user`
**涉及仓库**[`/Users/ray/Documents/project/www/slot/slot_user`](/Users/ray/Documents/project/www/slot/slot_user)
### 现状
[`MobileRegisterService.php`](/Users/ray/Documents/project/www/slot/slot_user/app/service/register/MobileRegisterService.php) 与 [`ImeiRegisterService.php`](/Users/ray/Documents/project/www/slot/slot_user/app/service/register/ImeiRegisterService.php) 在 `check()` 返回 false 时会走 `MobileLoginService` / `ImeiLoginService`,但 Controller 无法区分「新建」与「登录回退」。
[`NameRegisterService.php`](/Users/ray/Documents/project/www/slot/slot_user/app/service/register/NameRegisterService.php) 账号已存在直接返回 null报错成功则一定是新用户。
### 实现方式(最小改动)
在 [`AbstractRegisterService.php`](/Users/ray/Documents/project/www/slot/slot_user/app/service/register/AbstractRegisterService.php) 增加:
```php
/** 本次 register 请求是否真正创建了新用户false 表示走了登录回退) */
public bool $isNewUser = false;
```
各 RegisterService 在对应分支赋值:
| 类型 | 新用户创建 | 登录回退 |
|------|-----------|---------|
| mobile | `isNewUser = true` | `isNewUser = false` |
| imei | `isNewUser = true` | `isNewUser = false` |
| name | `isNewUser = true` | N/A失败返回 null |
在 [`UserController::register()`](`/Users/ray/Documents/project/www/slot/slot_user/app/api/controller/UserController.php) 响应中追加:
```php
$data['is_new_user'] = $registerService->isNewUser;
```
**login 接口不改**(老用户走 login 本就不弹窗)。
### 注册响应示例
```json
{
"token": "...",
"uid": 1387459,
"expires_in": 1234567890,
"is_new_user": true
}
```
---
## Part 2slot-console 实现 recommend-game BFF03 子需求)
**涉及仓库**[`/Users/ray/Documents/project/www/ray/slot-console`](/Users/ray/Documents/project/www/ray/slot-console)
### 路由
沿用 Webman 自动路由(参照 [`RewardGrantController`](slot-console/app/api/controller/RewardGrantController.php)
```text
GET /api/onboarding/recommend-game
```
需登录JWT`$request->userEntity->uid` / `$request->userEntity->source` 取用户上下文。
### 核心 Logic新建
`app/api/logic/OnboardingRecommendLogic.php` 编排步骤(对齐需求 §5.1
1.`uid``source`source 为空按 `DEFAULT` 交给 center 回退)
2.`CenterClient``onboardingChannelGame($source)`SDK 已有:[`CenterService.php`](slot_sdk/src/service/center/CenterService.php)
3. `enabled=false``game_codes` 为空 → 返回降级 payload
4.`game_codes` **简单随机**选一个;查 [`GGame`](slot-console/app/model/common/GGame.php) 可展示游戏
5. 不可用则剔除后继续随机;全部不可用 → `enabled=false`
6. 组装返回(字段见需求 §6.1
### GGame 扩展
在 [`GGame.php`](slot-console/app/model/common/GGame.php) 新增按 `game_code` 查单条可展示游戏方法(复用 `applyLobbyListableJoinsAndWhere`,返回 `id/name/icon_url/provider_code/game_code/category`)。
可参照 slot-admin [`OnboardingChannelGameLogic::resolveGamesByGameCodes`](slot-admin/app/game/logic/OnboardingChannelGameLogic.php) 的字段映射,但用户侧只返回需求文档定义的 `game` 结构。
### GatewayService可选薄封装
`app/service/center/CenterGatewayService.php`:封装 `CenterClient` 初始化host 来自 `ShareConfigService::get('centerApiHost')`header `server-name`Logic 不直接拼 HTTP。
### Controller新建
`app/api/controller/OnboardingController.php`
- `recommendGame(Request $request)` → 调 Logic → `success($payload)`
- center 调用失败 / 异常:记录日志,返回 `enabled=false`**不抛错阻断**
### 展示字段来源
| 字段 | 来源 |
|------|------|
| `uid` / `player_number` | JWT uid |
| `player_number_display` | `number_format($uid)` |
| `platform_name` | `ShareConfigService::sourceInfo($source)` 或 center 渠道配置(首版可用 `config`/常量,与运营名对齐) |
| `trial_bonus_amount` | 活动配置常量 `20000`$20 × 1000对齐 [`03_wallet_fund_flow.md`](docs/requirements/trial_withdrawal/03_wallet_fund_flow.md) |
| `countdown_seconds` | 有游戏时 `3`,无游戏时 `0` |
| `game` | GGame 查询结果 |
---
## Part 3lobby 前端接入(游戏大厅 PWA
**涉及仓库**[`/Users/ray/Documents/project/www/ray/lobby`](/Users/ray/Documents/project/www/ray/lobby)
这就是你的前端应用:**Vue 3 + Vite + Vant**,网关前缀通过 `VITE_APP_API_BASE_URL` 代理到各 slot 服务。
### 现有注册链路(已确认)
| 项 | 位置 |
| --- | --- |
| 注册 API | [`lobby/src/api/user/auth.api.ts`](lobby/src/api/user/auth.api.ts) → `POST /slot-user/api/user/register` |
| 注册编排 | [`lobby/src/services/user/auth.service.ts`](lobby/src/services/user/auth.service.ts) → `setAuth(result)` 存 token |
| 类型定义 | [`lobby/src/types/user/auth.d.ts`](lobby/src/types/user/auth.d.ts)(目前只有 token 字段,**无** `is_new_user` |
| Console API 范例 | [`lobby/src/api/console/console.ts`](lobby/src/api/console/console.ts) → `/slot-console/api/...` |
| 注册入口(需统一改) | [`telregisterbox.vue`](lobby/src/pages/sign-in/components/telregisterbox.vue)、[`emailregisterbox.vue`](lobby/src/pages/sign-in/components/emailregisterbox.vue)、[`login/index.vue`](lobby/src/pages/login/index.vue)、[`auth/telegram-callback/index.vue`](lobby/src/pages/auth/telegram-callback/index.vue)、[`register/index.vue`](lobby/src/pages/register/index.vue) |
典型流程(以手机注册为例):
```text
userAuthService.register(...) → setAuth → initializeUserSession() → router.push('/')
```
弹窗现有模式可参考 WS `Notify.popup`[`notifyPopupPayload.ts`](lobby/src/utils/notifyPopupPayload.ts) + `DailySignInBox.vue`),但 onboarding **不走 WS**,注册成功后 **HTTP 主动拉取**
### lobby 改动点
**1. 类型与 API**
- `AuthResponse` 增加 `is_new_user?: boolean``uid?: number`
- 新建 `lobby/src/types/console/onboarding.d.ts`(对齐需求 §6.1 payload
- 新建 `lobby/src/api/console/onboarding.api.ts`
```ts
GET /slot-console/api/onboarding/recommend-game
```
**2. 注册编排(集中在一处,避免各页面重复)**
扩展 [`auth.service.ts`](lobby/src/services/user/auth.service.ts)
```text
register(data):
1. result = registerAPI(data)
2. setAuth(result)
3. if result.is_new_user === true:
onboarding = await fetchRecommendGame() // 已 setAuthrequest 拦截器会自动带 Authorization
return { ...result, onboarding }
4. return result
```
recommend-game 失败时 **不阻断注册**`onboarding.enabled = false`,正常进首页。
**3. 弹窗 UI**
新建组件如 `lobby/src/components/OnboardingGamePopup.vue`(或 `pages/home/components/`
- `enabled=true` 时展示:玩家编号、`trial_bonus_amount`、游戏封面、`countdown_seconds` 倒计时
- LET'S PLAY → 用 `game.id` 走现有进游戏链路([`game.api.ts`](lobby/src/api/game/game.api.ts) / 首页 launch 逻辑)
- 关闭 → 仅关弹窗,不调后端
**4. 触发时机**
在注册成功且 `is_new_user` 的分支里,**跳转首页前或首页 onMounted** 展示弹窗(推荐:注册页拿到 onboarding 后 `router.push('/')` + 通过 pinia/commonStore 或 route state 传给首页展示,避免路由切换丢状态)。
各注册入口最终都应走 `userAuthService.register`,无需每个页面单独写 recommend 逻辑。
### 前端调用链路lobby
```text
1. lobby → POST /slot-user/api/user/register
2. is_new_user === true
lobby → GET /slot-console/api/onboarding/recommend-game
enabled=true → OnboardingGamePopup
3. is_new_user === false → 正常登录,无弹窗
4. initializeUserSession() → 进大厅首页 /
```
---
## Part 4文档更新
更新 [`03_console_recommend_game_bff.md`](docs/requirements/register_popup_game_entry/03_console_recommend_game_bff.md) 与父文档 §3.1:明确前端为 **lobby PWA**,注册后条件请求 recommend-game。
**注意**:后端不记录弹窗状态;刷新后是否再弹由 lobby 本地逻辑决定(需求 §8.3)。
---
## Part 5不在本次范围
- user 不调 center / console innerapi按你确认的方案
- 不新增 `user_onboarding_popup`
- 不实现 `mark_shown` / `mark_entered`
- login 接口不加 `is_new_user`(非必须)
---
## 验收要点
**后端**
1. 新手机号/新 IMEI 注册 → `is_new_user=true`;同账号再次 register → `is_new_user=false`
2. `is_new_user=true` 时 recommend-game 返回渠道游戏池随机一款(或 DEFAULT 回退)
3. center 无配置 / 调用失败 → `enabled=false`,注册仍成功
4. `game_codes` 含下架游戏 → 跳过,从剩余随机
**lobby 前端**
5. 老用户走 register 登录回退(`is_new_user=false`)→ 不请求 recommend-game无弹窗
6. 新用户注册成功 → 自动请求 recommend-game`enabled=true` 时展示 onboarding 弹窗
7. 点击 LET'S PLAY → 进入推荐游戏;点击关闭 → 留在大厅,不影响 Trial Balance
8. 各注册入口(手机/邮箱/Telegram 等)行为一致(均经 `userAuthService.register`