fix: 人员数据治理迁移8
This commit is contained in:
+13
@@ -3,7 +3,9 @@ package com.budwk.app.zhgh.personGovernance.apiConfig.model;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class PersonApiSyncOptions {
|
public class PersonApiSyncOptions {
|
||||||
@@ -11,11 +13,22 @@ public class PersonApiSyncOptions {
|
|||||||
private String unitConfigId;
|
private String unitConfigId;
|
||||||
private List<FieldRule> rules = new ArrayList<>();
|
private List<FieldRule> rules = new ArrayList<>();
|
||||||
|
|
||||||
|
public Map<String, String> fieldDefaults() {
|
||||||
|
Map<String, String> defaults = new HashMap<>();
|
||||||
|
for (FieldRule rule : rules) {
|
||||||
|
if (rule.getDefaultValue() != null && !rule.getDefaultValue().isEmpty()) {
|
||||||
|
defaults.put(rule.getTargetField(), rule.getDefaultValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class FieldRule {
|
public static class FieldRule {
|
||||||
private String sourceField;
|
private String sourceField;
|
||||||
private String targetField;
|
private String targetField;
|
||||||
private Integer length;
|
private Integer length;
|
||||||
private String dictCode;
|
private String dictCode;
|
||||||
|
private String defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.personGovernance.apiConfig.service;
|
|||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.service.BaseService;
|
import com.budwk.app.base.service.BaseService;
|
||||||
|
import com.budwk.app.sys.models.Sys_dict;
|
||||||
import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiConfig;
|
import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiConfig;
|
||||||
import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiSyncOptions;
|
import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiSyncOptions;
|
||||||
import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiFieldMapping;
|
import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiFieldMapping;
|
||||||
@@ -33,6 +34,8 @@ public interface PersonApiConfigService extends BaseService<PersonApiConfig> {
|
|||||||
|
|
||||||
NutMap syncOptionChoices();
|
NutMap syncOptionChoices();
|
||||||
|
|
||||||
|
Sys_dict findUserFieldDictionary(String code);
|
||||||
|
|
||||||
void deleteConfig(String id);
|
void deleteConfig(String id);
|
||||||
|
|
||||||
List<NutMap> tableNames();
|
List<NutMap> tableNames();
|
||||||
|
|||||||
+112
-27
@@ -54,6 +54,8 @@ import java.util.regex.Pattern;
|
|||||||
@IocBean(args = {"refer:dao"})
|
@IocBean(args = {"refer:dao"})
|
||||||
public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig> implements PersonApiConfigService {
|
public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig> implements PersonApiConfigService {
|
||||||
private static final Pattern SAFE_IDENTIFIER = Pattern.compile("^[A-Za-z0-9_]+$");
|
private static final Pattern SAFE_IDENTIFIER = Pattern.compile("^[A-Za-z0-9_]+$");
|
||||||
|
private static final String SYNC_OPTIONS_CHUNKS = "@chunks:";
|
||||||
|
private static final String USER_FIELD_DICTIONARIES = "USER_TABLE_COLUMN_DICTS";
|
||||||
/** JSON节点删除标记:Token接口未配置时,用于彻底移除数据请求中的Token字段或数组项。 */
|
/** JSON节点删除标记:Token接口未配置时,用于彻底移除数据请求中的Token字段或数组项。 */
|
||||||
private static final Object OMIT_TOKEN_PARAMETER = new Object();
|
private static final Object OMIT_TOKEN_PARAMETER = new Object();
|
||||||
|
|
||||||
@@ -124,6 +126,12 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public PersonApiConfig saveConfig(PersonApiConfig config, List<PersonApiFieldMapping> mappings,
|
public PersonApiConfig saveConfig(PersonApiConfig config, List<PersonApiFieldMapping> mappings,
|
||||||
List<PersonApiRequestParam> params) {
|
List<PersonApiRequestParam> params) {
|
||||||
|
return saveConfig(config, mappings, params, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PersonApiConfig saveConfig(PersonApiConfig config, List<PersonApiFieldMapping> mappings,
|
||||||
|
List<PersonApiRequestParam> params, PersonApiSyncOptions options) {
|
||||||
|
Map<String, String> defaults = options == null ? java.util.Collections.emptyMap() : options.fieldDefaults();
|
||||||
Assert.notNull(config, "配置不能为空");
|
Assert.notNull(config, "配置不能为空");
|
||||||
Assert.hasText(config.getConfigName(), "请填写配置名称");
|
Assert.hasText(config.getConfigName(), "请填写配置名称");
|
||||||
PersonApiConfig stored = Strings.isBlank(config.getId()) ? null : fetch(config.getId());
|
PersonApiConfig stored = Strings.isBlank(config.getId()) ? null : fetch(config.getId());
|
||||||
@@ -146,8 +154,8 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
mappings = mappings == null ? new ArrayList<>() : mappings;
|
mappings = mappings == null ? new ArrayList<>() : mappings;
|
||||||
if (Boolean.TRUE.equals(config.getEnabled())) {
|
if (Boolean.TRUE.equals(config.getEnabled())) {
|
||||||
Assert.hasText(config.getTargetTable(), "启用同步任务前请选择目标表");
|
Assert.hasText(config.getTargetTable(), "启用同步任务前请选择目标表");
|
||||||
Assert.isTrue(mappings.stream().anyMatch(mapping -> Strings.isNotBlank(mapping.getSourceField())
|
Assert.isTrue(mappings.stream().anyMatch(mapping -> Strings.isNotBlank(mapping.getTargetField())
|
||||||
&& Strings.isNotBlank(mapping.getTargetField())), "启用同步任务前请至少完成一条来源字段与目标字段的映射");
|
&& hasMappingValue(mapping, defaults)), "启用同步任务前请至少配置一条字段映射或默认值");
|
||||||
}
|
}
|
||||||
PersonApiFieldMapping identity = mappings.stream()
|
PersonApiFieldMapping identity = mappings.stream()
|
||||||
.filter(v -> Boolean.TRUE.equals(v.getIdentityFlag()))
|
.filter(v -> Boolean.TRUE.equals(v.getIdentityFlag()))
|
||||||
@@ -168,7 +176,7 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
Assert.isTrue(isUnitConfig(config) && "UPSERT".equals(config.getSyncMode())
|
Assert.isTrue(isUnitConfig(config) && "UPSERT".equals(config.getSyncMode())
|
||||||
&& "id".equals(config.getIdentityField()), "单位API配置必须使用存在即修改模式,并将来源单位编码映射到id作为唯一标识");
|
&& "id".equals(config.getIdentityField()), "单位API配置必须使用存在即修改模式,并将来源单位编码映射到id作为唯一标识");
|
||||||
Assert.isTrue(mappings.stream().anyMatch(mapping -> "name".equals(mapping.getTargetField())
|
Assert.isTrue(mappings.stream().anyMatch(mapping -> "name".equals(mapping.getTargetField())
|
||||||
&& Strings.isNotBlank(mapping.getSourceField())), "单位API配置必须映射单位名称name");
|
&& hasMappingValue(mapping, defaults)), "单位API配置必须映射单位名称name");
|
||||||
}
|
}
|
||||||
if ("HTTP_API".equals(config.getSourceType())) pagination(config, params);
|
if ("HTTP_API".equals(config.getSourceType())) pagination(config, params);
|
||||||
|
|
||||||
@@ -240,6 +248,9 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
}
|
}
|
||||||
dao().insert(params);
|
dao().insert(params);
|
||||||
}
|
}
|
||||||
|
if (options != null) {
|
||||||
|
saveSyncOptions(config.getId(), options);
|
||||||
|
}
|
||||||
if (previousDataQueryResultKey != null
|
if (previousDataQueryResultKey != null
|
||||||
&& !Objects.equals(previousDataQueryResultKey, config.getDataQueryResult())) {
|
&& !Objects.equals(previousDataQueryResultKey, config.getDataQueryResult())) {
|
||||||
deleteDataQueryResultQuietly(previousDataQueryResultKey);
|
deleteDataQueryResultQuietly(previousDataQueryResultKey);
|
||||||
@@ -267,32 +278,78 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
if (options != null) {
|
if (options != null) {
|
||||||
validateSyncOptions(config, mappings, options);
|
validateSyncOptions(config, mappings, options);
|
||||||
}
|
}
|
||||||
PersonApiConfig saved = saveConfig(config, mappings, params);
|
return saveConfig(config, mappings, params, options);
|
||||||
if (options != null) {
|
|
||||||
Sys_config settings = new Sys_config();
|
|
||||||
settings.setConfigKey(syncOptionsKey(saved.getId()));
|
|
||||||
settings.setConfigValue(Json.toJson(options));
|
|
||||||
settings.setNote("人员治理API同步选项");
|
|
||||||
dao().insertOrUpdate(settings);
|
|
||||||
}
|
|
||||||
return saved;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String syncOptionsKey(String configId) {
|
private String syncOptionsKey(String configId) {
|
||||||
return "personGovernance.syncOptions." + configId;
|
return "personGovernance.syncOptions." + configId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveSyncOptions(String configId, PersonApiSyncOptions options) {
|
||||||
|
String key = syncOptionsKey(configId);
|
||||||
|
String value = Json.toJson(options);
|
||||||
|
// 同时遵守字符列宽和文本列的字节上限,按 Unicode 码点分段以保留完整字符。
|
||||||
|
List<NutMap> column = listMap(Sqls.create("SELECT LEAST(CHARACTER_MAXIMUM_LENGTH, "
|
||||||
|
+ "FLOOR(CHARACTER_OCTET_LENGTH / 4)) valueLength FROM information_schema.COLUMNS "
|
||||||
|
+ "WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='sys_config' AND COLUMN_NAME='configValue'"));
|
||||||
|
Assert.isTrue(!column.isEmpty() && column.get(0).getInt("valueLength") > 0, "无法读取系统配置值列宽");
|
||||||
|
int limit = column.get(0).getInt("valueLength");
|
||||||
|
int remaining = value.codePointCount(0, value.length());
|
||||||
|
List<Sys_config> chunks = new ArrayList<>();
|
||||||
|
if (remaining > limit) {
|
||||||
|
int offset = 0;
|
||||||
|
while (remaining > 0) {
|
||||||
|
int size = Math.min(limit, remaining);
|
||||||
|
int end = value.offsetByCodePoints(offset, size);
|
||||||
|
chunks.add(syncOptionsRow(key + "." + (chunks.size() + 1), value.substring(offset, end)));
|
||||||
|
offset = end;
|
||||||
|
remaining -= size;
|
||||||
|
}
|
||||||
|
value = SYNC_OPTIONS_CHUNKS + chunks.size();
|
||||||
|
Assert.isTrue(value.length() <= limit, "系统配置值列宽不足以保存分段标记");
|
||||||
|
}
|
||||||
|
dao().insertOrUpdate(syncOptionsRow(key, value));
|
||||||
|
dao().clear(Sys_config.class, Cnd.where("configKey", "like", key + ".%"));
|
||||||
|
if (!chunks.isEmpty()) dao().insert(chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Sys_config syncOptionsRow(String key, String value) {
|
||||||
|
Sys_config settings = new Sys_config();
|
||||||
|
settings.setConfigKey(key);
|
||||||
|
settings.setConfigValue(value);
|
||||||
|
settings.setNote("人员治理API同步选项");
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PersonApiSyncOptions syncOptions(String configId) {
|
public PersonApiSyncOptions syncOptions(String configId) {
|
||||||
Sys_config settings = dao().fetch(Sys_config.class, syncOptionsKey(configId));
|
String key = syncOptionsKey(configId);
|
||||||
return settings == null ? new PersonApiSyncOptions()
|
Map<String, String> values = new LinkedHashMap<>();
|
||||||
: Json.fromJson(PersonApiSyncOptions.class, settings.getConfigValue());
|
dao().query(Sys_config.class, Cnd.where("configKey", "=", key).or("configKey", "like", key + ".%"))
|
||||||
|
.forEach(settings -> values.put(settings.getConfigKey(), settings.getConfigValue()));
|
||||||
|
String value = values.get(key);
|
||||||
|
if (value == null) return new PersonApiSyncOptions();
|
||||||
|
if (value.startsWith(SYNC_OPTIONS_CHUNKS)) {
|
||||||
|
int chunkCount = Integer.parseInt(value.substring(SYNC_OPTIONS_CHUNKS.length()));
|
||||||
|
Assert.isTrue(chunkCount > 0 && chunkCount == values.size() - 1, "人员治理同步选项分段不完整");
|
||||||
|
StringBuilder json = new StringBuilder();
|
||||||
|
for (int i = 1; i <= chunkCount; i++) {
|
||||||
|
String chunk = values.get(key + "." + i);
|
||||||
|
Assert.notNull(chunk, "人员治理同步选项分段不完整");
|
||||||
|
json.append(chunk);
|
||||||
|
}
|
||||||
|
value = json.toString();
|
||||||
|
}
|
||||||
|
return Json.fromJson(PersonApiSyncOptions.class, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NutMap syncOptionChoices() {
|
public NutMap syncOptionChoices() {
|
||||||
|
Sys_dict root = userFieldDictionaryRoot();
|
||||||
|
List<Sys_dict> dictionaries = root == null ? List.of()
|
||||||
|
: dao().query(Sys_dict.class, Cnd.where("parentId", "=", root.getId()).asc("name"));
|
||||||
return NutMap.NEW()
|
return NutMap.NEW()
|
||||||
.addv("dicts", dao().query(Sys_dict.class, Cnd.where("parentId", "=", "").or("parentId", "is", null).asc("name")))
|
.addv("dicts", dictionaries)
|
||||||
.addv("unitConfigs", dao().query(PersonApiConfig.class,
|
.addv("unitConfigs", dao().query(PersonApiConfig.class,
|
||||||
Cnd.where("sourceType", "=", "HTTP_API").and("targetTable", "=", "sys_unit")
|
Cnd.where("sourceType", "=", "HTTP_API").and("targetTable", "=", "sys_unit")
|
||||||
.and("syncMode", "=", "UPSERT").and("identityField", "=", "id")
|
.and("syncMode", "=", "UPSERT").and("identityField", "=", "id")
|
||||||
@@ -300,10 +357,26 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
.map(item -> NutMap.NEW().addv("id", item.getId()).addv("configName", item.getConfigName())).toList());
|
.map(item -> NutMap.NEW().addv("id", item.getId()).addv("configName", item.getConfigName())).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Sys_dict userFieldDictionaryRoot() {
|
||||||
|
return dao().fetch(Sys_dict.class, Cnd.where("code", "=", USER_FIELD_DICTIONARIES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sys_dict findUserFieldDictionary(String code) {
|
||||||
|
Sys_dict root = userFieldDictionaryRoot();
|
||||||
|
return root == null ? null : dao().fetch(Sys_dict.class,
|
||||||
|
Cnd.where("parentId", "=", root.getId()).and("code", "=", code));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isUnitConfig(PersonApiConfig config) {
|
private boolean isUnitConfig(PersonApiConfig config) {
|
||||||
return "HTTP_API".equals(config.getSourceType()) && "sys_unit".equalsIgnoreCase(config.getTargetTable());
|
return "HTTP_API".equals(config.getSourceType()) && "sys_unit".equalsIgnoreCase(config.getTargetTable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasMappingValue(PersonApiFieldMapping mapping, Map<String, String> defaults) {
|
||||||
|
return Strings.isNotBlank(mapping.getSourceField())
|
||||||
|
|| (mapping.getTargetField() != null && defaults.containsKey(mapping.getTargetField()));
|
||||||
|
}
|
||||||
|
|
||||||
private void validateSyncOptions(PersonApiConfig config, List<PersonApiFieldMapping> mappings, PersonApiSyncOptions options) {
|
private void validateSyncOptions(PersonApiConfig config, List<PersonApiFieldMapping> mappings, PersonApiSyncOptions options) {
|
||||||
if (Strings.isNotBlank(options.getUnitConfigId())) {
|
if (Strings.isNotBlank(options.getUnitConfigId())) {
|
||||||
PersonApiConfig unitConfig = fetch(options.getUnitConfigId());
|
PersonApiConfig unitConfig = fetch(options.getUnitConfigId());
|
||||||
@@ -311,29 +384,40 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
&& !Boolean.TRUE.equals(unitConfig.getDelFlag()) && isUnitConfig(unitConfig)
|
&& !Boolean.TRUE.equals(unitConfig.getDelFlag()) && isUnitConfig(unitConfig)
|
||||||
&& "UPSERT".equals(unitConfig.getSyncMode()) && "id".equals(unitConfig.getIdentityField()),
|
&& "UPSERT".equals(unitConfig.getSyncMode()) && "id".equals(unitConfig.getIdentityField()),
|
||||||
"请选择有效的单位API配置");
|
"请选择有效的单位API配置");
|
||||||
Assert.isTrue(mappings != null && mappings.stream().anyMatch(mapping -> "unitId".equals(mapping.getTargetField())
|
|
||||||
&& Strings.isNotBlank(mapping.getSourceField())), "自动同步单位需要配置unitId映射");
|
|
||||||
}
|
}
|
||||||
Assert.notNull(options.getRules(), "字段配置不能为空");
|
Assert.notNull(options.getRules(), "字段配置不能为空");
|
||||||
java.util.Set<List<String>> ruleFields = new java.util.HashSet<>();
|
java.util.Set<List<String>> ruleFields = new java.util.HashSet<>();
|
||||||
for (PersonApiSyncOptions.FieldRule rule : options.getRules()) {
|
for (PersonApiSyncOptions.FieldRule rule : options.getRules()) {
|
||||||
Assert.notNull(rule, "字段配置不能为空");
|
Assert.notNull(rule, "字段配置不能为空");
|
||||||
Assert.hasText(rule.getSourceField(), "请填写配置规则的来源字段");
|
boolean hasDefault = rule.getDefaultValue() != null && !rule.getDefaultValue().isEmpty();
|
||||||
Assert.isTrue(ruleFields.add(List.of(rule.getSourceField(), Strings.sNull(rule.getTargetField()))),
|
if (rule.getLength() != null || !hasDefault) {
|
||||||
|
Assert.hasText(rule.getSourceField(), "请填写配置规则的来源字段");
|
||||||
|
}
|
||||||
|
Assert.isTrue(ruleFields.add(List.of(Strings.sNull(rule.getSourceField()), Strings.sNull(rule.getTargetField()))),
|
||||||
"同一来源字段和目标字段只能配置一条规则");
|
"同一来源字段和目标字段只能配置一条规则");
|
||||||
Assert.isTrue(rule.getLength() == null || rule.getLength() >= 0, "字段长度必须为非负整数");
|
Assert.isTrue(rule.getLength() == null || rule.getLength() >= 0, "字段长度必须为非负整数");
|
||||||
Assert.isTrue(mappings != null && mappings.stream().anyMatch(mapping ->
|
PersonApiFieldMapping mapping = mappings == null ? null : mappings.stream().filter(item ->
|
||||||
Objects.equals(mapping.getSourceField(), rule.getSourceField())
|
Objects.equals(Strings.sNull(item.getSourceField()), Strings.sNull(rule.getSourceField()))
|
||||||
&& Objects.equals(Strings.sNull(mapping.getTargetField()), Strings.sNull(rule.getTargetField()))),
|
&& Objects.equals(Strings.sNull(item.getTargetField()), Strings.sNull(rule.getTargetField())))
|
||||||
"字段规则必须关联一条字段映射");
|
.findFirst().orElse(null);
|
||||||
|
Assert.notNull(mapping, "字段规则必须关联一条字段映射");
|
||||||
|
if (hasDefault) {
|
||||||
|
Assert.hasText(rule.getTargetField(), "设置默认值前请选择目标字段");
|
||||||
|
Assert.isTrue(!List.of("id", "pullTime").contains(rule.getTargetField())
|
||||||
|
&& !Boolean.TRUE.equals(mapping.getIdentityFlag()), "主键、批次时间和唯一标识不能设置默认值");
|
||||||
|
}
|
||||||
if (Strings.isNotBlank(rule.getDictCode())) {
|
if (Strings.isNotBlank(rule.getDictCode())) {
|
||||||
Assert.hasText(rule.getTargetField(), "选择字典前请填写目标字段");
|
Assert.hasText(rule.getTargetField(), "选择字典前请填写目标字段");
|
||||||
Assert.isTrue(!"pullTime".equals(rule.getTargetField())
|
Assert.isTrue(!"pullTime".equals(rule.getTargetField())
|
||||||
&& (!"id".equals(rule.getTargetField()) || isUnitConfig(config)), "系统生成字段不能配置字典");
|
&& (!"id".equals(rule.getTargetField()) || isUnitConfig(config)), "系统生成字段不能配置字典");
|
||||||
Assert.notNull(dao().fetch(Sys_dict.class, Cnd.where("code", "=", rule.getDictCode())
|
Assert.notNull(findUserFieldDictionary(rule.getDictCode()), "用户表字段字典下不存在该分类:" + rule.getDictCode());
|
||||||
.and(Cnd.exps("parentId", "=", "").or("parentId", "is", null))), "字典分类不存在");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Strings.isNotBlank(options.getUnitConfigId())) {
|
||||||
|
Map<String, String> defaults = options.fieldDefaults();
|
||||||
|
Assert.isTrue(mappings != null && mappings.stream().anyMatch(mapping -> "unitId".equals(mapping.getTargetField())
|
||||||
|
&& hasMappingValue(mapping, defaults)), "自动同步单位需要配置unitId映射");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -350,7 +434,8 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
PersonApiConfig config = fetch(id);
|
PersonApiConfig config = fetch(id);
|
||||||
config.setEnabled(false);
|
config.setEnabled(false);
|
||||||
reconcileTask(config);
|
reconcileTask(config);
|
||||||
dao().delete(Sys_config.class, syncOptionsKey(id));
|
String key = syncOptionsKey(id);
|
||||||
|
dao().clear(Sys_config.class, Cnd.where("configKey", "=", key).or("configKey", "like", key + ".%"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+21
-12
@@ -101,6 +101,7 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
List<PersonApiFieldMapping> mappings = dao.query(PersonApiFieldMapping.class,
|
List<PersonApiFieldMapping> mappings = dao.query(PersonApiFieldMapping.class,
|
||||||
Cnd.where("configId", "=", configId).asc("sort"));
|
Cnd.where("configId", "=", configId).asc("sort"));
|
||||||
PersonApiSyncOptions options = personApiConfigService.syncOptions(configId);
|
PersonApiSyncOptions options = personApiConfigService.syncOptions(configId);
|
||||||
|
Map<String, String> fieldDefaults = options.fieldDefaults();
|
||||||
boolean unitTarget = "sys_unit".equalsIgnoreCase(config.getTargetTable());
|
boolean unitTarget = "sys_unit".equalsIgnoreCase(config.getTargetTable());
|
||||||
if (unitTarget) {
|
if (unitTarget) {
|
||||||
// 单位写入可能随父批次一起提交,行锁一直持有到外层事务完成。
|
// 单位写入可能随父批次一起提交,行锁一直持有到外层事务完成。
|
||||||
@@ -117,9 +118,10 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
rule.getLength() != null && Strings.isBlank(rule.getTargetField())
|
rule.getLength() != null && Strings.isBlank(rule.getTargetField())
|
||||||
&& Objects.equals(rule.getSourceField(), m.getSourceField())))
|
&& Objects.equals(rule.getSourceField(), m.getSourceField())))
|
||||||
.filter(m -> Boolean.TRUE.equals(m.getRequired())
|
.filter(m -> Boolean.TRUE.equals(m.getRequired())
|
||||||
|| (Strings.isNotBlank(m.getSourceField()) && Strings.isNotBlank(m.getTargetField())))
|
|| (Strings.isNotBlank(m.getTargetField())
|
||||||
|
&& (Strings.isNotBlank(m.getSourceField()) || fieldDefaults.containsKey(m.getTargetField()))))
|
||||||
.toList();
|
.toList();
|
||||||
List<NutMap> columns = validateTarget(config, mappings);
|
List<NutMap> columns = validateTarget(config, mappings, fieldDefaults);
|
||||||
log.info("[人员同步] 目标表和字段映射校验完成,configId={},batchNo={},targetTable={},mappingCount={},columnCount={}",
|
log.info("[人员同步] 目标表和字段映射校验完成,configId={},batchNo={},targetTable={},mappingCount={},columnCount={}",
|
||||||
configId, batch.getBatchNo(), config.getTargetTable(), mappings.size(), columns.size());
|
configId, batch.getBatchNo(), config.getTargetTable(), mappings.size(), columns.size());
|
||||||
if ("SYSTEM_TABLE".equals(config.getSourceType())) {
|
if ("SYSTEM_TABLE".equals(config.getSourceType())) {
|
||||||
@@ -161,7 +163,7 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
}
|
}
|
||||||
log.info("[人员同步] 开始字段映射转换及目标表批量写入,configId={},batchNo={},sourceCount={},mappingCount={}",
|
log.info("[人员同步] 开始字段映射转换及目标表批量写入,configId={},batchNo={},sourceCount={},mappingCount={}",
|
||||||
configId, batch.getBatchNo(), rows.size(), mappings.size());
|
configId, batch.getBatchNo(), rows.size(), mappings.size());
|
||||||
insertPage(config, mappings, columns, rows, batch, lockKey, owner, options, dictionaryValues, unitIds);
|
insertPage(config, mappings, columns, rows, batch, lockKey, owner, options, dictionaryValues, unitIds, fieldDefaults);
|
||||||
applySyncOptions(config, options, dictionaryValues, unitIds);
|
applySyncOptions(config, options, dictionaryValues, unitIds);
|
||||||
log.info("[人员同步] 目标表批量写入完成,configId={},batchNo={},successCount={},newCount={},changeCount={},skipCount={}",
|
log.info("[人员同步] 目标表批量写入完成,configId={},batchNo={},successCount={},newCount={},changeCount={},skipCount={}",
|
||||||
configId, batch.getBatchNo(), batch.getSuccessCount(), batch.getNewCount(),
|
configId, batch.getBatchNo(), batch.getSuccessCount(), batch.getNewCount(),
|
||||||
@@ -201,7 +203,7 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
if (!rows.isEmpty() && pageSize > 0) {
|
if (!rows.isEmpty() && pageSize > 0) {
|
||||||
Assert.isTrue(fingerprints.add(DigestUtil.sha256Hex(Json.toJson(rows))), "接口重复返回同一页,已终止并回滚本批次");
|
Assert.isTrue(fingerprints.add(DigestUtil.sha256Hex(Json.toJson(rows))), "接口重复返回同一页,已终止并回滚本批次");
|
||||||
}
|
}
|
||||||
insertPage(config, mappings, columns, rows, batch, lockKey, owner, options, dictionaryValues, unitIds);
|
insertPage(config, mappings, columns, rows, batch, lockKey, owner, options, dictionaryValues, unitIds, fieldDefaults);
|
||||||
// 按接口原始条数判定,已存在而跳过的记录仍属于这一页。
|
// 按接口原始条数判定,已存在而跳过的记录仍属于这一页。
|
||||||
// 仅数量模式是单次限量查询,即使正好返回配置数量也不能继续构造下一页请求。
|
// 仅数量模式是单次限量查询,即使正好返回配置数量也不能继续构造下一页请求。
|
||||||
if (singleLimitRequest || pageSize == 0 || rows.size() < pageSize) break;
|
if (singleLimitRequest || pageSize == 0 || rows.size() < pageSize) break;
|
||||||
@@ -267,9 +269,8 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
}
|
}
|
||||||
boolean created = false;
|
boolean created = false;
|
||||||
for (Map.Entry<String, Set<String>> entry : dictionaryValues.entrySet()) {
|
for (Map.Entry<String, Set<String>> entry : dictionaryValues.entrySet()) {
|
||||||
Sys_dict parent = dao.fetch(Sys_dict.class, Cnd.where("code", "=", entry.getKey())
|
Sys_dict parent = personApiConfigService.findUserFieldDictionary(entry.getKey());
|
||||||
.and(Cnd.exps("parentId", "=", "").or("parentId", "is", null)));
|
Assert.notNull(parent, "用户表字段字典下不存在该分类:" + entry.getKey());
|
||||||
Assert.notNull(parent, "字典分类不存在:" + entry.getKey());
|
|
||||||
// 同一字典分类下串行检查和插入,避免并发同步重复创建子项。
|
// 同一字典分类下串行检查和插入,避免并发同步重复创建子项。
|
||||||
Sql lock = Sqls.create("SELECT id FROM sys_dict WHERE id=@id FOR UPDATE").setParam("id", parent.getId());
|
Sql lock = Sqls.create("SELECT id FROM sys_dict WHERE id=@id FOR UPDATE").setParam("id", parent.getId());
|
||||||
lock.setCallback(Sqls.callback.str());
|
lock.setCallback(Sqls.callback.str());
|
||||||
@@ -374,7 +375,8 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 校验目标表和业务唯一标识;数据库唯一约束由批量写入检查,冲突时整批回滚。 */
|
/** 校验目标表和业务唯一标识;数据库唯一约束由批量写入检查,冲突时整批回滚。 */
|
||||||
private List<NutMap> validateTarget(PersonApiConfig config, List<PersonApiFieldMapping> mappings) {
|
private List<NutMap> validateTarget(PersonApiConfig config, List<PersonApiFieldMapping> mappings,
|
||||||
|
Map<String, String> fieldDefaults) {
|
||||||
Assert.hasText(config.getTargetTable(), "请选择目标人员中间表");
|
Assert.hasText(config.getTargetTable(), "请选择目标人员中间表");
|
||||||
List<NutMap> columns = personApiConfigService.columns(config.getTargetTable());
|
List<NutMap> columns = personApiConfigService.columns(config.getTargetTable());
|
||||||
Map<String, NutMap> fields = new HashMap<>();
|
Map<String, NutMap> fields = new HashMap<>();
|
||||||
@@ -387,14 +389,16 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
Assert.isTrue("HTTP_API".equals(config.getSourceType()) && "UPSERT".equals(config.getSyncMode())
|
Assert.isTrue("HTTP_API".equals(config.getSourceType()) && "UPSERT".equals(config.getSyncMode())
|
||||||
&& "id".equals(config.getIdentityField()), "单位API必须按来源单位id新增或更新");
|
&& "id".equals(config.getIdentityField()), "单位API必须按来源单位id新增或更新");
|
||||||
Assert.isTrue(mappings.stream().anyMatch(mapping -> "name".equals(mapping.getTargetField())
|
Assert.isTrue(mappings.stream().anyMatch(mapping -> "name".equals(mapping.getTargetField())
|
||||||
&& Strings.isNotBlank(mapping.getSourceField())), "单位API配置必须映射单位名称name");
|
&& (Strings.isNotBlank(mapping.getSourceField()) || fieldDefaults.containsKey("name"))),
|
||||||
|
"单位API配置必须映射单位名称name");
|
||||||
}
|
}
|
||||||
Assert.isTrue(fields.get("id").getString("columnType").toLowerCase(Locale.ROOT).contains("char"), "中间表id须支持字符串主键");
|
Assert.isTrue(fields.get("id").getString("columnType").toLowerCase(Locale.ROOT).contains("char"), "中间表id须支持字符串主键");
|
||||||
Assert.notEmpty(mappings, "请配置字段映射");
|
Assert.notEmpty(mappings, "请配置字段映射");
|
||||||
Set<String> targets = new HashSet<>();
|
Set<String> targets = new HashSet<>();
|
||||||
for (PersonApiFieldMapping mapping : mappings) {
|
for (PersonApiFieldMapping mapping : mappings) {
|
||||||
Assert.hasText(mapping.getSourceField(), "请补全来源字段");
|
|
||||||
Assert.hasText(mapping.getTargetField(), "请补全系统字段");
|
Assert.hasText(mapping.getTargetField(), "请补全系统字段");
|
||||||
|
Assert.isTrue(Strings.isNotBlank(mapping.getSourceField()) || fieldDefaults.containsKey(mapping.getTargetField()),
|
||||||
|
"请填写来源字段或默认值:" + mapping.getTargetField());
|
||||||
Assert.isTrue(fields.containsKey(mapping.getTargetField()), "目标表不存在映射字段:" + mapping.getTargetField());
|
Assert.isTrue(fields.containsKey(mapping.getTargetField()), "目标表不存在映射字段:" + mapping.getTargetField());
|
||||||
Assert.isTrue(targets.add(mapping.getTargetField()), "系统字段不能重复映射:" + mapping.getTargetField());
|
Assert.isTrue(targets.add(mapping.getTargetField()), "系统字段不能重复映射:" + mapping.getTargetField());
|
||||||
}
|
}
|
||||||
@@ -422,7 +426,8 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
private void insertPage(PersonApiConfig config, List<PersonApiFieldMapping> mappings, List<NutMap> columns,
|
private void insertPage(PersonApiConfig config, List<PersonApiFieldMapping> mappings, List<NutMap> columns,
|
||||||
List<?> rows, PersonSyncBatch batch, String lockKey, String owner,
|
List<?> rows, PersonSyncBatch batch, String lockKey, String owner,
|
||||||
PersonApiSyncOptions options, Map<String, Set<String>> dictionaryValues, Set<String> unitIds) {
|
PersonApiSyncOptions options, Map<String, Set<String>> dictionaryValues, Set<String> unitIds,
|
||||||
|
Map<String, String> fieldDefaults) {
|
||||||
Assert.isTrue((long) batch.getSourceCount() + rows.size() <= MAX_ROWS, "超过单批最大行数,已终止同步");
|
Assert.isTrue((long) batch.getSourceCount() + rows.size() <= MAX_ROWS, "超过单批最大行数,已终止同步");
|
||||||
batch.setSourceCount(batch.getSourceCount() + rows.size());
|
batch.setSourceCount(batch.getSourceCount() + rows.size());
|
||||||
Map<String, String> types = new HashMap<>();
|
Map<String, String> types = new HashMap<>();
|
||||||
@@ -462,7 +467,11 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
}
|
}
|
||||||
NutMap values = NutMap.NEW();
|
NutMap values = NutMap.NEW();
|
||||||
for (PersonApiFieldMapping mapping : mappings) {
|
for (PersonApiFieldMapping mapping : mappings) {
|
||||||
Object value = personApiConfigService.readResponsePath(row, mapping.getSourceField());
|
Object value = Strings.isBlank(mapping.getSourceField()) ? null
|
||||||
|
: personApiConfigService.readResponsePath(row, mapping.getSourceField());
|
||||||
|
if (value == null) {
|
||||||
|
value = fieldDefaults.get(mapping.getTargetField());
|
||||||
|
}
|
||||||
if (Boolean.TRUE.equals(mapping.getRequired()) && !(preserveUserNulls && value == null)) {
|
if (Boolean.TRUE.equals(mapping.getRequired()) && !(preserveUserNulls && value == null)) {
|
||||||
Assert.isTrue(value != null && !Strings.isBlank(String.valueOf(value)), "必填来源字段为空:" + mapping.getSourceField());
|
Assert.isTrue(value != null && !Strings.isBlank(String.valueOf(value)), "必填来源字段为空:" + mapping.getSourceField());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,6 +448,9 @@
|
|||||||
<el-form-item label="长度等于">
|
<el-form-item label="长度等于">
|
||||||
<el-input v-model="fieldRuleDraft.length" clearable placeholder="留空不过滤,例如 10"></el-input>
|
<el-input v-model="fieldRuleDraft.length" clearable placeholder="留空不过滤,例如 10"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="默认值">
|
||||||
|
<el-input v-model="fieldRuleDraft.defaultValue" clearable placeholder="未配置来源或来源值为 null 时使用"></el-input>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="对应字典">
|
<el-form-item label="对应字典">
|
||||||
<el-select v-model="fieldRuleDraft.dictCode" clearable filterable placeholder="请选择字典分类">
|
<el-select v-model="fieldRuleDraft.dictCode" clearable filterable placeholder="请选择字典分类">
|
||||||
<el-option v-for="item in dictChoices" :key="item.code"
|
<el-option v-for="item in dictChoices" :key="item.code"
|
||||||
@@ -529,7 +532,7 @@
|
|||||||
dictChoices: [],
|
dictChoices: [],
|
||||||
fieldRuleVisible: false,
|
fieldRuleVisible: false,
|
||||||
fieldRuleRow: null,
|
fieldRuleRow: null,
|
||||||
fieldRuleDraft: { length: '', dictCode: '' },
|
fieldRuleDraft: { length: '', dictCode: '', defaultValue: '' },
|
||||||
params: [],
|
params: [],
|
||||||
testCases: [],
|
testCases: [],
|
||||||
identityRow: null,
|
identityRow: null,
|
||||||
@@ -641,7 +644,7 @@
|
|||||||
return this.testResults[this.activeEndpoint]
|
return this.testResults[this.activeEndpoint]
|
||||||
},
|
},
|
||||||
hasMappedField() {
|
hasMappedField() {
|
||||||
return this.mappings.some((v) => Boolean(v.sourceField && v.targetField))
|
return this.mappings.some((v) => Boolean(v.targetField && (v.sourceField || v.defaultValue)))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -663,6 +666,7 @@
|
|||||||
this.$set(this, 'fieldRuleDraft', {
|
this.$set(this, 'fieldRuleDraft', {
|
||||||
length: row.filterLength == null ? '' : String(row.filterLength),
|
length: row.filterLength == null ? '' : String(row.filterLength),
|
||||||
dictCode: row.dictCode || '',
|
dictCode: row.dictCode || '',
|
||||||
|
defaultValue: row.defaultValue || '',
|
||||||
})
|
})
|
||||||
this.$set(this, 'fieldRuleVisible', true)
|
this.$set(this, 'fieldRuleVisible', true)
|
||||||
},
|
},
|
||||||
@@ -670,7 +674,8 @@
|
|||||||
const row = this.fieldRuleRow
|
const row = this.fieldRuleRow
|
||||||
const length = this.fieldRuleDraft.length.trim()
|
const length = this.fieldRuleDraft.length.trim()
|
||||||
const dictCode = this.fieldRuleDraft.dictCode
|
const dictCode = this.fieldRuleDraft.dictCode
|
||||||
if ((length || dictCode) && !(row.sourceField || '').trim()) {
|
const defaultValue = this.fieldRuleDraft.defaultValue
|
||||||
|
if ((length || (dictCode && !defaultValue)) && !(row.sourceField || '').trim()) {
|
||||||
this.notifyWarning('请先填写来源字段')
|
this.notifyWarning('请先填写来源字段')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -682,8 +687,17 @@
|
|||||||
this.notifyWarning('选择字典前请填写目标字段')
|
this.notifyWarning('选择字典前请填写目标字段')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (defaultValue && !row.targetField) {
|
||||||
|
this.notifyWarning('设置默认值前请选择目标字段')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (defaultValue && (['id', 'pullTime'].includes(row.targetField) || row === this.identityRow)) {
|
||||||
|
this.notifyWarning('主键、批次时间和唯一标识不能设置默认值')
|
||||||
|
return
|
||||||
|
}
|
||||||
this.$set(row, 'filterLength', length ? Number(length) : null)
|
this.$set(row, 'filterLength', length ? Number(length) : null)
|
||||||
this.$set(row, 'dictCode', dictCode)
|
this.$set(row, 'dictCode', dictCode)
|
||||||
|
this.$set(row, 'defaultValue', defaultValue)
|
||||||
this.$set(this, 'fieldRuleVisible', false)
|
this.$set(this, 'fieldRuleVisible', false)
|
||||||
},
|
},
|
||||||
openQuickMapping() {
|
openQuickMapping() {
|
||||||
@@ -749,6 +763,7 @@
|
|||||||
isIdentityMapping(row) {
|
isIdentityMapping(row) {
|
||||||
return Boolean(row && row.sourceField && row.sourceField.trim()
|
return Boolean(row && row.sourceField && row.sourceField.trim()
|
||||||
&& row.targetField && row.targetField.trim()
|
&& row.targetField && row.targetField.trim()
|
||||||
|
&& !row.defaultValue
|
||||||
&& row.targetField !== 'pullTime'
|
&& row.targetField !== 'pullTime'
|
||||||
&& (row.targetField !== 'id' || this.form.targetTable === 'sys_unit'))
|
&& (row.targetField !== 'id' || this.form.targetTable === 'sys_unit'))
|
||||||
},
|
},
|
||||||
@@ -930,6 +945,7 @@
|
|||||||
this.$set(row, 'targetField', '')
|
this.$set(row, 'targetField', '')
|
||||||
this.$set(row, 'targetComment', '')
|
this.$set(row, 'targetComment', '')
|
||||||
this.$set(row, 'identityFlag', false)
|
this.$set(row, 'identityFlag', false)
|
||||||
|
this.$set(row, 'defaultValue', '')
|
||||||
})
|
})
|
||||||
this.$set(this, 'identityRow', null)
|
this.$set(this, 'identityRow', null)
|
||||||
this.$set(this.form, 'enabled', false)
|
this.$set(this.form, 'enabled', false)
|
||||||
@@ -972,6 +988,7 @@
|
|||||||
identityFlag: old ? Boolean(old.identityFlag) : false,
|
identityFlag: old ? Boolean(old.identityFlag) : false,
|
||||||
filterLength: old ? old.filterLength : null,
|
filterLength: old ? old.filterLength : null,
|
||||||
dictCode: old ? old.dictCode : '',
|
dictCode: old ? old.dictCode : '',
|
||||||
|
defaultValue: old ? old.defaultValue : '',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.$set(this, 'mappings', rows)
|
this.$set(this, 'mappings', rows)
|
||||||
@@ -1138,11 +1155,12 @@
|
|||||||
const syncOptions = {
|
const syncOptions = {
|
||||||
autoCreateDict: this.syncOptions.autoCreateDict,
|
autoCreateDict: this.syncOptions.autoCreateDict,
|
||||||
unitConfigId: !unitTarget && this.autoSyncUnit ? this.syncOptions.unitConfigId : '',
|
unitConfigId: !unitTarget && this.autoSyncUnit ? this.syncOptions.unitConfigId : '',
|
||||||
rules: this.mappings.filter((row) => row.filterLength != null || row.dictCode).map((row) => ({
|
rules: this.mappings.filter((row) => row.filterLength != null || row.dictCode || row.defaultValue).map((row) => ({
|
||||||
sourceField: row.sourceField,
|
sourceField: row.sourceField,
|
||||||
targetField: row.targetField,
|
targetField: row.targetField,
|
||||||
length: row.filterLength,
|
length: row.filterLength,
|
||||||
dictCode: row.dictCode,
|
dictCode: row.dictCode,
|
||||||
|
defaultValue: row.defaultValue,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
this.$set(this, 'formLoading', true)
|
this.$set(this, 'formLoading', true)
|
||||||
@@ -1243,11 +1261,12 @@
|
|||||||
this.$set(this.syncOptions, 'unitConfigId', syncOptions.unitConfigId || '')
|
this.$set(this.syncOptions, 'unitConfigId', syncOptions.unitConfigId || '')
|
||||||
this.$set(this, 'autoSyncUnit', Boolean(syncOptions.unitConfigId))
|
this.$set(this, 'autoSyncUnit', Boolean(syncOptions.unitConfigId))
|
||||||
;(syncOptions.rules || []).forEach((rule) => {
|
;(syncOptions.rules || []).forEach((rule) => {
|
||||||
const row = this.mappings.find((item) => item.sourceField === rule.sourceField
|
const row = this.mappings.find((item) => (item.sourceField || '') === (rule.sourceField || '')
|
||||||
&& (item.targetField || '') === (rule.targetField || ''))
|
&& (item.targetField || '') === (rule.targetField || ''))
|
||||||
if (row) {
|
if (row) {
|
||||||
this.$set(row, 'filterLength', rule.length)
|
this.$set(row, 'filterLength', rule.length)
|
||||||
this.$set(row, 'dictCode', rule.dictCode || '')
|
this.$set(row, 'dictCode', rule.dictCode || '')
|
||||||
|
this.$set(row, 'defaultValue', rule.defaultValue || '')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const params = (res.data.params || []).map((v) => {
|
const params = (res.data.params || []).map((v) => {
|
||||||
|
|||||||
@@ -10,14 +10,17 @@
|
|||||||
* Access_Token 路径:data->access_token
|
* Access_Token 路径:data->access_token
|
||||||
* Expires_in 路径:data->expires_in,单位:SECONDS
|
* Expires_in 路径:data->expires_in,单位:SECONDS
|
||||||
* 数据地址:http://127.0.0.1:18080/api/persons,GET 或 POST
|
* 数据地址:http://127.0.0.1:18080/api/persons,GET 或 POST
|
||||||
|
* 机构地址:http://127.0.0.1:18080/api/units,GET 或 POST
|
||||||
* 返回码路径:code,成功返回值:0,消息路径:msg
|
* 返回码路径:code,成功返回值:0,消息路径:msg
|
||||||
* 人员数组路径:data->list,总数路径:data->total
|
* 数据数组路径:data->list,总数路径:data->total
|
||||||
* 数据字段按人员接口字段表生成,全部为字符串;码值为模拟值。
|
* 数据字段按人员接口字段表生成,全部为字符串;码值为模拟值。
|
||||||
* 字段映射示例:gh→loginname、xm→username、xb→sex、sjhm→mobile、yx→email、lxrq→arrivalAtSchoolDate。
|
* 字段映射示例:gh→loginname、xm→username、xb→sex、sjhm→mobile、yx→email、lxrq→arrivalAtSchoolDate。
|
||||||
* gh、grbsm 均为唯一标识,可按实际目标中间表字段配置。
|
* gh、grbsm 均为唯一标识,可按实际目标中间表字段配置。
|
||||||
|
* 机构配置:dwdm→id(唯一标识)、dwmc→name、bmflmc→unitType。
|
||||||
|
* 人员配置:bmmz→unitId,值与机构接口的 dwdm 相同。
|
||||||
* 分页 Query 参数:pageNumber=${页码}、pageSize=${数量},例如起始页码 1、数量 20。
|
* 分页 Query 参数:pageNumber=${页码}、pageSize=${数量},例如起始页码 1、数量 20。
|
||||||
* POST JSON 也可使用 {"pageNumber":"${页码}","pageSize":"${数量}"}。
|
* POST JSON 也可使用 {"pageNumber":"${页码}","pageSize":"${数量}"}。
|
||||||
* 不配置分页参数时一次返回全部 100 条数据。
|
* 也支持 page 和 pagesize。不配置分页参数时返回全部 100 条人员或 5 条机构。
|
||||||
* 默认不要求认证;MOCK_REQUIRE_TOKEN=1 时,数据接口要求以下任意一种凭证:
|
* 默认不要求认证;MOCK_REQUIRE_TOKEN=1 时,数据接口要求以下任意一种凭证:
|
||||||
* Header Authorization: Bearer ${Access_Token}
|
* Header Authorization: Bearer ${Access_Token}
|
||||||
* Query 或 JSON 参数 access_token: ${Access_Token}
|
* Query 或 JSON 参数 access_token: ${Access_Token}
|
||||||
@@ -32,12 +35,18 @@ const REQUIRE_TOKEN = process.env.MOCK_REQUIRE_TOKEN === '1';
|
|||||||
const TOKEN = 'person-governance-mock-token';
|
const TOKEN = 'person-governance-mock-token';
|
||||||
const MAX_BODY_BYTES = 1024 * 1024;
|
const MAX_BODY_BYTES = 1024 * 1024;
|
||||||
|
|
||||||
|
const units = [
|
||||||
|
{ dwdm: 'MOCK-D1', dwmc: '计算机学院', bmflmc: '教学单位' },
|
||||||
|
{ dwdm: 'MOCK-D2', dwmc: '机械工程学院', bmflmc: '教学单位' },
|
||||||
|
{ dwdm: 'MOCK-D3', dwmc: '经济管理学院', bmflmc: '教学单位' },
|
||||||
|
{ dwdm: 'MOCK-D4', dwmc: '外国语学院', bmflmc: '教学单位' },
|
||||||
|
{ dwdm: 'MOCK-D5', dwmc: '教务处', bmflmc: '行政单位' }
|
||||||
|
];
|
||||||
|
|
||||||
const persons = Array.from({ length: 100 }, (_, index) => {
|
const persons = Array.from({ length: 100 }, (_, index) => {
|
||||||
const number = index + 1;
|
const number = index + 1;
|
||||||
const suffix = String(number).padStart(3, '0');
|
const suffix = String(number).padStart(3, '0');
|
||||||
const department = index % 5;
|
const { dwdm: departmentCode, dwmc: departmentName } = units[index % units.length];
|
||||||
const departmentCode = `MOCK-D${department + 1}`;
|
|
||||||
const departmentName = ['计算机学院', '机械工程学院', '经济管理学院', '外国语学院', '教务处'][department];
|
|
||||||
const female = number % 2 === 0;
|
const female = number % 2 === 0;
|
||||||
const retired = number % 10 === 0;
|
const retired = number % 10 === 0;
|
||||||
const birthYear = retired ? 1960 + index % 5 : 1980 + index % 15;
|
const birthYear = retired ? 1960 + index % 5 : 1980 + index % 15;
|
||||||
@@ -175,7 +184,7 @@ function positiveInteger(value, fallback, name) {
|
|||||||
const server = http.createServer(async (request, response) => {
|
const server = http.createServer(async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(request.url, 'http://localhost');
|
const url = new URL(request.url, 'http://localhost');
|
||||||
if (!['/api/token', '/api/persons'].includes(url.pathname)) {
|
if (!['/api/token', '/api/persons', '/api/units'].includes(url.pathname)) {
|
||||||
request.resume();
|
request.resume();
|
||||||
return reply(response, 404, 404, '接口不存在');
|
return reply(response, 404, 404, '接口不存在');
|
||||||
}
|
}
|
||||||
@@ -196,14 +205,15 @@ const server = http.createServer(async (request, response) => {
|
|||||||
return reply(response, 401, 401, 'Token 无效或未提供');
|
return reply(response, 401, 401, 'Token 无效或未提供');
|
||||||
}
|
}
|
||||||
const pageNumber = positiveInteger(params.pageNumber ?? params.page, 1, 'pageNumber');
|
const pageNumber = positiveInteger(params.pageNumber ?? params.page, 1, 'pageNumber');
|
||||||
const pageSize = positiveInteger(params.pageSize, 100, 'pageSize');
|
const pageSize = positiveInteger(params.pageSize ?? params.pagesize, 100, 'pageSize');
|
||||||
if (pageSize > 1000) {
|
if (pageSize > 1000) {
|
||||||
return reply(response, 400, 400, 'pageSize 不能超过 1000');
|
return reply(response, 400, 400, 'pageSize 不能超过 1000');
|
||||||
}
|
}
|
||||||
const start = (pageNumber - 1) * pageSize;
|
const start = (pageNumber - 1) * pageSize;
|
||||||
|
const records = url.pathname === '/api/units' ? units : persons;
|
||||||
return reply(response, 200, 0, '成功', {
|
return reply(response, 200, 0, '成功', {
|
||||||
list: persons.slice(start, start + pageSize),
|
list: records.slice(start, start + pageSize),
|
||||||
total: persons.length,
|
total: records.length,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
pageSize
|
pageSize
|
||||||
});
|
});
|
||||||
@@ -220,5 +230,5 @@ server.on('error', error => {
|
|||||||
|
|
||||||
server.listen(PORT, HOST, () => {
|
server.listen(PORT, HOST, () => {
|
||||||
console.log(`人员治理测试服务:http://${HOST}:${PORT},已预填充 ${persons.length} 条模拟数据`);
|
console.log(`人员治理测试服务:http://${HOST}:${PORT},已预填充 ${persons.length} 条模拟数据`);
|
||||||
console.log('Token 接口:/api/token;人员接口:/api/persons');
|
console.log('Token 接口:/api/token;人员接口:/api/persons;机构接口:/api/units');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user