SceneSchemaService.java
9.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package com.onto.engine.meta;
import com.onto.engine.model.ModelRepository;
import com.onto.engine.security.MaskService;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* 场景渲染元数据合成服务:把 M8 页面模板与 M1 字段类型、M5 脱敏、M2 命令、M3 接口、MetaRule 规则
* 合并为一份"前端通用渲染引擎"可直接消费的 Schema。前端据此动态生成"新增订单"页,无任何业务硬编码。
*/
@Service
public class SceneSchemaService {
private final ModelRepository models;
private final MaskService mask;
public SceneSchemaService(ModelRepository models, MaskService mask) {
this.models = models;
this.mask = mask;
}
public Map<String, Object> buildSceneSchema(String sceneId) {
Map<String, Object> scene = models.scene(sceneId);
if (scene.isEmpty()) throw new NoSuchElementException("未找到场景: " + sceneId);
Map<String, Object> template = models.templateByScene(sceneId);
// 从 M4 flowSteps(含嵌套控制流)递归找到本场景绑定的命令
String[] bound = findBoundCommand(asList(scene.get("flowSteps")), new String[]{null, null});
String cmdAggregate = bound[0], cmdId = bound[1];
Map<String, Object> out = new LinkedHashMap<>();
out.put("sceneId", sceneId);
out.put("sceneName", scene.get("sceneName"));
out.put("aggregateScope", scene.get("aggregateScope"));
out.put("permissionBind", scene.get("permissionBind"));
// M2 命令(前端提交时据此把表单值映射为命令入参)
if (cmdId != null) {
Map<String, Object> cmd = models.command(cmdAggregate, cmdId);
Map<String, Object> cmdOut = new LinkedHashMap<>();
cmdOut.put("aggregateId", cmdAggregate);
cmdOut.put("cmdId", cmdId);
cmdOut.put("desc", cmd.get("desc"));
cmdOut.put("inputParams", cmd.get("inputParams"));
cmdOut.put("validations", cmd.get("validations"));
cmdOut.put("bizSteps", cmd.get("bizSteps"));
cmdOut.put("emitEvents", cmd.get("emitEvents"));
out.put("command", cmdOut);
// M3 命令 API 地址(前端无硬编码 URL,从模型取)
Object api = asMap(models.deployOf(cmdAggregate).get("commandApi")).get(cmdId);
out.put("commandApi", api);
}
// M8 模板(逐组件富化 M1 类型 / M5 脱敏 / 引用下拉数据源)
if (!template.isEmpty()) {
Map<String, Object> tOut = new LinkedHashMap<>();
tOut.put("templateId", template.get("templateId"));
tOut.put("templateName", template.get("templateName"));
tOut.put("templateType", template.get("templateType"));
tOut.put("renderMode", template.get("renderMode"));
tOut.put("masterAggregate", template.get("masterAggregate"));
tOut.put("detailAggregates", template.get("detailAggregates"));
List<Object> enriched = new ArrayList<>();
for (Object c : asList(template.get("rootComponents"))) {
enriched.add(enrich(asMap(c)));
}
tOut.put("rootComponents", enriched);
out.put("template", tOut);
}
// MetaRule 规则(前端实时校验用,与后端同源)
List<Map<String, Object>> rules = new ArrayList<>();
for (Map<String, Object> group : models.ruleGroupsByScene(sceneId)) {
for (Object r : asList(group.get("rules"))) {
Map<String, Object> rule = asMap(r);
Map<String, Object> ro = new LinkedHashMap<>();
ro.put("groupId", group.get("groupId"));
ro.put("ruleId", rule.get("ruleId"));
ro.put("ruleName", rule.get("ruleName"));
ro.put("effect", rule.get("effect"));
ro.put("message", rule.get("message"));
ro.put("matchCondition", rule.get("matchCondition"));
rules.add(ro);
}
}
out.put("rules", rules);
out.put("ruleGlobalParams", models.ruleGlobalParams());
// 溯源信息(供前端"运行原理"面板展示每部分来自哪一层)
Map<String, String> provenance = new LinkedHashMap<>();
provenance.put("template", "M8-front-schema.yaml");
provenance.put("fieldType", "M1-domain.yaml");
provenance.put("command", "M2-command.yaml");
provenance.put("commandApi", "M3-deploy.yaml");
provenance.put("mask", "M5-security.yaml");
provenance.put("rules", "MetaRule-business.yaml");
provenance.put("permission", "M5-security.yaml (functionPermissions)");
out.put("sourceLayers", provenance);
return out;
}
/** 递归富化一个 M8 组件。 */
private Map<String, Object> enrich(Map<String, Object> comp) {
Map<String, Object> out = new LinkedHashMap<>();
out.put("compId", comp.get("compId"));
out.put("compType", comp.get("compType"));
out.put("width", comp.get("width"));
out.put("span", comp.get("span"));
Map<String, Object> fb = asMap(comp.get("fieldBind"));
if (!fb.isEmpty()) {
Map<String, Object> fbOut = new LinkedHashMap<>(fb);
String agg = String.valueOf(fb.get("bindAggregateId"));
String sourceType = String.valueOf(fb.get("bindSourceType"));
String entityName = fb.get("domainEntityName") == null ? null : String.valueOf(fb.get("domainEntityName"));
String field = String.valueOf(fb.get("domainFieldName"));
String entForType = "aggregate_root".equals(sourceType) ? null : entityName;
fbOut.put("fieldType", models.fieldType(agg, entForType, field));
boolean masked = Boolean.TRUE.equals(fb.get("maskField")) || mask.isMasked(field);
fbOut.put("masked", masked);
// 下拉引用数据源(通用,全部源自模型;无任何具体聚合/字段字面量):
// 1) select 绑定到某聚合根【自身标识】字段 => 选项来自该聚合(如客户/商品/订单下拉);
// 2) select 绑定到带 M1 refAggregate 的【引用】字段 => 选项来自被引用聚合,值=对方标识、回填本字段
// (如订单审核里 auditorId 引用 EmployeeAggregate.employeeId,渲染为员工下拉)。
if ("select".equals(comp.get("compType"))) {
if ("aggregate_root".equals(sourceType) && field.equals(models.rootIdentifier(agg))) {
fbOut.put("optionsSource", optionsSource(agg));
} else {
Map<String, Object> ref = models.refInfo(agg, entForType, field);
if (!ref.isEmpty()) fbOut.put("optionsSource", optionsSource(String.valueOf(ref.get("refAggregate"))));
}
}
out.put("fieldBind", fbOut);
}
List<Object> children = asList(comp.get("childComponents"));
if (!children.isEmpty()) {
List<Object> co = new ArrayList<>();
for (Object ch : children) co.add(enrich(asMap(ch)));
out.put("childComponents", co);
}
return out;
}
/** 某聚合根的下拉选项数据源描述(value=聚合根标识,label=展示字段,endpoint=/api/data/agg,通用)。 */
private Map<String, Object> optionsSource(String aggregateId) {
Map<String, Object> src = new LinkedHashMap<>();
src.put("aggregateId", aggregateId);
src.put("endpoint", "/api/data/" + aggregateId);
src.put("valueField", models.rootIdentifier(aggregateId));
src.put("labelField", displayField(aggregateId));
return src;
}
/** 聚合根的展示字段:首个 string 类型属性(如 customerName/productName),否则用标识字段。 */
private String displayField(String aggregateId) {
for (Object at : models.rootAttributes(aggregateId)) {
Map<String, Object> am = asMap(at);
if ("string".equals(am.get("type"))) return String.valueOf(am.get("name"));
}
return models.rootIdentifier(aggregateId);
}
/** 递归定位场景绑定的主命令(含嵌套控制流),返回 {aggregate, cmdId}(后者为最后命中)。 */
private static String[] findBoundCommand(List<Object> steps, String[] acc) {
for (Object st : steps) {
Map<String, Object> stp = asMap(st);
if ("bindCommand".equals(stp.get("stepType"))) {
acc[0] = String.valueOf(stp.get("bindAggregate"));
acc[1] = String.valueOf(stp.get("bindCommand"));
}
for (String key : List.of("then", "else", "body", "default")) findBoundCommand(asList(stp.get(key)), acc);
for (Object b : asList(stp.get("branches"))) findBoundCommand(asList(asMap(b).get("steps")), acc);
for (Object c : asList(stp.get("cases"))) findBoundCommand(asList(asMap(c).get("steps")), acc);
}
return acc;
}
@SuppressWarnings("unchecked")
private static Map<String, Object> asMap(Object o) {
return o instanceof Map ? (Map<String, Object>) o : Collections.emptyMap();
}
@SuppressWarnings("unchecked")
private static List<Object> asList(Object o) {
return o instanceof List ? (List<Object>) o : Collections.emptyList();
}
}