1.重构实时消息WebSocket连接
2.MySQL备份
This commit is contained in:
@@ -264,7 +264,7 @@
|
||||
## 4. 下注与对局模块(game/bet)
|
||||
|
||||
### 4.1 获取当前期详情
|
||||
- **POST** `/api/game/periodCurrent`
|
||||
- **POST** `/api/game/currentStatus`(兼容旧路径 `/api/game/periodCurrent`)
|
||||
|
||||
返回参数:
|
||||
- `runtime_enabled`:bool(含义:同 `lobbyInit.runtime_enabled`)
|
||||
@@ -276,19 +276,22 @@
|
||||
- `result_number`:int/null(未开奖为 null,含义:开奖号码)
|
||||
|
||||
### 4.2 提交下注
|
||||
- **POST** `/api/game/betPlace`
|
||||
- 用途:单期手动下注;玩家只需选择**压注号码**与**本笔压注总金额**。开奖只出一个号码,若该号码 ∈ 所选号码集合即视为**中奖**,派彩按整笔 `bet_amount`(落库为 `total_amount`)× 赔率计算(赔率与连胜倍率见服务端实现)。
|
||||
- **POST** `/api/game/placeBet`(兼容旧路径 `/api/game/betPlace`)
|
||||
- 用途:单期手动下注;玩家传入**压注号码**与**单注金额 `single_bet_amount`**。服务端按 `single_bet_amount × numbers数量` 计算本笔总扣款(落库 `total_amount`),开奖只出一个号码,若该号码 ∈ 所选号码集合即视为中奖。
|
||||
|
||||
请求参数:
|
||||
- `period_no`:string(含义:下注目标期号)
|
||||
- `numbers`:string(含义:本次压注号码集合,**英文逗号分隔**,如 `1,8,16`;每个号码为 1–36 的整数,数量不超过 `pick_max_number_count`(同 `lobbyInit.bet_config`),重复号码会去重)
|
||||
- `bet_amount`:string(含义:**本笔整笔压注金额**,> 0;服务端按此金额从余额扣款并写入注单 `total_amount`,**不再**按「单号金额 × 号码个数」计算)
|
||||
- `single_bet_amount`:string(含义:**单注金额**,> 0)
|
||||
- `bet_amount`:string(兼容字段,含义同 `single_bet_amount`)
|
||||
- `idempotency_key`:string(必填,含义:防止重复下单)
|
||||
|
||||
返回参数:
|
||||
- `order_no`:string(含义:下注订单号)
|
||||
- `period_no`:string(含义:实际落单期号)
|
||||
- `status`:string(`accepted`/`rejected`,含义:受理结果)
|
||||
- `single_bet_amount`:string(含义:本次单注金额)
|
||||
- `numbers_count`:int(含义:本次号码数量)
|
||||
- `locked_balance`:string(可选,含义:冻结金额)
|
||||
- `balance_after`:string(含义:下单后余额)
|
||||
- `current_streak`:int(含义:下单后连胜快照)
|
||||
@@ -298,9 +301,22 @@
|
||||
- `3001`:游戏已暂停(`runtime_enabled=false`,后台「游戏实时对局」关闭运行开关或作废本局后未重新开启;与非法流程类错误同段)
|
||||
- `5000`:系统繁忙;或 **用户 Redis 互斥锁**未获取(与后台钱包/并发写同一用户串行,文案与后台一致:「该用户正在被其他管理员操作(钱包/并发保存),请稍后再试」);或 **`coin` 条件更新**未命中(并发下注/派彩/后台已改余额:「扣款失败:该用户余额已被其他请求修改(如下注、派彩或其他管理员已保存),请刷新后重试」)。
|
||||
|
||||
> 说明:一键重复上一注、自动托管开启/停止均由前端控制,客户端在相应时机调用 `/api/game/betPlace` 即可完成,不再提供独立接口。
|
||||
### 4.3 自动托管
|
||||
- **POST** `/api/game/autoSpin`
|
||||
|
||||
### 4.3 查询我的下注记录(最近1个月)
|
||||
请求参数:
|
||||
- `action`:string(`start`/`stop`)
|
||||
- `period_no`:string(`action=start` 时必填)
|
||||
- `numbers`:string(`action=start` 时必填,英文逗号分隔)
|
||||
- `single_bet_amount`:string(`action=start` 时必填,支持兼容字段 `bet_amount`)
|
||||
- `rounds`:int(`action=start` 时必填,>=1)
|
||||
|
||||
返回参数:
|
||||
- `status`:string(`scheduled`/`stopped`)
|
||||
- `auto_mode`:bool
|
||||
- `remaining_rounds`:int(仅 `start` 返回)
|
||||
|
||||
### 4.4 查询我的下注记录(最近1个月)
|
||||
- **POST** `/api/game/betMyOrders`
|
||||
|
||||
请求参数:
|
||||
@@ -655,114 +671,54 @@
|
||||
|
||||
---
|
||||
|
||||
## 7. 推送模块(webman/push)
|
||||
## 7. WebSocket(H5)与状态同步
|
||||
|
||||
> 用于移动端实时监听对局状态、开奖结果、余额变更与强公告事件。
|
||||
> 协议与客户端行为对齐 [Pusher Channels](https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/)(webman/push 内置兼容客户端 `push.js`)。
|
||||
> 参考:[webman/push 官方文档](https://www.workerman.net/doc/webman/plugin/push.html)
|
||||
> 本版本已移除 webman/push 频道模式;H5 前端使用原生 WebSocket 直连,HTTP 轮询仅作为弱网兜底。
|
||||
|
||||
### 7.1 频道命名与职责(优化版)
|
||||
### 7.1 WebSocket 连接与消息
|
||||
|
||||
| 频道名 | 类型 | 订阅方 | 典型事件 |
|
||||
|--------|------|--------|----------|
|
||||
| `private-user-{user.uuid}` | 私有(`private-` 前缀) | 当前登录用户;`{user.uuid}` 与登录态/档案中的 **10 位 `uuid`** 一致 | `bet.accepted`、`wallet.changed`、`withdraw.review_required`、定向 `notice.popout` 等 |
|
||||
| `public-game-period` | 公共 | 所有在线客户端 | `period.tick`、`period.locked`、`period.opened` |
|
||||
| `public-operation-notice` | 公共 | 所有在线客户端 | 全站/渠道级 `notice.popout`(与私有公告二选一或并存,由实现约定) |
|
||||
- **连接地址**:由服务端配置下发(后台测试页读取 `H5_WEBSOCKET_URL`)
|
||||
- **客户端**:浏览器原生 `WebSocket`(`ws://` / `wss://`)
|
||||
- **连接时携带参数(建议)**:
|
||||
- URL Query:`token`(用户登录态 user-token)、`auth_token`(接口鉴权)、`device_id`(设备标识)、`lang`(`zh/en`)
|
||||
- 示例:`wss://ws.example.com/game?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`:错误描述
|
||||
- **建议消息**:
|
||||
- 心跳:`{"action":"ping"}`
|
||||
- 订阅状态流:`{"action":"subscribe","topics":["period.tick","period.opened"]}`
|
||||
- 订阅资金流:`{"action":"subscribe","topics":["bet.accepted","wallet.changed"]}`
|
||||
- 订阅托管流:`{"action":"subscribe","topics":["auto.spin.progress","wallet.changed"]}`
|
||||
|
||||
约定说明:
|
||||
### 7.1A 后台连接方式(管理端联调)
|
||||
|
||||
- **用户私有频道一律使用对外标识 `uuid`,不使用数据库主键 `user_id`**,避免与后台、日志、多端展示口径不一致,并降低枚举内网 ID 的风险。
|
||||
- 名称以 `private-` 开头的频道必须通过 **私有频道鉴权**(见 7.2)成功后才能收到服务端推送。
|
||||
- `public-*` 可直接订阅,无需鉴权 HTTP 步骤。
|
||||
- 后台菜单:仅保留一个菜单 `连接服务器websocket`,用于统一联调 WebSocket
|
||||
- 后台连接入口:
|
||||
- `/admin/test.GameCurrentStatus/wsConfig`
|
||||
- 后台页面能力:
|
||||
- 读取 `ws_url`、`connect_tip`、`sample_messages`
|
||||
- 手动连接/断开 WebSocket
|
||||
- 手动发送订阅与心跳报文
|
||||
- 实时查看服务端返回帧内容(用于联调事件格式)
|
||||
|
||||
### 7.2 连接地址与鉴权流程
|
||||
### 7.2 HTTP 兜底接口
|
||||
|
||||
**WebSocket 连接 URL(与官方 `push.js` 一致)**
|
||||
- **当前期状态**:`POST /api/game/currentStatus`(建议 1 秒/次兜底)
|
||||
- **开奖记录**:`POST /api/game/periodHistory`(建议 3~5 秒/次兜底)
|
||||
- **余额快照**:`POST /api/wallet/balanceSummary`(下注后主动刷新)
|
||||
|
||||
- 形如:`{websocket_base}/app/{app_key}`
|
||||
- 示例(本地默认配置见 `config/plugin/webman/push/app.php`):`ws://127.0.0.1:3131/app/{app_key}`
|
||||
- 生产环境请改为 `wss://` 与对外域名,并与网关/证书一致。
|
||||
### 7.3 一致性规则
|
||||
|
||||
**连接建立后的协议步骤(简述)**
|
||||
|
||||
1. 客户端建立 WebSocket,服务端下发 `pusher:connection_established`,payload 内含 **`socket_id`**(后续鉴权必填)。
|
||||
2. 订阅 **公共** 频道:发送 `pusher:subscribe`,`data` 仅含 `channel` 名即可。
|
||||
3. 订阅 **私有** 频道:
|
||||
- 客户端向 **鉴权接口** 发起 `POST`(`Content-Type: application/x-www-form-urlencoded`),表单字段:`channel_name`、`socket_id`。
|
||||
- 默认鉴权路径为 **`/plugin/webman/push/auth`**(与 `config/plugin/webman/push/app.php` 中 `auth` 一致,可随部署调整)。
|
||||
- 服务端校验「当前登录用户是否允许订阅该 `channel_name`」——对 `private-user-{uuid}` 应校验 **`uuid` 与当前用户一致**,否则返回 `403`。
|
||||
- 鉴权成功返回的 JSON 由 `push.js` 原样作为 `pusher:subscribe` 的 `data` 发送(含 `auth` 等字段)。
|
||||
|
||||
**与移动端登录态的关系**
|
||||
|
||||
- 客户端在调用鉴权接口时,除 `channel_name` / `socket_id` 外,需携带与 REST API 一致的 **`user-token`(及业务所需的 `auth-token`)**,由服务端解析用户身份后再比对 `private-user-{uuid}`。
|
||||
- **不建议**依赖浏览器 Cookie Session 作为唯一依据(H5 外还有 App 内嵌、小程序等);若仅沿用框架示例中的 Session,需在落地实现中改为 **无状态 token 校验**。
|
||||
|
||||
### 7.3 事件定义(初设)
|
||||
|
||||
| 事件名 | 建议频道 | 说明 |
|
||||
|--------|----------|------|
|
||||
| `period.tick` | `public-game-period` | 倒计时广播 |
|
||||
| `period.locked` | `public-game-period` | 封盘 |
|
||||
| `period.opened` | `public-game-period` | 开奖完成(中奖号码) |
|
||||
| `bet.accepted` | `private-user-{uuid}` | 下注成功回执 |
|
||||
| `bet.settled` | `private-user-{uuid}` | **每期每用户一条**:该局开奖对该用户全部注单的汇总(`total_win_amount`、`order_count`、`hit_order_count`、`result_number`、`balance_after`;不再按单笔注单重复推送) |
|
||||
| `wallet.changed` | `private-user-{uuid}` | 余额变化(中奖派彩入账等;`reason=payout` 等) |
|
||||
| `notice.popout` | `public-operation-notice` 或 `private-user-{uuid}` | 强公告(按业务选择广播或定向) |
|
||||
| `withdraw.review_required` | `private-user-{uuid}` | 提现进入审核 |
|
||||
|
||||
### 7.4 消息形态(客户端解析)
|
||||
|
||||
连接上收到的单帧一般为 JSON,常见两类:
|
||||
|
||||
- 协议类:`event` 为 `pusher:connection_established`、`pusher_internal:subscription_succeeded` 等。
|
||||
- 业务类:`event` 为业务事件名,`channel` 为频道名,`data` 为负载(可能为字符串化的 JSON,客户端需 `JSON.parse` 一次)。
|
||||
|
||||
业务负载示例(与初设一致,字段以实际实现为准):
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "period.opened",
|
||||
"channel": "public-game-period",
|
||||
"data": {
|
||||
"period_no": "20260416001",
|
||||
"result_number": 18,
|
||||
"open_time": 1776326400
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.5 降级与一致性
|
||||
|
||||
- 推送仅作 **体验增强**:断线、弱网时客户端仍应以 **HTTP 轮询/用户主动刷新**(如 `/api/game/periodCurrent`、`/api/wallet/balanceSummary`)为准。
|
||||
- 同一业务状态以 **服务端落库与接口查询** 为最终一致;推送到达顺序不保证与业务因果严格一致,需客户端幂等与去重(可带 `period_no` / `order_no` / 时间戳)。
|
||||
|
||||
### 7.6 使用 Apipost 调试 WebSocket 与私有频道
|
||||
|
||||
Apipost(v7+)支持 **WebSocket**:新建请求 → 选择 **WebSocket** → 类型选 **Raw**。私有频道遵循「先拿 `socket_id` → 再 HTTP 鉴权 → 再发 `pusher:subscribe`」,与 `vendor/webman/push/src/push.js` 行为一致。
|
||||
|
||||
**A. 仅调试公共频道(如 `public-game-period`)**
|
||||
|
||||
1. 启动 webman 与 push 进程,确认 `config/plugin/webman/push/app.php` 中 `websocket`、`app_key`。
|
||||
2. 在 Apipost 中 WebSocket URL 填:`ws://127.0.0.1:3131/app/{app_key}`(将 `{app_key}` 换成配置中的真实值)。
|
||||
3. 点击连接,在消息面板应收到一帧 `pusher:connection_established`,从中取出 `socket_id`(公共订阅可不依赖后续步骤,但便于对照协议)。
|
||||
4. 在发送框填入一行 JSON(勿带代码块标记)并发送:
|
||||
`{"event":"pusher:subscribe","data":{"channel":"public-game-period"}}`
|
||||
5. 成功时随后会收到 `pusher_internal:subscription_succeeded`;之后服务端向该频道 `trigger` 的事件会出现在消息列表中。
|
||||
|
||||
**B. 调试用户私有频道 `private-user-{uuid}`**
|
||||
|
||||
1. 同上先连接,从首帧解析出 **`socket_id`**。
|
||||
2. 新建 **HTTP** 请求:`POST http://{你的HTTP入口}/plugin/webman/push/auth`
|
||||
- Header:`Content-Type: application/x-www-form-urlencoded`
|
||||
- Body(x-www-form-urlencoded):`channel_name=private-user-{替换为真实uuid}&socket_id={上一步的socket_id}`
|
||||
- 若鉴权已接入 `user-token`,请在 Header 中一并带上与移动端一致的 **`user-token`**(及 `auth-token` 等),否则会得到 `403` 或无效签名。
|
||||
3. 将接口返回的 **JSON 正文**(整段)作为 `pusher:subscribe` 的 `data`:在 Apipost WebSocket 发送
|
||||
`{"event":"pusher:subscribe","data": <上一步响应 JSON 对象>}`
|
||||
注意:`push.js` 会把鉴权返回与 `channel` 字段合并后再发送;若手搓 JSON,需保证与官方协议一致(含 `auth` 字段)。
|
||||
4. 订阅成功后即可在消息面板等待该私有频道上的业务事件。
|
||||
|
||||
**说明**:若仅做协议连通性验证,可暂时使用服务端对鉴权接口的占位实现;**上线前**必须落实「`channel_name` 与当前用户 `uuid` 匹配」校验,避免越权订阅。
|
||||
- 倒计时以服务端下发时间为准,不信任本地时钟累计。
|
||||
- 下注成功后以 `placeBet` 返回的 `balance_after` 为准,再调用钱包接口兜底。
|
||||
- WebSocket 断线后立即重连,并并发触发 `currentStatus + balanceSummary` 全量回补。
|
||||
|
||||
---
|
||||
|
||||
@@ -772,13 +728,10 @@ Apipost(v7+)支持 **WebSocket**:新建请求 → 选择 **WebSocket** →
|
||||
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`)
|
||||
3. 建立 webman/push 连接并订阅:
|
||||
- `public-game-period`
|
||||
- `private-user-{user.uuid}`(`uuid` 取自登录/档案接口,与 7.1 一致)
|
||||
4. 收到 `period.tick` 实时刷新倒计时
|
||||
5. 用户下注调用 `POST /api/game/betPlace`
|
||||
6. 监听 `bet.accepted` + `wallet.changed` 更新下注结果和余额
|
||||
7. 监听 `period.opened` 渲染开奖动画并刷新开奖记录
|
||||
4. 建立 WebSocket(H5)连接,发送订阅消息监听状态流
|
||||
5. 用户下注调用 `POST /api/game/placeBet`
|
||||
6. 下单后调用 `POST /api/wallet/balanceSummary` 刷新余额(并等待 WebSocket 消息)
|
||||
7. 断线或页面回前台时,兜底调用 `currentStatus + periodHistory` 回补状态
|
||||
|
||||
## 8.2 充值到下注到提现闭环
|
||||
1. 拉取档位:`POST /api/finance/depositTierList`(玩家选择一档,并记下该档 `channels[].code`)
|
||||
@@ -786,8 +739,8 @@ Apipost(v7+)支持 **WebSocket**:新建请求 → 选择 **WebSocket** →
|
||||
- 返回 `paid=false`、`status=pending`、**非空 `pay_url`**:客户端在 WebView/浏览器中打开 `pay_url`(`GET /api/finance/depositMockPayPage`);用户在模拟页点击确认后,由 `POST /api/finance/depositMockNotify` 完成入账,或轮询 `depositDetail` / 等 `wallet.changed` 再刷新余额
|
||||
- 未来接真实第三方:将 `pay_url` 换为真网关,入账仅在支付平台 **异步通知** 中调用 `DepositSettlement::settle`(与当前 `depositMockNotify` 路径一致)
|
||||
3. 客户端可选轮询 `POST /api/finance/depositDetail` 兜底确认状态;入账成功后会收到 `wallet.changed`
|
||||
4. 下注:`POST /api/game/betPlace`
|
||||
5. 派彩后收到 `wallet.changed`
|
||||
4. 下注:`POST /api/game/placeBet`
|
||||
5. 轮询余额:`POST /api/wallet/balanceSummary`
|
||||
6. 查询流水:`POST /api/wallet/recordList`
|
||||
7. 提现:`POST /api/finance/withdrawCreate`(即时冻结 `user.coin` 与写出 `withdraw` 流水) -> `POST /api/finance/withdrawDetail`
|
||||
|
||||
@@ -799,22 +752,20 @@ Apipost(v7+)支持 **WebSocket**:新建请求 → 选择 **WebSocket** →
|
||||
|
||||
---
|
||||
|
||||
## 9. 游戏时序流程图(接口 + 推送)
|
||||
## 9. 游戏时序流程图(WebSocket + HTTP兜底)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[用户登录 /api/user/login] --> B[拉初始化 /api/game/lobbyInit]
|
||||
B --> C[连接webman/push并订阅频道]
|
||||
C --> D[收到 period.tick 倒计时]
|
||||
D --> E{0-20秒下注期?}
|
||||
E -- 是 --> F[提交下注 /api/game/betPlace]
|
||||
F --> G[推送 bet.accepted + wallet.changed]
|
||||
E -- 否 --> H[进入封盘状态 period.locked]
|
||||
H --> I[服务端算票与开奖]
|
||||
I --> J[推送 period.opened]
|
||||
J --> K[客户端开奖动画与结果展示]
|
||||
K --> L[客户端刷新开奖记录 /api/game/periodHistory]
|
||||
L --> D
|
||||
B --> C[连接 WebSocket 并订阅主题]
|
||||
C --> D{0-20秒下注期?}
|
||||
D -- 是 --> E[提交下注 /api/game/placeBet]
|
||||
E --> F[刷新余额 /api/wallet/balanceSummary]
|
||||
D -- 否 --> G[进入封盘与开奖阶段]
|
||||
G --> H[服务端算票与开奖]
|
||||
H --> I[WebSocket 推送状态变化]
|
||||
I --> J[断线兜底 /api/game/currentStatus]
|
||||
J --> C
|
||||
```
|
||||
|
||||
---
|
||||
@@ -874,7 +825,7 @@ flowchart TD
|
||||
1. **登录方式**:仅账号密码,还是要短信/邮箱验证码?
|
||||
2. **提现收款类型**:首版只做银行卡,还是同时支持电子钱包/加密地址?
|
||||
3. **自动托管**:是否首期上线;若不上线可先隐藏 `auto-bet` 接口。
|
||||
4. **push事件最小集**:是否先只上 `period.tick`、`period.opened`、`wallet.changed` 三类。
|
||||
4. **WebSocket 主题定义**:状态流、资金流、托管流的 topic 与消息体是否按本文固定。
|
||||
5. **错误码规范**:是否已有公司统一错误码表;若有需对齐替换本草案码段。
|
||||
|
||||
确认后可进入下一步:按该文档落地 controller + validate + service + 路由。
|
||||
|
||||
252
docs/_36字花_前端开发对接与交互逻辑说明书.txt
Normal file
252
docs/_36字花_前端开发对接与交互逻辑说明书.txt
Normal file
@@ -0,0 +1,252 @@
|
||||
《"36字花" 前端开发对接与交互逻辑说明书》
|
||||
适用对象
|
||||
:前端开发工程师 (React / Vue / 移动端 H5 开发者)
|
||||
核心要求
|
||||
:极高的渲染性能(移动端务必保持 60FPS,禁止重绘卡顿),极致的状态同步(30秒循环状态机绝对不可发生状态错乱)。
|
||||
一、 全局架构与技术栈建议 (Architecture Guidelines)
|
||||
状态管理 (State Management)
|
||||
:由于游戏状态极其复杂(连赢限额、统一下注金额、倒计时同步),强烈推荐使用全局状态管理库(如 React的
|
||||
Redux/Zustand
|
||||
或 Vue的
|
||||
Pinia
|
||||
),将
|
||||
“业务逻辑数据层”
|
||||
与
|
||||
“UI渲染层”
|
||||
完全解耦。
|
||||
动画性能 (Animation Performance)
|
||||
:
|
||||
盘面的 10 种状态切换和高频边框动画,
|
||||
严禁使用 JS 帧动画操作 DOM
|
||||
。
|
||||
必须全部采用
|
||||
CSS3
|
||||
transition
|
||||
、
|
||||
animation
|
||||
、
|
||||
box-shadow
|
||||
或
|
||||
SVG (
|
||||
stroke-dasharray
|
||||
)
|
||||
。
|
||||
涉及到全屏爆金币的粒子特效 (Particle Effects),建议直接调用轻量级 Canvas 动画库(如
|
||||
tsparticles
|
||||
或
|
||||
PixiJS
|
||||
)。
|
||||
时间同步 (Time Sync)
|
||||
:绝对不要信任客户端(手机/浏览器)的本地时间!游戏倒计时必须以 WebSocket/API 下发的服务器时间戳为基准进行倒推补偿。
|
||||
二、 核心状态机:30秒生命周期 (The 30s Lifecycle) 注意⚠️这个时间后台可配置
|
||||
前端的整个游戏循环被严格划分为 4 个生命周期,请在代码中建立全局的
|
||||
GameState
|
||||
枚举,并根据状态驱动 UI 渲染:
|
||||
GameState.BETTING
|
||||
|
||||
(0 - 20秒:下注期)
|
||||
行为
|
||||
:允许用户点击格子、选筹码、点确认。
|
||||
UI
|
||||
:倒计时递减。背景跑马灯常态运转。
|
||||
GameState.LOCKED
|
||||
|
||||
(20.0秒:封盘锁定)
|
||||
行为
|
||||
:
|
||||
前端立即进行物理锁盘
|
||||
。无论网络是否有延迟,只要本地计算到达20.0秒,立即禁用所有输入框、点击事件和按钮。
|
||||
UI
|
||||
:弹出
|
||||
[停止下注]
|
||||
提示,未点击确认的预选状态 (
|
||||
PRE_SELECTED
|
||||
) 格子全部强制清除。
|
||||
GameState.DRAWING
|
||||
|
||||
(20 - 25秒:算票与开奖)
|
||||
行为
|
||||
:等待后端 WebSocket 推送开奖结果。
|
||||
UI
|
||||
:收到结果后,前端触发 3 秒的高频加速跑马灯,最后 0.5 秒光圈定格在中奖格子上,触发
|
||||
WINNING
|
||||
状态大爆动画。
|
||||
GameState.PAYOUT
|
||||
|
||||
(25 - 30秒:结算派彩)
|
||||
行为
|
||||
:监听余额变更推送,更新连赢 (Streak) 状态。
|
||||
UI
|
||||
:播放中奖特效,更新底部走势图 (红蓝圆点),准备进入下一局。
|
||||
三、 组件级交互逻辑:36字花主键盘 (The 36-Grid System)
|
||||
前端需要封装一个
|
||||
<Cell />
|
||||
组件,该组件接收一个
|
||||
status
|
||||
属性(范围 0-9),并根据该枚举值切换对应的 CSS Class。
|
||||
📌 10 种状态枚举 (CellStatus Enum) 映射逻辑
|
||||
IDLE
|
||||
(闲置):默认状态,深色,偶发微光 CSS 动画。
|
||||
MARQUEE
|
||||
(跑马灯焦点):全局维护一个
|
||||
activeCellIndex
|
||||
每 0.1秒随机变更,命中的组件亮起青色霓虹边框。
|
||||
HOVER
|
||||
(悬浮):
|
||||
:hover
|
||||
伪类触发(仅 PC)。
|
||||
PRE_SELECTED
|
||||
(预选中):前端本地状态数组。显示筹码图标,金边流光。
|
||||
LOCKED
|
||||
(已确认):调用
|
||||
place_bet
|
||||
API 成功后切换至此状态。显示锁定印章,绿色边框。
|
||||
DISABLED
|
||||
(禁用):封盘时,或已选中数量达标(5个)时,其余格子强制渲染黑色 60% 遮罩。
|
||||
ERROR
|
||||
(错误抖动):触发 CSS
|
||||
shake
|
||||
动画,维持 0.5s 后回退到上一个状态。
|
||||
WINNING
|
||||
(中奖大爆):开奖目标,Z-index 置顶,放大 1.25 倍,播放强脉冲 CSS。
|
||||
LOSER
|
||||
(落选陪跑):透明度设为
|
||||
opacity: 0.2
|
||||
。
|
||||
AUTO_ACTIVE
|
||||
(自动托管中):全局遮罩下,该格子穿透遮罩,显示紫色虚线动画与
|
||||
AUTO
|
||||
印章。
|
||||
📌 核心防呆交互逻辑 (必须用代码写死限制)
|
||||
统一下注金额联动 (Chip Sync)
|
||||
:
|
||||
全局维护一个
|
||||
currentChipValue
|
||||
(当前选中的筹码)。
|
||||
如果用户修改了
|
||||
currentChipValue
|
||||
,前端必须
|
||||
遍历所有状态为
|
||||
PRE_SELECTED
|
||||
的格子,将它们显示的筹码瞬间同步为新金额
|
||||
。
|
||||
数量限制校验 (Max 5 Limit)
|
||||
:
|
||||
实时计算:
|
||||
count(PRE_SELECTED) + count(LOCKED)
|
||||
必须
|
||||
<= 5
|
||||
。
|
||||
等于 5 时,其余 31 个
|
||||
IDLE
|
||||
状态的格子必须变成
|
||||
DISABLED
|
||||
状态。如果强行点击,触发
|
||||
ERROR
|
||||
动画。
|
||||
连赢上限校验 (Streak Bet Limit)
|
||||
:
|
||||
如果玩家上一局赢了,API 会下发一个
|
||||
streakMaxBetLimit
|
||||
(连赢最高下注总额,如 💎 100)。
|
||||
前端需要写一个
|
||||
useEffect
|
||||
/
|
||||
watch
|
||||
:实时计算
|
||||
当前选中数字数量 * currentChipValue
|
||||
。如果这个乘积
|
||||
> streakMaxBetLimit
|
||||
,或者当前账户余额不足,左下角的那个筹码图标必须添加
|
||||
disabled
|
||||
属性(变灰不可点)。
|
||||
四、 核心中控台交互 (Control Panel & Actions)
|
||||
1.
|
||||
[✅ 确定下注 Confirm]
|
||||
主按钮的状态机
|
||||
这是全场最重要的按钮,前端必须维护它独立的四态机:
|
||||
Disabled
|
||||
:未选中任何格子时。
|
||||
Ready (高亮呼吸)
|
||||
:选中格子 > 0,且总注金 <= 余额。绑定
|
||||
onClick -> handleSubmit
|
||||
。
|
||||
Error (红色)
|
||||
:总注金 > 余额。文字变成“余额不足”。
|
||||
Success (绿色)
|
||||
:收到 API 200 成功响应后,维持绿色直到本局结束。
|
||||
1.
|
||||
Auto-Spin(自动托管)逻辑流
|
||||
数据层
|
||||
:调用
|
||||
/api/auto_spin
|
||||
告知后端要买哪些数字、金额和局数。
|
||||
UI 层
|
||||
:前端进入
|
||||
AUTO_MODE
|
||||
全局变量。
|
||||
整个盘面外层盖一个
|
||||
<div className="glass-overlay" />
|
||||
,
|
||||
pointer-events: none
|
||||
(阻断一切鼠标点击)。
|
||||
只有目标格子的状态被设为
|
||||
AUTO_ACTIVE
|
||||
,并使用 CSS
|
||||
z-index
|
||||
穿透遮罩。
|
||||
监听后端 WebSocket 下发的每一局扣款成功事件,更新底部控制台的进度条(如
|
||||
3/50 局
|
||||
)。
|
||||
3.
|
||||
Red/Blue 路子图渲染逻辑 (Trend Chart)
|
||||
接收一个含有最近 30 期开奖数字的 Array
|
||||
[08, 15, 36, ...]
|
||||
。
|
||||
渲染判断
|
||||
:
|
||||
item % 2 === 0
|
||||
(偶数) 渲染蓝色圆圈;
|
||||
item % 2 !== 0
|
||||
(奇数) 渲染红色圆圈。
|
||||
入场动画
|
||||
:当有新数字加入 Array 时,最后一个圆圈必须带有
|
||||
slide-in
|
||||
或
|
||||
pop-in
|
||||
动画。
|
||||
五、 网络延迟与极端异常处理 (Edge Cases & Fallbacks)
|
||||
博彩游戏的前端,对异常处理的要求极高,请前端必须实现以下机制:
|
||||
首屏强制公告 (Welcome Pop-out)
|
||||
进页面时调用
|
||||
/api/user/announcement
|
||||
。如果有未读公告,弹出
|
||||
<Modal />
|
||||
。
|
||||
“进入游戏”的
|
||||
Button
|
||||
绑定
|
||||
disabled={!isChecked}
|
||||
。不勾选绝对不给进。
|
||||
压秒网络卡顿防错 (The 19.9s Click)
|
||||
场景
|
||||
:倒计时剩 0.1 秒,玩家点击了【确定下注】,前端发起了 HTTP/Socket 请求,但因为网络差,请求还没到服务器,本地倒计时先归零了。
|
||||
处理方案
|
||||
:前端立即锁盘进入
|
||||
LOCKED
|
||||
状态,并显示 Loading (spinner)。当 2 秒后收到后端的 400 Bad Request(提示已封盘)时,前端必须
|
||||
清除这个格子的状态,并弹窗提示
|
||||
[网络延迟,下注失败,未扣款]
|
||||
。千万不能强行把它变成
|
||||
LOCKED
|
||||
绿勾。
|
||||
断线重连恢复 (Reconnection Recovery)
|
||||
场景
|
||||
:玩家切出微信看消息,5分钟后切回浏览器。
|
||||
处理方案
|
||||
:前端检测到
|
||||
visibilitychange
|
||||
或者 Socket 断开,必须立刻重新发起
|
||||
/api/game/current_status
|
||||
全量拉取请求。根据服务器返回的数据,瞬间重置本地的倒计时、当前连胜数、走势图数据。
|
||||
绝对不要依赖本地时间的积累运算。
|
||||
BIN
docs/《_36字花_ 前端开发对接与交互逻辑说明书》.docx
Normal file
BIN
docs/《_36字花_ 前端开发对接与交互逻辑说明书》.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user