diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/asyncTask/AsyncLatchUtils.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/asyncTask/AsyncLatchUtils.java index 25d4f235..3b2c532a 100644 --- a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/asyncTask/AsyncLatchUtils.java +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/asyncTask/AsyncLatchUtils.java @@ -1,5 +1,7 @@ package com.zt.plat.framework.common.util.asyncTask; +import com.alibaba.ttl.TransmittableThreadLocal; + import java.util.LinkedList; import java.util.List; import java.util.concurrent.*; @@ -9,7 +11,7 @@ import java.util.concurrent.*; * 多次提交,一次等待 */ public class AsyncLatchUtils { - private static final ThreadLocal> THREAD_LOCAL = ThreadLocal.withInitial(LinkedList::new); + private static final TransmittableThreadLocal> THREAD_LOCAL = TransmittableThreadLocal.withInitial(LinkedList::new); /** * 提交一个异步任务 diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java index ea4a8a93..4a4befdb 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java @@ -18,19 +18,24 @@ import com.zt.plat.framework.tenant.core.context.CompanyContextHolder; import com.zt.plat.framework.tenant.core.context.DeptContextHolder; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.NullValue; +import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.relational.*; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.SelectItem; +import org.apache.commons.lang3.StringUtils; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -67,7 +72,16 @@ public class DeptDataPermissionRule implements DataPermissionRule { private static final String DEPT_COLUMN_NAME = "dept_id"; private static final String USER_COLUMN_NAME = "user_id"; - static final Expression EXPRESSION_NULL = new NullValue(); + static final Expression EXPRESSION_NULL; + + static { + try { + EXPRESSION_NULL = CCJSqlParserUtil.parseCondExpression("1 = 0"); + } catch (JSQLParserException e) { + throw new RuntimeException(e); + } + } + public static final String SYSTEM_USERS = "system_users"; private final PermissionCommonApi permissionApi; @@ -177,7 +191,9 @@ public class DeptDataPermissionRule implements DataPermissionRule { // 情况三,拼接 Dept 和 Company User 的条件,最后组合 Expression deptExpression = buildDeptExpression(tableName, tableAlias, effectiveDeptIds); // Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds()); - Expression userExpression = buildUserExpression(tableName, tableAlias, effectiveSelf, loginUser.getId()); + // 使用工号替换 UserId + String userWorkCode = SecurityFrameworkUtils.getLoginUserWorkCode(); + Expression userExpression = buildUserExpression(tableName, tableAlias, effectiveSelf, loginUser.getId(), userWorkCode); if (deptExpression == null && userExpression == null) { // TODO ZT:获得不到条件的时候,暂时不抛出异常,而是不返回数据 log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", @@ -241,7 +257,7 @@ public class DeptDataPermissionRule implements DataPermissionRule { new ParenthesedExpressionList(new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)))); } - private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) { + private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId, String workCode) { // 如果不查看自己,则无需作为条件 if (Boolean.FALSE.equals(self)) { return null; @@ -250,8 +266,13 @@ public class DeptDataPermissionRule implements DataPermissionRule { if (StrUtil.isEmpty(columnName)) { return null; } + // 拼接条件 - return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId)); + if (StrUtil.isBlank(workCode)) { + return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId)); + } else { + return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new StringValue(workCode)); + } } // ==================== 添加配置 ==================== diff --git a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/handler/DefaultDBFieldHandler.java b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/handler/DefaultDBFieldHandler.java index ec449926..b3306ac0 100644 --- a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/handler/DefaultDBFieldHandler.java +++ b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/handler/DefaultDBFieldHandler.java @@ -11,6 +11,7 @@ import com.zt.plat.framework.security.core.LoginUser; import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; import com.zt.plat.framework.web.core.util.WebFrameworkUtils; import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.reflection.MetaObject; import org.springframework.context.annotation.Lazy; import org.springframework.util.ReflectionUtils; @@ -48,14 +49,16 @@ public class DefaultDBFieldHandler implements MetaObjectHandler { } Long userId = getUserId(); + String userWorkCode = SecurityFrameworkUtils.getLoginUserWorkCode(); + String savedUserWorkCodeOrUserId = StringUtils.isNotEmpty(userWorkCode) ? userWorkCode : userId == null ? null : userId.toString(); String userNickname = SecurityFrameworkUtils.getLoginUserNickname(); // 当前登录用户不为空,创建人为空,则当前登录用户为创建人 if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) { - baseDO.setCreator(userId.toString()); + baseDO.setCreator(savedUserWorkCodeOrUserId); } // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) { - baseDO.setUpdater(userId.toString()); + baseDO.setUpdater(savedUserWorkCodeOrUserId); } } if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BusinessBaseDO businessBaseDO) { diff --git a/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/LoginUser.java b/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/LoginUser.java index f9b739dd..cf026e49 100644 --- a/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/LoginUser.java +++ b/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/LoginUser.java @@ -31,6 +31,9 @@ public class LoginUser { // 用户关联的岗位信息 public static final String INFO_KEY_POST_IDS = "postIds"; + // 工号 + public static final String INFO_KEY_WORK_CODE = "workCode"; + /** * 用户编号 */ diff --git a/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/util/SecurityFrameworkUtils.java b/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/util/SecurityFrameworkUtils.java index 48a2bac1..e9efb450 100644 --- a/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/util/SecurityFrameworkUtils.java +++ b/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/util/SecurityFrameworkUtils.java @@ -15,6 +15,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS import org.springframework.util.StringUtils; import java.util.Collections; +import java.util.Map; /** * 安全服务工具类 @@ -93,6 +94,19 @@ public class SecurityFrameworkUtils { return loginUser != null ? loginUser.getVisitCompanyId() : null; } + @Nullable + public static String getLoginUserWorkCode() { + LoginUser loginUser = getLoginUser(); + if (loginUser == null) { + return null; + } + Map info = loginUser.getInfo(); + if (info == null) { + return null; + } + return MapUtil.getStr(info, LoginUser.INFO_KEY_WORK_CODE); + } + /** * 获得当前用户的编号,从上下文中 * 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-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/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/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); + } + } 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); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java index eee1404e..33552e9f 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -203,6 +203,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { .put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString()) .put(LoginUser.INFO_KEY_USERNAME, user.getUsername()) .put(LoginUser.INFO_KEY_PHONE, user.getMobile()) + .put(LoginUser.INFO_KEY_WORK_CODE, user.getWorkcode()) .put(LoginUser.INFO_KEY_POST_IDS, CollUtil.isEmpty(user.getPostIds()) ? "[]" : JsonUtils.toJsonString(user.getPostIds())) .build(); } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {