后端输出元模型(Meta) -> 前端按 Meta 渲染与补默认值 而不是前端自己写死规则。
最简实现路径(两步) 1)后端:提供“可直接驱动前端”的 Meta 接口 接口返回每个字段类型的定义,比如 TextField:
支持哪些属性(如 FieldName/Length/Nullable/DefaultValue) 每个属性的默认值(如 Length=255, Nullable=true) 每个属性的校验规则(如最大长度、必填条件) 前端拿到后,就知道“该展示什么、默认填什么、怎么校验”。
2)前端:严格按 Meta 生成默认值 前端新增字段时,不再写:
if TextField then Length=255(禁止写死) 而是改成:
从后端 Meta 里读取 TextField 的默认定义 自动填充到新字段草稿里 用户再改动时覆盖默认值 保存时后端再兜底(必须) 即使前端已按 Meta 补默认,后端保存仍要再做一次:
normalize(白名单) 按 Meta 补缺失默认值 校验 映射领域对象再落库 这样可以防止前端绕过或版本不一致。
你要的验收(改成你能直接验) 前端新增 TextField 时,默认值来自后端 Meta,不是前端常量 后端改了 TextField 默认长度(如 255->128),前端无代码改动即可生效 保存后 DB 中值来自“后端规则+领域对象序列化”
保存时不能直接存前端 JSON,必须走:
payload -> 强类型对象(TextField/NumberField/DateField…) -> 序列化 -> 落库
为什么必须这样 约束可控:每种字段类型有自己校验逻辑(TextField 长度、Number 精度等) 版本可演进:对象结构变更可以做版本迁移 防前端污染:前端乱传字段不会直接入库 可审计:序列化内容是标准领域结构,稳定可回放 后端实现最小方案 1)字段基类 + 子类 BaseField(通用属性:Id/Name/FieldName/Nullable/DefaultValue…) TextField extends BaseField(Length/Pattern…) NumberField extends BaseField(Precision/Scale…) 2)类型映射器 FieldTypeMapper.toDomain(Map row) FieldType=TextField -> TextField FieldType=NumberField -> NumberField 3)保存链路 normalize 白名单 row -> domain object domain.validate()(类型内校验) ObjectMapper 序列化领域对象集合 写入 md_entity_definition 4)版本化 序列化时带:
schemaVersion fieldType typeVersion(可选) 便于以后升级迁移脚本。
第一步:后端元模型驱动前端默认值 1.1 后端提供 Meta 接口 GET /metadata/getDomainModel?modelType=ui 返回结构里,每个字段类型必须包含:
字段类型标识:TextField 可配置属性列表:FieldName/Length/Nullable/DefaultValue… 每个属性的默认值:例如 Length=255, Nullable=true 校验规则:例如 Length <= 4000 前端新增字段时只认这里,不认本地常量。
1.2 前端新增字段流程 当用户选择“新增 TextField”:
调用/读取已缓存的 DomainModel 找到 TextField 的属性定义 用属性里的 default 组装初始字段对象 回填到编辑表单 伪流程: selectType(TextField) -> loadMeta(TextField) -> buildDefaultField(meta.props) -> render
1.3 后端保存兜底(防绕过) 即使前端已补默认,后端保存仍做:
normalize 白名单 按 Meta 再补一次缺失默认值(只补空,不覆盖用户输入) 按 Meta 校验合法性 进入第二步强类型映射 第二步:保存链路强类型领域化(TextField 等) 2.1 领域模型 BaseField(公共属性) TextField extends BaseField(Length/Pattern) NumberField extends BaseField(Precision/Scale) …(后续类型逐步补) 2.2 映射逻辑(Map -> 强类型) 新增 FieldDomainMapper:
输入:Map<String,Object> row 根据 FieldType 分派: TextField -> toTextField(row) NumberField -> toNumberField(row) 输出:BaseField 子类实例 2.3 类型内校验 每个子类执行自己的规则校验,例如:
TextField:Length 必须数字且范围合法 NumberField:Precision/Scale 关系合法 校验失败直接抛业务异常(4xx)。
2.4 序列化落库 最终落库不是前端 Map,而是:
List<BaseField(子类实例)> -> ObjectMapper -> md_entity_definition
并带版本字段:
schemaVersion fieldType (可选)typeVersion 开工顺序(建议) 先补 FieldDomainMapper + BaseField/TextField 最小闭环 替换保存链路:Map直存 -> 强类型映射后序列化 前端新增字段改为“读 Meta 默认值” 联调验证(改后端默认长度,前端自动生效)
结构 BaseField(基类) 放所有字段共有属性: Id, ParentId, Name, FieldName, FieldType, Nullable, DefaultValue, …
XxxField(子类) 放类型特有属性:
TextField: Length, Pattern NumberField: Precision, Scale DateField: Format … 通用保存链路(不写死 TextField) payload -> normalize -> 按 FieldType 选子类 -> 填充 BaseField 公共属性 -> 填充子类特有属性 -> 校验 -> 序列化落库
关键是:
公共属性映射统一走 BaseFieldMapper 子类属性映射通过 FieldTypeHandlerRegistry 分发 每个子类 handler 只管自己特有部分