3 动态配置元数据模型设计

审批流的动态配置能力完全建立在规范化的元数据模型之上。本章定义五张核心元数据表及其关联关系:流程定义表、节点表、条件规则表、表单绑定表、字段JSON Schema。这五张表共同构成完整的流程元数据库,支撑后续的动态加载、运行时解析和可视化编辑。

3.1 五张元数据表结构

每张表均包含主键、外键约束、业务字段和审计字段。关键字段使用 JSON 类型储存非结构化配置,以适应审批场景的灵活扩展。

表名 主键 核心字段(示例) 关联关系
flow_def 流程定义表 flow_def_id (UUID) name, version, status, deployment_time, meta_version 父表,被 node、form_bind 引用
flow_node 节点表 node_id (UUID) flow_def_id, node_type (start/approval/condition/end), label, config_json, sort_order FK → flow_def.flow_def_id ON DELETE CASCADE
cond_rule 条件规则表 rule_id (UUID) node_id, expression (JSON), target_node_id, priority FK → flow_node.node_id;target_node_id 指向 flow_node.node_id
form_bind 表单绑定表 bind_id (UUID) flow_def_id, form_id, form_version, field_mapping (JSON), active FK → flow_def.flow_def_id
field_schema 字段JSON Schema表 schema_id (UUID) bind_id, field_path (JSON), schema_json (JSON), validators (JSON) FK → form_bind.bind_id ON DELETE CASCADE
设计要点:
· 使用 UUID 作为主键,避免自增 ID 在分布式部署和动态配置导入时冲突。
· config_json / expression / field_mapping / schema_json 均使用原生 JSON 类型存储(MySQL 8.0+ JSON 或 PostgreSQL JSONB),支持高效查询与校验。
· 外键使用 ON DELETE CASCADE,确保删除流程定义时级联清理节点、规则和绑定关系。

3.2 元数据关系与ER图

下表为五张表之间的物理外键关系图。注意 cond_rule 同时引用两个节点表记录,构成分支跳转回路。

图解说明: 矩形代表表,菱形箭头指向从表的主键,实线箭头的尾端为外键所在表。图中省略了审计字段(created_at、updated_at)以保持清晰。
动态配置元数据模型 ER 图 flow_def flow_def_id (PK) name, version, status meta_version, deploy_time flow_node node_id (PK) flow_def_id (FK) node_type, label, config_json FK cond_rule rule_id (PK) node_id (FK), target_node_id (FK) expression (JSON), priority FK( node_id ) FK( target ) form_bind bind_id (PK) flow_def_id (FK) form_id, field_mapping (JSON) FK field_schema schema_id (PK) bind_id (FK) field_path, schema_json (JSON) FK 图例 · 蓝色:流程主体定义表 · 红色:条件规则表(节点自引用) · 绿色:表单绑定表 · 紫色:字段 Schema 表(依赖表单绑定)

3.3 创建元数据表的DDL语句

以 MySQL 8.0+ 为例,演示包含外键约束和级联策略的完整建表脚本。实际项目中建议使用数据库迁移工具(如 Flyway / Liquibase)管理版本。

