20260917231331

This commit is contained in:
oneao committed 2026-09-17 23:13:31 +08:00
1 parent 5cbf745bbf
commit f56e70b050
4 files changed
+454 -100

No files matched your search

+145 -100
View File
@@ -1,7 +1,14 @@
# FMS 删除规则设计
> 版本:v1.0。关联:`FMS新系统核心表结构设计.md`。
> 版本:v2.0(取代 v1)。关联:`FMS新系统核心表结构设计.md`(§9 模块关系)、`FMS模块设计评审.md`。
> 一句话:**删之前先查一查,查不过就拒绝;查什么、怎么查,由 IT 配置,不写代码。**
>
> v2 相对 v1 的变化(动因见 `FMS模块设计评审.md` 问题一/问题三):
> - **引用规则退出 `s_rule`**:原「勾引用」由 `s_relation.b_on_delete = 'restrict'` 表达,关系面板只配一列;
> - **`s_rule` 收敛为模块级**:去掉 `b_scope_type`、`b_scope_id`(拼接串)、`b_hook`,加 `b_kind` 显式区分勾条件与写 SQL;
> - **方向语义统一**:四个取值都描述「删除**被引用方**时,**引用方**怎么办」,被引用方由基数判定,不再有删源/删目标两套口径;
> - **默认值 `restrict`**:被引用即不可删成为默认,多数关系零配置。
> - 库中无存量数据,全部按新结构直接建,不做迁移。
## 这个设计解决什么问题
@@ -15,26 +22,31 @@
以前这些规矩要么写死在代码里(改一条要发版),要么干脆没有(误删了才发现)。现在的做法:**IT 在界面上配置,配完立即生效**。
规矩分两类,配在两个地方:
- **记录自身什么状态不许删** → 模块上配「删除规则」(勾条件 / 写 SQL);
- **被别人引用着不许删、删主单连带删明细** → 关系上选一个「删除行为」(一列四值,默认即防护)。
## 1. 整体图景
一个用户点「删除」,系统背后做四件事:
```
1. 找出连带要删的记录(删报销单 → 连费用明细、附件一起)
1. 找出连带要删的记录(删报销单 → 连费用明细一起)
2. 把配置的所有删除规则挨条查一遍
3. 任何一条不过 → 整批拒绝,告诉用户为什么
4. 全过 → 先弹预览给用户确认,再一个事务里一起删
```
IT 要做的事只有一件:**配规则**。配的地方在模块管理。
IT 要做的事只有两件:模块上**配条件规则**,关系上**选删除行为**。配的地方都在模块管理。
## 2. 规则:一条就是一句「什么情况不许删」
## 2. 模块规则:一条就是一句「什么情况不许删」
每条规则就是一句话:
> **当【什么条件】时,不许删,提示【什么话】**
规则有三种写法,按配的难度从易到难:
规则有两种写法,按配的难度从易到难:
### 2.1 勾条件(日常用这个,占绝大多数)
@@ -55,15 +67,7 @@ IT 要做的事只有一件:**配规则**。配的地方在模块管理。
会配查询条件的人,零学习成本。
### 2.2 勾引用(防误删最常用)
在**关系配置**上勾一下「有引用时拒绝删除」。
效果:这模块的记录只要被别的单据引用着(费用被账单引用),就删不掉,提示「费用【海运费 ¥2,000】已被账单 ZD-2026-001 引用」。
模块的删除规则 tab 里能看到这个勾的只读展示和跳转,不会迷路。
### 2.3 写 SQL(兜底,藏在角落)
### 2.2 写 SQL(兜底,藏在角落)
界面勾不出来的判断——比如「核销金额加起来大于 0」这种要查别的表的——在规则类型的「高级」选项里直接写一句 SQL:
@@ -77,19 +81,49 @@ where b.b_id in (:ids) and d.writeoff > 0
平时用不到,会写的人自己找得到,不会打扰别人。谁改过这条 SQL、每次执行了什么,系统日志里全程有记录(内部系统,不做限制,只做留痕)。
## 3. 连带删除:删主单时子表跟着删
(v1 的「勾引用」一节取消——引用保护挪到关系上,见 §3。)
这个不用配条件,在模块关系上选一个「删除时连带」的策略:
## 3. 关系上的删除行为:`s_relation.b_on_delete`(一列四值)
| 选项 | 意思 |
|---|---|
| 连带删除 | 删报销单,费用明细一起物理删掉 |
| 连带归档 | 明细不删,打个「已归档」标记,列表里不再显示,账单引用还能追溯 |
| 不连带 | 明细自己管自己 |
统一语义:**删除「被引用方」记录时,「引用方」记录怎么办。**
关键行为:
谁是"被引用方"由基数决定,不需要额外配置:
- **查连带时也过规则**:删报销单连带要删费用,但费用被账单引用着 → 整个删除(包括报销单)被拒绝,提示定位到那笔费用和那个账单
| 基数 | 外键在 | 被引用方 |
| --- | --- | --- |
| `many_to_one` | 源字段 | 目标 |
| `one_to_many` | 目标字段 | 源 |
| `one_to_one` | 目标字段(约定) | 源 |
四个取值:
| 值 | 含义 | 典型场景 |
| --- | --- | --- |
| `restrict`(**默认**) | 仍有引用方记录时,拒绝删除被引用方 | 费用→客户:客户被费用引用,删不掉 |
| `cascade` | 引用方记录一起物理删除 | 报销单→费用明细:删单连带删明细 |
| `archive` | 引用方记录打归档标记,不物理删 | 删报销单,明细归档供账单追溯 |
| `none` | 不处理 | 日志→用户等确实无关的关联 |
几个例子(源→目标按基数标注被引用方):
| 关系 | 基数 | 被引用方 | b_on_delete | 效果 |
| --- | --- | --- | --- | --- |
| 费用 → 客户 | many_to_one | 客户 | restrict(默认) | 客户被费用引用时删不掉 |
| 费用类型 → 费用 | one_to_many | 费用类型 | restrict(默认) | 类型下有费用时删不掉 |
| 报销单 → 费用明细 | one_to_many | 报销单 | cascade | 删报销单连带删明细 |
| 报销单 → 费用明细(归档版) | one_to_many | 报销单 | archive | 删报销单,明细归档可追溯 |
设计要点:
- **默认即防护**:新关系默认 `restrict`,「主数据被引用不许删」这类最通用的规矩零配置;确实要放开才显式改 `none`。安全方向从「记得配防护」反转为「记得放开」
- **一列取代原来的三个栏位**:v1 的 `b_on_delete`(三值)+「有引用时拒绝删除」勾选(假列,实际生成 `s_rule` 行)+ 方向口径混用,合并为**一个真列**
- **取值即语义,无非法组合**:v1 可同时配出「cascade + 引用规则」(连带删但又不许删,自相矛盾);单列四值后该组合在类型层面不存在
- **基数不禁取值**:是否 `cascade` 是「被引用方是否独占拥有引用方」的业务判断,只有配置者知道;把 `cascade` 配在共享主数据的关系上属于配置错误,系统不做基数层面的硬拦(内部系统,配置留痕即可),界面在选 `cascade` / `archive` 时给出确认提示
- **`archive` 前提**:引用方表需有 `b_archived` + `b_archive_datetime` 通用列,保存时服务层校验列存在
连带展开的关键行为:
- **查连带时也过规则**:删报销单连带要删费用明细,但费用明细被账单明细引用(restrict 入边)→ 整个删除(包括报销单)被拒绝,提示定位到那笔明细和那个账单
- **一荣俱荣一损俱损**:任何一条卡住,整批都删不了,不存在删一半的中间状态
- 关系套关系(单→明细→附件)会自动一层层找下去;层数太多(比如配置成了环)会直接报配置错误,不会死循环
@@ -111,67 +145,68 @@ where b.b_id in (:ids) and d.writeoff > 0
**配规则**(IT 日常):
- 模块管理 → 找到模块 → 「删除规则」tab → 新增
- 每条规则 3 个格子一分钟;改完立即生效,**不用发版**
- 常见规则可以**复制到其他模块**(多选目标模块批量克隆)——「审核中不可删」这种几十个模块通用的规矩,配一次、勾一圈目标模块就完事
- 模块管理 → 「删除规则」tab → 新增:模块级条件规则,每条 3 个格子一分钟;改完立即生效,**不用发版**
- 模块管理 → 「模块关联」tab → 每条关系一列「删除行为」,默认"被引用时禁止删除";主子表关系显式选「连带删除」或「连带归档」
- 常见条件规则可以**复制到其他模块**(多选目标模块批量克隆)——v2 起全部规则都是模块级、结构同构,无例外;复制时校验目标模块存在条件里用到的字段,缺字段的目标模块在确认框里点名
**看全貌**(排查、盘点):
- 系统管理 → 「业务规则」:全系统所有规则的列表——哪个模块的、什么类型、谁最后改的、什么时候。点行跳到对应模块
- 排查「这单为什么删不掉」:错误提示里带规则名 → 全局列表找到它 → 看条件/SQL → 完事
- 系统管理 → 「业务规则」:全系统所有模块级规则的列表——哪个模块的、什么类型、谁最后改的、什么时候。点行跳到对应模块
- 排查「这单为什么删不掉」:错误提示里带规则内容 → 全局列表找到它 → 看条件/SQL → 完事;被引用拦下的,提示里直接带引用方单号和关系来源
**谁可以配**:规则的增删改走权限控制(和菜单权限同体系)。看规则不受限。
## 6. 数据落库(给开发看的部分)
### 6.1 s_rule 规则表(新增)
### 6.1 s_relation:删除行为列
权威表结构见 `FMS新系统核心表结构设计.md` §9,本节只列差异:
```sql
create table s_rule (
b_id bigint not null,
b_code varchar(50) not null, -- 稳定标识(复制/导入导出用)
b_name nvarchar(100) not null, -- 规则名(如"审核中不可删")
b_scope_type varchar(20) not null, -- module / relation:挂在模块还是关系上
b_scope_id bigint not null, -- 挂在哪个模块/哪条关系上
b_hook varchar(30) not null, -- delete.pre(一期只做这个)
-- save.pre(将来做"已审核不能改")
b_predicate nvarchar(max) null, -- 条件(勾条件=JSON;写SQL=SQL文本;勾引用=空)
b_message nvarchar(500) null, -- 提示文案(写SQL的必填;勾条件的可留空自动生成)
b_canuse int not null default 1,
b_xh int not null default 0, -- 多条规则谁先查(并列按 b_xh, b_id)
b_inputuser_id bigint null,
b_inputdatetime datetime2 null default sysutcdatetime(),
b_updateuser_id bigint null,
b_updatedatetime datetime2 null,
constraint pk_s_rule primary key (b_id),
constraint ux_s_rule_code unique (b_code)
alter table dbo.s_relation add
b_on_delete varchar(20) not null default 'restrict';
-- restrict(默认) 被引用时禁止删除 / cascade 连带删除 / archive 连带归档 / none 不处理
```
- 语义、取值、被引用方判定见本文 §3
- `archive` 由服务层校验引用方表存在 `b_archived` / `b_archive_datetime` 列
- 引擎读取时只认 `b_canuse = 1` 的关系行
- 库中无存量数据,不需要从 v1 的 `none` 默认值做迁移;`fms_core.sql`、`fms_delete_rule.sql` 脚本需按本结构同步(现脚本默认值仍是 `none`)
### 6.2 s_rule:模块级规则表(v2 结构)
```sql
create table dbo.s_rule (
b_id bigint not null, -- 雪花主键(新增时前端经 /data/nextid 取号)
b_module_id varchar(50) not null, -- 所属模块编码
b_kind varchar(20) not null, -- condition 勾条件 / sql 写 SQL
b_predicate nvarchar(max) not null, -- condition=JSON AST(§6.3);sql=SQL 文本
b_message nvarchar(500) null, -- condition 可留空(引擎按条件自动生成);sql 必填
b_canuse tinyint not null default 1,
b_xh int not null default 0,-- 执行顺序(并列按 b_xh、b_id)
b_created_by varchar(50) null, -- 审计列由服务端维护,配置界面不提交
b_created_at datetime2 null,
b_updated_by varchar(50) null,
b_updated_at datetime2 null,
primary key (b_id)
);
-- b_scope_type / b_hook 的取值范围不建 CHECK 约束,由服务层校验
-- (与全系统「元数据合法性走服务层」的原则一致,新增取值不用改表)
create index ix_s_rule_scope on s_rule (b_scope_type, b_scope_id, b_hook, b_canuse, b_xh);
create index ix_s_rule_module on dbo.s_rule (b_module_id, b_canuse, b_xh);
```
要点:
相对 v1 的变化与理由:
- **模块上没有任何规则 = 直接删**(无约束)
- 想要旧「restrict 主数据」的效果?一键模板:给所有入边关系批量勾上引用规则
- 错误类型不用存——勾条件报「状态不符」、勾引用报「被引用」、写 SQL 报自定义文案,写法本身决定了报什么
- 提示文案只存这一列,不重复塞进条件 JSON 里
- **删模块/删关系时,服务层把挂着的规则一起删掉**(和字段、权限点同一套清理逻辑,全系统本来就不建物理外键)
- 删规则 = 物理删 + 日志记全文;临时不想用某条规则 → 停用(b_canuse=0),不删
- **去掉 `b_scope_type` / `b_scope_id`**:规则只挂模块,`b_module_id` 直存模块编码。v1 的拼接串键(`源模块|源字段|目标模块|目标字段`)随之消失——它无法有效索引、无法校验、编码含 `|` 时错位,是明确的反模式
- **去掉 `b_hook`**:一期只有 `delete.pre` 一种挂点,单值枚举列属过早抽象(`开发规范.md`「为真实出现的第二个使用方而抽象」);将来做 save.pre 时再按当时的真实需求定表形态
- **加 `b_kind`**:v1 靠 `b_predicate` 三态推断(空=引用 / `{` 开头=JSON / 其余=SQL),判别依据靠猜且依赖「SQL 不以 `{` 开头」这个未声明约定;显式一列后判别依据存在数据里
- `b_xh` 保留:多条规则任一不过即拒绝,顺序不影响结论,但决定错误提示的展示顺序,也支持「便宜的勾条件规则排前、贵的 SQL 规则排后」的执行优化
### 6.2 老表的变化
其他要点:
```sql
-- s_module:删掉 b_delete_policy(就是那个 restrict/lifecycle/hard 三选一,表达力不够,废弃)
-- s_relation:删掉 b_delete_policy,新增:
alter table s_relation add
b_on_delete varchar(20) null; -- cascade连带删 / archive连带归档 / none不连带(默认)
```
存量数据转换:旧 cascade→cascade;旧 archive→archive;旧 restrict→不连带+生成引用规则;旧 set-null→丢弃(本就不该有)。
- **模块上没有任何规则 = 该模块不受条件约束**(引用保护仍由关系默认值兜着)
- 错误类型不用存——勾条件报「状态不符」、写 SQL 报自定义文案,写法本身决定了报什么
- **删模块时,服务层把挂着的规则一起删掉**(`where b_module_id = …`,单条件直查,与字段、权限点同一套清理逻辑,全系统本来就不建物理外键)
- 删规则 = 物理删 + 日志记全文;临时不想用某条规则 → 停用(`b_canuse=0`),不删
### 6.3 勾条件那部分的格式(JSON,界面生成,人不手写)
@@ -197,12 +232,18 @@ alter table s_relation add
```
删除(模块, ids, 是否预览):
① 找连带:从这批 ids 出发,沿"连带删/连带归档"的关系一层层查,
全部装进删除清单(已装过的跳过——防关系成环;超过10层报配置错误)
② 查规则:按顺序逐条执行挂在清单上的删除规则
勾条件 → 按条件查清单内记录,不符的记一条错误(带实际值)
写SQL → 拿这批 ids 跑那条 SQL,查出结果每行记一条错误(全程写日志)
勾引用 → 查有没有清单外的单据引用清单内记录,有则记错误(带引用方单号)
① 执行器找连带:从这批 ids 出发,沿 b_on_delete ∈ {cascade, archive}
且 b_canuse=1 的关系,把引用方记录一层层装进删除清单
(被删的是被引用方;已装过的跳过——防关系成环;超过10层报配置错误)
② 检查器查规则:
模块条件:清单内每个模块的 s_rule(b_canuse=1) 逐条执行
勾条件 → 按条件查清单内记录,不符的记一条错误(带实际值)
写SQL → 拿清单内该模块的 id 跑那条 SQL,查出结果每行记一条错误(全程写日志)
引用保护:对清单内每条记录,查它作为「被引用方」的 restrict 入边
(b_on_delete='restrict' 且 b_canuse=1 的关系)
引用方存在、且引用方不在清单内 → 记错误(带引用方单号)
③ 有错误 → 整批拒绝,返回错误列表(按规则归类,每类带数量和前几条示例)
④ 全过 → 只是预览?返回"将删除 X 张单 + Y 条明细"摘要
真删?→ 开事务:再快速过一遍规则(防确认间隙有变化)
@@ -227,18 +268,18 @@ alter table s_relation add
DataSaveService.saveTable() 的 delete 分支,执行真删之前:
① 按表名反查模块(b_savetable = 这张表的 data 模块);查不到模块(如日志表)→跳过
② 该模块挂有删除规则 → 调检查器,传当前 connection(同一个事务!)
+ 待删的这批行
② 该模块挂有规则、或它的记录存在 restrict 入边关系 → 调检查器,
传当前 connection(同一个事务!) + 待删的这批行
③ 任一规则不过 → 抛 BusinessException(文案就是规则生成的提示)
→ 整个 saveobjt 回滚——主表的修改和子表的删除一起回滚,
用户的前端草稿还在,知道哪行费用删不掉、为什么
→ 整个 saveobjt 回滚——主表的修改和子表的删除一起回滚,
用户的前端草稿还在,知道哪行费用删不掉、为什么
④ 无规则或全过 → 照常执行 deletes
```
三个关键点:
- **同事务检查**:检查器用的就是 saveobjt 自己的 connection——表单里先改了主表状态、再删子表行,规则查到的是**改过之后**的值,不会出现「检查用旧数据、删的是新数据」的错位
- **小闭包语义**:勾引用规则检查子表行时,要把「本次 saveobjt 所有 deletes 覆盖的记录」当作一个小闭包——引用方也在被删之列就不算外部引用。场景:删主单连带删子表行(前端编排的),子表对主单的引用指向一个同批正在删除的记录,不该拦。实现:把请求数组里所有 deletes 的 id 收集起来传给检查器
- **小闭包语义**:检查 restrict 时,要把「本次 saveobjt 所有 deletes 覆盖的记录」当作一个小闭包——引用方也在被删之列就不算外部引用。场景:删主单连带删子表行(前端编排的),子表对主单的引用指向一个同批正在删除的记录,不该拦。实现:把请求数组里所有 deletes 的 id 收集起来传给检查器
- **不需要预览**:表单保存是「保存」动作,用户预期就是要么全成功要么报错,不做两阶段确认
### 6.6 接口
@@ -256,35 +297,39 @@ POST /data/saveobjt // 不变,deletes 分支内部自动走规则检查
## 7. 其他约定
- **字段联动**:规则里用了的字段,删除该字段时会被拦住并提示规则名——不让规则悄悄失效
- **字段联动**:条件规则里用了的字段,删除该字段时会被拦住并提示规则内容——不让规则悄悄失效
- **配置权限**:改规则要权限点(如 `rule.edit`),和菜单权限同一套体系;SQL 不限制内容,但改它的人必须有配置权限
- **归档标记**:连带归档要求子表有通用列 `b_archived` + `b_archive_datetime`(配归档时服务层校验列存在);通用查询默认不显示已归档行
- **将来扩展**(表结构已留位,暂不实现):
- 「已审核的单不能**改**」——同一套规则换个挂点(save.pre)就行,界面和错误提示全复用
- 「删之前先自动红冲」这种要执行动作的——预留了挂 Java 代码的口子
- 核销总额想在列表上显示成列——那是展示需求,将来单独做,跟删除无关
- **归档标记**:`archive` 要求引用方表有通用列 `b_archived` + `b_archive_datetime`(配归档时服务层校验列存在);通用查询默认不显示已归档行
- **将来扩展**(二期再议,**不留列**,届时按真实需求定表形态):
- 「已审核的单不能**改**」——形态未必与删除规则同构,到时候再决定复用还是另起
- 「删之前先自动红冲」这种要执行动作的——预留挂 Java 代码的口子
- 核销总额想在列表上显示成列——展示需求,单独做,跟删除无关
- **已知边界**:restrict 只覆盖 `s_relation` 里**登记过的**引用;没登记关系的表(日志、迁移脚本写的表)不在保护范围内。这是配置边界,不是数据库级引用完整性,文档与培训材料里要写明
## 8. 开工顺序
1. 建表 s_rule;删老字段、加 `b_on_delete`(存量按 §6.2 转换);前端基本信息表单同步去掉「删除策略」下拉
1. 建表:`s_relation` 加 `b_on_delete`(四值、默认 `restrict`);`s_rule` 按 §6.2 新结构建;同步重写 `fms_core.sql`、`fms_delete_rule.sql`(现脚本仍是 v1 结构)
2. 后端:条件(JSON → 参数化 SQL)+ 检查器 + 执行器(连带展开 → 规则 → 事务删除)
3. **saveobjt 接入检查器**:delete 分支反查模块 → 同事务规则检查 → 不过整体回滚(见 §6.5)
4. 错误提示:条件自动生成人话 + 字段格式化(金额带 ¥、日期带格式)+ SQL 占位符填充
5. 前端:删除先预览后确认、错误列表展示;模块管理「删除规则」tab(条件构造 + 复制到模块 + SQL 角落入口);系统管理全局规则列表页
6. 二期:「已审核不能改」、Java 钩子、聚合展示
5. 前端:关联面板一列化(去掉「有引用时拒绝删除」假列及其全部联动补丁)、规则面板收敛(去掉引用规则只读区与跳转)、列表删除先预览后确认、错误列表展示
6. 二期:save.pre、Java 钩子、聚合展示
## 9. 定案记录
| # | 事项 | 结论 |
|---|---|---|
| 1 | 节奏 | 一期只做删除规则;表结构给保存校验留好位 |
| 2 | 跨表判断 | 不做计算字段,直接写 SQL 兜底 |
| 3 | SQL | 兜底逃生舱:不限制内容、不做体验优化、入口藏角落;**每次执行全程写日志** |
| 4 | 旧 set-null | 废弃(财务上不该有悬空引用) |
| 5 | 归档 | 通用列 `b_archived` + `b_archive_datetime` |
| 6 | 条件规模 | 嵌套 ≤ 8 层、节点 ≤ 100;界面分组 ≤ 3 层 |
| 7 | 错误类型 | 不单独存列,由规则写法决定 |
| 8 | 级联清理 | 删模块/关系时业务层连带删规则(同权限点模式) |
| 9 | 批量 | ids 不设上限;整批一个数组参数 |
| 10 | 复制 | 规则可多选模块批量克隆;引用规则除外 |
| 11 | saveobjt 集成 | 引擎拆检查器/执行器;saveobjt 的 deletes 分支同事务调检查器,不过则整批回滚(§6.5);前端入参不变 |
| # | 事项 | 结论 | 版本 |
| --- | --- | --- | --- |
| 1 | 节奏 | 一期只做删除规则;保存校验二期再议,不留 `b_hook` 列 | v2 修订 |
| 2 | 跨表判断 | 不做计算字段,直接写 SQL 兜底 | v1 |
| 3 | SQL | 兜底逃生舱:不限制内容、不做体验优化、入口藏角落;**每次执行全程写日志** | v1 |
| 4 | 引用保护 | **`s_relation.b_on_delete='restrict'`(默认)表达,不进 `s_rule`**;删关系时规则随之消失,无孤儿清理 | v2 修订 |
| 5 | 归档 | 通用列 `b_archived` + `b_archive_datetime` | v1 |
| 6 | 条件规模 | 嵌套 ≤ 8 层、节点 ≤ 100;界面分组 ≤ 3 层 | v1 |
| 7 | 规则类型 | **显式 `b_kind` 列(condition / sql)**,不靠 `b_predicate` 首字符推断 | v2 修订 |
| 8 | 级联清理 | 删模块时服务层连带删 `s_rule`(`b_module_id` 单条件直查);关系级规则不复存在 | v2 修订 |
| 9 | 批量 | ids 不设上限;整批一个数组参数(OPENJSON) | v1 |
| 10 | 复制 | **全部规则可多选模块批量克隆**(v2 起无引用规则例外);复制时校验目标模块存在条件所用字段 | v2 修订 |
| 11 | saveobjt 集成 | 引擎拆检查器/执行器;saveobjt 的 deletes 分支同事务调检查器,不过则整批回滚(§6.5);前端入参不变 | v1 |
| 12 | 方向语义 | 四值统一描述「删除被引用方时引用方怎么办」;被引用方由基数判定(§3) | v2 新增 |
| 13 | 默认值 | `restrict`:被引用即不可删为默认,放开需显式改 `none` | v2 新增 |
| 14 | 存量迁移 | 库中无数据,直接按新结构建表,不做转换 | v2 新增 |