用户自定义字段(Custom Fields)设计方案,兼顾多语言、分组、校验、权限、查询性能、审计与多租户
1. 核心理念
- 字段=元数据:把“字段”本身当数据管理(而不是改表结构)。
- 值=数据:业务记录与字段定义分离存储。
- 渲染=规则:前端按字段元数据动态渲染表单与详情;后端按校验规则校验。
2. 适用场景与存储策略
三种常见模型,可混合使用:
- EAV(Entity–Attribute–Value):适合“字段很多但稀疏”的大规模扩展;查询复杂度高。
- JSON 列(推荐 for PG):主表保留
extra_fields jsonb
,查询用 GIN 索引与生成列;实现简单,性能可控。 - 派生物化列:对高频查询的关键自定义字段,使用生成列/冗余列或同步表提升检索性能。
在 PostgreSQL/云原生环境中,建议:主表 + jsonb + GIN 索引,对热点字段再加生成列或物化视图。
3. 元数据建模(字段定义)
最小可用的 4 张表(支持多租户、分组、排序、i18n、权限与校验):
-- 自定义表单/实体(例如:文物、资产、客户…)
CREATE TABLE cf_entity (id BIGSERIAL PRIMARY KEY,tenant_id BIGINT NOT NULL,code VARCHAR(64) NOT NULL UNIQUE, -- 如 "artifact"name_i18n_key VARCHAR(128) NOT NULL, -- i18n keystatus SMALLINT DEFAULT 1, -- 1启用/0禁用created_at TIMESTAMP NOT NULL DEFAULT NOW()
);-- 字段分组(用于页面分区与折叠)
CREATE TABLE cf_field_group (id BIGSERIAL PRIMARY KEY,tenant_id BIGINT NOT NULL,entity_code VARCHAR(64) NOT NULL,code VARCHAR(64) NOT NULL,name_i18n_key VARCHAR(128) NOT NULL,sort_order INT DEFAULT 0
);-- 字段定义(元数据核心)
CREATE TABLE cf_field_def (id BIGSERIAL PRIMARY KEY,tenant_id BIGINT NOT NULL,entity_code VARCHAR(64) NOT NULL,group_code VARCHAR(64), -- 归属分组field_key VARCHAR(64) NOT NULL, -- 唯一键,如 "material"label_i18n_key VARCHAR(128) NOT NULL, -- 标签 i18ntype VARCHAR(32) NOT NULL, -- text/textarea/number/date/datetime/select/multi_select/switch/image/file/ref/...required BOOLEAN DEFAULT FALSE,visible BOOLEAN DEFAULT TRUE,editable BOOLEAN DEFAULT TRUE,unique_scope VARCHAR(32), -- unique_in_entity / unique_in_tenant / nullplaceholder_i18n_key VARCHAR(128),help_i18n_key VARCHAR(128),default_value JSONB, -- 默认值(随类型)options JSONB, -- 选项(下拉/多选)/远程数据源配置/联动规则等validate_rules JSONB, -- 校验规则:正则、最小最大值、长度、必填条件表达式permission_expr JSONB, -- 权限规则:roles、scopes、dept、dataScopesort_order INT DEFAULT 0,version INT DEFAULT 1, -- 版本(用于灰度与历史追溯)status SMALLINT DEFAULT 1,updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);-- 选项字典(可独立维护,或放到 options 中)
CREATE TABLE cf_option (id BIGSERIAL PRIMARY KEY,tenant_id BIGINT NOT NULL,field_key VARCHAR(64) NOT NULL,value VARCHAR(128) NOT NULL,label_i18n_key VARCHAR(128) NOT NULL,sort_order INT DEFAULT 0,enabled BOOLEAN DEFAULT TRUE
);
4. 业务数据建模
4.1 主业务表(建议 jsonb 承载)
-- 示例:artifact 文物主表(只展示关键列)
CREATE TABLE artifact (id BIGSERIAL PRIMARY KEY,tenant_id BIGINT NOT NULL,name VARCHAR(255) NOT NULL,extra_fields JSONB DEFAULT '{}'::jsonb, -- 自定义字段值created_at TIMESTAMP NOT NULL DEFAULT NOW()
);-- GIN 索引支持 key/contain 查询
CREATE INDEX idx_artifact_extra_fields ON artifact USING GIN (extra_fields jsonb_path_ops);-- 对高频字段生成列(便于排序/where/join)
ALTER TABLE artifactADD COLUMN material GENERATED ALWAYS AS ((extra_fields->>'material')) STORED;CREATE INDEX idx_artifact_material ON artifact(material);
4.2 EAV(如需)
CREATE TABLE cf_value (id BIGSERIAL PRIMARY KEY,tenant_id BIGINT NOT NULL,entity_code VARCHAR(64) NOT NULL,record_id BIGINT NOT NULL, -- 关联业务记录idfield_key VARCHAR(64) NOT NULL,value_text TEXT,value_num NUMERIC(20,6),value_time TIMESTAMP,value_json JSONB,UNIQUE(tenant_id, entity_code, record_id, field_key)
);
5. 字段生命周期与版本
- 草稿→发布:字段定义支持版本号与状态;新版本发布对新建记录生效,老记录继续可读。
- 禁用/删除:仅标记禁用;保留历史值。
- 迁移器:当字段类型变化(如 text→select),提供后台数据迁移工具与回滚脚本。
6. 权限与可见性
- permission_expr 用于表达:
roles: ["curator","admin"], scopes:["artifact:edit"]
,以及条件可见/可编辑(基于租户、部门、记录状态)。 - 后端在读定义与保存值时都要二次校验,前端仅做 UX 限制。
7. 校验与联动
- validate_rules 结构示例:
{"requiredIf": "record.state == 'ON_SHOW'","regex": "^[A-Za-z0-9-]{1,32}$","min": 0,"max": 1000,"maxLength": 64,"custom": "script: value.startsWith('NHB-')" // 可选:受控脚本/DSL
}
- options 可配置远程数据源、联动逻辑:
{"datasource": { "type": "dict", "code": "MATERIAL_TYPE" },"cascade": { "parent": "category", "api": "/api/materials?cat=${value}" },"ui": { "widget": "select", "multiple": false, "clearable": true }
}
8. 查询与性能
-
常用查询走生成列或物化视图;
-
复杂筛选用
jsonb_path_query
/@>
; -
索引策略:
GIN
on(extra_fields)
;- 针对关键路径的表达式索引:
CREATE INDEX ON artifact ((extra_fields->>'material'));
-
归档:历史大表分区;冷数据移仓。
9. 审计与历史
- cf_field_def_audit:记录字段定义变更(谁、何时、前后对比 diff)。
- cf_value_audit:记录业务字段值变更(包含旧值/新值、校验结果、IP)。
- 便于合规(如政府项目)与问题回溯。
10. API 设计(REST/GraphQL)
GET /cf/entities/{entityCode}/fields?include=groups,options
POST /cf/entities/{entityCode}/fields
(增/改字段定义,需审批或发布)GET /{entityCode}/{id}
(返回业务数据 + 已过滤的字段定义)PUT /{entityCode}/{id}
(提交extra_fields
,后端按定义校验 + 权限)POST /cf/search/{entityCode}
(复杂筛选:结构化条件 + JSON 路径)
后端校验伪码:
// pseudo
Map<String, Object> values = request.getExtraFields();
List<FieldDef> defs = fieldService.listFor(entityCode, tenantId, roleCtx);
for (FieldDef def : defs) {if (!def.visible || !def.editableBy(roleCtx)) continue;Object v = values.get(def.fieldKey);validateRequired(def, v, recordCtx);validateType(def.type, v);validateRules(def.validateRules, v, recordCtx); // regex/min/max/customvalidateUnique(def.uniqueScope, def.fieldKey, v, recordId, tenantId);
}
save(values);
11. 前端渲染(React 示例思路)
- 拉取
entityCode
的字段定义 + 分组; - 按
group.sort_order
分区,按field.sort_order
排序; - 根据
type
映射组件(Input/Select/Checkbox/DatePicker/Upload/ImageCropper/RefPicker…); - 根据
permission_expr
控制disabled/hidden
; - 根据
validate_rules
生成 Form 规则; - 多语言从
*_i18n_key
取词条。
字段定义→渲染映射(示例)
type WidgetMap = {text: Input;number: InputNumber;date: DatePicker;datetime: DatePicker; // showTimeselect: Select;multi_select: Select; // mode="multiple"switch: Switch;image: Upload;file: Upload;ref: RemoteSelect; // 可搜索引用其它实体
};
12. 多租户与隔离
- 所有表加
tenant_id
; - 字段定义可租户级或平台级(全租户可用),平台级字段在租户端可“继承 + 局部覆盖 UI/校验”;
- 数据访问层统一注入
tenant_id
过滤器(例如 MyBatis 拦截器)。
13. 报表与导出
- 导出时根据最新字段定义动态列头(i18n 标签)与类型格式化;
- 对“超宽表”导出,提供选择字段与分页导出;
- 为常用字段建立物化视图用于 BI 查询。
14. 变更治理与灰度
- 草稿/发布两套集合,发布后标注
active_version
; - 前端带版本号请求;
- 支持字段级灰度(按租户/部门/角色启用新字段)。
15. 典型坑与规避
- 查询慢:未加 GIN/表达式索引;把所有筛选都丢给 jsonb。
- 无限制脚本校验:使用受限 DSL/白名单,避免注入与性能风险。
- 字段滥用:缺少审批流与命名规范(
[模块]-[业务]-[含义]
)。 - 强删字段:历史数据丢失;应软删 + 数据迁移器。
- 权限只做前端:必须后端再校验。
速用清单
- 用 jsonb + GIN 存值;高频筛选做生成列/表达式索引。
- 4 张元数据表:
cf_entity / cf_field_group / cf_field_def / cf_option
。 - 字段定义含 i18n、分组、排序、校验、权限、默认值、选项、联动、版本。
- API:获取定义→渲染;提交值→后端统一校验→持久化。
- 审计与灰度:定义与值都做版本/审计,字段变更有迁移器。