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;
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<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 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<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)) {
return null;
@@ -250,8 +266,13 @@ public class DeptDataPermissionRule implements DataPermissionRule {
if (StrUtil.isEmpty(columnName)) {
return null;
}
// 拼接条件
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.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) {

View File

@@ -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";
/**
* 用户编号
*/

View File

@@ -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<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.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 -> {
Optional<String> 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 {

View File

@@ -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<Long, Optional<AnonymousUserDetails>> cache;
@PostConstruct
@@ -105,8 +108,10 @@ public class ApiAnonymousUserService {
if (details == null) {
return Optional.empty();
}
try {
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());
@@ -114,10 +119,23 @@ public class ApiAnonymousUserService {
}
return Optional.of(resp.getAccessToken());
} catch (Exception ex) {
log.error("[ANONYMOUS] 获取用户 {} 的访问令牌时发生异常", details.getUserId(), 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();
}
}
}
}
log.error("[ANONYMOUS] 获取用户 {} 的访问令牌时发生异常", details.getUserId(), lastException);
return Optional.empty();
}
private OAuth2AccessTokenCreateReqDTO buildAccessTokenRequest(AnonymousUserDetails details) {
OAuth2AccessTokenCreateReqDTO req = new OAuth2AccessTokenCreateReqDTO();

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.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 ZTfallbackFactory =
@Tag(name = "RPC 服务 - 部门")
public interface DeptApi {
@@ -86,6 +89,11 @@ public interface DeptApi {
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
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")

View File

@@ -107,6 +107,12 @@ public class DeptApiImpl implements DeptApi {
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

View File

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

Some files were not shown because too many files have changed in this diff Show More