Skip to content

Commit ed5ea5e

Browse files
committed
#AI commit# CC: REQ-01文件数据集导入功能-params重建修复(方案A)+设计文档:文件source作业编辑态加载时getDecoratedJob用exchangis_job_param_config表查询重建params,FILE在该表无注册(M5)导致params.sources重建为空覆盖原始jobContent,前端无法回显encoding/delimiter/nullFormat及__file_bml_* BML引用。AbstractDataSourceService.buildAllUI加FILE分支:先标准构建paramsUI(sink始终走标准逻辑),再对FILE source用新helper buildFileSourceParamsUIFromContent从原始content.getSources()透传为InputElementUI列表(key/field=configKey,value=configValue,sort),保留全部7项(__file_bml_resource_id/version/owner/name+encoding/delimiter/nullFormat)及用户改过的值,__file_bml_*原样保留前端按前缀识别。非FILE类型零影响。parseDataSourceIdUi经确认无需改(FILE sourceId空跳过split,构造器保留type,BML引用住params.sources由本修复透传)。附params重建补充设计文档(开源版) / REQ-01 file dataset import params rebuild fix (solution A) + design doc: on file source job edit-load getDecoratedJob rebuilds params via exchangis_job_param_config table query, but FILE has no registration there (M5) so params.sources rebuilds empty and overwrites the original jobContent, losing encoding/delimiter/nullFormat and __file_bml_* BML refs. AbstractDataSourceService.buildAllUI adds FILE branch: build paramsUI standardly first (sink always standard), then for FILE source override sources via new helper buildFileSourceParamsUIFromContent which passes through original content.getSources() as InputElementUI list (key/field=configKey, value=configValue, sort), preserving all 7 items and user-edited values; __file_bml_* kept as-is, frontend identifies by prefix. Zero impact on non-FILE. parseDataSourceIdUi confirmed no change needed. Includes the params rebuild supplementary design doc (open-source)
1 parent 89c8615 commit ed5ea5e

2 files changed

