From 1e2b89f5fa6cc1b9e9e1fdc48dfda7abb3d55066 Mon Sep 17 00:00:00 2001 From: yangchaojin <549193112@qq.com> Date: Tue, 20 Jan 2026 08:43:31 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=85=AC=E5=8F=B8=E8=8A=82=E7=82=B9=E4=BF=A1=E6=81=AF=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/zt/plat/module/system/api/dept/DeptApi.java | 8 ++++++++ .../com/zt/plat/module/system/api/dept/DeptApiImpl.java | 6 ++++++ .../system/controller/admin/dept/DeptController.java | 7 +++++++ .../zt/plat/module/system/dal/mysql/dept/DeptMapper.java | 7 +++++++ .../zt/plat/module/system/service/dept/DeptService.java | 7 +++++++ .../plat/module/system/service/dept/DeptServiceImpl.java | 5 +++++ 6 files changed, 40 insertions(+) diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptApi.java index abc53972..da378c52 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptApi.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptApi.java @@ -2,6 +2,7 @@ package com.zt.plat.module.system.api.dept; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.module.system.api.dept.dto.*; import com.zt.plat.module.system.enums.ApiConstants; import io.swagger.v3.oas.annotations.Operation; @@ -15,6 +16,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static com.zt.plat.framework.common.pojo.CommonResult.success; + @FeignClient(name = ApiConstants.NAME) // TODO ZT:fallbackFactory = @Tag(name = "RPC 服务 - 部门") public interface DeptApi { @@ -86,6 +89,11 @@ public interface DeptApi { @Parameter(name = "userId", description = "用户编号", example = "1", required = true) CommonResult> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId); + @GetMapping(PREFIX+"/up-find-company-node") + @Operation(summary = "获取公司节点信息", description = "通过部门编号,向上追溯部门信息,直到上级部门是公司,返回追溯到的部门信息列表") + @Parameter(name = "deptId", description = "部门编号", required = true, example = "1024") + CommonResult> upFindCompanyNode(@RequestParam("deptId") Long deptId); + // ========== 数据同步专用接口 ========== @PostMapping(PREFIX + "/sync") diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptApiImpl.java index bed2d2b6..7c1e4781 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptApiImpl.java @@ -107,6 +107,12 @@ public class DeptApiImpl implements DeptApi { return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class)); } + @Override + public CommonResult> upFindCompanyNode(Long deptId) { + List depts = deptService.upFindCompanyNode(deptId); + return success(BeanUtils.toBean(depts, DeptRespDTO.class)); + } + // ========== 数据同步专用接口 ========== @Override diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptController.java index 54ba9b37..c2da8184 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptController.java @@ -165,4 +165,11 @@ public class DeptController { return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class)); } + @GetMapping("/up-find-company-node") + @Operation(summary = "获取公司节点信息", description = "通过部门编号,向上追溯部门信息,直到上级部门是公司,返回追溯到的部门信息列表") + @Parameter(name = "deptId", description = "部门编号", required = true, example = "1024") + public CommonResult> upFindCompanyNode(@RequestParam("deptId") Long deptId) { + List list = deptService.upFindCompanyNode(deptId); + return success(BeanUtils.toBean(list, DeptRespVO.class)); + } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java index a8416485..00029e99 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java @@ -10,6 +10,8 @@ import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptListReqVO; import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; import java.util.Collection; import java.util.List; @@ -167,4 +169,9 @@ public interface DeptMapper extends BaseMapperX { ); } + @Select(""" + SELECT sd.* FROM SYSTEM_DEPT sd START WITH sd.id = #{deptId} + CONNECT BY PRIOR sd.parent_id = sd.id AND PRIOR sd.is_company <> 1 ; + """) + List upFindCompanyNode(@Param("deptId") Long deptId); } 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 3996eef8..7f8258e1 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 @@ -185,4 +185,11 @@ public interface DeptService { // ========== 数据同步专用接口 ========== void syncDept(DeptSaveReqVO syncReqVO); + + /** + * 向上查找公司节点信息 + * @param deptId + * @return + */ + List upFindCompanyNode(Long deptId); } 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 0a5c607e..52d15ac5 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 @@ -960,4 +960,9 @@ public class DeptServiceImpl implements DeptService { // 注意:不发布变更事件,避免循环同步 } + @Override + public List upFindCompanyNode(Long deptId) { + return deptMapper.upFindCompanyNode(deptId); + } + } From 86ab64a6572afa67cf372193fcebe91b5d74fd1e Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 20 Jan 2026 08:57:08 +0800 Subject: [PATCH 2/3] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=20iwork=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E7=94=A8=E6=88=B7=E6=95=88=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/dal/mysql/dept/PostMapper.java | 9 +++ .../iwork/impl/IWorkSyncProcessorImpl.java | 75 ++++++++++++++++++- .../iwork/impl/IWorkSyncServiceImpl.java | 6 ++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/PostMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/PostMapper.java index 4556ce9a..d1bd29d5 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/PostMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/PostMapper.java @@ -9,6 +9,7 @@ import org.apache.ibatis.annotations.Mapper; import java.util.Collection; import java.util.List; +import java.util.Collections; @Mapper public interface PostMapper extends BaseMapperX { @@ -35,4 +36,12 @@ public interface PostMapper extends BaseMapperX { return selectOne(PostDO::getCode, code); } + default List selectByCodes(Collection codes) { + if (codes == null || codes.isEmpty()) { + return Collections.emptyList(); + } + return selectList(new LambdaQueryWrapperX() + .in(PostDO::getCode, codes)); + } + } 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 3c92a184..4db5f260 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 @@ -322,7 +322,41 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { if (records.isEmpty()) { return result; } + long batchStart = System.currentTimeMillis(); result.increasePulled(records.size()); + // 预取已有用户,避免逐条查询 + long preloadUsersStart = System.currentTimeMillis(); + Map existingUsers = new HashMap<>(); + List userIds = records.stream() + .map(IWorkHrUserPageRespVO.User::getId) + .filter(Objects::nonNull) + .map(Integer::longValue) + .distinct() + .toList(); + if (!userIds.isEmpty()) { + List users = adminUserMapper.selectBatchIds(userIds); + if (CollUtil.isNotEmpty(users)) { + users.forEach(user -> existingUsers.put(user.getId(), user)); + } + } + long preloadUsersMs = System.currentTimeMillis() - preloadUsersStart; + + // 预取岗位,避免逐条按编码查询 + long preloadPostsStart = System.currentTimeMillis(); + List postCodes = records.stream() + .map(IWorkHrUserPageRespVO.User::getJobtitleid) + .filter(Objects::nonNull) + .map(this::buildJobCode) + .distinct() + .toList(); + if (!postCodes.isEmpty()) { + List posts = postMapper.selectByCodes(postCodes); + if (CollUtil.isNotEmpty(posts)) { + posts.forEach(post -> postCache.put(buildPostCacheKey(post.getCode()), post)); + } + } + long preloadPostsMs = System.currentTimeMillis() - preloadPostsStart; + for (IWorkHrUserPageRespVO.User user : records) { if (user == null) { continue; @@ -344,7 +378,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE; // 直接沿用 iWork 原始密码,避免重复格式化造成校验偏差 String externalPassword = trimToNull(user.getPassword()); - AdminUserDO existing = adminUserMapper.selectById(user.getId()); + AdminUserDO existing = user.getId() == null ? null : existingUsers.get(user.getId().longValue()); UserSyncOutcome outcome; if (existing == null) { if (!options.isCreateIfMissing()) { @@ -377,6 +411,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { result.withMessage("同步人员失败: " + ex.getMessage()); } } + long totalMs = System.currentTimeMillis() - batchStart; + log.info("[iWork] 人员批次同步完成 size={} preloadUsersMs={} preloadPostsMs={} totalMs={}", + records.size(), preloadUsersMs, preloadPostsMs, totalMs); return result; } //TODO @@ -495,11 +532,43 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status); req.setId(existing.getId()); boolean disabledChanged = CommonStatusEnum.isDisable(status.getStatus()) && CommonStatusEnum.isEnable(existing.getStatus()); - adminUserService.updateUser(req); - syncPassword(existing, externalPassword); + boolean infoChanged = isUserInfoChanged(existing, req); + boolean passwordChanged = isPasswordChanged(existing, externalPassword); + + if (!infoChanged && !passwordChanged) { + return new UserSyncOutcome(SyncAction.SKIPPED, false, existing.getId()); + } + + if (infoChanged) { + adminUserService.updateUser(req); + } + if (passwordChanged) { + syncPassword(existing, externalPassword); + } return new UserSyncOutcome(SyncAction.UPDATED, disabledChanged, existing.getId()); } + private boolean isUserInfoChanged(AdminUserDO existing, UserSaveReqVO req) { + if (existing == null || req == null) { + return false; + } + return !Objects.equals(existing.getUsername(), req.getUsername()) + || !Objects.equals(existing.getWorkcode(), req.getWorkcode()) + || !Objects.equals(existing.getNickname(), req.getNickname()) + || !Objects.equals(existing.getRemark(), req.getRemark()) + || !Objects.equals(existing.getEmail(), req.getEmail()) + || !Objects.equals(existing.getMobile(), req.getMobile()) + || !Objects.equals(existing.getSex(), req.getSex()) + || !Objects.equals(existing.getStatus(), req.getStatus()); + } + + private boolean isPasswordChanged(AdminUserDO existing, String externalPassword) { + if (existing == null || StrUtil.isBlank(externalPassword)) { + return false; + } + return !StrUtil.equals(externalPassword, existing.getPassword()); + } + private DeptSaveReqVO buildSubcompanySaveReq(IWorkHrSubcompanyPageRespVO.Subcompany data, Long deptId, Long parentId, 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 95ac0cdc..bc0e763f 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 @@ -217,11 +217,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages(); int processedPages = 0; for (int page = startPage; processedPages < pagesLimit; page++) { + long pageStart = System.currentTimeMillis(); BatchExecution execution = executor.execute(page, pageSize); + long pageMs = System.currentTimeMillis() - pageStart; if (execution == null || execution.totalPulled == 0) { break; } processedPages++; + log.info("[iWork] 全量同步 {} 页={} pulled={} costMs={}", type, page, execution.totalPulled, pageMs); IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO(); batchStat.setEntityType(type); batchStat.setPageNumber(page); @@ -403,11 +406,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages(); int processedPages = 0; for (int page = startPage; processedPages < pagesLimit; page++) { + long pageStart = System.currentTimeMillis(); BatchExecution execution = executor.execute(page, pageSize); + long pageMs = System.currentTimeMillis() - pageStart; if (execution == null || execution.totalPulled == 0) { break; } processedPages++; + log.info("[iWork] 手动同步 {} 页={} pulled={} costMs={}", type, page, execution.totalPulled, pageMs); IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO(); batchStat.setEntityType(type); batchStat.setPageNumber(page); From 4595cef06e41202c9db4b614fac1beea458837d9 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 20 Jan 2026 08:57:34 +0800 Subject: [PATCH 3/3] =?UTF-8?q?1.=E8=8E=B7=E5=8F=96=20token=20=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=90=8E=E8=BF=9B=E8=A1=8C=E5=8D=81=E6=AC=A1=E9=87=8D?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/GatewaySecurityFilter.java | 13 ++++--- .../gateway/ApiAnonymousUserService.java | 38 ++++++++++++++----- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java index 7bd4e98f..00025b83 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java @@ -41,6 +41,7 @@ import java.time.Duration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import static com.zt.plat.framework.common.util.security.CryptoSignatureUtils.SIGNATURE_FIELD; import static com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties.*; @@ -471,11 +472,13 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { } securedRequest.removeHeader(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN); securedRequest.removeHeader(HttpHeaders.AUTHORIZATION); - anonymousUserService.issueAccessToken(anonymousDetails) - .ifPresent(token -> { - securedRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); - securedRequest.setHeader(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, token); - }); + Optional tokenOptional = anonymousUserService.issueAccessToken(anonymousDetails); + if (tokenOptional.isEmpty()) { + throw new SecurityValidationException(HttpStatus.UNAUTHORIZED, "匿名访问获取token失败"); + } + String token = tokenOptional.get(); + securedRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); + securedRequest.setHeader(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, token); } private static final class SecurityValidationException extends RuntimeException { diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiAnonymousUserService.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiAnonymousUserService.java index 788b0498..ae04eabe 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiAnonymousUserService.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiAnonymousUserService.java @@ -40,6 +40,9 @@ public class ApiAnonymousUserService { private final AdminUserApi adminUserApi; private final OAuth2TokenCommonApi oauth2TokenApi; + private static final int RETRY_ATTEMPTS = 10; + private static final Duration RETRY_DELAY = Duration.ofSeconds(5); + private LoadingCache> cache; @PostConstruct @@ -105,18 +108,33 @@ public class ApiAnonymousUserService { if (details == null) { return Optional.empty(); } - try { - OAuth2AccessTokenCreateReqDTO req = buildAccessTokenRequest(details); - OAuth2AccessTokenRespDTO resp = oauth2TokenApi.createAccessToken(req).getCheckedData(); - if (resp == null || !StringUtils.hasText(resp.getAccessToken())) { - log.warn("[ANONYMOUS] 获取用户 {} 的访问令牌失败: 响应为空", details.getUserId()); - return Optional.empty(); + OAuth2AccessTokenCreateReqDTO req = buildAccessTokenRequest(details); + Exception lastException = null; + for (int attempt = 1; attempt <= RETRY_ATTEMPTS; attempt++) { + try { + OAuth2AccessTokenRespDTO resp = oauth2TokenApi.createAccessToken(req).getCheckedData(); + if (resp == null || !StringUtils.hasText(resp.getAccessToken())) { + log.warn("[ANONYMOUS] 获取用户 {} 的访问令牌失败: 响应为空", details.getUserId()); + return Optional.empty(); + } + return Optional.of(resp.getAccessToken()); + } catch (Exception ex) { + lastException = ex; + if (attempt < RETRY_ATTEMPTS) { + log.warn("[ANONYMOUS] 获取用户 {} 的访问令牌失败,开始第 {} 次重试,原因:{}", + details.getUserId(), attempt, ex.getMessage()); + try { + Thread.sleep(RETRY_DELAY.toMillis()); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + log.error("[ANONYMOUS] 获取用户 {} 的访问令牌重试被中断", details.getUserId()); + return Optional.empty(); + } + } } - return Optional.of(resp.getAccessToken()); - } catch (Exception ex) { - log.error("[ANONYMOUS] 获取用户 {} 的访问令牌时发生异常", details.getUserId(), ex); - return Optional.empty(); } + log.error("[ANONYMOUS] 获取用户 {} 的访问令牌时发生异常", details.getUserId(), lastException); + return Optional.empty(); } private OAuth2AccessTokenCreateReqDTO buildAccessTokenRequest(AnonymousUserDetails details) {