-- 流程定义表
CREATE TABLE flow_def (
    flow_def_id   CHAR(36) NOT NULL PRIMARY KEY,
    name          VARCHAR(128) NOT NULL,
    version       VARCHAR(16) NOT NULL DEFAULT '1.0',
    status        ENUM('draft','active','archived') NOT NULL DEFAULT 'draft',
    meta_version  VARCHAR(16) NOT NULL DEFAULT '1.0',
    deployment_at DATETIME,
    created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 节点表
CREATE TABLE flow_node (
    node_id     CHAR(36) NOT NULL PRIMARY KEY,
    flow_def_id CHAR(36) NOT NULL,
    node_type   ENUM('start','approval','condition','end') NOT NULL,
    label       VARCHAR(128) NOT NULL,
    config_json JSON,
    sort_order  INT NOT NULL DEFAULT 0,
    created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT fk_node_flow_def FOREIGN KEY (flow_def_id)
        REFERENCES flow_def(flow_def_id) ON DELETE CASCADE,
    INDEX idx_node_flow_order (flow_def_id, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 条件规则表
CREATE TABLE cond_rule (
    rule_id       CHAR(36) NOT NULL PRIMARY KEY,
    node_id       CHAR(36) NOT NULL,
    expression    JSON NOT NULL,
    target_node_id CHAR(36) NOT NULL,
    priority      INT NOT NULL DEFAULT 0,
    created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT fk_rule_node FOREIGN KEY (node_id)
        REFERENCES flow_node(node_id) ON DELETE CASCADE,
    CONSTRAINT fk_rule_target FOREIGN KEY (target_node_id)
        REFERENCES flow_node(node_id) ON DELETE CASCADE,
    INDEX idx_rule_node (node_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 表单绑定表
CREATE TABLE form_bind (
    bind_id       CHAR(36) NOT NULL PRIMARY KEY,
    flow_def_id   CHAR(36) NOT NULL,
    form_id       VARCHAR(64) NOT NULL,
    form_version  VARCHAR(16) NOT NULL DEFAULT '1.0',
    field_mapping JSON,
    active        TINYINT(1) NOT NULL DEFAULT 1,
    created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT fk_bind_flow_def FOREIGN KEY (flow_def_id)
        REFERENCES flow_def(flow_def_id) ON DELETE CASCADE,
    INDEX idx_bind_def (flow_def_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 字段JSON Schema 表
CREATE TABLE field_schema (
    schema_id   CHAR(36) NOT NULL PRIMARY KEY,
    bind_id     CHAR(36) NOT NULL,
    field_path  JSON NOT NULL COMMENT '字段路径,如 ["invoice", "amount"]',
    schema_json JSON NOT NULL COMMENT 'JSON Schema 片段',
    validators  JSON COMMENT '自定义校验规则覆盖',
    created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT fk_schema_bind FOREIGN KEY (bind_id)
        REFERENCES form_bind(bind_id) ON DELETE CASCADE,
    INDEX idx_schema_bind (bind_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
⚠️ 常见错误:级联删除顺序
如果 cond_rule 的 target_node_id 指向自身所属流程定义下的节点,删除 flow_def 时,MySQL 会先删除外表引用的行。但如果 cond_rule 定义了两个外键指向 flow_node,必须确保 flow_node 不会因为级联先被删完导致 cond_rule 的 target 外键失效。实际测试表明,INNODB 的级联顺序按外键创建时间依次处理,不会出现死锁。建议在删除流程定义前先手动清理节点和规则,或利用事务回滚。生产环境强烈推荐使用外键 ON DELETE RESTRICT 并配合业务层级联逻辑。

3.4 字段 JSON Schema 设计要点

field_schema.schema_json 遵循 JSON Schema 规范,每个字段可独立定义类型、格式、枚举值、最小/最大值等。配合 field_path(JSON 数组),可灵活描述嵌套表单的校验规则。

-- 示例:发票审批表单的字段 schema(插入数据)
INSERT INTO field_schema (schema_id, bind_id, field_path, schema_json, validators) VALUES
(
    UUID(),
    '已存在的bind_id',
    '["invoice", "amount"]',
    '{"type": "number", "minimum": 0, "maximum": 999999, "title": "发票金额"}',
    '{"required": true, "customLogic": "amount > 0 && amount < 1e6"}'
);
性能优化建议:
· 为 schema_json 和 expression 等 JSON 字段建立 虚拟列 + 索引(MySQL 8.0 支持 GENERATED COLUMN AS (schema_json->>"$.type")),可加速按 JSON 内部属性过滤的查询。
· 避免在 JSON 字段上直接使用 LIKE 或全文检索,优先使用 JSON_CONTAINS 和 JSON_EXTRACT。
· 如果元数据频繁更新,考虑使用 ORM 二级缓存(如 Redis)减少数据库查询压力,但必须保证缓存失效策略与业务级联删除一致。

3.5 调试与验证

编写 DDL 后,可通过以下方式验证元数据模型是否正确:

  1. 数据库本身检查:SHOW CREATE TABLE flow_def; 确认外键定义无误。
  2. 插入测试数据:插入一条流程定义,再插入对应节点和规则,确认无外键冲突。
  3. 级联删除测试:DELETE FROM flow_def WHERE flow_def_id = 'xxx'; 检查子表是否自动清理(注意 MySQL 默认不报错但可能违反外键顺序)。
  4. JSON 校验:在应用层使用 JSON.is_valid() 或数据库函数验证 schema_json 的语法是否正确。

3.6 部署注意事项

使用容器化部署时,建议在应用启动时自动执行初始化 DDL(通过 schema.sql 或 ORM 的自动建表功能)。如果使用 Kubernetes Init Container 初始化数据库,请确保 JSON 列名的字符集和排序规则一致(UTF-8)。

# 在构建阶段将 DDL 文件打包到镜像中
# 启动命令示例
wait-for-it db:3306 -- mysql -h db -u root -p$MYSQL_ROOT_PASSWORD < /docker-entrypoint-initdb.d/schema.sql

3.7 常见错误排查

错误现象 可能原因 解决方案
Can't create table flow_node (errno: 150 - Foreign key constraint is incorrectly formed) 外键引用的主表 flow_def 不存在或引擎非 InnoDB 确认主表已创建且使用 ENGINE=InnoDB;检查字段类型是否匹配(CHAR vs VARCHAR)
更新 cond_rule 时报告 Cannot add or update a child row target_node_id 指向的节点不属于同一个 flow_def 或者尚未插入 先插入所有节点,再插入规则;应用层校验目标节点存在且属于同一流程
DELETE FROM flow_def 后子表仍有残留数据 外键 ON DELETE 不是 CASCADE,或存储引擎不支持(MyISAM) 改表为 InnoDB,并确保外键已正确添加
JSON 字段插入后查询乱码 连接字符集不是 utf8mb4 设置连接 SET NAMES utf8mb4;;表字段使用 CHARSET utf8mb4

3.8 小结