- 注册表: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 -- <路径>)
36 KiB
topic, canonical, date
| topic | canonical | date |
|---|---|---|
| 数据模型 | true | 2026-06-22 |
后端域 · 全平台数据模型总图
这是什么:数据视角的结构总图 —— 全平台落库了哪些表、表与表之间怎么连、有哪些必须先知道才不会踩坑的数据语义。它与 13模块.md(模块边界)、后端 README(工程落地)、MVP进度总账(完成度)正交:那三份分别回答"模块各管什么 / 代码怎么摆 / 建到几成",本页只回答"数据长什么样、怎么连"。 给谁看:要动表结构或写跨模块查询的后端工程师、做数据侧 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 章下钻。
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) 表达游戏对专区的多对多归属。
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) 至多一条待处理"的原子去重。
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,排序随之变化、把好游戏推得更靠前。
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 不作幂等判据)。
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 防重。
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 |
模块归属一图
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 有权威图。这么做的代价是数据一致性要 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进度总账 §2 为准,模块边界以 13模块.md 为准,工程落地以 后端 README 为准。
迁移只增不改。新增表或列时,回这里补对应的节点和说明 —— 主表进第 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 §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 §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_incomeADD 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 §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 §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 §5,本页只标这条级联在数据上动了 game_player.status 与 feed 出流两处状态,不新建表。
7.6 绘豆:平台类积分体系的立位(待单独设计)
历史上 D3 业务决策里有一套积分体系 point_rate=10(1 元 = 10 积分,打赏走积分计入创作者收益)。这套旧口径不照搬——创始人 2026-06-22 提出新设计「绘豆」,是平台侧的类积分体系,取代旧 point_rate=10 的定价口径。绘豆的具体形态(用途是打赏 / 生成额度 / 会员权益、与档位和订阅漏斗怎么挂、单价怎么定、要不要落独立账本表与幂等扣减)目前待单独设计,这里只占一个位:trade 现有的 TIP(打赏)入账入口是真的,但"积分 / 绘豆"这层产品形态尚未落库,设计成形前不要在 trade 或别处提前建绘豆相关的表或枚举。绘豆与预算即产品(档位决定生成级别与额度)、订阅漏斗一脉,设计时一并考虑。