Files
cmr-mini/doc/backend/业务后端数据库初版方案.md

701 lines
11 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.
# 业务后端数据库初版方案
> 文档版本v1.0
> 最后更新2026-04-02
## 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 负责查询与发布编排,客户端继续消费发布后的运行态配置。