# 业务后端数据库初版方案
> 文档版本：v1.0
> 最后更新：2026-04-02 08:28:05


## 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 负责查询与发布编排，客户端继续消费发布后的运行态配置。


