280 lines
12 KiB
Markdown
280 lines
12 KiB
Markdown
<!-- 09c08dbd-5e66-4d08-b29e-090a18ddb10d -->
|
||
---
|
||
todos:
|
||
- id: "user-is-new-user"
|
||
content: "slot-user:AbstractRegisterService 增加 isNewUser 标志,Mobile/Imei/Name RegisterService 赋值,UserController::register 返回 is_new_user"
|
||
status: pending
|
||
- id: "console-ggame-query"
|
||
content: "slot-console:GGame 新增按 game_code 查可展示单条游戏方法"
|
||
status: pending
|
||
- id: "console-recommend-logic"
|
||
content: "slot-console:OnboardingRecommendLogic(center 取池 → 随机 → g_game 组装 → 降级)"
|
||
status: pending
|
||
- id: "console-recommend-api"
|
||
content: "slot-console:OnboardingController + 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: "lobby:AuthResponse 增加 is_new_user;auth.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 1:slot-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 2:slot-console 实现 recommend-game BFF(03 子需求)
|
||
|
||
**涉及仓库**:[`/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 3:lobby 前端接入(游戏大厅 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() // 已 setAuth,request 拦截器会自动带 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`)
|