Lines changed: 273 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# REQ-01 文件数据集导入功能 — params 重建补充设计(开源)
2+
3+
> 配套文档:`REQ-01_文件数据集导入功能_设计_开源.md``REQ-01_文件数据集导入功能_fieldMap字段映射补充设计_开源.md`
4+
>
5+
> 本文档针对 **文件 source 作业编辑态加载时 `params.sources` 丢失** 问题,定位后端根因并给出修复方案,供开源团队在 `../exchangis` 实施。
6+
7+
---
8+
9+
## 1. 问题现象
10+
11+
保存文件 source 作业后,再次编辑加载,前端从 `GET /dss/exchangis/main/job/{id}` 拿到的 `result.jobContent``subJobs[i].params.sources`**空数组**
12+
13+
- 数据库 `exchangis_job_entity.job_content` 字段**含完整 params.sources**(前端保存时发的 saveContent,`config_key` 格式,含 `__file_bml_resource_id / __file_bml_version / __file_bml_owner / __file_bml_name` + `encoding / delimiter / nullFormat`)。
14+
- 但接口返回的 jobContent 里 `params.sources` 为空 → 前端 dyncRender 无参数可渲染 → 文件 source 的编码/分隔符/空值格式 + BML 引用全部丢失,无法回显,用户改过的值也丢。
15+
16+
---
17+
18+
## 2. 根因定位
19+
20+
### 2.1 调用链
21+
22+
```
23+
GET /dss/exchangis/main/job/{id}
24+
ExchangisJobRestfulApi.getJob() (ExchangisJobRestfulApi.java:320)
25+
→ jobInfoService.getDecoratedJob(request, id) ← "Decorated" = 装饰过,非原始 jobContent
26+
DefaultJobInfoService.getDecoratedJob() (DefaultJobInfoService.java:222)
27+
→ uiGetter.getJobDataSourceUIs(request, id) ← 用 UI 配置重建
28+
DefaultDataSourceService.getJobDataSourceUIs() (DefaultDataSourceService.java:145)
29+
→ buildAllUI(request, job, cnt) (AbstractDataSourceService.java:116)
30+
→ buildDataSourceParamsUI(dsIdsUI, content)
31+
→ buildDataSourceParamsFilledValueUI(paramConfigs, paramsList)
32+
(AbstractDataSourceService.java:168)
33+
→ jobVo.setJobContent(重建后的 content) (DefaultJobInfoService.java:234) ← 覆盖原始
34+
```
35+
36+
### 2.2 致命点:`buildDataSourceParamsFilledValueUI` 完全依赖 `paramConfigs`
37+
38+
`AbstractDataSourceService.buildDataSourceParamsFilledValueUI``AbstractDataSourceService.java:168-187`):
39+
40+
```java
41+
protected List<ElementUI<?>> buildDataSourceParamsFilledValueUI(
42+
List<ExchangisJobParamConfig> paramConfigs,
43+
List<ExchangisJobParamsContent.ExchangisJobParamsItem> paramsList) {
44+
List<ElementUI<?>> uis = new ArrayList<>();
45+
if (!Objects.isNull(paramConfigs) && !paramConfigs.isEmpty()) { // ← paramConfigs 来自 ExchangisJobParamConfig 表
46+
for (ExchangisJobParamConfig cfg : paramConfigs) {
47+
if (Objects.isNull(paramsList) || paramsList.isEmpty()) {
48+
uis.add(fillElementUIValue(cfg, ""));
49+
continue;
50+
}
51+
ExchangisJobParamsContent.ExchangisJobParamsItem selectedParamItem =
52+
getJobParamsItem(cfg.getConfigKey(), paramsList);
53+
// ... 按 cfg 填值 ...
54+
}
55+
}
56+
return uis; // paramConfigs 空 → 返回空 list
57+
}
58+
```
59+
60+
`paramConfigs``ExchangisJobParamConfigMapper` 按 source type 查 `exchangis_job_param_config` 表。
61+
62+
**FILE 类型在该表无注册**(M5 决策:文件 source 不创建 `ExchangisDataSourceDefinition`、不注册 param config),所以 `paramConfigs` 为空 → 重建后的 `params.sources` 为空。
63+
64+
### 2.3 `getDecoratedJob` 用重建结果覆盖原始 jobContent
65+
66+
`DefaultJobInfoService.getDecoratedJob``DefaultJobInfoService.java:222-243`):
67+
68+
```java
69+
List<ExchangisDataSourceUIViewer> jobDataSourceUIs = this.uiGetter.getJobDataSourceUIs(request, id);
70+
ObjectMapper objectMapper = JsonUtils.jackson();
71+
String content = objectMapper.writeValueAsString(jobDataSourceUIs); // 重建后的(params.sources 空)
72+
JsonNode contentJsonNode = objectMapper.readTree(content);
73+
ObjectNode objectNode = objectMapper.createObjectNode();
74+
objectNode.set("subJobs", contentJsonNode);
75+
jobVo.setJobContent(objectNode.toString()); // ← 覆盖原始 jobContent
76+
```
77+
78+
因此接口返回的 jobContent `params.sources` 为空,而数据库原始 `job_content` 字段是完整的——"数据库有、接口没有"。
79+
80+
---
81+
82+
## 3. 为什么前端无法规避
83+
84+
前端 `getInfo` 只能拿到 `data.jobContent`(已被后端重建为空 params)。前端的 `restoreFileSourceParams``dataSource.vue`)从这个空 params 还原,自然拿不到值。即使从 `dataSourceIds.source` 拿 BML 引用调 `reparseFileSource` 重拉 parseResult,也只能拿原始解析值,**用户改过的 encoding/delimiter/nullFormat 会丢**
85+
86+
**必须在后端修复**:重建时对文件 source 保留原始 params。
87+
88+
---
89+
90+
## 4. 修复方案
91+
92+
### 方案 A(推荐):`buildAllUI` 对文件 source 透传原始 params
93+
94+
`AbstractDataSourceService.buildAllUI``AbstractDataSourceService.java:116-129`)里,**检测 source type == "FILE"**,不走 `ExchangisJobParamConfig` 表查询重建,**直接从原始 content 的 params.sources 透传**
95+
96+
```java
97+
protected ExchangisDataSourceUIViewer buildAllUI(HttpServletRequest request,
98+
ExchangisJobEntity job,
99+
ExchangisJobInfoContent content) {
100+
ExchangisDataSourceIdsUI dataSourceIdsUI = buildDataSourceIdsUI(request, content);
101+
102+
// ⭐ REQ-01:文件 source 无 ExchangisJobParamConfig 注册(M5),表查询重建会返回空 list,
103+
// 导致 getDecoratedJob 用空 params 覆盖原始 jobContent。此处对 FILE 直接透传原始 params.sources
104+
// (config_key 格式),前端 restoreFileSourceParams 已能处理 config_key 格式。
105+
// File source has no param config registration (M5); table-based rebuild returns empty, which
106+
// overwrites the original jobContent. Pass through original params.sources (config_key format)
107+
// for FILE — frontend restoreFileSourceParams handles config_key format.
108+
ExchangisDataSourceParamsUI paramsUI;
109+
String sourceType = dataSourceIdsUI.getSource() == null ? null : dataSourceIdsUI.getSource().getType();
110+
if ("FILE".equalsIgnoreCase(sourceType)) {
111+
paramsUI = buildFileSourceParamsUIFromContent(content);
112+
} else {
113+
paramsUI = buildDataSourceParamsUI(dataSourceIdsUI, content);
114+
}
115+
116+
ExchangisJobTransformsContent transforms = content.getTransforms();
117+
List<ElementUI<?>> jobDataSourceSettingsUI = this.buildJobSettingsUI(job.getEngineType(), content);
118+
return new DefaultDataSourceUIViewer(
119+
content.getSubJobName(), dataSourceIdsUI, paramsUI, transforms, jobDataSourceSettingsUI);
120+
}
121+
```
122+
123+
`buildFileSourceParamsUIFromContent(content)` 实现要点(二选一):
124+
125+
- **简版(透传)**:把 `content.getSources()``List<ExchangisJobParamsContent.ExchangisJobParamsItem>`,含 `configKey / configName / configValue / sort`)转成 `List<ElementUI<?>>``InputElementUI``field=configKey, label=configName, value=configValue, sort=sort, show="_true"/"_false"`)。hidden 项(`__file_bml_*``show="_false"`
126+
- **复用版**:直接把 `content.getSources()` 包进 `ExchangisDataSourceParamsUI` 返回(前端已兼容 config_key 格式,无需转 UI)。
127+
128+
推荐简版(保持 viewer 输出结构一致:`params.sources` 仍是 `ElementUI` 列表,`__file_bml_*` hidden)。
129+
130+
### 方案 B(备选):`getDecoratedJob` 对文件 source subJob 不重建
131+
132+
`DefaultJobInfoService.getDecoratedJob`(line 222-243),检测 subJob source 是否 FILE,若是则保留原始 jobContent(不调 `getJobDataSourceUIs` 重建)。粒度较粗(整个 subJob 不装饰),可能影响 `dataSourceIds / transforms` 的 UI 装饰,**不推荐**
133+
134+
---
135+
136+
## 5. `dataSourceIds` 的 BML 字段(需同步确认)
137+
138+
`buildDataSourceIdsUI``parseDataSourceIdUi``AbstractDataSourceService.java:301-333`)用 `idValue.trim().split("\\.")``type / id / db / table`
139+
140+
```java
141+
ExchangisDataSourceIdUI ui = new ExchangisDataSourceIdUI(jobDataSource); // 从原始 content source 读
142+
if (StringUtils.isNotBlank(idValue)) {
143+
String[] split = idValue.trim().split("\\.");
144+
ui.setType(split[0]);
145+
ui.setId(split[1]);
146+
ui.setDb(split[2]);
147+
ui.setTable(split[3]);
148+
}
149+
```
150+
151+
文件 source 的 `dataSourceIds.source``{type:'FILE', id:0, resourceId, version, owner, name, path}`,没有传统 `type.ds.db.table` 字符串。需确认:
152+
153+
1. `ExchangisDataSourceIdUI(jobDataSource)` 构造是否保留 `resourceId / version / owner / name / path`(BML 引用)—— 前端编辑态回显依赖这些。
154+
2. `idValue` split 对 FILE type 是否会覆盖/污染 BML 字段。若会,对 `type == "FILE"` 跳过 split,保留 `jobDataSource` 原始字段。
155+
156+
---
157+
158+
## 6. 前端配合(已完成,无需开源团队改)
159+
160+
前端 `restoreFileSourceParams``wedatasphere-exchangis-web/cluster/src/pages/jobManagement/components/dataSource.vue`)已实现:
161+
162+
- 加载已保存作业时,把 `params.sources`(config_key 格式)重构为 UI 格式(`buildFileSourceParams`),值从 `config_value` 还原。
163+
- emit `updateSourceParams` 同步 `curTask.params`,避免再次保存丢值。
164+
- `saveAll` forEach 兜底兼容 config_key 格式项。
165+
166+
**前提**:接口返回的 `params.sources` 非空。本修复(方案 A)确保透传,前端即可正常回显。
167+
168+
---
169+
170+
## 7. 验证步骤
171+
172+
1. 前端新建文件 source 作业:上传 CSV/TXT → 改 encoding/delimiter/nullFormat → 选 sink → 字段映射 → 保存。
173+
2. `GET /dss/exchangis/main/job/{id}` 返回的 `result.jobContent.subJobs[i].params.sources` 应含 7 项(`__file_bml_resource_id / version / owner / name` + `encoding / delimiter / nullFormat`,config_key 格式)。
174+
3. 前端编辑态重新打开该作业:dyncRender 渲染 encoding/delimiter/nullFormat 输入框,值为用户保存值;FileUploadComponent 回显文件信息;字段映射正常。
175+
4. 再次保存,参数不丢(前端 saveAll forEach 兜底 + curTask.params 已是 UI 格式)。
176+
177+
---
178+
179+
## 8. 涉及文件(开源仓库 `../exchangis`
180+
181+
| 文件 | 方法 / 行号 | 改动 |
182+
|---|---|---|
183+
| `exchangis-datasource/exchangis-datasource-service/.../AbstractDataSourceService.java` | `buildAllUI` (116) | 加 FILE 分支,透传原始 params |
184+
| 同上 | `parseDataSourceIdUi` (301) | 确认 FILE 的 BML 字段不被 split 覆盖 |
185+
| 同上 | `buildDataSourceParamsFilledValueUI` (168) | 无需改(仅作为根因定位参照) |
186+
| `exchangis-job/exchangis-job-server/.../DefaultJobInfoService.java` | `getDecoratedJob` (222) | 无需改(方案 A 在更上层拦截) |
187+
188+
---
189+
190+
## 9. 备注
191+
192+
- 本修复不影响非文件 source(mysql/hive 等)的既有重建逻辑——只在 `source.type == "FILE"` 时走透传分支。
193+
- 文件 source 的 `__file_bml_*` 是 hidden 参数(`show="_false"`),不渲染但进入保存 payload,后端 handler 读取。重建时需保留这些项(不能因为 hidden 就丢)。

