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

11 KiB
Raw Blame History

业务后端数据库初版方案

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