diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql
index 12a37c4f..73b29247 100644
--- a/sql/dm/ruoyi-vue-pro-dm8.sql
+++ b/sql/dm/ruoyi-vue-pro-dm8.sql
@@ -1625,7 +1625,7 @@ INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon
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 (100, '用户管理', 'system:user:list', 2, 1, 1, 'user', 'ep:avatar', 'system/user/index', 'SystemUser', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2025-03-15 21:30:41', '0');
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 (101, '角色管理', '', 2, 2, 1, 'role', 'ep:user', 'system/role/index', 'SystemRole', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-05-01 18:35:29', '0');
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 (102, '菜单管理', '', 2, 3, 1, 'menu', 'ep:menu', 'system/menu/index', 'SystemMenu', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:03:50', '0');
-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 (103, '部门管理', '', 2, 4, 1, 'dept', 'fa:address-card', 'system/dept/index', 'SystemDept', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:28', '0');
+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 (103, '组织管理', '', 2, 4, 1, 'dept', 'fa:address-card', 'system/dept/index', 'SystemDept', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:28', '0');
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 (104, '岗位管理', '', 2, 5, 1, 'post', 'fa:address-book-o', 'system/post/index', 'SystemPost', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:39', '0');
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 (105, '字典管理', '', 2, 6, 1, 'dict', 'ep:collection', 'system/dict/index', 'SystemDictType', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:07:12', '0');
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 (106, '配置管理', '', 2, 8, 2, 'config', 'fa:connectdevelop', 'infra/config/index', 'InfraConfig', 0, '1', '1', '1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:02:45', '0');
diff --git a/sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql b/sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql
new file mode 100644
index 00000000..ab9dcc19
--- /dev/null
+++ b/sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql
@@ -0,0 +1,45 @@
+/*
+ * Databus API 凭证绑定与访问日志补充字段(DM8)
+ * Generated on 2025-12-09
+ */
+
+-- ----------------------------
+-- Table structure for databus_api_definition_credential
+-- ----------------------------
+CREATE TABLE databus_api_definition_credential (
+ id BIGINT NOT NULL PRIMARY KEY,
+ tenant_id BIGINT NOT NULL DEFAULT 0,
+ api_id BIGINT NOT NULL,
+ credential_id BIGINT NOT NULL,
+ app_id VARCHAR(128),
+ creator VARCHAR(64) DEFAULT '' NOT NULL,
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ updater VARCHAR(64) DEFAULT '' NOT NULL,
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ deleted BIT DEFAULT '0' NOT NULL
+);
+
+CREATE UNIQUE INDEX uk_databus_api_definition_credential ON databus_api_definition_credential (api_id, credential_id, deleted);
+CREATE INDEX idx_databus_api_definition_credential_api ON databus_api_definition_credential (api_id);
+CREATE INDEX idx_databus_api_definition_credential_cred ON databus_api_definition_credential (credential_id);
+
+COMMENT ON TABLE databus_api_definition_credential IS 'Databus API 凭证绑定表';
+COMMENT ON COLUMN databus_api_definition_credential.id IS '主键 ID';
+COMMENT ON COLUMN databus_api_definition_credential.tenant_id IS '租户编号';
+COMMENT ON COLUMN databus_api_definition_credential.api_id IS 'API 定义 ID';
+COMMENT ON COLUMN databus_api_definition_credential.credential_id IS '凭证 ID';
+COMMENT ON COLUMN databus_api_definition_credential.app_id IS '凭证应用标识冗余';
+COMMENT ON COLUMN databus_api_definition_credential.creator IS '创建者';
+COMMENT ON COLUMN databus_api_definition_credential.create_time IS '创建时间';
+COMMENT ON COLUMN databus_api_definition_credential.updater IS '更新者';
+COMMENT ON COLUMN databus_api_definition_credential.update_time IS '更新时间';
+COMMENT ON COLUMN databus_api_definition_credential.deleted IS '逻辑删除标记';
+
+-- ----------------------------
+-- Alter databus_api_access_log add credential columns
+-- ----------------------------
+ALTER TABLE databus_api_access_log ADD credential_app_id VARCHAR(128);
+COMMENT ON COLUMN databus_api_access_log.credential_app_id IS '调用凭证应用标识';
+
+ALTER TABLE databus_api_access_log ADD credential_id BIGINT;
+COMMENT ON COLUMN databus_api_access_log.credential_id IS '调用凭证 ID';
diff --git a/zt-dependencies/pom.xml b/zt-dependencies/pom.xml
index cf5b81fe..b0a132d9 100644
--- a/zt-dependencies/pom.xml
+++ b/zt-dependencies/pom.xml
@@ -32,8 +32,6 @@
3.4.5
2024.0.1
2023.0.3.2
-
- 2.4.0
2.8.3
4.6.0
@@ -89,8 +87,6 @@
1.2.5
0.9.0
4.12.0
- 11.4.7
- 11.4.7
2.15.1
4.5.13
@@ -136,20 +132,6 @@
import
-
-
-
-
- org.apache.seata
- seata-all
- ${seata.version}
-
-
- org.apache.seata
- seata-spring-boot-starter
- ${seata.version}
-
-
io.github.mouzt
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiDefinitionConvert.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiDefinitionConvert.java
index cc2736bd..3bc46e3d 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiDefinitionConvert.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiDefinitionConvert.java
@@ -7,6 +7,7 @@ import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.*;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO;
+import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiCredentialBinding;
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiFlowPublication;
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
@@ -18,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
@Mapper
@@ -48,6 +50,11 @@ public interface ApiDefinitionConvert {
detail.setApiLevelTransforms(convertTransforms(aggregate.getDefinition().getId(), aggregate.getApiLevelTransforms().values()));
detail.setSteps(convertSteps(aggregate.getSteps()));
detail.setPublication(convert(aggregate.getPublication()));
+ detail.setCredentialBindings(convertCredentialBindings(aggregate.getCredentialBindings()));
+ detail.setCredentialIds(detail.getCredentialBindings().stream()
+ .map(ApiCredentialBindingRespVO::getCredentialId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
return detail;
}
@@ -99,6 +106,15 @@ public interface ApiDefinitionConvert {
return publication == null ? null : BeanUtils.toBean(publication, ApiDefinitionPublicationRespVO.class);
}
+ default List convertCredentialBindings(List bindings) {
+ if (CollUtil.isEmpty(bindings)) {
+ return new ArrayList<>();
+ }
+ return bindings.stream()
+ .map(binding -> BeanUtils.toBean(binding, ApiCredentialBindingRespVO.class))
+ .collect(Collectors.toList());
+ }
+
/**
* 转换步骤列表(DO -> SaveReqVO)
*/
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/accesslog/ApiAccessLogPageReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/accesslog/ApiAccessLogPageReqVO.java
index 7d012a93..879304a6 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/accesslog/ApiAccessLogPageReqVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/accesslog/ApiAccessLogPageReqVO.java
@@ -47,6 +47,12 @@ public class ApiAccessLogPageReqVO extends PageParam {
@Schema(description = "请求路径", example = "/gateway/api/user/query")
private String requestPath;
+ @Schema(description = "应用标识", example = "app-portal-01")
+ private String credentialAppId;
+
+ @Schema(description = "凭证主键", example = "10086")
+ private Long credentialId;
+
@Schema(description = "请求时间区间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] requestTime;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/accesslog/ApiAccessLogRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/accesslog/ApiAccessLogRespVO.java
index 00126a9a..a439724c 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/accesslog/ApiAccessLogRespVO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/accesslog/ApiAccessLogRespVO.java
@@ -33,6 +33,12 @@ public class ApiAccessLogRespVO {
@Schema(description = "请求路径", example = "/gateway/api/user/query")
private String requestPath;
+ @Schema(description = "应用标识", example = "app-portal-01")
+ private String credentialAppId;
+
+ @Schema(description = "凭证主键", example = "10086")
+ private Long credentialId;
+
@Schema(description = "查询参数(JSON)")
private String requestQuery;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiCredentialBindingRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiCredentialBindingRespVO.java
new file mode 100644
index 00000000..1eb4f7ed
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiCredentialBindingRespVO.java
@@ -0,0 +1,17 @@
+package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class ApiCredentialBindingRespVO {
+
+ @Schema(description = "凭证主键", example = "10086")
+ private Long credentialId;
+
+ @Schema(description = "应用标识", example = "app-portal-01")
+ private String appId;
+
+ @Schema(description = "应用名称")
+ private String appName;
+}
\ No newline at end of file
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 c64ba1ec..37384ad2 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
@@ -53,6 +53,12 @@ public class ApiDefinitionDetailRespVO {
@Schema(description = "API 级别变换列表")
private List apiLevelTransforms = new ArrayList<>();
+ @Schema(description = "授权凭证 ID 列表")
+ private List credentialIds = new ArrayList<>();
+
+ @Schema(description = "授权凭证详情列表")
+ private List credentialBindings = new ArrayList<>();
+
@Schema(description = "步骤列表")
private List steps = 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/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 afd54dca..a0375294 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
@@ -46,6 +46,9 @@ public class ApiDefinitionSaveReqVO {
@Valid
private List apiLevelTransforms = new ArrayList<>();
+ @Schema(description = "授权的客户端凭证 ID 列表")
+ private List credentialIds = new ArrayList<>();
+
@Schema(description = "步骤列表")
@NotEmpty(message = "编排步骤不能为空")
@Valid
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiAccessLogDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiAccessLogDO.java
index f6ab6f90..e46b5507 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiAccessLogDO.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiAccessLogDO.java
@@ -52,6 +52,16 @@ public class ApiAccessLogDO extends TenantBaseDO {
*/
private String requestPath;
+ /**
+ * 调用使用的应用标识
+ */
+ private String credentialAppId;
+
+ /**
+ * 调用使用的凭证主键
+ */
+ private Long credentialId;
+
/**
* 查询参数(JSON 字符串)
*/
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionCredentialDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionCredentialDO.java
new file mode 100644
index 00000000..bfd11c89
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionCredentialDO.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 与客户端凭证的授权关联。
+ */
+@Data
+@TableName("databus_api_definition_credential")
+@KeySequence("databus_api_definition_credential_seq")
+@EqualsAndHashCode(callSuper = true)
+public class ApiDefinitionCredentialDO extends BaseDO {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * API 主键
+ */
+ private Long apiId;
+
+ /**
+ * 客户端凭证主键
+ */
+ private Long credentialId;
+
+ /**
+ * 绑定时的应用标识冗余,便于快速校验
+ */
+ private String appId;
+}
\ No newline at end of file
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiAccessLogMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiAccessLogMapper.java
index 8d58239b..8fe6d4d9 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiAccessLogMapper.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiAccessLogMapper.java
@@ -20,6 +20,8 @@ public interface ApiAccessLogMapper extends BaseMapperX {
.eqIfPresent(ApiAccessLogDO::getResponseStatus, reqVO.getResponseStatus())
.eqIfPresent(ApiAccessLogDO::getStatus, reqVO.getStatus())
.likeIfPresent(ApiAccessLogDO::getClientIp, reqVO.getClientIp())
+ .eqIfPresent(ApiAccessLogDO::getCredentialAppId, reqVO.getCredentialAppId())
+ .eqIfPresent(ApiAccessLogDO::getCredentialId, reqVO.getCredentialId())
.eqIfPresent(ApiAccessLogDO::getTenantId, reqVO.getTenantId())
.likeIfPresent(ApiAccessLogDO::getRequestPath, reqVO.getRequestPath());
if (ArrayUtil.isNotEmpty(reqVO.getRequestTime()) && reqVO.getRequestTime().length == 2) {
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionCredentialMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionCredentialMapper.java
new file mode 100644
index 00000000..6ed1552d
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionCredentialMapper.java
@@ -0,0 +1,32 @@
+package com.zt.plat.module.databus.dal.mysql.gateway;
+
+import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
+import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionCredentialDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ApiDefinitionCredentialMapper extends BaseMapperX {
+
+ default List selectByApiId(Long apiId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(ApiDefinitionCredentialDO::getApiId, apiId));
+ }
+
+ default void deleteByApiId(Long apiId) {
+ delete(new LambdaQueryWrapperX()
+ .eq(ApiDefinitionCredentialDO::getApiId, apiId));
+ }
+
+ /**
+ * 按 API 逻辑删除已有绑定,保留操作记录。
+ */
+ default void logicDeleteByApiId(Long apiId) {
+ ApiDefinitionCredentialDO entity = new ApiDefinitionCredentialDO();
+ entity.setDeleted(Boolean.TRUE);
+ update(entity, new LambdaQueryWrapperX()
+ .eq(ApiDefinitionCredentialDO::getApiId, apiId));
+ }
+}
\ No newline at end of file
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java
index bce32b79..b0ed9987 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java
@@ -83,7 +83,7 @@ public class ApiGatewayAccessLogger {
try {
ApiAccessLogDO update = new ApiAccessLogDO();
update.setId(logId);
- int responseStatus = resolveHttpStatus(context);
+ Integer responseStatus = resolveHttpStatus(context);
context.setResponseStatus(responseStatus);
update.setResponseStatus(responseStatus);
String responseMessage = resolveResponseMessage(context, responseStatus);
@@ -193,6 +193,8 @@ public class ApiGatewayAccessLogger {
logDO.setRequestBody(toJson(context.getRequestBody()));
logDO.setClientIp(context.getClientIp());
logDO.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
+ logDO.setCredentialAppId(context.getCredentialAppId());
+ logDO.setCredentialId(context.getCredentialId());
logDO.setStatus(3);
logDO.setRequestTime(toLocalDateTime(context.getRequestTime()));
logDO.setTenantId(parseTenantId(context.getTenantId()));
@@ -231,7 +233,7 @@ public class ApiGatewayAccessLogger {
return 3;
}
- private String resolveErrorMessage(ApiInvocationContext context, int responseStatus) {
+ private String resolveErrorMessage(ApiInvocationContext context, Integer responseStatus) {
if (!isErrorStatus(responseStatus)) {
return null;
}
@@ -248,7 +250,7 @@ public class ApiGatewayAccessLogger {
return null;
}
- private String extractErrorCode(Object responseBody, int responseStatus) {
+ private String extractErrorCode(Object responseBody, Integer responseStatus) {
if (!isErrorStatus(responseStatus)) {
return null;
}
@@ -259,16 +261,14 @@ public class ApiGatewayAccessLogger {
return null;
}
- private int resolveHttpStatus(ApiInvocationContext context) {
- Integer status = context.getResponseStatus();
- if (status != null) {
- return status;
- }
- // 默认兜底为 200,避免日志中出现空的 HTTP 状态码
- return HttpStatus.OK.value();
+ private Integer resolveHttpStatus(ApiInvocationContext context) {
+ return context.getResponseStatus();
}
- private String resolveResponseMessage(ApiInvocationContext context, int responseStatus) {
+ private String resolveResponseMessage(ApiInvocationContext context, Integer responseStatus) {
+ if (responseStatus == null) {
+ return null;
+ }
if (StringUtils.hasText(context.getResponseMessage())) {
return truncate(context.getResponseMessage());
}
@@ -276,8 +276,8 @@ public class ApiGatewayAccessLogger {
return resolved != null ? resolved.getReasonPhrase() : null;
}
- private boolean isErrorStatus(int responseStatus) {
- return responseStatus >= 400;
+ private boolean isErrorStatus(Integer responseStatus) {
+ return responseStatus != null && responseStatus >= 400;
}
private Map buildExtra(ApiInvocationContext context) {
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
index e7b9dbcb..f24dfc08 100644
--- 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
@@ -10,6 +10,7 @@ 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_CREDENTIAL_UNAUTHORIZED;
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EXCEEDED;
/**
@@ -80,6 +81,9 @@ public class ApiGatewayErrorProcessor {
if (API_RATE_LIMIT_EXCEEDED.getCode().equals(code)) {
return HttpStatus.TOO_MANY_REQUESTS.value();
}
+ if (API_CREDENTIAL_UNAUTHORIZED.getCode().equals(code)) {
+ return HttpStatus.FORBIDDEN.value();
+ }
}
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
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 8f442153..f1f39fbb 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
@@ -7,10 +7,10 @@ import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
import com.zt.plat.framework.common.util.monitor.TracerUtils;
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.ApiCredentialBinding;
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;
@@ -19,6 +19,7 @@ import org.springframework.http.*;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UriComponentsBuilder;
@@ -29,12 +30,12 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
+import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_CREDENTIAL_UNAUTHORIZED;
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
- * management-side debug invocations and external HTTP requests share identical
- * behaviour (other than security concerns handled by {@link GatewaySecurityFilter}).
+ * 统一处理 API 门户的请求映射、分发与响应构建。
+ * 管理端调试与外部 HTTP 请求共享同一套逻辑,安全校验由 {@link GatewaySecurityFilter} 执行。
*/
@Slf4j
@Component
@@ -58,7 +59,7 @@ public class ApiGatewayExecutionService {
private final ApiDefinitionService apiDefinitionService;
/**
- * Maps a raw HTTP message (as provided by Spring Integration) into a context message.
+ * 将 Spring Integration 提供的原始消息映射为网关上下文消息。
*/
public Message mapRequest(Message> message) {
ApiInvocationContext context = requestMapper.map(message.getPayload(), message.getHeaders());
@@ -70,7 +71,7 @@ public class ApiGatewayExecutionService {
}
/**
- * Dispatches the API invocation and applies gateway error processing rules on failure scenarios.
+ * 分发 API 调用,并在异常场景套用统一错误处理。
*/
public ApiInvocationContext dispatch(Message message) {
ApiInvocationContext context = message.getPayload();
@@ -78,6 +79,7 @@ public class ApiGatewayExecutionService {
ApiInvocationContext responseContext;
ApiDefinitionAggregate debugAggregate = null;
try {
+ enforceCredentialAuthorization(context);
if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
debugAggregate = resolveDebugAggregate(context);
}
@@ -128,7 +130,7 @@ public class ApiGatewayExecutionService {
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);
context.getAttributes().put(ATTR_DEBUG_INVOKE, Boolean.TRUE);
ApiInvocationContext responseContext = dispatch(mappedMessage);
@@ -155,7 +157,7 @@ public class ApiGatewayExecutionService {
builder.setHeader(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
builder.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, HttpMethod.POST.name());
- String basePath = normalizeBasePath(properties.getBasePath());
+ String basePath = properties.getBasePath();
String rawQuery = buildQueryString(reqVO.getQueryParams());
String requestUri = basePath + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
if (StringUtils.hasText(rawQuery)) {
@@ -169,7 +171,6 @@ public class ApiGatewayExecutionService {
if (reqVO.getHeaders() != null) {
requestHeaders.putAll(reqVO.getHeaders());
}
- normalizeJwtHeaders(requestHeaders, reqVO.getQueryParams());
requestHeaders.putIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
builder.setHeader(HEADER_REQUEST_HEADERS, requestHeaders);
requestHeaders.forEach((key, value) -> {
@@ -223,8 +224,7 @@ public class ApiGatewayExecutionService {
context.setHttpMethod(HttpMethod.POST.name());
}
if (!StringUtils.hasText(context.getRequestPath())) {
- String basePath = normalizeBasePath(properties.getBasePath());
- String path = basePath + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
+ String path = properties.getBasePath() + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
context.setRequestPath(path);
}
}
@@ -245,15 +245,29 @@ public class ApiGatewayExecutionService {
.build();
}
- private String normalizeBasePath(String basePath) {
- if (!StringUtils.hasText(basePath)) {
- return ApiGatewayProperties.DEFAULT_BASE_PATH;
+ /**
+ * 调用前校验凭证白名单,非调试调用需匹配绑定的 appId。
+ */
+ private void enforceCredentialAuthorization(ApiInvocationContext context) {
+ if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
+ return;
}
- String normalized = basePath.startsWith("/") ? basePath : "/" + basePath;
- while (normalized.endsWith("/") && normalized.length() > 1) {
- normalized = normalized.substring(0, normalized.length() - 1);
+ ApiDefinitionAggregate aggregate = apiDefinitionService.findByCodeAndVersion(context.getApiCode(), context.getApiVersion())
+ .orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND));
+ if (CollectionUtils.isEmpty(aggregate.getCredentialBindings())) {
+ return;
+ }
+ String appId = context.getCredentialAppId();
+ if (!StringUtils.hasText(appId)) {
+ throw ServiceExceptionUtil.exception(API_CREDENTIAL_UNAUTHORIZED);
+ }
+ boolean matched = aggregate.getCredentialBindings().stream()
+ .map(ApiCredentialBinding::getAppId)
+ .filter(StringUtils::hasText)
+ .anyMatch(boundAppId -> appId.trim().equalsIgnoreCase(boundAppId));
+ if (!matched) {
+ throw ServiceExceptionUtil.exception(API_CREDENTIAL_UNAUTHORIZED);
}
- return normalized;
}
private String buildQueryString(Map queryParams) {
@@ -290,36 +304,4 @@ public class ApiGatewayExecutionService {
builder.queryParam(key, value);
}
- private void normalizeJwtHeaders(Map headers, Map queryParams) {
- String token = GatewayJwtResolver.resolveJwtToken(headers, queryParams, objectMapper);
- if (!StringUtils.hasText(token)) {
- return;
- }
- ensureHeaderValue(headers, GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, token);
- ensureHeaderValue(headers, HttpHeaders.AUTHORIZATION, "Bearer " + token);
- }
-
- private void ensureHeaderValue(Map headers, String headerName, String value) {
- if (!StringUtils.hasText(headerName) || value == null) {
- return;
- }
- String existingKey = findHeaderKey(headers, headerName);
- if (existingKey != null) {
- headers.put(existingKey, value);
- } else {
- headers.put(headerName, value);
- }
- }
-
- private String findHeaderKey(Map headers, String headerName) {
- if (headers == null || !StringUtils.hasText(headerName)) {
- return null;
- }
- for (String key : headers.keySet()) {
- if (headerName.equalsIgnoreCase(key)) {
- return key;
- }
- }
- return null;
- }
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java
index 5d813d52..1c4ba0da 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java
@@ -35,6 +35,7 @@ public class ApiGatewayRequestMapper {
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_REMOTE_ADDRESS = org.springframework.integration.http.HttpHeaders.PREFIX + "remoteAddress";
+ private static final String HEADER_CREDENTIAL_ID = "X-Databus-Credential-Id";
@SuppressWarnings("unchecked")
public ApiInvocationContext map(Object payload, Map headers) {
@@ -79,18 +80,29 @@ public class ApiGatewayRequestMapper {
}
Map requestHeaders = (Map) headers.get(HEADER_REQUEST_HEADERS);
- GatewayHeaderUtils.mergeNormalizedHeaders(requestHeaders, context.getRequestHeaders());
+ if (requestHeaders != null) {
+ context.getRequestHeaders().putAll(requestHeaders);
+ }
headers.forEach((key, value) -> {
- if (isInternalHeader(key)) {
+ if (isInternalHeader(key) || value == null) {
return;
}
- String normalized = GatewayHeaderUtils.normalizeHeaderValue(value);
- if (normalized != null) {
- context.getRequestHeaders().putIfAbsent(key, normalized);
- }
+ context.getRequestHeaders().putIfAbsent(key, String.valueOf(value));
});
context.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
context.setClientIp(resolveClientIp(headers, context.getRequestHeaders()));
+ String appId = GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), ApiGatewayProperties.APP_ID_HEADER);
+ if (StringUtils.hasText(appId)) {
+ context.setCredentialAppId(appId.trim());
+ }
+ String credentialIdHeader = GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HEADER_CREDENTIAL_ID);
+ if (StringUtils.hasText(credentialIdHeader)) {
+ try {
+ context.setCredentialId(Long.valueOf(credentialIdHeader.trim()));
+ } catch (NumberFormatException ignored) {
+ context.setCredentialId(null);
+ }
+ }
captureAccessLogId(context);
populateQueryParams(headers, context, originalRequestUri);
if (properties.isEnableTenantHeader()) {
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiCredentialBinding.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiCredentialBinding.java
new file mode 100644
index 00000000..6fd70b47
--- /dev/null
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiCredentialBinding.java
@@ -0,0 +1,18 @@
+package com.zt.plat.module.databus.framework.integration.gateway.domain;
+
+import lombok.Builder;
+import lombok.Value;
+
+/**
+ * API 授权绑定的凭证信息。
+ */
+@Value
+@Builder
+public class ApiCredentialBinding {
+
+ Long credentialId;
+
+ String appId;
+
+ String appName;
+}
\ No newline at end of file
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiDefinitionAggregate.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiDefinitionAggregate.java
index 24121989..730bc36a 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiDefinitionAggregate.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiDefinitionAggregate.java
@@ -26,6 +26,8 @@ public class ApiDefinitionAggregate {
ApiFlowPublication publication;
+ List credentialBindings;
+
public List getSteps() {
return steps == null ? Collections.emptyList() : steps;
}
@@ -34,4 +36,8 @@ public class ApiDefinitionAggregate {
return apiLevelTransforms == null ? Collections.emptyMap() : apiLevelTransforms;
}
+ public List getCredentialBindings() {
+ return credentialBindings == null ? Collections.emptyList() : credentialBindings;
+ }
+
}
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiInvocationContext.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiInvocationContext.java
index a17dc89f..f27b4fbf 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiInvocationContext.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiInvocationContext.java
@@ -29,6 +29,10 @@ public class ApiInvocationContext {
private String userAgent;
+ private String credentialAppId;
+
+ private Long credentialId;
+
private String httpMethod;
private String requestPath;
@@ -72,6 +76,8 @@ public class ApiInvocationContext {
copy.tenantId = this.tenantId;
copy.clientIp = this.clientIp;
copy.userAgent = this.userAgent;
+ copy.credentialAppId = this.credentialAppId;
+ copy.credentialId = this.credentialId;
copy.httpMethod = this.httpMethod;
copy.requestPath = this.requestPath;
copy.requestBody = this.requestBody;
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/CachedBodyHttpServletRequest.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/CachedBodyHttpServletRequest.java
index f722c8f1..0048bf19 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/CachedBodyHttpServletRequest.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/CachedBodyHttpServletRequest.java
@@ -15,11 +15,11 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.TreeSet;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -104,7 +104,7 @@ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
return;
}
if (!StringUtils.hasText(value)) {
- additionalHeaders.remove(name);
+ removeHeader(name);
return;
}
additionalHeaders.put(name, new ArrayList<>(Collections.singletonList(value)));
@@ -115,7 +115,7 @@ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
return;
}
if (CollectionUtils.isEmpty(values)) {
- additionalHeaders.remove(name);
+ removeHeader(name);
return;
}
additionalHeaders.put(name, new ArrayList<>(values));
@@ -125,7 +125,7 @@ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
if (!StringUtils.hasText(name) || !StringUtils.hasText(value)) {
return;
}
- additionalHeaders.compute(name, (key, existing) -> {
+ additionalHeaders.compute(name, (k, existing) -> {
List list = existing == null ? new ArrayList<>() : new ArrayList<>(existing);
list.add(value);
return list;
@@ -152,23 +152,20 @@ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
@Override
public Enumeration getHeaders(String name) {
- List combined = new ArrayList<>();
if (StringUtils.hasText(name)) {
List custom = additionalHeaders.get(name);
+ // 如果自定义头已写入,则直接返回,避免与原始头部合并造成重复
if (!CollectionUtils.isEmpty(custom)) {
- combined.addAll(custom);
+ return Collections.enumeration(custom);
}
}
- Enumeration parent = super.getHeaders(name);
- while (parent.hasMoreElements()) {
- combined.add(parent.nextElement());
- }
- return Collections.enumeration(combined);
+ return super.getHeaders(name);
}
@Override
public Enumeration getHeaderNames() {
- Set names = new LinkedHashSet<>();
+ // 使用忽略大小写的集合,避免父请求头名与自定义头名大小写不同而重复
+ Set names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
Enumeration parent = super.getHeaderNames();
while (parent.hasMoreElements()) {
names.add(parent.nextElement());
diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java
index 6698602b..48c2a627 100644
--- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java
+++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java
@@ -61,6 +61,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private static final TypeReference