fix: 人员数据治理迁移功能优化
This commit is contained in:
+6
@@ -144,6 +144,12 @@ public class PersonApiConfigController {
|
||||
return Result.success(personApiConfigService.queryThirdParty(data, "DATA"));
|
||||
}
|
||||
|
||||
@At(value = "/queryFieldValues", methods = "POST")
|
||||
@SaCheckPermission("data.person.governance.api.test")
|
||||
public Result queryFieldValues(@Param("data") String data, @Param("fields") String fields) {
|
||||
return Result.success(personApiConfigService.queryFieldValues(data, fields));
|
||||
}
|
||||
|
||||
/** 保存当前接口请求及返回结果为可重复执行的测试用例。 */
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
|
||||
+2
@@ -47,6 +47,8 @@ public interface PersonApiConfigService extends BaseService<PersonApiConfig> {
|
||||
/** 使用页面当前config、params查询,返回第三方JSON值;非JSON返回原文,不写数据库。 */
|
||||
Object queryThirdParty(String data, String requestScope);
|
||||
|
||||
NutMap queryFieldValues(String data, String fields);
|
||||
|
||||
/** page为当前页码或起始数,pageSize为数量或区间长度;结束数=page+pageSize,返回第三方JSON。 */
|
||||
Object querySyncPage(PersonApiConfig config, List<PersonApiRequestParam> params, long page, int pageSize);
|
||||
|
||||
|
||||
+109
-4
@@ -44,6 +44,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -506,8 +507,12 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
||||
List<PersonApiRequestParam> params = payload.get("params") == null
|
||||
? new ArrayList<>()
|
||||
: Json.fromJsonAsList(PersonApiRequestParam.class, Json.toJson(payload.get("params")));
|
||||
// 状态信息仅供内部判断,查询接口不额外包装第三方业务字段;同步入口仍沿用原结构。
|
||||
NutMap response = executeHttp(config, params, requestScope, null);
|
||||
return queryResponse(config, params, requestScope, false);
|
||||
}
|
||||
|
||||
private Object queryResponse(PersonApiConfig config, List<PersonApiRequestParam> params,
|
||||
String requestScope, boolean fieldQuery) {
|
||||
NutMap response = executeHttp(config, params, requestScope, null, fieldQuery);
|
||||
Assert.isTrue(response.getBoolean("success"), response.getString("error") == null
|
||||
? "第三方返回HTTP错误:" + response.get("status") : response.getString("error"));
|
||||
String body = response.getString("data");
|
||||
@@ -522,6 +527,101 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap queryFieldValues(String data, String fields) {
|
||||
Assert.hasText(fields, "请输入查询字段");
|
||||
List<String> names = java.util.Arrays.stream(fields.split("[,,]", -1))
|
||||
.map(String::trim).distinct().toList();
|
||||
Assert.isTrue(names.stream().noneMatch(String::isEmpty), "请输入字段名,字段之间用逗号分隔");
|
||||
Assert.hasText(data, "请求配置不能为空");
|
||||
NutMap payload = Json.fromJson(NutMap.class, data);
|
||||
Assert.notNull(payload, "请求配置格式不正确");
|
||||
PersonApiConfig config = Json.fromJson(PersonApiConfig.class, Json.toJson(payload.get("config")));
|
||||
Assert.notNull(config, "请求配置不能为空");
|
||||
List<PersonApiRequestParam> params = payload.get("params") == null ? List.of()
|
||||
: Json.fromJsonAsList(PersonApiRequestParam.class, Json.toJson(payload.get("params")));
|
||||
|
||||
setFieldQueryPage(config, params, 100);
|
||||
Object firstPage = queryResponse(config, params, "DATA", true);
|
||||
String totalPath = Strings.isBlank(config.getResponseTotalPath()) ? "data->total" : config.getResponseTotalPath();
|
||||
int total = fieldQueryTotal(firstPage, totalPath);
|
||||
if (total == 0) {
|
||||
return NutMap.NEW().addv("fields", names).addv("rows", List.of()).addv("total", 0);
|
||||
}
|
||||
|
||||
setFieldQueryPage(config, params, total);
|
||||
Object response = queryResponse(config, params, "DATA", true);
|
||||
int returnedTotal = fieldQueryTotal(response, totalPath);
|
||||
String listPath = config.getResponseDataPath();
|
||||
if (Strings.isBlank(listPath) || "$.data".equals(listPath.trim()) || "data".equals(listPath.trim())) {
|
||||
Object configured = readResponsePath(response, listPath);
|
||||
if (!(configured instanceof List<?>)) listPath = "data->list";
|
||||
}
|
||||
Object dataset = readResponsePath(response, listPath);
|
||||
Assert.isTrue(dataset instanceof List<?>, "数据列表路径未找到数组:" + listPath);
|
||||
List<?> records = (List<?>) dataset;
|
||||
Assert.isTrue(records.size() >= Math.max(total, returnedTotal),
|
||||
"接口未返回全部数据,预计 " + Math.max(total, returnedTotal) + " 条,实际返回 " + records.size() + " 条");
|
||||
|
||||
LinkedHashSet<List<Object>> groups = new LinkedHashSet<>();
|
||||
LinkedHashSet<String> found = new LinkedHashSet<>();
|
||||
for (Object record : records) {
|
||||
Assert.isTrue(record instanceof Map<?, ?>, "数据列表中的记录必须为对象");
|
||||
Map<?, ?> row = (Map<?, ?>) record;
|
||||
List<Object> values = new ArrayList<>(names.size());
|
||||
for (String name : names) {
|
||||
if (row.containsKey(name)) found.add(name);
|
||||
values.add(row.get(name));
|
||||
}
|
||||
groups.add(values);
|
||||
}
|
||||
List<String> missing = names.stream().filter(name -> !found.contains(name)).toList();
|
||||
Assert.isTrue(missing.isEmpty(), "返回数据中找不到字段:" + String.join("、", missing));
|
||||
return NutMap.NEW().addv("fields", names).addv("rows", new ArrayList<>(groups)).addv("total", records.size());
|
||||
}
|
||||
|
||||
private int fieldQueryTotal(Object response, String path) {
|
||||
if (response instanceof Map<?, ?> map) {
|
||||
Assert.isTrue(!Boolean.FALSE.equals(map.get("status")), "数据接口返回失败:" + Strings.sNull(map.get("message")));
|
||||
}
|
||||
Object value = readResponsePath(response, path);
|
||||
try {
|
||||
int total = new java.math.BigDecimal(String.valueOf(value)).intValueExact();
|
||||
if (total >= 0) return total;
|
||||
} catch (NumberFormatException | ArithmeticException ignored) {
|
||||
}
|
||||
throw new IllegalArgumentException("返回总数必须为非负整数,请检查路径:" + path);
|
||||
}
|
||||
|
||||
private void setFieldQueryPage(PersonApiConfig config, List<PersonApiRequestParam> params, int pageSize) {
|
||||
boolean sendBody = !List.of("GET", "HEAD").contains(Strings.sNull(config.getRequestMethod()).toUpperCase(Locale.ROOT));
|
||||
Map<String, Integer> paging = Map.of("pageNumber", 1, "pageSize", pageSize);
|
||||
LinkedHashSet<String> replaced = new LinkedHashSet<>();
|
||||
if (sendBody && "JSON".equals(config.getBodyType()) && Strings.isNotBlank(config.getBodyContent())) {
|
||||
Object body = parseResponseObject(config.getBodyContent());
|
||||
if (body instanceof com.alibaba.fastjson.JSONObject json) {
|
||||
paging.forEach((name, value) -> {
|
||||
if (json.containsKey(name)) {
|
||||
json.put(name, value);
|
||||
replaced.add(name);
|
||||
}
|
||||
});
|
||||
config.setBodyContent(com.alibaba.fastjson.JSON.toJSONString(json,
|
||||
com.alibaba.fastjson.serializer.SerializerFeature.WriteMapNullValue));
|
||||
}
|
||||
}
|
||||
for (PersonApiRequestParam param : params) {
|
||||
boolean sent = "QUERY".equals(param.getParamLocation())
|
||||
|| (sendBody && "FORM_DATA".equals(config.getBodyType()) && "BODY".equals(param.getParamLocation()));
|
||||
if (sent && "DATA".equals(normalizeScope(param.getRequestScope()))
|
||||
&& param.getParamName() != null && paging.containsKey(param.getParamName())) {
|
||||
param.setParamValue(String.valueOf(paging.get(param.getParamName())));
|
||||
replaced.add(param.getParamName());
|
||||
}
|
||||
}
|
||||
Assert.isTrue(replaced.size() == paging.size(), "请在数据请求参数中配置 pageNumber 和 pageSize");
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步取数:params是保存的两套参数,page为页码或起点,pageSize为数量或区间长度;仅替换请求副本。
|
||||
* Access_Token标记传入缓存Token,参数值可使用Bearer ${Access_Token}保留前缀。
|
||||
@@ -788,9 +888,14 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
||||
/** 已保存测试和页面查询共享HTTP组装;校验仅在实际请求时执行,响应不自动落库。 */
|
||||
private NutMap executeHttp(PersonApiConfig config, List<PersonApiRequestParam> params,
|
||||
String scope, String testCondition) {
|
||||
return executeHttp(config, params, scope, testCondition, false);
|
||||
}
|
||||
|
||||
private NutMap executeHttp(PersonApiConfig config, List<PersonApiRequestParam> params,
|
||||
String scope, String testCondition, boolean fieldQuery) {
|
||||
Assert.isTrue(List.of("TOKEN", "DATA").contains(scope), "请求配置范围不正确");
|
||||
long start = System.currentTimeMillis();
|
||||
boolean omitToken = "DATA".equals(scope) && Strings.isBlank(config.getTokenRequestUrl());
|
||||
boolean omitToken = !fieldQuery && "DATA".equals(scope) && Strings.isBlank(config.getTokenRequestUrl());
|
||||
params = params.stream()
|
||||
.filter(param -> scope.equals(normalizeScope(param.getRequestScope())))
|
||||
// 未配置Token接口时,Header、Query及Form Data中的Token参数均不进入实际请求。
|
||||
@@ -855,7 +960,7 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
||||
try (HttpResponse response = request.execute()) {
|
||||
String responseBody = response.body();
|
||||
// 按排查要求打印第三方响应原文;返回值复用同一正文,不附加请求参数或统计信息。
|
||||
Logs.get().info(responseBody);
|
||||
if (!fieldQuery) Logs.get().info(responseBody);
|
||||
return NutMap.NEW()
|
||||
.addv("success", response.isOk())
|
||||
.addv("status", response.getStatus())
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
:loading="queryLoading[activeEndpoint]"
|
||||
@click="testApi"
|
||||
>查询</el-button>
|
||||
<el-button v-if="activeEndpoint==='DATA'" @click="openFieldValueQuery">查询字段值</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -439,6 +440,24 @@
|
||||
@click="save"
|
||||
>保存配置</el-button>
|
||||
</div>
|
||||
<el-dialog title="查询字段值" :visible.sync="fieldValueQueryVisible" append-to-body width="760px"
|
||||
:close-on-click-modal="false" custom-class="person-governance-dialog">
|
||||
<el-input v-model="fieldValueQueryInput" :disabled="fieldValueQueryLoading" clearable
|
||||
placeholder="输入返回字段名,多个字段用逗号分隔,例如 xb,mz" @keyup.enter.native="queryFieldValues"></el-input>
|
||||
<div v-if="fieldValueQueryResult" style="margin-top: 16px">
|
||||
<p>读取 {{ fieldValueQueryResult.total }} 条记录,共 {{ fieldValueQueryResult.rows.length }} 组不同值</p>
|
||||
<el-table :data="fieldValueQueryResult.rows" border max-height="420" empty-text="接口没有返回数据">
|
||||
<el-table-column v-for="(field, index) in fieldValueQueryResult.fields" :key="field" :label="field"
|
||||
min-width="120" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ formatFieldValue(scope.row[index]) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-button @click="fieldValueQueryVisible=false">关闭</el-button>
|
||||
<el-button type="primary" :loading="fieldValueQueryLoading" @click="queryFieldValues">查询</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog title="字段配置" :visible.sync="fieldRuleVisible" append-to-body width="520px"
|
||||
:close-on-click-modal="false" custom-class="person-governance-dialog">
|
||||
<el-form :model="fieldRuleDraft" label-width="120px">
|
||||
@@ -543,6 +562,10 @@
|
||||
testResults: { TOKEN: '', DATA: '' },
|
||||
responseWarnings: { TOKEN: '', DATA: '' },
|
||||
queryLoading: { TOKEN: false, DATA: false },
|
||||
fieldValueQueryVisible: false,
|
||||
fieldValueQueryInput: '',
|
||||
fieldValueQueryLoading: false,
|
||||
fieldValueQueryResult: null,
|
||||
cronValidating: false,
|
||||
cronDialogVisible: false,
|
||||
cronDialogLoading: false,
|
||||
@@ -1180,6 +1203,39 @@
|
||||
})
|
||||
})
|
||||
},
|
||||
openFieldValueQuery() {
|
||||
this.$set(this, 'fieldValueQueryVisible', true)
|
||||
},
|
||||
queryFieldValues() {
|
||||
if (this.fieldValueQueryLoading) return
|
||||
const fields = this.fieldValueQueryInput.trim()
|
||||
if (!fields) {
|
||||
this.notifyWarning('请输入查询字段')
|
||||
return
|
||||
}
|
||||
this.$set(this, 'fieldValueQueryLoading', true)
|
||||
this.$set(this, 'fieldValueQueryResult', null)
|
||||
$.post(apiBase + '/queryFieldValues', {
|
||||
fields,
|
||||
data: JSON.stringify({
|
||||
config: this.form,
|
||||
params: this.params.filter((param) => param.requestScope === 'DATA'),
|
||||
}),
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$set(this, 'fieldValueQueryResult', res.data)
|
||||
} else {
|
||||
this.notifyWarning(res.msg || '查询字段值失败')
|
||||
}
|
||||
}).fail(() => {
|
||||
this.notifyWarning('查询字段值失败,请检查网络和接口配置')
|
||||
}).always(() => {
|
||||
this.$set(this, 'fieldValueQueryLoading', false)
|
||||
})
|
||||
},
|
||||
formatFieldValue(value) {
|
||||
return typeof value === 'string' && value ? value : JSON.stringify(value)
|
||||
},
|
||||
testApi() {
|
||||
// 捕获发起时的页签:请求期间切换页签,响应仍写回原来的范围。
|
||||
const scope = this.activeEndpoint
|
||||
|
||||
@@ -51,9 +51,10 @@
|
||||
{{row.enabled ? '是' : '否'}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="130">
|
||||
<el-table-column label="操作" width="190">
|
||||
<template v-slot="{row}">
|
||||
<el-button type="text" @click="edit(row)">编辑</el-button>
|
||||
<el-button type="text" :loading="!!syncingRows[row.id]" @click="syncOnce(row)">执行一次</el-button>
|
||||
<el-button type="text" class="text-danger" @click="remove(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -102,6 +103,7 @@
|
||||
pageLeaving: false,
|
||||
typeVisible: false,
|
||||
pendingCreateType: '',
|
||||
syncingRows: {},
|
||||
pageForm: { configName: '', sourceType: '', enabled: '', pageNumber: 1, pageSize: 10, totalCount: 0 },
|
||||
}
|
||||
},
|
||||
@@ -183,6 +185,38 @@
|
||||
edit(row) {
|
||||
this.leavePage('/platform/personGovernance/apiConfig/form?id=' + row.id)
|
||||
},
|
||||
async syncOnce(row) {
|
||||
if (this.syncingRows[row.id]) return
|
||||
this.$set(this.syncingRows, row.id, true)
|
||||
try {
|
||||
await this.$confirm('确认执行一次「' + row.configName + '」同步?本次执行会写入目标表。', '执行一次', {
|
||||
confirmButtonText: '执行',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
} catch (cancelled) {
|
||||
this.$delete(this.syncingRows, row.id)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await $.post(loc() + '/sync', { id: row.id })
|
||||
if (res.code !== 0) {
|
||||
this.notifyWarning(res.msg || '同步执行失败')
|
||||
return
|
||||
}
|
||||
const batch = res.data
|
||||
if (!batch || batch.status !== 'SUCCESS') {
|
||||
this.notifyWarning((batch && batch.errorMessage) || '同步执行失败')
|
||||
return
|
||||
}
|
||||
this.notifySuccess('同步完成,新增 ' + (batch.newCount || 0) + ' 条,更新 ' + (batch.changeCount || 0) + ' 条')
|
||||
this.pageData()
|
||||
} catch (error) {
|
||||
this.notifyWarning('未收到同步结果,执行状态未知')
|
||||
} finally {
|
||||
this.$delete(this.syncingRows, row.id)
|
||||
}
|
||||
},
|
||||
remove(row) {
|
||||
this.$confirm('删除后不可继续同步,确认删除吗?', '提示').then(() => {
|
||||
$.post(loc() + '/delete', { id: row.id })
|
||||
|
||||
Reference in New Issue
Block a user