13-describe():字段元数据JSON的生成

CommonQuery零代码查询的第一步就是调describe——前端拿到字段元数据数组,才知道渲染什么控件、什么列、什么校验。这篇拆describe()怎么把缓存里的四类元数据拼成前端能消费的JSON,以及mapDisplayType映射表。

源码:browise-metadata/src/main/java/com/browise/ea/core/engine/EaEngine.java L77-150


一、describe在前端的三条消费路径

① CommonQuery.vue挂载
     describe(sqlId) → conditions渲染查询表单 + columns渲染列表

② CommonForm.vue
     describe(sqlId) → fields渲染编辑表单(label/required/pattern/控件类型)

③ FormEditor设计器
     SqlSelectDialog选SQL → describe(sqlId) → 字段面板可选字段列表

一次调用,三处消费——describe是前后端元数据协作的唯一出口


二、输出的完整JSON结构

{
  "sqlCode": "SYS_USER_s",
  "title": "用户查询",
  "sqlMode": "1",
  "hasRawSql": false,
  "columns": [
    {
      "name": "psnName",
      "column": "PSN_NAME",
      "label": "姓名",
      "dataType": "1",
      "displayType": "text",
      "visible": true,
      "required": false,
      "width": 120,
      "order": 1,
      "codeName": null,
      "codeItem": false,
      "colSpan": 1,
      "rowSpan": 1,
      "encrypted": false,
      "masked": false,
      "pattern": "^[\\u4e00-\\u9fa5]{2,10}$",
      "maskRule": "1,1"
    }
  ],
  "conditions": [
    {
      "paramName": "psnName",
      "label": "姓名",
      "dataType": "1",
      "displayType": "text",
      "required": false,
      "operator": "like"
    }
  ]
}

columns来自EA03(字段)、conditions来自EA05(查询条件)——同一份元数据的两种视角:columns管"查出来怎么显示",conditions管"用什么参数查"。


三、手拼JSON的60行

public String describe(String eaCode) {
    EaSqlMeta sqlMeta = resolveSqlMeta(eaCode);
    List<EaFieldMeta> fields = cache.getFieldMetas(eaCode);
    List<EaConditionMeta> conditions = cache.getConditionMetas(eaCode);

    StringBuilder sb = new StringBuilder("{");
    sb.append("\"sqlCode\":\"").append(escapeJson(eaCode)).append("\",");
    sb.append("\"title\":\"").append(escapeJson(sqlMeta.getTitle())).append("\",");
    sb.append("\"sqlMode\":\"").append(sqlMeta.getSqlMode()).append("\",");
    sb.append("\"hasRawSql\":").append(sqlMeta.isHasRawSql()).append(",");

    sb.append("\"columns\":[");
    for (int i = 0; i < fields.size(); i++) {
        EaFieldMeta f = fields.get(i);
        // 16个字段逐个append——null值不输出引号直接null
        sb.append("{\"name\":\"").append(escapeJson(f.getFieldAlias())).append("\",");
        sb.append("\"column\":\"").append(escapeJson(f.getColumnName())).append("\",");
        // ... label/dataType/displayType/visible/required/width/order
        //     codeName/codeItem/colSpan/rowSpan/encrypted/masked
        // pattern/maskRule只在非空时追加(可选字段)
    }
    sb.append("],");

    sb.append("\"conditions\":[");
    for (EaConditionMeta c : conditions) {
        // paramName/label/dataType/displayType/required/operator
        // label为空时回退用paramName展示
    }
    sb.append("]");

    return sb.append("}").toString();
}

escapeJson处理五个字符:

private String escapeJson(String s) {
    return s.replace("\\", "\\\\").replace("\"", "\\\"")
            .replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
}

为什么不引Jackson? 第06篇说过——metadata引擎保持纯JDK零依赖。代价是手拼+转义,收益是引擎可以脱离任何JSON生态独立编译测试。元数据字段全是简单类型(字符串/数字/布尔),手拼没有嵌套对象/数组的复杂性——这个场景下手拼可控。


四、mapDisplayType:元数据码到前端控件

private String mapDisplayType(String eae991) {
    if (eae991 == null) return "text";
    switch (eae991) {
        case "2": return "date";
        case "3": return "number";
        case "4": return "datetime";
        case "5": return "date";
        default:  return "text";     // "1"和未知值都兜底text
    }
}

这是EA体系(eae991数字码)和前端体系(语义字符串)的翻译表

eae991 displayType 前端渲染
1/null text TextBox
2 date DateBox
3 number NumberBox
4 datetime 日期+时间选择
5 date 兼容别名,同date

为什么不直接存"date"/"number"进元数据?——EA表结构从1990年代社保标准延续下来(eae991这套编号是原电子政务标准),browise不动存量元数据的编码习惯,在出口处翻译。前端组件也不感知EA编码——两边的知识边界就在这个switch。

codeName有值时前端还会再升一级:ComboBox + 字典选项(HttpClient拉AA10码表)。


五、conditions的operator

sb.append("\"operator\":\"").append(c.getOperatorSymbol()).append("\"");

EaConditionMeta.getOperatorSymbol()把EAE012翻译成SQL符号(第08篇的表)。describe把它透传给前端——前端据此决定查询控件的形态

operator 前端行为
= 单值输入框
like 单值输入框(提示模糊)
> / >= 范围起点(通常配对出现两个条件)
< / <= 范围终点

CommonQuery遇到 >= startDate + <= endDate 两个同名参数的条件对,渲染成日期范围控件——元数据里没存"范围"这个概念,前端靠运算符模式识别。轻量但有约定:配范围条件时EA05要连续配两行。


六、pattern与maskRule的透传

if (f.getRegexRule() != null && !f.getRegexRule().isEmpty()) {
    sb.append(",\"pattern\":\"").append(escapeJson(f.getRegexRule())).append("\"");
}
if (f.getMaskRule() != null && !f.getMaskRule().isEmpty()) {
    sb.append(",\"maskRule\":\"").append(escapeJson(f.getMaskRule())).append("\"");
}

可选字段非空才输出——JSON瘦身。两个字段在前端的用途:

  • pattern → 表单校验规则(required + pattern双重前端校验,引擎层validateParams兜底)
  • maskRule → 列表脱敏显示("3,4"→保头3保尾4;后端postProcess也用它做查询结果脱敏——同一个规则字符串,前端管显示后端管数据,双保险

七、describe的缓存命中路径

describe不走数据库——纯缓存操作:

describe("SYS_USER_s")
  → resolveSqlMeta → cache(ConcurrentHashMap读)
  → cache.getFieldMetas / getConditionMetas(预索引的List)
  → StringBuilder拼装
  → 返回

零SQL、零IO、微秒级。管理界面改了元数据后要 refresh()(第06篇的synchronized重载)——新元数据才可见。describe的高频调用(每个页面挂载都调)不打数据库,这是"启动全量装载+运行时纯内存"缓存策略的回报。


✅ 亮点:describe作为前后端元数据协作唯一出口的三条消费路径、手拼JSON的依赖取舍、mapDisplayType作为EA编码与前端控件的翻译边界、pattern/maskRule前端显示后端数据的双保险。适合设计元数据API的人。扩展方向:第14篇字段级SM4加密、第48篇CommonQuery怎么消费这份JSON。

Logo

一站式 AI 云服务平台

更多推荐