first commit

This commit is contained in:
2026-09-21 19:21:35 +08:00
commit 6c944c4ffd
2733 changed files with 296956 additions and 0 deletions
+447
View File
@@ -0,0 +1,447 @@
# 流水抽奖接口文档(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**
### 统一响应结构
```json
{
"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 可不传(或传空对象 `{}`)。
#### 响应示例
```json
{
"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` |
```json
{
"lotteryId": 1
}
```
#### 成功响应
```json
{
"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 分布式锁**(用户级 + 宝箱级),客户端仍需在按钮上做防连点,避免频繁触发"抽奖处理中"。
#### 抽奖成功后的处理
1. 用 `prizeId` 定位动画停留位置并播放中奖动画
2. 动画结束后重新调用 `myStats` 刷新可用流水与剩余次数
3. 饰品已在背包,如需"立即分解"走通用背包分解接口 `/api/userPackSack/decompose`,分解产生的余额/积分变动来源为 `63 流水抽奖奖励`
---
### 3. 我的抽奖记录
**`POST /api/turnoverLottery/myRecords`**
分页查询当前登录玩家的历史中奖记录,按抽奖时间倒序。
#### 请求参数
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `lotteryId` | Long | 否 | 全部 | 按宝箱筛选,不传查全部 |
| `pageNum` | Int | 否 | 1 | 页码,从 1 开始 |
| `pageSize` | Int | 否 | 10 | 每页条数,**最大 100** |
```json
{
"pageNum": 1,
"pageSize": 20
}
```
#### 响应示例
```json
{
"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** |
```json
{
"minPrice": 1000,
"pageNum": 1,
"pageSize": 20
}
```
#### 响应示例
```json
{
"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 }
```
---
## 六、对接注意事项
1. **不要传 userId**:所有接口的用户身份从 token 解析,传了也会被忽略。
2. **`myStats` 无需 body**:之前该接口曾要求传 `{}`,现已改为无参,body 传或不传都可以。
3. **次数按宝箱各算**:`lotteryChances` 在每个宝箱内单独计算(因为门槛可能不同),不是全局共享的次数。多个宝箱的次数是"同一份可用流水"的不同换算结果,抽任一宝箱都会消耗同一份流水。
4. **抽奖不动余额**:客户端不要在抽奖后刷新余额,抽奖不产生金币/积分变化;只有分解饰品才会变。
5. **动画与发奖解耦**:接口返回成功即已发奖入背包,动画播放期间活动被关闭不影响结果。
6. **爆率字段区分**:给玩家看 `displayPercent`,`realPercent` / `realCount` 属于内部数据。
7. **奖池自动重置**:当宝箱所有奖品剩余库存耗尽时,下一次抽奖会自动按后台配置的初始真实数量重置奖池,客户端无需处理。
8. **全服记录已过滤脏数据**:`globalRecords` 服务端已排除机器人、白号、封禁及已删除用户,客户端直接展示即可。
9. **公示区别高频轮询**:`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`(搜索"流水抽奖") |