diff --git a/sql/dm/字典导入菜单权限_DM8.sql b/sql/dm/字典导入菜单权限_DM8.sql
new file mode 100644
index 00000000..bea0a1d2
--- /dev/null
+++ b/sql/dm/字典导入菜单权限_DM8.sql
@@ -0,0 +1,15 @@
+-- DM8 字典导入按钮权限脚本
+-- 幂等处理:清理旧的导入权限按钮,再重新写入
+
+DELETE FROM system_role_menu WHERE menu_id = 103001;
+DELETE FROM system_menu WHERE id = 103001;
+
+INSERT INTO system_menu (
+ id, name, permission, type, sort, parent_id, path, icon, component, component_name,
+ status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted
+) VALUES (
+ 103001, '字典导入', 'system:dict:import', 3, 6, 105, '#', '#', '', NULL,
+ 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'
+);
+
+-- 如需同步给指定角色,请手工向 system_role_menu 插入对应关系记录
diff --git a/zt-dependencies/pom.xml b/zt-dependencies/pom.xml
index a89588ec..f283b9a4 100644
--- a/zt-dependencies/pom.xml
+++ b/zt-dependencies/pom.xml
@@ -86,6 +86,8 @@
4.1.116.Final
1.2.5
0.9.0
+
+ 2.15.1
4.5.13
2.17.0
@@ -661,6 +663,13 @@
+
+
+ com.yomahub
+ liteflow-spring-boot-starter
+ ${liteflow.version}
+
+
org.pf4j
diff --git a/zt-framework/zt-spring-boot-starter-excel/src/main/java/com/zt/plat/framework/excel/core/util/ExcelUtils.java b/zt-framework/zt-spring-boot-starter-excel/src/main/java/com/zt/plat/framework/excel/core/util/ExcelUtils.java
index 46ffd576..9d23b05c 100644
--- a/zt-framework/zt-spring-boot-starter-excel/src/main/java/com/zt/plat/framework/excel/core/util/ExcelUtils.java
+++ b/zt-framework/zt-spring-boot-starter-excel/src/main/java/com/zt/plat/framework/excel/core/util/ExcelUtils.java
@@ -49,4 +49,11 @@ public class ExcelUtils {
.doReadAllSync();
}
+ public static List read(MultipartFile file, Class head, int sheetNo) throws IOException {
+ return EasyExcel.read(file.getInputStream(), head, null)
+ .autoCloseStream(false)
+ .sheet(sheetNo)
+ .doReadSync();
+ }
+
}
diff --git a/zt-gateway/src/main/resources/application.yaml b/zt-gateway/src/main/resources/application.yaml
index 2d837261..4c2b9a32 100644
--- a/zt-gateway/src/main/resources/application.yaml
+++ b/zt-gateway/src/main/resources/application.yaml
@@ -221,6 +221,13 @@ spring:
- Path=/admin-api/databus/**
filters:
- RewritePath=/admin-api/databus/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
+ ## rule-server 服务
+ - id: rule-admin-api # 路由的编号
+ uri: grayLb://rule-server
+ predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
+ - Path=/admin-api/rule/**
+ filters:
+ - RewritePath=/admin-api/rule/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
x-forwarded:
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStatusEnum.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStatusEnum.java
index 76ffda64..28b2d5a9 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStatusEnum.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStatusEnum.java
@@ -10,12 +10,13 @@ import lombok.Getter;
@Getter
public enum ApiStatusEnum {
- DRAFT(0),
- ONLINE(1),
- OFFLINE(2),
- DEPRECATED(3);
+ DRAFT(0, "草稿"),
+ ONLINE(1, "已上线"),
+ OFFLINE(2, "已下线"),
+ DEPRECATED(3, "已废弃");
private final int status;
+ private final String label;
public static boolean isOnline(Integer status) {
return status != null && status == ONLINE.status;
@@ -25,4 +26,21 @@ public enum ApiStatusEnum {
return status != null && status == DEPRECATED.status;
}
+ public static ApiStatusEnum fromStatus(Integer status) {
+ if (status == null) {
+ return null;
+ }
+ for (ApiStatusEnum value : values()) {
+ if (value.status == status) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ public static String labelOf(Integer status) {
+ ApiStatusEnum value = fromStatus(status);
+ return value != null ? value.label : "未知";
+ }
+
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/config/GatewayWebClientConfiguration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/config/GatewayWebClientConfiguration.java
index 2945f5ed..8113e1a3 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/config/GatewayWebClientConfiguration.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/config/GatewayWebClientConfiguration.java
@@ -4,21 +4,46 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.resources.ConnectionProvider;
+
+import java.time.Duration;
@Configuration
public class GatewayWebClientConfiguration {
private final int maxInMemorySize;
+ private final long maxIdleTimeMillis;
+ private final long evictInBackgroundMillis;
+ private final ReactorClientHttpConnector httpConnector;
public GatewayWebClientConfiguration(
- @Value("${databus.gateway.web-client.max-in-memory-size:20971520}") int maxInMemorySize) {
+ @Value("${databus.gateway.web-client.max-in-memory-size:20971520}") int maxInMemorySize,
+ @Value("${databus.gateway.web-client.max-idle-time:45000}") long maxIdleTimeMillis,
+ @Value("${databus.gateway.web-client.evict-in-background-interval:20000}") long evictInBackgroundMillis) {
this.maxInMemorySize = maxInMemorySize;
+ this.maxIdleTimeMillis = maxIdleTimeMillis > 0 ? maxIdleTimeMillis : 45000L;
+ this.evictInBackgroundMillis = Math.max(evictInBackgroundMillis, 0L);
+ this.httpConnector = buildConnector();
}
@Bean
public WebClientCustomizer gatewayWebClientCustomizer() {
- return builder -> builder.codecs(configurer ->
- configurer.defaultCodecs().maxInMemorySize(maxInMemorySize));
+ return builder -> builder
+ .clientConnector(httpConnector)
+ .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(maxInMemorySize));
+ }
+
+ private ReactorClientHttpConnector buildConnector() {
+ ConnectionProvider.Builder providerBuilder = ConnectionProvider.builder("databus-gateway")
+ .maxIdleTime(Duration.ofMillis(maxIdleTimeMillis));
+ if (evictInBackgroundMillis > 0) {
+ providerBuilder.evictInBackground(Duration.ofMillis(evictInBackgroundMillis));
+ }
+ ConnectionProvider provider = providerBuilder.build();
+ HttpClient httpClient = HttpClient.create(provider).compress(true);
+ return new ReactorClientHttpConnector(httpClient);
}
}
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 ce07a981..3c07d9c4 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
@@ -1,6 +1,7 @@
package com.zt.plat.module.databus.framework.integration.gateway.core;
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
+import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
import lombok.RequiredArgsConstructor;
import org.springframework.integration.core.MessagingTemplate;
@@ -36,6 +37,24 @@ public class ApiFlowDispatcher {
return (ApiInvocationContext) reply.getPayload();
}
+ public ApiInvocationContext dispatchWithAggregate(ApiDefinitionAggregate aggregate, ApiInvocationContext context) {
+ IntegrationFlowManager.DebugFlowHandle handle = integrationFlowManager.obtainDebugHandle(aggregate);
+ MessageChannel channel = handle.channel();
+ Message message = MessageBuilder.withPayload(context)
+ .setHeader("apiCode", aggregate.getDefinition().getApiCode())
+ .setHeader("version", aggregate.getDefinition().getVersion())
+ .build();
+ try {
+ Message> reply = messagingTemplate.sendAndReceive(channel, message);
+ if (reply == null) {
+ throw ServiceExceptionUtil.exception(API_FLOW_NO_REPLY, aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
+ }
+ return (ApiInvocationContext) reply.getPayload();
+ } finally {
+ integrationFlowManager.releaseDebugHandle(handle);
+ }
+ }
+
private MessageChannel requireInputChannel(String apiCode, String version) {
// 未命中时,进行一次兜底补偿查询
return integrationFlowManager.locateInputChannel(apiCode, version)
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
index 1c593897..f84e5fdc 100644
--- 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
@@ -3,12 +3,15 @@ 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.framework.common.exception.util.ServiceExceptionUtil;
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.domain.ApiDefinitionAggregate;
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 com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
@@ -23,6 +26,9 @@ import java.lang.reflect.Array;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Optional;
+
+import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_DEFINITION_NOT_FOUND;
/**
* Orchestrates API portal request mapping, dispatch and response building so that
@@ -40,6 +46,7 @@ public class ApiGatewayExecutionService {
private static final String HEADER_QUERY_STRING = org.springframework.integration.http.HttpHeaders.PREFIX + "queryString";
private static final String HEADER_REMOTE_ADDRESS = org.springframework.integration.http.HttpHeaders.PREFIX + "remoteAddress";
private static final String LOCAL_DEBUG_REMOTE_ADDRESS = "127.0.0.1";
+ private static final String ATTR_DEBUG_INVOKE = "gatewayDebugInvoke";
private final ApiGatewayRequestMapper requestMapper;
private final ApiFlowDispatcher apiFlowDispatcher;
@@ -47,6 +54,7 @@ public class ApiGatewayExecutionService {
private final ApiGatewayProperties properties;
private final ObjectMapper objectMapper;
private final ApiGatewayAccessLogger accessLogger;
+ private final ApiDefinitionService apiDefinitionService;
/**
* Maps a raw HTTP message (as provided by Spring Integration) into a context message.
@@ -67,8 +75,16 @@ public class ApiGatewayExecutionService {
ApiInvocationContext context = message.getPayload();
accessLogger.onRequest(context);
ApiInvocationContext responseContext;
+ ApiDefinitionAggregate debugAggregate = null;
try {
- responseContext = apiFlowDispatcher.dispatch(context.getApiCode(), context.getApiVersion(), context);
+ if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
+ debugAggregate = resolveDebugAggregate(context);
+ }
+ if (debugAggregate != null) {
+ responseContext = apiFlowDispatcher.dispatchWithAggregate(debugAggregate, context);
+ } else {
+ responseContext = apiFlowDispatcher.dispatch(context.getApiCode(), context.getApiVersion(), context);
+ }
} catch (ServiceException ex) {
errorProcessor.applyServiceException(context, ex);
accessLogger.onException(context, ex);
@@ -113,10 +129,20 @@ public class ApiGatewayExecutionService {
ApiInvocationContext context = mappedMessage.getPayload();
// Ensure query parameters & headers from debug payload are reflected after mapping.
mergeDebugMetadata(context, reqVO);
+ context.getAttributes().put(ATTR_DEBUG_INVOKE, Boolean.TRUE);
ApiInvocationContext responseContext = dispatch(mappedMessage);
return buildResponseEntity(responseContext);
}
+ private ApiDefinitionAggregate resolveDebugAggregate(ApiInvocationContext context) {
+ Optional activeDefinition = apiDefinitionService.findByCodeAndVersion(context.getApiCode(), context.getApiVersion());
+ if (activeDefinition.isPresent()) {
+ return activeDefinition.get();
+ }
+ return apiDefinitionService.findByCodeAndVersionIncludingInactive(context.getApiCode(), context.getApiVersion())
+ .orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND));
+ }
+
private Message> buildDebugMessage(ApiGatewayInvokeReqVO reqVO) {
Object payload = preparePayload(reqVO.getPayload());
MessageBuilder