Merge branch 'dev' into test

This commit is contained in:
chenbowen
2026-01-20 08:59:11 +08:00
17 changed files with 214 additions and 25 deletions

View File

@@ -1,5 +1,7 @@
package com.zt.plat.framework.common.util.asyncTask; package com.zt.plat.framework.common.util.asyncTask;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
@@ -9,7 +11,7 @@ import java.util.concurrent.*;
* 多次提交,一次等待 * 多次提交,一次等待
*/ */
public class AsyncLatchUtils { public class AsyncLatchUtils {
private static final ThreadLocal<List<TaskInfo>> THREAD_LOCAL = ThreadLocal.withInitial(LinkedList::new); private static final TransmittableThreadLocal<List<TaskInfo>> THREAD_LOCAL = TransmittableThreadLocal.withInitial(LinkedList::new);
/** /**
* 提交一个异步任务 * 提交一个异步任务

View File

@@ -18,19 +18,24 @@ import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder; import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue; 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.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.*; import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectItem; import net.sf.jsqlparser.statement.select.SelectItem;
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; 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 DEPT_COLUMN_NAME = "dept_id";
private static final String USER_COLUMN_NAME = "user_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"; public static final String SYSTEM_USERS = "system_users";
private final PermissionCommonApi permissionApi; private final PermissionCommonApi permissionApi;
@@ -177,7 +191,9 @@ public class DeptDataPermissionRule implements DataPermissionRule {
// 情况三,拼接 Dept 和 Company User 的条件,最后组合 // 情况三,拼接 Dept 和 Company User 的条件,最后组合
Expression deptExpression = buildDeptExpression(tableName, tableAlias, effectiveDeptIds); Expression deptExpression = buildDeptExpression(tableName, tableAlias, effectiveDeptIds);
// Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds()); // 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) { if (deptExpression == null && userExpression == null) {
// TODO ZT获得不到条件的时候暂时不抛出异常而是不返回数据 // TODO ZT获得不到条件的时候暂时不抛出异常而是不返回数据
log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
@@ -241,7 +257,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
new ParenthesedExpressionList(new ExpressionList<LongValue>(CollectionUtils.convertList(deptIds, LongValue::new)))); new ParenthesedExpressionList(new ExpressionList<LongValue>(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)) { if (Boolean.FALSE.equals(self)) {
return null; return null;
@@ -250,8 +266,13 @@ public class DeptDataPermissionRule implements DataPermissionRule {
if (StrUtil.isEmpty(columnName)) { if (StrUtil.isEmpty(columnName)) {
return null; 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));
}
} }
// ==================== 添加配置 ==================== // ==================== 添加配置 ====================

View File

@@ -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.security.core.util.SecurityFrameworkUtils;
import com.zt.plat.framework.web.core.util.WebFrameworkUtils; import com.zt.plat.framework.web.core.util.WebFrameworkUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -48,14 +49,16 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
} }
Long userId = getUserId(); Long userId = getUserId();
String userWorkCode = SecurityFrameworkUtils.getLoginUserWorkCode();
String savedUserWorkCodeOrUserId = StringUtils.isNotEmpty(userWorkCode) ? userWorkCode : userId == null ? null : userId.toString();
String userNickname = SecurityFrameworkUtils.getLoginUserNickname(); String userNickname = SecurityFrameworkUtils.getLoginUserNickname();
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人 // 当前登录用户不为空,创建人为空,则当前登录用户为创建人
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) { if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
baseDO.setCreator(userId.toString()); baseDO.setCreator(savedUserWorkCodeOrUserId);
} }
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人 // 当前登录用户不为空,更新人为空,则当前登录用户为更新人
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) { if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) {
baseDO.setUpdater(userId.toString()); baseDO.setUpdater(savedUserWorkCodeOrUserId);
} }
} }
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BusinessBaseDO businessBaseDO) { if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BusinessBaseDO businessBaseDO) {

View File

@@ -31,6 +31,9 @@ public class LoginUser {
// 用户关联的岗位信息 // 用户关联的岗位信息
public static final String INFO_KEY_POST_IDS = "postIds"; public static final String INFO_KEY_POST_IDS = "postIds";
// 工号
public static final String INFO_KEY_WORK_CODE = "workCode";
/** /**
* 用户编号 * 用户编号
*/ */

View File

@@ -15,6 +15,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
/** /**
* 安全服务工具类 * 安全服务工具类
@@ -93,6 +94,19 @@ public class SecurityFrameworkUtils {
return loginUser != null ? loginUser.getVisitCompanyId() : null; return loginUser != null ? loginUser.getVisitCompanyId() : null;
} }
@Nullable
public static String getLoginUserWorkCode() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return null;
}
Map<String, String> info = loginUser.getInfo();
if (info == null) {
return null;
}
return MapUtil.getStr(info, LoginUser.INFO_KEY_WORK_CODE);
}
/** /**
* 获得当前用户的编号,从上下文中 * 获得当前用户的编号,从上下文中
* *

View File

@@ -41,6 +41,7 @@ import java.time.Duration;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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.framework.common.util.security.CryptoSignatureUtils.SIGNATURE_FIELD;
import static com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties.*; 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(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN);
securedRequest.removeHeader(HttpHeaders.AUTHORIZATION); securedRequest.removeHeader(HttpHeaders.AUTHORIZATION);
anonymousUserService.issueAccessToken(anonymousDetails) Optional<String> tokenOptional = anonymousUserService.issueAccessToken(anonymousDetails);
.ifPresent(token -> { if (tokenOptional.isEmpty()) {
securedRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); throw new SecurityValidationException(HttpStatus.UNAUTHORIZED, "匿名访问获取token失败");
securedRequest.setHeader(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, 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 { private static final class SecurityValidationException extends RuntimeException {

View File

@@ -40,6 +40,9 @@ public class ApiAnonymousUserService {
private final AdminUserApi adminUserApi; private final AdminUserApi adminUserApi;
private final OAuth2TokenCommonApi oauth2TokenApi; private final OAuth2TokenCommonApi oauth2TokenApi;
private static final int RETRY_ATTEMPTS = 10;
private static final Duration RETRY_DELAY = Duration.ofSeconds(5);
private LoadingCache<Long, Optional<AnonymousUserDetails>> cache; private LoadingCache<Long, Optional<AnonymousUserDetails>> cache;
@PostConstruct @PostConstruct
@@ -105,18 +108,33 @@ public class ApiAnonymousUserService {
if (details == null) { if (details == null) {
return Optional.empty(); return Optional.empty();
} }
try { OAuth2AccessTokenCreateReqDTO req = buildAccessTokenRequest(details);
OAuth2AccessTokenCreateReqDTO req = buildAccessTokenRequest(details); Exception lastException = null;
OAuth2AccessTokenRespDTO resp = oauth2TokenApi.createAccessToken(req).getCheckedData(); for (int attempt = 1; attempt <= RETRY_ATTEMPTS; attempt++) {
if (resp == null || !StringUtils.hasText(resp.getAccessToken())) { try {
log.warn("[ANONYMOUS] 获取用户 {} 的访问令牌失败: 响应为空", details.getUserId()); OAuth2AccessTokenRespDTO resp = oauth2TokenApi.createAccessToken(req).getCheckedData();
return Optional.empty(); 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) { private OAuth2AccessTokenCreateReqDTO buildAccessTokenRequest(AnonymousUserDetails details) {

View File

@@ -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.pojo.CommonResult;
import com.zt.plat.framework.common.util.collection.CollectionUtils; 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.api.dept.dto.*;
import com.zt.plat.module.system.enums.ApiConstants; import com.zt.plat.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -15,6 +16,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@FeignClient(name = ApiConstants.NAME) // TODO ZTfallbackFactory = @FeignClient(name = ApiConstants.NAME) // TODO ZTfallbackFactory =
@Tag(name = "RPC 服务 - 部门") @Tag(name = "RPC 服务 - 部门")
public interface DeptApi { public interface DeptApi {
@@ -86,6 +89,11 @@ public interface DeptApi {
@Parameter(name = "userId", description = "用户编号", example = "1", required = true) @Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<Set<CompanyDeptInfoRespDTO>> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId); CommonResult<Set<CompanyDeptInfoRespDTO>> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId);
@GetMapping(PREFIX+"/up-find-company-node")
@Operation(summary = "获取公司节点信息", description = "通过部门编号,向上追溯部门信息,直到上级部门是公司,返回追溯到的部门信息列表")
@Parameter(name = "deptId", description = "部门编号", required = true, example = "1024")
CommonResult<List<DeptRespDTO>> upFindCompanyNode(@RequestParam("deptId") Long deptId);
// ========== 数据同步专用接口 ========== // ========== 数据同步专用接口 ==========
@PostMapping(PREFIX + "/sync") @PostMapping(PREFIX + "/sync")

View File

@@ -107,6 +107,12 @@ public class DeptApiImpl implements DeptApi {
return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class)); return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class));
} }
@Override
public CommonResult<List<DeptRespDTO>> upFindCompanyNode(Long deptId) {
List<DeptDO> depts = deptService.upFindCompanyNode(deptId);
return success(BeanUtils.toBean(depts, DeptRespDTO.class));
}
// ========== 数据同步专用接口 ========== // ========== 数据同步专用接口 ==========
@Override @Override

View File

@@ -165,4 +165,11 @@ public class DeptController {
return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class)); 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<List<DeptRespVO>> upFindCompanyNode(@RequestParam("deptId") Long deptId) {
List<DeptDO> list = deptService.upFindCompanyNode(deptId);
return success(BeanUtils.toBean(list, DeptRespVO.class));
}
} }

View File

@@ -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.controller.admin.dept.vo.dept.DeptListReqVO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import org.apache.ibatis.annotations.Mapper; 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.Collection;
import java.util.List; import java.util.List;
@@ -167,4 +169,9 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
); );
} }
@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<DeptDO> upFindCompanyNode(@Param("deptId") Long deptId);
} }

View File

@@ -9,6 +9,7 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Collections;
@Mapper @Mapper
public interface PostMapper extends BaseMapperX<PostDO> { public interface PostMapper extends BaseMapperX<PostDO> {
@@ -35,4 +36,12 @@ public interface PostMapper extends BaseMapperX<PostDO> {
return selectOne(PostDO::getCode, code); return selectOne(PostDO::getCode, code);
} }
default List<PostDO> selectByCodes(Collection<String> codes) {
if (codes == null || codes.isEmpty()) {
return Collections.emptyList();
}
return selectList(new LambdaQueryWrapperX<PostDO>()
.in(PostDO::getCode, codes));
}
} }

View File

@@ -185,4 +185,11 @@ public interface DeptService {
// ========== 数据同步专用接口 ========== // ========== 数据同步专用接口 ==========
void syncDept(DeptSaveReqVO syncReqVO); void syncDept(DeptSaveReqVO syncReqVO);
/**
* 向上查找公司节点信息
* @param deptId
* @return
*/
List<DeptDO> upFindCompanyNode(Long deptId);
} }

