zizi d97e194383 docs(治理): SoT注册表+docs-gate六检门,清缩历史档126→69
- 注册表:docs/architecture/README.md §2,37 份 canonical(frontmatter topic+canonical:true)与表双向机器对账
- 门:.agents/tools/docs-gate.py 六检(品牌根/canonical唯一/死链/入口卫生/留痕隔离/设计档申报),挂 .githooks/pre-commit(本仓已激活)+ .gitea/workflows/docs-gate.yml(待runner)+ wave-close 第8步;旧 check-deadlinks.sh 退役并入 G3
- 清缩:删 54 项历史档(plans 16/agent-specs 设计与spike 20/brainstorms 5/memorys 3/goals+作战清单完成史归档/王蓝莓design/SAA现状html/add-game-template);channel-spike 564K 代码资产迁仓级 spikes/;六件删前蒸馏已迁(代码评审16条open项→进度总账§5、prefix-cache字段表→cheap-model skill、意图基线29条→需求清单附录、九门降级rationale→验收门、A11 TODO→tech-decisions、3layer边界→littlejs-game-dev 指针)
- 修口径约90处:六处SAA『现行主线』旧标、Nacos/RocketMQ『未部署』旧述(07-01反转)、gameDefinition残留、全部死链改 git show 定位;AGENTS.md 249→136行(决策史归tech-decisions);_index 改纯在飞板;对外演示版 md→html
- 依据:docs/agent-specs/2026-07-02-文档治理-{全量普查与裁决-report,SoT注册表与治理门-设计}.md(四路普查191份md+Codex/Opus双评审必修项已折入);恢复基线 8ea97234(单档 git checkout 8ea97234 -- <路径>)
2026-07-02 14:24:12 +08:00

361 lines
36 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.

