完善后端联调链路与模拟器多通道支持

This commit is contained in:
2026-04-01 18:48:59 +08:00
parent 94a1f0ba78
commit a70dc8d5d0
51 changed files with 4037 additions and 197 deletions

View File

@@ -0,0 +1,696 @@
# 业务后端数据库初版方案
## 1. 目标
本文档定义本项目业务后端第一版数据库方案。
第一版目标不是一次性覆盖整个平台所有能力,而是先稳定支撑以下范围:
- 微信小程序登录与用户身份
- 用户身体资料
- 多租户与俱乐部基础隔离
- 首页卡片、赛事、地图、Event 查询
- 配置对象的版本化管理与发布
- `launch` 启动与 `session_token`
- 小程序业务层与配置驱动游戏层对接
明确不纳入第一版数据库的能力:
- 支付与分账
- UGC 审核流
- 订单退款
- 成绩回放明细
- GPS / 心率明细归档
- 复杂运营报表
这些能力建议放到后续 migration。
## 2. 核心原则
### 2.1 业务数据与配置数据分层
数据库里同时存在两类对象:
- 业务状态对象
- 配置发布对象
两者要分层,不要揉成一个“超级事件表”。
### 2.2 运行态配置仍然走发布产物
数据库管理的是编辑态与发布关系,客户端运行时最终仍然消费静态发布结果,例如:
- `manifest_url`
- `manifest_checksum_sha256`
- 地图资源路径
- Event 发布配置
不要让客户端在运行时直接拼数据库对象。
### 2.3 游戏规则不进业务表细字段
业务后端负责:
- 用户
- 赛事
- 报名
- 发布
- 启动
业务后端不应该成为所有玩法字段的解释器。玩法细节优先放在版本化 `jsonb` 内容中。
### 2.4 所有对外 ID 使用 public_id
数据库内部主键统一使用 `uuid`
对客户端暴露的对象使用稳定 `public_id`
- `user_public_id`
- `competition_public_id`
- `map_public_id`
- `event_public_id`
- `release_public_id`
- `session_public_id`
原因:
- 内外 ID 解耦
- 便于迁移
- 便于白标和多端统一
- 避免顺序 ID 暴露内部增长信息
### 2.5 token 只存 hash不存明文
以下内容不应明文入库:
- 短信验证码
- refresh token
- session token
数据库只保存 hash。
## 3. 第一版建议模块
第一版数据库建议拆成 6 组:
1. 租户与组织
2. 用户与登录
3. 配置对象与发布
4. 赛事业务对象
5. 页面与卡片
6. 启动与 session
## 4. 表清单
### 4.1 租户与组织
#### `tenants`
平台租户。
建议字段:
- `id`
- `tenant_code`
- `name`
- `status`
- `theme_jsonb`
- `settings_jsonb`
- `created_at`
- `updated_at`
说明:
- `theme_jsonb` 存白标主题
- `settings_jsonb` 存租户级开关
#### `clubs`
租户下的俱乐部或品牌实体。
建议字段:
- `id`
- `tenant_id`
- `club_code`
- `name`
- `status`
- `profile_jsonb`
- `created_at`
- `updated_at`
说明:
- 第一版建议 club 从属于 tenant
- 不建议一开始做过深的组织树
### 4.2 用户与登录
#### `app_users`
平台用户主表。
建议字段:
- `id`
- `user_public_id`
- `default_tenant_id`
- `status`
- `nickname`
- `avatar_url`
- `last_login_at`
- `created_at`
- `updated_at`
#### `login_identities`
登录身份绑定表。
建议字段:
- `id`
- `user_id`
- `identity_type`
- `provider`
- `provider_subject`
- `country_code`
- `mobile`
- `status`
- `profile_jsonb`
- `created_at`
- `updated_at`
身份示例:
- 手机号
- 微信 `openid`
- 微信 `unionid`
#### `client_devices`
客户端设备标识记录。
建议字段:
- `id`
- `device_key`
- `platform`
- `first_seen_at`
- `last_seen_at`
- `meta_jsonb`
说明:
- `device_key` 对应前端 `device_id`
#### `auth_sms_codes`
短信验证码发送与校验记录。
建议字段:
- `id`
- `scene`
- `country_code`
- `mobile`
- `client_type`
- `device_key`
- `code_hash`
- `provider_payload_jsonb`
- `expires_at`
- `cooldown_until`
- `consumed_at`
- `created_at`
#### `auth_refresh_tokens`
刷新 token 持久化表。
建议字段:
- `id`
- `user_id`
- `client_type`
- `device_key`
- `token_hash`
- `issued_at`
- `expires_at`
- `revoked_at`
- `replaced_by_token_id`
#### `user_body_profiles`
用户当前身体档案。
建议字段:
- `id`
- `user_id`
- `status`
- `completed_at`
- `current_version_id`
- `created_at`
- `updated_at`
#### `user_body_profile_versions`
身体档案历史版本。
建议字段:
- `id`
- `profile_id`
- `version_no`
- `gender`
- `birth_date`
- `height_cm`
- `weight_kg`
- `resting_heart_rate_bpm`
- `max_heart_rate_bpm`
- `created_at`
### 4.3 配置对象与发布
这一组直接对应你现有的配置驱动架构。
#### `maps` / `map_versions`
地图对象及版本。
主表管理:
- `map_public_id`
- `slug`
- `name`
- `status`
- `tenant_id`
- `current_version_id`
版本表管理:
- `version_no`
- `content_jsonb`
- `created_at`
#### `playfields` / `playfield_versions`
路线、点位、场地定义。
#### `game_modes` / `game_mode_versions`
玩法模式配置,例如:
- `classic-sequential`
- `score-o`
#### `resource_packs` / `resource_pack_versions`
资源包,例如:
- 音效
- 素材包
- UI 资源集
#### `events` / `event_versions`
Event 本身与版本。
建议:
- `events` 管对象身份
- `event_versions` 管编辑态装配结果
`event_versions` 推荐显式引用:
- `map_version_id`
- `playfield_version_id`
- `game_mode_version_id`
- `resource_pack_version_id`
同时保留:
- `content_jsonb`
这样既有强关系,又保留灵活字段。
#### `event_releases`
发布记录表。
建议字段:
- `id`
- `release_public_id`
- `event_id`
- `event_version_id`
- `release_no`
- `manifest_url`
- `manifest_checksum_sha256`
- `status`
- `published_by_user_id`
- `published_at`
- `payload_jsonb`
说明:
- 客户端主要读这张表产出的 URL 与校验值
- `events.current_release_id` 可指向当前对外生效版本
### 4.4 赛事业务对象
#### `competitions`
赛事主表。
建议字段:
- `id`
- `competition_public_id`
- `tenant_id`
- `club_id`
- `slug`
- `display_name`
- `status`
- `registration_enabled`
- `leaderboard_enabled`
- `realtime_board_enabled`
- `competition_start_at`
- `competition_end_at`
- `content_jsonb`
- `created_at`
- `updated_at`
#### `competition_events`
赛事与 Event 的关联表。
建议字段:
- `id`
- `competition_id`
- `event_id`
- `event_release_id`
- `is_default`
- `sort_order`
- `relation_status`
- `created_at`
说明:
- 支持赛事绑定多个 Event
- 支持按赛事锁定某个 release
#### `registrations`
报名记录。
建议字段:
- `id`
- `registration_public_id`
- `competition_id`
- `user_id`
- `group_id`
- `status`
- `form_payload_jsonb`
- `approved_at`
- `cancelled_at`
- `created_at`
- `updated_at`
说明:
- 第一版先不强行拆复杂参赛人结构
- `form_payload_jsonb` 足够承接早期变化
### 4.5 页面与卡片
#### `page_configs` / `page_config_versions`
H5 / 白标页面配置。
主表建议字段:
- `id`
- `tenant_id`
- `club_id`
- `page_code`
- `name`
- `status`
- `current_version_id`
- `created_at`
- `updated_at`
版本表建议字段:
- `id`
- `page_config_id`
- `version_no`
- `dsl_jsonb`
- `theme_jsonb`
- `feature_flags_jsonb`
- `status`
- `created_at`
#### `cards`
首页卡片与运营入口。
建议字段:
- `id`
- `card_public_id`
- `tenant_id`
- `club_id`
- `card_type`
- `display_name`
- `competition_id`
- `event_id`
- `map_id`
- `page_config_id`
- `html_url`
- `cover_url`
- `display_slot`
- `display_priority`
- `status`
- `starts_at`
- `ends_at`
- `created_at`
- `updated_at`
说明:
- 这张表直接支撑 `/cards`
- 允许卡片指向赛事、页面或其他目标
### 4.6 启动与 session
#### `game_sessions`
游戏启动记录。
建议字段:
- `id`
- `session_public_id`
- `tenant_id`
- `user_id`
- `competition_id`
- `registration_id`
- `event_id`
- `event_release_id`
- `launch_request_id`
- `participant_public_id`
- `device_key`
- `client_type`
- `route_code`
- `status`
- `session_token_hash`
- `session_token_expires_at`
- `realtime_endpoint`
- `realtime_token_hash`
- `launched_at`
- `started_at`
- `ended_at`
- `created_at`
- `updated_at`
说明:
- 第一版闭环到 `launch` 即可
- `session_token` 用于后续 session 相关接口开放后继续扩展
- `launch_request_id` 需要唯一,支撑幂等
## 5. 当前 API 到表的映射
### `POST /auth/sms/send`
写:
- `auth_sms_codes`
### `POST /auth/login/sms`
读写:
- `auth_sms_codes`
- `app_users`
- `login_identities`
- `user_body_profiles`
- `user_body_profile_versions`
- `auth_refresh_tokens`
### `POST /auth/login/wechat`
读写:
- `app_users`
- `login_identities`
- `user_body_profiles`
- `user_body_profile_versions`
- `auth_refresh_tokens`
### `POST /auth/refresh`
读写:
- `auth_refresh_tokens`
### `PUT /me/body-profile`
读写:
- `user_body_profiles`
- `user_body_profile_versions`
### `GET /cards`
读:
- `cards`
- 可选补充 `competitions`
### `GET /competitions/{competition_id}`
读:
- `competitions`
- `competition_events`
- `events`
- `event_releases`
- `registrations`
### `GET /events/{event_id}` / `GET /competitions/{competition_id}/events/{event_id}`
读:
- `events`
- `event_releases`
- `maps`
- `competitions`
- `registrations`
### `POST /competitions/{competition_id}/registrations`
写:
- `registrations`
### `POST /events/{event_id}/launch`
读写:
- `events`
- `event_releases`
- `registrations` 可选
- `game_sessions`
## 6. 第一版不建议做复杂拆分的地方
以下字段第一版优先用 `jsonb`,不要先做一堆子表:
- 赛事详情扩展内容
- 报名附加表单
- 页面 DSL
- theme 配置
- feature flags
- Event 覆盖项
- 配置对象的实验字段
原因很简单:
- 你现在业务和玩法都在快速变化
- 先保留灵活性比过度范式化更重要
## 7. 第一版不建议进入数据库的内容
第一版不建议落库:
- 实时网关运行态内存结构
- GPS 点逐秒明细
- 心率逐秒明细
- WebGL 渲染状态
- 设备桥接瞬时事件
这些应该仍然留在:
- `realtime-gateway`
- 对象存储
- 后续归档服务
不应直接压进业务主库。
## 8. 推荐 migration 顺序
建议按下面顺序建表:
1. 租户与组织
2. 用户与登录
3. 配置对象与版本表
4. 发布表
5. 赛事与关联
6. 页面与卡片
7. session
这样依赖关系最清晰。
## 9. 第二版可新增的模块
建议后续 migration 再补:
- `orders`
- `payment_transactions`
- `refunds`
- `ugc_assets`
- `ugc_posts`
- `ugc_reviews`
- `session_uploads`
- `session_results`
- `gps_tracks`
- `heart_rate_streams`
- `channel_entries`
- `campaigns`
## 10. 当前最适合你的起步方式
如果你现在准备开始做后端,我建议不要先写所有 API而是按这个顺序开工
1. 先建数据库与 migration
2. 先写用户、赛事、Event、launch 这 4 个核心域
3. 先让小程序跑通登录 -> 看赛事 -> launch -> 进入游戏
4. 再补报名
5. 再补页面配置、卡片、俱乐部首页
6. 支付和 UGC 放到后续版本
## 11. 一句话总结
第一版数据库应该同时支撑两件事:
- 业务闭环
- 配置发布
但不能把它们混成一套随意增长的表结构。
正确方向是:
> PostgreSQL 存业务状态 + 版本化配置对象Go API 负责查询与发布编排,客户端继续消费发布后的运行态配置。