From 633772f8b1f4632a4643576da2682af8cef02567 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 18 Dec 2025 14:46:12 +0800 Subject: [PATCH 1/5] =?UTF-8?q?1.=20=E4=BF=AE=E6=AD=A3=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=20sql=20=E7=AE=80=E5=8C=96=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/dm/ruoyi-vue-pro-dm8.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql index e159dc37..42b3bd3d 100644 --- a/sql/dm/ruoyi-vue-pro-dm8.sql +++ b/sql/dm/ruoyi-vue-pro-dm8.sql @@ -3560,7 +3560,7 @@ COMMENT ON TABLE system_users IS '用户信息表'; -- ---------------------------- -- @formatter:off -- SET IDENTITY_INSERT system_users ON; -INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '后台管理', NULL, '管理员', '[1,2]', '11aoteman@126.com', '18818260277', 2, 1, 'http://test.zt.iocoder.cn/test/20250502/avatar_1746154660449.png', 0, '0:0:0:0:0:0:0:1', '2025-05-10 18:03:15', 'admin', '2021-01-05 17:03:47', NULL, '2025-05-10 18:03:15', '0', 1); +INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '后台管理', NULL, '管理员', '[1,2]', '11aoteman@126.com', '18818260277', 2, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-05-10 18:03:15', 'admin', '2021-01-05 17:03:47', NULL, '2025-05-10 18:03:15', '0', 1); INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (100, 'zt', '$2a$04$h.aaPKgO.odHepnk5PCsWeEwKdojFWdTItxGKfx1r0e1CSeBzsTJ6', 'ZT', NULL, '不要吓我', '[1]', 'zt@iocoder.cn', '15601691300', 1, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-04-08 09:36:40', '', '2021-01-07 09:07:17', NULL, '2025-04-21 14:23:08', '0', 1); INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, NULL, NULL, 'yuanma@iocoder.cn', '15601701300', 0, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2025-04-21 14:23:08', '0', 1); INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, NULL, '[1,2]', '111@qq.com', '15601691200', 1, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-03-28 20:01:16', '', '2021-01-21 02:13:53', NULL, '2025-04-21 14:23:08', '0', 1); From a86b98b0f57a0e43f9b0d64c275be77a8b871121 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 18 Dec 2025 20:26:27 +0800 Subject: [PATCH 2/5] =?UTF-8?q?1.=20=E6=96=B0=E5=A2=9E=20permissionApi=20?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=BD=93=E5=89=8D=E7=94=A8=E6=88=B7=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=BA=A7=E5=88=AB=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/api/permission/PermissionApi.java | 6 +++ .../enums/permission/DataScopeEnum.java | 22 ++++++++ .../api/permission/PermissionApiImpl.java | 6 +++ .../service/permission/PermissionService.java | 9 ++++ .../permission/PermissionServiceImpl.java | 44 +++++++++++++++ .../permission/PermissionServiceTest.java | 53 +++++++++++++++++++ 6 files changed, 140 insertions(+) diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java index 9e541926..b30f62f6 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java @@ -4,6 +4,7 @@ import com.zt.plat.framework.common.biz.system.permission.PermissionCommonApi; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.module.system.api.permission.dto.*; import com.zt.plat.module.system.enums.ApiConstants; +import com.zt.plat.module.system.enums.permission.DataScopeEnum; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Operation; @@ -50,4 +51,9 @@ public interface PermissionApi extends PermissionCommonApi { @Parameter(name = "userId", description = "用户编号", example = "1", required = true) CommonResult> getUserRoleIdListByUserId(@RequestParam("userId") Long userId); + @GetMapping(PREFIX + "/user-data-permission-level") + @Operation(summary = "获得用户的数据权限级别") + @Parameter(name = "userId", description = "用户编号", example = "1", required = true) + CommonResult getUserDataPermissionLevel(@RequestParam("userId") Long userId); + } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataScopeEnum.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataScopeEnum.java index b0edcfd0..4a1ab13f 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataScopeEnum.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataScopeEnum.java @@ -1,10 +1,12 @@ package com.zt.plat.module.system.enums.permission; +import com.fasterxml.jackson.annotation.JsonValue; import com.zt.plat.framework.common.core.ArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; +import java.util.Objects; /** * 数据范围枚举类 @@ -33,6 +35,26 @@ public enum DataScopeEnum implements ArrayValuable { public static final Integer[] ARRAYS = Arrays.stream(values()).map(DataScopeEnum::getScope).toArray(Integer[]::new); + /** + * Jackson 序列化时输出整数 code,兼容旧客户端 + */ + @JsonValue + public Integer getScope() { + return scope; + } + + public static DataScopeEnum findByScope(Integer scope) { + if (scope == null) { + return null; + } + for (DataScopeEnum value : values()) { + if (Objects.equals(value.scope, scope)) { + return value; + } + } + return null; + } + @Override public Integer[] array() { return ARRAYS; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java index 955fa69a..771f322b 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java @@ -6,6 +6,7 @@ import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.module.system.api.permission.dto.*; import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO; import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO; +import com.zt.plat.module.system.enums.permission.DataScopeEnum; import com.zt.plat.module.system.service.permission.PermissionService; import org.springframework.context.annotation.Primary; import org.springframework.validation.annotation.Validated; @@ -65,6 +66,11 @@ public class PermissionApiImpl implements PermissionApi { return success(permissionService.getUserRoleIdListByUserIdFromCache(userId)); } + @Override + public CommonResult getUserDataPermissionLevel(Long userId) { + return success(permissionService.getUserDataPermissionLevel(userId)); + } + @Override public CommonResult hasAnyPermissions(Long userId, String... permissions) { return success(permissionService.hasAnyPermissions(userId, permissions)); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java index 28a029fd..a1a88dd3 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java @@ -1,6 +1,7 @@ package com.zt.plat.module.system.service.permission; import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; +import com.zt.plat.module.system.enums.permission.DataScopeEnum; import java.util.Collection; import java.util.Set; @@ -143,4 +144,12 @@ public interface PermissionService { */ DeptDataPermissionRespDTO getDeptDataPermission(Long userId); + /** + * 获得用户的数据权限级别 + * + * @param userId 用户编号 + * @return 数据权限范围枚举 + */ + DataScopeEnum getUserDataPermissionLevel(Long userId); + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java index ac7a1553..6bb37d18 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java @@ -27,6 +27,7 @@ import com.zt.plat.module.system.enums.permission.RoleTypeEnum; import com.zt.plat.module.system.service.dept.DeptService; import com.zt.plat.module.system.service.user.AdminUserService; import com.zt.plat.module.system.service.userdept.UserDeptService; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -57,6 +58,15 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.ROLE_CAN_NOT_UP @Slf4j public class PermissionServiceImpl implements PermissionService { + private static final List DATA_SCOPE_PRIORITY = Arrays.asList( + DataScopeEnum.ALL, + DataScopeEnum.COMPANY_AND_DEPT, + DataScopeEnum.DEPT_AND_CHILD, + DataScopeEnum.DEPT_ONLY, + DataScopeEnum.DEPT_CUSTOM, + DataScopeEnum.SELF + ); + @Resource private RoleMenuMapper roleMenuMapper; @Resource @@ -404,6 +414,40 @@ public class PermissionServiceImpl implements PermissionService { return result; } + @Override + @DataPermission(enable = false) + @TenantIgnore + public DataScopeEnum getUserDataPermissionLevel(Long userId) { + List roles = getEnableUserRoleListByUserIdFromCache(userId); + if (CollUtil.isEmpty(roles)) { + return DataScopeEnum.SELF; + } + + DataScopeEnum best = null; + for (RoleDO role : roles) { + DataScopeEnum scopeEnum = DataScopeEnum.findByScope(role.getDataScope()); + if (scopeEnum == null) { + continue; + } + if (best == null || compareScope(scopeEnum, best) < 0) { + best = scopeEnum; + if (DataScopeEnum.ALL.equals(best)) { + break; + } + } + } + return best != null ? best : DataScopeEnum.SELF; + } + + private int compareScope(DataScopeEnum left, DataScopeEnum right) { + return getScopePriority(left) - getScopePriority(right); + } + + private int getScopePriority(DataScopeEnum scope) { + int idx = DATA_SCOPE_PRIORITY.indexOf(scope); + return idx >= 0 ? idx : Integer.MAX_VALUE; + } + /** * 获得自身的代理对象,解决 AOP 生效问题 * diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/permission/PermissionServiceTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/permission/PermissionServiceTest.java index 73095709..9bfefac6 100644 --- a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/permission/PermissionServiceTest.java +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/permission/PermissionServiceTest.java @@ -1,7 +1,9 @@ package com.zt.plat.module.system.service.permission; import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.enums.CommonStatusEnum; import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; +import com.zt.plat.framework.common.util.json.JsonUtils; import com.zt.plat.module.system.controller.admin.permission.vo.role.RoleSaveReqVO; import com.zt.plat.module.system.dal.dataobject.permission.RoleDO; import com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDO; @@ -11,6 +13,7 @@ import com.zt.plat.module.system.dal.mysql.permission.RoleMapper; import com.zt.plat.module.system.dal.mysql.permission.RoleMenuMapper; import com.zt.plat.module.system.dal.mysql.permission.UserRoleMapper; import com.zt.plat.module.system.dal.mysql.rolemenuexclusion.RoleMenuExclusionMapper; +import com.zt.plat.module.system.enums.permission.DataScopeEnum; import com.zt.plat.module.system.enums.permission.RoleTypeEnum; import com.zt.plat.module.system.service.dept.DeptService; import com.zt.plat.module.system.service.user.AdminUserService; @@ -408,4 +411,54 @@ public class PermissionServiceTest extends BaseDbUnitTest { assertEquals(1, exclusionDOS.size()); assertEquals(101L, exclusionDOS.get(0).getMenuId()); } + + @Test + public void testGetUserDataPermissionLevel_noRolesReturnSelf() { + Long userId = 1000L; + + DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId); + + assertEquals(DataScopeEnum.SELF, result); + } + + @Test + public void testGetUserDataPermissionLevel_pickHighestPriority() { + Long userId = 2000L; + RoleDO roleCustom = randomPojo(RoleDO.class, o -> o + .setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope()) + .setId(110L) + .setTenantId(0L)); + roleMapper.insert(roleCustom); + RoleDO roleCompany = randomPojo(RoleDO.class, o -> o + .setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setDataScope(DataScopeEnum.COMPANY_AND_DEPT.getScope()) + .setId(120L) + .setTenantId(0L)); + roleMapper.insert(roleCompany); + + userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleCustom.getId()))); + userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleCompany.getId()))); + + DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId); + + assertEquals(DataScopeEnum.COMPANY_AND_DEPT, result); + } + + @Test + public void testGetUserDataPermissionLevel_serializeAsNumber() { + Long userId = 3000L; + RoleDO roleAll = randomPojo(RoleDO.class, o -> o + .setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setDataScope(DataScopeEnum.ALL.getScope()) + .setId(210L) + .setTenantId(0L)); + roleMapper.insert(roleAll); + userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleAll.getId()))); + + DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId); + + assertEquals(DataScopeEnum.ALL, result); + assertEquals("1", JsonUtils.toJsonString(result)); + } } From 494de02d65d948ed1ccba972276ed034855a5ea0 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 18 Dec 2025 21:39:49 +0800 Subject: [PATCH 3/5] =?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()); }); From bdd22ed132e0481202dfe7bc415908ac5cd3f0a0 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 18 Dec 2025 22:26:05 +0800 Subject: [PATCH 4/5] =?UTF-8?q?1.=20=E5=8E=BB=E9=99=A4=E9=83=A8=E9=97=A8?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E4=B8=BA=E7=A9=BA=E6=97=B6=EF=BC=8C=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E4=BD=BF=E7=94=A8=20id=20=E4=BD=9C=E4=B8=BA=20code=20?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E6=98=A0=E5=B0=84=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iwork/impl/IWorkSyncProcessorImpl.java | 4 +- .../impl/IWorkSyncProcessorImplTest.java | 141 ++++++++++++++++++ .../iwork/impl/IWorkSyncServiceImplTest.java | 62 ++++++++ 3 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImplTest.java create mode 100644 zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImplTest.java 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 bbabb064..abf94ac4 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 @@ -513,7 +513,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { req.setIsGroup(Boolean.FALSE); req.setDeptSource(DeptSourceEnum.IWORK.getSource()); req.setExternalSystemCode(ExternalPlatformEnum.IWORK.getCode()); - req.setExternalDeptCode(StrUtil.blankToDefault(trimToNull(data.getSubcompanycode()), String.valueOf(data.getId()))); + req.setExternalDeptCode(trimToNull(data.getSubcompanycode())); req.setExternalDeptName(data.getSubcompanyname()); return req; } @@ -533,7 +533,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { req.setIsGroup(Boolean.FALSE); req.setDeptSource(DeptSourceEnum.IWORK.getSource()); req.setExternalSystemCode(ExternalPlatformEnum.IWORK.getCode()); - req.setExternalDeptCode(StrUtil.blankToDefault(trimToNull(data.getDepartmentcode()), String.valueOf(data.getId()))); + req.setExternalDeptCode(trimToNull(data.getDepartmentcode())); req.setExternalDeptName(data.getDepartmentname()); return req; } diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImplTest.java new file mode 100644 index 00000000..a16f5f84 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImplTest.java @@ -0,0 +1,141 @@ +package com.zt.plat.module.system.service.integration.iwork.impl; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO; +import com.zt.plat.module.system.dal.mysql.dept.PostMapper; +import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper; +import com.zt.plat.module.system.service.dept.DeptService; +import com.zt.plat.module.system.service.dept.PostService; +import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor; +import com.zt.plat.module.system.service.user.AdminUserService; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests for cross-page pending handling and placeholder backfill in IWorkSyncProcessorImpl. + */ +class IWorkSyncProcessorImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private IWorkSyncProcessorImpl processor; + + @Mock + private DeptService deptService; + @Mock + private PostService postService; + @Mock + private PostMapper postMapper; + @Mock + private AdminUserService adminUserService; + @Mock + private AdminUserMapper adminUserMapper; + + @Test + void shouldProcessPendingChildWhenParentArrivesInLaterPage() { + IWorkSyncProcessor.DeptSyncContext context = new IWorkSyncProcessor.DeptSyncContext(); + IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true); + + IWorkHrDepartmentPageRespVO.Department child = new IWorkHrDepartmentPageRespVO.Department(); + child.setId(200); + child.setDepartmentname("child"); + child.setSupdepid(100); // parent comes later + + IWorkHrDepartmentPageRespVO.Department parent = new IWorkHrDepartmentPageRespVO.Department(); + parent.setId(100); + parent.setDepartmentname("parent"); + parent.setSupdepid(0); // root + + when(deptService.getDept(anyLong())).thenReturn(null); + when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(100L, 200L); + + processor.syncDepartments(List.of(child), options, context); + + verify(deptService, never()).createDept(any()); + assertEquals(1, context.getPendingDepartments().size()); + + processor.syncDepartments(List.of(parent), options, context); + + verify(deptService, times(2)).createDept(any()); + assertTrue(context.getPendingDepartments().isEmpty(), "pending should be cleared after parent processed"); + } + + @Test + void shouldInsertPlaceholderWhenParentMissingAfterFlush() { + IWorkSyncProcessor.DeptSyncContext context = new IWorkSyncProcessor.DeptSyncContext(); + IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true); + + IWorkHrDepartmentPageRespVO.Department child = new IWorkHrDepartmentPageRespVO.Department(); + child.setId(300); + child.setDepartmentname("orphan"); + child.setSupdepid(9999); // never provided + + when(deptService.getDept(anyLong())).thenReturn(null); + when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(300L); + + processor.syncDepartments(List.of(child), options, context); + assertEquals(1, context.getPendingDepartments().size()); + + IWorkSyncProcessor.BatchResult flushResult = processor.flushDeptPending(context, options); + assertNotNull(flushResult); + + ArgumentCaptor captor = ArgumentCaptor.forClass(DeptSaveReqVO.class); + verify(deptService, times(1)).createDept(captor.capture()); + DeptSaveReqVO placeholderReq = captor.getValue(); + assertTrue(Boolean.TRUE.equals(placeholderReq.getDelayCodeGeneration())); + assertNull(placeholderReq.getCode()); + + assertTrue(context.getPendingDepartments().isEmpty(), "pending should be cleared after placeholder insert"); + assertTrue(context.getPlaceholderDeptIds().contains(300L)); + } + + @Test + void shouldKeepExternalCodeNullWhenDepartmentCodeBlank() { + IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true); + + IWorkHrDepartmentPageRespVO.Department dept = new IWorkHrDepartmentPageRespVO.Department(); + dept.setId(500); + dept.setDepartmentname("blank-code-dept"); + dept.setDepartmentcode(" "); + dept.setSupdepid(0); + + when(deptService.getDept(anyLong())).thenReturn(null); + when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(500L); + + processor.syncDepartments(List.of(dept), options, null); + + ArgumentCaptor captor = ArgumentCaptor.forClass(DeptSaveReqVO.class); + verify(deptService, times(1)).createDept(captor.capture()); + DeptSaveReqVO req = captor.getValue(); + assertNull(req.getExternalDeptCode(), "externalDeptCode should remain null when source code is blank"); + } + + @Test + void shouldKeepExternalCodeNullWhenSubcompanyCodeBlank() { + IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true); + + IWorkHrSubcompanyPageRespVO.Subcompany subcompany = new IWorkHrSubcompanyPageRespVO.Subcompany(); + subcompany.setId(600); + subcompany.setSubcompanyname("blank-code-sub"); + subcompany.setSubcompanycode(null); + subcompany.setSupsubcomid(0); + + when(deptService.getDept(anyLong())).thenReturn(null); + when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(600L); + + processor.syncSubcompanies(List.of(subcompany), options, null); + + ArgumentCaptor captor = ArgumentCaptor.forClass(DeptSaveReqVO.class); + verify(deptService, times(1)).createDept(captor.capture()); + DeptSaveReqVO req = captor.getValue(); + assertNull(req.getExternalDeptCode(), "externalDeptCode should remain null when source code is null or blank"); + } +} diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImplTest.java new file mode 100644 index 00000000..b8b1eb32 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImplTest.java @@ -0,0 +1,62 @@ +package com.zt.plat.module.system.service.integration.iwork.impl; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncReqVO; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO; +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 org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.stubbing.Answer; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +class IWorkSyncServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private IWorkSyncServiceImpl syncService; + + @Mock + private IWorkOrgRestService orgRestService; + @Mock + private IWorkSyncProcessor syncProcessor; + @Mock + private DeptService deptService; + + @Test + void shouldBackfillCodesWhenPlaceholdersExistAfterFullSync() { + IWorkFullSyncReqVO reqVO = new IWorkFullSyncReqVO(); + reqVO.setPageSize(1); + reqVO.setMaxPages(1); + + IWorkHrDepartmentPageRespVO pageResp = new IWorkHrDepartmentPageRespVO(); + pageResp.setSuccess(true); + IWorkHrDepartmentPageRespVO.Department dept = new IWorkHrDepartmentPageRespVO.Department(); + dept.setId(1); + pageResp.setDataList(List.of(dept)); + when(orgRestService.listDepartments(any())).thenReturn(pageResp); + + // 在部门同步时标记占位 ID + doAnswer((Answer) invocation -> { + IWorkSyncProcessor.DeptSyncContext context = invocation.getArgument(2); + if (context != null) { + context.getPlaceholderDeptIds().add(500L); + } + return IWorkSyncProcessor.BatchResult.empty(); + }).when(syncProcessor).syncDepartments(any(), any(), any(IWorkSyncProcessor.DeptSyncContext.class)); + + when(syncProcessor.flushDeptPending(any(), any())).thenReturn(IWorkSyncProcessor.BatchResult.empty()); + + syncService.fullSyncDepartments(reqVO); + + verify(deptService, times(1)).backfillMissingCodesWithoutEvent(argThat(set -> set.contains(500L))); + } +} From d688932f6dbbcd4a0720cc8f2dc2a563eb137003 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Mon, 22 Dec 2025 13:43:56 +0800 Subject: [PATCH 5/5] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=E6=8B=86=E5=87=BA?= =?UTF-8?q?=E7=9A=84=E4=B8=9A=E5=8A=A1=E6=A8=A1=E5=9D=97=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8A=A0=E8=BD=BD=E6=95=B0=E6=8D=AE=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusinessDataPermissionConfiguration.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java index 174049ad..5f9a19c8 100644 --- a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java @@ -7,6 +7,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; import java.util.LinkedHashSet; import java.util.Set; @@ -18,14 +20,12 @@ import java.util.Set; public class BusinessDataPermissionConfiguration { @Bean - public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext) { + public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext, Environment environment) { Set basePackages = new LinkedHashSet<>(); + addConfiguredBasePackages(environment, basePackages); if (AutoConfigurationPackages.has(beanFactory)) { basePackages.addAll(AutoConfigurationPackages.get(beanFactory)); } - if (basePackages.isEmpty()) { - basePackages.add("com.zt"); - } ClassLoader classLoader = applicationContext != null ? applicationContext.getClassLoader() : Thread.currentThread().getContextClassLoader(); @@ -35,6 +35,21 @@ public class BusinessDataPermissionConfiguration { return new BusinessDataPermissionEntityScanner(basePackages, classLoader); } + private void addConfiguredBasePackages(Environment environment, Set basePackages) { + if (environment == null) { + return; + } + String configured = environment.getProperty("zt.info.base-package"); + if (!StringUtils.hasText(configured)) { + return; + } + for (String pkg : configured.split("[,;\\s]+")) { + if (StringUtils.hasText(pkg)) { + basePackages.add(pkg.trim()); + } + } + } + @Bean public CompanyDataPermissionRuleCustomizer autoCompanyDataPermissionRuleCustomizer(BusinessDataPermissionEntityScanner scanner) { return rule -> scanner.getEntityMetadata().forEach(metadata -> {