entry : request.getParameterMap().entrySet()) {
+ sortedMap.put(entry.getKey(), entry.getValue()[0]);
+ }
+ return sortedMap;
+ }
+}
diff --git a/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/aop/ApiSignatureAspect.java b/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/aop/ApiSignatureAspect.java
index 6729848c..815e8a20 100644
--- a/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/aop/ApiSignatureAspect.java
+++ b/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/aop/ApiSignatureAspect.java
@@ -1,29 +1,18 @@
package com.zt.plat.framework.signature.core.aop;
-import cn.hutool.core.lang.Assert;
-import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.BooleanUtil;
-import cn.hutool.core.util.ObjUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.crypto.digest.DigestUtil;
-import com.zt.plat.framework.common.exception.ServiceException;
-import com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.zt.plat.framework.common.util.servlet.ServletUtils;
+import com.zt.plat.framework.signature.core.ApiSignatureVerifier;
import com.zt.plat.framework.signature.core.annotation.ApiSignature;
+import com.zt.plat.framework.signature.core.config.ApiSignatureProperties;
+import com.zt.plat.framework.signature.core.model.ApiSignatureRule;
import com.zt.plat.framework.signature.core.redis.ApiSignatureRedisDAO;
import jakarta.servlet.http.HttpServletRequest;
-import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
-import java.util.Map;
import java.util.Objects;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import static com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
/**
* 拦截声明了 {@link ApiSignature} 注解的方法,实现签名
@@ -32,143 +21,33 @@ import static com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConsta
*/
@Aspect
@Slf4j
-@AllArgsConstructor
+@Deprecated
public class ApiSignatureAspect {
- private final ApiSignatureRedisDAO signatureRedisDAO;
+ private final ApiSignatureVerifier verifier;
+ private final ApiSignatureProperties properties;
+
+ public ApiSignatureAspect(ApiSignatureRedisDAO signatureRedisDAO) {
+ this(new ApiSignatureVerifier(signatureRedisDAO), new ApiSignatureProperties());
+ }
+
+ public ApiSignatureAspect(ApiSignatureVerifier verifier, ApiSignatureProperties properties) {
+ this.verifier = verifier;
+ this.properties = properties;
+ }
@Before("@annotation(signature)")
public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) {
- // 1. 验证通过,直接结束
- if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) {
- return;
- }
-
- // 2. 验证不通过,抛出异常
- log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(),
- joinPoint.getArgs());
- throw new ServiceException(BAD_REQUEST.getCode(),
- StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg()));
+ HttpServletRequest request = Objects.requireNonNull(ServletUtils.getRequest());
+ ApiSignatureRule rule = ApiSignatureRule.from(signature, properties);
+ verifier.verify(rule, request);
+ log.debug("[beforePointCut][方法{} 参数({}) 签名校验通过]", joinPoint.getSignature(), joinPoint.getArgs());
}
public boolean verifySignature(ApiSignature signature, HttpServletRequest request) {
- // 1.1 校验 Header
- if (!verifyHeaders(signature, request)) {
- return false;
- }
- // 1.2 校验 appId 是否能获取到对应的 appSecret
- String appId = request.getHeader(signature.appId());
- String appSecret = signatureRedisDAO.getAppSecret(appId);
- Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId);
-
- // 2. 校验签名【重要!】
- String clientSignature = request.getHeader(signature.sign()); // 客户端签名
- String serverSignatureString = buildSignatureString(signature, request, appSecret); // 服务端签名字符串
- String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名
- if (ObjUtil.notEqual(clientSignature, serverSignature)) {
- return false;
- }
-
- // 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
- String nonce = request.getHeader(signature.nonce());
- if (BooleanUtil.isFalse(signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit()))) {
- String timestamp = request.getHeader(signature.timestamp());
- log.info("[verifySignature][appId({}) timestamp({}) nonce({}) sign({}) 存在重复请求]", appId, timestamp, nonce, clientSignature);
- throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), "存在重复请求");
- }
+ ApiSignatureRule rule = ApiSignatureRule.from(signature, properties);
+ verifier.verify(rule, request);
return true;
}
- /**
- * 校验请求头加签参数
- *
- * 1. appId 是否为空
- * 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟
- * 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
- * 4. sign 是否为空
- *
- * @param signature signature
- * @param request request
- * @return 是否校验 Header 通过
- */
- private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) {
- // 1. 非空校验
- String appId = request.getHeader(signature.appId());
- if (StrUtil.isBlank(appId)) {
- return false;
- }
- String timestamp = request.getHeader(signature.timestamp());
- if (StrUtil.isBlank(timestamp)) {
- return false;
- }
- String nonce = request.getHeader(signature.nonce());
- if (StrUtil.length(nonce) < 10) {
- return false;
- }
- String sign = request.getHeader(signature.sign());
- if (StrUtil.isBlank(sign)) {
- return false;
- }
-
- // 2. 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值)
- long expireTime = signature.timeUnit().toMillis(signature.timeout());
- long requestTimestamp = Long.parseLong(timestamp);
- long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp);
- if (timestampDisparity > expireTime) {
- return false;
- }
-
- // 3. 检查 nonce 是否存在,有且仅能使用一次
- return signatureRedisDAO.getNonce(appId, nonce) == null;
- }
-
- /**
- * 构建签名字符串
- *
- * 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
- *
- * @param signature signature
- * @param request request
- * @param appSecret appSecret
- * @return 签名字符串
- */
- private String buildSignatureString(ApiSignature signature, HttpServletRequest request, String appSecret) {
- SortedMap parameterMap = getRequestParameterMap(request); // 请求头
- SortedMap headerMap = getRequestHeaderMap(signature, request); // 请求参数
- String requestBody = StrUtil.nullToDefault(ServletUtils.getBody(request), ""); // 请求体
- return MapUtil.join(parameterMap, "&", "=")
- + requestBody
- + MapUtil.join(headerMap, "&", "=")
- + appSecret;
- }
-
- /**
- * 获取请求头加签参数 Map
- *
- * @param request 请求
- * @param signature 签名注解
- * @return signature params
- */
- private static SortedMap getRequestHeaderMap(ApiSignature signature, HttpServletRequest request) {
- SortedMap sortedMap = new TreeMap<>();
- sortedMap.put(signature.appId(), request.getHeader(signature.appId()));
- sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp()));
- sortedMap.put(signature.nonce(), request.getHeader(signature.nonce()));
- return sortedMap;
- }
-
- /**
- * 获取请求参数 Map
- *
- * @param request 请求
- * @return queryParams
- */
- private static SortedMap getRequestParameterMap(HttpServletRequest request) {
- SortedMap sortedMap = new TreeMap<>();
- for (Map.Entry entry : request.getParameterMap().entrySet()) {
- sortedMap.put(entry.getKey(), entry.getValue()[0]);
- }
- return sortedMap;
- }
-
}
\ No newline at end of file
diff --git a/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/config/ApiSignatureProperties.java b/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/config/ApiSignatureProperties.java
new file mode 100644
index 00000000..d2158a89
--- /dev/null
+++ b/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/config/ApiSignatureProperties.java
@@ -0,0 +1,68 @@
+package com.zt.plat.framework.signature.core.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * API 签名配置
+ */
+@Data
+@ConfigurationProperties(prefix = "zt.api-signature")
+public class ApiSignatureProperties {
+
+ /**
+ * 是否开启全局签名校验
+ */
+ private boolean enabled = true;
+
+ /**
+ * 签名有效期
+ */
+ private int timeout = 60;
+
+ /**
+ * 时间单位
+ */
+ private TimeUnit timeUnit = TimeUnit.SECONDS;
+
+ /**
+ * 校验失败时的提示信息
+ */
+ private String message = "签名不正确";
+
+ /**
+ * 请求头:appId
+ */
+ private String appId = "appId";
+
+ /**
+ * 请求头:timestamp
+ */
+ private String timestamp = "timestamp";
+
+ /**
+ * 请求头:nonce
+ */
+ private String nonce = "nonce";
+
+ /**
+ * 请求头:sign
+ */
+ private String sign = "sign";
+
+ /**
+ * 需要进行签名校验的路径,默认全量
+ */
+ private List includePaths = new ArrayList<>(Arrays.asList("/**"));
+
+ /**
+ * 无需签名校验的路径
+ */
+ private List excludePaths = new ArrayList<>(Arrays.asList("/error", "/swagger-ui/**", "/v3/api-docs/**"));
+
+}
\ No newline at end of file
diff --git a/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/model/ApiSignatureRule.java b/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/model/ApiSignatureRule.java
new file mode 100644
index 00000000..1c71c962
--- /dev/null
+++ b/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/model/ApiSignatureRule.java
@@ -0,0 +1,50 @@
+package com.zt.plat.framework.signature.core.model;
+
+import cn.hutool.core.util.StrUtil;
+import com.zt.plat.framework.signature.core.annotation.ApiSignature;
+import com.zt.plat.framework.signature.core.config.ApiSignatureProperties;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 签名校验规则
+ */
+@Getter
+@Builder
+public class ApiSignatureRule {
+
+ private final int timeout;
+ private final TimeUnit timeUnit;
+ private final String message;
+ private final String appId;
+ private final String timestamp;
+ private final String nonce;
+ private final String sign;
+
+ public static ApiSignatureRule from(ApiSignatureProperties properties) {
+ return ApiSignatureRule.builder()
+ .timeout(properties.getTimeout())
+ .timeUnit(properties.getTimeUnit())
+ .message(properties.getMessage())
+ .appId(properties.getAppId())
+ .timestamp(properties.getTimestamp())
+ .nonce(properties.getNonce())
+ .sign(properties.getSign())
+ .build();
+ }
+
+ public static ApiSignatureRule from(ApiSignature signature, ApiSignatureProperties defaults) {
+ return ApiSignatureRule.builder()
+ .timeout(signature.timeout())
+ .timeUnit(signature.timeUnit())
+ .message(StrUtil.blankToDefault(signature.message(), defaults.getMessage()))
+ .appId(signature.appId())
+ .timestamp(signature.timestamp())
+ .nonce(signature.nonce())
+ .sign(signature.sign())
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/web/ApiSignatureHandlerInterceptor.java b/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/web/ApiSignatureHandlerInterceptor.java
new file mode 100644
index 00000000..faf528ff
--- /dev/null
+++ b/zt-framework/zt-spring-boot-starter-protection/src/main/java/com/zt/plat/framework/signature/core/web/ApiSignatureHandlerInterceptor.java
@@ -0,0 +1,80 @@
+package com.zt.plat.framework.signature.core.web;
+
+import com.zt.plat.framework.signature.core.ApiSignatureVerifier;
+import com.zt.plat.framework.signature.core.annotation.ApiSignature;
+import com.zt.plat.framework.signature.core.config.ApiSignatureProperties;
+import com.zt.plat.framework.signature.core.model.ApiSignatureRule;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.util.UrlPathHelper;
+
+import java.util.List;
+
+/**
+ * 全局 API 签名拦截器
+ */
+@RequiredArgsConstructor
+public class ApiSignatureHandlerInterceptor implements HandlerInterceptor {
+
+ private final ApiSignatureVerifier verifier;
+ private final ApiSignatureProperties properties;
+ private final AntPathMatcher pathMatcher = new AntPathMatcher();
+ private final UrlPathHelper urlPathHelper = new UrlPathHelper();
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ if (!properties.isEnabled()) {
+ return true;
+ }
+ if (!(handler instanceof HandlerMethod handlerMethod)) {
+ return true;
+ }
+ if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
+ return true;
+ }
+ String lookupPath = urlPathHelper.getLookupPathForRequest(request);
+ if (shouldSkip(lookupPath)) {
+ return true;
+ }
+
+ ApiSignatureRule rule = createRule(handlerMethod);
+ verifier.verify(rule, request);
+ return true;
+ }
+
+ private boolean shouldSkip(String path) {
+ List includePaths = properties.getIncludePaths();
+ if (!CollectionUtils.isEmpty(includePaths)) {
+ boolean matched = includePaths.stream().anyMatch(pattern -> pathMatcher.match(pattern, path));
+ if (!matched) {
+ return true;
+ }
+ }
+ List excludePaths = properties.getExcludePaths();
+ if (!CollectionUtils.isEmpty(excludePaths)) {
+ for (String pattern : excludePaths) {
+ if (pathMatcher.match(pattern, path)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private ApiSignatureRule createRule(HandlerMethod handlerMethod) {
+ ApiSignature signature = handlerMethod.getMethodAnnotation(ApiSignature.class);
+ if (signature != null) {
+ return ApiSignatureRule.from(signature, properties);
+ }
+ signature = handlerMethod.getBeanType().getAnnotation(ApiSignature.class);
+ if (signature != null) {
+ return ApiSignatureRule.from(signature, properties);
+ }
+ return ApiSignatureRule.from(properties);
+ }
+}
diff --git a/zt-framework/zt-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/zt-framework/zt-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 96ad764d..3ed881de 100644
--- a/zt-framework/zt-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/zt-framework/zt-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,4 +1,3 @@
com.zt.plat.framework.idempotent.config.ZtIdempotentConfiguration
com.zt.plat.framework.lock4j.config.ZtLock4jConfiguration
com.zt.plat.framework.ratelimiter.config.ZtRateLimiterConfiguration
-com.zt.plat.framework.signature.config.ZtApiSignatureAutoConfiguration
\ No newline at end of file
diff --git a/zt-framework/zt-spring-boot-starter-protection/src/test/java/com/zt/plat/framework/signature/core/ApiSignatureTest.java b/zt-framework/zt-spring-boot-starter-protection/src/test/java/com/zt/plat/framework/signature/core/ApiSignatureTest.java
index 3d93d079..163bd03b 100644
--- a/zt-framework/zt-spring-boot-starter-protection/src/test/java/com/zt/plat/framework/signature/core/ApiSignatureTest.java
+++ b/zt-framework/zt-spring-boot-starter-protection/src/test/java/com/zt/plat/framework/signature/core/ApiSignatureTest.java
@@ -3,13 +3,13 @@ package com.zt.plat.framework.signature.core;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.DigestUtil;
-import com.zt.plat.framework.signature.core.annotation.ApiSignature;
-import com.zt.plat.framework.signature.core.aop.ApiSignatureAspect;
+import com.zt.plat.framework.signature.core.ApiSignatureVerifier;
+import com.zt.plat.framework.signature.core.config.ApiSignatureProperties;
+import com.zt.plat.framework.signature.core.model.ApiSignatureRule;
import com.zt.plat.framework.signature.core.redis.ApiSignatureRedisDAO;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -28,9 +28,6 @@ import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class ApiSignatureTest {
- @InjectMocks
- private ApiSignatureAspect apiSignatureAspect;
-
@Mock
private ApiSignatureRedisDAO signatureRedisDAO;
@@ -45,13 +42,16 @@ public class ApiSignatureTest {
String sign = DigestUtil.sha256Hex(signString);
// 准备参数
- ApiSignature apiSignature = mock(ApiSignature.class);
- when(apiSignature.appId()).thenReturn("appId");
- when(apiSignature.timestamp()).thenReturn("timestamp");
- when(apiSignature.nonce()).thenReturn("nonce");
- when(apiSignature.sign()).thenReturn("sign");
- when(apiSignature.timeout()).thenReturn(60);
- when(apiSignature.timeUnit()).thenReturn(TimeUnit.SECONDS);
+ ApiSignatureProperties properties = new ApiSignatureProperties();
+ ApiSignatureRule apiSignature = ApiSignatureRule.builder()
+ .appId("appId")
+ .timestamp("timestamp")
+ .nonce("nonce")
+ .sign("sign")
+ .timeout(60)
+ .timeUnit(TimeUnit.SECONDS)
+ .message(properties.getMessage())
+ .build();
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader(eq("appId"))).thenReturn(appId);
when(request.getHeader(eq("timestamp"))).thenReturn(String.valueOf(timestamp));
@@ -62,11 +62,13 @@ public class ApiSignatureTest {
when(request.getContentType()).thenReturn("application/json");
when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test")));
// mock 方法
+ when(signatureRedisDAO.getNonce(eq(appId), eq(nonce))).thenReturn(null);
when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret);
when(signatureRedisDAO.setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS))).thenReturn(true);
// 调用
- boolean result = apiSignatureAspect.verifySignature(apiSignature, request);
+ ApiSignatureVerifier verifier = new ApiSignatureVerifier(signatureRedisDAO);
+ boolean result = verifier.verify(apiSignature, request);
// 断言结果
assertTrue(result);
}
diff --git a/zt-gateway/src/main/java/com/zt/plat/gateway/filter/security/TokenAuthenticationFilter.java b/zt-gateway/src/main/java/com/zt/plat/gateway/filter/security/TokenAuthenticationFilter.java
index c75714f5..8198ee0d 100644
--- a/zt-gateway/src/main/java/com/zt/plat/gateway/filter/security/TokenAuthenticationFilter.java
+++ b/zt-gateway/src/main/java/com/zt/plat/gateway/filter/security/TokenAuthenticationFilter.java
@@ -44,6 +44,8 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
private static final TypeReference> CHECK_RESULT_TYPE_REFERENCE
= new TypeReference>() {};
+ private static final String ADMIN_DATABUS_PORTAL_PREFIX = "/admin-api/databus/api/portal";
+
/**
* 空的 LoginUser 的结果
*
@@ -85,6 +87,13 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
// 移除 login-user 的请求头,避免伪造模拟
exchange = SecurityFrameworkUtils.removeLoginUser(exchange);
+ // API Portal 通过网关访问时无需认证,直接放行
+ String rawPath = exchange.getRequest().getURI().getRawPath();
+ if (rawPath != null && (rawPath.equals(ADMIN_DATABUS_PORTAL_PREFIX)
+ || rawPath.startsWith(ADMIN_DATABUS_PORTAL_PREFIX + "/"))) {
+ return chain.filter(exchange);
+ }
+
// 情况一,如果没有 Token 令牌,则直接继续 filter
String token = SecurityFrameworkUtils.obtainAuthorization(exchange);
if (StrUtil.isEmpty(token)) {
diff --git a/zt-module-ai/zt-module-ai-server/src/main/resources/application.yaml b/zt-module-ai/zt-module-ai-server/src/main/resources/application.yaml
index 73d96ed9..53a15dc8 100644
--- a/zt-module-ai/zt-module-ai-server/src/main/resources/application.yaml
+++ b/zt-module-ai/zt-module-ai-server/src/main/resources/application.yaml
@@ -212,8 +212,8 @@ zt:
exclude-urls: # 如下 url,仅仅是为了演示,去掉配置也没关系
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
swagger:
- title: 管理后台
- description: 提供管理员管理的所有功能
+ title: ai 模块
+ description: 提供ai功能
version: ${zt.info.version}
tenant: # 多租户相关配置项
enable: true
diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml b/zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml
index 07020c50..adfbf58f 100644
--- a/zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml
+++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml
@@ -141,8 +141,8 @@ zt:
exclude-urls: # 如下 url,仅仅是为了演示,去掉配置也没关系
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
swagger:
- title: 管理后台
- description: 提供管理员管理的所有功能
+ title: 流程模块
+ description: 提供流程模块功能
version: ${zt.info.version}
tenant: # 多租户相关配置项
enable: true
diff --git a/zt-module-databus/zt-module-databus-server/pom.xml b/zt-module-databus/zt-module-databus-server/pom.xml
index 0d51180c..008a387f 100644
--- a/zt-module-databus/zt-module-databus-server/pom.xml
+++ b/zt-module-databus/zt-module-databus-server/pom.xml
@@ -140,11 +140,6 @@
org.springframework.integration
spring-integration-scripting
-
- org.springframework.retry
- spring-retry
-
-
org.springframework.boot
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiClientCredentialController.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiClientCredentialController.java
new file mode 100644
index 00000000..3682b7d8
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiClientCredentialController.java
@@ -0,0 +1,79 @@
+package com.zt.plat.module.databus.controller.admin.gateway;
+
+import com.zt.plat.framework.common.pojo.CommonResult;
+import com.zt.plat.framework.common.pojo.PageResult;
+import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiClientCredentialConvert;
+import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialPageReqVO;
+import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialRespVO;
+import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialSaveReqVO;
+import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialSimpleRespVO;
+import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
+import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static com.zt.plat.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - API 客户端凭证")
+@RestController
+@RequestMapping("/databus/gateway/credential")
+@RequiredArgsConstructor
+@Validated
+public class ApiClientCredentialController {
+
+ private final ApiClientCredentialService credentialService;
+
+ @GetMapping("/page")
+ @Operation(summary = "分页查询客户端凭证")
+ public CommonResult> page(ApiClientCredentialPageReqVO reqVO) {
+ PageResult page = credentialService.getPage(reqVO);
+ return success(ApiClientCredentialConvert.INSTANCE.convertPage(page));
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "查询凭证详情")
+ public CommonResult get(@RequestParam("id") Long id) {
+ ApiClientCredentialDO credential = credentialService.get(id);
+ return success(ApiClientCredentialConvert.INSTANCE.convert(credential));
+ }
+
+ @PostMapping("/create")
+ @Operation(summary = "新增客户端凭证")
+ public CommonResult create(@Valid @RequestBody ApiClientCredentialSaveReqVO reqVO) {
+ return success(credentialService.create(reqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新客户端凭证")
+ public CommonResult update(@Valid @RequestBody ApiClientCredentialSaveReqVO reqVO) {
+ credentialService.update(reqVO);
+ return success(Boolean.TRUE);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除客户端凭证")
+ public CommonResult delete(@RequestParam("id") Long id) {
+ credentialService.delete(id);
+ return success(Boolean.TRUE);
+ }
+
+ @GetMapping("/list-simple")
+ @Operation(summary = "获取启用的凭证列表(精简)")
+ public CommonResult> listSimple() {
+ List list = credentialService.listEnabled();
+ return success(ApiClientCredentialConvert.INSTANCE.convertSimpleList(list));
+ }
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayController.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayController.java
index e7a44307..5f451796 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayController.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayController.java
@@ -1,25 +1,20 @@
package com.zt.plat.module.databus.controller.admin.gateway;
-import com.zt.plat.framework.common.exception.ServiceException;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiDefinitionConvert;
import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionDetailRespVO;
-import com.zt.plat.module.databus.framework.integration.gateway.core.ApiFlowDispatcher;
+import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayExecutionService;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
-import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
-import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
-import org.springframework.util.StringUtils;
+import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@@ -30,44 +25,13 @@ import static com.zt.plat.framework.common.pojo.CommonResult.success;
@RequiredArgsConstructor
public class ApiGatewayController {
- private final ApiFlowDispatcher apiFlowDispatcher;
+ private final ApiGatewayExecutionService executionService;
private final ApiDefinitionService apiDefinitionService;
@PostMapping(value = "/invoke", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "测试调用 API 编排")
- public CommonResult invoke(@RequestBody ApiGatewayInvokeReqVO reqVO) {
- ApiInvocationContext context = ApiInvocationContext.create();
- context.setApiCode(reqVO.getApiCode());
- context.setApiVersion(reqVO.getVersion());
- context.setRequestBody(reqVO.getPayload());
- if (reqVO.getHeaders() != null) {
- context.getRequestHeaders().putAll(reqVO.getHeaders());
- }
- if (reqVO.getQueryParams() != null) {
- context.getRequestQueryParams().putAll(reqVO.getQueryParams());
- }
-
- ApiInvocationContext responseContext = context;
- try {
- responseContext = apiFlowDispatcher.dispatch(reqVO.getApiCode(), reqVO.getVersion(), context);
- } catch (ServiceException ex) {
- handleServiceException(responseContext, ex);
- } catch (Exception ex) {
- handleUnexpectedException(responseContext, ex);
- }
-
- int status = responseContext.getResponseStatus() != null ? responseContext.getResponseStatus() : HttpStatus.OK.value();
- String message = StringUtils.hasText(responseContext.getResponseMessage())
- ? responseContext.getResponseMessage()
- : HttpStatus.valueOf(status).getReasonPhrase();
-
- ApiGatewayResponse envelope = ApiGatewayResponse.builder()
- .code(status >= 200 && status < 400 ? "SUCCESS" : "ERROR")
- .message(message)
- .data(responseContext.getResponseBody())
- .traceId(responseContext.getRequestId())
- .build();
- return success(envelope);
+ public ResponseEntity invoke(@RequestBody ApiGatewayInvokeReqVO reqVO) {
+ return executionService.invokeForDebug(reqVO);
}
@GetMapping("/definitions")
@@ -79,29 +43,4 @@ public class ApiGatewayController {
return success(definitions);
}
- private void handleServiceException(ApiInvocationContext context, ServiceException ex) {
- String message = StringUtils.hasText(ex.getMessage()) ? ex.getMessage() : "API 调用失败";
- context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
- context.setResponseMessage(message);
- Map body = new HashMap<>();
- if (ex.getCode() != null) {
- body.put("errorCode", ex.getCode());
- }
- body.put("errorMessage", message);
- context.setResponseBody(body);
- }
-
- private void handleUnexpectedException(ApiInvocationContext context, Exception ex) {
- String message = StringUtils.hasText(ex.getMessage())
- ? ex.getMessage()
- : ex.getCause() != null && StringUtils.hasText(ex.getCause().getMessage())
- ? ex.getCause().getMessage()
- : "API invocation encountered an unexpected error";
- context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
- context.setResponseMessage(message);
- Map body = new HashMap<>();
- body.put("errorMessage", message);
- body.put("exception", ex.getClass().getSimpleName());
- context.setResponseBody(body);
- }
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyAuthController.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyAuthController.java
deleted file mode 100644
index 9547c46b..00000000
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyAuthController.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.zt.plat.module.databus.controller.admin.gateway;
-
-import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
-import com.zt.plat.framework.common.pojo.CommonResult;
-import com.zt.plat.framework.common.pojo.PageResult;
-import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiPolicyAuthConvert;
-import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO;
-import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyRespVO;
-import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySaveReqVO;
-import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySimpleRespVO;
-import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO;
-import com.zt.plat.module.databus.service.gateway.ApiPolicyAuthService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-import static com.zt.plat.framework.common.pojo.CommonResult.success;
-import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_POLICY_NOT_FOUND;
-
-@Tag(name = "管理后台 - 网关认证策略")
-@RestController
-@RequestMapping("/databus/gateway/policy/auth")
-@RequiredArgsConstructor
-@Validated
-public class ApiPolicyAuthController {
-
- private final ApiPolicyAuthService authService;
-
- @GetMapping("/page")
- @Operation(summary = "分页查询认证策略")
- public CommonResult> getAuthPolicyPage(@Valid ApiPolicyPageReqVO reqVO) {
- PageResult pageResult = authService.getPage(reqVO);
- return success(ApiPolicyAuthConvert.INSTANCE.convertPage(pageResult));
- }
-
- @GetMapping("/{id}")
- @Operation(summary = "查询认证策略详情")
- public CommonResult getAuthPolicy(@PathVariable("id") Long id) {
- ApiPolicyAuthDO policy = authService.get(id)
- .orElseThrow(() -> ServiceExceptionUtil.exception(API_POLICY_NOT_FOUND));
- return success(ApiPolicyAuthConvert.INSTANCE.convert(policy));
- }
-
- @GetMapping("/simple-list")
- @Operation(summary = "获取认证策略精简列表")
- public CommonResult> getAuthPolicySimpleList() {
- List list = authService.getSimpleList();
- return success(ApiPolicyAuthConvert.INSTANCE.convertSimpleList(list));
- }
-
- @PostMapping
- @Operation(summary = "创建认证策略")
- public CommonResult createAuthPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) {
- Long id = authService.create(reqVO);
- return success(id);
- }
-
- @PutMapping
- @Operation(summary = "更新认证策略")
- public CommonResult updateAuthPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) {
- authService.update(reqVO);
- return success(Boolean.TRUE);
- }
-
- @DeleteMapping("/{id}")
- @Operation(summary = "删除认证策略")
- public CommonResult deleteAuthPolicy(@PathVariable("id") Long id) {
- authService.delete(id);
- return success(Boolean.TRUE);
- }
-
-}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiClientCredentialConvert.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiClientCredentialConvert.java
new file mode 100644
index 00000000..29e1e821
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiClientCredentialConvert.java
@@ -0,0 +1,41 @@
+package com.zt.plat.module.databus.controller.admin.gateway.convert;
+
+import com.zt.plat.framework.common.pojo.PageResult;
+import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialRespVO;
+import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialSimpleRespVO;
+import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Mapper
+public interface ApiClientCredentialConvert {
+
+ ApiClientCredentialConvert INSTANCE = Mappers.getMapper(ApiClientCredentialConvert.class);
+
+ ApiClientCredentialRespVO convert(ApiClientCredentialDO bean);
+
+ List convertList(List list);
+
+ default PageResult convertPage(PageResult page) {
+ if (page == null) {
+ return PageResult.empty();
+ }
+ PageResult result = new PageResult<>();
+ result.setList(convertList(page.getList()));
+ result.setTotal(page.getTotal());
+ return result;
+ }
+
+ default List convertSimpleList(List list) {
+ return list == null ? List.of() : list.stream().map(item -> {
+ ApiClientCredentialSimpleRespVO vo = new ApiClientCredentialSimpleRespVO();
+ vo.setId(item.getId());
+ vo.setAppId(item.getAppId());
+ vo.setAppName(item.getAppName());
+ return vo;
+ }).collect(Collectors.toList());
+ }
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyAuthConvert.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyAuthConvert.java
deleted file mode 100644
index 3220d221..00000000
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyAuthConvert.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.zt.plat.module.databus.controller.admin.gateway.convert;
-
-import com.zt.plat.framework.common.pojo.PageResult;
-import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyRespVO;
-import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySimpleRespVO;
-import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-
-@Mapper
-public interface ApiPolicyAuthConvert {
-
- ApiPolicyAuthConvert INSTANCE = Mappers.getMapper(ApiPolicyAuthConvert.class);
-
- ApiPolicyRespVO convert(ApiPolicyAuthDO bean);
-
- List convertList(List list);
-
- PageResult convertPage(PageResult page);
-
- List convertSimpleList(List list);
-
-}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialPageReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialPageReqVO.java
new file mode 100644
index 00000000..e11f09c0
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialPageReqVO.java
@@ -0,0 +1,19 @@
+package com.zt.plat.module.databus.controller.admin.gateway.vo.credential;
+
+import com.zt.plat.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Schema(description = "管理后台 - API 客户端凭证分页查询 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ApiClientCredentialPageReqVO extends PageParam {
+
+ @Schema(description = "关键字,匹配 appId 或名称", example = "databus-app")
+ private String keyword;
+
+ @Schema(description = "是否启用")
+ private Boolean enabled;
+
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialRespVO.java
new file mode 100644
index 00000000..ceb9d621
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialRespVO.java
@@ -0,0 +1,42 @@
+package com.zt.plat.module.databus.controller.admin.gateway.vo.credential;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - API 客户端凭证 Response VO")
+@Data
+public class ApiClientCredentialRespVO {
+
+ @Schema(description = "记录编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "应用标识", example = "databus-app")
+ private String appId;
+
+ @Schema(description = "应用名称", example = "数据总线默认应用")
+ private String appName;
+
+ @Schema(description = "加密密钥", example = "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=")
+ private String encryptionKey;
+
+ @Schema(description = "加密算法", example = "AES")
+ private String encryptionType;
+
+ @Schema(description = "签名算法", example = "MD5")
+ private String signatureType;
+
+ @Schema(description = "是否启用", example = "true")
+ private Boolean enabled;
+
+ @Schema(description = "备注", example = "默认应用凭证")
+ private String remark;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+ @Schema(description = "最后更新时间")
+ private LocalDateTime updateTime;
+
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialSaveReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialSaveReqVO.java
new file mode 100644
index 00000000..15765118
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialSaveReqVO.java
@@ -0,0 +1,41 @@
+package com.zt.plat.module.databus.controller.admin.gateway.vo.credential;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - API 客户端凭证保存 Request VO")
+@Data
+public class ApiClientCredentialSaveReqVO {
+
+ @Schema(description = "记录编号,仅更新时必填", example = "1024")
+ private Long id;
+
+ @Schema(description = "应用标识", example = "databus-app")
+ @NotBlank(message = "应用标识不能为空")
+ private String appId;
+
+ @Schema(description = "应用名称", example = "数据总线默认应用")
+ private String appName;
+
+ @Schema(description = "加密密钥", example = "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=")
+ @NotBlank(message = "加密密钥不能为空")
+ private String encryptionKey;
+
+ @Schema(description = "加密算法", example = "AES")
+ @NotBlank(message = "加密算法不能为空")
+ private String encryptionType;
+
+ @Schema(description = "签名算法", example = "MD5")
+ @NotBlank(message = "签名算法不能为空")
+ private String signatureType;
+
+ @Schema(description = "是否启用", example = "true")
+ @NotNull(message = "启用状态不能为空")
+ private Boolean enabled;
+
+ @Schema(description = "备注", example = "默认应用凭证")
+ private String remark;
+
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialSimpleRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialSimpleRespVO.java
new file mode 100644
index 00000000..103f5ba7
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/credential/ApiClientCredentialSimpleRespVO.java
@@ -0,0 +1,19 @@
+package com.zt.plat.module.databus.controller.admin.gateway.vo.credential;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - API 客户端凭证精简 Response VO")
+@Data
+public class ApiClientCredentialSimpleRespVO {
+
+ @Schema(description = "记录编号", example = "1024")
+ private Long id;
+
+ @Schema(description = "应用标识", example = "databus-app")
+ private String appId;
+
+ @Schema(description = "应用名称", example = "数据总线默认应用")
+ private String appName;
+
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionDetailRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionDetailRespVO.java
index dda4cf3b..c64ba1ec 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionDetailRespVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionDetailRespVO.java
@@ -26,30 +26,18 @@ public class ApiDefinitionDetailRespVO {
@Schema(description = "HTTP 方法", example = "POST")
private String httpMethod;
- @Schema(description = "URI 模板", example = "/external/order/create")
- private String uriPattern;
-
@Schema(description = "状态", example = "1")
private Integer status;
- @Schema(description = "是否灰度")
- private Boolean greyReleased;
-
@Schema(description = "描述")
private String description;
- @Schema(description = "认证策略编号")
- private Long authPolicyId;
-
@Schema(description = "限流策略编号")
private Long rateLimitId;
@Schema(description = "响应模板(JSON)")
private String responseTemplate;
- @Schema(description = "缓存策略(JSON)")
- private String cacheStrategy;
-
@Schema(description = "创建时间")
private LocalDateTime createTime;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPageReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPageReqVO.java
index cf5d82f4..3f6025ce 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPageReqVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPageReqVO.java
@@ -10,7 +10,7 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
public class ApiDefinitionPageReqVO extends PageParam {
- @Schema(description = "关键字,匹配编码/描述/URI", example = "order")
+ @Schema(description = "关键字,匹配编码或描述", example = "order")
private String keyword;
@Schema(description = "API 状态", example = "1")
@@ -19,7 +19,4 @@ public class ApiDefinitionPageReqVO extends PageParam {
@Schema(description = "HTTP 方法", example = "POST")
private String httpMethod;
- @Schema(description = "是否灰度", example = "true")
- private Boolean greyReleased;
-
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSaveReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSaveReqVO.java
index f9239581..afd54dca 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSaveReqVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSaveReqVO.java
@@ -29,10 +29,6 @@ public class ApiDefinitionSaveReqVO {
@NotBlank(message = "HTTP 方法不能为空")
private String httpMethod;
- @Schema(description = "URI 模板", example = "/external/order/create")
- @NotBlank(message = "URI 模板不能为空")
- private String uriPattern;
-
@Schema(description = "API 状态", example = "1")
@NotNull(message = "API 状态不能为空")
private Integer status;
@@ -40,21 +36,12 @@ public class ApiDefinitionSaveReqVO {
@Schema(description = "描述")
private String description;
- @Schema(description = "认证策略编号")
- private Long authPolicyId;
-
@Schema(description = "限流策略编号")
private Long rateLimitId;
@Schema(description = "响应模板(JSON)")
private String responseTemplate;
- @Schema(description = "缓存策略(JSON)")
- private String cacheStrategy;
-
- @Schema(description = "是否开启灰度发布")
- private Boolean greyReleased;
-
@Schema(description = "API 级别变换列表")
@Valid
private List apiLevelTransforms = new ArrayList<>();
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepRespVO.java
index e6f7b71d..fba77079 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepRespVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepRespVO.java
@@ -37,9 +37,6 @@ public class ApiDefinitionStepRespVO {
@Schema(description = "超时时间(毫秒)")
private Long timeout;
- @Schema(description = "重试策略(JSON)")
- private String retryStrategy;
-
@Schema(description = "降级策略(JSON)")
private String fallbackStrategy;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java
index 650430ac..2f66ed5e 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java
@@ -39,9 +39,6 @@ public class ApiDefinitionStepSaveReqVO {
@Schema(description = "超时时间(毫秒)", example = "5000")
private Long timeout;
- @Schema(description = "重试策略(JSON)")
- private String retryStrategy;
-
@Schema(description = "降级策略(JSON)")
private String fallbackStrategy;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSummaryRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSummaryRespVO.java
index 75e528cb..865b06d9 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSummaryRespVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSummaryRespVO.java
@@ -21,15 +21,9 @@ public class ApiDefinitionSummaryRespVO {
@Schema(description = "HTTP 方法", example = "POST")
private String httpMethod;
- @Schema(description = "URI 模板", example = "/external/order/create")
- private String uriPattern;
-
@Schema(description = "状态", example = "1")
private Integer status;
- @Schema(description = "是否灰度", example = "true")
- private Boolean greyReleased;
-
@Schema(description = "描述")
private String description;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiClientCredentialDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiClientCredentialDO.java
new file mode 100644
index 00000000..76d0ad44
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiClientCredentialDO.java
@@ -0,0 +1,37 @@
+package com.zt.plat.module.databus.dal.dataobject.gateway;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * API 客户端凭证,用于维护 appId 与加解密配置的关联关系。
+ */
+@Data
+@TableName("databus_api_client_credential")
+@KeySequence("databus_api_client_credential_seq")
+@EqualsAndHashCode(callSuper = true)
+public class ApiClientCredentialDO extends BaseDO {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ private String appId;
+
+ private String appName;
+
+ private String encryptionKey;
+
+ private String encryptionType;
+
+ private String signatureType;
+
+ private Boolean enabled;
+
+ private String remark;
+
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionDO.java
index 596bb40d..af287c02 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionDO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionDO.java
@@ -24,8 +24,6 @@ public class ApiDefinitionDO extends TenantBaseDO {
private String apiCode;
- private String uriPattern;
-
private String httpMethod;
private String version;
@@ -37,16 +35,10 @@ public class ApiDefinitionDO extends TenantBaseDO {
private String description;
- private Long authPolicyId;
-
private Long rateLimitId;
private String responseTemplate;
- private String cacheStrategy;
-
private LocalDateTime updatedAt;
- private Boolean greyReleased;
-
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyAuthDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyAuthDO.java
deleted file mode 100644
index 90054af6..00000000
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyAuthDO.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.zt.plat.module.databus.dal.dataobject.gateway;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * Authentication policy definition.
- */
-@TableName("databus_policy_auth")
-@KeySequence("databus_policy_auth_seq")
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class ApiPolicyAuthDO extends TenantBaseDO {
-
- @TableId(type = IdType.ASSIGN_ID)
- private Long id;
-
- private String name;
-
- private String type;
-
- private String config;
-
- private String description;
-
-}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiStepDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiStepDO.java
index b0f17529..74af0037 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiStepDO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiStepDO.java
@@ -38,8 +38,6 @@ public class ApiStepDO extends TenantBaseDO {
private Long timeout;
- private String retryStrategy;
-
private String fallbackStrategy;
private String conditionExpr;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiClientCredentialMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiClientCredentialMapper.java
new file mode 100644
index 00000000..39a7bbcf
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiClientCredentialMapper.java
@@ -0,0 +1,47 @@
+package com.zt.plat.module.databus.dal.mysql.gateway;
+
+import cn.hutool.core.util.StrUtil;
+import com.zt.plat.framework.common.pojo.PageResult;
+import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
+import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialPageReqVO;
+import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+import java.util.Optional;
+
+@Mapper
+public interface ApiClientCredentialMapper extends BaseMapperX {
+
+ default Optional selectByAppId(String appId) {
+ if (StrUtil.isBlank(appId)) {
+ return Optional.empty();
+ }
+ LambdaQueryWrapperX query = new LambdaQueryWrapperX<>();
+ query.eq(ApiClientCredentialDO::getAppId, appId)
+ .eq(ApiClientCredentialDO::getDeleted, false);
+ return Optional.ofNullable(selectOne(query));
+ }
+
+ default PageResult selectPage(ApiClientCredentialPageReqVO reqVO) {
+ LambdaQueryWrapperX query = new LambdaQueryWrapperX<>();
+ if (StrUtil.isNotBlank(reqVO.getKeyword())) {
+ String keyword = reqVO.getKeyword();
+ query.and(wrapper -> wrapper.like(ApiClientCredentialDO::getAppId, keyword)
+ .or().like(ApiClientCredentialDO::getAppName, keyword));
+ }
+ query.eqIfPresent(ApiClientCredentialDO::getEnabled, reqVO.getEnabled())
+ .eq(ApiClientCredentialDO::getDeleted, false)
+ .orderByDesc(ApiClientCredentialDO::getUpdateTime)
+ .orderByDesc(ApiClientCredentialDO::getId);
+ return selectPage(reqVO, query);
+ }
+
+ default List selectEnabledList() {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ApiClientCredentialDO::getEnabled, true)
+ .eq(ApiClientCredentialDO::getDeleted, false)
+ .orderByAsc(ApiClientCredentialDO::getAppId));
+ }
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionMapper.java
index d4278f4a..8d2e2514 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionMapper.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionMapper.java
@@ -31,26 +31,15 @@ public interface ApiDefinitionMapper extends BaseMapperX {
if (StrUtil.isNotBlank(reqVO.getKeyword())) {
String keyword = reqVO.getKeyword();
query.and(wrapper -> wrapper.like(ApiDefinitionDO::getApiCode, keyword)
- .or().like(ApiDefinitionDO::getDescription, keyword)
- .or().like(ApiDefinitionDO::getUriPattern, keyword));
+ .or().like(ApiDefinitionDO::getDescription, keyword));
}
- query.eqIfPresent(ApiDefinitionDO::getStatus, reqVO.getStatus())
- .eqIfPresent(ApiDefinitionDO::getHttpMethod, reqVO.getHttpMethod())
-// .eqIfPresent(ApiDefinitionDO::getGreyReleased, reqVO.getGreyReleased())
- .orderByDesc(ApiDefinitionDO::getUpdateTime)
+ query.eqIfPresent(ApiDefinitionDO::getStatus, reqVO.getStatus())
+ .eqIfPresent(ApiDefinitionDO::getHttpMethod, reqVO.getHttpMethod())
+ .orderByDesc(ApiDefinitionDO::getUpdateTime)
.orderByDesc(ApiDefinitionDO::getId);
return selectPage(reqVO, query);
}
- default Long selectCountByAuthPolicyId(Long policyId) {
- if (policyId == null) {
- return 0L;
- }
- return selectCount(new LambdaQueryWrapperX()
- .eq(ApiDefinitionDO::getAuthPolicyId, policyId)
- .eq(ApiDefinitionDO::getDeleted, false));
- }
-
default Long selectCountByRateLimitPolicyId(Long policyId) {
if (policyId == null) {
return 0L;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyAuthMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyAuthMapper.java
deleted file mode 100644
index b1bf14ba..00000000
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyAuthMapper.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.zt.plat.module.databus.dal.mysql.gateway;
-
-import cn.hutool.core.util.StrUtil;
-import com.zt.plat.framework.common.pojo.PageResult;
-import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
-import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
-import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO;
-import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
-
-@Mapper
-public interface ApiPolicyAuthMapper extends BaseMapperX {
-
- default PageResult selectPage(ApiPolicyPageReqVO reqVO) {
- LambdaQueryWrapperX query = new LambdaQueryWrapperX<>();
- if (StrUtil.isNotBlank(reqVO.getKeyword())) {
- String keyword = reqVO.getKeyword();
- query.and(wrapper -> wrapper.like(ApiPolicyAuthDO::getName, keyword)
- .or().like(ApiPolicyAuthDO::getDescription, keyword));
- }
- query.eqIfPresent(ApiPolicyAuthDO::getType, reqVO.getType())
- .eq(ApiPolicyAuthDO::getDeleted, false)
- .orderByDesc(ApiPolicyAuthDO::getUpdateTime)
- .orderByDesc(ApiPolicyAuthDO::getId);
- return selectPage(reqVO, query);
- }
-
- default List selectSimpleList() {
- return selectList(new LambdaQueryWrapperX()
- .eq(ApiPolicyAuthDO::getDeleted, false)
- .orderByDesc(ApiPolicyAuthDO::getUpdateTime)
- .orderByDesc(ApiPolicyAuthDO::getId));
- }
-}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStepTypeEnum.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStepTypeEnum.java
index 589a49f1..28416514 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStepTypeEnum.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStepTypeEnum.java
@@ -10,9 +10,10 @@ import lombok.Getter;
@Getter
public enum ApiStepTypeEnum {
+ START,
HTTP,
RPC,
SCRIPT,
- FLOW;
+ END;
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ApiGatewayProperties.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ApiGatewayProperties.java
index ac37137a..036f41d3 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ApiGatewayProperties.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ApiGatewayProperties.java
@@ -1,10 +1,14 @@
package com.zt.plat.module.databus.framework.integration.config;
+import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.StringUtils;
import java.util.ArrayList;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
/**
* Configuration properties for the unified API portal.
@@ -13,24 +17,72 @@ import java.util.List;
@ConfigurationProperties(prefix = "databus.api-portal")
public class ApiGatewayProperties {
- private String basePath = "/api/portal";
+ public static final String DEFAULT_BASE_PATH = "/admin-api/databus/api/portal";
+ public static final String LEGACY_BASE_PATH = "/databus/api/portal";
+
+ private String basePath = DEFAULT_BASE_PATH;
+
+ public void setBasePath(String basePath) {
+ if (!StringUtils.hasText(basePath)) {
+ this.basePath = DEFAULT_BASE_PATH;
+ return;
+ }
+ String normalized = basePath.startsWith("/") ? basePath : "/" + basePath;
+ if (LEGACY_BASE_PATH.equals(normalized)) {
+ this.basePath = DEFAULT_BASE_PATH;
+ return;
+ }
+ this.basePath = normalized;
+ }
+
+ public List getAllBasePaths() {
+ Set candidates = new LinkedHashSet<>();
+ candidates.add(basePath);
+ candidates.add(DEFAULT_BASE_PATH);
+ candidates.add(LEGACY_BASE_PATH);
+ return new ArrayList<>(candidates);
+ }
private List allowedIps = new ArrayList<>();
private List deniedIps = new ArrayList<>();
- private boolean enableSignature = false;
+ private Security security = new Security();
- private String signatureHeader = "X-Signature";
+ private boolean enableTenantHeader = false;
- private String signatureSecret;
-
- private boolean enableTenantHeader = true;
-
- private String tenantHeader = "X-Tenant-Id";
+ private String tenantHeader = "ZT-Tenant-Id";
private boolean enableAudit = true;
private boolean enableRateLimit = true;
+ @Data
+ public static class Security {
+
+ private boolean enabled = true;
+
+ private String appIdHeader = "ZT-App-Id";
+
+ private String timestampHeader = "ZT-Timestamp";
+
+ private String nonceHeader = "ZT-Nonce";
+
+ private String signatureHeader = "ZT-Signature";
+
+ private String signatureType = CryptoSignatureUtils.SIGNATURE_TYPE_MD5;
+
+ private String encryptionType = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
+
+ private long allowedClockSkewSeconds = 300;
+
+ private long nonceTtlSeconds = 600;
+
+ private String nonceRedisKeyPrefix = "databus:gateway:nonce:";
+
+ private boolean requireBodyEncryption = true;
+
+ private boolean encryptResponse = true;
+ }
+
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/GatewayIntegrationConfiguration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/GatewayIntegrationConfiguration.java
index 9d75d547..b92aa746 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/GatewayIntegrationConfiguration.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/GatewayIntegrationConfiguration.java
@@ -1,47 +1,30 @@
package com.zt.plat.module.databus.framework.integration.config;
-import com.zt.plat.framework.common.exception.ServiceException;
-import com.zt.plat.module.databus.framework.integration.gateway.core.ApiFlowDispatcher;
-import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayRequestMapper;
+import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayExecutionService;
import com.zt.plat.module.databus.framework.integration.gateway.core.ErrorHandlingStrategy;
-import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
-import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewaySecurityFilter;
import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.http.dsl.Http;
-import org.springframework.integration.support.MessageBuilder;
-import org.springframework.messaging.Message;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-import org.springframework.util.StringUtils;
-
-import java.util.HashMap;
-import java.util.Map;
/**
* Configures the unified API portal inbound gateway and supporting beans.
*/
-@Slf4j
@Configuration
@EnableConfigurationProperties(ApiGatewayProperties.class)
@RequiredArgsConstructor
public class GatewayIntegrationConfiguration {
private final ApiGatewayProperties properties;
- private final ApiGatewayRequestMapper requestMapper;
- private final ObjectProvider apiFlowDispatcherProvider;
private final ErrorHandlingStrategy errorHandlingStrategy;
@Bean(name = "apiPortalTaskExecutor")
@@ -68,7 +51,7 @@ public class GatewayIntegrationConfiguration {
}
@Bean
- public IntegrationFlow apiGatewayInboundFlow() {
+ public IntegrationFlow apiGatewayInboundFlow(ApiGatewayExecutionService executionService) {
String pattern = properties.getBasePath() + "/{apiCode}/{version}";
return IntegrationFlow.from(Http.inboundGateway(pattern)
.requestMapping(spec -> spec
@@ -77,71 +60,9 @@ public class GatewayIntegrationConfiguration {
.requestPayloadType(String.class)
.mappedRequestHeaders("*")
.mappedResponseHeaders("*"))
- .handle(this, "mapRequest", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
- .handle(this, "dispatch", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
- .handle(this, "buildResponse", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
+ .handle(executionService, "mapRequest", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
+ .handle(executionService, "dispatch", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
+ .handle(executionService, "buildResponseEntity", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
.get();
}
-
- public Message mapRequest(Message> message) {
- ApiInvocationContext context = requestMapper.map(message.getPayload(), message.getHeaders());
- return MessageBuilder.withPayload(context)
- .copyHeaders(message.getHeaders())
- .setHeaderIfAbsent("apiCode", context.getApiCode())
- .setHeaderIfAbsent("version", context.getApiVersion())
- .build();
- }
-
- public ApiInvocationContext dispatch(Message message) {
- ApiInvocationContext context = message.getPayload();
- try {
- return apiFlowDispatcherProvider.getObject()
- .dispatch(context.getApiCode(), context.getApiVersion(), context);
- } catch (ServiceException ex) {
- handleServiceException(context, ex);
- log.warn("[API-PORTAL] ServiceException while dispatching apiCode={} version={}: {}", context.getApiCode(), context.getApiVersion(), ex.getMessage());
- return context;
- } catch (Exception ex) {
- handleUnexpectedException(context, ex);
- log.error("[API-PORTAL] Unexpected exception while dispatching apiCode={} version={}", context.getApiCode(), context.getApiVersion(), ex);
- return context;
- }
- }
-
- public ResponseEntity buildResponse(ApiInvocationContext context) {
- int status = context.getResponseStatus() != null ? context.getResponseStatus() : HttpStatus.OK.value();
- ApiGatewayResponse envelope = ApiGatewayResponse.builder()
- .code(status >= 200 && status < 400 ? "SUCCESS" : "ERROR")
- .message(StringUtils.hasText(context.getResponseMessage()) ? context.getResponseMessage() : HttpStatus.valueOf(status).getReasonPhrase())
- .data(context.getResponseBody())
- .traceId(context.getRequestId())
- .build();
- return ResponseEntity.status(status).body(envelope);
- }
-
- private void handleServiceException(ApiInvocationContext context, ServiceException ex) {
- String message = StringUtils.hasText(ex.getMessage()) ? ex.getMessage() : "API invocation failed";
- context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
- context.setResponseMessage(message);
- if (context.getResponseBody() == null) {
- Map body = new HashMap<>();
- if (ex.getCode() != null) {
- body.put("errorCode", ex.getCode());
- }
- body.put("errorMessage", message);
- context.setResponseBody(body);
- }
- }
-
- private void handleUnexpectedException(ApiInvocationContext context, Exception ex) {
- String message = StringUtils.hasText(ex.getMessage()) ? ex.getMessage() : "API invocation encountered an unexpected error";
- context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
- context.setResponseMessage(message);
- if (context.getResponseBody() == null) {
- Map body = new HashMap<>();
- body.put("errorMessage", message);
- body.put("exception", ex.getClass().getSimpleName());
- context.setResponseBody(body);
- }
- }
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowAssembler.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowAssembler.java
index a1d8c005..6da2de4c 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowAssembler.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowAssembler.java
@@ -9,8 +9,10 @@ import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDe
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor;
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpec;
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpecParser;
+import com.zt.plat.module.databus.framework.integration.gateway.expression.GatewayExpressionHelper;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
import com.zt.plat.module.databus.framework.integration.gateway.step.StepHandlerFactory;
+import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.aop.Advice;
@@ -27,17 +29,13 @@ import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
-import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_PARALLEL_FAILED;
-import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_PARALLEL_INTERRUPTED;
-import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_TRANSFORM_EVALUATION_FAILED;
-import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_TRANSFORM_RESPONSE_STATUS_INVALID;
+import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.*;
/**
- * Assembles dynamic integration flows per API definition.
+ * 按 API 定义装配动态集成流程。
*/
@Slf4j
@Component
@@ -58,7 +56,7 @@ public class ApiFlowAssembler {
IntegrationFlowBuilder builder = IntegrationFlow.from(MessageChannels.direct(inputChannelName)
.datatype(ApiInvocationContext.class)
.interceptor(monitoringInterceptor))
- .log(message -> String.format("[API-PORTAL] entering flow %s", flowId))
+ .log(message -> String.format("[API-PORTAL] 进入流程 %s", flowId))
.handle(ApiInvocationContext.class,
applyTransforms(aggregate, TransformPhaseEnum.REQUEST_PRE),
endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()));
@@ -88,6 +86,10 @@ public class ApiFlowAssembler {
}
private GenericHandler applyTransforms(ApiDefinitionAggregate aggregate, TransformPhaseEnum phase) {
+ boolean mutateRequest = phase == TransformPhaseEnum.REQUEST_PRE || phase == TransformPhaseEnum.REQUEST_POST;
+ boolean mutateResponse = phase == TransformPhaseEnum.RESPONSE_PRE
+ || phase == TransformPhaseEnum.RESPONSE_POST
+ || phase == TransformPhaseEnum.ERROR;
return (payload, headers) -> {
var transformDefinition = aggregate.getApiLevelTransforms().get(phase.name());
if (transformDefinition != null && StringUtils.hasText(transformDefinition.getExpression())) {
@@ -95,7 +97,7 @@ public class ApiFlowAssembler {
ExpressionSpec spec = ExpressionSpecParser.parse(rawExpression, ExpressionTypeEnum.JSON);
try {
Object result = expressionExecutor.evaluate(spec, payload, payload.getRequestBody(), headers);
- applyTransformResult(payload, result);
+ GatewayExpressionHelper.applyContextMutations(payload, result, mutateRequest, mutateResponse);
} catch (Exception ex) {
if (ex instanceof ServiceException serviceException) {
throw serviceException;
@@ -107,48 +109,6 @@ public class ApiFlowAssembler {
};
}
- private void applyTransformResult(ApiInvocationContext context, Object result) {
- if (!(result instanceof Map, ?> map)) {
- return;
- }
- Object headerUpdates = map.get("requestHeaders");
- if (headerUpdates instanceof Map, ?> headerMap) {
- headerMap.forEach((key, value) -> context.getRequestHeaders().put(String.valueOf(key), value));
- }
- Object variableUpdates = map.get("variables");
- if (variableUpdates instanceof Map, ?> variables) {
- variables.forEach((key, value) -> context.getVariables().put(String.valueOf(key), value));
- }
- Object attributeUpdates = map.get("attributes");
- if (attributeUpdates instanceof Map, ?> attributes) {
- attributes.forEach((key, value) -> context.getAttributes().put(String.valueOf(key), value));
- }
- if (map.containsKey("responseBody")) {
- context.setResponseBody(map.get("responseBody"));
- }
- if (map.containsKey("responseStatus")) {
- context.setResponseStatus(asInteger(map.get("responseStatus")));
- }
- if (map.containsKey("responseMessage")) {
- Object message = map.get("responseMessage");
- context.setResponseMessage(message == null ? null : String.valueOf(message));
- }
- }
-
- private Integer asInteger(Object value) {
- if (value == null) {
- return null;
- }
- if (value instanceof Number number) {
- return number.intValue();
- }
- try {
- return Integer.parseInt(String.valueOf(value));
- } catch (NumberFormatException ex) {
- throw ServiceExceptionUtil.exception(API_TRANSFORM_RESPONSE_STATUS_INVALID, value);
- }
- }
-
private IntegrationFlowBuilder applySequential(IntegrationFlowBuilder builder, ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) {
GenericHandler handler = stepHandlerFactory.build(aggregate, stepDefinition);
return builder.handle(ApiInvocationContext.class, handler, endpoint -> {
@@ -238,6 +198,7 @@ public class ApiFlowAssembler {
private interface FlowSegment {
}
+ @Getter
private static final class SequentialSegment implements FlowSegment {
private final ApiStepDefinition step;
@@ -245,11 +206,9 @@ public class ApiFlowAssembler {
this.step = step;
}
- public ApiStepDefinition getStep() {
- return step;
- }
}
+ @Getter
private static final class ParallelSegment implements FlowSegment {
private final String group;
private final List steps;
@@ -259,12 +218,5 @@ public class ApiFlowAssembler {
this.steps = steps;
}
- public String getGroup() {
- return group;
- }
-
- public List getSteps() {
- return steps;
- }
}
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowDispatcher.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowDispatcher.java
index 2484aaa2..5ff7a99a 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowDispatcher.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowDispatcher.java
@@ -13,7 +13,8 @@ import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErro
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_FLOW_NO_REPLY;
/**
- * Dispatches API invocation contexts to the appropriate integration flow.
+ * api 分发.
+ * @author chenbowen
*/
@Component
@RequiredArgsConstructor
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowRegistration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowRegistration.java
index cdd57f1c..1c83f018 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowRegistration.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowRegistration.java
@@ -6,6 +6,7 @@ import org.springframework.integration.dsl.IntegrationFlow;
/**
* Metadata returned by the assembler for flow registration.
+ * @author chenbowen
*/
@Value
@Builder
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayErrorProcessor.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayErrorProcessor.java
new file mode 100644
index 00000000..e7b9dbcb
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayErrorProcessor.java
@@ -0,0 +1,97 @@
+package com.zt.plat.module.databus.framework.integration.gateway.core;
+
+import com.zt.plat.framework.common.exception.ServiceException;
+import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_AUTH_UNAUTHORIZED;
+import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EXCEEDED;
+
+/**
+ * Shared error processor that maps exceptions into {@link ApiInvocationContext} responses.
+ */
+@Component
+public class ApiGatewayErrorProcessor {
+
+ /**
+ * Applies the given {@link Throwable} to the context, unwrapping {@link ServiceException}
+ * instances when present and falling back to a generic error payload otherwise.
+ */
+ public void apply(ApiInvocationContext context, Throwable throwable) {
+ ServiceException serviceException = resolveServiceException(throwable);
+ if (serviceException != null) {
+ applyServiceException(context, serviceException);
+ } else {
+ applyUnexpectedException(context, throwable);
+ }
+ }
+
+ public void applyServiceException(ApiInvocationContext context, ServiceException ex) {
+ String message = StringUtils.hasText(ex.getMessage()) ? ex.getMessage() : "API invocation failed";
+ context.setResponseStatus(resolveHttpStatus(ex, context));
+ context.setResponseMessage(message);
+ if (context.getResponseBody() == null) {
+ Map body = new HashMap<>();
+ if (ex.getCode() != null) {
+ body.put("errorCode", ex.getCode());
+ }
+ body.put("errorMessage", message);
+ context.setResponseBody(body);
+ }
+ }
+
+ public void applyUnexpectedException(ApiInvocationContext context, Throwable throwable) {
+ String message = determineUnexpectedMessage(throwable);
+ context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+ context.setResponseMessage(message);
+ if (context.getResponseBody() == null) {
+ Map body = new HashMap<>();
+ body.put("errorMessage", message);
+ body.put("exception", throwable.getClass().getSimpleName());
+ context.setResponseBody(body);
+ }
+ }
+
+ public ServiceException resolveServiceException(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof ServiceException serviceException) {
+ return serviceException;
+ }
+ current = current.getCause();
+ }
+ return null;
+ }
+
+ private int resolveHttpStatus(ServiceException ex, ApiInvocationContext context) {
+ if (context.getResponseStatus() != null) {
+ return context.getResponseStatus();
+ }
+ Integer code = ex.getCode();
+ if (code != null) {
+ if (API_AUTH_UNAUTHORIZED.getCode().equals(code)) {
+ return HttpStatus.UNAUTHORIZED.value();
+ }
+ if (API_RATE_LIMIT_EXCEEDED.getCode().equals(code)) {
+ return HttpStatus.TOO_MANY_REQUESTS.value();
+ }
+ }
+ return HttpStatus.INTERNAL_SERVER_ERROR.value();
+ }
+
+ private String determineUnexpectedMessage(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (StringUtils.hasText(current.getMessage())) {
+ return current.getMessage();
+ }
+ current = current.getCause();
+ }
+ return "API invocation encountered an unexpected error";
+ }
+}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java
new file mode 100644
index 00000000..d84dea8d
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java
@@ -0,0 +1,286 @@
+package com.zt.plat.module.databus.framework.integration.gateway.core;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zt.plat.framework.common.exception.ServiceException;
+import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
+import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
+import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
+import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
+import com.zt.plat.module.databus.framework.integration.gateway.security.GatewayJwtResolver;
+import com.zt.plat.module.databus.framework.integration.gateway.security.GatewaySecurityFilter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.*;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Array;
+import java.net.URI;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Orchestrates API portal request mapping, dispatch and response building so that
+ * management-side debug invocations and external HTTP requests share identical
+ * behaviour (other than security concerns handled by {@link GatewaySecurityFilter}).
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ApiGatewayExecutionService {
+
+ private static final String HEADER_REQUEST_HEADERS = org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders";
+ private static final String HEADER_REQUEST_URI = org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri";
+ private static final String HEADER_REQUEST_PARAMS = org.springframework.integration.http.HttpHeaders.PREFIX + "requestParams";
+ private static final String HEADER_QUERY_STRING = org.springframework.integration.http.HttpHeaders.PREFIX + "queryString";
+
+ private final ApiGatewayRequestMapper requestMapper;
+ private final ApiFlowDispatcher apiFlowDispatcher;
+ private final ApiGatewayErrorProcessor errorProcessor;
+ private final ApiGatewayProperties properties;
+ private final ObjectMapper objectMapper;
+
+ /**
+ * Maps a raw HTTP message (as provided by Spring Integration) into a context message.
+ */
+ public Message mapRequest(Message> message) {
+ ApiInvocationContext context = requestMapper.map(message.getPayload(), message.getHeaders());
+ return MessageBuilder.withPayload(context)
+ .copyHeaders(message.getHeaders())
+ .setHeaderIfAbsent("apiCode", context.getApiCode())
+ .setHeaderIfAbsent("version", context.getApiVersion())
+ .build();
+ }
+
+ /**
+ * Dispatches the API invocation and applies gateway error processing rules on failure scenarios.
+ */
+ public ApiInvocationContext dispatch(Message message) {
+ ApiInvocationContext context = message.getPayload();
+ try {
+ return apiFlowDispatcher.dispatch(context.getApiCode(), context.getApiVersion(), context);
+ } catch (ServiceException ex) {
+ errorProcessor.applyServiceException(context, ex);
+ log.warn("[API-PORTAL] 分发 apiCode={} version={} 时出现 ServiceException: {}", context.getApiCode(), context.getApiVersion(), ex.getMessage());
+ return context;
+ } catch (Exception ex) {
+ ServiceException nestedServiceException = errorProcessor.resolveServiceException(ex);
+ if (nestedServiceException != null) {
+ errorProcessor.applyServiceException(context, nestedServiceException);
+ log.warn("[API-PORTAL] 分发 apiCode={} version={} 时出现 ServiceException(包装异常): {}", context.getApiCode(), context.getApiVersion(), nestedServiceException.getMessage());
+ if (log.isDebugEnabled()) {
+ log.debug("[API-PORTAL] 包装异常堆栈", ex);
+ }
+ } else {
+ errorProcessor.applyUnexpectedException(context, ex);
+ log.error("[API-PORTAL] 分发 apiCode={} version={} 时出现未预期异常", context.getApiCode(), context.getApiVersion(), ex);
+ }
+ return context;
+ }
+ }
+
+ /**
+ * Builds a HTTP response entity for the external gateway flow.
+ */
+ public ResponseEntity buildResponseEntity(ApiInvocationContext context) {
+ int status = resolveStatus(context);
+ ApiGatewayResponse envelope = buildResponseEnvelope(context, status);
+ return ResponseEntity.status(status).body(envelope);
+ }
+
+ /**
+ * Executes a debug invocation by reusing the same mapping/dispatch pipeline as the public gateway.
+ */
+ public ResponseEntity invokeForDebug(ApiGatewayInvokeReqVO reqVO) {
+ Message> rawMessage = buildDebugMessage(reqVO);
+ Message mappedMessage = mapRequest(rawMessage);
+ ApiInvocationContext context = mappedMessage.getPayload();
+ // Ensure query parameters & headers from debug payload are reflected after mapping.
+ mergeDebugMetadata(context, reqVO);
+ ApiInvocationContext responseContext = dispatch(mappedMessage);
+ return buildResponseEntity(responseContext);
+ }
+
+ private Message> buildDebugMessage(ApiGatewayInvokeReqVO reqVO) {
+ Object payload = preparePayload(reqVO.getPayload());
+ MessageBuilder