aslan-server/rc-service/rc-service-other/docs/Aslan游戏王活动接口.md

192 lines
14 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.

# 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
```json
{
"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服务端限制到 1100。 |
`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服务端限制到 1100。 |
`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` | 01440默认 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` 为不重复的 17`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` 配置 01440 分钟。定时任务每 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)` 索引。迁移不回填钱包流水,也不扫描更新既有业务大表。
- 当前交付只新增了迁移文件,**尚未在测试或生产数据库执行**。执行前仍需按环境发布流程复核目标库、变更窗口和执行计划。