# 定制层级

在通道 1（元数据驱动定制，见[定制通道](customization-channels.md)）内部，xly 支持**分层覆盖模型**。基础表单在系统级定义；租户和用户在其上叠加修改，且不修改基础表单。框架在请求时合并这些层，并向 SPA 返回一个合并后的表单。

自定义字段的贯穿示例见[切片 4](../slices/04-custom-field.md)。本页是规范概览。

## 各层

```mermaid
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 强制；见[无物理外键、语义外键的现实](semantic-fk.md)。

## 每层回答的问题

| 层 | 作用域 | 回答 |
|---|---|---|
| `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`。如果自定义字段超出了基础表已经提供的列，保存时就没有可绑定目标，除非满足以下条件之一：

1. 基础表刻意设计得很“宽”，预留了定制列。
2. 协调一次手工 schema 迁移来添加该列。
3. 定制只与视图相关（字段是计算或展示出来的，不存储）。

情况 (2) 是典型的工程师主导路径。情况 (1) 解释了 schema 中一些看起来异常宽泛的表。当定制需要超过覆盖能力的*结构性*变化时，[切片 5（每客户 SQL 覆盖）](../slices/05-customer-sql-override.md)就是下一个通道。