exchangis-datasource/exchangis-datasource-service/src/main/java/com/webank/wedatasphere/exchangis/datasource/service/AbstractDataSourceService.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,29 @@ protected ExchangisDataSourceUIViewer buildAllUI(HttpServletRequest request, Exc
118118
ExchangisDataSourceIdsUI dataSourceIdsUI = buildDataSourceIdsUI(request, content);
119119

120120
// ----------- 构建 dataSourceParamsUI
121+
// 先走标准表查询重建(sink 始终走该逻辑;FILE source 因无 param config 注册会得到空 source list)
122+
// Standard table-based rebuild first (sink always uses this; FILE source gets an empty
123+
// source list because no param config is registered for it).
121124
ExchangisDataSourceParamsUI paramsUI = buildDataSourceParamsUI(dataSourceIdsUI, content);
122125

126+
// ⭐ REQ-01: 文件 source 无 ExchangisJobParamConfig 注册(M5 决策),表查询重建会返回空 source list,
127+
// 导致 getDecoratedJob 用空 params 覆盖原始 jobContent,前端编辑态无法回显
128+
// encoding/delimiter/nullFormat 及 __file_bml_* BML 引用。此处对 FILE source 直接从原始
129+
// content 透传 params.sources(config_key 格式 → InputElementUI),保留全部 7 项及用户改过的值。
130+
// 非 FILE 类型不进入此分支,零影响。
131+
// File source has no param config registration (M5); table-based rebuild returns an empty
132+
// source list, which overwrites the original jobContent via getDecoratedJob, so the frontend
133+
// cannot restore encoding/delimiter/nullFormat and __file_bml_* BML references. Here we
134+
// pass through params.sources directly from the original content for FILE (config_key
135+
// format → InputElementUI), preserving all 7 items and user-edited values.
136+
// Non-FILE types never enter this branch — zero impact.
137+
if (Objects.nonNull(dataSourceIdsUI) && Objects.nonNull(dataSourceIdsUI.getSource())) {
138+
String sourceType = dataSourceIdsUI.getSource().getType();
139+
if ("FILE".equalsIgnoreCase(sourceType)) {
140+
paramsUI.setSources(buildFileSourceParamsUIFromContent(content));
141+
}
142+
}
143+
123144
// ----------- 构建 dataSourceTransformsUI
124145
ExchangisJobTransformsContent transforms = content.getTransforms();
125146

@@ -128,6 +149,65 @@ protected ExchangisDataSourceUIViewer buildAllUI(HttpServletRequest request, Exc
128149
return new DefaultDataSourceUIViewer(content.getSubJobName(), dataSourceIdsUI, paramsUI, transforms, jobDataSourceSettingsUI);
129150
}
130151

152+
/**
153+
* 文件 source 的 params.sources 透传构建(REQ-01)。
154+
* <p>
155+
* FILE 类型在 exchangis_job_param_config 表无注册(M5 决策),无法走标准的
156+
* buildDataSourceParamsFilledValueUI 重建流程(会得到空 list)。本方法直接从原始
157+
* content.getParams().getSources() 读取已保存的 config_key 格式参数项,逐个转换为
158+
* InputElementUI,确保以下 7 项全部保留且值正确:
159+
* - __file_bml_resource_id / __file_bml_version / __file_bml_owner / __file_bml_name(BML 引用,hidden)
160+
* - encoding / delimiter / nullFormat(用户可编辑)
161+
* <p>
162+
* __file_bml_* 为 hidden 参数:InputElementUI 无 show/hidden 字段,原样保留——前端按
163+
* "__file_bml_" 前缀识别(设计文档 §9),不会渲染但进入保存 payload。
164+
* <p>
165+
* File source params.sources passthrough builder (REQ-01).
166+
* FILE type has no registration in the exchangis_job_param_config table (M5 decision), so it
167+
* cannot go through the standard buildDataSourceParamsFilledValueUI rebuild (which yields an
168+
* empty list). This method reads the saved config_key-format params directly from the original
169+
* content.getParams().getSources() and converts each to an InputElementUI, ensuring all 7
170+
* items are preserved with correct values:
171+
* - __file_bml_resource_id / __file_bml_version / __file_bml_owner / __file_bml_name (BML refs, hidden)
172+
* - encoding / delimiter / nullFormat (user-editable)
173+
* <p>
174+
* __file_bml_* are hidden params: InputElementUI has no show/hidden field, so they are kept
175+
* as-is — the frontend recognizes them by the "__file_bml_" prefix (design §9); they are not
176+
* rendered but remain in the save payload.
177+
*
178+
* @param content job info content
179+
* @return source params UI list (config_key → InputElementUI)
180+
*/
181+
private List<ElementUI<?>> buildFileSourceParamsUIFromContent(ExchangisJobInfoContent content) {
182+
List<ElementUI<?>> sourceUIs = new ArrayList<>();
183+
if (Objects.isNull(content) || Objects.isNull(content.getParams())) {
184+
return sourceUIs;
185+
}
186+
List<ExchangisJobParamsContent.ExchangisJobParamsItem> sources = content.getParams().getSources();
187+
if (Objects.isNull(sources) || sources.isEmpty()) {
188+
return sourceUIs;
189+
}
190+
for (ExchangisJobParamsContent.ExchangisJobParamsItem item : sources) {
191+
if (Objects.isNull(item)) {
192+
continue;
193+
}
194+
InputElementUI ui = new InputElementUI();
195+
// key/field 均设为 configKey,前端可按 key 或 config_key 读取
196+
// Set both key and field to configKey so the frontend can read by either key or config_key
197+
ui.setKey(item.getConfigKey());
198+
ui.setField(item.getConfigKey());
199+
ui.setLabel(item.getConfigName());
200+
// configValue 为 Object 类型,转为 String 供 InputElementUI.value 使用;null 转空串
201+
// configValue is Object; convert to String for InputElementUI.value; null → empty string
202+
Object configValue = item.getConfigValue();
203+
String valueStr = Objects.nonNull(configValue) ? String.valueOf(configValue) : "";
204+
ui.setValue(valueStr);
205+
ui.setSort(item.getSort());
206+
sourceUIs.add(ui);
207+
}
208+
return sourceUIs;
209+
}
210+
131211

132212
protected List<ElementUI<?>> buildJobSettingsUI(String jobEngineType) {
133213
if (Strings.isNullOrEmpty(jobEngineType)) {

0 commit comments

Comments
 (0)