# 疯狂转盘 前端接口文档 > **版本**:v2.0 > **更新时间**:2025-01 > **适用前端**:Web/H5/小程序 > **Base URL**:`{host}/api/wheel` --- ## 📋 目录 1. [玩法概述](#一玩法概述) 2. [通用约定](#二通用约定) 3. [接口清单](#三接口清单) 4. [接口详情](#四接口详情) 5. [时间轴使用指南](#五时间轴使用指南) 6. [前端对接要点](#六前端对接要点) --- ## 一、玩法概述 疯狂转盘是一款 **选颜色+符号 → 下注 → 转盘停 → 命中得饰品** 的真人轮盘游戏。 ### 核心规则 | 项目 | 说明 | |------|------| | **盘面结构** | 内外圈各 24 格,索引一一对应
• 外圈符号:固定布局(A金/B红/C紫/D蓝)
• 内圈颜色:每轮随机排布(金6/红8/紫10) | | **下注方式** | 选 1 个「颜色×符号」组合 + 1 个下注额
**每轮每人最多 50 注** | | **下注额档位** | 10 档:5 / 10 / 20 / 50 / 100 / 200 / 500 / 1000 / 2000 / 5000 元 | | **倍率** | 12 个倍率(3 色 × 4 符号),所有档位共用
例:gold+D 倍率=22,押 100 元中奖得 2200 元 | | **命中判定** | 下注的 `color+symbol` == 开奖的 `resultColor+resultSymbol` | | **中奖奖励** | 饰品价值 = **下注额 × 倍率**
直接进背包,无需选择"保留/分解" | | **未中奖励** | 每注发 **0.01 元安慰饰品**(保证每注必有产出) | | **决策池** | 单一真实奖池,所有人同一个落点
• `real`(真实池,参与落点/RTP 决策)= 普通(02)+主播(01)+招商(04)+卡商(05)
• `display`(展示,可下注/返奖但不入真实池)= 机器人(03)+白号(06) | ### 一局生命周期 ``` newRound 创建 ↓ 下注期 (30s) ← 玩家可调用 /dobet 下注 ↓ 封盘期 (2s) ← 不再接受下注,倒计时"即将开奖" ↓ 开奖 + 动画 (7s) ← 后端出结果,前端播转盘动画 ↓ 结果展示 → 下一局 ``` --- ## 二、通用约定 ### 2.1 请求方式 - **协议**:HTTPS - **Method**:全部 `POST` - **Content-Type**:`application/json` - **鉴权**:`Authorization: Bearer {token}`(登录后从响应头或登录接口获取) > ⚠️ **重要**:`userId` 由后端从 token 解析,**客户端不要传 userId 参数**。 ### 2.2 统一响应结构 ```json { "code": 200, "msg": "操作成功", "data": {} } ``` | code | 含义 | 前端处理 | |------|------|----------| | 200 | 成功 | 正常处理 data | | 500 | 业务失败 | Toast 显示 msg | | 701 | 未实名认证 | 弹实名认证弹窗 | | 702 | 账户余额不足 | 提示充值 | | 703 | 玩家未充值,不能游玩 | 引导首充 | ### 2.3 货币单位 - 所有金额字段为 **String 类型**,单位:**元**(精确到小数点后 2 位) - 示例:`"balance": "9800.50"` 表示 9800.50 元 ### 2.4 时间格式 - **时间戳**:`Long` 类型,**秒级** Unix 时间戳(如 `1738300000`) - **时间字符串**:`yyyy-MM-dd HH:mm:ss`(东八区) --- ## 三、接口清单 | # | 接口名称 | 路径 | 用途 | 调用时机 | |---|---------|------|------|---------| | 1 | ~~获取游戏配置~~ | ~~`/game_config`~~ | **已废弃**,数据已合并到 `/last_round_info` | ~~进入游戏页时调用 1 次~~ | | 2 | 投注 | `/dobet` | 下注一笔 | 玩家点击下注按钮 | | 3 | 获取当前局状态 | `/last_round_info` | **一次拿全**:配置+状态+开奖+下注 | **轮询**(1-2 秒间隔) | | 4 | 我的下注记录 | `/history_bet_info` | 我的全部下注记录(中奖+未中奖,分页) | 打开"记录"页 | | 5 | 全服中奖记录 | `/round_history_bet_info` | 全服中奖公示(只含真正命中,分页) | 打开"中奖榜"页 | | 6 | 历史开奖结果 | `/round_results` | 每一轮的开奖符号(分页) | 打开"历史开奖"页 | --- ## 四、接口详情 ### 4.1 ~~获取游戏配置~~ (已废弃) > **⚠️ 已废弃**:本接口数据已全部合并到 `/last_round_info`,前端只需轮询一个接口即可。 > 旧接口保留仅为兼容旧版前端,新前端请直接使用 `/last_round_info`。 --- ### 4.2 投注 **`POST /api/wheel/dobet`** 下注一笔。**每轮每人上限 50 注**。 #### 请求参数 ```json { "bet": 100, "color": "gold", "symbol": "D" } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `bet` | Integer | ✅ | 下注额,必须是 10 档之一 | | `color` | String | ✅ | 内圈色:`gold` / `red` / `purple` | | `symbol` | String | ✅ | 外圈符:`A` / `B` / `C` / `D` | #### 响应示例 ```json { "code": 200, "msg": "操作成功", "data": { "roundId": 10086, "cost": "100.00", "balance": "9700.50", "credits": "1200.00" } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `roundId` | Long | 本回合 ID | | `cost` | String | 本次扣款(= 下注额) | | `balance` | String | 下注后余额(金币) | | `credits` | String | 下注后积分 | #### 常见错误 | code | msg | 场景 | |------|-----|------| | 500 | 网站维护中...... | 全站维护 | | 500 | 疯狂转盘维护中...... | 转盘维护 | | 500 | 游戏不在投注期 | 已封盘/开奖中 | | 500 | 每轮最多下注50注 | 超出上限 | | 500 | 该下注组合未配置奖品 | 矩阵缺失(联系运营) | | 702 | 账户余额不足 | 需充值 | | 703 | 玩家未充值不能游玩任何游戏 | 需首充 | --- ### 4.3 获取当前局状态 **`POST /api/wheel/last_round_info`** **核心接口**,轮询调用(建议 1-2 秒间隔),**一次返回全部数据**:游戏配置 + 当前回合状态 + 开奖结果 + 我的下注。 #### 请求参数 ```json {} ``` #### 响应示例 ```json { "code": 200, "msg": "操作成功", "data": { "roundId": 10086, "periodNo": 202501011, "serverTime": 1738300050, "startTime": 1738300000, "bettedTime": 1738300030, "spinTime": 1738300032, "endTime": 1738300039, "status": "spined", "outerSymbols": ["A","D","C","D","B","D","C","D","B","C","D","C","A","D","C","D","C","D","B","D","C","D","C","D"], "bets": [5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000], "myPoolType": "real", "rewardMatrix": [ { "betAmount": 5, "color": "gold", "symbol": "D", "multiplier": 22, "prizeValue": "110.00", "prizeName": "黄金玫瑰", "prizeGrade": 3, "prizeExterior": "崭新出厂", "prizeImage": "https://example.com/prize.png", "prizeLevelImg": "https://example.com/level3.png" } ], "innerColors": ["gold","gold","red","purple","purple","red","gold","purple","red","purple","gold","purple","red","purple","gold","gold","purple","purple","red","gold","purple","red","purple"], "resultIndex": 5, "resultColor": "gold", "resultSymbol": "D", "totalWin": "2200.00", "userBets": [ { "color": "gold", "symbol": "D", "count": 3, "totalBet": "300.00" }, { "color": "red", "symbol": "A", "count": 1, "totalBet": "50.00" } ], "winResult": [ { "color": "gold", "symbol": "D", "hit": true, "boxRecordId": 123456, "prizeName": "黄金玫瑰", "prizeImage": "https://example.com/prize.png", "prizeGrade": 5, "prizeLevelImg": "https://example.com/level5.png", "prizeExterior": "崭新出厂", "prizeValue": "2200.00" }, { "color": "red", "symbol": "A", "hit": false, "boxRecordId": 123457, "prizeName": "安慰饰品", "prizeImage": "https://example.com/consolation.png", "prizeGrade": 1, "prizeLevelImg": "https://example.com/level1.png", "prizeExterior": "崭新出厂", "prizeValue": "0.01" } ] } } ``` #### 响应字段说明 **游戏配置字段**(原 `/game_config` 接口数据,现已合并): | 字段 | 类型 | 说明 | |------|------|------| | `outerSymbols` | Array\ | 外圈 24 格符号固定布局(`A`/`B`/`C`/`D`)
例:`["A","D","C","D",...]` | | `bets` | Array\ | 10 档下注额 | | `myPoolType` | String | 当前用户所属决策池
`real`:真实池(普通02/主播01/招商04/卡商05,参与落点与 RTP)
`display`:展示(机器人03/白号06,可下注/返奖但不入真实池)
`null`:不可参与(如超管99) | | `rewardMatrix` | Array | 奖励矩阵(120 条,10 档 × 3 色 × 4 符号)
每条含 `betAmount`/`color`/`symbol`/`multiplier` 及饰品信息(名称/图片/品质图片/磨损度/价格) | **当局状态字段**: | 字段 | 类型 | 说明 | |------|------|------| | `roundId` | Long | 回合 ID | | `periodNo` | Long | 全局唯一期号 | | `serverTime` | Long | **服务器当前时间**(秒级时间戳)
用于和其他时间点对比算倒计时 | | `startTime` | Long | 本局开始时间 | | `bettedTime` | Long | **封盘时间点** = startTime + 下注时间
到这个时刻后不能再下注 | | `spinTime` | Long | **开奖时间点** = bettedTime + 封盘时间
到这个时刻后开始播转盘动画 | | `endTime` | Long | **本局结束时间** = spinTime + 开奖动画时间
动画播完、下一局即将开始 | | `status` | String | `betting`:投注期
`spined`:封盘/开奖中 | | `innerColors` | Array\ | **本轮内圈 24 色实际排列**(`gold`/`red`/`purple`)
例:`["gold","red","purple",...]` 用于渲染盘面 | | `resultIndex` | Integer | 开奖格子索引(0-23)
未开奖时为 `null` | | `resultColor` | String | 开奖颜色 `gold`/`red`/`purple`
未开奖时为 `null` | | `resultSymbol` | String | 开奖符号 `A`/`B`/`C`/`D`
未开奖时为 `null` | | `totalWin` | String | 当前用户本轮总返奖(饰品价值合计) | | `userBets` | Array | **当前用户本轮下注汇总**(按颜色+符号聚合,见下表) | | `winResult` | Array | **当前用户本轮发奖明细**(每注一条,见下表) | **userBets 结构**(我的下注汇总,按颜色+符号聚合): | 字段 | 类型 | 说明 | |------|------|------| | `color` | String | 下注的颜色 | | `symbol` | String | 下注的符号 | | `count` | Integer | 下注次数 | | `totalBet` | String | 总下注额 | **winResult 结构**(我的发奖明细,每注一条): | 字段 | 类型 | 说明 | |------|------|------| | `color` | String | 下注的颜色 | | `symbol` | String | 下注的符号 | | `hit` | Boolean | 是否命中(`true`=中奖矩阵饰品,`false`=安慰饰品) | | `boxRecordId` | Long | **背包记录ID**(用于调用分解/出售接口) | | `prizeName` | String | 实际发放的饰品名称 | | `prizeImage` | String | 饰品图片 URL | | `prizeGrade` | Integer | 饰品品质等级 | | `prizeLevelImg` | String | 饰品品质图片 URL | | `prizeExterior` | String | 饰品磨损度 | | `prizeValue` | String | 饰品价格(命中=下注额×倍率;安慰=0.01) | > 💡 **前端使用提示**: > - 未开奖时 `winResult` 为空 > - 已开奖后,`winResult` 每注一条:`hit=true` 是中奖饰品,`hit=false` 是安慰饰品(0.01元) > - 使用 `boxRecordId` 调用分解/出售接口 --- ### 4.4 我的下注记录 **`POST /api/wheel/history_bet_info`** 当前登录用户自己的全部下注记录(中奖+未中奖),分页。未中奖注单(`win = 0.01` 安慰奖)的饰品字段为 `null`。 #### 请求参数 ```json { "pageNum": 1, "pageSize": 10 } ``` #### 响应示例 ```json { "code": 200, "msg": "操作成功", "data": { "total": 25, "rows": [ { "periodNo": 2026090725038, "endTime": "2026-09-07 09:14:49", "userId": 666299, "nickName": "李四2", "avatar": "/profile/upload/xxx.png", "bet": "5.00", "betSymbol": "D", "win": "40.00", "betCount": 3, "prizeGrade": 1, "prizeName": "某某饰品", "prizeImage": "/profile/upload/xxx.png" } ] } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `total` | Long | 总中奖注数 | | `rows` | Array | 中奖注单列表 | | `rows[].periodNo` | Long | 完整期号 | | `rows[].endTime` | String | 回合结束时间(yyyy-MM-dd HH:mm:ss) | | `rows[].userId` | Long | 用户 ID | | `rows[].nickName` | String | 用户昵称(完整,不脱敏) | | `rows[].avatar` | String | 用户头像 | | `rows[].bet` | String | 单注下注额 | | `rows[].betSymbol` | String | 中奖符号 | | `rows[].win` | String | 中奖额 | | `rows[].betCount` | Integer | 该中奖符号下注次数 | | `rows[].prizeGrade` | Integer | 饰品品质等级(对应 tt_ornaments_level.id:1金/2红/3紫/4蓝) | | `rows[].prizeName` | String | 饰品名称 | | `rows[].prizeImage` | String | 饰品图片 | --- ### 4.5 全服中奖记录 **`POST /api/wheel/round_history_bet_info`** 全服中奖公示(只保留真正命中 `win > 0.01` 的注单,排除 0.01 安慰奖),分页。结构与 `history_bet_info` 完全一致,只是数据范围为全服所有用户。 #### 请求参数 ```json { "pageNum": 1, "pageSize": 30 } ``` #### 响应示例 ```json { "code": 200, "msg": "操作成功", "data": { "total": 99, "rows": [ { "periodNo": 2026090725038, "endTime": "2026-09-07 09:14:49", "userId": 666299, "nickName": "李四2", "avatar": "/profile/upload/xxx.png", "bet": "5.00", "betSymbol": "D", "win": "40.00", "betCount": 3, "prizeGrade": 1, "prizeName": "某某饰品", "prizeImage": "/profile/upload/xxx.png" } ] } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `total` | Long | 总中奖注数 | | `rows` | Array | 中奖注单列表 | | `rows[].periodNo` | Long | 完整期号 | | `rows[].endTime` | String | 回合结束时间 | | `rows[].userId` | Long | 中奖用户 ID | | `rows[].nickName` | String | 中奖用户昵称(完整,不脱敏) | | `rows[].avatar` | String | 中奖用户头像 | | `rows[].bet` | String | 单注下注额 | | `rows[].betSymbol` | String | 中奖符号 | | `rows[].win` | String | 中奖额 | | `rows[].betCount` | Integer | 该中奖符号下注次数 | | `rows[].prizeGrade` | Integer | 饰品品质等级(对应 tt_ornaments_level.id:1金/2红/3紫/4蓝) | | `rows[].prizeName` | String | 饰品名称 | | `rows[].prizeImage` | String | 饰品图片 | --- ### 4.6 历史开奖结果 **`POST /api/wheel/round_results`** 获取每一轮的开奖符号(用于展示历史开奖记录,如右侧滚动的历史中奖列表)。 #### 请求参数 ```json { "pageNum": 1, "pageSize": 20 } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `pageNum` | Integer | 否 | 页码(默认 1) | | `pageSize` | Integer | 否 | 每页条数(默认 20) | #### 响应示例 ```json { "code": 200, "msg": "操作成功", "data": { "rows": [ { "periodNo": 2025010100086, "date": "20250101", "roundId": 86, "resultIndex": 5, "resultColor": "gold", "resultSymbol": "D", "endTime": "2025-01-01 12:15:39" }, { "periodNo": 2025010100085, "date": "20250101", "roundId": 85, "resultIndex": 12, "resultColor": "red", "resultSymbol": "A", "endTime": "2025-01-01 12:15:00" } ], "total": 2156 } } ``` #### 响应字段 | 字段 | 类型 | 说明 | |------|------|------| | `rows` | Array | 历史开奖记录列表 | | `rows[].periodNo` | Long | **全局唯一期号**(13位,前8位=日期yyyyMMdd,后5位=局号) | | `rows[].date` | String | **日期**(yyyyMMdd,从 periodNo 拆分而来) | | `rows[].roundId` | Long | **当天局号**(从 periodNo 拆分而来) | | `rows[].resultIndex` | Integer | 开奖格子索引(0-23) | | `rows[].resultColor` | String | 开奖颜色(gold/red/purple) | | `rows[].resultSymbol` | String | 开奖符号(A/B/C/D) | | `rows[].endTime` | String | 本局结束时间(yyyy-MM-dd HH:mm:ss) | | `total` | Long | 总记录数 | #### 前端展示建议 ```javascript // 展示期号 function formatPeriodNo(periodNo) { const str = String(periodNo).padStart(13, '0'); const year = str.slice(0, 4); const month = str.slice(4, 6); const day = str.slice(6, 8); const round = parseInt(str.slice(8)); // 去前导零 return `${year}-${month}-${day}-${round}`; } // 渲染列表 data.rows.forEach(item => { // 期号:2025-01-01-86 const periodText = formatPeriodNo(item.periodNo); // 开奖结果:金色箱子 × 15 const symbolIcon = getSymbolIcon(item.resultColor, item.resultSymbol); renderHistoryItem(periodText, symbolIcon); }); ``` --- ## 五、时间轴使用指南 **核心**:后端返回的是**具体时间点**(startTime/bettedTime/spinTime/endTime),前端根据这些时间点和 `serverTime` 做倒计时和阶段切换。 ### 一局的完整时间轴 ``` startTime (本局创建) │ ├─ 【下注期】serverTime < bettedTime │ 前端显示:倒计时 "距封盘 X 秒",接受下注 │ 倒计时 = bettedTime - serverTime │ ├─ bettedTime (封盘时刻) │ ├─ 【封盘期】bettedTime ≤ serverTime < spinTime │ 前端显示:"封盘中",倒计时 "X 秒后开奖" │ 倒计时 = spinTime - serverTime │ ├─ spinTime (开奖时刻) │ ├─ 【开奖中】spinTime ≤ serverTime < endTime │ 前端显示:播转盘旋转动画 │ 指针停在 resultIndex 格,显示 resultColor+resultSymbol │ 动画时长 = endTime - spinTime(由后台"开奖动画时间"配置) │ ├─ endTime (本局结束) │ └─ 【等待下一局】serverTime ≥ endTime 前端显示:倒计时 "下一局 X 秒后开始" 继续轮询,拿到新 roundId 进入新一轮 ``` ### 前端倒计时计算公式 | 阶段 | 判断条件 | 倒计时公式 | 显示文案 | |------|---------|-----------|---------| | **下注期** | `serverTime < bettedTime` | `bettedTime - serverTime` | "距封盘 X 秒" | | **封盘期** | `bettedTime ≤ serverTime < spinTime` | `spinTime - serverTime` | "X 秒后开奖" | | **开奖中** | `spinTime ≤ serverTime < endTime` | 播动画 | "转盘旋转中" | | **结果展示** | `serverTime ≥ endTime` | `下一局startTime - serverTime` | "下一局 X 秒后" | ### 关键提醒 1. **后端不返回时长配置**(下注时间/封盘时间/动画时间),只返回时间点。前端不需要自己算,直接用时间点差值。 2. **时间点会随后台配置变化**:运营改"下注时间"从 30→50 秒,新局的 `bettedTime` 就会自动延后,前端倒计时自动变长。 3. **开奖动画时长** = `endTime - spinTime`,这个窗口内播动画 + 展示结果。 --- ## 六、前端对接要点 ### 6.1 进入游戏页 ```javascript // 只需轮询 /last_round_info,一次拿到配置+状态 setInterval(async () => { const round = await getLastRoundInfo(); // 首次进入:用配置数据渲染静态元素 if (firstTime) { renderWheel(round.outerSymbols); renderBetPanel(round.bets, round.rewardMatrix); firstTime = false; } // 每次轮询:更新动态状态 updateRoundState(round); }, 2000); ``` ``` ### 6.2 轮询当前局状态 ```javascript // 每 1-2 秒调用一次 setInterval(async () => { const round = await getLastRoundInfo(); // 根据 status 和时间点切换 UI if (round.status === 'betting') { if (round.serverTime < round.bettedTime) { // 下注期 showCountdown('距封盘', round.bettedTime - round.serverTime); enableBetting(true); } else { // 封盘期(status 可能还未切到 spined) showCountdown('即将开奖', round.spinTime - round.serverTime); enableBetting(false); } } else if (round.status === 'spined') { // 已开奖 if (round.resultIndex !== null) { // 播转盘动画,指针停在 resultIndex playSpinAnimation(round.resultIndex, round.innerColors); // 展示发奖明细(winResult 每注一条,hit 区分中奖/安慰) showWinResult(round.winResult, round.totalWin); } // 倒计时下一局 if (round.serverTime >= round.endTime) { showCountdown('下一局', estimateNextRound()); } } }, 2000); ``` ### 6.3 下注 ```javascript async function doBet(color, symbol, bet) { try { const res = await api.post('/api/wheel/dobet', { color, symbol, bet }); if (res.code === 200) { // 扣款成功,更新余额 updateBalance(res.data.balance, res.data.credits); // Toast 提示 showToast('下注成功'); } } catch (err) { if (err.code === 702) { showToast('余额不足,请充值'); } else if (err.code === 500) { showToast(err.msg); // "游戏不在投注期" / "每轮最多50注" } } } ``` ### 6.4 展示饰品信息 ```javascript // winResult 每注一条,hit 区分中奖/安慰 round.winResult.forEach(item => { if (item.hit) { // 中奖,展示矩阵饰品 showPrize({ name: item.prizeName, image: item.prizeImage, levelImg: item.prizeLevelImg, exterior: item.prizeExterior, value: item.prizeValue, boxRecordId: item.boxRecordId }); } else { // 未中,展示安慰饰品 showConsolation(item.color, item.symbol); } }); ``` ### 6.5 渲染盘面(innerColors) ```javascript // innerColors 是 24 个元素的数组,已经是 gold/red/purple // 例:["gold","red","purple",...] function renderWheel(innerColors, outerSymbols) { for (let i = 0; i < 24; i++) { const color = innerColors[i]; // 'gold' / 'red' / 'purple' const symbol = outerSymbols[i]; // 'A' / 'B' / 'C' / 'D' // 渲染第 i 格:内圈色 + 外圈符号 renderCell(i, color, symbol); } } ``` ### 6.6 注意事项 | 注意项 | 说明 | |-------|------| | **单接口轮询** | 只需轮询 `/last_round_info`,配置数据已合并,无需单独调 `/game_config` | | **不要自动重发 dobet** | 下注接口**非幂等**,失败时提示玩家手动重试 | | **轮询间隔** | 建议 1-2 秒,不要过于频繁 | | **时区** | 所有时间戳为东八区(UTC+8) | | **饰品已入背包** | 中奖后饰品直接进背包,无需"保留/分解"抉择
要兑现走通用背包分解接口 | | **维护模式** | 后台"游戏关闭"开关打开后:
① 新局无法下注(`dobet` 返回 500 维护提示)
② 轮询接口返回 `isMaintenance: true`,前端应弹窗并跳转
③ 进行中回合不强制结算,玩家可继续看结果 | ---