From 575aa279bdb8d02084b3c3b21c2fc2d107f199c1 Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Fri, 15 May 2026 15:46:06 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E6=8E=A5=E5=8F=A3/api/game?= =?UTF-8?q?/lobbyInit=EF=BC=8C=E6=96=B0=E5=A2=9E=E8=B5=94=E7=8E=87?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/controller/Game.php | 2 + app/common/library/game/StreakWinReward.php | 19 +++++++++ docs/36字花-移动端接口设计草案.md | 45 +++++++++++++++------ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/app/api/controller/Game.php b/app/api/controller/Game.php index 40b6ed2..f1688b1 100644 --- a/app/api/controller/Game.php +++ b/app/api/controller/Game.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace app\api\controller; use app\common\library\game\BetChips; +use app\common\library\game\StreakWinReward; use app\common\library\game\ZiHuaDictionary; use app\common\model\BetOrder; use app\common\model\GameRecord; @@ -67,6 +68,7 @@ class Game extends MobileBase BetChips::lobbyChipsPayload() ), 'dictionary' => $items, + 'streak_win_reward' => StreakWinReward::lobbyPayload(), 'user_snapshot' => [ 'coin' => $user->coin, 'current_streak' => $user->current_streak ?? 0, diff --git a/app/common/library/game/StreakWinReward.php b/app/common/library/game/StreakWinReward.php index 5e5091c..862625f 100644 --- a/app/common/library/game/StreakWinReward.php +++ b/app/common/library/game/StreakWinReward.php @@ -106,6 +106,25 @@ final class StreakWinReward return self::$cache; } + /** + * lobbyInit 用:连胜赔率档位(与后台「连胜奖励」、派彩公式一致)。 + * + * @return array{rows: list} + */ + public static function lobbyPayload(): array + { + $rows = []; + foreach (self::loadRows() as $row) { + $rows[] = [ + 'streak' => (int) ($row['streak'] ?? 0), + 'odds_factor' => (int) ($row['odds_factor'] ?? 0), + 'is_jackpot' => ($row['is_jackpot'] ?? false) === true, + ]; + } + + return ['rows' => $rows]; + } + /** * streak_at_bet 为下注时快照(0 表示尚未连胜);档位取 min(streak_at_bet+1, 10)。 */ diff --git a/docs/36字花-移动端接口设计草案.md b/docs/36字花-移动端接口设计草案.md index 4248590..2fae430 100644 --- a/docs/36字花-移动端接口设计草案.md +++ b/docs/36字花-移动端接口设计草案.md @@ -235,6 +235,11 @@ - `default_bet_chip_id`:int(含义:默认选中的筹码标识,来自 `game_config.default_bet_chip_id`,非法或指向无效档位时服务端回退为首个有效档) - `min_bet_per_number`:string(含义:单号码最小下注额,须 ≤ 所选筹码面额且受后台配置约束) - `max_bet_per_number`:string(含义:单号码最大下注额) +- `streak_win_reward`:object(含义:连胜赔率配置,来自 `game_config.streak_win_reward`,与后台「连胜奖励」一致) + - `rows`:array(固定 1~10 档,按 `streak` 升序) + - `streak`:int(连胜档位 1~10;下注时 `streak_at_bet=0` 适用档位 1,`streak_at_bet=n` 适用档位 `min(n+1, 10)`) + - `odds_factor`:int(赔率乘数;中奖派彩 = 本笔 `total_amount` × `odds_factor`) + - `is_jackpot`:bool(是否大奖档,触发 `jackpot.hit` 等流程) - `dictionary`:array - `number`:int(1-36,含义:字花编号) - `name`:string(含义:字花名称) @@ -713,24 +718,38 @@ > 本版本已移除 webman/push 频道模式;H5 前端使用原生 WebSocket 直连,HTTP 轮询仅作为弱网兜底。 +### 7.0 部署与连接前置条件(当前实现) + +以下与代码 `app/process/GameWebSocketServer.php`、`config/process.php`、`app/common/library/admin/WebSocketConfigHelper.php` 一致,**文档此前未写明的条件**在此补齐: + +- **独立进程**:需在运行环境中启动 Webman 自定义进程 **`gameWebSocketServer`**(见 `config/process.php`),默认监听 **`H5_WEBSOCKET_LISTEN`**(缺省为 `websocket://0.0.0.0:3131`)。未启动进程则客户端无法建连或立刻断线。 +- **连接 URL**:优先使用环境变量 **`H5_WEBSOCKET_URL`**(完整 `ws://` / `wss://` 地址,建议带路径,如 `.env-example` 所示)。未配置时,后台 **`GET /admin/test.GameCurrentStatus/wsConfig`**(及游戏实时页同类接口)会通过 `WebSocketConfigHelper` 按当前请求的 `Host` / `X-Forwarded-*` 推导 `ws(s)://{host}/ws/`,本地兜底 **`ws://127.0.0.1:3131/ws/`**。 +- **移动端配置缺口**:**`POST /api/game/lobbyInit` 当前不下发 WebSocket 地址**;H5 需与运维约定同一套 `H5_WEBSOCKET_URL`(打包进前端配置、远程配置中心等),与 HTTP API 基址可不同域。 +- **混合内容**:若 H5 页面为 **HTTPS**,浏览器要求 WebSocket 使用 **`wss://`**,否则会被拦截。 +- **事件投递依赖 Redis**:HTTP 侧业务通过 **`GameWebSocketEventBus`**(Redis 列表)将事件投递到 WebSocket 进程;Redis 不可用或队列异常时,**除 `admin.live.snapshot` 外**的广播类推送可能收不到。后台若订阅了 `admin.live.snapshot`,服务端有**每秒直连构建快照**的兜底,不依赖队列。 +- **鉴权(重要)**:**当前 `GameWebSocketServer` 在握手阶段不校验** URL Query 中的 `token` / `auth_token` / `user-token` 等;**任何人拿到地址即可建立连接**(与 §1 HTTP 接口必须 `auth-token` + `user-token` 不同)。Query 中上述参数为**预留/习惯写法**,便于后续若要在 `onWebSocketConnect` 中实现鉴权再与 HTTP 对齐;**文档中若写「未登录返回 1101」属规划口径,非现网行为**。 +- **订阅才有业务推送**:建连后仅会收到握手首帧(见下)及本连接已订阅主题的消息;不发送 `subscribe` 则收不到 `period.tick` 等(`admin.live.snapshot` 同上,需显式订阅)。 + ### 7.1 WebSocket 连接与消息 -- **连接地址**:由服务端配置下发(后台测试页读取 `H5_WEBSOCKET_URL`) +- **连接地址**:见 **§7.0**(环境变量 `H5_WEBSOCKET_URL` 或后台 `wsConfig` 返回的 `ws_url`) - **客户端**:浏览器原生 `WebSocket`(`ws://` / `wss://`) -- **连接时携带参数(建议)**: - - URL Query:`token`(用户登录态 user-token)、`auth_token`(接口鉴权)、`device_id`(设备标识)、`lang`(`zh/en`) - - 示例:`wss://ws.example.com/ws?token=xxx&auth_token=xxx&device_id=ios_001&lang=zh` -- **连接成功返回(服务端首帧建议)**: +- **连接时携带参数(可选 / 预留)**: + - URL Query 可带 `token`(与 HTTP 头 `user-token` 同义习惯)、`auth_token`(与 HTTP 头 `auth-token` 同义习惯)、`device_id`、`lang` 等,**当前服务端不解析、不校验**;若后续版本实现握手鉴权,以发布说明为准。 + - 示例(习惯写法):`wss://ws.example.com/ws?token=xxx&auth_token=xxx&device_id=ios_001&lang=zh` +- **连接成功首帧(当前实现)**: - `event`:`ws.connected` - - `connection_id`:连接唯一标识 - - `server_time`:服务器时间戳(秒) - - `heartbeat_interval`:心跳间隔(秒) -- **连接失败返回(建议)**: - - `event`:`ws.error` - - `code`:错误码(如 `1101` 未登录、`1103` 鉴权失败) - - `message`:错误描述 + - `message`:固定文案 `WebSocket connected`(便于联调日志) + - `connection_id`:连接唯一标识(进程内) + - `server_time`:服务器时间戳(**秒**,int) + - `heartbeat_interval`:建议心跳间隔(**秒**,当前实现固定为 `30`) +- **连接后错误帧(当前实现,非 HTTP 业务码)**: + - JSON 无法解析:`event`=`ws.error`,`message`=`Invalid JSON payload`(无 `code` 或与 HTTP `code` 不同体系) + - 未知 `action`:`event`=`ws.error`,`message`=`Unsupported action`,并可能带 `received_action` + - 进程级异常:`onError` 回包含 `event`=`ws.error`、`message`=`Server internal error` 及 Workerman 侧 `code`/`detail`(**非** §1.2 的 HTTP API `code` 段) - **建议消息**: - 心跳:`{"action":"ping"}` + - 服务端对心跳的当前实现回包:`{"event":"pong","server_time":"YYYY-mm-dd HH:ii:ss"}`(**注意**:此处 `server_time` 为**本地时间字符串**,与业务推送帧里 `server_time` 常用**秒级 int** 不一致,客户端解析时请分支处理) - 订阅状态流:`{"action":"subscribe","topics":["period.tick","period.opened"]}` - 订阅资金流:`{"action":"subscribe","topics":["bet.accepted","wallet.changed"]}` - 订阅托管流:`{"action":"subscribe","topics":["auto.spin.progress","wallet.changed"]}` @@ -790,7 +809,7 @@ 1. `GET /api/v1/authToken?secret=xxx×tamp=xxx&device_id=xxx&signature=xxx` 获取 `auth-token` 2. `POST /api/user/login` 登录(请求头带 `auth-token`) 3. `POST /api/game/lobbyInit` 拉首页初始化(请求头带 `auth-token`) -4. 建立 WebSocket(H5)连接,发送订阅消息监听状态流 +4. 取得 WebSocket 基址(**当前非 lobbyInit 下发**:与运维/打包配置中的 `H5_WEBSOCKET_URL` 或自建配置接口一致)后建立 WebSocket 连接,**立即发送 `subscribe`** 监听状态流(见 §7.0 / §7.1) 5. 用户下注调用 `POST /api/game/placeBet` 6. 下单后以 `placeBet.balance_after` 与 `wallet.changed` 同步余额 7. 断线或页面回前台时,重连 WebSocket 并重新订阅主题回补实时状态