Files
henan-backend/docs/疯狂转盘接口文档.md
T
2026-09-21 19:21:35 +08:00

23 KiB
Raw Blame History

疯狂转盘 前端接口文档

版本: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 统一响应结构

{
  "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 注。

请求参数

{
  "bet": 100,
  "color": "gold",
  "symbol": "D"
}
参数 类型 必填 说明
bet Integer ✅ 下注额,必须是 10 档之一
color String ✅ 内圈色:gold / red / purple
symbol String ✅ 外圈符:A / B / C / D

响应示例

{
  "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 秒间隔),一次返回全部数据:游戏配置 + 当前回合状态 + 开奖结果 + 我的下注。

请求参数

{}

响应示例

{
  "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<String> 外圈 24 格符号固定布局(A/B/C/D)
例:["A","D","C","D",...]
bets Array<Integer> 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<String> 本轮内圈 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。

请求参数

{
  "pageNum": 1,
  "pageSize": 10
}

响应示例

{
  "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 完全一致,只是数据范围为全服所有用户。

请求参数

{
  "pageNum": 1,
  "pageSize": 30
}

响应示例

{
  "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

获取每一轮的开奖符号(用于展示历史开奖记录,如右侧滚动的历史中奖列表)。

请求参数

{
  "pageNum": 1,
  "pageSize": 20
}
字段 类型 必填 说明
pageNum Integer 否 页码(默认 1)
pageSize Integer 否 每页条数(默认 20)

响应示例

{
  "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 总记录数

前端展示建议

// 展示期号
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 进入游戏页

// 只需轮询 /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 下注

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 展示饰品信息

// 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)

// 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,前端应弹窗并跳转
③ 进行中回合不强制结算,玩家可继续看结果