13 KiB
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:
{
"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 解析当前可见活动。返回活动、固定七个奖项、六个排名奖励档、服务器时间和活动状态。
关键返回字段:
{
"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
{
"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按活动时区自然日统计;活动结束结算只使用OVERALLTop30。- 用户资料通过 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 示例:
{
"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且非空,并比对冻结的完整奖励项快照。
单独保存奖项请求:
{
"activityId": "190000000000000001",
"prizes": []
}
单独保存排名奖励请求:
{
"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。
响应:
{
"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 策略确认锁影响,并在低峰发布。