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) { 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);