14 KiB
Aslan 游戏王活动接口
本文对应 H5 activity/game-king/aslan.html 与 webconsole 游戏王配置页。接口契约以当前仓库的 AslanGameKingRestController、AslanGameKingBackRestController、AslanGameKingModels 和 AslanGameKingWalletListener 为准。
通用约定
- 下列地址均为网关或 webconsole 域名后的相对路径;H5 通过现有
common_aslan请求层调用。 - Controller 返回值会由框架包装为
ResultResponse:成功时核心结构为{"status":true,"errorCode":0,"time":<毫秒时间戳>,"body":...};业务数据均在body。 - DTO 中的 Long ID 在响应 JSON 中按字符串输出,H5 应保留字符串,不要转成 JavaScript Number。
- 时间字段
startTime、endTime、settlementTime、serverTime、drawTime、deliverTime均为 Unix 毫秒时间戳。
H5 鉴权头
H5 五个接口都使用网关鉴权,不接受客户端提交 userId 或 sysOrigin 业务字段。
| Header | 必填 | 精确格式/说明 |
|---|---|---|
Authorization |
是 | Bearer <app access token>;网关从已校验 token 取得用户 ID,再通过内部头注入 AppExtCommand.reqUserId。 |
Req-Sys-Origin |
是 | origin=<活动所属系统>;originChild=<子系统>;。origin 必须与后台活动的 sysOrigin 完全相同;未传 originChild 时框架回落为 origin。 |
Req-Client |
是 | H5 固定传 H5。 |
Req-App-Intel |
是 | 分号分隔,例如 version=<版本>;build=<正整数>;channel=<渠道>;;version、正数 build、channel 都由网关校验。 |
Req-Inner-Internal 是网关生成的内部头,H5 不应自行构造。抽奖 JSON 即使额外带入 reqUserId、reqSysOrigin,Controller 也不会使用这些字段。
Webconsole 鉴权
webconsole 接口使用 Authorization: Bearer <console JWT>。迁移 SQL 同时登记了 activity:game-king:* 资源与 ActivityGameKing 菜单;账号需拥有相应后台菜单/资源权限。
H5 接口
基础路径:/activity/game/king/aslan
1. 活动详情
GET /activity/game/king/aslan/detail
Query:
| 参数 | 必填 | 说明 |
|---|---|---|
activityId |
否 | 活动 ID。省略时,按当前 Req-Sys-Origin 依次选择进行中、最近将开始、最近结束的已启用活动。 |
body 为 Detail:
activity:活动配置,主要字段为id、activityCode、activityName、activityDesc、sysOrigin、timeZone、startTime、endTime、settlementDelayMinutes、settlementTime、coinPerDraw、rankingType、rankingPeriod、enabled、settlementStatus。兼容字段eventTypes恒为["GAME_EXPENDITURE"],不代表可配置白名单。prizes:7 个奖项;主要字段为id、prizeName、prizeImage、weight、stock、resourceGroupId、sortOrder、rewardItems。rankRewards:固定档位1、2、3、4-7、8-10、11-30及其resourceGroupId、rewardName、rewardItems。supportedRankingTypes:固定为TYCOON、VICTORIOUS。supportedRankingPeriods:固定为DAILY、OVERALL。activityStatus:UPCOMING、ACTIVE、ENDED或DISABLED。serverTime:服务端当前毫秒时间戳。
2. 我的活动状态
GET /activity/game/king/aslan/me
Query:可选 activityId,选择规则同详情接口。
body 为 UserState:activityId、totalConsumed、earnedChances、usedChances、availableChances、nextChanceRemaining、rank。其中抽奖机会只由支出流水累计,earnedChances = floor(totalConsumed / coinPerDraw)。
3. 抽奖
POST /activity/game/king/aslan/draw
Body:
{
"activityId": "可选活动ID",
"requestId": "本次点击生成且重试时保持不变的唯一值"
}
requestId必填、去除首尾空格后非空、最长 64 字符。- 幂等范围为“活动 + 当前用户 +
requestId”。网络重试必须复用原值;新的一次抽奖必须生成新值。 - 仅
ACTIVE活动且availableChances > 0时可抽。
body 为 DrawResult:
record:id、activityId、userId、requestId、prizeId、prizeName、prizeImage、resourceGroupId、deliveryStatus、failureReason、drawTime、deliverTime。prize:命中奖项及展示用rewardItems。remainingChances:本次扣减后的剩余机会。idempotentReplay:是否返回同一requestId的已有结果。
4. 排行榜
GET /activity/game/king/aslan/ranking
| Query 参数 | 必填 | 说明 |
|---|---|---|
activityId |
否 | 活动 ID。 |
rankingType |
否 | TYCOON(游戏支出)或兼容维度 VICTORIOUS;省略时使用活动配置。本活动事件采集只接收支出,因此运营配置固定使用 TYCOON。 |
period |
否 | DAILY 或 OVERALL;省略时使用活动配置。 |
limit |
否 | 默认 30,服务端限制到 1~100。 |
body 为 Ranking:rankingType、period、statDate、entries、my。DAILY 的 statDate 是活动 timeZone 下当天的 yyyy-MM-dd,OVERALL 时为空。每个 RankEntry 包含 rank、userId、account、nickname、avatar、score、totalConsumed、totalWon、me。
同分排序固定为:分值降序、首次到达该分值时间升序、userId 升序。
5. 我的抽奖记录
GET /activity/game/king/aslan/draw-records
| Query 参数 | 必填 | 说明 |
|---|---|---|
activityId |
否 | 活动 ID。 |
limit |
否 | 默认 20,服务端限制到 1~100。 |
body 为 DrawRecord[],字段同抽奖结果中的 record。逐项发奖明细 deliveryItems 只在管理端填充。
Webconsole 管理接口
基础路径:/activity/game-king
| 方法与路径 | 关键入参 | body/作用 |
|---|---|---|
GET /list |
无 | Activity[]。 |
GET /detail/{id} |
活动 ID | Detail。 |
POST /save |
SaveCommand JSON |
新增或修改活动,返回 Activity。 |
PUT /enable/{id} |
Query enabled=true/false |
启停活动。活动开始后不能再改变启用状态。 |
GET /prizes/{id} |
活动 ID | Prize[]。 |
PUT /prizes/{id} |
{"activityId":"同路径ID","prizes":[...]} |
覆盖保存 7 个奖项;活动开始后禁止修改。 |
GET /rank-rewards/{id} |
活动 ID | RankReward[]。 |
PUT /rank-rewards/{id} |
{"activityId":"同路径ID","rankRewards":[...]} |
覆盖保存 6 个固定排名奖励档;活动开始后禁止修改。 |
GET /draw-records/{id} |
可选 deliveryStatus、limit |
抽奖记录及 deliveryItems;状态可为 PENDING/PROCESSING/SUCCESS/FAILED/UNKNOWN,limit 默认/最大均为 100。 |
POST /draw/retry/{recordId} |
抽奖记录 ID | 仅重试已核实可重试的 FAILED 记录,不处理 UNKNOWN。 |
GET /settlement-records/{id} |
活动 ID | Top 30 冻结结算记录及逐项 deliveryItems。 |
POST /settlement/{id} |
活动 ID | 到达 settlementTime 后冻结 TYCOON/OVERALL Top 30 并发奖。 |
POST /settlement/retry/{recordId} |
结算记录 ID | 仅重试已核实可重试的 FAILED 记录,不处理 UNKNOWN。 |
POST /delivery-item/resolve/{itemId} |
Query delivered=true/false |
只处理 UNKNOWN 单项;语义见下文。 |
POST /backfill/{id} |
lastId、limit 可选 |
服务端按固定第三方游戏支出口径分页补算钱包流水,返回 BackfillResult。 |
SaveCommand 关键字段与约束
| 字段 | 约束 |
|---|---|
id |
修改时传;新增不传。 |
activityCode |
必填,最长 64,活动间唯一。 |
activityName |
必填,最长 128。 |
activityDesc |
可选,最长 1000。 |
sysOrigin |
必填,必须是服务端支持的 SysOriginPlatformEnum 值,并与 H5 Req-Sys-Origin.origin、钱包事件 sysOrigin 一致。 |
timeZone |
必填;默认 Asia/Riyadh,必须是合法 IANA 时区。 |
startTime、endTime |
必填毫秒时间戳,且 startTime < endTime。 |
settlementDelayMinutes |
0~1440,默认 30;settlementTime = endTime + settlementDelayMinutes。 |
coinPerDraw |
必填,正整数金币数。 |
eventTypes |
已废弃且可不传;旧客户端传值也会被忽略,服务端统一保存 GAME_EXPENDITURE 兼容哨兵。 |
rankingType |
TYCOON 或 VICTORIOUS,默认 TYCOON。 |
rankingPeriod |
DAILY 或 OVERALL,默认 OVERALL。 |
enabled |
默认 false。首次以 true 保存时,startTime 必须晚于当前时间。 |
prizes、rankRewards |
可随活动一起保存,也可通过独立接口保存。启用前两者都必须完整。 |
同一 sysOrigin 的已启用活动时间窗不得重叠。活动开始后,统计时间、系统、时区、结算延时、coinPerDraw 和启用状态等统计口径会锁定。事件资格由服务端固定分类器决定,不允许 webconsole 手工配置。
奖项必须恰好 7 个且全部 enabled=true,sortOrder 为不重复的 1~7,prizeName/prizeImage 非空,weight > 0,stock >= -1,并配置 resourceGroupId。stock=-1 表示无限库存,0 表示耗尽。
排名奖励必须恰好为 1-1、2-2、3-3、4-7、8-10、11-30 六档。奖项和排名奖励引用的资源组必须已上架、sysOrigin 与活动一致且至少包含一个奖励配置。
补数返回
POST /backfill/{id} 不接收 eventType 或 receiptType。limit 默认 200、最大 500;调用方只需把本次 nextLastId 作为下次 lastId 继续请求,直到返回 finished=true。钱包侧会强制 EXPENDITURE,并以四个转义后的左锚定游戏前缀查询,避免扫描活动时间窗内的全部非游戏支出。
BackfillResult 字段:eventType(恒为 GAME_EXPENDITURE)、receiptType(恒为 1/EXPENDITURE)、scanned、accepted、nextLastId、finished。补数查询边界同实时统计,为 [startTime,endTime);调用钱包历史接口时实现为 endTime - 1ms。
钱包事件统计契约
- RocketMQ topic:逻辑 topic
RC_DEFAULT_APP_ORDINARY,运行时由${rocketmq.topics.RC_DEFAULT_APP_ORDINARY}解析。 - tag:
wallet_sync_gold_v2。 - 独立 consumer group:
ASLAN_GAME_KING_WALLET_SYNC_V1。不得和钱包落库消费者共用 group,否则会竞争消费。 - 事件模型:
WalletReceiptSyncEvent。使用字段assetRecordId、userId、sysOrigin、eventType、eventId、receiptType、bizType、amount、createTime。 - 唯一有效分类条件为:
bizType=WalletBizType.GOLD.getType()、receiptType=EXPENDITURE(1),并且eventType以HOT_GAME_、HKYS_GAME_、BAISHUN_GAME_、YOMI_GAME_之一开头;此外金额必须非 0、sysOrigin必须与活动相同、createTime必须位于[startTime,endTime)。 - 分类器使用严格
startsWith,且前缀后必须有具体游戏 ID/标识;裸HOT_GAME_等前缀不会计入。禁止仅判断“支出”、contains("GAME")或GoldOriginMapping,这些宽泛条件会误收礼物/奖励等流水或遗漏 YOMI。 - 有效流水只累计 TYCOON 与抽奖机会;
INCOME不进入该活动统计。 - 实时 MQ、MQ 重投和后台补数共用唯一幂等键
(activity_id, asset_record_id)。 - DAILY 按活动
timeZone落日表;OVERALL 使用全活动累计。活动结算奖励固定按 TYCOON/OVERALL,而不是当前 H5 正在查看的榜单维度。
结算截点与发奖核账
- 默认在
endTime + 30 分钟到达结算截点;后台可通过settlementDelayMinutes配置 0~1440 分钟。定时任务每 5 分钟扫描一次到期活动。 - 统计有效时间仍只到
endTime(不含);30 分钟只是给异步消息到达留出的缓冲,不代表 RocketMQ 已提供“无更早消息”的 watermark。要求严格完整时,应在截点前按nextLastId连续调用固定口径补数直至finished=true,再执行结算。 - 首次结算会在活动行排他锁内冻结 TYCOON/OVERALL Top 30;已进入统计事务会先完成,冻结后到达的 MQ 或补数会被拒绝,排名快照不会再漂移。
- 每个奖励组拆成
deliveryItems逐项发放。普通失败可标为FAILED并在运营核实后走 retry;外部适配器抛错,或PROCESSING超过 10 分钟租期时,结果不能判定是否已到账,因此标为UNKNOWN。 UNKNOWN永远不会被定时任务或普通 retry 盲目补发。运营核对下游账目后调用/delivery-item/resolve/{itemId}:delivered=true表示确认已到账,仅把该项记为成功且不再次发送;delivered=false表示确认未到账,原子解锁父记录与该项后再补发。- 结算中存在
FAILED或UNKNOWN时活动聚合状态为PARTIAL_FAILED;全部成功后为COMPLETED。
数据库迁移
- 测试环境:
.deploy/test-deploy/sql/20260717_aslan_game_king.sql - 生产环境:
.deploy/prod-deploy/sql/20260717_aslan_game_king.sql - 两份 SQL 内容应保持字节一致。迁移创建 9 张独立活动表,并登记 webconsole 菜单/资源。活动表
event_types只为兼容旧服务保留,默认且只写GAME_EXPENDITURE,真实流水的ledger.event_type仍保留用于审计。 - SQL 文件开头已记录性能评估:实时写路径使用唯一键/主键聚合,榜单直接读取维度索引,恢复与到期扫描均有状态时间索引;补数绑定
sys_origin + EXPENDITURE并使用四个转义后的左锚定前缀,利用钱包(sys_origin,event_type,create_time)索引。迁移不回填钱包流水,也不扫描更新既有业务大表。 - 当前交付只新增了迁移文件,尚未在测试或生产数据库执行。执行前仍需按环境发布流程复核目标库、变更窗口和执行计划。