13-describe元数据输出
13-describe():字段元数据JSON的生成
CommonQuery零代码查询的第一步就是调describe——前端拿到字段元数据数组,才知道渲染什么控件、什么列、什么校验。这篇拆describe()怎么把缓存里的四类元数据拼成前端能消费的JSON,以及mapDisplayType映射表。
文章目录
源码:
browise-metadata/src/main/java/com/browise/ea/core/engine/EaEngine.javaL77-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。
更多推荐



所有评论(0)