825 lines
47 KiB
Markdown
825 lines
47 KiB
Markdown
# 36字花移动端接口设计草案(V1)
|
||
|
||
本文基于 `docs/36字花-数据库与实施计划.md` 与 PRD,先给出移动端可对接的接口清单与字段初设。
|
||
口径遵循:**全平台单期号、单开奖结果**;渠道仅用于归属、分润与风控,不拆分对局。
|
||
|
||
**补充(2026-04)**:§**1.5** 描述服务端 **Redis 热点缓存**(`GameHotDataRedis`),**不改变**各接口 URL、参数与响应字段约定,仅供联调与运维对照。
|
||
|
||
## 1. 设计约定
|
||
|
||
### 1.1 基础约定
|
||
- 协议:HTTPS + JSON
|
||
- 接口命名规范:`/api/{module}/{action}`,且必须满足正则 `^/api/[a-z]+/[a-z]+[A-Z][a-zA-Z]*$`
|
||
- **请求方法**:所有移动端业务接口(`/api/*`,不含 `/api/v1/authToken`)一律使用 `POST` 调用;查询类接口同时兼容 `GET`(便于浏览器/调试工具直接访问),客户端统一走 `POST`
|
||
- `POST` 时请求头 `Content-Type: application/json`,参数放在 JSON body
|
||
- `GET` 兼容模式下,参数走 URL query string
|
||
- **例外**:公告模块 `/api/notice/noticeList`、`/api/notice/noticeDetail`、`/api/notice/noticeConfirm` 与模拟收银台页 `/api/finance/depositMockPayPage` **仅支持 `GET`**,参数一律走 URL query string
|
||
- 鉴权类接口 `/api/v1/authToken` 仍为 `GET`
|
||
- 时间:UTC 时间戳(秒) + 服务端时区配置
|
||
- 金额:数字传输(如 `"100.00"`),客户端展示统一保留两位小数(存储仍为 `decimal(18,2)`)
|
||
- 幂等:关键写接口要求 `idempotency_key`
|
||
- 请求头(必带):
|
||
- `auth-token`:通过 `GET /api/v1/authToken` 获取的接口鉴权令牌(含义:接口访问的签名鉴权凭证)
|
||
- `user-token`:用户登录态令牌;需要登录的接口必带
|
||
- 语言请求头:
|
||
- `lang=zh`:返回中文(默认)
|
||
- `lang=en`:返回英文
|
||
|
||
### 1.2 通用响应结构
|
||
```json
|
||
{
|
||
"code": 1,
|
||
"message": "ok",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
- `code=1` 表示成功,非 1 为业务错误
|
||
- `/api/*` 所有接口返回文案支持中英双语:默认中文;请求头 `lang=en` 返回英文,`lang=zh` 返回中文
|
||
- 建议错误码段(按错误性质):
|
||
- `1000-1099`:参数错误(字段缺失、类型错误、格式错误、超范围)
|
||
- `1100-1199`:鉴权错误(未登录、token 失效、权限不足)
|
||
- `2000-2999`:业务错误(余额不足、对局不存在、订单不存在、公告不存在)
|
||
- `3000-3099`:流程错误(非法流程/状态不允许,如封盘后下注、重复确认、状态跃迁非法)
|
||
- `5000-5999`:系统错误(服务异常、依赖超时、未知错误)
|
||
|
||
- 推荐基础错误码(首版):
|
||
- `1`:成功
|
||
- `1001`:参数缺失
|
||
- `1002`:参数格式错误
|
||
- `1003`:参数取值非法
|
||
- `1101`:未登录或登录已过期
|
||
- `1103`:无权限操作
|
||
- `2001`:余额不足
|
||
- `2002`:对局不存在
|
||
- `2003`:订单不存在
|
||
- `2004`:公告不存在
|
||
- `3001`:当前流程不允许该操作
|
||
- `3002`:已封盘,禁止下注
|
||
- `3003`:重复请求(幂等冲突)
|
||
- `5000`:系统繁忙,请稍后重试
|
||
|
||
### 1.3 鉴权方式
|
||
- **接口鉴权(auth-token)**:所有移动端业务接口请求时必须携带请求头 `auth-token`(由 `/api/v1/authToken` 签发)
|
||
- **用户登录鉴权(user-token)**:需要登录的接口携带请求头 `user-token`;token 失效后调用刷新或重新登录
|
||
|
||
### 1.4 获取接口鉴权 Token(auth-token)
|
||
- **GET** `/api/v1/authToken`
|
||
- 用途:获取 `auth-token`(所有接口请求头必带)
|
||
|
||
请求示例:
|
||
`/api/v1/authToken?secret=564d14asdasd113e46542asd6das1a2a×tamp=1776331077&device_id=1&signature=AD84C49880896DBC16C59C7B122D1FF7`
|
||
|
||
请求参数:
|
||
- `secret`:string(含义:客户端密钥;服务端从环境变量 `AUTH_TOKEN_SECRET` 校验)
|
||
- `timestamp`:int(含义:请求时间戳;服务端允许与服务器时间误差 ±300 秒)
|
||
- `device_id`:string(含义:设备码)
|
||
- `signature`:string(含义:签名值)
|
||
|
||
签名算法:
|
||
- 取参与签名的参数(不含 `signature`):`device_id`、`secret`、`timestamp`
|
||
- 按参数名 **a-z** 排序后拼接为字符串:`key=value&key=value...`
|
||
- 计算:`signature = strtoupper(md5(拼接字符串))`
|
||
|
||
返回参数:
|
||
- `auth_token`:string(含义:接口鉴权 token;放到请求头 `auth-token`)
|
||
- `expires_in`:int(含义:有效期秒数)
|
||
- `server_time`:int(含义:服务器时间戳,用于校时)
|
||
|
||
可能错误码:
|
||
- `1001` 参数缺失
|
||
- `1002` 参数格式错误
|
||
- `1103` 密钥无效/签名错误
|
||
- `3001` 时间戳无效
|
||
|
||
### 1.5 服务端性能与 Redis 热点缓存(实现说明)
|
||
|
||
> **对客户端无契约变更**:请求路径、参数、响应 JSON 形状与错误码均不因缓存而改变;本节仅说明服务端如何降延迟、读路径与一致性注意点。
|
||
|
||
**与「框架文件缓存」的区别**
|
||
|
||
| 配置 | 作用域 |
|
||
|------|--------|
|
||
| `CACHE_DRIVER`(`config/cache.php`,如 `file`) | Think-ORM / `get_sys_config()` 等**系统参数表 `config`** 的模型缓存,落盘在 `runtime/cache`,**不参与**本游戏业务热点路径。 |
|
||
| `GAME_HOT_CACHE_*`(`config/game_hot_cache.php`) | 游戏侧 **`user` / `game_config` / `game_record`** 行级 JSON 缓存,走 **`support\Redis`**(`config/redis.php` 连接),键前缀 `dfw:v1:`。 |
|
||
|
||
**服务端缓存覆盖(与移动端直接相关的读路径)**
|
||
|
||
- **用户**:会员鉴权优先读 Redis 中的 `user` 行快照,未命中再查库并回填。**余额、连胜、打码量等变更**落库后,统一经 **`GameHotDataCoordinator::afterUserCommitted($userId)`**:先 **`GameHotDataRedis::userReplaceCacheFromDb`** 与 DB 对齐,再向 Redis 写队列投递幂等刷新任务(见 `GameHotDataWriteQueue` / `GameHotDataQueueConsumer`),用于削峰而非替代同步回源。
|
||
- **游戏配置**:`game_config` 按 `config_key` 缓存。后台直连 `Db` 更新时须 **`GameHotDataCoordinator::afterGameConfigKeyCommitted($key)`**(模型 `GameConfig` 事件与独立表单控制器已接入);独立保存接口在写入前对同一 `config_key` 使用 **`GameHotDataLock`(`TYPE_GAME_CONFIG`)** 互斥。勿仅删除缓存键而不回源,否则最长不一致窗口为 TTL。
|
||
- **对局**:当前活跃局、按 `id` 的局、最新一条 `game_record` 等;写库后经 **`GameHotDataCoordinator::afterGameRecordCommitted`** 同步刷新相关 Redis 键并入队。开奖/封盘等路径另可按记录 id 使用 **`GameHotDataLock`(`TYPE_GAME_RECORD`)** 串行化。
|
||
|
||
**环境变量(示例见仓库根目录 `.env-example`)**
|
||
|
||
- `GAME_HOT_CACHE_ENABLED`:是否启用上述 Redis 热点缓存(`false` 时全程回退数据库)。
|
||
- `GAME_HOT_CACHE_TTL_GAME_CONFIG` / `GAME_HOT_CACHE_TTL_GAME_RECORD` / `GAME_HOT_CACHE_TTL_USER`:各类缓存 TTL(秒);**以写后同步回源为主**,TTL 仅作兜底。
|
||
- `GAME_HOT_CACHE_ENABLE_WRITE_QUEUE` 及队列长度、消费进程间隔等:控制写库后的**幂等刷新任务**是否入队及背压策略(见 `config/game_hot_cache.php`)。
|
||
|
||
**一致性提示(联调/测试)**
|
||
|
||
- 任何绕过协调入口、只改 DB 不调用 **`GameHotDataCoordinator`** 的手工脚本,都可能与 Redis 短期不一致;生产环境应避免。
|
||
- **`POST /api/game/betPlace`** 扣款路径使用与后台钱包加减点相同的 **用户维度 Redis 锁**(`GameHotDataRedis::userAdminMutationLockTry`)及 **`WHERE coin = ?` 条件更新**,与并发派彩/后台调账互斥;失败时返回 **§4.2** 所列中文说明。
|
||
- 客户端仍可按 **§3.2 `dictionaryList` 的 `version`** 做本地缓存;服务端字典另有 Redis 加速,二者可同时存在。
|
||
|
||
---
|
||
|
||
## 2. 认证与账户模块(user)
|
||
|
||
### 2.1 注册
|
||
- **POST** `/api/user/register`
|
||
- 用途:仅手机号注册并绑定邀请归属(admin/channel)
|
||
|
||
请求参数:
|
||
- `username`:string,手机号(含义:注册账号,仅支持大陆手机号)
|
||
- `password`:string,明文经 HTTPS 传输(含义:登录密码,服务端需加盐哈希存储)
|
||
- `invite_code`:string,必填(含义:子代理邀请码,用于绑定渠道 `channel_id` 与归属)
|
||
- `device_id`:string,可选(含义:设备标识,用于风控与登录记录)
|
||
|
||
返回参数:
|
||
- `user-token`:string(含义:后续接口登录态令牌;用于需要登录的接口请求头)
|
||
- `refresh_token`:string,可选(含义:用于刷新访问令牌)
|
||
- `expires_in`:int(秒,含义:令牌有效期)
|
||
- `user`:object(仅返回非私密信息,不返回 `id`)
|
||
- `uuid`:string(含义:用户对外唯一标识,10 位)
|
||
- `username`:string(含义:用户昵称/展示名)
|
||
- `coin`:string(含义:当前余额)
|
||
- `channel_id`:int(含义:归属渠道 ID)
|
||
- `risk_flags`:int(含义:风控状态位)
|
||
|
||
### 2.2 登录
|
||
- **POST** `/api/user/login`
|
||
|
||
请求参数:
|
||
- `username`:string(含义:登录账号,当前支持手机号)
|
||
- `password`:string(含义:登录密码)
|
||
- `device_id`:string,可选(含义:设备标识,辅助风控)
|
||
|
||
返回参数:
|
||
- `user-token`:string(含义:访问令牌;用于需要登录的接口请求头)
|
||
- `refresh_token`:string,可选(含义:用于刷新访问令牌)
|
||
- `expires_in`:int(含义:访问令牌剩余有效秒数)
|
||
- `user`:object(仅返回非私密信息,不返回 `id`)
|
||
- `uuid`:string(含义:用户对外唯一标识,10 位)
|
||
- `username`:string(含义:用户昵称/展示名)
|
||
- `coin`:string(含义:当前余额)
|
||
- `channel_id`:int(含义:归属渠道 ID)
|
||
- `risk_flags`:int(含义:风控状态位)
|
||
|
||
### 2.3 获取当前用户信息
|
||
- **POST** `/api/user/profile`
|
||
|
||
返回参数(金额类字段统一 2 位小数字符串,与钱包展示口径一致):
|
||
|
||
**基础档案**
|
||
- `uuid`:string(含义:用户对外唯一标识,10 位)
|
||
- `username`:string(含义:昵称)
|
||
- `head_image`:string(含义:头像地址)
|
||
- `phone`:string(含义:手机号)
|
||
- `email`:string(含义:邮箱)
|
||
- `register_invite_code`:string(含义:注册邀请码快照)
|
||
- `channel_id`:int(含义:归属渠道 ID)
|
||
- `risk_flags`:int(含义:风控状态位)
|
||
- `current_streak`:int(含义:当前连胜次数)
|
||
- `last_bet_period_no`:string(含义:最近一笔有效下注所在期号)
|
||
- `create_time`:int(含义:注册时间戳)
|
||
|
||
**资金与提现配额**
|
||
- `coin` / `coin_balance`:string(含义:当前余额;两字段同值)
|
||
- `frozen_balance`:string(含义:冻结余额;无冻结场景,固定 `0.00`)
|
||
- `total_deposit_coin`:string(含义:累计充值)
|
||
- `total_withdraw_coin`:string(含义:累计提现;受理后累加)
|
||
- `bet_flow_coin`:string(含义:打码量/流水;开奖结算后按每注 `total_amount` 1:1 累加)
|
||
- `max_withdrawable`:string(含义:**当前允许发起的单笔最大提现金额** = `min(coin_balance, max_withdraw_by_flow)`)
|
||
- `withdraw_flow`:object(含义:打码量 / 提现配额诊断快照,此处额外附 `pending_withdraw`)
|
||
- `ratio`:string(打码量倍数;`0` 表示不限打码)
|
||
- `net_deposit`:string(净充值 = max(0, 累计充值 − 累计提现))
|
||
- `required_bet_flow`:string(按门槛口径所需打码量,纯展示)
|
||
- `remaining_bet_flow`:string(按门槛口径还差多少打码量,纯展示)
|
||
- `eligible`:bool(是否满足整体门槛,纯展示;真正放行以 `max_withdrawable` 为准)
|
||
- `max_withdraw_by_flow`:string/null(仅按打码量折算的上限;`ratio=0` 时为 `null`)
|
||
- `flow_unlimited`:bool(是否处于"不限打码"状态)
|
||
- `pending_withdraw`:object
|
||
- `count`:int(当前待审核提现订单数)
|
||
- `max`:int(单用户最多允许的待审核提现数,当前为 `3`;超过 `withdrawCreate` 返回 `code=2004`)
|
||
|
||
### 2.4 刷新令牌(可选)
|
||
- **POST** `/api/user/refreshToken`
|
||
|
||
请求参数:
|
||
- `refresh_token`:string(含义:续签访问令牌的凭证)
|
||
|
||
返回参数:
|
||
- `user-token`:string(含义:新访问令牌)
|
||
- `expires_in`:int(含义:新令牌有效期)
|
||
|
||
---
|
||
|
||
## 3. 游戏大厅与字典模块(game/lobby)
|
||
|
||
### 3.1 获取首页初始化数据
|
||
- **POST** `/api/game/lobbyInit`
|
||
- 用途:一次返回本局、配置、36字花字典、用户快捷展示
|
||
|
||
返回参数:
|
||
- `server_time`:int(含义:服务端当前时间,用于客户端校时)
|
||
- `runtime_enabled`:bool(含义:**游戏运行开关**;`false` 时表示后台维护——**禁止下注**,且 idle 时不会自动创建新期、派彩结束后也不会自动创建下一期;**当前已开盘的局仍会开奖、派彩并结算**。移动端应禁用下注入口并提示「维护」类文案)
|
||
- `period`:object
|
||
- `period_no`:string(含义:当前全局期号)
|
||
- `status`:string(`betting`/`locked`/`settling`/`finished`/`void`,含义:当前期状态;`void` 表示该期已作废)
|
||
- `countdown`:int(含义:当前期倒计时秒数)
|
||
- `lock_at`:int(含义:封盘时间戳)
|
||
- `open_at`:int(含义:预计开奖时间戳)
|
||
- `bet_config`:object
|
||
- `pick_max_number_count`:int(含义:单注最多可选号码数,来自 `game_config.config_key = pick_max_number_count`,缺省与库内种子一致,通常为 10,合法范围 1–36)
|
||
- `chips`:array[string](如 `["1.00","5.00"]`,含义:快捷筹码面额)
|
||
- `single_number_max_bet`:string(含义:单号码最大下注额)
|
||
- `dictionary`:array<object>
|
||
- `number`:int(1-36,含义:字花编号)
|
||
- `name`:string(含义:字花名称)
|
||
- `category`:string(含义:字花分类)
|
||
- `icon`:string(含义:图标资源地址)
|
||
- `user_snapshot`:object(`coin`、`current_streak`,含义:用户状态快照)
|
||
|
||
### 3.2 获取36字花字典(可缓存)
|
||
- **POST** `/api/game/dictionaryList`
|
||
|
||
返回参数:
|
||
- `version`:string(含义:字典版本号,前端可用于缓存比对)
|
||
- `items`:同 `dictionary`(含义:36字花字典明细)
|
||
|
||
## 4. 下注与对局模块(game/bet)
|
||
|
||
### 4.1 获取当前期详情
|
||
- **POST** `/api/game/periodCurrent`
|
||
|
||
返回参数:
|
||
- `runtime_enabled`:bool(含义:同 `lobbyInit.runtime_enabled`)
|
||
- `period_id`:int(含义:当前期主键 ID)
|
||
- `period_no`:string(含义:当前期号)
|
||
- `status`:string(含义:当前期状态,含 `void` 已作废)
|
||
- `countdown`:int(含义:当前期剩余秒数)
|
||
- `bet_close_in`:int(含义:距离封盘剩余秒数)
|
||
- `result_number`:int/null(未开奖为 null,含义:开奖号码)
|
||
|
||
### 4.2 提交下注
|
||
- **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`),重复号码会去重)
|
||
- `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(含义:下单后连胜快照)
|
||
|
||
**可能错误码(补充)**(其余见文档头部错误码分段;扣款与缓存一致性强相关):
|
||
|
||
- `3001`:游戏已暂停(`runtime_enabled=false`,后台「游戏实时对局」关闭运行开关或作废本局后未重新开启;与非法流程类错误同段)
|
||
- `5000`:系统繁忙;或 **用户 Redis 互斥锁**未获取(与后台钱包/并发写同一用户串行,文案与后台一致:「该用户正在被其他管理员操作(钱包/并发保存),请稍后再试」);或 **`coin` 条件更新**未命中(并发下注/派彩/后台已改余额:「扣款失败:该用户余额已被其他请求修改(如下注、派彩或其他管理员已保存),请刷新后重试」)。
|
||
|
||
### 4.3 自动托管
|
||
- **POST** `/api/game/autoSpin`
|
||
|
||
请求参数:
|
||
- `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`
|
||
|
||
请求参数:
|
||
- `page`:int(可选,默认 1)
|
||
- `page_size`:int(可选,默认 20)
|
||
|
||
返回参数:
|
||
- `list`:array<object>
|
||
- `order_no`:string(含义:下注订单号)
|
||
- `period_no`:string(含义:所属期号)
|
||
- `numbers`:array[int](含义:下注号码)
|
||
- `bet_amount`:string(含义:本笔整笔压注金额,与 `total_amount` 相同)
|
||
- `total_amount`:string(含义:本笔整笔压注金额)
|
||
- `result_number`:int/null(含义:开奖号码,未开可空)
|
||
- `win_amount`:string(含义:中奖金额)
|
||
- `status`:string(含义:订单状态)
|
||
- `create_time`:int(含义:下注时间)
|
||
- `pagination`:object(`page`、`page_size`、`total`,含义:分页信息)
|
||
|
||
---
|
||
|
||
## 5. 钱包与资金模块(wallet/finance)
|
||
|
||
### 5.1 余额同步口径(已移除独立摘要接口)
|
||
- 已移除 `/api/wallet/balanceSummary`。
|
||
- 余额同步来源调整为:
|
||
- 下注返回 `placeBet.balance_after`
|
||
- WebSocket 推送 `wallet.changed`
|
||
- 充值/提现详情接口(如 `depositDetail` / `withdrawDetail`)作为业务单据维度核对
|
||
- 倍数 `ratio` 由后台「游戏配置 → `withdraw_bet_flow_ratio`」维护,修改后对新请求立即生效。
|
||
- 历史累计类字段(`total_deposit_coin` / `total_withdraw_coin` / `bet_flow_coin`)均为累加语义;若后续审核驳回,回冲逻辑由后台审核流程负责。
|
||
|
||
### 5.2 钱包流水
|
||
- **POST** `/api/wallet/recordList`
|
||
|
||
请求参数:
|
||
- `page`:int(可选,默认 1)
|
||
- `page_size`:int(可选,默认 20)
|
||
- `type`:string,可选(含义:流水类型筛选,可选值如下;不传表示查询全部)
|
||
- `deposit`:充值入账(充值订单成功后,金额入账到玩家余额)
|
||
- `withdraw`:提现出账(提现订单受理/打款后,金额从玩家余额扣除或冻结)
|
||
- `bet`:下注扣款(提交下注时从玩家余额扣除的投注金额)
|
||
- `payout`:开奖派彩(中奖后系统将奖金入账到玩家余额)
|
||
- `adjust`:人工调整(后台管理员加/扣点,对应 `biz_type=admin_credit/admin_deduct`)
|
||
- `bet_void`:期次作废退款(后台「游戏实时对局」作废本局时,退回待开奖注单本金)
|
||
|
||
返回参数:
|
||
- `list`:array<object>
|
||
- `record_id`:int(含义:钱包流水 ID)
|
||
- `biz_type`:string(含义:业务类型)
|
||
- `direction`:int(1入2出,含义:资金方向)
|
||
- `amount`:string(含义:本次变动金额)
|
||
- `balance_before`:string(含义:变动前余额)
|
||
- `balance_after`:string(含义:变动后余额)
|
||
- `ref_type`:string(含义:关联业务单类型)
|
||
- `ref_id`:string(含义:关联业务单标识)
|
||
- `create_time`:int(含义:流水时间)
|
||
|
||
补充约定:
|
||
- 金额字段(`amount`、`balance_before`、`balance_after` 等)客户端显示统一两位小数。
|
||
- 后台管理员加减点会生成 `biz_type=admin_credit/admin_deduct` 的流水记录,备注默认模板:`后台管理员(操作管理员)加点/扣点100(值)`(示例)。
|
||
|
||
### 5.3 充值档位列表
|
||
- **POST** `/api/finance/depositTierList`
|
||
|
||
说明:
|
||
- 由后台「配置管理 → 充值档位」维护,存放在 `game_config.deposit_tier`(JSON 数组)。
|
||
- 后台表单中的「支付货币」下拉来源于 `game_config.finance_cashier.currencies`(不再前端硬编码)。
|
||
- 初始化/重建档位时按当前 `finance_cashier` 货币集合生成:**每种货币 6 条档位**(运营可再编辑)。
|
||
- 仅返回启用状态(`status=1`)的档位,按 `sort` 升序;玩家仅能从中选择。
|
||
- 档位仅描述"充值规格",不再包含收款账户;具体收款由第三方支付网关返回的 `pay_url` 引导。
|
||
- **多语言**:后台保存 `title`(中文名)、`title_en`(英文名)、`desc`(中文描述)、`desc_en`(英文描述)。接口返回的 `title` / `desc` 会根据请求头 `lang` 自动适配:
|
||
- `lang=zh`(默认):返回 `title` / `desc`,若为空则回退到英文
|
||
- `lang=en`:返回 `title_en` / `desc_en`,若为空则回退到中文
|
||
- 移动端客户端仅看到单一 `title` / `desc`,无需自行判断语言
|
||
|
||
请求参数:无(无需 body 与 query)
|
||
|
||
返回参数:
|
||
- `list`:list,档位列表;每一项结构:
|
||
- `id`:string(含义:档位稳定 ID,创建订单时作为 `tier_id` 原样回传;与 `tier_key` 同值)
|
||
- `tier_key`:string(含义:与 `id` 相同,兼容旧字段名)
|
||
- `title`:string(含义:档位名称,已按 `lang` 头切换;例如 `lang=en` 下返回 `"Starter Pack"`,`lang=zh` 下返回 `"新手首充礼包"`)
|
||
- `currency`:string(含义:标价币种,如 `CNY`)
|
||
- `pay_amount`:string(2 位小数,含义:对外标价金额,与业务配置一致;展示用)
|
||
- `amount`:string(2 位小数,含义:玩家本次需支付的充值金额)
|
||
- `bonus_amount`:string(2 位小数,含义:该档位赠送金额,无赠送为 `0.00`)
|
||
- `total_amount`:string(2 位小数,含义:到账总额 = amount + bonus_amount,方便前端直接展示"到账 120")
|
||
- `desc`:string(含义:档位描述/活动文案,已按 `lang` 头切换;可空)
|
||
- `channels`:array(含义:可用支付渠道列表,用于 `depositCreate` 的 `channel_code`;渠道与档位不再做单独绑定,所有启用渠道自动兼容全部档位)
|
||
- 每项:`code`(string,渠道代码,小写,与创建订单时传入的 `channel_code` 一致)、`name`(展示名)、`sort`(排序)
|
||
|
||
### 5.3A 获取充值/提现配置
|
||
- **POST** `/api/finance/depositWithdrawConfig`
|
||
- 兼容旧接口:`POST /api/finance/cashierConfig`(返回结构一致,建议客户端统一切到 `depositWithdrawConfig`)
|
||
|
||
用途:
|
||
- 一次性返回充值与提现页面所需配置:货币列表、汇率、可用充值渠道、提现银行、提现限额与文案配置。
|
||
|
||
返回参数:
|
||
- `platform_coin_label`:string(平台币名称,按 `lang` 适配)
|
||
- `currencies`:array
|
||
- `code`:string(货币代码)
|
||
- `label`:string(货币展示名,按 `lang` 适配)
|
||
- `deposit_coins_per_fiat`:string(充值汇率)
|
||
- `withdraw_coins_per_fiat`:string(提现汇率)
|
||
- `rates`:array(兼容字段)
|
||
- `currency`:string
|
||
- `diamonds_per_fiat_unit`:string
|
||
- `pay_channels`:array(充值渠道)
|
||
- `code`:string(渠道代码)
|
||
- `name`:string(展示名)
|
||
- `sort`:int(排序)
|
||
- `status`:int(启用状态,1=启用)
|
||
- `tier_ids`:array(兼容字段;当前固定空数组,表示自动兼容全部充值档位)
|
||
- `withdraw`:object
|
||
- `banks`:array(提现银行)
|
||
- `min_ewallet`:string(电子钱包最低提现)
|
||
- `min_bank`:string(银行卡最低提现)
|
||
- `rate_hint`:string(汇率提示文案)
|
||
- `processing_note`:string(到账提示文案)
|
||
- `fee_note`:string(手续费提示文案)
|
||
- `rate_mode`:string(`fixed` / `live`)
|
||
- `fields`:object(提现表单必填项开关)
|
||
|
||
### 5.4 创建充值订单
|
||
- **POST** `/api/finance/depositCreate`
|
||
- `Content-Type: application/json`(推荐)、`application/x-www-form-urlencoded` 或 **`multipart/form-data`**(如 Apifox 的 form-data);字段名与下表一致即可,服务端通过统一参数名读取,**不限制**为某一种 body 类型。
|
||
|
||
说明(与真实「创建订单 → 调起三方 → 异步回调」一致):
|
||
- **创建单**:`depositCreate` 仅写入 **待支付** 订单(`status=pending`),**不在此请求内入账**。返回体中 `paid=false`,`pay_url` 为 **模拟第三方收银台** 完整 URL(HMAC 防篡改,见下 §5.4.1)。
|
||
- **客户端**:在 WebView/系统浏览器中打开 `pay_url`;用户完成支付后(模拟页为「确认支付」按钮),由服务端 `depositMockNotify` 验签后调用 `DepositSettlement::settle` 入账,并推送 `wallet.changed`;客户端可轮询 `depositDetail` 或依赖推送更新余额。
|
||
- **未来接入真实第三方支付**:将 `pay_url` 与回调 URL 替换为真网关,入账仍**仅**在回调/验签成功路径中调用 `DepositSettlement::settle`(与当前模拟回调一致)。
|
||
- 档位与渠道取自 `depositTierList`:创建订单时须选择返回 `channels` 中某一渠道的 `code` 作为 `channel_code` 传入;服务端会校验档位存在、启用且渠道已启用。
|
||
- **HMAC 密钥**:模拟链路与签名校验使用环境变量 **`DEPOSIT_MOCK_HMAC_KEY`**(或 `config('app.deposit_mock_hmac_key')`);生产环境务必配置,与代码中默认值区分。
|
||
- **并发上限**:同一用户最多同时存在 **3 笔待支付充值单**(`status=0`);超过后创建接口返回 `code=2005`。
|
||
- **超时失效**:充值单创建后 **60 秒内未支付**将自动置为失败(`status=failed`),并在订单备注记录失败原因(`[timeout] unpaid over 60s`)。
|
||
- **定时任务兜底**:服务端进程 `depositOrderExpireTicker` 每 **10 秒**主动扫描超时待支付单,保证即使用户不访问任何充值接口也会准时失效。
|
||
|
||
请求参数(**三者缺一不可**,任一为空或空白即 `code=1001` 参数缺失):
|
||
- `tier_id`:string,必填(含义:玩家选择的充值档位 ID,取自 `depositTierList` 的 `id`;也可用同义字段名 `tier_key`)
|
||
- `channel_code`:string,必填(含义:支付渠道代码,**小写**;须与所选档位在 `depositTierList` 返回的 `channels[].code` 之一一致,例如默认内置渠道常为 `directpay`)
|
||
- `idempotency_key`:string,必填,≤64(含义:客户端生成的唯一键,短时间内同 `idempotency_key` 不会重复下单;建议 UUID。**调试工具中若使用变量,请确保解析后非空**)
|
||
|
||
> **常见 1001 原因**:只传了 `tier_id` + `idempotency_key`,**漏传 `channel_code`**。请先调 `depositTierList`,用对应档位下 `channels` 中某项的 `code` 作为 `channel_code`。
|
||
|
||
返回参数:
|
||
- `order_no`:string(含义:充值订单号)
|
||
- `amount`:string(2 位小数,含义:玩家本次支付的充值金额,与所选档位 `amount` 一致)
|
||
- `bonus_amount`:string(2 位小数,含义:本次赠送金额,与所选档位 `bonus_amount` 一致,无赠送为 `0.00`)
|
||
- `total_amount`:string(2 位小数,含义:实际入账总额 = amount + bonus_amount)
|
||
- `pay_channel`:string(含义:支付通道标识,与请求中选择的 `channel_code` 一致,落库在订单上)
|
||
- `paid`:bool(含义:当前单据是否已到账;`true` 表示钱包已入账、`status=paid`;`false` 表示待玩家在第三方支付页面完成支付)
|
||
- `pay_url`:string(含义:第三方支付收银台地址;**`paid=false`(待支付)** 时返回**完整 URL**(如 `https://你的域名/api/finance/depositMockPayPage?order_no=...&sign=...`);`paid=true` 时为空串)
|
||
- `status`:string(`pending`/`paid`/`failed`,含义:本接口创建成功时为 `pending`,入账完成后为 `paid`)
|
||
- `create_time`:int(含义:订单创建时间,秒级时间戳)
|
||
- `pay_time`:int(含义:订单到账时间,未到账为 0)
|
||
|
||
#### 5.4.1 模拟第三方:收银台页与「异步通知」回调(开发/无真网关时使用)
|
||
|
||
- **GET** `/api/finance/depositMockPayPage`
|
||
- **Query**:`order_no`(与 `depositCreate` 返回一致)、`sign`(HMAC,与 `pay_url` 中一致;**不要自行拼接,须完整使用 `depositCreate` 返回的 `pay_url` 或同接口再次查询到的地址**)
|
||
- 无需 `auth-token` / `user-token`(外跳浏览器使用)。
|
||
- 返回:HTML 页面,用户点击 **「确认支付(模拟成功)」** 即提交到下方 `depositMockNotify`。
|
||
|
||
- **POST** `/api/finance/depositMockNotify`
|
||
- **Body**(`application/x-www-form-urlencoded` 或 JSON 均可,字段名一致即可):`order_no`、`sign`(与上页/支付链接一致)
|
||
- 无需 `user-token`;`auth-token` 可选(当前实现不校验)。
|
||
- 验签成功后:对 `status=0` 的订单执行入账(`DepositSettlement::settle`,`source=third_party` 语义),并推送 `wallet.changed`。已入账订单**幂等**再调返回当前订单信息。
|
||
- 成功响应:与 `depositCreate` 成功体相同结构(`code=1` + `data` 为统一充值订单结构)。
|
||
|
||
错误码约定:
|
||
- `1001`:缺少必填参数(`tier_id`(或 `tier_key`)、`channel_code`、`idempotency_key` 任一未传或为空字符串)
|
||
- `1002`:`idempotency_key` 过长,或与其他玩家的订单冲突
|
||
- `1003`:模拟回调/链接参数非法(如 `sign` 与 `order_no` 不匹配)——`depositMockNotify` 与无效支付链接
|
||
- `2000`:订单落库或入账失败(事务回滚后返回原始错误描述)
|
||
- `2003`:所选 `tier_id` 不存在、已停用或不在启用列表中
|
||
- `2004`:`channel_code` 未配置或已禁用
|
||
- `2005`:待支付充值单超过上限(`data.max_pending`、`data.pending_count`、`data.expire_seconds`)
|
||
|
||
### 5.5 查看充值订单详情
|
||
- **POST** `/api/finance/depositDetail`
|
||
|
||
请求参数:
|
||
- `order_no`:string,必填(含义:充值订单号)
|
||
|
||
返回参数(与 `depositCreate` 统一结构):
|
||
- `order_no`:string(含义:充值订单号)
|
||
- `amount`:string(2 位小数,含义:本单充值金额)
|
||
- `bonus_amount`:string(2 位小数,含义:本单赠送金额,无赠送为 `0.00`)
|
||
- `total_amount`:string(2 位小数,含义:入账总额)
|
||
- `pay_channel`:string(含义:支付通道标识)
|
||
- `paid`:bool(含义:是否已到账)
|
||
- `pay_url`:string(含义:第三方支付页面地址,已到账为空串)
|
||
- `status`:string(`pending`/`paid`/`failed`)
|
||
- `create_time`:int(含义:订单创建时间)
|
||
- `pay_time`:int(含义:订单到账时间,未到账为 0)
|
||
|
||
### 5.6 查询充值订单列表
|
||
- **POST** `/api/finance/depositList`
|
||
|
||
用于我的充值记录页的分页列表;列表含订单状态,到账时间/支付通道等完整字段请再调用 `/api/finance/depositDetail` 获取。
|
||
|
||
请求参数:
|
||
- `page`:int,选填,默认 `1`(含义:页码,从 1 开始)
|
||
- `page_size`:int,选填,默认 `20`,最大 `100`(含义:每页数量,超出范围回退为 `20`)
|
||
|
||
返回参数:
|
||
- `list`:array(含义:充值订单列表,按 `id desc` 排序)
|
||
- `order_no`:string(含义:充值订单号)
|
||
- `amount`:string(2 位小数,含义:本单充值金额)
|
||
- `bonus_amount`:string(2 位小数,含义:本单赠送金额,无赠送为 `0.00`)
|
||
- `status`:string(含义:订单状态,与 `depositDetail` 一致:`pending`/`paid`/`failed`)
|
||
- `pagination`:object(含义:分页信息)
|
||
- `page`:int(含义:当前页码)
|
||
- `page_size`:int(含义:每页数量)
|
||
- `total`:int(含义:总记录数)
|
||
|
||
### 5.7 提现申请
|
||
- **POST** `/api/finance/withdrawCreate`
|
||
|
||
请求参数:
|
||
- `withdraw_coin`:string(含义:申请提现金额,必须 > 0)
|
||
- `receive_account`:string(含义:收款账号)
|
||
- `receive_type`:string(`bank`/`ewallet`/`crypto`,含义:收款类型)
|
||
- `idempotency_key`:string(含义:防重复提交提现)
|
||
|
||
返回参数:
|
||
- `order_no`:string(含义:提现订单号)
|
||
- `status`:string(`pending_review`/`processing`,含义:提现状态)
|
||
- `fee_coin`:string(含义:手续费)
|
||
- `actual_arrival_coin`:string(含义:实到账金额)
|
||
- `risk_review_required`:bool(含义:是否命中人工审核)
|
||
|
||
校验顺序(任一失败即返回对应错误码,不再创建订单):
|
||
1. 参数完整性与金额合法性(`code=1001`;金额必须为数值且 > 0)
|
||
2. **待审核订单数限制**:同一用户 `status=0`(待审核)的 `withdraw_order` 不得超过 3 笔,否则 `code=2004 Too many pending withdraw orders`,`data` 中回传:
|
||
- `max_pending`:上限值(当前为 `3`)
|
||
- `pending_count`:当前待审核订单数
|
||
3. `coin_balance >= withdraw_coin`,否则 `code=2001 Insufficient balance`
|
||
4. **单笔上限校验**:`withdraw_coin <= max_withdrawable`,否则 `code=2002 Withdraw exceeds available bet flow`,`data` 中回传:
|
||
- `max_withdrawable`:**当前允许的单笔最大提现金额**(= `min(coin_balance, max_withdraw_by_flow)`,前端据此提示"最大可提现金额为 XXX")
|
||
- `coin_balance`、`bet_flow_coin`、`total_withdraw_coin`、`ratio`
|
||
- `max_withdraw_by_flow`:仅按打码量折算的上限(= `max(0, bet_flow_coin / ratio - total_withdraw_coin)`);`ratio=0` 时为 `null`
|
||
5. 以上全通过后在同一事务内:
|
||
- `withdraw_order` 写入:`amount` / `fee`(默认 0.5%) / `actual_amount = amount - fee` / `status=0`(待审核) / `channel_id` 取自用户归属渠道快照。
|
||
- `user` 表原子更新:`coin -= withdraw_coin` 且 `total_withdraw_coin += withdraw_coin`(WHERE `coin >= withdraw_coin` 防止并发超额扣减)。
|
||
- `user_wallet_record` 写入 `biz_type=withdraw`、`direction=2`、`amount=withdraw_coin`、`ref_type=withdraw_order`、`idempotency_key=wd_apply_{order_no}`,代表"冻结"动作。
|
||
|
||
说明(打码量即提现配额模型):
|
||
- 单笔最大可提现 `max_withdrawable = min(coin_balance, max_withdraw_by_flow)`;每笔提现按 `withdraw_coin × ratio` 消耗打码配额,已消耗部分累积在 `total_withdraw_coin`。
|
||
- `ratio = 0` 时视为"不限打码",单笔上限仅受 `coin_balance` 约束。
|
||
- 采用"申请即冻结"语义:提现在移动端提交后立即从 `user.coin` 中扣减并写出金流水;后台审核 **拒绝** 时由管理端在同一事务中回冲余额、`total_withdraw_coin` 与流水,不出现"等待审核期间用户还能把这笔钱再下注"的漏洞。
|
||
- 后台审核 **通过** 时不再额外触碰余额;若管理员调整了 `amount` 或 `fee`,按新旧差额再生成一条 `withdraw` / `withdraw_refund` 流水以保持账务平衡,并同步修正 `total_withdraw_coin`。
|
||
- `withdraw_bet_flow_ratio` 由后台「游戏配置」维护,默认 `1.00`,修改后对新请求立即生效。
|
||
|
||
### 5.8 查看提现订单详情
|
||
- **POST** `/api/finance/withdrawDetail`
|
||
|
||
请求参数:
|
||
- `order_no`:string,必填(含义:提现订单号)
|
||
|
||
返回参数:
|
||
- `order_no`:string(含义:提现订单号)
|
||
- `status`:string(`pending_review`/`approved`/`rejected`,含义:审核状态;`status=3 已打款` 暂未对外暴露,合并到 `approved`)
|
||
- `withdraw_coin`:string(含义:申请提现金额,与后台 `withdraw_order.amount` 对齐)
|
||
- `fee_coin`:string(含义:手续费,与后台 `withdraw_order.fee` 对齐)
|
||
- `actual_arrival_coin`:string(含义:实际到账金额 = 申请金额 - 手续费;后台审核调整后会同步刷新)
|
||
- `reject_reason`:string/null(含义:拒绝原因,`status=rejected` 时取自 `withdraw_order.remark`,否则为 `null`)
|
||
- `create_time`:int(含义:申请时间)
|
||
- `review_time`:int/null(含义:审核时间戳,未审核为 `null`)
|
||
|
||
### 5.9 查询提现订单列表
|
||
- **POST** `/api/finance/withdrawList`
|
||
|
||
用于我的提现记录页的分页列表;列表含审核/打款状态摘要,手续费、实到账、拒绝原因等请再调用 `/api/finance/withdrawDetail` 获取。
|
||
|
||
请求参数:
|
||
- `page`:int,选填,默认 `1`(含义:页码,从 1 开始)
|
||
- `page_size`:int,选填,默认 `20`,最大 `100`(含义:每页数量,超出范围回退为 `20`)
|
||
|
||
返回参数:
|
||
- `list`:array(含义:提现订单列表,按 `id desc` 排序)
|
||
- `order_no`:string(含义:提现订单号)
|
||
- `amount`:string(2 位小数,含义:申请提现金额,与后台 `withdraw_order.amount` 对齐)
|
||
- `status`:string(含义:订单状态,与 `withdrawDetail` 一致:`pending_review`/`approved`/`rejected`;后台已打款 `status=3` 合并为 `approved`)
|
||
- `pagination`:object(含义:分页信息)
|
||
- `page`:int(含义:当前页码)
|
||
- `page_size`:int(含义:每页数量)
|
||
- `total`:int(含义:总记录数)
|
||
|
||
---
|
||
|
||
## 6. 公告与消息模块(operation/notice)
|
||
|
||
### 6.1 拉取公告列表
|
||
- **GET** `/api/notice/noticeList`
|
||
|
||
请求参数(query string):
|
||
- `page`:int(可选,默认 1)
|
||
- `page_size`:int(可选,默认 20)
|
||
|
||
返回参数:
|
||
- `list`:array<object>
|
||
- `notice_id`:int(含义:公告 ID)
|
||
- `title`:string(含义:公告标题)
|
||
- `notice_type`:string(`silent`/`popout`,含义:公告类型)
|
||
- `is_read`:bool(含义:当前用户是否已读)
|
||
- `publish_time`:int(含义:发布时间)
|
||
|
||
### 6.2 公告详情
|
||
- **GET** `/api/notice/noticeDetail`
|
||
|
||
请求参数(query string):
|
||
- `id`:int,必填(含义:公告 ID)
|
||
|
||
返回参数:
|
||
- `notice_id`:int(含义:公告 ID)
|
||
- `title`:string(含义:公告标题)
|
||
- `content`:string(含义:公告正文)
|
||
- `notice_type`:string(含义:公告类型)
|
||
- `must_confirm`:bool(含义:是否必须手动确认)
|
||
- `publish_time`:int(含义:发布时间)
|
||
|
||
### 6.3 强弹窗确认已读
|
||
- **GET** `/api/notice/noticeConfirm`
|
||
|
||
请求参数(query string):
|
||
- `notice_id`:int(含义:待确认公告 ID)
|
||
|
||
返回参数:
|
||
- `notice_id`:int(含义:已确认公告 ID)
|
||
- `confirmed`:bool(含义:确认结果)
|
||
- `confirm_time`:int(含义:确认时间)
|
||
|
||
---
|
||
|
||
## 7. WebSocket(H5)与状态同步
|
||
|
||
> 本版本已移除 webman/push 频道模式;H5 前端使用原生 WebSocket 直连,HTTP 轮询仅作为弱网兜底。
|
||
|
||
### 7.1 WebSocket 连接与消息
|
||
|
||
- **连接地址**:由服务端配置下发(后台测试页读取 `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.1.1 消息协议字段定义(联调口径)
|
||
|
||
- 客户端 -> 服务端:
|
||
- `action`:动作名(当前约定 `ping` / `subscribe`)
|
||
- `topics`:仅 `subscribe` 时必填,表示要订阅的主题列表(数组)
|
||
- 服务端 -> 客户端:
|
||
- `event`:事件名(如 `period.tick`、`wallet.changed`、`jackpot.hit`)
|
||
- `topic`:所属主题(通常与 `event` 一致;用于前端按主题路由)
|
||
- `data`:业务载荷(对象)
|
||
- `server_time`:服务端时间戳(秒,倒计时与对时基准)
|
||
|
||
#### 7.1.2 订阅行为说明
|
||
|
||
- **仅建立连接不会自动下发全部业务消息**;客户端需要发送 `subscribe` 明确订阅主题。
|
||
- 成功订阅后服务端返回:`{"event":"ws.subscribed","topics":[...]}`。
|
||
- 若未订阅主题,通常只能收到握手首帧(`ws.connected`)和心跳回包(`pong`)。
|
||
|
||
#### 7.1.3 推送频率与触发规则(当前实现)
|
||
|
||
- `period.tick`:**每秒一次**(用于倒计时、状态同步)。
|
||
- `admin.live.snapshot`:**每秒一次**(后台实时对局页全量快照)。
|
||
- `period.opened` / `period.payout` / `admin.live.opened`:按开奖流程阶段触发(事件触发型,非固定频率)。
|
||
- `wallet.changed`:仅在余额发生变更时推送(如下注扣款、充值入账、派彩入账)。
|
||
- `jackpot.hit`:**仅在本期存在中大奖命中用户时推送**;无命中不推送。
|
||
|
||
### 7.1A 后台连接方式(管理端联调)
|
||
|
||
- 后台菜单:仅保留一个菜单 `连接服务器websocket`,用于统一联调 WebSocket
|
||
- 后台连接入口:
|
||
- `/admin/test.GameCurrentStatus/wsConfig`
|
||
- 后台页面能力:
|
||
- 读取 `ws_url`、`connect_tip`、`sample_messages`
|
||
- 手动连接/断开 WebSocket
|
||
- 手动发送订阅与心跳报文
|
||
- 实时查看服务端返回帧内容(用于联调事件格式)
|
||
|
||
### 7.2 HTTP 兜底接口
|
||
|
||
- 本版本已移除以下兜底接口:`/api/game/currentStatus`、`/api/game/periodHistory`、`/api/wallet/balanceSummary`。
|
||
- 状态与余额统一以 WebSocket 推送为主,HTTP 仅保留业务动作/详情查询接口(如 `placeBet`、`depositDetail`、`withdrawDetail`)。
|
||
|
||
### 7.3 一致性规则
|
||
|
||
- 倒计时以服务端下发时间为准,不信任本地时钟累计。
|
||
- 下注成功后以 `placeBet` 返回的 `balance_after` 为准,并等待 `wallet.changed` 同步。
|
||
- WebSocket 断线后立即重连并重新订阅主题,不再依赖 `currentStatus/periodHistory/balanceSummary` 回补。
|
||
|
||
---
|
||
|
||
## 8. 移动端完整调用流程
|
||
|
||
## 8.1 首次进入游戏
|
||
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)连接,发送订阅消息监听状态流
|
||
5. 用户下注调用 `POST /api/game/placeBet`
|
||
6. 下单后以 `placeBet.balance_after` 与 `wallet.changed` 同步余额
|
||
7. 断线或页面回前台时,重连 WebSocket 并重新订阅主题回补实时状态
|
||
|
||
## 8.2 充值到下注到提现闭环
|
||
1. 拉取档位:`POST /api/finance/depositTierList`(玩家选择一档,并记下该档 `channels[].code`)
|
||
2. 创建订单:`POST /api/finance/depositCreate`(`tier_id` + `channel_code` + `idempotency_key`,三者为必填;可用 JSON / form-data / x-www-form-urlencoded)
|
||
- 返回 `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/placeBet`
|
||
5. 监听余额:`wallet.changed`(或按订单详情接口核对)
|
||
6. 查询流水:`POST /api/wallet/recordList`
|
||
7. 提现:`POST /api/finance/withdrawCreate`(即时冻结 `user.coin` 与写出 `withdraw` 流水) -> `POST /api/finance/withdrawDetail`
|
||
|
||
## 8.3 公告强触达流程
|
||
1. 客户端监听 `notice.popout`
|
||
2. 拉取详情 `GET /api/notice/noticeDetail`
|
||
3. 用户勾选确认 `GET /api/notice/noticeConfirm?notice_id=...`
|
||
4. 未确认前可由前端阻断下注入口
|
||
|
||
---
|
||
|
||
## 9. 游戏时序流程图(WebSocket + HTTP兜底)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[用户登录 /api/user/login] --> B[拉初始化 /api/game/lobbyInit]
|
||
B --> C[连接 WebSocket 并订阅主题]
|
||
C --> D{0-20秒下注期?}
|
||
D -- 是 --> E[提交下注 /api/game/placeBet]
|
||
E --> F[等待 wallet.changed 同步余额]
|
||
D -- 否 --> G[进入封盘与开奖阶段]
|
||
G --> H[服务端算票与开奖]
|
||
H --> I[WebSocket 推送状态变化]
|
||
I --> J[断线重连并重新订阅]
|
||
J --> C
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 后台渠道分红比例配置(管理端补充)
|
||
|
||
> 本节为管理后台 `/admin/channel`「分配比例」弹窗补充口径,用于便于管理员按角色层级设置二次分红比例。
|
||
|
||
### 10.1 角色组展示规则
|
||
|
||
- 表格列顺序调整为:`角色组层级` -> `负责人` -> `状态` -> `分配比例(%)`
|
||
- `角色组层级` 在 `负责人` 前展示,降低识别与分配成本
|
||
- 层级路径使用 `/` 拼接,如:`顶级组 / 运营组 / 一组`
|
||
- 同一负责人若存在多个角色组,按多标签展示多条路径
|
||
- 无角色组时显示 `-`
|
||
|
||
### 10.2 接口:读取渠道管理员分配配置
|
||
|
||
- **GET** `/admin/channel/channelAdminShareList?id={channel_id}`
|
||
|
||
返回参数(`data.list[]`)新增:
|
||
- `group_paths`:array<string>(负责人所属角色组层级路径列表)
|
||
- `group_paths_text`:string(层级路径拼接文本,`|` 分隔,用于兼容纯文本场景)
|
||
|
||
返回示例(节选):
|
||
```json
|
||
{
|
||
"code": 1,
|
||
"message": "ok",
|
||
"data": {
|
||
"channel_id": 1,
|
||
"channel_name": "渠道A",
|
||
"list": [
|
||
{
|
||
"admin_id": 12,
|
||
"username": "zhuguan1",
|
||
"group_paths": ["顶级组 / 运营组 / A组"],
|
||
"group_paths_text": "顶级组 / 运营组 / A组",
|
||
"status": 1,
|
||
"share_rate": "30.00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 10.3 保存约束(沿用现有)
|
||
|
||
- **POST** `/admin/channel/saveChannelAdminShare`
|
||
- 仅 `status=1` 的行参与占比汇总
|
||
- 启用项分配比例总和必须严格等于 `100.00`
|
||
|
||
---
|
||
|
||
## 11. 需要你确认的实现口径(进入接口开发前)
|
||
|
||
1. **登录方式**:仅账号密码,还是要短信/邮箱验证码?
|
||
2. **提现收款类型**:首版只做银行卡,还是同时支持电子钱包/加密地址?
|
||
3. **自动托管**:是否首期上线;若不上线可先隐藏 `auto-bet` 接口。
|
||
4. **WebSocket 主题定义**:状态流、资金流、托管流的 topic 与消息体是否按本文固定。
|
||
5. **错误码规范**:是否已有公司统一错误码表;若有需对齐替换本草案码段。
|
||
|
||
确认后可进入下一步:按该文档落地 controller + validate + service + 路由。
|