From 756b2d7f148ee267aacd8340b45d800b457b8c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=8C=E5=85=88=E8=88=9C?= <2499071264@qq.com> Date: Wed, 11 Nov 2026 09:36:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BA=BA=E5=91=98=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=B2=BB=E7=90=86=E8=BF=81=E7=A7=BB6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- .../controller/PersonApiConfigController.java | 6 + .../apiConfig/model/PersonApiSyncOptions.java | 21 +++ .../service/PersonApiConfigService.java | 5 + .../impl/PersonApiConfigServiceImpl.java | 87 ++++++++++- .../impl/PersonGovernanceSyncServiceImpl.java | 131 +++++++++++++++-- .../personGovernance/apiConfig/form.html | 135 +++++++++++++++++- 7 files changed, 370 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/model/PersonApiSyncOptions.java diff --git a/.gitignore b/.gitignore index 6d7bb81..d066b35 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,4 @@ src/main/resources/application-dev.yaml .vscode bin node-data-center-mock/ -.workbuddy \ No newline at end of file +.workbuddy/* \ No newline at end of file diff --git a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/controller/PersonApiConfigController.java b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/controller/PersonApiConfigController.java index dd3340a..82ed3ed 100644 --- a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/controller/PersonApiConfigController.java +++ b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/controller/PersonApiConfigController.java @@ -68,6 +68,12 @@ public class PersonApiConfigController { return Result.success(personApiConfigService.detail(id)); } + @At + @SaCheckPermission("data.person.governance.api") + public Result syncOptionChoices() { + return Result.success(personApiConfigService.syncOptionChoices()); + } + /** * 保存API配置。 * diff --git a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/model/PersonApiSyncOptions.java b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/model/PersonApiSyncOptions.java new file mode 100644 index 0000000..382a18b --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/model/PersonApiSyncOptions.java @@ -0,0 +1,21 @@ +package com.budwk.app.zhgh.personGovernance.apiConfig.model; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class PersonApiSyncOptions { + private Boolean autoCreateDict; + private String unitConfigId; + private List rules = new ArrayList<>(); + + @Data + public static class FieldRule { + private String sourceField; + private String targetField; + private Integer length; + private String dictCode; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/PersonApiConfigService.java b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/PersonApiConfigService.java index 020d588..ed72914 100644 --- a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/PersonApiConfigService.java +++ b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/PersonApiConfigService.java @@ -4,6 +4,7 @@ import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.service.BaseService; 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.PersonApiFieldMapping; import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiRequestParam; import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiTestCase; @@ -28,6 +29,10 @@ public interface PersonApiConfigService extends BaseService { */ PersonApiConfig saveConfig(String data); + PersonApiSyncOptions syncOptions(String configId); + + NutMap syncOptionChoices(); + void deleteConfig(String id); List tableNames(); diff --git a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonApiConfigServiceImpl.java b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonApiConfigServiceImpl.java index c1f4f0e..302e470 100644 --- a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonApiConfigServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonApiConfigServiceImpl.java @@ -11,11 +11,14 @@ import com.budwk.app.base.dao.CndPlus; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.sys.models.Sys_task; +import com.budwk.app.sys.models.Sys_config; +import com.budwk.app.sys.models.Sys_dict; import com.budwk.app.zhgh.personGovernance.apiConfig.job.PersonGovernanceSyncJob; import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiConfig; import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiFieldMapping; import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiRequestParam; import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiTestCase; +import com.budwk.app.zhgh.personGovernance.apiConfig.model.PersonApiSyncOptions; import com.budwk.app.zhgh.personGovernance.apiConfig.service.PersonApiConfigService; import com.budwk.app.zhgh.personGovernance.memberMatch.model.PersonMemberMatchConfig; import org.nutz.aop.interceptor.ioc.TransAop; @@ -103,6 +106,7 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl }); return NutMap.NEW() .addv("config", config) + .addv("syncOptions", syncOptions(id)) .addv("mappings", dao().query(PersonApiFieldMapping.class, Cnd.where("configId", "=", id).asc("sort"))) .addv("params", params) .addv("testCases", testCases); @@ -157,7 +161,14 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl Assert.isTrue(mappings.stream().filter(v -> Boolean.TRUE.equals(v.getIdentityFlag())).count() == 1 && identity != null && Strings.isNotBlank(identity.getSourceField()) && Strings.isNotBlank(identity.getTargetField()) - && !List.of("id", "pullTime").contains(identity.getTargetField()), "请选择一个有效的业务唯一标识映射"); + && !"pullTime".equals(identity.getTargetField()) + && (!"id".equals(identity.getTargetField()) || isUnitConfig(config)), "请选择一个有效的业务唯一标识映射"); + } + if ("sys_unit".equalsIgnoreCase(config.getTargetTable())) { + Assert.isTrue(isUnitConfig(config) && "UPSERT".equals(config.getSyncMode()) + && "id".equals(config.getIdentityField()), "单位API配置必须使用存在即修改模式,并将来源单位编码映射到id作为唯一标识"); + Assert.isTrue(mappings.stream().anyMatch(mapping -> "name".equals(mapping.getTargetField()) + && Strings.isNotBlank(mapping.getSourceField())), "单位API配置必须映射单位名称name"); } if ("HTTP_API".equals(config.getSourceType())) pagination(config, params); @@ -251,7 +262,78 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl PersonApiFieldMapping.class, Json.toJson(payload.get("mappings"))); List params = Json.fromJsonAsList( PersonApiRequestParam.class, Json.toJson(payload.get("params"))); - return saveConfig(config, mappings, params); + PersonApiSyncOptions options = payload.containsKey("syncOptions") + ? Json.fromJson(PersonApiSyncOptions.class, Json.toJson(payload.get("syncOptions"))) : null; + if (options != null) { + validateSyncOptions(config, mappings, options); + } + PersonApiConfig saved = saveConfig(config, mappings, params); + 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) { + return "personGovernance.syncOptions." + configId; + } + + @Override + public PersonApiSyncOptions syncOptions(String configId) { + Sys_config settings = dao().fetch(Sys_config.class, syncOptionsKey(configId)); + return settings == null ? new PersonApiSyncOptions() + : Json.fromJson(PersonApiSyncOptions.class, settings.getConfigValue()); + } + + @Override + public NutMap syncOptionChoices() { + return NutMap.NEW() + .addv("dicts", dao().query(Sys_dict.class, Cnd.where("parentId", "=", "").or("parentId", "is", null).asc("name"))) + .addv("unitConfigs", dao().query(PersonApiConfig.class, + Cnd.where("sourceType", "=", "HTTP_API").and("targetTable", "=", "sys_unit") + .and("syncMode", "=", "UPSERT").and("identityField", "=", "id") + .and(Cnd.exps("delFlag", "=", false).or("delFlag", "is", null)).asc("configName")).stream() + .map(item -> NutMap.NEW().addv("id", item.getId()).addv("configName", item.getConfigName())).toList()); + } + + private boolean isUnitConfig(PersonApiConfig config) { + return "HTTP_API".equals(config.getSourceType()) && "sys_unit".equalsIgnoreCase(config.getTargetTable()); + } + + private void validateSyncOptions(PersonApiConfig config, List mappings, PersonApiSyncOptions options) { + if (Strings.isNotBlank(options.getUnitConfigId())) { + PersonApiConfig unitConfig = fetch(options.getUnitConfigId()); + Assert.isTrue(!"sys_unit".equalsIgnoreCase(config.getTargetTable()) && unitConfig != null + && !Boolean.TRUE.equals(unitConfig.getDelFlag()) && isUnitConfig(unitConfig) + && "UPSERT".equals(unitConfig.getSyncMode()) && "id".equals(unitConfig.getIdentityField()), + "请选择有效的单位API配置"); + Assert.isTrue(mappings != null && mappings.stream().anyMatch(mapping -> "unitId".equals(mapping.getTargetField()) + && Strings.isNotBlank(mapping.getSourceField())), "自动同步单位需要配置unitId映射"); + } + Assert.notNull(options.getRules(), "字段配置不能为空"); + java.util.Set> ruleFields = new java.util.HashSet<>(); + for (PersonApiSyncOptions.FieldRule rule : options.getRules()) { + Assert.notNull(rule, "字段配置不能为空"); + Assert.hasText(rule.getSourceField(), "请填写配置规则的来源字段"); + Assert.isTrue(ruleFields.add(List.of(rule.getSourceField(), Strings.sNull(rule.getTargetField()))), + "同一来源字段和目标字段只能配置一条规则"); + Assert.isTrue(rule.getLength() == null || rule.getLength() >= 0, "字段长度必须为非负整数"); + Assert.isTrue(mappings != null && mappings.stream().anyMatch(mapping -> + Objects.equals(mapping.getSourceField(), rule.getSourceField()) + && Objects.equals(Strings.sNull(mapping.getTargetField()), Strings.sNull(rule.getTargetField()))), + "字段规则必须关联一条字段映射"); + if (Strings.isNotBlank(rule.getDictCode())) { + Assert.hasText(rule.getTargetField(), "选择字典前请填写目标字段"); + Assert.isTrue(!"pullTime".equals(rule.getTargetField()) + && (!"id".equals(rule.getTargetField()) || isUnitConfig(config)), "系统生成字段不能配置字典"); + Assert.notNull(dao().fetch(Sys_dict.class, Cnd.where("code", "=", rule.getDictCode()) + .and(Cnd.exps("parentId", "=", "").or("parentId", "is", null))), "字典分类不存在"); + } + } } @Override @@ -268,6 +350,7 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl PersonApiConfig config = fetch(id); config.setEnabled(false); reconcileTask(config); + dao().delete(Sys_config.class, syncOptionsKey(id)); } @Override diff --git a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonGovernanceSyncServiceImpl.java b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonGovernanceSyncServiceImpl.java index e417ba9..b906572 100644 --- a/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonGovernanceSyncServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/personGovernance/apiConfig/service/impl/PersonGovernanceSyncServiceImpl.java @@ -5,6 +5,9 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.crypto.digest.DigestUtil; import com.budwk.app.base.constant.RedisConstant; import com.budwk.app.sys.models.Sys_task; +import com.budwk.app.sys.models.Sys_dict; +import com.budwk.app.sys.models.Sys_unit; +import com.budwk.app.sys.services.SysDictService; import com.budwk.app.sys.models.Sys_user; import com.budwk.app.sys.models.Sys_user_history; import com.budwk.app.sys.models.Sys_user_source; @@ -55,6 +58,8 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ private RedisService redisService; @Inject private PersonMemberMatchService personMemberMatchService; + @Inject + private SysDictService sysDictService; /** * configId为保存的配置,triggerType为MANUAL/SCHEDULED;返回批次,FAILED时errorMessage为原因。 @@ -88,9 +93,22 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ }); List mappings = dao.query(PersonApiFieldMapping.class, Cnd.where("configId", "=", configId).asc("sort")); + PersonApiSyncOptions options = personApiConfigService.syncOptions(configId); + boolean unitTarget = "sys_unit".equalsIgnoreCase(config.getTargetTable()); + if (unitTarget) { + // 单位写入可能随父批次一起提交,行锁一直持有到外层事务完成。 + Sql lock = Sqls.create("SELECT id FROM person_api_config WHERE id=@id FOR UPDATE").setParam("id", configId); + lock.setCallback(Sqls.callback.str()); + dao.execute(lock); + } + Map> dictionaryValues = new TreeMap<>(); + Set unitIds = new LinkedHashSet<>(); // 页面会加载整张表的列;批次字段由系统生成,未配置的非必填列交给数据库默认值。 mappings = mappings.stream() - .filter(m -> !List.of("id", "pullTime").contains(Strings.sNull(m.getTargetField()))) + .filter(m -> !"pullTime".equals(m.getTargetField()) && (unitTarget || !"id".equals(m.getTargetField()))) + .filter(m -> Strings.isNotBlank(m.getTargetField()) || options.getRules().stream().noneMatch(rule -> + rule.getLength() != null && Strings.isBlank(rule.getTargetField()) + && Objects.equals(rule.getSourceField(), m.getSourceField()))) .filter(m -> Boolean.TRUE.equals(m.getRequired()) || (Strings.isNotBlank(m.getSourceField()) && Strings.isNotBlank(m.getTargetField()))) .toList(); @@ -136,7 +154,8 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ } log.info("[人员同步] 开始字段映射转换及目标表批量写入,configId={},batchNo={},sourceCount={},mappingCount={}", configId, batch.getBatchNo(), rows.size(), mappings.size()); - insertPage(config, mappings, columns, rows, batch, lockKey, owner); + insertPage(config, mappings, columns, rows, batch, lockKey, owner, options, dictionaryValues, unitIds); + applySyncOptions(config, options, dictionaryValues, unitIds); log.info("[人员同步] 目标表批量写入完成,configId={},batchNo={},successCount={},newCount={},changeCount={},skipCount={}", configId, batch.getBatchNo(), batch.getSuccessCount(), batch.getNewCount(), batch.getChangeCount(), batch.getSourceCount() - batch.getSuccessCount()); @@ -175,12 +194,13 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ if (!rows.isEmpty() && pageSize > 0) { Assert.isTrue(fingerprints.add(DigestUtil.sha256Hex(Json.toJson(rows))), "接口重复返回同一页,已终止并回滚本批次"); } - insertPage(config, mappings, columns, rows, batch, lockKey, owner); + insertPage(config, mappings, columns, rows, batch, lockKey, owner, options, dictionaryValues, unitIds); // 按接口原始条数判定,已存在而跳过的记录仍属于这一页。 // 仅数量模式是单次限量查询,即使正好返回配置数量也不能继续构造下一页请求。 if (singleLimitRequest || pageSize == 0 || rows.size() < pageSize) break; page = Math.addExact(page, paging.getLong("step")); } + applySyncOptions(config, options, dictionaryValues, unitIds); // 第三方接口同步同样以配置开关为准,分页全部写入完成后只执行一次会员规则匹配。 if (shouldMatchMemberRules(config)) { personMemberMatchService.reconcileForApiConfig(configId); @@ -222,6 +242,57 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ } /** 工会会员与福利会员匹配只受配置开关控制,两类来源均可在同步完成后执行。 */ + private void applySyncOptions(PersonApiConfig config, PersonApiSyncOptions options, + Map> dictionaryValues, Set unitIds) { + if (!unitIds.isEmpty() && Strings.isNotBlank(options.getUnitConfigId())) { + Set missing = missingUnitIds(unitIds); + if (!missing.isEmpty()) { + PersonApiConfig unitConfig = dao.fetch(PersonApiConfig.class, options.getUnitConfigId()); + Assert.isTrue(!"sys_unit".equalsIgnoreCase(config.getTargetTable()) && unitConfig != null + && "sys_unit".equalsIgnoreCase(unitConfig.getTargetTable()) + && "HTTP_API".equals(unitConfig.getSourceType()), "关联的单位API配置不可用"); + PersonSyncBatch unitBatch = execute(unitConfig.getId(), "LINKED"); + Assert.isTrue("SUCCESS".equals(unitBatch.getStatus()), "自动同步单位失败:" + unitBatch.getErrorMessage()); + Set stillMissing = missingUnitIds(missing); + Assert.isTrue(stillMissing.isEmpty(), "单位API未返回所需单位:" + String.join("、", + stillMissing.stream().limit(5).toList())); + } + } + boolean created = false; + for (Map.Entry> entry : dictionaryValues.entrySet()) { + Sys_dict parent = dao.fetch(Sys_dict.class, Cnd.where("code", "=", entry.getKey()) + .and(Cnd.exps("parentId", "=", "").or("parentId", "is", null))); + Assert.notNull(parent, "字典分类不存在:" + entry.getKey()); + // 同一字典分类下串行检查和插入,避免并发同步重复创建子项。 + Sql lock = Sqls.create("SELECT id FROM sys_dict WHERE id=@id FOR UPDATE").setParam("id", parent.getId()); + lock.setCallback(Sqls.callback.str()); + dao.execute(lock); + Set existing = new HashSet<>(); + dao.query(Sys_dict.class, Cnd.where("parentId", "=", parent.getId())) + .forEach(item -> existing.add(item.getCode())); + for (String value : entry.getValue()) { + if (existing.add(value)) { + Sys_dict item = new Sys_dict(); + item.setCode(value); + item.setName(value); + sysDictService.save(item, parent.getId()); + created = true; + } + } + } + if (created) sysDictService.clearCache(); + } + + private Set missingUnitIds(Set unitIds) { + Set missing = new LinkedHashSet<>(unitIds); + List ids = new ArrayList<>(unitIds); + for (int start = 0; start < ids.size(); start += WRITE_BATCH_SIZE) { + List chunk = ids.subList(start, Math.min(start + WRITE_BATCH_SIZE, ids.size())); + dao.query(Sys_unit.class, Cnd.where("id", "in", chunk)).forEach(unit -> missing.remove(unit.getId())); + } + return missing; + } + private boolean shouldMatchMemberRules(PersonApiConfig config) { return Boolean.TRUE.equals(config.getMatchMemberRules()); } @@ -301,7 +372,14 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ List columns = personApiConfigService.columns(config.getTargetTable()); Map fields = new HashMap<>(); columns.forEach(c -> fields.put(c.getString("columnName"), c)); - Assert.isTrue(fields.containsKey("id") && fields.containsKey("pullTime"), "目标中间表必须包含id和pullTime字段"); + boolean unitTarget = "sys_unit".equalsIgnoreCase(config.getTargetTable()); + Assert.isTrue(fields.containsKey("id") && (unitTarget || fields.containsKey("pullTime")), "目标中间表必须包含id和pullTime字段"); + if (unitTarget) { + Assert.isTrue("HTTP_API".equals(config.getSourceType()) && "UPSERT".equals(config.getSyncMode()) + && "id".equals(config.getIdentityField()), "单位API必须按来源单位id新增或更新"); + Assert.isTrue(mappings.stream().anyMatch(mapping -> "name".equals(mapping.getTargetField()) + && Strings.isNotBlank(mapping.getSourceField())), "单位API配置必须映射单位名称name"); + } Assert.isTrue(fields.get("id").getString("columnType").toLowerCase(Locale.ROOT).contains("char"), "中间表id须支持字符串主键"); Assert.notEmpty(mappings, "请配置字段映射"); Set targets = new HashSet<>(); @@ -315,7 +393,8 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ if (!"APPEND".equals(mode)) { Assert.hasText(config.getIdentityField(), "去重同步必须配置唯一标识"); Assert.isTrue(targets.contains(config.getIdentityField()) - && !List.of("id", "pullTime").contains(config.getIdentityField()), "唯一标识必须是有效的业务映射字段"); + && !"pullTime".equals(config.getIdentityField()) + && (unitTarget || !"id".equals(config.getIdentityField())), "唯一标识必须是有效的业务映射字段"); } return columns; } @@ -333,12 +412,18 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ */ @Aop(TransAop.READ_COMMITTED) private void insertPage(PersonApiConfig config, List mappings, List columns, - List rows, PersonSyncBatch batch, String lockKey, String owner) { + List rows, PersonSyncBatch batch, String lockKey, String owner, + PersonApiSyncOptions options, Map> dictionaryValues, Set unitIds) { Assert.isTrue((long) batch.getSourceCount() + rows.size() <= MAX_ROWS, "超过单批最大行数,已终止同步"); batch.setSourceCount(batch.getSourceCount() + rows.size()); Map types = new HashMap<>(); columns.forEach(c -> types.put(c.getString("columnName"), c.getString("columnType"))); String mode = syncMode(config); + boolean unitTarget = "sys_unit".equalsIgnoreCase(config.getTargetTable()); + List lengthRules = options.getRules().stream() + .filter(rule -> rule.getLength() != null).toList(); + List dictionaryRules = Boolean.TRUE.equals(options.getAutoCreateDict()) + ? options.getRules().stream().filter(rule -> Strings.isNotBlank(rule.getDictCode())).toList() : List.of(); PersonApiFieldMapping identityMapping = mappings.stream() .filter(mapping -> Strings.isNotBlank(config.getIdentityField()) && Objects.equals(config.getIdentityField(), mapping.getTargetField())) @@ -349,6 +434,11 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ int chunkSourceCount = Math.min(offset + WRITE_BATCH_SIZE, rows.size()) - offset; for (Object row : rows.subList(offset, Math.min(offset + WRITE_BATCH_SIZE, rows.size()))) { Assert.isTrue(row instanceof Map, "数据集行必须为JSON对象"); + boolean matchesLength = lengthRules.stream().allMatch(rule -> { + Object value = personApiConfigService.readResponsePath(row, rule.getSourceField()); + return value != null && String.valueOf(value).length() == rule.getLength(); + }); + if (!matchesLength) continue; // 先过滤空业务标识,再校验其他必填字段及转换类型;未配置标识的全量追加保持原行为。 // 不改变原始rows和sourceCount,避免整页被过滤时提前结束分页,跳过数量由原始数减成功数统计。 if (identityMapping != null) { @@ -373,8 +463,13 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ Object identity = values.get(config.getIdentityField()); Assert.isTrue(identity != null && Strings.isNotBlank(String.valueOf(identity)), "唯一标识不能为空:" + config.getIdentityField()); } - values.put("id", R.UU32()); - values.put("pullTime", batch.getStartedAt()); + if (unitTarget) { + Assert.hasText(values.getString("name"), "单位名称不能为空"); + values.putIfAbsent("unitcode", values.get("id")); + } else { + values.put("id", R.UU32()); + values.put("pullTime", batch.getStartedAt()); + } valuesList.add(values); } // 当前500条全部被过滤时不查询目标表,也不提交空批次。 @@ -390,7 +485,8 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ // 保护标识只约束定时任务;人工同步仍保持现有行为,便于管理员主动修复数据。 boolean protectSysUser = "SCHEDULED".equals(batch.getTriggerType()) && "sys_user".equalsIgnoreCase(config.getTargetTable()); - dao.run(connection -> writeChunk(connection, config, valuesList, mode, protectSysUser, counts)); + dao.run(connection -> writeChunk(connection, config, valuesList, mode, protectSysUser, counts, + dictionaryRules, dictionaryValues, Strings.isNotBlank(options.getUnitConfigId()) ? unitIds : null)); batch.setNewCount(batch.getNewCount() + counts[0]); batch.setChangeCount(batch.getChangeCount() + counts[1]); batch.setSuccessCount(batch.getSuccessCount() + counts[0] + counts[1]); @@ -406,7 +502,9 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ */ @Aop(TransAop.READ_COMMITTED) private void writeChunk(Connection connection, PersonApiConfig config, List rows, - String mode, boolean protectSysUser, int[] counts) throws SQLException { + String mode, boolean protectSysUser, int[] counts, + List dictionaryRules, + Map> dictionaryValues, Set unitIds) throws SQLException { String table = quoteIdentifier(config.getTargetTable()); Map existing = new HashMap<>(); Set protectedRows = new HashSet<>(); @@ -514,6 +612,19 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ } } if (!updates.isEmpty()) executeWriteBatch(connection, table, new ArrayList<>(updates.values()), true); + for (Collection written : List.of(inserts.values(), updates.values())) { + for (NutMap row : written) { + for (PersonApiSyncOptions.FieldRule rule : dictionaryRules) { + String value = row.getString(rule.getTargetField()); + if (Strings.isNotBlank(value)) { + dictionaryValues.computeIfAbsent(rule.getDictCode(), key -> new LinkedHashSet<>()).add(value); + } + } + if (unitIds != null && Strings.isNotBlank(row.getString("unitId"))) { + unitIds.add(row.getString("unitId")); + } + } + } if (protectSysUser) { String changeReason = syncHistoryReason(config); recordSyncHistories(updateSnapshots, updateChangeTypes, changeReason); diff --git a/src/main/resources/views/platform/personGovernance/apiConfig/form.html b/src/main/resources/views/platform/personGovernance/apiConfig/form.html index 5535ec0..7adde72 100644 --- a/src/main/resources/views/platform/personGovernance/apiConfig/form.html +++ b/src/main/resources/views/platform/personGovernance/apiConfig/form.html @@ -133,6 +133,15 @@ + + + + + + + + +
是否匹配会员规则
+
+ + +
+
+ + + + + +
设为标识 - -