From 494de02d65d948ed1ccba972276ed034855a5ea0 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 18 Dec 2025 21:39:49 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=E7=88=B6=E5=AD=90?= =?UTF-8?q?=E9=83=A8=E9=97=A8=E8=B7=A8=E9=A1=B5=E5=9C=BA=E6=99=AF=E4=B8=8B?= =?UTF-8?q?=EF=BC=8C=E6=97=A0=E6=B3=95=E6=AD=A3=E7=A1=AE=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=B1=82=E7=BA=A7=E4=BB=A3=E7=A0=81=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/service/dept/DeptService.java | 7 ++ .../system/service/dept/DeptServiceImpl.java | 23 ++++++ .../integration/iwork/IWorkSyncProcessor.java | 51 +++++++++++- .../iwork/impl/IWorkSyncProcessorImpl.java | 81 +++++++++++++++++-- .../iwork/impl/IWorkSyncServiceImpl.java | 26 ++++-- 5 files changed, 173 insertions(+), 15 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java index c97510df..80834565 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java @@ -174,4 +174,11 @@ public interface DeptService { * @return 部门列表 */ List searchDeptTree(String keyword); + + /** + * 回填缺失的部门编码(不触发事件)。 + * + * @param deptIds 需要回填的部门 ID 列表 + */ + void backfillMissingCodesWithoutEvent(Collection deptIds); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java index 03cadf43..24104dc1 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java @@ -900,4 +900,27 @@ public class DeptServiceImpl implements DeptService { } } + @Override + @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) + public void backfillMissingCodesWithoutEvent(Collection deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return; + } + List targets = deptMapper.selectBatchIds(deptIds); + for (DeptDO dept : targets) { + if (dept == null || StrUtil.isNotBlank(dept.getCode())) { + continue; + } + Integer source = ObjectUtil.defaultIfNull(dept.getDeptSource(), DeptSourceEnum.EXTERNAL.getSource()); + try { + String code = generateDeptCode(dept.getParentId(), source); + validateDeptCodeUnique(dept.getId(), code); + updateDeptCode(dept.getId(), code); + } catch (Exception ex) { + log.warn("[iWork] 回填部门编码失败 id={} name={} msg={}", dept.getId(), dept.getName(), ex.getMessage()); + } + } + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java index c19d3975..17ec9b6a 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java @@ -5,23 +5,42 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJo import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO; import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Abstraction for applying iWork entities into local persistence. */ public interface IWorkSyncProcessor { - BatchResult syncSubcompanies(List data, SyncOptions options); + BatchResult syncSubcompanies(List data, + SyncOptions options); + + BatchResult syncSubcompanies(List data, + SyncOptions options, + DeptSyncContext context); BatchResult syncDepartments(List data, SyncOptions options); + BatchResult syncDepartments(List data, + SyncOptions options, + DeptSyncContext context); + BatchResult syncJobTitles(List data, SyncOptions options); BatchResult syncUsers(List data, SyncOptions options); + /** + * 对当次同步累计的待处理/占位部门做最终补偿(跨页父子依赖)。 + */ + default BatchResult flushDeptPending(DeptSyncContext context, SyncOptions options) { + return BatchResult.empty(); + } + /** * Execution options shared by batch and single sync flows. */ @@ -53,6 +72,32 @@ public interface IWorkSyncProcessor { } } + /** + * 部门/分部跨页同步上下文,用于累计待处理记录与已就绪父级。 + */ + final class DeptSyncContext { + private final Set readyParentIds = new HashSet<>(); + private final List pendingSubcompanies = new ArrayList<>(); + private final List pendingDepartments = new ArrayList<>(); + private final Set placeholderDeptIds = new HashSet<>(); + + public Set getReadyParentIds() { + return readyParentIds; + } + + public List getPendingSubcompanies() { + return pendingSubcompanies; + } + + public List getPendingDepartments() { + return pendingDepartments; + } + + public Set getPlaceholderDeptIds() { + return placeholderDeptIds; + } + } + /** * Aggregated result for a sync batch. */ @@ -170,11 +215,11 @@ public interface IWorkSyncProcessor { } default BatchResult syncSubcompany(IWorkHrSubcompanyPageRespVO.Subcompany data, SyncOptions options) { - return syncSubcompanies(Collections.singletonList(data), options); + return syncSubcompanies(Collections.singletonList(data), options, null); } default BatchResult syncDepartment(IWorkHrDepartmentPageRespVO.Department data, SyncOptions options) { - return syncDepartments(Collections.singletonList(data), options); + return syncDepartments(Collections.singletonList(data), options, null); } default BatchResult syncJobTitle(IWorkHrJobTitlePageRespVO.JobTitle data, SyncOptions options) { diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java index 3c004a2b..bbabb064 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java @@ -50,14 +50,34 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { @Override public BatchResult syncSubcompanies(List data, SyncOptions options) { + return syncSubcompanies(data, options, null); + } + + @Override + public BatchResult syncSubcompanies(List data, + SyncOptions options, + DeptSyncContext context) { + return syncSubcompaniesInternal(data, options, context, false); + } + + private BatchResult syncSubcompaniesInternal(List data, + SyncOptions options, + DeptSyncContext context, + boolean allowPlaceholderOnRemaining) { List records = CollUtil.emptyIfNull(data); BatchResult result = BatchResult.empty(); - if (records.isEmpty()) { + if (records.isEmpty() + && (context == null || CollUtil.isEmpty(context.getPendingSubcompanies()))) { return result; } result.increasePulled(records.size()); - List queue = new ArrayList<>(records); - Set readyParentIds = new HashSet<>(); + List queue = new ArrayList<>(); + if (context != null && CollUtil.isNotEmpty(context.getPendingSubcompanies())) { + queue.addAll(context.getPendingSubcompanies()); + context.getPendingSubcompanies().clear(); + } + queue.addAll(records); + Set readyParentIds = context != null ? context.getReadyParentIds() : new HashSet<>(); int guard = 0; int maxPasses = Math.max(1, queue.size() * 2); while (!queue.isEmpty() && guard++ < maxPasses) { @@ -106,6 +126,12 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { break; } } + if (!queue.isEmpty()) { + if (context != null && !allowPlaceholderOnRemaining) { + context.getPendingSubcompanies().addAll(queue); + queue.clear(); + } + } if (!queue.isEmpty()) { for (IWorkHrSubcompanyPageRespVO.Subcompany remaining : queue) { log.warn("[iWork] 分部父级缺失,延迟生成编码插入占位: id={} name={}", remaining.getId(), remaining.getSubcompanyname()); @@ -117,6 +143,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { try { DeptSyncOutcome outcome = upsertDept(saveReq.getId(), saveReq, isCanceledFlag(remaining.getCanceled()), options); applyDeptOutcome(result, outcome, "分部", remaining.getSubcompanyname()); + if (context != null && outcome.deptId() != null) { + context.getPlaceholderDeptIds().add(outcome.deptId()); + } } catch (Exception ex) { log.error("[iWork] 分部占位插入失败: id={} name={}", remaining.getId(), remaining.getSubcompanyname(), ex); result.increaseFailed(); @@ -128,14 +157,34 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { @Override public BatchResult syncDepartments(List data, SyncOptions options) { + return syncDepartments(data, options, null); + } + + @Override + public BatchResult syncDepartments(List data, + SyncOptions options, + DeptSyncContext context) { + return syncDepartmentsInternal(data, options, context, false); + } + + private BatchResult syncDepartmentsInternal(List data, + SyncOptions options, + DeptSyncContext context, + boolean allowPlaceholderOnRemaining) { List records = CollUtil.emptyIfNull(data); BatchResult result = BatchResult.empty(); - if (records.isEmpty()) { + if (records.isEmpty() + && (context == null || CollUtil.isEmpty(context.getPendingDepartments()))) { return result; } result.increasePulled(records.size()); - List queue = new ArrayList<>(records); - Set readyParentIds = new HashSet<>(); + List queue = new ArrayList<>(); + if (context != null && CollUtil.isNotEmpty(context.getPendingDepartments())) { + queue.addAll(context.getPendingDepartments()); + context.getPendingDepartments().clear(); + } + queue.addAll(records); + Set readyParentIds = context != null ? context.getReadyParentIds() : new HashSet<>(); int guard = 0; int maxPasses = Math.max(1, queue.size() * 2); while (!queue.isEmpty() && guard++ < maxPasses) { @@ -184,6 +233,12 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { break; } } + if (!queue.isEmpty()) { + if (context != null && !allowPlaceholderOnRemaining) { + context.getPendingDepartments().addAll(queue); + queue.clear(); + } + } if (!queue.isEmpty()) { for (IWorkHrDepartmentPageRespVO.Department remaining : queue) { log.warn("[iWork] 部门父级缺失,延迟生成编码插入占位: id={} name={}", remaining.getId(), remaining.getDepartmentname()); @@ -195,6 +250,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { try { DeptSyncOutcome outcome = upsertDept(saveReq.getId(), saveReq, isCanceledFlag(remaining.getCanceled()), options); applyDeptOutcome(result, outcome, "部门", remaining.getDepartmentname()); + if (context != null && outcome.deptId() != null) { + context.getPlaceholderDeptIds().add(outcome.deptId()); + } } catch (Exception ex) { log.error("[iWork] 部门占位插入失败: id={} name={}", remaining.getId(), remaining.getDepartmentname(), ex); result.increaseFailed(); @@ -204,6 +262,17 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { return result; } + @Override + public BatchResult flushDeptPending(DeptSyncContext context, SyncOptions options) { + BatchResult result = BatchResult.empty(); + if (context == null) { + return result; + } + result.merge(syncSubcompaniesInternal(Collections.emptyList(), options, context, true)); + result.merge(syncDepartmentsInternal(Collections.emptyList(), options, context, true)); + return result; + } + @Override public BatchResult syncJobTitles(List data, SyncOptions options) { List records = CollUtil.emptyIfNull(data); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java index 8954ea8f..b06959d6 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil; import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*; import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum; +import com.zt.plat.module.system.service.dept.DeptService; import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService; import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor; import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService; @@ -31,6 +32,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { private final IWorkOrgRestService orgRestService; private final IWorkSyncProcessor syncProcessor; + private final DeptService deptService; @Override public IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO) { @@ -64,11 +66,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { boolean syncJobTitle = scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE); int processedPages = 0; IWorkSyncProcessor.SyncOptions options = buildFullSyncOptions(reqVO); + IWorkSyncProcessor.DeptSyncContext deptSyncContext = (syncDepartments || syncSubcompanies) + ? new IWorkSyncProcessor.DeptSyncContext() + : null; if (syncSubcompanies) { - processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats); + processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats, deptSyncContext); } if (syncDepartments) { - processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats); + processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats, deptSyncContext); } if (syncJobTitle) { processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats); @@ -76,6 +81,13 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { if (syncUsers) { processedPages += executeUserFullSync(reqVO, options, respVO.getUserStat(), batchStats); } + if (deptSyncContext != null) { + IWorkSyncProcessor.BatchResult flushResult = syncProcessor.flushDeptPending(deptSyncContext, options); + updateStat(respVO.getDepartmentStat(), flushResult, 0); + if (CollUtil.isNotEmpty(deptSyncContext.getPlaceholderDeptIds())) { + deptService.backfillMissingCodesWithoutEvent(deptSyncContext.getPlaceholderDeptIds()); + } + } respVO.setProcessedPages(processedPages); return respVO; } @@ -83,7 +95,8 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { private int executeSubcompanyFullSync(IWorkFullSyncReqVO reqVO, IWorkSyncProcessor.SyncOptions options, IWorkSyncEntityStatVO stat, - List batches) { + List batches, + IWorkSyncProcessor.DeptSyncContext context) { return executePaged(reqVO, IWorkSyncEntityTypeEnum.SUBCOMPANY, batches, (page, pageSize) -> { IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO(); query.setCurpage(page); @@ -92,7 +105,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query); ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage()); List dataList = CollUtil.emptyIfNull(pageResp.getDataList()); - IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options); + IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options, context); updateStat(stat, result, dataList.size()); return new BatchExecution(result, dataList.size()); }); @@ -101,7 +114,8 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { private int executeDepartmentFullSync(IWorkFullSyncReqVO reqVO, IWorkSyncProcessor.SyncOptions options, IWorkSyncEntityStatVO stat, - List batches) { + List batches, + IWorkSyncProcessor.DeptSyncContext context) { return executePaged(reqVO, IWorkSyncEntityTypeEnum.DEPARTMENT, batches, (page, pageSize) -> { IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO(); query.setCurpage(page); @@ -110,7 +124,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query); ensureIWorkSuccess("拉取部门", pageResp.isSuccess(), pageResp.getMessage()); List dataList = CollUtil.emptyIfNull(pageResp.getDataList()); - IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartments(dataList, options); + IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartments(dataList, options, context); updateStat(stat, result, dataList.size()); return new BatchExecution(result, dataList.size()); });