23 KiB
疯狂转盘 前端接口文档
版本:v2.0
更新时间:2025-01
适用前端:Web/H5/小程序
Base URL:{host}/api/wheel
📋 目录
一、玩法概述
疯狂转盘是一款 选颜色+符号 → 下注 → 转盘停 → 命中得饰品 的真人轮盘游戏。
核心规则
| 项目 | 说明 |
|---|---|
| 盘面结构 | 内外圈各 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 |
||
| 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 秒后" |
关键提醒
- 后端不返回时长配置(下注时间/封盘时间/动画时间),只返回时间点。前端不需要自己算,直接用时间点差值。
- 时间点会随后台配置变化:运营改"下注时间"从 30→50 秒,新局的
bettedTime就会自动延后,前端倒计时自动变长。 - 开奖动画时长 =
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,前端应弹窗并跳转③ 进行中回合不强制结算,玩家可继续看结果 |