定制层级
在通道 1(元数据驱动定制,见定制通道)内部,xly 支持分层覆盖模型。基础表单在系统级定义;租户和用户在其上叠加修改,且不修改基础表单。框架在请求时合并这些层,并向 SPA 返回一个合并后的表单。
自定义字段的贯穿示例见切片 4。本页是规范概览。
各层
flowchart TB
classDef sys fill:#e8f0fe,stroke:#4285f4
classDef tenant fill:#fef7e0,stroke:#fbbc04
classDef user fill:#f3e8fd,stroke:#a142f4
M["gdsconfigformmaster<br/>系统默认:表单<br/>(sSqlStr · sWhere · sOrder)"]:::sys
P["gdsconfigformpersonalize<br/>每租户整表单覆盖<br/>(替换 sSqlStr / sWhere / sOrder)"]:::tenant
S["gdsconfigformslave<br/>系统默认字段"]:::sys
CS["gdsconfigformcustomslave<br/>每租户字段<br/>(按 sName 新增 · 隐藏 · 覆盖)"]:::tenant
US["gdsconfigformuserslave<br/>每用户视图微调<br/>(列顺序 · 隐藏列)"]:::user
OUT["合并后的表单<br/>返回给 SPA"]
M --> P
P --> S
S --> CS
CS --> US
US --> OUT
M -. "总是加载" .-> OUT
P -. "租户有覆盖时加载" .-> OUT
CS -. "租户有覆盖时加载" .-> OUT
US -. "用户有偏好时加载" .-> OUT
自上而下读这条链:系统 → 租户 → 用户。每一层都通过 sParentId 连接到上一层。没有任何连接由 FK 强制;见无物理外键、语义外键的现实。
每层回答的问题
| 层 | 作用域 | 回答 |
|---|---|---|
gdsconfigformmaster |
系统级 | “这个表单默认长什么样?” |
gdsconfigformpersonalize |
每租户 | “这个租户是否要不同的 SQL / where / order?” |
gdsconfigformslave |
系统级 | “这个表单默认有哪些字段?” |
gdsconfigformcustomslave |
每租户 | “这个租户是否要额外字段、隐藏字段或改标签?” |
gdsconfigformuserslave |
每用户 | “这个用户是否在自己的表格里调整过列顺序或隐藏列?” |
合并如何发生
框架按顺序读取每层,并按 sName(字段名)合并。自定义从表行与基础从表拥有相同 sName 时:覆盖。新的 sName:追加。没有对应自定义行的基础从表:原样透传。入口是 BusinessBaseServiceImpl.getModelBysId(第 181 行),它会调用 BaseServiceImpl.getModelConfigByModleId(第 55 行);真正的 slave + customslave 合并发生在 BusinessGdsconfigformsServiceImpl.getGdsconfigformslaveShow(第 392 行),组合 getFormSlaveData(第 87 行)和 getFormCustomSlaveData(第 121 行),随后可选叠加 getUserFormSlaveData(第 156 行)。
两个数据库视图通过连接 form-master 和相关 slave 表来支持合并:
-
gdsconfigformslavemasterview— 基础层。 -
gdsconfigformcustomslavemasterview— 覆盖层。
服务代码直接读取这些视图;真正的合并逻辑在 Java 中,而不是 SQL 中。
这些层做不到什么
覆盖表允许租户向表单添加字段,但不会也不能对底层物理表执行 ALTER TABLE。如果自定义字段超出了基础表已经提供的列,保存时就没有可绑定目标,除非满足以下条件之一:
- 基础表刻意设计得很“宽”,预留了定制列。
- 协调一次手工 schema 迁移来添加该列。
- 定制只与视图相关(字段是计算或展示出来的,不存储)。
情况 (2) 是典型的工程师主导路径。情况 (1) 解释了 schema 中一些看起来异常宽泛的表。当定制需要超过覆盖能力的结构性变化时,切片 5(每客户 SQL 覆盖)就是下一个通道。