# 业务后端数据库初版方案 ## 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 负责查询与发布编排,客户端继续消费发布后的运行态配置。