View File

@@ -960,4 +960,9 @@ public class DeptServiceImpl implements DeptService {
// 注意:不发布变更事件,避免循环同步 // 注意:不发布变更事件,避免循环同步
} }
@Override
public List<DeptDO> upFindCompanyNode(Long deptId) {
return deptMapper.upFindCompanyNode(deptId);
}
} }

View File

@@ -322,7 +322,41 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
if (records.isEmpty()) { if (records.isEmpty()) {
return result; return result;
} }
long batchStart = System.currentTimeMillis();
result.increasePulled(records.size()); result.increasePulled(records.size());
// 预取已有用户,避免逐条查询
long preloadUsersStart = System.currentTimeMillis();
Map<Long, AdminUserDO> existingUsers = new HashMap<>();
List<Long> userIds = records.stream()
.map(IWorkHrUserPageRespVO.User::getId)
.filter(Objects::nonNull)
.map(Integer::longValue)
.distinct()
.toList();
if (!userIds.isEmpty()) {
List<AdminUserDO> 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<String> postCodes = records.stream()
.map(IWorkHrUserPageRespVO.User::getJobtitleid)
.filter(Objects::nonNull)
.map(this::buildJobCode)
.distinct()
.toList();
if (!postCodes.isEmpty()) {
List<PostDO> 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) { for (IWorkHrUserPageRespVO.User user : records) {
if (user == null) { if (user == null) {
continue; continue;
@@ -344,7 +378,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE; CommonStatusEnum status = inactive ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE;
// 直接沿用 iWork 原始密码,避免重复格式化造成校验偏差 // 直接沿用 iWork 原始密码,避免重复格式化造成校验偏差
String externalPassword = trimToNull(user.getPassword()); String externalPassword = trimToNull(user.getPassword());
AdminUserDO existing = adminUserMapper.selectById(user.getId()); AdminUserDO existing = user.getId() == null ? null : existingUsers.get(user.getId().longValue());
UserSyncOutcome outcome; UserSyncOutcome outcome;
if (existing == null) { if (existing == null) {
if (!options.isCreateIfMissing()) { if (!options.isCreateIfMissing()) {
@@ -377,6 +411,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
result.withMessage("同步人员失败: " + ex.getMessage()); result.withMessage("同步人员失败: " + ex.getMessage());
} }
} }
long totalMs = System.currentTimeMillis() - batchStart;
log.info("[iWork] 人员批次同步完成 size={} preloadUsersMs={} preloadPostsMs={} totalMs={}",
records.size(), preloadUsersMs, preloadPostsMs, totalMs);
return result; return result;
} }
//TODO //TODO
@@ -495,11 +532,43 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor {
UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status); UserSaveReqVO req = buildUserSaveReq(source, username, deptId, postId, status);
req.setId(existing.getId()); req.setId(existing.getId());
boolean disabledChanged = CommonStatusEnum.isDisable(status.getStatus()) && CommonStatusEnum.isEnable(existing.getStatus()); boolean disabledChanged = CommonStatusEnum.isDisable(status.getStatus()) && CommonStatusEnum.isEnable(existing.getStatus());
adminUserService.updateUser(req); boolean infoChanged = isUserInfoChanged(existing, req);
syncPassword(existing, externalPassword); 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()); 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, private DeptSaveReqVO buildSubcompanySaveReq(IWorkHrSubcompanyPageRespVO.Subcompany data,
Long deptId, Long deptId,
Long parentId, Long parentId,

View File

@@ -217,11 +217,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages(); int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages();
int processedPages = 0; int processedPages = 0;
for (int page = startPage; processedPages < pagesLimit; page++) { for (int page = startPage; processedPages < pagesLimit; page++) {
long pageStart = System.currentTimeMillis();
BatchExecution execution = executor.execute(page, pageSize); BatchExecution execution = executor.execute(page, pageSize);
long pageMs = System.currentTimeMillis() - pageStart;
if (execution == null || execution.totalPulled == 0) { if (execution == null || execution.totalPulled == 0) {
break; break;
} }
processedPages++; processedPages++;
log.info("[iWork] 全量同步 {} 页={} pulled={} costMs={}", type, page, execution.totalPulled, pageMs);
IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO(); IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO();
batchStat.setEntityType(type); batchStat.setEntityType(type);
batchStat.setPageNumber(page); batchStat.setPageNumber(page);
@@ -403,11 +406,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService {
int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages(); int pagesLimit = reqVO.getMaxPages() == null ? Integer.MAX_VALUE : reqVO.getMaxPages();
int processedPages = 0; int processedPages = 0;
for (int page = startPage; processedPages < pagesLimit; page++) { for (int page = startPage; processedPages < pagesLimit; page++) {
long pageStart = System.currentTimeMillis();
BatchExecution execution = executor.execute(page, pageSize); BatchExecution execution = executor.execute(page, pageSize);
long pageMs = System.currentTimeMillis() - pageStart;
if (execution == null || execution.totalPulled == 0) { if (execution == null || execution.totalPulled == 0) {
break; break;
} }
processedPages++; processedPages++;
log.info("[iWork] 手动同步 {} 页={} pulled={} costMs={}", type, page, execution.totalPulled, pageMs);
IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO(); IWorkSyncBatchStatVO batchStat = new IWorkSyncBatchStatVO();
batchStat.setEntityType(type); batchStat.setEntityType(type);
batchStat.setPageNumber(page); batchStat.setPageNumber(page);

View File

@@ -203,6 +203,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
.put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString()) .put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString())
.put(LoginUser.INFO_KEY_USERNAME, user.getUsername()) .put(LoginUser.INFO_KEY_USERNAME, user.getUsername())
.put(LoginUser.INFO_KEY_PHONE, user.getMobile()) .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())) .put(LoginUser.INFO_KEY_POST_IDS, CollUtil.isEmpty(user.getPostIds()) ? "[]" : JsonUtils.toJsonString(user.getPostIds()))
.build(); .build();
} else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {