1.新增退出登录的接口

This commit is contained in:
2026-05-29 14:25:59 +08:00
parent 4324c19d30
commit 4bd332a6ff
5 changed files with 138 additions and 19 deletions

View File

@@ -5,6 +5,8 @@
**补充2026-04**:§**1.5** 描述服务端 **Redis 热点缓存**`GameHotDataRedis`**不改变**各接口 URL、参数与响应字段约定仅供联调与运维对照。
**补充2026-05**:§**1.3** / §**1.4** / §**2.3** 描述 **单设备登录****`POST /api/user/logout`** 退出接口(依赖 Redis 存储 `device_id` 绑定)。
## 1. 设计约定
### 1.1 基础约定
@@ -63,6 +65,11 @@
### 1.3 鉴权方式
- **接口鉴权auth-token**:所有移动端业务接口请求时必须携带请求头 `auth-token`(由 `/api/v1/authToken` 签发)
- **用户登录鉴权user-token**:需要登录的接口携带请求头 `user-token`token 失效后调用刷新或重新登录
- **单设备登录(实现约束)**
- 获取 `auth-token` 时 Query 中的 `device_id` 会与该 `auth-token` 绑定Redis
- 同一账号**仅允许一个活跃设备**:新设备登录成功后,会清除该用户其它 `user-token` / `refresh_token`,并绑定新 `device_id`
- 旧设备继续携带原 `user-token` 访问需登录接口时,返回 `code=1101``message` 为「您的账号已在其他设备登录,请重新登录」(`lang=en` 时为英文)
- 客户端应为每个安装实例生成**稳定且唯一**的 `device_id`(勿每次随机,否则会被视为新设备)
### 1.4 获取接口鉴权 Tokenauth-token
- **GET** `/api/v1/authToken`
@@ -87,6 +94,10 @@
- `expires_in`int含义有效期秒数
- `server_time`int含义服务器时间戳用于校时
服务端行为(与单设备登录相关):
- 签发成功后将 `device_id``auth_token` 写入 RedisTTL 与 `expires_in` 一致
- 后续登录、已登录业务接口、WebSocket 握手均以此 `auth-token` 所绑定的 `device_id` 与用户活跃设备比对
可能错误码:
- `1001` 参数缺失
- `1002` 参数格式错误
@@ -128,13 +139,13 @@
### 2.1 注册
- **POST** `/api/user/register`
- 用途仅手机号注册并绑定邀请归属admin/channel
- 用途仅手机号注册并绑定邀请归属admin/channel;注册成功后会自动登录
- 请求头:必带 `auth-token`(设备标识以该 token 绑定的 `device_id` 为准,见 §1.3
请求参数:
- `username`string手机号含义注册账号仅支持大陆手机号
- `password`string明文经 HTTPS 传输(含义:登录密码,服务端需加盐哈希存储)
- `invite_code`string必填含义子代理邀请码用于绑定渠道 `channel_id` 与归属)
- `device_id`string可选含义设备标识用于风控与登录记录
返回参数:
- `user-token`string含义后续接口登录态令牌用于需要登录的接口请求头
@@ -149,11 +160,14 @@
### 2.2 登录
- **POST** `/api/user/login`
- 请求头:必带 `auth-token`(其绑定的 `device_id` 即本次登录设备,**无需**在 body 重复传 `device_id`
请求参数:
- `username`string含义登录账号当前支持手机号
- `password`string含义登录密码
- `device_id`string可选含义设备标识辅助风控
服务端行为:
- 登录成功后会清除该用户其它会话 token并将活跃设备设为当前 `auth-token` 对应 `device_id`(见 §1.3
返回参数:
- `user-token`string含义访问令牌用于需要登录的接口请求头
@@ -166,7 +180,29 @@
- `channel_id`int含义归属渠道 ID
- `risk_flags`int含义风控状态位
### 2.3 获取当前用户信息
### 2.3 退出登录
- **POST** `/api/user/logout`
- 用途:主动退出当前账号,作废登录态并释放单设备占用
请求头:
- `auth-token`:必填
- `user-token`:已登录时必填
请求参数(可选):
- `refresh_token`string含义刷新令牌建议传入以便服务端一并删除未传且已登录时服务端尝试删除当前会话的 refresh_token
返回参数:
- `data`:空对象 `{}`
服务端行为:
- 删除当前 `user-token`、清除 Redis 中该用户的活跃 `device_id` 绑定
- 若提供 `refresh_token` 则删除对应 refresh 记录
- **幂等**:未登录或 token 已失效时仍返回 `code=1`(便于客户端清理本地缓存)
可能错误码:
- `1101``auth-token` 缺失或无效(与 §1.4 一致)
### 2.4 获取当前用户信息
- **POST** `/api/user/profile`
返回参数(金额类字段统一 2 位小数字符串,与钱包展示口径一致):
@@ -203,8 +239,9 @@
- `count`int当前待审核提现订单数
- `max`int单用户最多允许的待审核提现数当前为 `3`;超过 `withdrawCreate` 返回 `code=2004`
### 2.4 刷新令牌(可选)
### 2.5 刷新令牌(可选)
- **POST** `/api/user/refreshToken`
- 请求头:必带 `auth-token`;已登录会话须与当前活跃 `device_id` 一致§1.3
请求参数:
- `refresh_token`string含义续签访问令牌的凭证
@@ -765,7 +802,7 @@
- **混合内容**:若 H5 页面为 **HTTPS**,浏览器要求 WebSocket 使用 **`wss://`**,否则会被拦截。
- **事件投递依赖 Redis**HTTP 侧业务通过 **`GameWebSocketEventBus`**Redis 列表)将事件投递到 WebSocket 进程Redis 不可用或队列异常时,**除 `admin.live.snapshot` 外**的广播类推送可能收不到。后台若订阅了 `admin.live.snapshot`,服务端有**每秒直连构建快照**的兜底,不依赖队列。
- **握手鉴权2026-05 重构后强制)**`GameWebSocketServer::onWebSocketConnect` 通过 `GameWebSocketAuthHelper::authorize` 校验 URL Query。两种合法身份
- **mobileH5/移动端)**:必须同时携带 Query **`auth-token`**、**`user-token`**(与 HTTP 请求头同名,**统一用连字符**)。校验通过后绑定 `user_id`,分发器仅向其推送本人的 user 级主题。服务端仍兼容旧别名 `auth_token` / `user_token` 解析,但**新接入请只用连字符**。
- **mobileH5/移动端)**:必须同时携带 Query **`auth-token`**、**`user-token`**(与 HTTP 请求头同名,**统一用连字符**)。校验通过后绑定 `user_id`,分发器仅向其推送本人的 user 级主题;并校验 `auth-token` 绑定 `device_id` 与用户活跃设备一致§1.3),异设备会话将被拒绝。服务端仍兼容旧别名 `auth_token` / `user_token` 解析,但**新接入请只用连字符**。
- **admin后台/运维)**:必须携带 Query **`admin-ws-token`**(由后台 `wsConfig` 签发,写入 Redis默认 TTL 7200s`ws_url` 已自动拼接该参数admin 模式 `user_id=0`,可观测全量推送。
- 任一身份不通过 → 服务端发送 `{"event":"ws.error","code":1101,"message":"Authentication failed: ..."}` 并立即 `close`
- **服务端按 user_id 过滤user 级主题)**:以下 topic 的 `data.user_id` 必须 **等于** 当前连接绑定的 `user_id` 才会下发——**`bet.win` / `user.streak` / `wallet.changed` / `bet.accepted` / `auto.spin.progress`**。其它 topic`period.tick` / `period.opened` / `jackpot.hit` / `admin.*`按订阅广播。admin 模式不参与此过滤。
@@ -918,13 +955,14 @@ php scripts/republish_bet_win.php --period-no=20260526-183418-c9c90ef1
## 8. 移动端完整调用流程
## 8.1 首次进入游戏
1. `GET /api/v1/authToken?secret=xxx&timestamp=xxx&device_id=xxx&signature=xxx` 获取 `auth-token`
2. `POST /api/user/login` 登录(请求头带 `auth-token`
3. `POST /api/game/lobbyInit` 拉首页初始化(请求头带 `auth-token`
1. `GET /api/v1/authToken?secret=xxx&timestamp=xxx&device_id=xxx&signature=xxx` 获取 `auth-token``device_id` 须为客户端稳定设备码)
2. `POST /api/user/login` 登录(请求头带 `auth-token` + 登录成功后带 `user-token`
3. `POST /api/game/lobbyInit` 拉首页初始化(请求头带 `auth-token` + `user-token`
4. 取得 WebSocket 基址(**当前非 lobbyInit 下发**:与运维/打包配置中的 `H5_WEBSOCKET_URL` 或自建配置接口一致)后建立 WebSocket 连接,**立即发送 `subscribe`** 监听状态流(见 §7.0 / §7.1**务必包含 `bet.win`**
5. 用户下注调用 `POST /api/game/placeBet`
6. 下单后以 `placeBet.balance_after``wallet.changed` 同步余额;开奖结算后监听 **`bet.win`**`is_win=true`)展示中奖,大奖档看 `data.is_jackpot`(连接已绑定用户,载荷无 `user_id`
7. 断线或页面回前台时,重连 WebSocket 并重新订阅主题回补实时状态
8. 用户主动退出:`POST /api/user/logout`(请求头 `auth-token` + `user-token`body 可选 `refresh_token`),成功后清除本地 token 并关闭 WebSocket
## 8.2 充值到下注到提现闭环
1. 拉取档位:`POST /api/finance/depositTierList`(玩家选择一档,并记下该档 `channels[].code`

View File

@@ -6,6 +6,8 @@ Scope: **platform-wide single period number and single draw result**; channels a
**Addendum (2026-04)**: §**1.5** describes server-side **Redis hot-spot caching** (`GameHotDataRedis`). It does **not** change any interface URL, parameter, or response field contracts; it is for integration testing and operations reference only.
**Addendum (2026-05)**: §**1.3** / §**1.4** / §**2.3** document **single-device login** and **`POST /api/user/logout`** (Redis-backed `device_id` binding).
## 1. Design Conventions
### 1.1 Base Conventions
@@ -64,6 +66,11 @@ Scope: **platform-wide single period number and single draw result**; channels a
### 1.3 Authentication
- **API auth (`auth-token`)**: all mobile business APIs must send header `auth-token` (issued by `/api/v1/authToken`)
- **User session (`user-token`)**: login-protected APIs send header `user-token`; on expiry, refresh or log in again
- **Single-device session (server rule)**:
- `device_id` in the `authToken` query is bound to that `auth-token` in Redis
- Each account allows **one active device** only: a successful login on a new device clears other `user-token` / `refresh_token` rows and binds the new `device_id`
- Old devices calling login-protected APIs get `code=1101` with message like “logged in on another device, please sign in again” (`lang=en` for English)
- Clients must use a **stable unique** `device_id` per install (do not randomize on every launch)
### 1.4 Obtain API Auth Token (`auth-token`)
- **GET** `/api/v1/authToken`
@@ -88,6 +95,10 @@ Response parameters:
- `expires_in`: int (TTL in seconds)
- `server_time`: int (server timestamp for clock sync)
Server behavior (single-device):
- On success, stores `device_id` for this `auth_token` in Redis with TTL = `expires_in`
- Login, login-protected HTTP APIs, and WebSocket handshake compare this `device_id` with the users active device
Possible error codes:
- `1001` missing parameter
- `1002` invalid parameter format
@@ -129,13 +140,13 @@ Possible error codes:
### 2.1 Register
- **POST** `/api/user/register`
- Purpose: phone-only registration with invite attribution (admin/channel)
- Purpose: phone-only registration with invite attribution (admin/channel); auto-login on success
- Headers: `auth-token` required (device id is taken from that tokens bound `device_id`; see §1.3)
Request parameters:
- `username`: string, phone number (registration account; mainland China mobile only)
- `password`: string, plaintext over HTTPS (login password; server stores salted hash)
- `invite_code`: string, required (sub-agent invite code; binds `channel_id` and ownership)
- `device_id`: string, optional (device id for risk control and login logs)
Response parameters:
- `user-token`: string (session token for login-protected APIs)
@@ -150,11 +161,14 @@ Response parameters:
### 2.2 Login
- **POST** `/api/user/login`
- Headers: `auth-token` required (bound `device_id` is the login device; **do not** send `device_id` in body)
Request parameters:
- `username`: string (login account; currently phone)
- `password`: string (login password)
- `device_id`: string, optional (risk assist)
Server behavior:
- On success, clears other sessions for this user and sets active device to the `auth-token`s `device_id` (§1.3)
Response parameters:
- `user-token`: string (access token for login-protected APIs)
@@ -167,7 +181,29 @@ Response parameters:
- `channel_id`: int (attribution channel id)
- `risk_flags`: int (risk status bitmask)
### 2.3 Current User Profile
### 2.3 Logout
- **POST** `/api/user/logout`
- Purpose: sign out, invalidate session, release single-device lock
Headers:
- `auth-token`: required
- `user-token`: required when logged in
Request parameters (optional):
- `refresh_token`: string (recommended; server deletes it; if omitted while logged in, server tries current session refresh token)
Response:
- `data`: empty object `{}`
Server behavior:
- Deletes current `user-token`, clears Redis active `device_id` for the user
- Deletes `refresh_token` when provided
- **Idempotent**: returns `code=1` even if already logged out (client can clear local storage)
Possible error codes:
- `1101`: missing or invalid `auth-token` (same as §1.4)
### 2.4 Current User Profile
- **POST** `/api/user/profile`
Response parameters (amount fields as 2-decimal strings, aligned with wallet display):
@@ -204,8 +240,9 @@ Response parameters (amount fields as 2-decimal strings, aligned with wallet dis
- `count`: int (pending-review withdraw orders)
- `max`: int (max pending-review withdraws per user, currently `3`; exceeds → `withdrawCreate` returns `code=2004`)
### 2.4 Refresh Token (Optional)
### 2.5 Refresh Token (Optional)
- **POST** `/api/user/refreshToken`
- Headers: `auth-token` required; active `device_id` must match (§1.3)
Request parameters:
- `refresh_token`: string (credential to renew access token)
@@ -680,7 +717,7 @@ Aligned with `app/process/GameWebSocketServer.php`, `config/process.php`, `app/c
- **Mixed content**: HTTPS pages require **`wss://`**.
- **Redis event bus**: HTTP uses **`GameWebSocketEventBus`** (Redis list); if Redis down, broadcast pushes may fail except **`admin.live.snapshot`** has per-second direct snapshot fallback.
- **Handshake auth (2026-05, mandatory)**: `GameWebSocketServer::onWebSocketConnect` via `GameWebSocketAuthHelper::authorize` on URL query:
- **mobile**: query **`auth-token`** + **`user-token`** (hyphenated; legacy `auth_token`/`user_token` parsed but **use hyphens for new clients**). Binds `user_id`; user topics only to owner.
- **mobile**: query **`auth-token`** + **`user-token`** (hyphenated; legacy `auth_token`/`user_token` parsed but **use hyphens for new clients**). Binds `user_id`; user topics only to owner. Also validates `auth-token`s bound `device_id` against the users active device (§1.3); other-device sessions are rejected.
- **admin**: query **`admin-ws-token`** (from admin `wsConfig`, Redis, TTL 7200s). `user_id=0`, full observability.
- Failure → `{"event":"ws.error","code":1101,"message":"Authentication failed: ..."}` then `close`.
- **Server filter (user topics)**: `bet.win`, `user.streak`, `wallet.changed`, `bet.accepted`, `auto.spin.progress` only if `data.user_id` equals connection `user_id`. Others broadcast by subscription. Admin mode not filtered.
@@ -811,13 +848,14 @@ Integration: `php scripts/verify_ws_topic_subscribe.php`.
## 8. Mobile End-to-End Call Flows
## 8.1 First Game Entry
1. `GET /api/v1/authToken?secret=xxx&timestamp=xxx&device_id=xxx&signature=xxx``auth-token`
2. `POST /api/user/login` (header `auth-token`)
3. `POST /api/game/lobbyInit` (header `auth-token`)
1. `GET /api/v1/authToken?secret=xxx&timestamp=xxx&device_id=xxx&signature=xxx``auth-token` (stable `device_id` per install)
2. `POST /api/user/login` (headers `auth-token`; after login also `user-token`)
3. `POST /api/game/lobbyInit` (headers `auth-token` + `user-token`)
4. WebSocket base URL (**not in lobbyInit today**: ops/bundle `H5_WEBSOCKET_URL` or custom config) → connect, **send `subscribe` immediately** (§7.0/7.1; **include `bet.win`**)
5. `POST /api/game/placeBet`
6. Balance: `placeBet.balance_after` + `wallet.changed`; after draw, **`bet.win`** (`is_win=true`), jackpot style via `data.is_jackpot` (no `user_id` in payload)
7. On disconnect/foreground, reconnect and resubscribe
8. User logout: `POST /api/user/logout` (headers `auth-token` + `user-token`, optional body `refresh_token`); clear local tokens and close WebSocket
## 8.2 Deposit → Bet → Withdraw
1. `POST /api/finance/depositTierList` (pick tier + `channels[].code`)