575 lines
22 KiB
Markdown
575 lines
22 KiB
Markdown
# 游戏接入架构
|
||
|
||
本文定义语音房 H5 弹窗游戏的服务边界、平台接入、金币改账、App 游戏列表、后台管理和补单对账方案。游戏可以来自百顺、灵仙、即构、小游等多个平台,但 App 侧看到的是一个合并后的游戏列表。
|
||
|
||
## 目标
|
||
|
||
- 在语音房内打开 H5 弹窗游戏,支持多个第三方游戏平台并行接入。
|
||
- App 获取统一游戏列表,不关心游戏来自哪个平台,但返回字段必须包含 `platform_code` 便于排查和埋点。
|
||
- 第三方平台回调可以获取用户信息、查询余额、变更用户金币和补单。
|
||
- 后台增加一级菜单 `游戏管理`,二级菜单 `游戏列表`,列表按平台筛选和维护。
|
||
- 所有金币变化仍由 `wallet-service` 落账,游戏平台、gateway、admin 和 room-service 都不能直接改金币余额。
|
||
|
||
## 非目标
|
||
|
||
- 不把游戏状态放进 Room Cell。房间只负责展示入口、房间上下文和必要的 IM 通知。
|
||
- 不让第三方平台直接访问 wallet-service 或 user-service。
|
||
- 不用 Redis 保存金币、订单或补单事实。Redis 只可用于短期 launch token、限流和幂等热缓存。
|
||
- 不把不同平台的协议字段直接扩散到 App、room-service 或 wallet-service。
|
||
|
||
## 服务边界
|
||
|
||
新增 `game-service`,端口建议使用 `13008`,MySQL 数据库建议使用 `hyapp_game`。
|
||
|
||
| 服务 | 职责 |
|
||
|---|---|
|
||
| `gateway-service` | App HTTP 入口;游戏列表、启动游戏、第三方平台公网回调入口;鉴权、协议转换、原始请求体透传。 |
|
||
| `game-service` | 游戏平台配置、游戏目录、展示规则、H5 启动会话、平台回调验签、平台协议适配、游戏订单、补单和对账任务 owner。 |
|
||
| `wallet-service` | 用户 `COIN` 的唯一 owner;处理游戏扣款、派奖、退款、补单入账和幂等流水。 |
|
||
| `user-service` | 用户基础资料 owner;向 game-service 提供平台回调需要的用户资料投影。 |
|
||
| `room-service` | 语音房状态 owner;只提供 `room_id` 上下文和可选的在房校验,不处理游戏订单或金币。 |
|
||
| `server/admin` | 后台 RBAC、菜单、审计和后台 HTTP API;通过 game-service gRPC 管理平台和游戏配置。 |
|
||
| `cron-service` | 只触发 game-service 的补单/对账批处理,不直接扫游戏表或修改钱包。 |
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
App["App voice room"] -->|"GET /api/v1/games"| Gateway["gateway-service"]
|
||
App -->|"POST /api/v1/games/{game_id}/launch"| Gateway
|
||
Provider["game platform callback"] -->|"provider HTTP callback"| Gateway
|
||
Admin["admin frontend"] -->|"admin HTTP"| AdminServer["server/admin"]
|
||
|
||
Gateway -->|"gRPC"| Game["game-service"]
|
||
AdminServer -->|"gRPC"| Game
|
||
Game -->|"gRPC ApplyGameCoinChange"| Wallet["wallet-service"]
|
||
Game -->|"gRPC user profile"| User["user-service"]
|
||
Game -. optional .->|"validate room context"| Room["room-service"]
|
||
|
||
Game --> GameDB[("hyapp_game")]
|
||
Wallet --> WalletDB[("hyapp_wallet")]
|
||
```
|
||
|
||
## 平台适配模型
|
||
|
||
每个平台实现一个 adapter,把第三方协议转换成内部统一命令。
|
||
|
||
```go
|
||
type PlatformAdapter interface {
|
||
VerifyCallback(rawBody []byte, headers map[string]string, query map[string]string, secret PlatformSecret) error
|
||
ParseOperation(path string, rawBody []byte) (ProviderOperation, error)
|
||
BuildResponse(result ProviderResult) ([]byte, string, error)
|
||
BuildLaunchURL(session LaunchSession, user GameUser, game GameConfig) (string, error)
|
||
}
|
||
```
|
||
|
||
内部统一操作只保留业务语义:
|
||
|
||
| 内部操作 | 说明 |
|
||
|---|---|
|
||
| `get_user_info` | 平台获取用户 ID、昵称、头像、余额等启动或运行信息。 |
|
||
| `get_balance` | 平台查询用户当前金币余额。 |
|
||
| `change_coin` | 平台发起扣款、派奖、退款、冲正等金币变化。 |
|
||
| `repair_order` | 平台或后台补单,重放遗漏订单或修复平台异常订单。 |
|
||
| `query_order` | 平台查询内部订单处理结果。 |
|
||
|
||
平台差异只能停留在 adapter 层。进入 game-service 领域层以后,所有金额单位、订单 ID、用户 ID、游戏 ID、状态机和错误码都必须统一。
|
||
|
||
## 数据模型
|
||
|
||
### game_platforms
|
||
|
||
游戏平台配置表。
|
||
|
||
```sql
|
||
game_platforms(
|
||
app_code VARCHAR(32) NOT NULL,
|
||
platform_code VARCHAR(64) NOT NULL,
|
||
platform_name VARCHAR(128) NOT NULL,
|
||
status VARCHAR(32) NOT NULL,
|
||
api_base_url VARCHAR(512) NOT NULL DEFAULT '',
|
||
callback_secret_ciphertext VARCHAR(1024) NOT NULL DEFAULT '',
|
||
callback_ip_whitelist JSON NULL,
|
||
adapter_config JSON NULL,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
created_at_ms BIGINT NOT NULL,
|
||
updated_at_ms BIGINT NOT NULL,
|
||
PRIMARY KEY(app_code, platform_code)
|
||
);
|
||
```
|
||
|
||
`status` 建议取值:
|
||
|
||
| 状态 | 语义 |
|
||
|---|---|
|
||
| `active` | 可展示、可启动、可接收回调。 |
|
||
| `maintenance` | App 可展示维护态,不允许启动新会话;已发生订单仍可接收回调。 |
|
||
| `disabled` | 不展示、不允许启动;回调只允许查询历史订单或按配置拒绝。 |
|
||
|
||
### game_catalog
|
||
|
||
第三方游戏目录表,一条记录对应一个平台下的一个游戏。
|
||
|
||
```sql
|
||
game_catalog(
|
||
app_code VARCHAR(32) NOT NULL,
|
||
game_id VARCHAR(96) NOT NULL,
|
||
platform_code VARCHAR(64) NOT NULL,
|
||
provider_game_id VARCHAR(128) NOT NULL,
|
||
game_name VARCHAR(128) NOT NULL,
|
||
category VARCHAR(64) NOT NULL DEFAULT '',
|
||
icon_url VARCHAR(512) NOT NULL DEFAULT '',
|
||
cover_url VARCHAR(512) NOT NULL DEFAULT '',
|
||
launch_mode VARCHAR(32) NOT NULL DEFAULT 'h5_popup',
|
||
orientation VARCHAR(32) NOT NULL DEFAULT 'portrait',
|
||
min_coin BIGINT NOT NULL DEFAULT 0,
|
||
status VARCHAR(32) NOT NULL,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
tags JSON NULL,
|
||
created_at_ms BIGINT NOT NULL,
|
||
updated_at_ms BIGINT NOT NULL,
|
||
PRIMARY KEY(app_code, game_id),
|
||
UNIQUE KEY uk_game_provider(app_code, platform_code, provider_game_id),
|
||
KEY idx_game_platform_status(app_code, platform_code, status, sort_order)
|
||
);
|
||
```
|
||
|
||
`game_id` 是内部稳定 ID,App、订单和补单都使用它;`provider_game_id` 只在 adapter 和平台请求中使用。
|
||
|
||
### game_display_rules
|
||
|
||
控制游戏在 App 内的展示范围。
|
||
|
||
```sql
|
||
game_display_rules(
|
||
app_code VARCHAR(32) NOT NULL,
|
||
rule_id VARCHAR(96) NOT NULL,
|
||
game_id VARCHAR(96) NOT NULL,
|
||
scene VARCHAR(64) NOT NULL,
|
||
region_id BIGINT NOT NULL DEFAULT 0,
|
||
language VARCHAR(32) NOT NULL DEFAULT '',
|
||
platform VARCHAR(16) NOT NULL DEFAULT '',
|
||
visible TINYINT(1) NOT NULL DEFAULT 1,
|
||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
created_at_ms BIGINT NOT NULL,
|
||
updated_at_ms BIGINT NOT NULL,
|
||
PRIMARY KEY(app_code, rule_id),
|
||
KEY idx_game_display(app_code, scene, region_id, visible, sort_order)
|
||
);
|
||
```
|
||
|
||
`scene` 当前先支持 `voice_room`,后续可以扩展首页、活动页或个人中心入口。`region_id=0` 表示全区域默认规则,具体区域规则优先级高于默认规则。
|
||
|
||
### game_launch_sessions
|
||
|
||
启动会话表,绑定用户、房间、游戏和平台短期令牌。
|
||
|
||
```sql
|
||
game_launch_sessions(
|
||
app_code VARCHAR(32) NOT NULL,
|
||
session_id VARCHAR(96) NOT NULL,
|
||
user_id BIGINT NOT NULL,
|
||
display_user_id VARCHAR(32) NOT NULL,
|
||
room_id VARCHAR(96) NOT NULL DEFAULT '',
|
||
scene VARCHAR(64) NOT NULL,
|
||
platform_code VARCHAR(64) NOT NULL,
|
||
game_id VARCHAR(96) NOT NULL,
|
||
provider_game_id VARCHAR(128) NOT NULL,
|
||
launch_token_hash VARCHAR(128) NOT NULL,
|
||
status VARCHAR(32) NOT NULL,
|
||
expires_at_ms BIGINT NOT NULL,
|
||
created_at_ms BIGINT NOT NULL,
|
||
updated_at_ms BIGINT NOT NULL,
|
||
PRIMARY KEY(app_code, session_id),
|
||
KEY idx_game_launch_user(app_code, user_id, created_at_ms),
|
||
KEY idx_game_launch_token(app_code, launch_token_hash)
|
||
);
|
||
```
|
||
|
||
H5 URL 里只放短期 `session_token` 或 provider 需要的签名参数,不直接暴露钱包能力。会话过期后不允许继续用它拉用户信息,但历史订单仍按 provider order id 幂等处理。
|
||
|
||
### game_orders
|
||
|
||
游戏金币变化订单表。
|
||
|
||
```sql
|
||
game_orders(
|
||
app_code VARCHAR(32) NOT NULL,
|
||
order_id VARCHAR(96) NOT NULL,
|
||
platform_code VARCHAR(64) NOT NULL,
|
||
provider_order_id VARCHAR(160) NOT NULL,
|
||
provider_round_id VARCHAR(160) NOT NULL DEFAULT '',
|
||
game_id VARCHAR(96) NOT NULL,
|
||
provider_game_id VARCHAR(128) NOT NULL,
|
||
user_id BIGINT NOT NULL,
|
||
room_id VARCHAR(96) NOT NULL DEFAULT '',
|
||
op_type VARCHAR(32) NOT NULL,
|
||
coin_amount BIGINT NOT NULL,
|
||
status VARCHAR(32) NOT NULL,
|
||
wallet_transaction_id VARCHAR(96) NOT NULL DEFAULT '',
|
||
wallet_balance_after BIGINT NOT NULL DEFAULT 0,
|
||
request_hash VARCHAR(128) NOT NULL,
|
||
raw_payload_ref VARCHAR(256) NOT NULL DEFAULT '',
|
||
failure_code VARCHAR(64) NOT NULL DEFAULT '',
|
||
failure_message VARCHAR(256) NOT NULL DEFAULT '',
|
||
created_at_ms BIGINT NOT NULL,
|
||
updated_at_ms BIGINT NOT NULL,
|
||
PRIMARY KEY(app_code, order_id),
|
||
UNIQUE KEY uk_game_provider_order(app_code, platform_code, provider_order_id),
|
||
KEY idx_game_order_user_time(app_code, user_id, created_at_ms),
|
||
KEY idx_game_order_round(app_code, platform_code, provider_round_id)
|
||
);
|
||
```
|
||
|
||
`op_type` 建议取值:
|
||
|
||
| 类型 | 钱包语义 |
|
||
|---|---|
|
||
| `debit` | 用户下注或购买局内道具,扣 `COIN`。 |
|
||
| `credit` | 用户中奖或平台派奖,加 `COIN`。 |
|
||
| `refund` | 扣款后失败退款,加 `COIN`,必须关联原订单。 |
|
||
| `reverse` | 平台冲正,按平台规则转成扣款或退款,不允许直接传负数。 |
|
||
|
||
订单状态机:
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> received
|
||
received --> wallet_applying
|
||
wallet_applying --> succeeded
|
||
wallet_applying --> failed
|
||
wallet_applying --> conflict
|
||
succeeded --> refunded
|
||
succeeded --> reversed
|
||
failed --> repair_pending
|
||
repair_pending --> wallet_applying
|
||
```
|
||
|
||
### game_callback_logs
|
||
|
||
回调日志表用于审计、排错和补偿,不作为金币事实。
|
||
|
||
```sql
|
||
game_callback_logs(
|
||
app_code VARCHAR(32) NOT NULL,
|
||
callback_id VARCHAR(96) NOT NULL,
|
||
platform_code VARCHAR(64) NOT NULL,
|
||
operation VARCHAR(64) NOT NULL,
|
||
request_id VARCHAR(96) NOT NULL DEFAULT '',
|
||
provider_request_id VARCHAR(160) NOT NULL DEFAULT '',
|
||
signature_valid TINYINT(1) NOT NULL DEFAULT 0,
|
||
request_hash VARCHAR(128) NOT NULL,
|
||
response_code VARCHAR(64) NOT NULL DEFAULT '',
|
||
response_body_hash VARCHAR(128) NOT NULL DEFAULT '',
|
||
status VARCHAR(32) NOT NULL,
|
||
created_at_ms BIGINT NOT NULL,
|
||
PRIMARY KEY(app_code, callback_id),
|
||
KEY idx_game_callback_platform_time(app_code, platform_code, created_at_ms)
|
||
);
|
||
```
|
||
|
||
原始请求体如果较大,存对象存储或压缩表;主表只保留 hash 和引用,避免日志表膨胀影响查询。
|
||
|
||
### game_repair_orders
|
||
|
||
补单任务表,来源可以是平台回调、后台手动、对账任务。
|
||
|
||
```sql
|
||
game_repair_orders(
|
||
app_code VARCHAR(32) NOT NULL,
|
||
repair_id VARCHAR(96) NOT NULL,
|
||
platform_code VARCHAR(64) NOT NULL,
|
||
provider_order_id VARCHAR(160) NOT NULL,
|
||
reason VARCHAR(128) NOT NULL,
|
||
status VARCHAR(32) NOT NULL,
|
||
operator_admin_id BIGINT NOT NULL DEFAULT 0,
|
||
attempts INT NOT NULL DEFAULT 0,
|
||
next_run_at_ms BIGINT NOT NULL DEFAULT 0,
|
||
last_error_code VARCHAR(64) NOT NULL DEFAULT '',
|
||
created_at_ms BIGINT NOT NULL,
|
||
updated_at_ms BIGINT NOT NULL,
|
||
PRIMARY KEY(app_code, repair_id),
|
||
UNIQUE KEY uk_game_repair_provider_order(app_code, platform_code, provider_order_id)
|
||
);
|
||
```
|
||
|
||
补单只能重放到 `game_orders` 和 `wallet-service` 幂等命令,不能直接 `UPDATE wallet_accounts`。
|
||
|
||
## 钱包改账
|
||
|
||
钱包底层统一表达为“用户金币增加或减少 + 业务类型”,最终都落到 `wallet_transactions` 和 `wallet_entries`。RPC 层不要暴露裸的通用 `ChangeCoin(user_id, amount, type)` 给所有业务直接调用,而是按业务场景封装入口;游戏场景固定由 game-service 调 wallet-service 的 `ApplyGameCoinChange`,wallet-service 内部再转换成统一账本分录。
|
||
|
||
game-service 调 wallet-service 新增内部 RPC:
|
||
|
||
```proto
|
||
message ApplyGameCoinChangeRequest {
|
||
RequestMeta meta = 1;
|
||
string command_id = 2;
|
||
int64 user_id = 3;
|
||
string platform_code = 4;
|
||
string game_id = 5;
|
||
string provider_order_id = 6;
|
||
string provider_round_id = 7;
|
||
string op_type = 8;
|
||
int64 coin_amount = 9;
|
||
string room_id = 10;
|
||
string request_hash = 11;
|
||
}
|
||
|
||
message ApplyGameCoinChangeResponse {
|
||
string wallet_transaction_id = 1;
|
||
int64 balance_after = 2;
|
||
bool idempotent_replay = 3;
|
||
}
|
||
```
|
||
|
||
`command_id` 固定为:
|
||
|
||
```text
|
||
game:{platform_code}:{provider_order_id}
|
||
```
|
||
|
||
钱包侧要求:
|
||
|
||
- `coin_amount` 必须是正整数,方向由 `op_type` 决定。
|
||
- `op_type` 只表达业务方向,最终统计口径落在 `wallet_transactions.biz_type` 和 `wallet_entries.available_delta`。
|
||
- `app_code + command_id` 唯一幂等,同一命令重复请求返回同一结果。
|
||
- 同一 `command_id` 携带不同 `request_hash` 必须返回冲突,不允许二次改账。
|
||
- `debit` 余额不足时不写成功交易、不写分录、不写 outbox。
|
||
- `credit/refund/reverse` 必须写 `wallet_transactions`、`wallet_entries` 和 `wallet_outbox`。
|
||
- 游戏账务 `biz_type` 至少包括 `game_debit`、`game_credit`、`game_refund`、`game_reverse`。
|
||
|
||
统一账本落账示例:
|
||
|
||
| 游戏动作 | `op_type` | `wallet_transactions.biz_type` | `wallet_entries.asset_type` | `wallet_entries.available_delta` |
|
||
|---|---|---|---|---:|
|
||
| 下注扣金币 | `debit` | `game_debit` | `COIN` | `-coin_amount` |
|
||
| 中奖派奖 | `credit` | `game_credit` | `COIN` | `+coin_amount` |
|
||
| 失败退款 | `refund` | `game_refund` | `COIN` | `+coin_amount` |
|
||
| 平台冲正 | `reverse` | `game_reverse` | `COIN` | 按冲正规则转换 |
|
||
|
||
用户金币收支统计只查 `wallet_entries`:
|
||
|
||
```sql
|
||
SELECT biz_type, SUM(available_delta) AS coin_delta
|
||
FROM wallet_entries
|
||
WHERE app_code = ?
|
||
AND user_id = ?
|
||
AND asset_type = 'COIN'
|
||
AND created_at_ms >= ?
|
||
AND created_at_ms < ?
|
||
GROUP BY biz_type;
|
||
```
|
||
|
||
## App 接口
|
||
|
||
### 获取合并游戏列表
|
||
|
||
```http
|
||
GET /api/v1/games?scene=voice_room&room_id={room_id}
|
||
```
|
||
|
||
返回:
|
||
|
||
```json
|
||
{
|
||
"games": [
|
||
{
|
||
"game_id": "bs_rocket_001",
|
||
"platform_code": "baishun",
|
||
"name_key": "game.bs_rocket_001.name",
|
||
"name": "Rocket",
|
||
"icon_url": "https://cdn.example.com/game/rocket.png",
|
||
"cover_url": "https://cdn.example.com/game/rocket-cover.png",
|
||
"category": "casual",
|
||
"launch_mode": "h5_popup",
|
||
"orientation": "portrait",
|
||
"min_coin": 100,
|
||
"enabled": true,
|
||
"maintenance": false,
|
||
"sort_order": 10
|
||
}
|
||
],
|
||
"server_time_ms": 1778490000000
|
||
}
|
||
```
|
||
|
||
列表聚合规则:
|
||
|
||
- 只返回 `game_platforms.status in active/maintenance` 且 `game_catalog.status in active/maintenance` 的游戏。
|
||
- `maintenance=true` 时可以展示但不能启动。
|
||
- 根据用户区域、语言、平台和 `scene` 过滤 `game_display_rules`。
|
||
- 返回是多个平台合并排序,不按平台分组;后台才按平台筛选。
|
||
|
||
### 启动游戏
|
||
|
||
```http
|
||
POST /api/v1/games/{game_id}/launch
|
||
```
|
||
|
||
请求:
|
||
|
||
```json
|
||
{
|
||
"scene": "voice_room",
|
||
"room_id": "room_123"
|
||
}
|
||
```
|
||
|
||
返回:
|
||
|
||
```json
|
||
{
|
||
"session_id": "game_sess_1778490000_abcd",
|
||
"launch_url": "https://provider.example.com/h5?...",
|
||
"expires_at_ms": 1778490900000,
|
||
"orientation": "portrait"
|
||
}
|
||
```
|
||
|
||
启动时校验:
|
||
|
||
- 用户必须已登录且 onboarding completed。
|
||
- 游戏和平台不能是 `disabled`。
|
||
- `maintenance` 状态拒绝启动。
|
||
- 如果 `scene=voice_room` 且产品要求必须在房间内,game-service 通过 room-service 做只读校验;校验失败不创建启动会话。
|
||
|
||
## 第三方平台回调
|
||
|
||
公网入口统一放 gateway-service:
|
||
|
||
```http
|
||
POST /api/v1/game-callbacks/{platform_code}/{operation}
|
||
```
|
||
|
||
gateway 行为:
|
||
|
||
- 保留原始 body、query、header,不提前解析成统一 JSON。
|
||
- 生成 `request_id` 并透传到内部调用。
|
||
- 按 `platform_code` 转发给 game-service。
|
||
- 回调响应不套 App 业务 envelope,因为第三方通常要求固定响应格式。
|
||
|
||
game-service 行为:
|
||
|
||
1. 读取平台配置和 adapter。
|
||
2. 验签、IP 白名单、时间戳/nonce 防重放。
|
||
3. 写 `game_callback_logs`。
|
||
4. adapter 解析为内部 `ProviderOperation`。
|
||
5. 需要用户资料时读 user-service;需要余额或改账时读/写 wallet-service。
|
||
6. 写 `game_orders` 或 `game_repair_orders`。
|
||
7. adapter 构造平台要求的响应。
|
||
|
||
## 补单和对账
|
||
|
||
补单来源:
|
||
|
||
- 平台主动补单回调。
|
||
- 后台管理员按 `platform_code + provider_order_id` 手动补单。
|
||
- cron-service 触发 game-service 扫描 `game_repair_orders`。
|
||
- 后续平台支持订单查询时,由 game-service 拉取平台订单对账。
|
||
|
||
补单约束:
|
||
|
||
- 补单必须复用原始 `provider_order_id` 生成同一个钱包 `command_id`。
|
||
- 钱包幂等命中时视为补单成功,但必须记录 `idempotent_replay=true`。
|
||
- 原订单请求 hash 不一致时进入 `conflict`,需要人工处理。
|
||
- 后台手动补单必须写 admin 审计,包含操作人、原因和平台订单号。
|
||
|
||
## 后台管理
|
||
|
||
新增一级菜单:
|
||
|
||
```text
|
||
游戏管理
|
||
└── 游戏列表
|
||
```
|
||
|
||
游戏列表默认按平台 tab 或筛选展示:
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| 平台 | 百顺、灵仙、即构、小游等 `platform_code/platform_name`。 |
|
||
| 游戏 ID | 内部 `game_id` 和平台 `provider_game_id`。 |
|
||
| 名称/图标 | App 展示资源。 |
|
||
| 类目 | casual、slot、board 等运营类目。 |
|
||
| 状态 | active、maintenance、disabled。 |
|
||
| 展示范围 | scene、区域、语言、端。 |
|
||
| 最低金币 | App 展示和启动前提示用。 |
|
||
| 排序 | 合并列表排序。 |
|
||
| 回调状态 | 最近回调成功率、最近错误码。 |
|
||
|
||
后台接口由 `server/admin` 提供:
|
||
|
||
```http
|
||
GET /api/v1/admin/game/platforms
|
||
POST /api/v1/admin/game/platforms
|
||
PATCH /api/v1/admin/game/platforms/{platform_code}
|
||
GET /api/v1/admin/game/games?platform_code=baishun
|
||
POST /api/v1/admin/game/games
|
||
PATCH /api/v1/admin/game/games/{game_id}
|
||
PATCH /api/v1/admin/game/games/{game_id}/status
|
||
GET /api/v1/admin/game/orders
|
||
GET /api/v1/admin/game/callback-logs
|
||
POST /api/v1/admin/game/repair-orders
|
||
```
|
||
|
||
`server/admin` 只做权限、审计和后台协议;平台配置和游戏目录事实仍写入 game-service。
|
||
|
||
当前第一期已落地平台配置和游戏目录维护接口,订单查询、回调日志查询和手动补单入口随补单状态机一起实现。
|
||
|
||
## 并发与可靠性
|
||
|
||
- game-service 可以多实例无状态部署;订单事实在 MySQL,短期 session 可同时写 MySQL 和 Redis。
|
||
- 同一用户多游戏并发改金币时,由 wallet-service 对 `wallet_accounts(app_code,user_id,COIN)` 行锁和命令幂等保证余额正确。
|
||
- 平台重复回调必须返回幂等结果,不允许重复扣款或重复派奖。
|
||
- 平台回调乱序时,game-service 只按 provider order id 和 round id 记录事实;是否允许先派奖后扣款由平台 adapter 配置决定。
|
||
- game_callback_logs 不能作为成功事实;成功事实是 `game_orders.status=succeeded` 加 wallet 成功交易。
|
||
- game-service outbox 只发布游戏订单完成、补单完成等事件;钱包余额变化仍以 wallet outbox 为准。
|
||
|
||
## 安全要求
|
||
|
||
- 每个平台必须独立 secret,支持轮换;后台只能看到 masked secret。
|
||
- 回调验签使用原始 body,不使用重新序列化后的 JSON。
|
||
- 支持 IP 白名单,但 IP 白名单失败不能替代签名校验。
|
||
- 请求时间戳和 nonce 防重放;nonce 可短期放 Redis,最终仍以 provider order id 幂等兜底。
|
||
- H5 启动 token 短有效期,绑定 `app_code/user_id/game_id/platform_code/room_id`。
|
||
- 所有 provider 回调、补单、后台状态变更必须写审计日志。
|
||
- 金币金额使用整数,不允许 float,不允许负数金额表达方向。
|
||
|
||
## 监控指标
|
||
|
||
| 指标 | 说明 |
|
||
|---|---|
|
||
| `game_callback_total{platform,operation,status}` | 平台回调量和状态。 |
|
||
| `game_callback_verify_failed_total{platform}` | 验签失败和白名单失败。 |
|
||
| `game_order_total{platform,op_type,status}` | 游戏订单状态。 |
|
||
| `game_wallet_apply_duration_ms{platform,op_type}` | 调钱包耗时。 |
|
||
| `game_wallet_conflict_total{platform}` | 幂等冲突数量。 |
|
||
| `game_repair_pending_total{platform}` | 待补单数量。 |
|
||
| `game_launch_total{platform,game_id,status}` | 游戏启动量和失败原因。 |
|
||
|
||
## 落地顺序
|
||
|
||
1. 新增 `game-service` 空服务、`hyapp_game` schema、配置和 docker-compose 端口 `13008`。
|
||
2. 定义 `api/proto/game/v1/game.proto`,包含 App 列表、启动、后台管理和回调处理 RPC。
|
||
3. wallet-service 增加 `ApplyGameCoinChange` RPC 和 `game_*` 账务类型测试。
|
||
4. game-service 实现平台配置、游戏目录、展示规则和 App 合并列表。
|
||
5. game-service 实现 H5 launch session,gateway 暴露 `/api/v1/games` 和 `/api/v1/games/{game_id}/launch`。
|
||
6. 实现第一个平台 adapter,先打通 `get_user_info/get_balance/change_coin`。
|
||
7. 增加 `game_orders`、`game_callback_logs`、补单幂等和冲突处理。
|
||
8. server/admin 增加 `游戏管理 / 游戏列表`,接入 game-service 管理接口。
|
||
9. 增加补单后台入口和 cron-service 触发的 game-service 补偿批处理。
|
||
10. 接入第二个平台前,固化 adapter 测试基线,避免平台差异污染核心领域层。
|
||
|
||
## 验收清单
|
||
|
||
- App 能在语音房拿到多个平台合并后的游戏列表。
|
||
- 禁用平台、维护平台、禁用游戏、区域不匹配游戏不会被错误启动。
|
||
- 启动游戏返回短期 H5 URL,过期 session 不能再换取用户信息。
|
||
- 平台扣金币成功后 wallet 余额减少,重复回调不重复扣。
|
||
- 平台派奖成功后 wallet 余额增加,重复回调不重复加。
|
||
- 同一 provider order id 不同 payload 返回冲突并进入人工处理。
|
||
- 余额不足时 game_orders 失败,wallet 不写成功交易。
|
||
- 后台可以按平台维护游戏、上下架、排序和查看回调日志。
|
||
- 后台补单只能通过 game-service 和 wallet-service 幂等命令完成,不能直接改余额。
|
||
- cron-service 只触发补单批处理,不直接拥有游戏或钱包状态。
|