304 lines
13 KiB
Markdown
304 lines
13 KiB
Markdown
# Yumi 游戏王活动接口
|
||
|
||
本文对应 `chatapp3-golang` 的 Yumi Game King 实现。业务只接受 `LIKEI` 租户的权威游戏金币消耗事件,不提供事件白名单输入框,也不会统计通用钱包支出。
|
||
|
||
## 1. 通用约定
|
||
|
||
- H5 外部网关地址:`/go/app/h5/game-king/yumi/*`;Go 服务内路由不带 `/go`。
|
||
- 后台外部网关地址:`/go/resident-activity/game-king/*`;Go 服务内路由不带 `/go`。
|
||
- H5 和后台均传 `Authorization`。H5 支持 `Bearer <token>`;后台沿用 Console 完整 Authorization。
|
||
- 内部任务传 `X-Internal-Token: <HTTP_INTERNAL_CALLBACK_SECRET>`。
|
||
- 时间字段均为毫秒时间戳;`timeZone` 使用 IANA 时区,默认 `Asia/Riyadh`。
|
||
- 所有业务 ID 在 JSON 响应中均为字符串,避免 JavaScript 丢失 Snowflake 精度。请求中的 ID 可传字符串或数字,建议统一传字符串。
|
||
- 成功响应的数据同时位于 `data` 和 `body`:
|
||
|
||
```json
|
||
{
|
||
"status": true,
|
||
"errorCode": 0,
|
||
"errorMsg": "success",
|
||
"code": "success",
|
||
"message": "success",
|
||
"data": {},
|
||
"body": {},
|
||
"time": 1784300000000
|
||
}
|
||
```
|
||
|
||
- 错误时 HTTP 状态码与 `errorCode` 一致,稳定业务码位于 `code` / `errorCodeName`。
|
||
|
||
## 2. H5 接口
|
||
|
||
### 2.1 活动详情
|
||
|
||
`GET /go/app/h5/game-king/yumi/detail?activityId={activityId}`
|
||
|
||
`activityId` 可不传;不传时按登录用户 `sysOrigin=LIKEI` 解析当前可见活动。返回活动、固定七个奖项、六个排名奖励档、服务器时间和活动状态。
|
||
|
||
关键返回字段:
|
||
|
||
```json
|
||
{
|
||
"activity": {
|
||
"id": "190000000000000001",
|
||
"activityCode": "YUMI_GAME_KING_S1",
|
||
"sysOrigin": "LIKEI",
|
||
"startTime": 1784300000000,
|
||
"endTime": 1784900000000,
|
||
"coinPerDraw": 1000,
|
||
"eventTypes": ["GAME_CONSUME_GOLD"],
|
||
"rankingType": "TYCOON",
|
||
"rankingPeriod": "OVERALL",
|
||
"settlementStatus": "NOT_STARTED"
|
||
},
|
||
"prizes": [],
|
||
"rankRewards": [],
|
||
"supportedRankingTypes": ["TYCOON"],
|
||
"supportedRankingPeriods": ["DAILY", "OVERALL"],
|
||
"activityStatus": "UPCOMING",
|
||
"serverTime": 1784299900000
|
||
}
|
||
```
|
||
|
||
`activityStatus`:`UPCOMING`、`ACTIVE`、`ENDED`、`DISABLED`。
|
||
|
||
### 2.2 我的进度
|
||
|
||
`GET /go/app/h5/game-king/yumi/me?activityId={activityId}`
|
||
|
||
返回:
|
||
|
||
- `totalConsumed`:活动时间窗内累计游戏消耗金币。
|
||
- `earnedChances`:`floor(totalConsumed / coinPerDraw)`。
|
||
- `usedChances` / `availableChances`:已用和可用抽奖次数。
|
||
- `nextChanceRemaining`:距离下一次机会仍需消耗的金币。
|
||
- `rank`:总榜名次;无消费时为 `null`。
|
||
|
||
### 2.3 抽奖
|
||
|
||
`POST /go/app/h5/game-king/yumi/draw`
|
||
|
||
```json
|
||
{
|
||
"activityId": "190000000000000001",
|
||
"requestId": "9af73ee9-65ca-4cf5-b23c-f4c222c3ee6e"
|
||
}
|
||
```
|
||
|
||
- `activityId` 和 `requestId` 均必填;`requestId` 最长 64 字符。客户端一次点击生成一个稳定值;超时重试必须复用原值。
|
||
- 幂等范围是 `activityId + userId + requestId`。已经提交的结果即使活动随后结束,也会原样返回,且不会再次扣次数或发奖。
|
||
- 权重仅表示相对概率;库存 `-1` 为无限,`0` 为耗尽,正数为剩余库存。有限库存使用条件更新防止超发。
|
||
- 返回 `record`、`prize`、`remainingChances`、`idempotentReplay`。
|
||
|
||
### 2.4 排行榜
|
||
|
||
`GET /go/app/h5/game-king/yumi/ranking?activityId={activityId}&rankingType=TYCOON&period=OVERALL&limit=30`
|
||
|
||
- `rankingType`:正式口径仅 `TYCOON`。旧 H5 请求 `VICTORIOUS` 时返回空榜,避免兼容页面报错。
|
||
- `period`:`DAILY` 或 `OVERALL`;配置页固定保存 `OVERALL`,H5 仍可切日榜。
|
||
- `limit` 默认 30,最大 100。
|
||
- 排序固定为:消费金币降序、达到当前消费值的事件时间升序、用户 ID 升序。
|
||
- `DAILY` 按活动时区自然日统计;活动结束结算只使用 `OVERALL` Top30。
|
||
- 用户资料通过 Java 批量接口补齐;资料服务异常时榜单仍返回 `userId` 和分数。
|
||
|
||
### 2.5 我的抽奖记录
|
||
|
||
`GET /go/app/h5/game-king/yumi/draw-records?activityId={activityId}&limit=20`
|
||
|
||
`limit` 最大 100。返回奖品快照及 `PENDING / PROCESSING / SUCCESS / FAILED / UNKNOWN` 发放状态。
|
||
|
||
## 3. 后台管理接口
|
||
|
||
所有接口要求菜单别名 `ResidentGameKingActivity`。写接口还要求对应按钮权限:
|
||
|
||
| 权限别名 | 接口用途 |
|
||
| --- | --- |
|
||
| `resident-activity:yumi-game-king:edit` | 保存活动、奖项、排名奖励 |
|
||
| `resident-activity:yumi-game-king:enable` | 启停活动 |
|
||
| `resident-activity:yumi-game-king:settle` | 人工触发到期结算 |
|
||
| `resident-activity:yumi-game-king:reconcile` | FAILED 重试、UNKNOWN 核账 |
|
||
| `resident-activity:yumi-game-king:backfill` | 权威事件补数 |
|
||
|
||
### 3.1 活动配置
|
||
|
||
| 方法 | 地址 | 说明 |
|
||
| --- | --- | --- |
|
||
| GET | `/go/resident-activity/game-king/list` | 活动列表 |
|
||
| GET | `/go/resident-activity/game-king/detail/{id}` | 完整详情 |
|
||
| POST | `/go/resident-activity/game-king/save` | 新增或修改 |
|
||
| PUT | `/go/resident-activity/game-king/enable/{id}?enabled=true` | 启停;布尔值只接受 `true/false/1/0` |
|
||
| GET | `/go/resident-activity/game-king/prizes/{id}` | 七个奖项 |
|
||
| PUT | `/go/resident-activity/game-king/prizes/{id}` | 保存七个奖项 |
|
||
| GET | `/go/resident-activity/game-king/rank-rewards/{id}` | 六个排名奖励档 |
|
||
| PUT | `/go/resident-activity/game-king/rank-rewards/{id}` | 保存六个排名奖励档 |
|
||
|
||
`POST /save` 示例:
|
||
|
||
```json
|
||
{
|
||
"id": "0",
|
||
"activityCode": "YUMI_GAME_KING_S1",
|
||
"activityName": "Yumi Game King",
|
||
"activityDesc": "",
|
||
"sysOrigin": "LIKEI",
|
||
"timeZone": "Asia/Riyadh",
|
||
"startTime": 1784300000000,
|
||
"endTime": 1784900000000,
|
||
"settlementDelayMinutes": 30,
|
||
"coinPerDraw": 1000,
|
||
"rankingType": "TYCOON",
|
||
"rankingPeriod": "OVERALL",
|
||
"enabled": false,
|
||
"prizes": [
|
||
{
|
||
"id": "0",
|
||
"prizeName": "Prize 1",
|
||
"prizeImage": "https://example.com/prize-1.png",
|
||
"weight": 100,
|
||
"stock": -1,
|
||
"resourceGroupId": "190000000000000101",
|
||
"enabled": true,
|
||
"sortOrder": 1
|
||
}
|
||
],
|
||
"rankRewards": [
|
||
{
|
||
"id": "0",
|
||
"startRank": 1,
|
||
"endRank": 1,
|
||
"resourceGroupId": "190000000000000201",
|
||
"rewardName": "Top 1"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
示例数组为节省篇幅只展示一项,真实启用配置必须满足:
|
||
|
||
- 恰好 7 个奖项,全部启用,`sortOrder` 完整覆盖 1..7 且不重复;权重必须大于 0。
|
||
- 恰好 6 个排名档:`1`、`2`、`3`、`4-7`、`8-10`、`11-30`。
|
||
- `sysOrigin` 固定 `LIKEI`;`rankingType` 固定 `TYCOON`;配置 `rankingPeriod` 固定 `OVERALL`。
|
||
- 首次启用时 `startTime` 必须严格晚于当前时间;同租户启用活动时间窗不能重叠。
|
||
- 计数窗口严格为 `[startTime, endTime)`。活动开始后锁定时间、口径、奖项、排名奖励和启停状态,只允许修改名称、说明等展示信息。
|
||
- `settlementDelayMinutes` 默认 30,可配置 0..1440。
|
||
- 每次保存/启用及每次真实发奖前都会重新校验奖励资源组:必须存在、已上架、明确属于 `LIKEI` 且非空,并比对冻结的完整奖励项快照。
|
||
|
||
单独保存奖项请求:
|
||
|
||
```json
|
||
{
|
||
"activityId": "190000000000000001",
|
||
"prizes": []
|
||
}
|
||
```
|
||
|
||
单独保存排名奖励请求:
|
||
|
||
```json
|
||
{
|
||
"activityId": "190000000000000001",
|
||
"rankRewards": []
|
||
}
|
||
```
|
||
|
||
路径 ID 与 body `activityId` 必须一致。
|
||
|
||
### 3.2 抽奖发放管理
|
||
|
||
| 方法 | 地址 | 说明 |
|
||
| --- | --- | --- |
|
||
| GET | `/go/resident-activity/game-king/draw-records/{id}?deliveryStatus=&limit=100` | 查看抽奖和发放明细 |
|
||
| POST | `/go/resident-activity/game-king/draw/retry/{recordId}` | 只重试 `FAILED` |
|
||
| POST | `/go/resident-activity/game-king/delivery-item/resolve/{itemId}?delivered=true` | 人工核销 `UNKNOWN` |
|
||
|
||
`delivered` 只接受 `true/false/1/0`:
|
||
|
||
- `true`:已通过 `trackId/businessNo` 确认下游到账,只记为成功,不再发送。
|
||
- `false`:已确认下游未到账,先转为 `FAILED`,再执行一次显式重试。
|
||
- 非法或缺失值返回 400,绝不默认为 `false`。
|
||
|
||
### 3.3 排名结算
|
||
|
||
| 方法 | 地址 | 说明 |
|
||
| --- | --- | --- |
|
||
| GET | `/go/resident-activity/game-king/settlement-records/{id}` | 查看冻结名次与发放状态 |
|
||
| POST | `/go/resident-activity/game-king/settlement/{id}` | 到结算时间后人工冻结并派发一小批 |
|
||
| POST | `/go/resident-activity/game-king/settlement/retry/{recordId}` | 只重试 `FAILED` |
|
||
|
||
结算只冻结一次 `OVERALL` Top30。记录和发奖任务在同一事务生成;人工接口每次最多派发该活动 4 个 `PENDING`,其余由内部定时任务继续。单项硬超时 20 秒,因此最坏外部调用约 80 秒,并为数据库处理留出余量。
|
||
|
||
### 3.4 事件补数
|
||
|
||
`POST /go/resident-activity/game-king/backfill/{id}?lastId=0&limit=200`
|
||
|
||
- 只读取 `task_center_event_archive_outbox` 的 `LIKEI + GAME_CONSUME_GOLD + 活动时间窗` 数据。
|
||
- 在任何归档扫描前都会检查迁移 058 的四列在线索引;索引未完成、不可见或列序不符时返回 `503 backfill_index_required`,不会退化为全表扫描。
|
||
- 仅允许对已启用且结算尚未冻结的活动补数;活动结束后的结算等待期仍可补,进入结算后返回 409,游标不会静默推进。
|
||
- 调用方只能传不透明游标 `lastId` 和 `limit`,不能传 eventType 或钱包口径;`limit` 最大 500。
|
||
- 返回 `eventType`、`scanned`、`accepted`、`nextLastId`、`finished`。未完成时用返回的 `nextLastId` 继续。
|
||
- 若结算恰好在一页补数中途冻结,接口返回 409 且不返回新的游标;调用方保留原 `lastId`,已成功写入的前序事件会由活动账本幂等跳过。
|
||
- 活动账本以 `activityId + eventId` 幂等,实时事件和补数重复不会重复累计。
|
||
|
||
## 4. 游戏消耗事件接入
|
||
|
||
唯一统计事件是任务中心的 `GAME_CONSUME_GOLD`。事件经过任务中心统一校验后、在任务中心自身业务去重之前同步扇出给 Game King;Game King 使用独立账本保证幂等。
|
||
|
||
已接入事件 ID 口径:
|
||
|
||
- Java 游戏:`GAME_CONSUME:<provider>:<orderId>`,已覆盖 `YOMI`、`LINGXIAN`、`HOTGAME`、`BAISHUN_LEGACY`、`LUDO`、`TURNTABLE`。
|
||
- Go GameOpen:`GAME_OPEN_CONSUME:<orderId>`。
|
||
- Go HotGame:`GAME_HOTGAME_CONSUME:<orderId>`。
|
||
- Go Baishun:`BAISHUN_GAME_CONSUME:<sysOrigin>:<orderId>`。
|
||
|
||
RocketMQ 默认配置:
|
||
|
||
- topic:`RC_DEFAULT_APP_ORDINARY`
|
||
- tag:`task_center_event`
|
||
- consumer group:`task-center-progress`
|
||
- 开关:`CHATAPP_TASK_CENTER_MQ_ENABLED`(兼容 `TASK_CENTER_EVENT_MQ_ENABLED`)
|
||
|
||
活动累计使用事件的 `occurredAt` 判断 `[startTime,endTime)`,因此活动开始前的历史游戏消耗不会计入。只接受正数 `deltaValue`;退款、胜利收入和非游戏钱包消耗不计入。
|
||
|
||
## 5. 发奖和异常状态
|
||
|
||
发奖以“奖励资源组”为一个下游调用单元,使用 Java `SendActivityReward`,`origin=GAME_KING_AWARD`,`trackId=deliveryItem.id`。后台明细同时返回稳定 `trackId`、`businessNo`、用户 ID、资源组 ID 和冻结奖励项。
|
||
|
||
- `PENDING`:本地事务已提交,尚未领取。
|
||
- `PROCESSING`:已领取,正在校验/调用下游。
|
||
- `SUCCESS`:Java 明确成功。
|
||
- `FAILED`:在发奖请求发出前就已明确失败,例如资源组不存在、下架、跨租户或与冻结快照不一致;只允许运营显式重试。
|
||
- `UNKNOWN`:发奖请求已尝试但结果不能证明,或 `PROCESSING` 超过 10 分钟;禁止自动重试,必须先核账再 resolve。
|
||
|
||
一旦活动启用,奖励组内的实际奖励项内容必须保持与冻结快照一致。若运营修改了组内容,发放会在调用 Java 前进入 `FAILED`;应先恢复原快照或完成明确处置,再重试。
|
||
|
||
## 6. 内部结算任务
|
||
|
||
`POST /internal/game-king/settle-due`
|
||
|
||
Header:`X-Internal-Token`。
|
||
|
||
本 Go 服务不启动 ticker。由 `chatapp-cron` 周期调用:
|
||
|
||
- 每次最多修复 20 个过期 `PROCESSING`,并扫描、冻结 10 个到期活动。
|
||
- 每次最多派发全局 4 个 `PENDING`;单项硬超时 20 秒,调用方超时应配置为至少 120 秒。
|
||
- 只自动恢复 `PENDING`。`FAILED` / `UNKNOWN` 永不自动重试;超过 10 分钟的 `PROCESSING` 只转为 `UNKNOWN`。
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"scanned": 2,
|
||
"settled": 2,
|
||
"failedIds": []
|
||
}
|
||
```
|
||
|
||
## 7. 数据库迁移与性能
|
||
|
||
迁移文件:`migrations/058_yumi_game_king.sql`。
|
||
|
||
- 新建活动、七奖项、六档奖励、幂等消费账本、总榜/日榜聚合、抽奖、结算和发放表。
|
||
- 排行读取物化聚合并使用复合排序索引,不对消费流水做运行时 `GROUP BY`。
|
||
- 补数给 `task_center_event_archive_outbox` 增加 `(sys_origin,event_type,occurred_at,id)` 复合索引,以范围 + keyset 扫描,避免全表排序。
|
||
- 迁移不执行历史 `UPDATE/DELETE/backfill`。新增复合索引仍属于线上 DDL,执行前应按目标 MySQL 版本、表数据量和 DDL 策略确认锁影响,并在低峰发布。
|