Files
henan-backend/docs/幸运升级接口文档.md
T
2026-09-21 19:21:35 +08:00

341 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 幸运升级接口文档(Cocos 客户端对接)
> 适用版本:当前 master。所有接口路径前缀 `/api/luckyUpgrade`(另有 1 个关联接口在 `/api/userPackSack`)。
> 代码位置:`skins-service/service-playingmethod/.../ApiLuckyUpgradeController.java`、`ApiLuckyUpgradeServiceImpl.java`、`ApiUpgradeRecordServiceImpl.java`。
---
## 一、玩法机制(实现口径)
### 1.1 一局流程
```
选饰品位(upgradeId) + 选成功率(probability)
↓ 后端按实时价算钱:price = 饰品价 × probability / 100
↓ 校验(身份 / 封禁 / 删除 / 未充值 703 / 余额 702)
↓ 加锁(饰品位 + 奖池类型)→ 扣款(source=40 幸运升级消费)
↓ 判定(见 1.2)
├─ 成功:获得该饰品位对应的整件饰品,进度与幸运值清 0
└─ 失败:获得后台配置的「失败奖励」饰品(安慰奖,可多件),进度与幸运值累加
↓ 写 tt_upgrade_record 记录(含各项快照)+ 背包 tt_box_records(source=4 幸运升级)+ 站内消息
↓ 返回升级记录(含 gainOrnamentList,可用于结果页立即分解)
```
### 1.2 消耗与判定公式
| 项 | 公式 / 取值 |
|---|---|
| 单次消耗 | `price = 饰品实时价(tt_ornament.use_price) × probability / 100`,保留 2 位小数(**前端不传价格**,由后端算,防 0 元 100% 中奖) |
| 判定概率 | `eff = min(probability, 75)`(**封顶 75**:选 90% 也只按 75% 判定,但钱按 90% 收) |
| 本轮空间 | `fullV`:从该饰品位对应奖池的幸运区间 `[min, max)` 里随机取 `nextInt(max-min) + min` |
| 判定 | 每拉一次 `进度 += eff`;`进度 + eff >= fullV` 即中奖,中奖后 `fullV` 重新随机、进度清 0;否则失败继续累加 |
| 推论 | **玩家为一件饰品付出的总成本 ≈ 饰品价 × fullV / 100**。所以幸运区间 = 该池的"售价百分比区间":`[50,250]` 表示平均要付 50%~250% 原价才出;压到个位数(如 `[9,10]`)等于必中、平台必亏 |
### 1.3 幸运值与幸运区间:三套独立奖池
`幸运值` = 该池"累计原始概率",失败累加 `probability`(原始值,不是封顶后的 75)、中奖清 0;界面与记录里展示的就是它。
| 奖池 | 判定/显示用 userType | 幸运区间字段 | 幸运值字段 | 本轮已投金额字段 |
|---|---|---|---|---|
| 普通用户 | `02` | `tt_upgrade_ornaments.luck_section` | `probability_invested` | `amount_invested` / `effective_amount_invested` |
| 主播 | `01` | `anchor_luck_section` | `anchor_probability_invested` | `anchor_amount_invested` / `anchor_effective_amount_invested` |
| 白号 | `06`(`is_blank_account=1` 优先判定) | `blank_luck_section`(未配置回退 `luck_section`) | `blank_probability_invested` | `blank_amount_invested` / `blank_effective_amount_invested` |
- 三池**完全隔离**:进度缓存在 Redis `upgrade_range_fixed:{饰品位}:{池类型}` / `upgrade_range:{饰品位}:{池类型}`,互不影响、互不阻塞(锁也是按「饰品位 + 池类型」)。
- 同一池内是**共享进度**:同一个池子的玩家一起把进度推到 `fullV`,谁那一拉跨过阈值谁中奖(和普通/主播现有行为一致,白号同理,白号之间共享 06 池)。
- 白号记录里的幸运值/已投金额读的都是白号自己的池,不再是真实玩家池的读数。
### 1.4 获得与失败奖励
- **成功**:获得 `tt_upgrade_ornaments.ornaments_id` 对应的整件饰品,`gain_ornaments_price` = 该饰品实时价。
- **失败**:获得后台「幸运升级 → 失败奖励」为该饰品位配置的饰品(`tt_upgrade_fail_ornaments`,可多件,按 `ornament_number` 计数),`gain_ornaments_price` = 安慰奖总价值;该饰品位**没配失败奖励时啥都不给**(`gainOrnamentList` 为 `[]`、金额 0)。
- 所有获得物都写入背包 `tt_box_records`(`source = 4` 幸运升级、状态 = 在背包)。
### 1.5 流水与返佣口径
| 场景 | 流水 source |
|---|---|
| 升级消费 | `40`(GOLD_DEVICE,「幸运升级消费」,输出) |
| 游戏内分解(结果页立即分解) | `41`(GOLD_DEVICE_REWARD,「幸运升级奖励」,输入) |
| 背包里分解(事后在背包页分解) | `10`(DECOMPOSE_ORNAMENT,输入) |
- 消费 source 40 计入:今日/历史投注流水、综合消费排行榜、流水抽奖资格、**上级返佣**(按 `classicGameCommissionRate` 口径)、**VIP 流水返利**。
- **白号例外**:白号消费不触发上级返佣、不触发 VIP 流水返利;后台充值/流水/记录类页面普遍按 `is_blank_account=1` 排除白号。
---
## 二、通用约定
- **域名**:`{host}`(例:`http://103.108.67.60:28081`)。
- **Content-Type**:`application/json`;**鉴权**:除 `getOrnamentsList` 外都需要请求头带 token,401 时按项目统一逻辑弹登录。
- **统一响应**:
```json
{ "code": 200, "msg": "操作成功", "data": { } }
```
| code | 含义 |
|---|---|
| 200 | 成功(客户端以 `code === 200` 判定) |
| 401 | 未登录 / 登录过期,msg「登录过期,请重新登录。」 |
| 702 | 账户余额不足 |
| 703 | 玩家未充值不能游玩任何游戏 |
| 500 | 业务失败,msg 直接给玩家看 |
- **维护开关**(`sys_config`,后台「网站设置」):`websiteMaintenance=1` → 「网站维护中......」;`luckyUpgradeMaintenance=1` → 「幸运升级维护中......」。两个开关都**先于登录校验**返回。
- **通用拦截**(`upgrade` 接口):非 主播(01)/普通(02)/白号 → 「非法的用户类型」;`status=1` → 「账号已被封禁,无法参与游戏」;`del_flag != 0` → 「账号状态异常,无法参与游戏」。
- 幸运升级相关也有 `/api/luckyUpgrade/**` 其他接口,本文件只覆盖下表这些。
---
## 三、接口清单
| 接口 | 说明 | 鉴权 | 分页 |
|---|---|---|---|
| `POST /api/luckyUpgrade/getOrnamentsList` | 可升级饰品列表(含当前幸运值) | 否(未登录按普通用户口径) | 无 |
| `POST /api/luckyUpgrade/upgrade` | 拉取一次(扣款 + 判定 + 发奖) | 是 | - |
| `POST /api/luckyUpgrade/recordPage` | 记录页:全服/我的 × 当前饰品/全部饰品 | 是 | 真分页 |
| `POST /api/luckyUpgrade/recentPulls` | 某饰品位最近 50 次拉取流水 | 是 | 无(固定 50) |
| `POST /api/luckyUpgrade/myHistoryRecord` | 我的记录(全部饰品,成败都返回) | 是 | 真分页 |
| `POST /api/luckyUpgrade/getUpgradeRecord` | 通用条件查询(内部/后台复用) | 是 | 真分页 |
| `POST /api/luckyUpgrade/tenTopUpgradeRecord` | 十佳掉落榜(按实际获得价值 Top10) | 是 | 无(固定 10) |
| `POST /api/userPackSack/decompose` | 分解获得的饰品(结果页或背包) | 是 | - |
---
## 四、接口详情
### 4.1 `POST /api/luckyUpgrade/getOrnamentsList` — 可升级饰品列表
**请求**(全部可选,无分页、无排序参数,返回按 `use_price` 升序)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 可直接取某一个饰品位(`tt_upgrade_ornaments.id`) |
| type | String | 饰品类型数字字典码(1匕首 2手枪 3步枪 4微型冲锋枪 5重型武器 6手套 7印花 8其它);空串按不传处理 |
| itemName | String | 饰品名称模糊匹配 |
| priceMin / priceMax | BigDecimal | 按价格区间过滤(**只在传了 `priceMax` 时生效**;不传 `priceMin` 默认 0) |
| userType | String | **后端注入,客户端不要传**(用登录用户身份决定返回哪个池的幸运值) |
```json
{ "type": "3", "itemName": "", "priceMax": 5000 }
```
**响应**:`data` 为数组,仅返回 `status='0'`(启用)的饰品位:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | **可升级饰品ID**(后续 `upgrade.upgradeId`、`recordPage.upgradeId`、`recentPulls.upgradeRecordId` 都传它) |
| ornamentId | Long | 饰品ID(`tt_ornament.id`) |
| name | String | 饰品全名(优先展示) |
| itemName | String | 名称(与 name 同源字段) |
| usePrice | BigDecimal | 实时价格(= 计算消耗、展示目标金额的基准) |
| imageUrl | String | 饰品图片 |
| shortName / typeName / rarityName / exteriorName / levelImg | String | 简称 / 类型 / 稀有度 / 磨损度 / 品质图 |
| luckyValue | Integer | **当前幸运值**(按登录身份取对应池:普通/主播/白号) |
### 4.2 `POST /api/luckyUpgrade/upgrade` — 拉取一次
**请求**
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| upgradeId | Integer | 是 | 可升级饰品ID(`getOrnamentsList` 的 `id`) |
| probability | Integer | 是 | 本次选择的成功率,1~100;**判定按 min(概率,75),扣款按传入值** |
```json
{ "upgradeId": 25, "probability": 10 }
```
**响应**:`data` = 本条升级记录(`tt_upgrade_record`),字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Long | 升级记录ID |
| userId / nickName / userType / userAvatar | - | 玩家信息(昵称是升级时快照) |
| probability | Integer | 本次选择的成功率(原始概率快照) |
| luckyValue | Integer | 本次升级时的幸运值快照(中奖为 0) |
| amountConsumed | BigDecimal | 本次实际消耗 |
| amountRequired | BigDecimal | 本轮目标金额快照 = 饰品价 × fullV% |
| amountInvested / effectiveAmountInvested | BigDecimal | 本轮已投金额(实际扣款口径 / 真实计算口径)快照 |
| effectiveAmount | BigDecimal | 真实计算金额 = 饰品价 × min(概率,75)% |
| targetUpgradeId | Integer | 可升级饰品ID |
| targetOrnamentId | Long | 目标饰品ID |
| targetOrnamentPrice | BigDecimal | 目标饰品价格 |
| isVictory | Boolean | 成败:true=升级成功 |
| gainOrnamentsPrice | BigDecimal | 本次获得饰品总价值(成功=目标饰品价,失败=安慰奖总价,可能为 0) |
| gainOrnamentList | Array | **本次实际获得物**(JSON 数组,不是字符串),每项:`id`=背包记录ID(`tt_box_records.id`)、`ornamentId`、`ornamentName`、`ornamentHashName`、`ornamentPrice`、`ornamentImgUrl`、`ornamentLevelId`、`ornamentLevelImg`、`ornamentNumber`;无获得时为 `[]` |
| openTime | String | 升级时间 `yyyy-MM-dd HH:mm:ss` |
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 93, "userId": 34052018, "probability": 10, "luckyValue": 0,
"amountConsumed": 181.95, "amountRequired": 1819.52, "isVictory": true,
"gainOrnamentsPrice": 1819.52,
"gainOrnamentList": [
{ "id": 11530, "ornamentId": 1312995578, "ornamentName": "AK-47 | 表面淬火 (略有磨损)",
"ornamentPrice": 1819.52, "ornamentImgUrl": "/uploads/xxx.png", "ornamentLevelImg": "/profile/xxx.png" }
],
"openTime": "2026-09-11 18:24:01"
}
}
```
**失败返回示例**:`{"code":500,"msg":"系统繁忙,请稍后重试。"}`(同池并发抢锁失败,客户端可直接重试);`{"code":703,"msg":"玩家未充值不能游玩任何游戏"}`;`{"code":702,"msg":"账户余额不足"}`;`{"code":500,"msg":"该饰品未在幸运升级开放。"}`。
**结果页立即分解**:取 `gainOrnamentList[i].id` 作为 `packSackIds`,调 4.8 接口(`decomposeType="game"`)。不分解就等于饰品留在背包里。
### 4.3 `POST /api/luckyUpgrade/recordPage` — 记录页(主力接口)
**请求**(二维组合,共 4 种场景,均需登录)
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| scope | Integer | 否 | 0 | 0=全服(不过滤用户) 1=我的 |
| itemScope | Integer | 否 | 0 | 0=全部饰品 1=当前饰品 |
| upgradeId | Long | 条件必填 | - | 可升级饰品ID;`itemScope=1` 时必填,为空返回 500;`itemScope=0` 时传了也忽略 |
| page | Integer | 否 | 1 | 页码(≥1) |
| size | Integer | 否 | 10 | 每页条数(1~20,超限参数错误) |
```json
{ "scope": 1, "itemScope": 1, "upgradeId": 25, "page": 1, "size": 10 }
```
**响应**:`data` 为分页对象 `{ records, total, current, size, pages }`,`records` 按 `openTime` 倒序:
| 字段 | 类型 | 说明 |
|---|---|---|
| userId / avatar / nickName / vipLevel | - | 玩家信息(昵称是升级时快照) |
| probability | Integer | 成功率(原始概率,如 89 表示 89%) |
| luckyValue | Integer | 幸运值快照(按该玩家所属池;白号是白号池的值) |
| amountConsumed | BigDecimal | 消耗 |
| gainOrnamentsPrice | BigDecimal | 获得饰品总价值 |
| isVictory | Boolean | 成败:true=升级成功(前端胜负样式用) |
| openTime | String | 升级时间 |
| targetOrnamentId | Long | 目标饰品ID |
| name / imageUrl / usePrice / exteriorName / levelImg | - | **本次实际获得的饰品**:成功=目标饰品,失败=安慰奖饰品(`gainOrnamentList` 首件);未配安慰奖的失败记录会回退成目标饰品且金额为 0,**胜负请用 isVictory 判断,不要看图片** |
- 全服(scope=0)不过滤白号、机器人、封禁用户,白号可正常上榜。
- 旧的 `recentPulls` / `myHistoryRecord` 场景都被本接口覆盖,可继续用但建议逐步切到本接口。
### 4.4 `POST /api/luckyUpgrade/recentPulls` — 某饰品位最近 50 次拉取流水
**请求**:`{ "upgradeRecordId": 25 }`
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| upgradeRecordId | Long | 是 | ⚠️ 字段名有历史误导性:**实际语义是「可升级饰品ID」`tt_upgrade_ornaments.id`**(`getOrnamentsList` 的 `id`)。为空返回 500「upgradeRecordId(升级饰品ID)不能为空」 |
| 其他字段 | - | 否 | `ornamentId`/`userType`/`userId`/`page`/`size`/`limit` 全部忽略 |
- **无分页**:固定最近 **50** 条(SQL `LIMIT 50`),没有 `page`/`total`。
- **不过滤**:全服所有人(含白号、机器人、封禁/删除用户),成功与失败都返回。
- `nickName`/`avatar`/`vipLevel` 可能为 `null`(该记录的用户已不在 `tt_user`),客户端要能容错。
**响应**:`data` 直接是数组(不是分页对象):
| 字段 | 类型 | 说明 |
|---|---|---|
| avatar / nickName / vipLevel | - | 拉取人信息 |
| probability | Integer | 本次拉取**选择的成功率**(不是中奖率) |
| luckyValue | Integer | 本次拉取时的**幸运值快照**(累计原始概率;成功为 0),与 `recordPage` 同口径 |
| isVictory | Boolean | 成败:true=升级成功 |
| name / imageUrl / usePrice / exteriorName / levelImg | - | **目标饰品**("谁在拉这个饰品",不是获得物) |
| openTime | String | 拉取时间 |
### 4.5 `POST /api/luckyUpgrade/myHistoryRecord` — 我的记录(全部饰品)
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
| page | Integer | 否 | 1 | 页码 |
| size | Integer | 否 | 10 | 1~20 |
**响应**:`data` 为分页对象,`records` 是 `tt_upgrade_record`(字段同 4.2 的响应,含 `gainOrnamentList`),仅当前登录用户、不含他人记录、成败都返回、按时间倒序;昵称/头像会补齐。
### 4.6 `POST /api/luckyUpgrade/getUpgradeRecord` — 通用条件查询
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page / size | Integer | **必传** | 不传会 500(内部直接用于构造分页对象) |
| upgradeRecordId | Long | 否 | 按记录ID精确查 |
| ornamentId | Long | 否 | 按目标饰品ID过滤 |
| userType | String | 否 | 按用户类型过滤(01/02/06…) |
| userId | Integer | 否 | 按用户过滤 |
**响应**:`data` 为分页对象(`records` 为 `tt_upgrade_record`,`userAvatar` 已补齐,`gainOrnamentList` 空值兜底为 `[]`)。主要用于内部/后台复用,客户端记录页请用 `recordPage`。
### 4.7 `POST /api/luckyUpgrade/tenTopUpgradeRecord` — 十佳掉落榜
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| isVictory | Boolean | 否 | `true` 只看成功、`false` 只看失败;不传则全部记录一起排 |
**响应**:`data` 为分页对象(固定第 1 页、10 条),按 **`gainOrnamentsPrice`(实际获得价值)倒序**取 Top10 —— 即"实际掉落榜",不是目标饰品价值榜。
### 4.8 `POST /api/userPackSack/decompose` — 分解饰品(结果页/背包)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| packSackIds | Long[] | 条件必填 | 背包记录ID(`tt_box_records.id`,即 `gainOrnamentList[i].id`);`isAll=true` 时可不传 |
| isAll | Boolean | 是 | 是否全选 |
| decomposeType | String | 否 | `game`=游戏结果页立即分解(按饰品来源记 `GOLD_DEVICE_REWARD(41)`);`backpack`=背包分解(记 `DECOMPOSE_ORNAMENT(10)`) |
返回 `{"code":200,"msg":"成功分解N件饰品"}`。受 `websiteMaintenance` 开关拦截。
---
## 五、前端对接要点
1. **upgradeId 语义**:所有接口里的 `upgradeId` / `recentPulls.upgradeRecordId` 都是**可升级饰品ID**(`tt_upgrade_ornaments.id`),来自 `getOrnamentsList` 的 `id`,不是 `tt_ornament.id`,也不是升级记录ID。
2. **扣款以服务端为准**:不要传价格;展示价格用 `usePrice × probability / 100`(与后端同公式,后端保留 2 位小数)。
3. **概率封顶 75**:UI 上若提供 >75% 的档位,要提示"更高概率只加钱、不提升判定"。
4. **幸运值怎么用**:主界面实时幸运值取 `getOrnamentsList.luckyValue`;拉取成功后用 `upgrade` 响应里的 `luckyValue` 立即覆盖,不必等列表刷新。记录里的幸运值是历史快照。
5. **分池**:幸运值/幸运区间按 普通(02) / 主播(01) / 白号(06) 三套独立,白号看到的是白号池的值;同一池内进度是共享的,别人垫的进度也算在你头上。
6. **分页**:`recordPage`/`myHistoryRecord` 的 `size` 上限 20;切换 tab(我的/全服、当前饰品/全部饰品)时 `page` 重置为 1。
7. **失败记录展示**:以 `isVictory` 判断胜负,`name/imageUrl/...` 是"实际获得物"(失败=安慰奖);无安慰奖时金额为 0。
8. **结果页分解**:`gainOrnamentList` 里的 `id` 就是分解入参;不调用 = 饰品留在背包。
9. **并发**:同一饰品位 + 同一奖池同时拉取会串行,抢不到锁返回 500「系统繁忙,请稍后重试。」,可直接提示重试。
---
## 六、后台配置与调参
### 6.1 「幸运升级 → 饰品列表」(`tt_upgrade_ornaments`)
| 后台列 | 字段 | 说明 |
|---|---|---|
| 普通用户 / 幸运区间 | `luck_section` | 普通用户池的 `[min, max)`,即"售价百分比区间" |
| 普通用户 / 本轮已投金额 | `amount_invested` | 普通池当前累计投入(运行时值,后台不可编辑回写) |
| 主播用户 / 幸运区间 | `anchor_luck_section` | 主播池区间 |
| 白号用户 / 幸运区间 | `blank_luck_section` | 白号池区间;为空则回退 `luck_section` |
| 白号用户 / 本轮已投金额 | `blank_amount_invested` | 白号池当前累计投入 |
| 状态 | `status` | 0=启用(=可升级、会出现在客户端列表) 1=禁用 |
- 调难度就调"幸运区间":区间 = 玩家为该饰品付出的**价格百分比**。想更容易出就整体下调(如 `[80,280]`→`[60,200]`);**不要压到个位数**(如 `[9,10]` 等于必中,等于按 9% 价格卖货),也不要配成 `min == max`(会被后端防护强制改成 `[0,160]`)。
- **改完区间要清缓存才立即生效**:`DEL upgrade_range:{饰品位}:{池}` + `DEL upgrade_range_fixed:{饰品位}:{池}`(池=01/02/06)。不清的话当前这一轮仍按旧空间跑,等本轮中奖后自动按新配置重建。Redis 实测在 db0,密码见 `application-*.yml`。
- 白号是独立奖池,可与普通/主播配不同的区间;「批量填货」新增饰品位时白号区间默认取普通用户的默认值。
### 6.2 「幸运升级 → 失败奖励」(`tt_upgrade_fail_ornaments`)
每个饰品位可配多件失败奖励(`ornament_name` / `ornament_price` / `ornament_number` / `ornament_level_id`),玩家升级失败即发放这些饰品;**没配就不发**(记录里 `gainOrnamentList=[]`、`gainOrnamentsPrice=0`)。
### 6.3 维护开关(后台「网站设置」,`sys_config`)
| key | 值 | 效果 |
|---|---|---|
| `websiteMaintenance` | `1` | 全站维护:`upgrade` 与分解接口都拦截 |
| `luckyUpgradeMaintenance` | `1` | 只拦幸运升级:`upgrade` 返回「幸运升级维护中......」 |
### 6.4 数据落库
| 表 | 说明 |
|---|---|
| `tt_upgrade_record` | 每条拉取记录(成败、消耗、获得总额、各项快照、`gain_ornament_list` JSON、`lucky_value`) |
| `tt_box_records` | 获得物入背包(`source=4` 幸运升级),`id` 即结果页分解入参 |
| `tt_user_blend_ercash` | 余额流水:消费 40、游戏内分解 41、背包分解 10 |
| `tt_upgrade_ornaments` | 饰品位配置 + 三个池的运行进度(各池幸运值/已投金额) |
字段变更历史见 `docs/updata_sql.md`(含白号三列的 ALTER)。