Merge branch 'refs/heads/zt-test' into test

# Conflicts:
#	zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/biz/system/permission/PermissionCommonApi.java
#	zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/DeptContextHolder.java
#	zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java
This commit is contained in:
FCL
2026-01-19 10:59:08 +08:00
178 changed files with 7735 additions and 273 deletions

View File

@@ -192,24 +192,4 @@
</dependency>
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,18 +0,0 @@
package com.zt.plat.module.databus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Databus 模块的启动类
*
* @author ZT
*/
@SpringBootApplication
public class DatabusServerApplication {
public static void main(String[] args) {
SpringApplication.run(DatabusServerApplication.class, args);
}
}

View File

@@ -42,6 +42,9 @@ public class ApiClientCredentialRespVO {
@Schema(description = "匿名访问固定用户昵称", example = "张三")
private String anonymousUserNickname;
@Schema(description = "是否启用加密", example = "true")
private Boolean enableEncryption;
@Schema(description = "创建时间")
private LocalDateTime createTime;

View File

@@ -45,4 +45,8 @@ public class ApiClientCredentialSaveReqVO {
@Schema(description = "匿名访问固定用户 ID", example = "1024")
private Long anonymousUserId;
@Schema(description = "是否启用加密", example = "true")
@NotNull(message = "启用加密标识不能为空")
private Boolean enableEncryption;
}

View File

@@ -38,4 +38,6 @@ public class ApiClientCredentialDO extends BaseDO {
private Long anonymousUserId;
private Boolean enableEncryption;
}

View File

@@ -108,7 +108,9 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
credential = credentialService.findActiveCredential(appId)
.orElseThrow(() -> new SecurityValidationException(HttpStatus.UNAUTHORIZED, "应用凭证不存在或已禁用"));
boolean allowAnonymous = Boolean.TRUE.equals(credential.getAllowAnonymous());
boolean enableEncryption = Boolean.TRUE.equals(credential.getEnableEncryption());
ApiAnonymousUserService.AnonymousUserDetails anonymousDetails = null;
byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream());
if (allowAnonymous) {
Long anonymousUserId = credential.getAnonymousUserId();
if (anonymousUserId == null) {
@@ -117,24 +119,25 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
anonymousDetails = anonymousUserService.find(anonymousUserId)
.orElseThrow(() -> new SecurityValidationException(HttpStatus.UNAUTHORIZED, "匿名访问固定用户不可用"));
}
String timestampHeader = requireHeader(request, TIMESTAMP_HEADER, "缺少时间戳");
// 校验时间戳与随机数,防止请求被重放
validateTimestamp(timestampHeader, security);
String nonce = requireHeader(request, NONCE_HEADER, "缺少随机数");
if (nonce.length() < 8) {
throw new SecurityValidationException(HttpStatus.BAD_REQUEST, "随机数长度不足");
}
String signature = requireHeader(request, SIGNATURE_HEADER, "缺少签名");
if (enableEncryption){
String nonce = requireHeader(request, NONCE_HEADER, "缺少随机数");
if (nonce.length() < 8) {
throw new SecurityValidationException(HttpStatus.BAD_REQUEST, "随机数长度不足");
}
String signature = requireHeader(request, SIGNATURE_HEADER, "缺少签名");
byte[] originalBody = StreamUtils.copyToByteArray(request.getInputStream());
// 尝试按凭证配置解密请求体,并构建签名载荷进行校验
byte[] decryptedBody = decryptRequestBody(originalBody, credential, security);
verifySignature(request, decryptedBody, signature, credential, security, appId, timestampHeader);
ensureNonce(tenantId, appId, nonce, security);
// 尝试按凭证配置解密请求体,并构建签名载荷进行校验
byte[] decryptedBody = decryptRequestBody(requestBody, credential, security);
verifySignature(request, decryptedBody, signature, credential, security, appId, timestampHeader);
ensureNonce(tenantId, appId, nonce, security);
requestBody = decryptedBody;
}
// 使用可重复读取的请求包装,供后续过滤器继续消费
CachedBodyHttpServletRequest securedRequest = new CachedBodyHttpServletRequest(request, decryptedBody);
CachedBodyHttpServletRequest securedRequest = new CachedBodyHttpServletRequest(request, requestBody);
securedRequest.setHeader(APP_ID_HEADER, credential.getAppId());
securedRequest.setHeader(HEADER_CREDENTIAL_ID, credential.getId() != null ? String.valueOf(credential.getId()) : null);
ApiGatewayAccessLogger.propagateLogIdHeader(securedRequest, accessLogId);
@@ -238,6 +241,11 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
private byte[] decryptRequestBody(byte[] originalBody,
ApiClientCredentialDO credential,
ApiGatewayProperties.Security security) {
// 检查是否启用加密,如果未启用则直接返回原文
if (credential != null && Boolean.FALSE.equals(credential.getEnableEncryption())) {
return originalBody != null ? originalBody : new byte[0];
}
if (originalBody == null || originalBody.length == 0) {
return new byte[0];
}
@@ -390,6 +398,11 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
private void encryptResponse(ContentCachingResponseWrapper responseWrapper,
ApiClientCredentialDO credential,
ApiGatewayProperties.Security security) throws IOException {
// 检查是否启用加密,如果未启用则直接返回,不加密响应
if (credential != null && Boolean.FALSE.equals(credential.getEnableEncryption())) {
return;
}
if (!security.isEncryptResponse()) {
return;
}
@@ -524,6 +537,10 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
if (security == null || credential == null) {
return false;
}
// 检查是否启用加密,如果未启用则不加密错误响应
if (Boolean.FALSE.equals(credential.getEnableEncryption())) {
return false;
}
if (!security.isEncryptResponse()) {
return false;
}

View File

@@ -46,9 +46,9 @@ public class HttpStepHandler implements ApiStepHandler {
private final WebClient.Builder webClientBuilder;
private final ExpressionExecutor expressionExecutor;
private static final Duration RETRY_DELAY = Duration.ofMillis(200);
private static final int RETRY_ATTEMPTS = 3;
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(20);
private static final Duration RETRY_DELAY = Duration.ofSeconds(5);
private static final int RETRY_ATTEMPTS = 5;
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(40);
private static final Set<String> DEFAULT_FORWARDED_HEADERS = Set.of(
"authorization",

View File

@@ -1,7 +1,5 @@
package com.zt.plat.module.databus.service.gateway.impl;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
@@ -11,14 +9,12 @@ import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
import com.zt.plat.module.databus.dal.mysql.gateway.ApiClientCredentialMapper;
import com.zt.plat.module.databus.service.gateway.ApiAnonymousUserService;
import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -36,16 +32,6 @@ public class ApiClientCredentialServiceImpl implements ApiClientCredentialServic
private final ApiClientCredentialMapper credentialMapper;
private final ApiAnonymousUserService anonymousUserService;
private LoadingCache<String, Optional<ApiClientCredentialDO>> credentialCache;
@PostConstruct
public void initCache() {
credentialCache = Caffeine.newBuilder()
.maximumSize(256)
.expireAfterWrite(Duration.ofMinutes(5))
.build(this::loadCredentialSync);
}
@Override
public PageResult<ApiClientCredentialDO> getPage(ApiClientCredentialPageReqVO reqVO) {
return credentialMapper.selectPage(reqVO);
@@ -67,7 +53,6 @@ public class ApiClientCredentialServiceImpl implements ApiClientCredentialServic
credential.setAnonymousUserId(null);
}
credentialMapper.insert(credential);
invalidateCache(credential.getAppId());
return credential.getId();
}
@@ -86,8 +71,6 @@ public class ApiClientCredentialServiceImpl implements ApiClientCredentialServic
updateObj.setAnonymousUserId(null);
}
credentialMapper.updateById(updateObj);
invalidateCache(existing.getAppId());
invalidateCache(updateObj.getAppId());
if (!Objects.equals(existing.getAnonymousUserId(), updateObj.getAnonymousUserId())) {
anonymousUserService.invalidate(existing.getAnonymousUserId());
anonymousUserService.invalidate(updateObj.getAnonymousUserId());
@@ -99,7 +82,6 @@ public class ApiClientCredentialServiceImpl implements ApiClientCredentialServic
public void delete(Long id) {
ApiClientCredentialDO existing = ensureExists(id);
credentialMapper.deleteById(id);
invalidateCache(existing.getAppId());
anonymousUserService.invalidate(existing.getAnonymousUserId());
}
@@ -118,11 +100,7 @@ public class ApiClientCredentialServiceImpl implements ApiClientCredentialServic
if (!StringUtils.hasText(appId)) {
return Optional.empty();
}
return credentialCache.get(appId.trim());
}
private Optional<ApiClientCredentialDO> loadCredentialSync(String appId) {
Optional<ApiClientCredentialDO> credential = credentialMapper.selectByAppId(appId)
Optional<ApiClientCredentialDO> credential = credentialMapper.selectByAppId(appId.trim())
.filter(item -> Boolean.TRUE.equals(item.getEnabled()));
if (credential.isEmpty()) {
log.debug("[API-PORTAL] 未找到 appId={} 的有效凭证", appId);
@@ -147,13 +125,6 @@ public class ApiClientCredentialServiceImpl implements ApiClientCredentialServic
return credential;
}
private void invalidateCache(String appId) {
if (!StringUtils.hasText(appId)) {
return;
}
credentialCache.invalidate(appId.trim());
}
private void normalizeAnonymousSettings(ApiClientCredentialSaveReqVO reqVO) {
if (Boolean.TRUE.equals(reqVO.getAllowAnonymous())) {
if (reqVO.getAnonymousUserId() == null) {

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