Files
36-character-flower/docs/36字花-移动端接口设计草案.md
2026-04-30 19:04:19 +08:00

825 lines
47 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 获取接口鉴权 Tokenauth-token
- **GET** `/api/v1/authToken`
- 用途:获取 `auth-token`(所有接口请求头必带)
请求示例:
`/api/v1/authToken?secret=564d14asdasd113e46542asd6das1a2a&timestamp=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合法范围 136
- `chips`array[string](如 `["1.00","5.00"]`,含义:快捷筹码面额)
- `single_number_max_bet`string含义单号码最大下注额
- `dictionary`array<object>
- `number`int1-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`;每个号码为 136 的整数,数量不超过 `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`int1入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`string2 位小数,含义:对外标价金额,与业务配置一致;展示用)
- `amount`string2 位小数,含义:玩家本次需支付的充值金额)
- `bonus_amount`string2 位小数,含义:该档位赠送金额,无赠送为 `0.00`
- `total_amount`string2 位小数,含义:到账总额 = 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`**模拟第三方收银台** 完整 URLHMAC 防篡改,见下 §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`string2 位小数,含义:玩家本次支付的充值金额,与所选档位 `amount` 一致)
- `bonus_amount`string2 位小数,含义:本次赠送金额,与所选档位 `bonus_amount` 一致,无赠送为 `0.00`
- `total_amount`string2 位小数,含义:实际入账总额 = 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`string2 位小数,含义:本单充值金额)
- `bonus_amount`string2 位小数,含义:本单赠送金额,无赠送为 `0.00`
- `total_amount`string2 位小数,含义:入账总额)
- `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`string2 位小数,含义:本单充值金额)
- `bonus_amount`string2 位小数,含义:本单赠送金额,无赠送为 `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`string2 位小数,含义:申请提现金额,与后台 `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. WebSocketH5与状态同步
> 本版本已移除 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&timestamp=xxx&device_id=xxx&signature=xxx` 获取 `auth-token`
2. `POST /api/user/login` 登录(请求头带 `auth-token`
3. `POST /api/game/lobbyInit` 拉首页初始化(请求头带 `auth-token`
4. 建立 WebSocketH5连接发送订阅消息监听状态流
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 + 路由。