---
topic: 数据模型
canonical: true
date: 2026-06-22
---
# 后端域 · 全平台数据模型总图
> **这是什么**:数据视角的结构总图 —— 全平台落库了哪些表、表与表之间怎么连、有哪些必须先知道才不会踩坑的数据语义。它与 [13模块.md](../架构/13模块.md)(模块边界)、[后端 README](README.md)(工程落地)、[MVP进度总账](../../mvp/MVP进度总账.md)(完成度)正交:那三份分别回答"模块各管什么 / 代码怎么摆 / 建到几成",本页只回答"数据长什么样、怎么连"。
> **给谁看**:要动表结构或写跨模块查询的后端工程师、做数据侧 code review 的人、想搞清"一款游戏从一句话到能赚钱,数据在库里怎么流转"的人。
> **怎么读**:第 1 章一张跨域主干图建立全局心智;第 2 章按业务链路下钻看每条链的实体关系;第 3 章是按表速查的索引,从这里跳进具体迁移;第 4、5 章是两处最容易误解的真相 —— 软关联和数据语义。
地面实情以 Flyway 迁移为准。执行权威目录是 `game-cloud/huijing-server/src/main/resources/db/migration/`,共 25 个 `V1``V25``.sql``contracts/db-schemas/` 是跨模块迁移的授权源副本,但只含 18 个(`V1`-`V13` + `V21`-`V25`):单模块的 additive `ALTER`(`V14`-`V20`)不进 contracts,只在执行目录。只读 contracts 会漏掉 `game_aigc_task``level/trace/source/modify` 系列列、`game_source_project` 的并发幂等唯一键、以及 `game_feed_rank``exposure_limit`。每个 `V*.sql` 的头注写满了权属决策、幂等语义、seam 边界和状态机流转,是表级细节的权威源;本页只锚点引用,不抄全文。
迁移覆盖的是 11 个 `game-module-*` 业务模块加 passport 身份层,约 40 张表。蓝图里另列的 `pay`(支付)和 `ip`(素材授权)两个模块当前没有独立的 `game_pay_*` / `game_ip_*` 迁移落地:赋余额走 trade 的 `game_trade_grant`,素材登记走 studio 的 `game_material`,真实支付与版权授权是 M4 之后的事。
---
## 1. 一张图看清全平台数据骨架
把每个模块的主表和它们的跨模块连线画到一张图上,就是平台数据的主干。明细表(报价、进度、弹幕这类)刻意省掉,留给第 2 章下钻。
```mermaid
erDiagram
game_player ||--o{ game_project : "creator_user_id 创作"
game_project ||--o{ game_version : "1:N 版本"
game_project ||--|| game_version : "current_version_id 指针"
game_version ||--|| game_runtime_package : "一版本一包"
game_aigc_task ||--o| game_version : "生成产物回填"
game_source_project }o--|| game_version : "源↔产物解耦"
game_project ||--o{ game_feed_rank : "status=4 入流"
game_runtime_session }o--|| game_telemetry_event : "试玩回灌"
game_telemetry_event }o--|| game_telemetry_game_stat : "聚合"
game_telemetry_game_stat ..> game_project : "回灌 play/like"
game_telemetry_game_stat ..> game_feed_rank : "回灌 quality"
game_ad_revenue }o--|| game_trade_income : "source_ref 分账"
game_trade_income }o--|| game_trade_account : "入账"
game_trade_account ||--o{ game_trade_withdraw : "提现"
game_compliance_gate_result }o--|| game_project : "publish 落门→回 status"
```
主干上能读出五条链:身份层 `game_player` 是所有创作的归属起点;一款游戏 `game_project` 带多个版本 `game_version`,版本编译出一个对外包 `game_runtime_package`;过了合规门、状态变成已发布的游戏进 feed 排序表 `game_feed_rank`;玩家试玩产生的会话被遥测聚合,聚合结果反向回灌游戏热度和排序;广告收入经分账流水入创作者账户,再走提现。下面五张子图把每条链的明细补全。
---
## 2. 按业务链路下钻的实体关系
约 40 张表全画在一起会糊成一团,所以按业务链路拆成五张子图,每张配一段说明。
### 2.1 创作链路(project + aigc + studio + source)
创作链从创作者打开工作台开始。`game_studio_session` 是有状态的创作会话,`createDraft` 时调 project 侧 `createProject` 回写 `game_id`;会话里的每一步生成挂在 `game_studio_taskchain` 上,任务链只持引用、不重建生成状态机 —— 它引一个 `game_aigc_task`,引一个 `game_version`
`game_aigc_task` 是无状态的生成原子,一行就是一次异步生成任务。它的 `prompt_hash` 是产物缓存键,命中就跳过模型重算;`status` 走 0 排队 / 1 运行 / 2 成功 / 3 失败 / 4 超时 / 5 取消,成功时把 `version_id` 回填。生命周期相关的几列是后续 `ALTER` 加的:`V15``level` 是会员档配额维度;`V16``trace_json` 是九门轨迹账本、`readiness_score` 是就绪分;`V17``source` 区分 online / orchestrator / worker 三种任务来源;`V19``modify_mode` / `base_version_id` / `modify_patch` 是"改源不改包"生命周期里 modify、extend 两种操作的载荷,`modify_mode` 为空就是首次 create。
一款游戏 `game_project` 和它的版本 `game_version` 是 1:N,外加一根 `current_version_id` 指针指向当前生效版本。版本表的 `package_url` / `checksum` / `bundle_size` 不在生成时写,而是 runtime 编译成功后回写(权威写者见第 5 章)。
`game_source_project` 是"改源不改包"范式的源侧存储面,和产物面 `game_version` 解耦:它存 `source_json` 源工件,`source_hash` 是可寻址的幂等键,`version_id` 构建成功才回填、失败留 NULL 成为孤儿(`status` 里 2 就是孤儿态)。`base_version_id` 记 modify 的血缘。`V20` 给它加了 `uk_game_source_hash(game_id, source_hash, deleted, tenant_id)` 做并发幂等。
`game_studio_asset` 是会话内的资产槽位 seam,MVP 不做六路并发;它和创作者私有素材库 `game_material` 是两个概念 —— 后者按 `creator_user_id` 归属隔离,`category` 冻结成 sprite / character / effect / scene / ui / music 六类,字节存储归 infra FileApi,表里只登记引用。
游戏的归属专区由 `game_zone` 和关系表 `game_project_zone` 承载,双轨设计:`game_zone.type` 区分 1 官方精选 / 2 UGC 普通,迁移末尾种了两条种子(官方精选、创作广场);`game_project_zone``uk_game_zone(game_id, zone_id)` 表达游戏对专区的多对多归属。
```mermaid
erDiagram
game_studio_session ||--o{ game_studio_taskchain : "session_id"
game_studio_session ||--o{ game_studio_asset : "会话内资产槽"
game_studio_session }o--|| game_project : "createDraft 回写 game_id"
game_studio_taskchain }o--|| game_aigc_task : "aigc_task_id 引用"
game_studio_taskchain }o--|| game_version : "version_id 引用"
game_aigc_task ||--o| game_version : "成功回填 version_id"
game_aigc_task ||--o{ game_aigc_task : "retry_of 重生成血缘"
game_project ||--o{ game_version : "1:N"
game_project ||--|| game_version : "current_version_id 指针"
game_source_project }o--|| game_project : "game_id 源侧"
game_source_project ||--o| game_version : "构建成功回填 version_id"
game_project }o--o{ game_zone : "game_project_zone M:N"
game_material }o--|| game_player : "creator_user_id 私有库"
```
`game_project``play_count` / `like_count` 是 telemetry 回灌的物化字段(口径见第 5 章);`status` 状态机走 0 草稿 / 1 审核中 / 2 通过 / 3 拒绝 / 4 已发布 / 5 下架 / 6 封禁,是创作链的总闸,后面编译、合规、分发都围着它转。
### 2.2 编译发布与合规闸门(runtime + compliance)
aigc 产物交给 runtime 编译。`game_runtime_build` 是编译任务,`status` 走 0 排队 / 1 编译中 / 2 成功 / 3 失败,成功时把 `package_url` / `bundle_size` / `checksum` 回写。编译产出一份对外清单 `game_runtime_package`,一个版本一行(`uk_version`),试玩宿主据它加载游戏:`entry` / `runtime_version` / `preload_policy` / `sandbox_attr` / `allow_origins` 描述 iframe 沙箱;`status` 走 0 预览就绪 / 1 已发布 / 2 已失效,是取包门禁的权威字段,和 `game_version.status` 不是一回事。MVP 没有 OSS,`V10` 给它加的 `package_json LONGTEXT` 让整包内嵌进 DB。
试玩会话 `game_runtime_session` 既是试玩入口,也是遥测回灌的源头。`client_play_token` 做幂等(`uk_play_token`),`scene` 区分 preview / play;它的 `sessionId` 透传进遥测做 `session_id` 对账键,但试玩本身不计 `play_count``V11``player_user_id` 放宽成可空、加了 `anon_id`,匿名玩家也能试玩。
游戏要发布,先过锁风门。`game_compliance_gate_result` 每次 publish 落一条(非幂等),`verdict` 走 pass / review / block,取各原子检查的 max,`details JSON` 存每个原子的明细。内容分级 `game_content_rating` 给出 all / 8+ / 12+ / 16+,`rated_by` 区分门自动评级还是 admin 人工。门的结论回写 `game_project.status`。封禁台账 `game_user_ban``target_type` 区分 1 游戏 / 2 用户,`ban_type` 区分 1 封禁 / 2 降权,`uk_target_active` 保证同一目标至多一条生效封禁。政策正文 `game_policy_document` 存富文本加版本,app 取最新已发布版;申诉 `game_compliance_appeal` 软关联 `game_user_ban`(不建外键),它的 `pending_flag` 是 STORED 生成列,借 NULL 互不冲突的特性实现"每个 (user, ban) 至多一条待处理"的原子去重。
```mermaid
erDiagram
game_aigc_task ||--o| game_runtime_build : "产物触发编译"
game_runtime_build }o--|| game_version : "version_id"
game_runtime_build ||--|| game_runtime_package : "成功产清单"
game_runtime_package ||--|| game_version : "uk_version 一版一行"
game_runtime_session }o--|| game_version : "试玩 version_id"
game_runtime_package ..> game_version : "回写 package_url/checksum"
game_compliance_gate_result }o--|| game_project : "publish 落门"
game_compliance_gate_result ..> game_project : "verdict 回 status"
game_content_rating }o--|| game_version : "uk_game_version 分级"
game_user_ban ..> game_project : "target_id 封游戏"
game_user_ban ..> game_player : "target_id 封用户"
game_compliance_appeal }o--o| game_user_ban : "ban_id 软关联"
```
### 2.3 分发与数据回路(feed + telemetry)
游戏 `status=4` 后进 feed。`game_feed_rank` 是排序缓存兼精选覆盖层:`zone_id` 为 0 是默认混合流、大于 0 是专区;`quality_score` 由 telemetry 回灌、只读不参与计算;`boost` 是运营加权;`sort_score` 降序出流。`V25` 加的 `exposure_limit` 是限曝光降权项,排序公式变成 `sort_score = quality + boost - exposure_limit`,大于等于 9999 就是硬剔除。互动信号走 `game_feed_interact_log`,`action` 区分 1 点赞 / 2 收藏 / 3 分享 / 4 举报,`uk_user_game_action(user_id, anon_id, game_id, action)` 做幂等、`active` 软开关让取消不删行;举报(`action=4`)转交 compliance。
遥测这一侧,`game_telemetry_event` 是原始事件落库,MQ 消费时靠唯一键防重。原来的 `uk_dedup(trace_id, event, ts)` 在同毫秒同名事件上会误吞、重放会重计数,`V10` 把它换成了 `uk_event_id(event_id)` —— `event_id` 是客户端生成的 UUID,逐事件实例全局唯一,才是真正的幂等真身。`process_status` 走 0 待聚合 / 1 已聚合 / 2 失败待重试。聚合结果落 `game_telemetry_game_stat`,按游戏乘日期(`uk_game_date`),它的 `play_count` / `like_count` 回灌 project、`quality_score` 回灌 feed。
回灌是双向闭环:游戏进 feed → 玩家试玩产生会话 → 会话事件进遥测 → 聚合 → 热度和质量分回灌 project 和 feed,排序随之变化、把好游戏推得更靠前。
```mermaid
flowchart LR
P["game_project<br/>status=4"] --> R["game_feed_rank<br/>sort_score 出流"]
R --> S["game_runtime_session<br/>试玩会话"]
S --> E["game_telemetry_event<br/>原始事件"]
E --> G["game_telemetry_game_stat<br/>日聚合"]
G -. "回灌 play/like" .-> P
G -. "回灌 quality" .-> R
IL["game_feed_interact_log<br/>点赞/收藏/分享/举报"] -. "举报转 compliance" .-> R
```
### 2.4 钱财闭环(ad + trade)
广告位 `game_ad_slot` 是配置(对齐第 7 类契约):`slot_id` 业务唯一,`type` 区分 rewarded / interstitial / banner,`provider` 区分 csj / gdt / mock,`ecpm_floor` 是底价(分),`block_minor` / `max_per_session` 是合规约束。每次曝光或激励落一条 `game_ad_revenue`:`creator_user_id``game_id` 反查 project 拿到,`event_type` 区分 1 曝光 / 2 激励,`revenue_amount` 以分计,`settle_status` 走 0 未结算 / 1 已结算,`uk_trace(trace_id, event_type, tenant_id)` 防重。这张表的 `id` 是 ad 到 trade seam 的对账锚点 —— 它就是 `game_trade_income.source_ref`
trade 侧 `game_trade_account` 是创作者收益账户,物化汇总流水,守一条不变式 `balance + frozen + total_withdraw = total_income`,余额行级原子增减。分账后入账走 `game_trade_income`:`source` 区分 1 广告 / 2 打赏,广告的 `source_ref` 就是 `game_ad_revenue.id`,`uk_source(source, source_ref, tenant_id)` 防重复分账,`net_amount = gross × share_rate`,`trace_id` 从 ad 透传过来串起对账链。提现 `game_trade_withdraw``status` 走 0 待审核 / 1 打款中 / 2 已打款 / 3 驳回 / 4 打款失败,`uk_biz_no` 幂等,`V14` 加的 `transfer_ref` 是打款转账单号的对账锚点。
旁支两条都是赋值类:`game_trade_grant` 是 admin 赋余额流水,`source` 固定 `admin_grant`,独立于 income 防止污染营收报表;会员订阅 `game_trade_subscription` 一用户一行(`uk_user`),`plan` 区分月 / 季 / 年,`expire_time` 是判定有效的权威字段、续期叠加,配套的 `game_trade_subscription_grant` 是订阅赋值的幂等账本,`uk_biz_no` 挡任意历史 bizNo 重放(这是 P0 修复:行内 `last_grant_biz_no` 不作幂等判据)。
```mermaid
erDiagram
game_ad_slot ||--o{ game_ad_revenue : "slot_id 计费"
game_ad_revenue ..> game_project : "game_id 反查 creator"
game_ad_revenue }o--|| game_trade_income : "id = source_ref 对账"
game_trade_income }o--|| game_trade_account : "净额入账"
game_trade_account ||--o{ game_trade_withdraw : "提现 uk_biz_no"
game_trade_grant ..> game_trade_account : "admin 赋余额"
game_player ||--|| game_trade_subscription : "uk_user 一用户一行"
game_trade_subscription ||--o{ game_trade_subscription_grant : "uk_biz_no 幂等账本"
```
### 2.5 身份、社区与 B 端(passport + community + biz)
身份层是整张图的归属起点。`game_player` 是玩家兼创作者的最小身份,`id` 就是 OAuth2 token 里的 `userId`(`userType=MEMBER`),`mobile` 是登录主键(`uk_mobile`),`creator_flag` 是创作者白名单位(0 玩家 / 1 创作者,A2 种子名单),`real_name` / `id_card_no` 是 AES 密文预留、提现前才收集,`first_anon_id` 做匿名到登录的归因。邀请码 `game_invite_code` 是报备前的受限激活旁路,`code` 唯一,条件更新原子核销防超发。这两张表物理上不在 `game_*` 迁移里(详见第 4 章)。
社区围着用户和游戏转。`game_community_message` 是站内消息中心,按 `user_id` 归属隔离,`type` 区分系统 / 公告 / 审核 / 收益。创作者等级 `game_community_level`(`uk_creator`)的 `published_count` 在游戏发布通知时原子累加,`last_milestone` 幂等;新人奖励 `game_community_reward`(`uk_creator_milestone`)记触发、发放留 M4。评论 `game_community_comment``target_id``game_project.id`,`parent_id` 预留楼中楼,`report_status` 转 compliance。关注 `game_community_follow`(`uk_user_target`)的 `active` 是软开关。排行 `game_community_rank` 是排行的权威源(DB 是真相、Redis ZSET 只是缓存),`dimension` 区分 game_hot / creator_hot。弹幕 `game_community_danmaku``game_id` 为房间,WS 广播加落库回放。
B 端定制走一条单据链。`game_biz_lead` 是询单主单,`biz_type` 区分品牌 / 文旅 / 通用,`status` 走 0 待跟进 / 1 已报价 / 2 制作中 / 3 待验收 / 4 已交付,`demo_version_id` 指向一个 `game_version`。报价 `game_biz_quote` 挂在 lead 上(报价不等于可支付订单,`quote_no` 由 service 串行递增、DB 不强约束)。进度 `game_biz_progress` 每推进一步落一条,组成看板时间线。验收 `game_biz_acceptance` 是三态交付确认,`signed_offline` 是 M4 前的线下兜底,`uk_lead_version` 防重。
```mermaid
erDiagram
game_player ||--o| game_invite_code : "invite_code_id 核销"
game_player ||--o{ game_community_message : "user_id 消息"
game_player ||--|| game_community_level : "uk_creator 等级"
game_community_level ||--o{ game_community_reward : "uk_creator_milestone"
game_player ||--o{ game_community_comment : "user_id 评论"
game_community_comment ..> game_project : "target_id 指游戏"
game_player ||--o{ game_community_follow : "uk_user_target 关注"
game_player ||--o{ game_community_danmaku : "弹幕"
game_player ||--o{ game_biz_lead : "customer/owner 询单"
game_biz_lead ||--o{ game_biz_quote : "lead_id 报价"
game_biz_lead ||--o{ game_biz_progress : "lead_id 进度"
game_biz_lead ||--o{ game_biz_acceptance : "lead_id 验收"
```
---
## 3. 全平台主表总清单
按表索引,从这里跳进具体迁移看逐列细节。
| 表名 | 模块 | 核心职责 | 主键 / 关键唯一键 | 迁移锚点 |
|---|---|---|---|---|
| game_project | project | 游戏主表 / Game 实体,创作链总闸 | id(=GamePackage.gameId) | V1 L14 |
| game_version | project | 版本表,编译产物回写落点 | id(=GamePackage.versionId) | V1 L44 |
| game_review_record | project | 审核记录,锁风门二态 | game_id / version_id | V1 L66 |
| game_zone | project | 双轨专区(官方精选 / UGC) | type;末尾种 2 条 | V1 L87 |
| game_project_zone | project | 游戏↔专区 M:N | uk_game_zone(game_id,zone_id) | V1 L106 |
| game_aigc_task | aigc | 无状态生成原子 / 异步队列 | id(=taskId);prompt_hash 缓存键 | V2 L23;V15/16/17/19 ALTER |
| game_runtime_build | runtime | 编译任务 | game_id / version_id | V3 L23 |
| game_runtime_package | runtime | 对外清单,试玩宿主据此加载 | uk_version(一版一行) | V3 L56;V10 ALTER |
| game_runtime_session | runtime | 试玩会话,遥测回灌源 | uk_play_token | V3 L90;V11 ALTER |
| game_feed_rank | feed | 排序缓存 / 精选覆盖层 | uk_game_zone(game_id,zone_id) | V4 L19;V25 ALTER |
| game_feed_interact_log | feed | 互动信号幂等流水 | uk_user_game_action | V4 L47 |
| game_telemetry_event | telemetry | 原始事件,MQ 消费幂等落库 | uk_event_id(event_id) | V5 L16;V10 替换 uk |
| game_telemetry_game_stat | telemetry | 游戏×日聚合 | uk_game_date(game_id,stat_date) | V5 L51 |
| game_ad_slot | ad | 广告位配置(契约#7) | uk_slot(slot_id) | V6 L21 |
| game_ad_revenue | ad | 广告收入台账,ad↔trade seam | uk_trace(trace_id,event_type,tenant_id) | V6 L54 |
| game_trade_account | trade | 创作者收益账户,流水物化汇总 | uk_user(user_id,tenant_id) | V7 L32 |
| game_trade_income | trade | 分账后入账流水 | uk_source(source,source_ref,tenant_id) | V7 L59 |
| game_trade_withdraw | trade | 提现申请 | uk_biz_no | V7 L93;V14 ALTER |
| game_trade_grant | trade | admin 赋余额流水 | uk_biz_no | V21 L27 |
| game_trade_subscription | trade | 会员订阅,一用户一行 | uk_user(user_id,tenant_id) | V21 L60 |
| game_trade_subscription_grant | trade | 订阅赋值幂等账本 | uk_biz_no | V21 L95 |
| game_studio_session | studio | 有状态创作工作台 | creator_user_id / game_id | V8 L24 |
| game_studio_taskchain | studio | 创作任务链(只持引用) | session_id → session | V8 L52 |
| game_studio_asset | studio | 会话内资产槽位 seam | session_id | V8 L78 |
| game_source_project | studio | 源项目工件,源侧存储面 | uk_game_source_hash | V18 L28;V20 ALTER |
| game_material | studio | 创作者私有素材库(六类) | creator_user_id 隔离 | V24 L22 |
| game_compliance_gate_result | compliance | 锁风门评估台账(非幂等) | game_id / version_id | V9 L22 |
| game_content_rating | compliance | 内容分级 | uk_game_version | V9 L45 |
| game_user_ban | compliance | 封禁台账 | uk_target_active | V9 L68 |
| game_policy_document | compliance | 政策正文(富文本+版本) | uk_type_version | V22 L23 |
| game_compliance_appeal | compliance | 申诉台账,生成列原子去重 | uk_user_ban_pending | V22 L56 |
| game_community_message | community | 站内消息中心 | user_id 隔离 | V12 L23 |
| game_community_level | community | 创作者等级 | uk_creator | V12 L47 |
| game_community_reward | community | 新人奖励触发记录 | uk_creator_milestone | V12 L68 |
| game_community_comment | community | 评论(预留楼中楼) | target_id → project | V23 L25 |
| game_community_follow | community | 关注(active 软开关) | uk_user_target | V23 L49 |
| game_community_rank | community | 排行权威源(DB=真相) | uk_dimension_target | V23 L72 |
| game_community_danmaku | community | 弹幕(WS 广播+落库回放) | game_id 房间 | V23 L93 |
| game_biz_lead | biz | B/G 端定制询单主单 | customer/owner_user_id | V13 L26 |
| game_biz_quote | biz | 报价(非可支付订单) | lead_id → lead | V13 L55 |
| game_biz_progress | biz | 进度工单(看板时间线) | lead_id | V13 L77 |
| game_biz_acceptance | biz | 交付验收(三态) | uk_lead_version | V13 L98 |
| game_player | passport | 玩家 / 创作者最小身份 | id(=OAuth2 userId);uk_mobile | V11 L25(寄宿 system) |
| game_invite_code | passport | 邀请码(报备前激活旁路) | uk_code | V11 L58 |
### 模块归属一图
```mermaid
flowchart TB
subgraph P[project · 5 表]
p1[game_project] --- p2[game_version] --- p3[game_review_record]
p4[game_zone] --- p5[game_project_zone]
end
subgraph G[aigc · 1 表]
g1[game_aigc_task]
end
subgraph RT[runtime · 3 表]
r1[game_runtime_build] --- r2[game_runtime_package] --- r3[game_runtime_session]
end
subgraph FD[feed · 2 表]
f1[game_feed_rank] --- f2[game_feed_interact_log]
end
subgraph TM[telemetry · 2 表]
t1[game_telemetry_event] --- t2[game_telemetry_game_stat]
end
subgraph AD[ad · 2 表]
a1[game_ad_slot] --- a2[game_ad_revenue]
end
subgraph TR[trade · 6 表]
tr1[account] --- tr2[income] --- tr3[withdraw]
tr4[grant] --- tr5[subscription] --- tr6[sub_grant]
end
subgraph ST[studio · 5 表]
s1[session] --- s2[taskchain] --- s3[asset]
s4[source_project] --- s5[material]
end
subgraph CP[compliance · 5 表]
c1[gate_result] --- c2[content_rating] --- c3[user_ban]
c4[policy_document] --- c5[appeal]
end
subgraph CM[community · 7 表]
m1[message] --- m2[level] --- m3[reward] --- m4[comment]
m5[follow] --- m6[rank] --- m7[danmaku]
end
subgraph BZ[biz · 4 表]
b1[lead] --- b2[quote] --- b3[progress] --- b4[acceptance]
end
subgraph PP[passport · 2 表 · 寄宿 system]
pl1[game_player] --- pl2[game_invite_code]
end
```
---
## 4. 跨表关系为什么没有外键
上面所有的连线在代码里都没有物理外键,全是逻辑软关联。这是单体内有意的解耦:模块之间只经各自的 `-api` 包用 DTO、Feign 软关联,归属隔离靠 Service 的可信边界 —— Mapper 强制带 `getLoginUserId()` 谓词,或走 DataPermission —— 而不是靠 DB 约束。依赖方向在 [13模块.md](../架构/13模块.md) 有权威图。这么做的代价是数据一致性要 Service 自己保,收益是模块能各自演进、后续拆独立仓时不被外键绊住。
所有 `game_*` 表里的 `creator_user_id` / `user_id` / `player_user_id`,数值上都等于 `game_player.id`,也就是 OAuth2 的 `userId`,但代码层一根外键都没有。
`game_player``game_invite_code` 这两张身份表有个容易误解的地方:它们由 `V11.0.0__create_passport_player_invite.sql` 建,但物理归属不在任何 `game_*` 业务模块,而在 `huijing-module-system``PlayerDO``@TableName("game_player")` 映射,锚点在 `game-cloud/huijing-module-system/huijing-module-system-server/src/main/java/com/wanxiang/huijing/module/system/dal/dataobject/passport/PlayerDO.java`(`id``userId``userType=MEMBER`)。13 模块都引这张表、但它不在 13 模块清单里。
admin 和运营侧的用户是另一套人,走 Huijing 原生的 `system_users`,不走 `game_player``ProjectServiceImpl``AdminUserApi` 就是这条线(锚点 `game-module-project-server/.../ProjectServiceImpl.java`)。一句话:C 端玩家创作者用 `game_player`,B 端管理员用 `system_users`,两套身份在数值空间和代码路径上都分开。
---
## 5. 必须知道的数据语义
几处分散在迁移头注里的语义,踩错会出真 bug,集中讲清。
两个 `quality_score` 口径不能互灌。aigc 的 `game_aigc_task.quality_score``DECIMAL(4,3)`、取值 0 到 1,衡量生成质量(模型把游戏做得好不好);telemetry 和 feed 的 `quality_score` 是 0 到 100,衡量运营质量(玩家玩得好不好、留不留存)。多个迁移头注都显式警告过这一点。把生成质量分灌进运营排序、或反过来,都是错的。
`play_count` 有三处口径,只有一处是权威。`game_runtime_session` 的试玩不计 play_count;`game_telemetry_game_stat` 的聚合才是权威源;`game_project.play_count` 是从聚合回灌过来的物化字段,读它没问题,但它的真相在 telemetry 聚合那里。
源和产物是两个存储面。`game_source_project` 存源(`source_json`),`game_version` 存产物(`package_url` 指向的包)。"改源不改包"指改了源要重新构建才生成新产物,不是直接动包。两个面解耦,源构建失败时会留下孤儿源(`game_source_project.status=2`)而不污染产物面。
`package_url` / `checksum` / `bundle_size` 的权威写者是 runtime(决策5)。生成时这几列是空的,runtime 编译成功后才回写 `game_version`。别在 aigc 或 studio 侧抢着写这几列。
全平台的共性范式统一交代一次,后面读任何一张表都按这套理解。每张表都有 Yudao/Huijing 6 审计列:`creator` / `create_time` / `updater` / `update_time` / `deleted BIT(1)` / `tenant_id BIGINT`(MVP 单租户恒为 0)—— 注意业务归属列 `creator_user_id` / `user_id` 和审计列 `creator` 是两回事,前者是业务上谁拥有这条数据,后者是技术上谁创建了这行。金额一律用 `BIGINT` 以"分"计、禁浮点(ad 和 trade 全线),比例才用 `DECIMAL`。状态机一律 `TINYINT`,非法流转由 Service 校验、DO 层不承载。幂等键有四种范式:资金、计费、邀请用 `uk_biz_no``uk(trace_id + type)``uk_source`;互动、关注用唯一键加 `active` 软开关,取消时翻 `active` 不删行。逻辑删除维度纳入唯一键(`…, deleted, tenant_id`)是 Huijing 惯例,这样软删后同业务键可以重新插入。
---
## 6. 维护与边界声明
本页是数据结构 SoT 的"总图"层,只画表和表怎么连。表的逐列细节以迁移头注为准,完成度以 [MVP进度总账](../../mvp/MVP进度总账.md) §2 为准,模块边界以 [13模块.md](../架构/13模块.md) 为准,工程落地以 [后端 README](README.md) 为准。
迁移只增不改。新增表或列时,回这里补对应的节点和说明 —— 主表进第 1 章主干图、明细进第 2 章对应链路子图、所有表进第 3 章清单,并锚点到新迁移。
---
## 7. 待落地的数据契约缺口(contract-first 清单)
下面这几条都是设计已经讲清、但库里还没改的硬缺口或待建表。它们的共同点是改动横跨契约文件、Flyway 迁移和 `-api` 镜像,属于 contract-first 动作而非顺手加一列,所以集中登记成待落地清单,别因为这里写了就当已建。本节内容均为**创始人 2026-06-22 历史回收判定捡回**。
### 7.1 `game_trade_income` 缺 `game_id`:按游戏算钱的连接键断在这里
入账流水现在只有 `source` / `source_ref` / `gross_amount` / `share_rate` / `net_amount`,能顺着 `source_ref` 串到广告单或打赏单,却串不到具体哪款游戏。结果是两件事同时被卡:一是按游戏粒度算单位经济(哪款游戏赚了多少、成本回不回得来),二是收益回流语料需要的 join 键(把"线上赚了多少钱"这个结果标签接到某款游戏的源工件上,见 [生成引擎/数据飞轮.md](../架构/生成引擎/数据飞轮.md) §3.3)。`game_ad_revenue` 这一侧本来就带 `game_id`(它靠 game_id 反查创作者),缺口只在 trade 这一段没把它透传下来、也没落列。
补法是一条四步链,缺一即断:广告台账查询接口 `AdRevenueApi.getUnsettledRevenue` 返回的 `AdRevenueRespDTO` 先加上 `gameId` 字段;trade 的 `SettlementJob` 拿到后透传进 `IncomeService.recordIncome`,写入新增的 `game_id` 列;打赏线的 `recordIncome` 同样把打赏单所属游戏的 gameId 带进来。这条链在变现侧的工程口径(W4 ③④、防重计数硬约束、为什么否决"往 telemetry 加列")已写在 [运营/变现与单位经济.md](../运营/变现与单位经济.md) §6 待办首条,本页只补它在数据模型侧的列与镜像清单,两处一并维护、别让缺口口径漂移。
contract-first 待落地:
- 契约 `contracts/api-schemas/ad.yaml` —— `AdRevenueRespDTO`(`getUnsettledRevenue` 的 itemType)加 `gameId: Long`,语义"产生这笔收入的游戏 ID,供 trade 落 game_trade_income.game_id 做按游戏归因"。
- 契约 `contracts/api-schemas/trade.yaml` —— `IncomeRespVO``gameId`;`recordIncome` 的入参说明补 gameId 透传。
- Flyway 新迁移(版本号按落地时仓内最新顺延,现仓已到 V25)—— `game_trade_income` `ADD COLUMN game_id BIGINT NULL`(nullable,对存量行无影响、向后兼容),加普通索引 `idx_game_id` 供按游戏聚合。
- Java 镜像 —— `IncomeDO``gameId` 字段、`IncomeRespVO``gameId``recordIncome` 签名带 gameId 透传;`AdRevenueRespDTO` 同步加。
- 注意分账幂等键 `uk_source(source, source_ref, tenant_id)` 不动:game_id 是归因维度、不进幂等键。
### 7.2 `game_telemetry_game_stat` 缺留存字段:先定口径再加聚合列
聚合表现在只有 play / end / complete / duration / like / favorite 这几个量,拿不出"次留 / N 日留存"。这同样卡着收益回流语料(没有留存标签就训不出"什么样的游戏留得住人")和未来推荐排序里的 freshness / 留存信号。它和 §7.1 同源但落点不同:§7.1 缺的是 trade 侧的连接键,这条缺的是 telemetry 侧的留存口径。
这条不是单纯加列,得**先定义留存口径再加聚合字段**——留存按设备(anonId)还是按登录用户算、次留的窗口怎么切(自然日还是 24 小时滚动)、回访事件以哪个埋点为准,这些定下来之前不要急着 ALTER。口径定了之后,聚合字段才好确定是落"次留人数 / 分母人数"两列由读侧算率,还是直接物化一个留存率。
contract-first 待落地:
- 先产出留存口径定义(归到遥测域,可在本页或观测体系档补一段口径说明),作为加列的前置。
- 契约 `contracts/events.schema.json` —— 若现有埋点不足以判定回访,可能要新增或明确一个回访事件;先核对 `game_play_start` 能否承载,能则不新增事件。
- Flyway 新迁移 —— `game_telemetry_game_stat` 按定好的口径 `ADD COLUMN`(如 `retained_uv` / 留存分母,均 nullable)。
- Java 镜像 —— 聚合 Service 补留存计算、`game_telemetry_game_stat` 对应 DO 加字段。
### 7.3 锁风三档裁决落点:MVP 写 `details` JSON,不改表
锁风三档(标准 / 严格 / 人工复核)的裁决要能回答"这条内容为什么走了严格档",落点是现有的 `game_compliance_gate_result` 台账。但 `GateResultDO` 现在只有 `id` / `game_id` / `version_id` / `verdict` / `rating` / `details` / `trace_id`,没有 `lock_strength``ip_id` 列。MVP 阶段的取舍是**写进已有的 `details` JSON 列**——`details` 本来就存各原子明细数组 `[{atom,verdict,reason}]`,给它再加 `lockStrength``ipId` 两个键即可,不改表、不加迁移,溯源时从 JSON 里读。这条选 JSON 而非升列的理由是:MVP 还没有"按档位聚合审核量做报表"的需求,软扩字段够用且零迁移成本。完整设计在 [运营/审核台运营.md](../运营/审核台运营.md) §3.3,本页只登记它在数据模型侧的落点结论。
只有在确有按档位做报表(需要 SQL `WHERE lock_strength=…` / `GROUP BY`)的需求时,才 contract-first 升列:`game_compliance_gate_result` `ADD COLUMN lock_strength VARCHAR / ip_id BIGINT`(均 nullable、向后兼容),`GateResultDO` 加两字段,Mapper 不动(MyBatis-Plus 自动映射)。升列迁移的版本号要和 §7.4 的信用台账迁移错开,别撞同一个版本号。
### 7.4 创作者信用:独立扣分台账,不在等级表加列
创作者信用要把单次违规处置转成对人的长期约束(信用低 → 审核自动拉严档 + feed 基线曝光降低)。它的宿主候选是 community 的等级引擎 `game_community_level`,但那张表只有 `level` / `published_count` / `last_milestone`,没有 `credit_score`,`CommunityNotifyApi` 也没有扣分端点。创始人 2026-06-22 定的落点是**独立扣分台账,而不是在等级表上加一个 `credit_score` 列**——每次扣分记一条流水(谁、因为哪次处置、扣多少、什么时候),信用分由台账聚合得出。选台账而非加列,是为了让信用变化可追溯、可审计、可对账,也便于将来做误伤申诉时逐条回放;一个汇总列做不到这些。
contract-first 待落地:
- 新建 Flyway 迁移建表 `game_community_credit_ledger`(继承 TenantBaseDO 拿 6 审计列),关键字段:`creator_user_id`(归属)、`delta`(本次扣分,负值)、`reason_type`(对应哪类处置:降权 / 下架 / 封禁)、`source_ref`(关联的处置单 / ban id)、`biz_no`(幂等键)、`trace_id`(对账)。当前信用分 = 该创作者全部 delta 之和(或物化一个汇总,但权威源是台账)。
- 契约 `contracts/api-schemas/community.yaml` —— `CommunityNotifyApi` 现有 4 方法(generate-done / review-result / income-changed / project-published),为它加第 5 个扣分端点 `deduct-credit`,**带幂等键 `bizNo`**(同 community 既有 `(userId,type,bizRef)` 去重范式),供 compliance 在处置时同进程 @Primary 调用。
- 跨模块依赖 —— compliance-server 调 community 的 `-api` 扣分,扣分失败不回滚处置主操作(扣分是处置的附带副作用),走幂等补偿。
- 注意迁移版本号与 §7.3 锁风升列(若升)错开。
- 完整设计与误伤治理在 [运营/审核台运营.md](../运营/审核台运营.md) §3.5,本页只登记数据落点。
### 7.5 封号级联:数据语义上的两条断链
封一个创作者账号现在只写 `game_user_ban` 台账,既不置 `game_player.status=DISABLE`,也不遍历下架其全部已发布游戏,而创作准入的 `validateCreator` 又不查 ban 表。结果是封了号拦不住本人继续创作、其旧内容仍在 feed 里跑——一个真实的越权 / 绕管缺陷。这条横跨鉴权域(准入)和合规域(处置),两边都容易以为对方管而都漏。数据侧要补两条断链:封号时同步把 `game_player.status``DISABLE`(走 passport `-api`,复用既有禁用态判定)、按 `creator_id` 查其全部已发布游戏批量走 `offlineRank` 下架。准入侧的 `validateCreator` 改法详见 [鉴权与权限.md](鉴权与权限.md) §5,本页只标这条级联在数据上动了 `game_player.status` 与 feed 出流两处状态,不新建表。
### 7.6 绘豆:平台类积分体系的立位(待单独设计)
历史上 D3 业务决策里有一套积分体系 `point_rate=10`(1 元 = 10 积分,打赏走积分计入创作者收益)。这套旧口径**不照搬**——创始人 2026-06-22 提出新设计「绘豆」,是平台侧的类积分体系,取代旧 `point_rate=10` 的定价口径。绘豆的具体形态(用途是打赏 / 生成额度 / 会员权益、与档位和订阅漏斗怎么挂、单价怎么定、要不要落独立账本表与幂等扣减)目前**待单独设计**,这里只占一个位:trade 现有的 TIP(打赏)入账入口是真的,但"积分 / 绘豆"这层产品形态尚未落库,设计成形前不要在 trade 或别处提前建绘豆相关的表或枚举。绘豆与预算即产品(档位决定生成级别与额度)、订阅漏斗一脉,设计时一并考虑。