- 注册表: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 -- <路径>)
34 KiB
date, topic, status
| date | topic | status |
|---|---|---|
| 2026-06-22 | 后端图说——绘境AI 架构图集(按领域拆分·04) | 现行为主 · 每图具体映射源档见该图脚注 / 内联小注 · 通用图例见 00-系统总览图说 |
04 · 后端图说
本篇是绘境AI 架构图集的一部分(全 8 篇:00 系统总览 + 01–07 各域)。通用图例、状态码、按角色读路径见 00 · 系统总览图说。 本域命门(最该据图 review 的设计缝):DB 镜像漂移;桩与真分清;全图无物理外键。
本域讲什么故事:后端域回答服务实现视角的几件事——13 个业务模块在工程上怎么摆(两段式 -api / -server、包名自动套端前缀、错误码分段)、全平台数据长什么样、怎么跨域连、以及「谁能做什么」这条鉴权约束在代码里如何强制。它的八张图里,图 5 是全仓唯一的 ER 权威(据 Flyway V1–V25 真实落库的约 40 张表画出五条业务链),图 6 是五条业务链路的跨模块调用,图 7 是鉴权双模型。读完这个域,你知道「这套后端结构是不是想要的那个」。
最该据图 review 的命门缝:后端域八张图整体都是「现」(已落代码、已 e2e 验证),但现行结构里仍有几处桩与缝必须照实看清——图 5 的 contracts DB 镜像已漂移(只读 contracts 会漏掉 aigc 的 level/trace 列、source_project 的并发幂等唯一键、feed 的 exposure_limit)、图 6 的 compliance 检测原子恒返回 pass / feed 游标分页是桩 / 真广告联盟 SDK 是桩(这些缺口的共同特点是逻辑已建、卡在不可压缩的日历闸门或 W-G1 质量门)、图 7 的网关双重校验是未来态而非现状。还有一条贯穿全域的设计意图最该让评审看出:全图无物理外键(归属靠 Service 可信边界的 eq 谓词、不靠 DB 约束),以及源(game_source_project)与产物(game_version)两个存储面解耦——「改源不改包」范式在数据层的落点。
本域阅读约定与同步纪律:本图说不另立设计,只把后端域四份 canonical 设计档画出来——工程落地(分层、两段式、端前缀、错误码、单体形态)出自
后端/README.md,全平台数据模型出自数据模型.md,鉴权与权限出自鉴权与权限.md,模块边界与业务链路另引架构域的13模块.md。frontmatter 记了这四份的当前 commit hash 作为防漂移门。设计一变动,本图说与对应 SVG 必须同步更新。本域状态分布:后端域八张图全部是「现」(现行已建)——它们画的都是已落代码、已 e2e 验证过的工程结构与数据形态,不存在「设计已出待接线」或「远期目标态」的图。但「现行已建的结构」里仍有几处设计缝与桩必须照实标出:contracts 的 DB 镜像已漂移、compliance 检测原子恒返回 pass、feed 游标分页是桩、网关双重校验是未来态而非现状——这些图上都用文字或虚线如实点出,绝不因为整体是「现」就把桩画成真。
本域徽章(主要关心其中三个):数据(
#475569):跨表软关联而非物理外键、两个 quality_score 口径不互灌、play_count 三处口径只有一处权威、金额一律 BIGINT 以分计——这是图 5 / 图 6 的主轴。安全(#dc2626):权限只在后端可信边界强制、B 端 RBAC 与 C 端归属隔离两套并存、创作白名单必须在后端、mock 后门生产关死——这是图 7 / 图 8 的主轴。可靠(#16a34a):幂等键四范式、资金守恒不变式、统一发布编排的跨表原子事务、契约与实现解耦让模块独立演进。后端域图说里,虚线主要承担「软关联」与「异步回灌」两层语义:图 5 的跨表连线全是逻辑软关联(代码里一根物理外键都没有),用虚线画;图 6 的飞轮回路是异步回灌,用绿虚线画,与同步主调的实线区分开。红色虚线框另外标「越界红线」。
后端图 1 · 后端分层与依赖(实现视角)〔引·现〕
这张图回答「后端是什么形状」。它把后端的实现视角浓缩成三层:最下面是 Huijing fork 提供的基座(system 用户/权限/OAuth2、infra 文件/任务/日志、bpm 工作流,以及 pay——pay 是 Huijing 原生模块,MVP 阶段还没接入这套单体),中间是 13 个 game 业务模块(本域主体),每个模块再拆成 -api / -server 两段式工程结构。
flowchart TB
subgraph HJ["Huijing fork — 基座(开箱即用,不属于业务)"]
SYS["system<br/>用户 / 权限 / OAuth2"]
INFRA["infra<br/>文件 / 任务 / 日志"]
BPM["bpm 工作流"]
PAYN["pay(huijing 原生)<br/>当前未接入单体"]
end
subgraph BIZ["13 个 game 业务模块 — 本域主体"]
direction LR
STU["studio 创作编排"]
AGC["aigc 生成原子"]
RT["runtime 编译/发布"]
PRJ["project 生命周期"]
FED["feed 游戏流"]
TEL["telemetry 遥测"]
TRD["trade 分账结算"]
CMU["community 社区"]
IP["ip 素材/授权<br/>(seam 寄宿 compliance)"]
CMP["compliance 安全/审核"]
BIZM["biz B端定制"]
AD["ad 广告"]
end
subgraph PKG["每个模块的两段式工程结构"]
API["-api 包<br/>枚举 / 错误码段 / 跨模块 DTO·Feign"]
SRV["-server 包<br/>controller / service / dal / convert"]
end
BIZ --> HJ
STU --> PKG
AGC --> PKG
RT --> PKG
note["全部以 jar 聚合进 huijing-server 单体运行<br/>包名统一 com.wanxiang.huijing.game.module.{模块}.{层}"]
PKG -.-> note
(图源:后端/README.md §1)
读这张图要抓住两个后端实现上的特殊归属,README 在图注里专门点了出来:其一,pay 单独画在基座里,因为它是 Huijing 原生模块、当前未接入单体;其二,ip 不独立建模块,而是作为一条 seam(接缝/横切能力)寄宿在 compliance 里。这两点不是疏漏,是有意的工程取舍,后面图 5 的数据视角会再次印证——库里确实没有独立的 game_pay_* / game_ip_* 迁移落地。
这张图的边界声明很清晰:后端域只回答「模块在工程上怎么实现(HOW)」,不回答「每个模块各管什么领域、彼此怎么依赖(WHAT)」——那是架构域的职责,在第三篇 13 模块图里。两者各守一摊、互不重复,这样无论改架构还是改代码,都只有一处需要更新。所以这张图刻意薄;要看 13 模块各自的职责边界与依赖方向,去架构篇,本图说不重画一份会过期的副本。
后端图 2 · 两段式模块:-api 契约面 / -server 实现面〔SVG新·类·现〕
这张图把图 1 里「每个模块的两段式工程结构」放大讲清。它回答的是后端低耦合到底靠什么落地——答案是一道很硬的边界:每个 game 模块都拆成 -api 和 -server 两个 Maven 子模块,跨模块只准依赖对方的 -api,绝不许直接碰 -server。
图左半边是单个模块的剖面(以 project 为范例,所有模块同构)。-api 是契约面,只声明、不实现:里面放枚举(状态机取值、业务常量)、错误码常量(本模块独占段,见图 4)、跨模块 DTO(模块间传输对象)、以及 Feign 接口或 @Primary 本地 bean(远程调用声明——单体内同进程直调,拆微服务才换真 Feign)。-server 是实现面,放真正干活的代码:controller(app/admin 双端)、service(业务逻辑、@Transactional 只加这层、归属校验在此强制)、dal(DO + Mapper)、convert(DO↔VO↔DTO 转换)。
图右半边讲清这道边界为什么值钱。当 studio 这样的上游要调 project / aigc / runtime 时,它的 pom 只声明依赖各模块的 -api,编译期就根本拿不到对方的实现类——越界在构建期就被挡死,而不是靠人自觉。好处是任一模块都能独立演进、独立测试,后续从单体拆成独立仓时不被实现耦合绊住:契约稳定、实现可换。图里特意用红色虚线框标出「这些模块各自的 -server 对 studio 不可见」,强调依赖图里根本不存在指向 -server 的边——这条红线画出来是为了让人一眼记住禁止什么。
要补充三个工程上的硬约束,图右侧也钉住了:DTO/VO/DO 三层语义不可混用(DTO 在 -api 跨模块共享、VO 在 -server controller 面向前端、DO 在 -server dal 映射表,错位会出问题);契约不放后端(API/DB/SDK/event 等 8 类跨端契约的单一事实源是仓根的 contracts/,后端只引用、不另立,DB schema 的 Flyway 执行副本要和 contracts 源 diff 一致);双包同名 controller 必须显式 bean 名(controller.admin 与 controller.app 下同简单类名的控制器,裸 @RestController 会 bean 名相撞、整个 context 启动失败,这是踩过两次的坑)。物理形态上,13 模块是逻辑划分,全部以 jar 聚合进 huijing-server 单体进程运行;Nacos/RocketMQ/Sentinel 已于 2026-07-01 反转为 MVP 生产基建(自托管 mini-infra,接入实现进行中)。
后端图 3 · 包名 → 端前缀自动生效〔Mer新·流·现〕
这张图讲一个很容易被当成「魔法」的机制:app 控制器和 admin 控制器的代码里都不写 /app-api、/admin-api 前缀,但请求进来却能各自落到对的端上。它的原理不是魔法,而是 Huijing 框架按包路径通配:控制器放在 controller.app.* 包下,框架自动给它套 /app-api 前缀;放在 controller.admin.* 包下,套 /admin-api。这条规则的前提,正是图 2 里那条统一包名约定——所有业务代码都在 com.wanxiang.huijing.game.module.{模块}.{层} 之下,框架才能据此通配。
flowchart LR
DEV["控制器源码<br/>@RequestMapping('/project')<br/>(不写端前缀)"]:::src
subgraph SCAN["Huijing 框架按包路径通配"]
direction TB
APP{"包在<br/>controller.app.*<br/>之下?"}
ADM{"包在<br/>controller.admin.*<br/>之下?"}
end
DEV --> APP
DEV --> ADM
APP -->|是| PAPP["自动套 /app-api<br/>→ /app-api/project"]:::app
ADM -->|是| PADM["自动套 /admin-api<br/>→ /admin-api/project"]:::adm
PAPP --> UT1["URL 前缀反推 userType<br/>/app-api → MEMBER (C 端)"]:::note
PADM --> UT2["URL 前缀反推 userType<br/>/admin-api → ADMIN (B 端)"]:::note
classDef src fill:#eff6ff,stroke:#2563eb,stroke-width:2px;
classDef app fill:#f5f3ff,stroke:#7c3aed,stroke-width:1.5px;
classDef adm fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px;
classDef note fill:#f1f5f9,stroke:#475569,stroke-width:1.2px;
这张图最该让人看出的,是它和鉴权的衔接——端前缀不只是路由,还反推用户类型。框架据 URL 前缀实时推导 userType:/app-api 推出 MEMBER(C 端创作者+玩家),/admin-api 推出 ADMIN(B 端运营+管理员)。userType 不在登录时记死、而是按前缀实时算,这正是图 7 鉴权双模型的入口:一个 C 端 token 去打 /admin-api,会因为「token 携带的 userType 与 URL 推导出的不一致」在 TokenAuthenticationFilter 那一步就被拒。所以这张看似只讲路由的图,其实是鉴权链路的第一段。它没有现行/远期的灰度,就是当前真相。
后端图 4 · 错误码分段:各模块独占一段〔Mer新·讲·现〕
这张图回答「全平台的错误码怎么不打架」。规则很简单:错误码格式是 1-{模块段}-{业务}-{细分},每个模块独占一段、互不重叠,新增模块在自己 -api 的错误码常量类里登记自己那一段。这样任意两个模块各自演进、各自加错误码,都不会撞号。
flowchart TB
FMT["错误码格式:1-{模块段}-{业务}-{细分}<br/>各模块独占一段,禁止冲突"]:::fmt
subgraph BASE["Huijing 基座段(源档明确给出)"]
direction LR
B1["1-001 system"]
B2["1-002 infra"]
end
subgraph GAME["13 game 业务模块段"]
direction LR
G1["1-100 project"]
G2["1-101 aigc"]
G3["1-102 runtime"]
G4["1-103 feed"]
G5["1-104 telemetry"]
end
subgraph INFER["顺延分配(源档未逐一给出·按『每模块一段顺延』推断)"]
direction LR
I1["1-105 pay"]
I2["1-106 trade"]
I3["1-107 community"]
I4["1-108 ip"]
I5["1-109 compliance"]
I6["1-110 biz"]
I7["1-111 ad"]
end
FMT --> BASE
FMT --> GAME
FMT --> INFER
classDef fmt fill:#fefce8,stroke:#ca8a04,stroke-width:2px;
读这张图要注意一处诚实标注:源档只明确给出了 system / infra / project / aigc / runtime / feed 六段(图上前两组),其余模块(pay/trade/community/ip/compliance/biz/ad)的段号是按「每模块一段顺延」规则推断分配的(图上第三组单独框出、标「顺延·推断」),不是源档逐一写死的。这处推断照实标,是为了不把推论当成已核实的事实——真要给某模块定错误码段,以该模块 -api 的错误码常量类登记的为准。举两个图 7 会再次出现的真实锚点:project 的归属守卫抛 PROJECT_NOT_OWNER(1_100_000_001),落在 project 的 100 段;创作白名单拒绝抛 PLAYER_CREATE_NOT_IN_WHITELIST(1_002_090_003),落在 passport 寄宿的 system/infra 段——这也印证了 passport 身份层物理上寄宿在基座、不在 13 模块清单里这件事。
后端图 5 · 全平台数据模型跨域 ER(canonical)〔SVG新·ER·现〕
这张图回答「全平台的数据长什么样、怎么跨域连」——也是全仓唯一的 ER 权威,其它图说(如本主档别处、运维篇)要画数据都引用它、不另起一份。它据 Flyway V1–V25 真实落库的约 40 张表画出来,按五条业务链分块:① 创作链路(studio+aigc+project+source)、② 编译发布与合规闸门(runtime+compliance)、③ 分发与数据回路(feed+telemetry)、④ 钱财闭环(ad+trade)、⑤ 身份关联(community+biz),最上面一条是所有创作的归属起点 passport 身份层。
这张图最该先记住的是图顶那条全图铁律:所有连线都是逻辑「软关联」,代码里一根物理外键都没有。这是单体内有意的解耦——模块之间只经各自的 -api 包用 DTO 软关联,归属隔离靠 Service 的可信边界(Mapper 强制带 eq(userId) 谓词),而不是靠 DB 约束。代价是数据一致性要 Service 自己保,收益是模块能各自演进、后续拆独立仓时不被外键绊住。所有 game_* 表里的 creator_user_id / user_id,数值上都等于 game_player.id(OAuth2 的 userId),但代码层一根外键都没有。图上因此用实线画主干强关系、虚线画软关联与回灌,二者一眼区分。
图右下角用三个框钉住了三处最容易误读的真相,读图时务必一并记住。第一处是必须知道的数据语义坑(踩错会出真 bug):两个 quality_score 不能互灌(aigc 的是 0–1 衡量生成质量、telemetry/feed 的是 0–100 衡量运营质量);play_count 有三处口径但只有一处权威(试玩会话不计、telemetry 聚合才是权威源、project 的是回灌的物化字段);金额一律 BIGINT 以「分」计、禁浮点;业务归属列 creator_user_id 与审计列 creator 是两回事。第二处是contracts 镜像已漂移——执行权威是 huijing-server 里的 25 个迁移 V1–V25,但 contracts/db-schemas/ 只含 18 个(单模块 additive 的 V14–V20 不进 contracts),只读 contracts 会漏掉 aigc 的 level/trace/source/modify 列、source_project 的并发幂等唯一键、feed 的 exposure_limit。这是个真实的漂移面,图上诚实画出、不掩盖。第三处是两套身份数值与代码路径都分开:C 端玩家创作者用 game_player(物理寄宿 huijing-module-system、不在 13 模块清单),B 端管理员用 system_users;而 pay/ip 当前没有独立迁移落地,赋余额走 trade 的 grant、素材登记走 studio 的 game_material——这和图 1 的「pay 未接单体、ip 寄宿 compliance」从数据侧对上了。
还有一个贯穿全图、最该让评审看出的设计意图是源与产物两个存储面解耦:game_source_project 存源(source_json)、game_version 存产物(package_url 指向的包),「改源不改包」指的是改了源要重新构建才生成新产物、不是直接动包;源构建失败时会留下孤儿源(status=2)而不污染产物面。这正是生成主线「游戏是长生命周期项目、改源重建」范式在数据层的落点。迁移只增不改——新增表或列要回 数据模型.md 补节点,逐列细节以迁移头注为准。
后端图 6 · 五条业务链路(跨模块调用)〔SVG新·时·现〕
如果说图 5 是数据的静态结构,这张图就是它的动态——把「一句话到能赚钱」拆成五条链,看每条链怎么跨模块串起调用。五条链分别是:链 1 创作(一句话→可试玩草稿)、链 2 发布(草稿→已发布入流)、链 3 试玩(刷流→取包→沙箱跑→互动)、链 4 广告收益(曝光/激励→计费→分账→入钱包)、链 5 数据回路(遥测→聚合→质量分→feed 重排)。每条链左侧标该步的主调模块,实线是同步主调、绿虚线是异步回灌。
读这张图要抓住几条最该看出的设计意图。链 2 发布是全仓唯一一处真正的跨表原子发布:project 的统一发布编排把 compliance 裁决、runtime 出包、feed 入流串成一个事务,任一步失败就整体回滚、不留半截状态——这是脊柱级的可靠性能力,图上用事务边界框单独强调。链 5 数据回路里 telemetry ↔ feed 是 13 模块里唯一一处双向依赖:feed 消费 telemetry 算出的质量分来排序,telemetry 回收 feed 上的互动信号来计算,这条唯一的双向边正是平台「越用越聪明」飞轮的机制所在,图上用一条绕回的绿虚线画出来。链 4 广告收益的两段靠 source_ref 对账锚点串起:ad 的 revenue.id 就是 trade 的 income.source_ref,trace_id 从 ad 一路透传到 trade 串起对账链,金额全程 BIGINT 以分计——这条边界不容含糊,是钱的事。
这张图也照实标出了几处设计缝与桩(用 📌 标在对应步骤上),绝不把桩画成真:链 3 的游标分页目前是桩(永远返回第一页)、packageUrl 字段恒为 null(真实取包要走 GET /runtime/package/{vid});链 4 的真广告联盟 SDK 是桩、被 mock 挡在审核闸门前;compliance 的检测原子目前也是桩。这些缺口的共同特点是逻辑已建、卡在不可压缩的日历闸门(ICP 备案、支付进件、广告审核)或 W-G1 质量门,而不是代码没写——策略是先把逻辑建好、闸门到位即接。最后一个衔接点:这五条链正是运维冒烟门验的五条端到端闭环(见运维篇图 4),冒烟门按链分组点检每条链的关键 API 是否都在岗。
后端图 7 · 鉴权 OAuth2 / RBAC 双模型〔SVG新·类·现〕
这张图回答「谁能做什么」。它要先记住图顶那条铁律:权限只在后端可信边界强制,前端拦截一律不算数;当前 MVP 是单体,可信边界就是服务侧那一个点(不是网关)。权限基座是 Huijing fork 自带的 Spring Security + OAuth2 + RBAC,全仓没有引入 sa-token。
这张图的核心是两类用户、两套权限模型并存,这是它最该让人看清的一件事。系统只有两类用户:MEMBER(C 端,创作者+玩家同型)和 ADMIN(B 端,运营+管理员),userType 按 URL 前缀实时推导(接图 3)。一个请求穿过服务侧 TokenAuthenticationFilter(取 token 换登录态、校验 userType 与端前缀一致)和 URL 准入后,按端类型分流到两套截然不同的强制机理:
- B 端走声明式 RBAC(图左):后台所有 admin 控制器统一用
@PreAuthorize("@ss.hasPermission('模块:资源:动作')")声明所需权限位,真正的裁决在PermissionServiceImpl按「角色→角色拥有的菜单」求交集,超管短路、严格模式(权限点找不到 Menu 即判无权限),落在 RoleDO/MenuDO/RoleMenuDO/UserRoleDO 四张表上。强制点是方法级的注解。 - C 端走 Service 层归属隔离(图右,黄金范式):app 控制器完全不用
@PreAuthorize——这是此前文档最大的认知缺口。它的范式是控制器取getLoginUserId()透传给 Service,Service/Mapper 用eq(creatorUserId/userId)把当前用户钉进 WHERE 谓词,从数据层强制只能碰本人数据。落地靠一个黄金归属守卫validateProjectOwner(不是本人就抛PROJECT_NOT_OWNER「无权操作他人的游戏项目」),后续 C 端写域直接克隆它。强制点是数据层的谓词,不是角色权限。
图底还叠了一层创作白名单(A2 内测准入):C 端虽创作者与玩家同型,但「能不能创作」由 validateCreator 卡住,creator_flag≠1 就拒。这道白名单最该记住的是——前端守卫挡不住直连 API,所以它必须在后端强制。图右下角的「现状 vs 未来态」是这张图的纠偏点:控制器注释里多处写的「网关+服务端双重校验」是微服务拆分后才成立的未来态,当前单体下网关根本不在请求路径上、权限强制 100% 在服务侧单点;图上用实心绿块画现状、虚线框画未来态,把这处与现状不符的旧说法如实标清。mock 后门是 staging 现状、生产必须关死的红线,它和 validateCreator 查无行放行是配套的内测豁免。这张图配合图 8 一起看:图 7 讲两套强制机理,图 8 讲哪些端点落哪一档。
后端图 8 · 匿名 / 登录态的准入矩阵〔Mer新·讲·现〕
这张图把图 7 的两套机理落到「具体端点该走哪一档」的速查上。平台的端点按准入要求分四档:匿名公开读、登录态、B 端 RBAC 权限位、C 端归属隔离。匿名读端点用 @PermitAll 由框架扫描注解自动转成免登 URL 列表,其余端点落到 anyRequest().authenticated() 的兜底(未登录即 401)。
flowchart TB
REQ["请求到达服务侧"] --> Q1{"端点带<br/>@PermitAll?"}
Q1 -->|是| ANON["① 匿名公开读<br/>任何人无需 token<br/>(app 端共 34 处匿名读)"]:::anon
Q1 -->|否| Q2{"已登录?<br/>(userType 须匹配端前缀)"}
Q2 -->|否| E401["401 未登录"]:::deny
Q2 -->|是| Q3{"端类型?"}
Q3 -->|"/admin-api · B 端"| RBAC["③ RBAC 权限位<br/>@PreAuthorize + PermissionServiceImpl<br/>持对应角色-菜单权限的 ADMIN"]:::rbac
Q3 -->|"/app-api · C 端"| OWN["④ 归属隔离<br/>Service eq(userId) + validateProjectOwner<br/>MEMBER 且是数据归属人"]:::own
RBAC -->|无权| E403["403 无权限"]:::deny
OWN -->|非本人| E403
ANON -.->|"匿名读三纪律"| RULE["只暴露公开字段·裁剪 PII<br/>公开读靠手动 eq 谓词隔离(不假定 DataPermission 自动生效)<br/>跨租户读显式收敛"]:::rule
classDef anon fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px;
classDef rbac fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px;
classDef own fill:#ede9fe,stroke:#7c3aed,stroke-width:1.5px;
classDef deny fill:#fee2e2,stroke:#dc2626,stroke-width:1.5px;
classDef rule fill:#fff7ed,stroke:#d97706,stroke-width:1.3px,stroke-dasharray:5 4;
这张图最该让人看出的,是匿名读这一档不是「无所谓」、而是有三条必须守的纪律(图上用橙色虚线框单独标出,出自安全规则、P-OPN-08 已实证):只暴露公开字段、把 PII 裁剪掉;公开读靠手动 eq 谓词隔离,不能假定框架的 DataPermission 会自动生效(它只注册在 AdminUserDO 等业务表上);跨租户读要显式收敛。这三条是匿名读端点最容易踩的坑——以为放了 @PermitAll 就完事,结果把私有数据或 PII 漏出去。一个要记住的细节是登录端点本身免登:C 端的 send-sms-code / sms-login / invite-register 三个带 @PermitAll(不然没法登录),但 me 走登录态;sms-login 是「已注册即登录、未注册自动注册」一体,成功后发真 OAuth2 token。这张图没有现行/远期边界,就是当前每个端点准入的真相速查表。
后端图 9 · 核心对象生命周期状态机〔Mer新·状态机·现〕
这张图回答全平台最核心的那个对象——一款游戏(game_project)——这辈子会经历哪些状态、是谁在推它走。前面图 5 是数据的静态结构、图 6 是链路的动态调用,这张图是第三个视角:把 game_project.status 这条七态主链单独抽出来当状态机看。它之所以是「总闸」,是因为创作链、发布链、合规链都不直接互相调用,而是各自读写这一个 status 字段来协同——谁也绕不开它。新人记住这张图,就抓住了后端业务流转的主心骨。
stateDiagram-v2
direction LR
[*] --> 草稿0: createDraft 建项目
草稿0 --> 审核中1: submitPublish 提交发布(project 驱动)
审核中1 --> 通过2: compliance 裁决 verdict=pass 回写
审核中1 --> 拒绝3: compliance 裁决 verdict=block 回写
通过2 --> 已发布4: 统一发布编排入 feed(status=4 出流)
拒绝3 --> 草稿0: 改源重做后再提交
已发布4 --> 下架5: 运营/创作者下架(offlineRank 出流)
已发布4 --> 封禁6: compliance 处置(user_ban 落台账)
下架5 --> 审核中1: 重新提交复审
封禁6 --> [*]: 终态(申诉另走 compliance_appeal)
通过2 --> [*]
下架5 --> [*]
note right of 审核中1
谁推 / 谁回写两分:
project 只驱动状态机迁移(submit/offline);
compliance 只给结论(pass/review/block),
经 gate_result.verdict 回写 project.status,
自己不持有这条主链。
end note
note left of 草稿0
改源不改包:源(source_project.source_json)
与产物(version.package_url)两面解耦。
create=首次脚手架 / modify=确定性改源 /
extend=经 base_version_id 记血缘扩展;
任一操作都「重新 build 产新 version」、
而非直接动旧 package。source_hash 幂等去重,
构建失败留孤儿源(status=2)不污染产物面。
end note
这张图最该让人看清两件事。其一是「谁推」与「谁回写」的两分:status 这条主链的所有权属于 project,但驱动它前进的有两方——project 自己负责「提交发布(submitPublish)」「下架(offlineRank)」这类主动迁移,而「通过 / 拒绝 / 封禁」这三步的结论来自 compliance,compliance 不持有这条主链、只把 gate_result.verdict 回写进 project.status。这正是图 6「唯一真原子发布」和图 7「合规处置」在状态层面的落点:发布是 project 把 compliance 裁决、runtime 出包、feed 入流串成一个事务,任一步失败整体回滚、status 不留半截。其二是状态机背后那条「改源不改包」范式(图左侧 note):一款游戏不是一次性产物、而是长生命周期项目,game_source_project 存源、game_version 存产物,两个存储面解耦;create(首次脚手架)/ modify(确定性改源)/ extend(经 base_version_id 记血缘的扩展)三种操作改的都是源,然后重新 build 出一个新 version,而不是去动旧包——source_hash 做幂等去重,构建失败时留下孤儿源(status=2)而不污染产物面。这张图没有现行/远期的灰度,七态都是已落库、已 e2e 验证的现行真相;唯一要注意的边界是「封禁」后的申诉不在这条主链上、另走 compliance_appeal 台账(见图 5 合规块)。
后端图 10 · 两套身份 + 匿名→登录归因数据视图〔Mer新·数据模型·现〕(P2)
这张图回答一个读懂全平台数据归属的前提问题:「谁拥有这条数据」到底怎么判定。答案分两层:第一层是平台根本就有两套互不相干的身份体系——C 端的玩家兼创作者用 game_player、B 端的运营与管理员用 system_users,它们在数值空间和代码路径上都分开;第二层是 game_* 业务表全都没有物理外键,归属不靠 DB 约束、而靠 Service 可信边界在查询里强制带 eq(userId) 谓词。这两层一起,才是图 5「两套身份」框和图 7「C 端归属隔离」范式在数据视角的完整底座。
erDiagram
game_player {
bigint id "= OAuth2 userId(MEMBER)· 不在13模块清单却被全模块引用"
varchar mobile "uk_mobile 登录主键"
tinyint creator_flag "0 玩家 / 1 创作者(A2 白名单)"
varchar first_anon_id "匿名→登录归因衔接"
}
system_users {
bigint id "= OAuth2 userId(ADMIN)· Huijing 原生"
varchar username "B 端账号"
}
game_project {
bigint id "gameId"
bigint creator_user_id "软关联 game_player.id(无物理外键)"
}
game_telemetry_event {
varchar event_id "uk_event_id 幂等真身"
bigint player_user_id "可空·登录态归属"
varchar anon_id "匿名态归属"
}
game_runtime_session {
bigint id "试玩会话"
bigint player_user_id "可空(V11 放宽)"
varchar anon_id "V11 加·匿名也能试玩"
}
game_player ||..o{ game_project : "creator_user_id eq 谓词(软关联·零外键)"
game_player ||..o{ game_runtime_session : "player_user_id(登录后)"
game_player ||..o{ game_telemetry_event : "player_user_id(登录后)"
game_runtime_session }o..|| game_telemetry_event : "anon_id 串起匿名行为"
system_users ||..o{ game_project : "B 端 RBAC 经 AdminUserApi(另一套人)"
读这张图,新人最该看出三点。一是两套身份的彻底分离:game_player 物理上寄宿在 huijing-module-system、却不在 13 模块清单里——它是被全平台 game_* 表共同引用的归属起点,所有业务表里的 creator_user_id / user_id / player_user_id 数值上都等于 game_player.id(即 OAuth2 的 userId);而 B 端管理员是另一套人,走 Huijing 原生的 system_users,ProjectServiceImpl 引 AdminUserApi 就是这条线。两套人不共用一张表、不共享数值空间。二是「零外键 + eq 谓词」这条归属强制机理:图上 game_player 指向各业务表的连线全部用虚线(..)画,因为代码里一根物理外键都没有——归属隔离完全靠 Service/Mapper 在 WHERE 里钉 eq(creatorUserId),这是图 7 C 端「黄金归属范式」在数据层的落点,也是为什么动跨模块查询时必须自己带归属谓词、不能指望 DB 帮你拦。三是匿名→登录的归因衔接:试玩会话(game_runtime_session)和遥测事件(game_telemetry_event)的 player_user_id 都是可空的(V11 放宽),匿名玩家靠 anon_id 占位先玩起来、产生行为数据,登录后再经 game_player.first_anon_id 把这段匿名轨迹归并到真实身份上——这条衔接是「先让人无门槛玩、再做转化归因」的数据基础。这张图没有现行/远期边界,是当前数据归属判定的真相底座;标注 (P2) 是因为它服务于 feed/试玩这条 P2 链路的归属语义。
后端域图清单与状态表
| # | 图名 | 家族 | 形式 | 状态 | 覆盖内容 |
|---|---|---|---|---|---|
| 1 | 后端分层与依赖(实现视角) | 架 | 引(内联 README §1) | 现 | 基座 + 13 模块 + 两段式三层;pay 未接单体 / ip 寄宿 compliance 两处特殊归属;WHAT 归架构域、本域只讲 HOW |
| 2 | 两段式模块 -api/-server | 类 | SVG新 | 现 | 单模块剖面(-api 枚举/错误码/DTO/Feign · -server controller/service/dal/convert)+ 跨模块只依赖 -api 的越界红线 + DTO/VO/DO 三层 + 单体形态 |
| 3 | 包名 → 端前缀自动生效 | 流 | Mer新 | 现 | controller.app/admin 包路径如何自动套 /app-api、/admin-api + 前缀反推 userType 接鉴权 |
| 4 | 错误码分段 | 讲 | Mer新 | 现 | 1-{模块段}-*** 各模块独占段;六段源档给出 + 七段顺延推断(诚实标推断) |
| 5 | 全平台数据模型跨域 ER(canonical) | ER | SVG新 | 现 | 五链路约 40 表跨域 ER + 全图无物理外键铁律 + 三处真相框(数据语义坑 / contracts 镜像漂移 / 两套身份)+ 源↔产物解耦 |
| 6 | 五条业务链路 | 时 | SVG新 | 现 | 创作/发布/试玩/广告收益/数据回路 跨模块调用;唯一真原子发布 + 唯一双向边 + source_ref 对账锚点;游标/联盟SDK/检测原子桩照实标 |
| 7 | 鉴权 OAuth2/RBAC 双模型 | 类 | SVG新 | 现 | 两类用户两套权限并存:B 端 @PreAuthorize RBAC + C 端 Service 归属隔离 + 创作白名单 + 网关双重校验=未来态纠偏 + mock 后门红线 |
| 8 | 匿名/登录准入矩阵 | 讲 | Mer新 | 现 | 四档准入(匿名/登录/RBAC/归属)速查 + 匿名读三纪律 + 登录端点本身免登 |
| 9 | 核心对象生命周期状态机 | 状态机 | Mer新 | 现 | game_project.status 七态主链(草稿→审核→通过/拒绝→已发布→下架/封禁)+ 每迁移标谁推谁回写(project 驱动状态 / compliance 给结论)+ 改源不改包(create/modify/extend→重 build 产新 version 不动旧 package · source/version 两面解耦) |
| 10 | 两套身份 + 匿名→登录归因数据视图 | 数据模型 | Mer新 | 现(P2) | game_player(C 端·不在13模块清单却被全模块引用)vs system_users(B 端)两套身份数值与代码路径分开 + game_* 全表零物理外键(归属靠 Service eq 谓词)+ anon_id 匿名→登录归因衔接 |
状态分布:现 ×10(后端域全部现行已建,无接/缓/建/future)。但「现行已建的结构」里照实标出了若干设计缝与桩——contracts DB 镜像漂移(图 5)、compliance 检测原子恒 pass / feed 游标分页桩 / 真联盟 SDK 桩(图 6)、网关双重校验是未来态而非现状(图 7)——绝不因整体为「现」就把桩画成真。图 9 / 图 10 是补的两张新人视角图(对象生命周期状态机 + 身份归属数据底座),都是现行真相、无远期灰度。防漂移门:本文 frontmatter 记后端域四份源档的 commit hash(README/数据模型/鉴权与权限 @ 38357c3d、13模块 @
7ecd616e),源档变更即比对。