16 KiB
流水抽奖接口文档(Cocos 客户端对接)
一、功能说明
玩家无需额外付费,仅凭当日已发生的游戏下注流水自动累计抽奖资格。
核心规则:
| 规则 | 说明 |
|---|---|
| 流水来源 | 仅统计游戏消费流水,排除 Roll 房、商城消费、福利类 |
| 兑换比例 | 每个宝箱可单独配置流水门槛(默认 3000 流水 = 1 次) |
| 过期机制 | 按自然日统计,次日零点自动清零,不累积 |
| 抽奖消耗 | 只消耗"今日流水额度",不扣余额、不扣积分 |
| 中奖发放 | 饰品直接进背包(tt_box_records.source = 14) |
| 余额变动 | 只有玩家在背包分解该饰品时才产生余额/积分变动(source = 63 流水抽奖奖励) |
| 同时开启 | 全站同一时间只有一个抽奖活动为开启状态 |
参与统计的流水来源码(TtAccountRecordSource.getTurnoverLotteryCodes()):
| source | 含义 |
|---|---|
| 2 | 经典开箱消费 |
| 4 | 对战消费 |
| 40 | 幸运升级消费 |
| 38 | 疯狂转盘消费 |
| 60 | 奥林匹斯消费 |
二、通用约定
- Base URL:
{host}/api/turnoverLottery - 请求方式:全部为
POST - Content-Type:
application/json - 鉴权:需登录态,请求头携带 token。
userId由后端从 token 解析,客户端不要传 userId
统一响应结构
{
"code": 200,
"msg": "操作成功",
"data": {}
}
| code | 含义 |
|---|---|
| 200 | 成功 |
| 500 | 业务失败,提示语见 msg |
| 701 | 未实名认证,客户端统一弹实名认证窗口 |
客户端判定成功请以
code === 200为准;失败时直接向玩家展示msg。
三、接口清单
| # | 接口 | 路径 | 用途 |
|---|---|---|---|
| 1 | 获取抽奖信息 | POST /api/turnoverLottery/myStats |
进入抽奖页拉取流水、次数、宝箱与奖品 |
| 2 | 执行抽奖 | POST /api/turnoverLottery/draw |
点击抽奖,返回中奖结果供播放动画 |
| 3 | 我的抽奖记录 | POST /api/turnoverLottery/myRecords |
展示玩家历史中奖记录 |
| 4 | 全服中奖记录 | POST /api/turnoverLottery/globalRecords |
展示全服玩家中奖公示(跑马灯/滚动列表) |
四、接口详情
1. 获取抽奖信息
POST /api/turnoverLottery/myStats
进入抽奖页时调用,一次性返回玩家流水统计 + 所有开启中的抽奖宝箱及其奖品。
请求参数
无需参数,body 可不传(或传空对象 {})。
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"totalTurnover": 12000.00,
"usedTurnover": 3000.00,
"availableTurnover": 9000.00,
"lotteryList": [
{
"id": 1,
"lotteryName": "每日流水抽奖",
"status": 1,
"turnoverThreshold": 3000,
"sort": 0,
"lotteryChances": 3,
"totalValue": 8250.00,
"prizes": [
{
"id": 6,
"ornamentName": "M9 刺刀 | 渐变之色",
"price": 8200.00,
"imageUrl": "/profile/upload/2026/08/08/xxx.png",
"ornamentLevel": 3,
"exteriorName": "略有磨损",
"levelImg": "/profile/upload/level/red.png",
"displayCount": 10,
"displayPercent": "50.00%",
"realCount": 5,
"realPercent": "45.45%",
"sort": 0
}
]
}
]
}
}
字段说明
顶层字段
| 字段 | 类型 | 说明 |
|---|---|---|
totalTurnover |
Decimal | 今日累计有效流水总额 |
usedTurnover |
Decimal | 今日已消耗的流水(抽奖已用掉的部分) |
availableTurnover |
Decimal | 当前可用流水 = totalTurnover - usedTurnover,不会为负 |
lotteryList |
Array | 开启中的抽奖宝箱列表,按 sort 升序 |
lotteryList[] 宝箱字段
| 字段 | 类型 | 说明 |
|---|---|---|
id |
Long | 宝箱 ID,抽奖时作为 lotteryId 传入 |
lotteryName |
String | 宝箱名称 |
status |
Int | 状态,始终为 1(接口只返回开启中的) |
turnoverThreshold |
Int | 该宝箱的流水门槛(多少流水抽 1 次) |
sort |
Int | 展示排序,越小越靠前 |
lotteryChances |
Int | 该宝箱可抽次数 = availableTurnover ÷ turnoverThreshold 向下取整 |
totalValue |
Decimal | 奖池总价值(按初始真实数量 × 单价累加) |
prizes |
Array | 奖品列表,按 sort 升序 |
prizes[] 奖品字段
| 字段 | 类型 | 说明 |
|---|---|---|
id |
Long | 奖品 ID |
ornamentName |
String | 饰品名称 |
price |
Decimal | 饰品价值 |
imageUrl |
String | 饰品图片 |
ornamentLevel |
Int | 品质等级 ID |
exteriorName |
String | 磨损度(崭新出厂/略有磨损/久经沙场/破损不堪/战痕累累/无涂装),可能为 null |
levelImg |
String | 品质底图,可用于饰品卡片背景 |
displayCount |
Int | 展示数量 |
displayPercent |
String | 展示爆率,如 "50.00%",用于前端展示给玩家 |
realCount |
Int | 当前剩余库存 |
realPercent |
String | 真实爆率(内部数据,不建议展示给玩家) |
sort |
Int | 展示排序 |
图片路径处理:
imageUrl/levelImg若以/开头(且非//),是服务器相对路径,需拼接资源域名前缀;否则为完整 URL 直接使用。
展示建议:转盘/宝箱格子的爆率请使用
displayPercent,realPercent是后台真实概率,不要展示。
2. 执行抽奖
POST /api/turnoverLottery/draw
玩家点击抽奖时调用。建议点击立即请求接口,拿到结果后再播放动画——饰品在接口返回成功时已经入背包,动画只是表现层,即使动画中途活动被关闭也不影响已发的奖。
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
lotteryId |
Long | 是 | 宝箱 ID,取 myStats 返回的 lotteryList[].id |
{
"lotteryId": 1
}
成功响应
{
"code": 200,
"msg": "操作成功",
"data": {
"prizeId": 6,
"ornamentName": "M9 刺刀 | 渐变之色",
"price": 8200.00,
"imageUrl": "/profile/upload/2026/08/08/xxx.png",
"ornamentLevel": 3,
"exteriorName": "略有磨损",
"lotteryName": "每日流水抽奖",
"costTurnover": 3000,
"msg": "抽奖成功!获得: M9 刺刀 | 渐变之色"
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
prizeId |
Long | 中奖奖品 ID,可与 myStats 的 prizes[].id 对应,用于定位动画停留格子 |
ornamentName |
String | 中奖饰品名称 |
price |
Decimal | 饰品价值 |
imageUrl |
String | 饰品图片 |
ornamentLevel |
Int | 品质等级 ID |
exteriorName |
String | 磨损度,可能为 null |
lotteryName |
String | 宝箱名称 |
costTurnover |
Int | 本次消耗的流水值 |
msg |
String | 中奖文案,可直接展示 |
失败响应(code = 500,提示语在外层 msg)
| msg | 触发场景 | 客户端处理建议 |
|---|---|---|
抽奖活动不存在或未开启 |
活动被后台关闭或已删除 | 提示后刷新 myStats |
抽奖次数不足 |
可用流水不够一次门槛 | 提示玩家去游戏累计流水 |
奖品配置为空 |
该宝箱未配置奖品 | 提示"活动准备中" |
奖品库存不足 |
全部奖品初始数量为 0 | 提示"活动准备中" |
抽奖流水门槛配置错误,请联系管理员 |
门槛配置为 0 或负数 | 提示联系客服 |
抽奖处理中,请勿重复操作 |
同一用户并发点击被锁拦截 | 禁用按钮防连点,稍后可重试 |
抽奖处理中,请稍后再试 |
同一宝箱有其他玩家正在抽 | 短暂延迟后自动重试一次 |
系统繁忙,请稍后重试 |
服务端异常(事务已回滚,未扣流水未发奖) | 提示重试 |
重要:抽奖接口已加双层 Redis 分布式锁(用户级 + 宝箱级),客户端仍需在按钮上做防连点,避免频繁触发"抽奖处理中"。
抽奖成功后的处理
- 用
prizeId定位动画停留位置并播放中奖动画 - 动画结束后重新调用
myStats刷新可用流水与剩余次数 - 饰品已在背包,如需"立即分解"走通用背包分解接口
/api/userPackSack/decompose,分解产生的余额/积分变动来源为63 流水抽奖奖励
3. 我的抽奖记录
POST /api/turnoverLottery/myRecords
分页查询当前登录玩家的历史中奖记录,按抽奖时间倒序。
请求参数
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
lotteryId |
Long | 否 | 全部 | 按宝箱筛选,不传查全部 |
pageNum |
Int | 否 | 1 | 页码,从 1 开始 |
pageSize |
Int | 否 | 10 | 每页条数,最大 100 |
{
"pageNum": 1,
"pageSize": 20
}
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 35,
"pageNum": 1,
"pageSize": 20,
"rows": [
{
"id": 128,
"userId": 688660,
"lotteryId": 1,
"prizeId": 6,
"ornamentId": null,
"ornamentName": "M9 刺刀 | 渐变之色",
"imageUrl": "/profile/upload/2026/08/08/xxx.png",
"ornamentsPrice": 8200.00,
"costTurnover": 3000,
"createTime": "2026-08-08 17:18:37"
}
]
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
total |
Long | 总记录数,用于分页组件 |
pageNum |
Int | 当前页码 |
pageSize |
Int | 每页条数 |
rows |
Array | 记录列表 |
rows[].id |
Long | 记录 ID |
rows[].lotteryId |
Long | 宝箱 ID |
rows[].prizeId |
Long | 奖品 ID |
rows[].ornamentName |
String | 饰品名称 |
rows[].imageUrl |
String | 饰品图片 |
rows[].ornamentsPrice |
Decimal | 奖品价值 |
rows[].costTurnover |
Int | 该次消耗的流水值 |
rows[].createTime |
String | 抽奖时间 yyyy-MM-dd HH:mm:ss |
失败时返回 code = 500,msg 为 查询失败,请稍后重试。
4. 全服中奖记录
POST /api/turnoverLottery/globalRecords
分页查询全服所有玩家的中奖记录,按中奖时间倒序。用于抽奖页的中奖公示区(跑马灯、滚动榜单)。
已在服务端过滤:机器人(
user_type='03')、白号(is_blank_account=1)、封禁用户(status='1')、已删除用户(del_flag!='0')的记录不会出现在结果里,客户端无需再做过滤。
请求参数
| 参数 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
lotteryId |
Long | 否 | 全部 | 按宝箱筛选,不传查全部宝箱 |
minPrice |
Decimal | 否 | 不限 | 最低奖品价值门槛,只展示价值 ≥ 该值的记录(做"大奖公示"时用) |
pageNum |
Int | 否 | 1 | 页码,从 1 开始 |
pageSize |
Int | 否 | 20 | 每页条数,最大 100 |
{
"minPrice": 1000,
"pageNum": 1,
"pageSize": 20
}
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 1286,
"pageNum": 1,
"pageSize": 20,
"rows": [
{
"id": 128,
"userId": 688660,
"nickName": "云深不知",
"avatar": "/profile/avatar/2026/08/08/aaa.png",
"lotteryId": 1,
"lotteryName": "每日流水抽奖",
"prizeId": 6,
"ornamentName": "M9 刺刀 | 渐变之色",
"imageUrl": "/profile/upload/2026/08/08/xxx.png",
"ornamentsPrice": 8200.00,
"ornamentLevel": 3,
"levelImg": "/profile/upload/level/red.png",
"exteriorName": "略有磨损",
"createTime": "2026-08-08 17:18:37"
}
]
}
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
total |
Long | 总记录数 |
pageNum |
Int | 当前页码 |
pageSize |
Int | 每页条数 |
rows |
Array | 记录列表,按中奖时间倒序 |
rows[].id |
Long | 记录 ID |
rows[].userId |
Int | 中奖用户 ID |
rows[].nickName |
String | 中奖用户昵称,用于公示展示 |
rows[].avatar |
String | 中奖用户头像 |
rows[].lotteryId |
Long | 宝箱 ID |
rows[].lotteryName |
String | 宝箱名称 |
rows[].prizeId |
Long | 奖品 ID |
rows[].ornamentName |
String | 饰品名称 |
rows[].imageUrl |
String | 饰品图片 |
rows[].ornamentsPrice |
Decimal | 奖品价值 |
rows[].ornamentLevel |
Int | 品质等级 ID |
rows[].levelImg |
String | 品质底图,可作饰品卡片背景 |
rows[].exteriorName |
String | 磨损度,可能为 null |
rows[].createTime |
String | 中奖时间 yyyy-MM-dd HH:mm:ss |
失败时返回 code = 500,msg 为 查询失败,请稍后重试。
与
myRecords的区别:myRecords只返回当前登录玩家自己的记录(不含昵称/头像/品质图);globalRecords返回全服记录并附带中奖者昵称、头像、品质底图,专供公示展示。
轮询建议:公示区如需实时滚动,建议 15~30 秒轮询一次
pageNum=1,不要高频请求。
五、典型调用流程
进入抽奖页
│
├─→ POST /myStats
│ 拿到 availableTurnover、lotteryList
│ 渲染宝箱、奖品格子(爆率用 displayPercent)
│ 显示可抽次数 lotteryChances
│
├─→ 玩家点击抽奖(按钮置灰防连点)
│ POST /draw { lotteryId }
│ ├─ code=200 → 用 prizeId 定位并播放动画
│ │ 动画结束后重新 POST /myStats 刷新
│ └─ code=500 → 按钮恢复,展示 msg
│
├─→ 查看我的中奖记录
│ POST /myRecords { pageNum, pageSize }
│
└─→ 中奖公示区(跑马灯,15~30s 轮询)
POST /globalRecords { pageNum: 1, pageSize: 20 }
六、对接注意事项
- 不要传 userId:所有接口的用户身份从 token 解析,传了也会被忽略。
myStats无需 body:之前该接口曾要求传{},现已改为无参,body 传或不传都可以。- 次数按宝箱各算:
lotteryChances在每个宝箱内单独计算(因为门槛可能不同),不是全局共享的次数。多个宝箱的次数是"同一份可用流水"的不同换算结果,抽任一宝箱都会消耗同一份流水。 - 抽奖不动余额:客户端不要在抽奖后刷新余额,抽奖不产生金币/积分变化;只有分解饰品才会变。
- 动画与发奖解耦:接口返回成功即已发奖入背包,动画播放期间活动被关闭不影响结果。
- 爆率字段区分:给玩家看
displayPercent,realPercent/realCount属于内部数据。 - 奖池自动重置:当宝箱所有奖品剩余库存耗尽时,下一次抽奖会自动按后台配置的初始真实数量重置奖池,客户端无需处理。
- 全服记录已过滤脏数据:
globalRecords服务端已排除机器人、白号、封禁及已删除用户,客户端直接展示即可。 - 公示区别高频轮询:
globalRecords建议 15~30 秒轮询一次,pageSize上限 100。
七、相关代码位置
| 内容 | 文件 |
|---|---|
| 用户端接口 | skins-service/service-admin/src/main/java/com/ruoyi/admin/controller/TurnoverLotteryUserController.java |
| 业务实现 | skins-service/service-admin/src/main/java/com/ruoyi/admin/service/impl/TurnoverLotteryServiceImpl.java |
| 全服记录查询 | skins-service/service-admin/src/main/java/com/ruoyi/admin/mapper/TurnoverLotteryRecordMapper.java |
| 全服记录 VO | skins-model/src/main/java/com/ruoyi/domain/vo/lottery/TurnoverLotteryWinVO.java |
| 奖品实体 | skins-model/src/main/java/com/ruoyi/domain/entity/lottery/TtTurnoverLotteryPrize.java |
| 记录实体 | skins-model/src/main/java/com/ruoyi/domain/entity/lottery/TtTurnoverLotteryRecord.java |
| 建表 SQL | docs/updata_sql.md(搜索"流水抽奖") |