Merge remote-tracking branch 'base-version/main' into dev
This commit is contained in:
@@ -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 (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 (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 (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 (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 (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');
|
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');
|
||||||
|
|||||||
45
sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql
Normal file
45
sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql
Normal file
@@ -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';
|
||||||
@@ -32,8 +32,6 @@
|
|||||||
<spring.boot.version>3.4.5</spring.boot.version>
|
<spring.boot.version>3.4.5</spring.boot.version>
|
||||||
<spring.cloud.version>2024.0.1</spring.cloud.version>
|
<spring.cloud.version>2024.0.1</spring.cloud.version>
|
||||||
<spring.cloud.alibaba.version>2023.0.3.2</spring.cloud.alibaba.version>
|
<spring.cloud.alibaba.version>2023.0.3.2</spring.cloud.alibaba.version>
|
||||||
<!-- 分布式事务相关 -->
|
|
||||||
<seata.version>2.4.0</seata.version>
|
|
||||||
<!-- Web 相关 -->
|
<!-- Web 相关 -->
|
||||||
<springdoc.version>2.8.3</springdoc.version>
|
<springdoc.version>2.8.3</springdoc.version>
|
||||||
<knife4j.version>4.6.0</knife4j.version>
|
<knife4j.version>4.6.0</knife4j.version>
|
||||||
@@ -90,8 +88,6 @@
|
|||||||
<mqtt.version>1.2.5</mqtt.version>
|
<mqtt.version>1.2.5</mqtt.version>
|
||||||
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
||||||
<okhttp3.version>4.12.0</okhttp3.version>
|
<okhttp3.version>4.12.0</okhttp3.version>
|
||||||
<docx4j.version>11.4.7</docx4j.version>
|
|
||||||
<docx4j-jaxb.version>11.4.7</docx4j-jaxb.version>
|
|
||||||
<!-- 规则引擎 -->
|
<!-- 规则引擎 -->
|
||||||
<liteflow.version>2.15.1</liteflow.version>
|
<liteflow.version>2.15.1</liteflow.version>
|
||||||
<vertx.version>4.5.13</vertx.version>
|
<vertx.version>4.5.13</vertx.version>
|
||||||
@@ -137,20 +133,6 @@
|
|||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 分布式事务:Seata -->
|
|
||||||
<!-- 显式覆盖 Spring Cloud Alibaba BOM 中的 Seata 1.8.0,升级到 2.4.0 以支持达梦数据库 -->
|
|
||||||
<!-- 注意:Seata 2.2.0+ 改为使用 org.apache.seata groupId -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.seata</groupId>
|
|
||||||
<artifactId>seata-all</artifactId>
|
|
||||||
<version>${seata.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.seata</groupId>
|
|
||||||
<artifactId>seata-spring-boot-starter</artifactId>
|
|
||||||
<version>${seata.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 业务组件 -->
|
<!-- 业务组件 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.github.mouzt</groupId>
|
<groupId>io.github.mouzt</groupId>
|
||||||
|
|||||||
@@ -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.ApiDefinitionDO;
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
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.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.ApiDefinitionAggregate;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiFlowPublication;
|
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiFlowPublication;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
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.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
@@ -48,6 +50,11 @@ public interface ApiDefinitionConvert {
|
|||||||
detail.setApiLevelTransforms(convertTransforms(aggregate.getDefinition().getId(), aggregate.getApiLevelTransforms().values()));
|
detail.setApiLevelTransforms(convertTransforms(aggregate.getDefinition().getId(), aggregate.getApiLevelTransforms().values()));
|
||||||
detail.setSteps(convertSteps(aggregate.getSteps()));
|
detail.setSteps(convertSteps(aggregate.getSteps()));
|
||||||
detail.setPublication(convert(aggregate.getPublication()));
|
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;
|
return detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +106,15 @@ public interface ApiDefinitionConvert {
|
|||||||
return publication == null ? null : BeanUtils.toBean(publication, ApiDefinitionPublicationRespVO.class);
|
return publication == null ? null : BeanUtils.toBean(publication, ApiDefinitionPublicationRespVO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default List<ApiCredentialBindingRespVO> convertCredentialBindings(List<ApiCredentialBinding> bindings) {
|
||||||
|
if (CollUtil.isEmpty(bindings)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return bindings.stream()
|
||||||
|
.map(binding -> BeanUtils.toBean(binding, ApiCredentialBindingRespVO.class))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换步骤列表(DO -> SaveReqVO)
|
* 转换步骤列表(DO -> SaveReqVO)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ public class ApiAccessLogPageReqVO extends PageParam {
|
|||||||
@Schema(description = "请求路径", example = "/gateway/api/user/query")
|
@Schema(description = "请求路径", example = "/gateway/api/user/query")
|
||||||
private String requestPath;
|
private String requestPath;
|
||||||
|
|
||||||
|
@Schema(description = "应用标识", example = "app-portal-01")
|
||||||
|
private String credentialAppId;
|
||||||
|
|
||||||
|
@Schema(description = "凭证主键", example = "10086")
|
||||||
|
private Long credentialId;
|
||||||
|
|
||||||
@Schema(description = "请求时间区间")
|
@Schema(description = "请求时间区间")
|
||||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||||
private LocalDateTime[] requestTime;
|
private LocalDateTime[] requestTime;
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ public class ApiAccessLogRespVO {
|
|||||||
@Schema(description = "请求路径", example = "/gateway/api/user/query")
|
@Schema(description = "请求路径", example = "/gateway/api/user/query")
|
||||||
private String requestPath;
|
private String requestPath;
|
||||||
|
|
||||||
|
@Schema(description = "应用标识", example = "app-portal-01")
|
||||||
|
private String credentialAppId;
|
||||||
|
|
||||||
|
@Schema(description = "凭证主键", example = "10086")
|
||||||
|
private Long credentialId;
|
||||||
|
|
||||||
@Schema(description = "查询参数(JSON)")
|
@Schema(description = "查询参数(JSON)")
|
||||||
private String requestQuery;
|
private String requestQuery;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -53,6 +53,12 @@ public class ApiDefinitionDetailRespVO {
|
|||||||
@Schema(description = "API 级别变换列表")
|
@Schema(description = "API 级别变换列表")
|
||||||
private List<ApiDefinitionTransformRespVO> apiLevelTransforms = new ArrayList<>();
|
private List<ApiDefinitionTransformRespVO> apiLevelTransforms = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "授权凭证 ID 列表")
|
||||||
|
private List<Long> credentialIds = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "授权凭证详情列表")
|
||||||
|
private List<ApiCredentialBindingRespVO> credentialBindings = new ArrayList<>();
|
||||||
|
|
||||||
@Schema(description = "步骤列表")
|
@Schema(description = "步骤列表")
|
||||||
private List<ApiDefinitionStepRespVO> steps = new ArrayList<>();
|
private List<ApiDefinitionStepRespVO> steps = new ArrayList<>();
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ public class ApiDefinitionSaveReqVO {
|
|||||||
@Valid
|
@Valid
|
||||||
private List<ApiDefinitionTransformSaveReqVO> apiLevelTransforms = new ArrayList<>();
|
private List<ApiDefinitionTransformSaveReqVO> apiLevelTransforms = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "授权的客户端凭证 ID 列表")
|
||||||
|
private List<Long> credentialIds = new ArrayList<>();
|
||||||
|
|
||||||
@Schema(description = "步骤列表")
|
@Schema(description = "步骤列表")
|
||||||
@NotEmpty(message = "编排步骤不能为空")
|
@NotEmpty(message = "编排步骤不能为空")
|
||||||
@Valid
|
@Valid
|
||||||
|
|||||||
@@ -52,6 +52,16 @@ public class ApiAccessLogDO extends TenantBaseDO {
|
|||||||
*/
|
*/
|
||||||
private String requestPath;
|
private String requestPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用使用的应用标识
|
||||||
|
*/
|
||||||
|
private String credentialAppId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用使用的凭证主键
|
||||||
|
*/
|
||||||
|
private Long credentialId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询参数(JSON 字符串)
|
* 查询参数(JSON 字符串)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ public interface ApiAccessLogMapper extends BaseMapperX<ApiAccessLogDO> {
|
|||||||
.eqIfPresent(ApiAccessLogDO::getResponseStatus, reqVO.getResponseStatus())
|
.eqIfPresent(ApiAccessLogDO::getResponseStatus, reqVO.getResponseStatus())
|
||||||
.eqIfPresent(ApiAccessLogDO::getStatus, reqVO.getStatus())
|
.eqIfPresent(ApiAccessLogDO::getStatus, reqVO.getStatus())
|
||||||
.likeIfPresent(ApiAccessLogDO::getClientIp, reqVO.getClientIp())
|
.likeIfPresent(ApiAccessLogDO::getClientIp, reqVO.getClientIp())
|
||||||
|
.eqIfPresent(ApiAccessLogDO::getCredentialAppId, reqVO.getCredentialAppId())
|
||||||
|
.eqIfPresent(ApiAccessLogDO::getCredentialId, reqVO.getCredentialId())
|
||||||
.eqIfPresent(ApiAccessLogDO::getTenantId, reqVO.getTenantId())
|
.eqIfPresent(ApiAccessLogDO::getTenantId, reqVO.getTenantId())
|
||||||
.likeIfPresent(ApiAccessLogDO::getRequestPath, reqVO.getRequestPath());
|
.likeIfPresent(ApiAccessLogDO::getRequestPath, reqVO.getRequestPath());
|
||||||
if (ArrayUtil.isNotEmpty(reqVO.getRequestTime()) && reqVO.getRequestTime().length == 2) {
|
if (ArrayUtil.isNotEmpty(reqVO.getRequestTime()) && reqVO.getRequestTime().length == 2) {
|
||||||
|
|||||||
@@ -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<ApiDefinitionCredentialDO> {
|
||||||
|
|
||||||
|
default List<ApiDefinitionCredentialDO> selectByApiId(Long apiId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiDefinitionCredentialDO>()
|
||||||
|
.eq(ApiDefinitionCredentialDO::getApiId, apiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void deleteByApiId(Long apiId) {
|
||||||
|
delete(new LambdaQueryWrapperX<ApiDefinitionCredentialDO>()
|
||||||
|
.eq(ApiDefinitionCredentialDO::getApiId, apiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 API 逻辑删除已有绑定,保留操作记录。
|
||||||
|
*/
|
||||||
|
default void logicDeleteByApiId(Long apiId) {
|
||||||
|
ApiDefinitionCredentialDO entity = new ApiDefinitionCredentialDO();
|
||||||
|
entity.setDeleted(Boolean.TRUE);
|
||||||
|
update(entity, new LambdaQueryWrapperX<ApiDefinitionCredentialDO>()
|
||||||
|
.eq(ApiDefinitionCredentialDO::getApiId, apiId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,7 +83,7 @@ public class ApiGatewayAccessLogger {
|
|||||||
try {
|
try {
|
||||||
ApiAccessLogDO update = new ApiAccessLogDO();
|
ApiAccessLogDO update = new ApiAccessLogDO();
|
||||||
update.setId(logId);
|
update.setId(logId);
|
||||||
int responseStatus = resolveHttpStatus(context);
|
Integer responseStatus = resolveHttpStatus(context);
|
||||||
context.setResponseStatus(responseStatus);
|
context.setResponseStatus(responseStatus);
|
||||||
update.setResponseStatus(responseStatus);
|
update.setResponseStatus(responseStatus);
|
||||||
String responseMessage = resolveResponseMessage(context, responseStatus);
|
String responseMessage = resolveResponseMessage(context, responseStatus);
|
||||||
@@ -193,6 +193,8 @@ public class ApiGatewayAccessLogger {
|
|||||||
logDO.setRequestBody(toJson(context.getRequestBody()));
|
logDO.setRequestBody(toJson(context.getRequestBody()));
|
||||||
logDO.setClientIp(context.getClientIp());
|
logDO.setClientIp(context.getClientIp());
|
||||||
logDO.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
|
logDO.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
|
||||||
|
logDO.setCredentialAppId(context.getCredentialAppId());
|
||||||
|
logDO.setCredentialId(context.getCredentialId());
|
||||||
logDO.setStatus(3);
|
logDO.setStatus(3);
|
||||||
logDO.setRequestTime(toLocalDateTime(context.getRequestTime()));
|
logDO.setRequestTime(toLocalDateTime(context.getRequestTime()));
|
||||||
logDO.setTenantId(parseTenantId(context.getTenantId()));
|
logDO.setTenantId(parseTenantId(context.getTenantId()));
|
||||||
@@ -231,7 +233,7 @@ public class ApiGatewayAccessLogger {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveErrorMessage(ApiInvocationContext context, int responseStatus) {
|
private String resolveErrorMessage(ApiInvocationContext context, Integer responseStatus) {
|
||||||
if (!isErrorStatus(responseStatus)) {
|
if (!isErrorStatus(responseStatus)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -248,7 +250,7 @@ public class ApiGatewayAccessLogger {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String extractErrorCode(Object responseBody, int responseStatus) {
|
private String extractErrorCode(Object responseBody, Integer responseStatus) {
|
||||||
if (!isErrorStatus(responseStatus)) {
|
if (!isErrorStatus(responseStatus)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -259,16 +261,14 @@ public class ApiGatewayAccessLogger {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int resolveHttpStatus(ApiInvocationContext context) {
|
private Integer resolveHttpStatus(ApiInvocationContext context) {
|
||||||
Integer status = context.getResponseStatus();
|
return context.getResponseStatus();
|
||||||
if (status != null) {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
// 默认兜底为 200,避免日志中出现空的 HTTP 状态码
|
|
||||||
return HttpStatus.OK.value();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveResponseMessage(ApiInvocationContext context, int responseStatus) {
|
private String resolveResponseMessage(ApiInvocationContext context, Integer responseStatus) {
|
||||||
|
if (responseStatus == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (StringUtils.hasText(context.getResponseMessage())) {
|
if (StringUtils.hasText(context.getResponseMessage())) {
|
||||||
return truncate(context.getResponseMessage());
|
return truncate(context.getResponseMessage());
|
||||||
}
|
}
|
||||||
@@ -276,8 +276,8 @@ public class ApiGatewayAccessLogger {
|
|||||||
return resolved != null ? resolved.getReasonPhrase() : null;
|
return resolved != null ? resolved.getReasonPhrase() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isErrorStatus(int responseStatus) {
|
private boolean isErrorStatus(Integer responseStatus) {
|
||||||
return responseStatus >= 400;
|
return responseStatus != null && responseStatus >= 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> buildExtra(ApiInvocationContext context) {
|
private Map<String, Object> buildExtra(ApiInvocationContext context) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
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_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;
|
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)) {
|
if (API_RATE_LIMIT_EXCEEDED.getCode().equals(code)) {
|
||||||
return HttpStatus.TOO_MANY_REQUESTS.value();
|
return HttpStatus.TOO_MANY_REQUESTS.value();
|
||||||
}
|
}
|
||||||
|
if (API_CREDENTIAL_UNAUTHORIZED.getCode().equals(code)) {
|
||||||
|
return HttpStatus.FORBIDDEN.value();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return HttpStatus.INTERNAL_SERVER_ERROR.value();
|
return HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.framework.common.util.monitor.TracerUtils;
|
||||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
|
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.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.domain.ApiDefinitionAggregate;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
|
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.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.framework.integration.gateway.security.GatewaySecurityFilter;
|
||||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -19,6 +19,7 @@ import org.springframework.http.*;
|
|||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.support.MessageBuilder;
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
import org.springframework.web.servlet.HandlerMapping;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
@@ -29,12 +30,12 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
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;
|
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
|
* 统一处理 API 门户的请求映射、分发与响应构建。
|
||||||
* management-side debug invocations and external HTTP requests share identical
|
* 管理端调试与外部 HTTP 请求共享同一套逻辑,安全校验由 {@link GatewaySecurityFilter} 执行。
|
||||||
* behaviour (other than security concerns handled by {@link GatewaySecurityFilter}).
|
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -58,7 +59,7 @@ public class ApiGatewayExecutionService {
|
|||||||
private final ApiDefinitionService apiDefinitionService;
|
private final ApiDefinitionService apiDefinitionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a raw HTTP message (as provided by Spring Integration) into a context message.
|
* 将 Spring Integration 提供的原始消息映射为网关上下文消息。
|
||||||
*/
|
*/
|
||||||
public Message<ApiInvocationContext> mapRequest(Message<?> message) {
|
public Message<ApiInvocationContext> mapRequest(Message<?> message) {
|
||||||
ApiInvocationContext context = requestMapper.map(message.getPayload(), message.getHeaders());
|
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<ApiInvocationContext> message) {
|
public ApiInvocationContext dispatch(Message<ApiInvocationContext> message) {
|
||||||
ApiInvocationContext context = message.getPayload();
|
ApiInvocationContext context = message.getPayload();
|
||||||
@@ -78,6 +79,7 @@ public class ApiGatewayExecutionService {
|
|||||||
ApiInvocationContext responseContext;
|
ApiInvocationContext responseContext;
|
||||||
ApiDefinitionAggregate debugAggregate = null;
|
ApiDefinitionAggregate debugAggregate = null;
|
||||||
try {
|
try {
|
||||||
|
enforceCredentialAuthorization(context);
|
||||||
if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
|
if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
|
||||||
debugAggregate = resolveDebugAggregate(context);
|
debugAggregate = resolveDebugAggregate(context);
|
||||||
}
|
}
|
||||||
@@ -128,7 +130,7 @@ public class ApiGatewayExecutionService {
|
|||||||
Message<?> rawMessage = buildDebugMessage(reqVO);
|
Message<?> rawMessage = buildDebugMessage(reqVO);
|
||||||
Message<ApiInvocationContext> mappedMessage = mapRequest(rawMessage);
|
Message<ApiInvocationContext> mappedMessage = mapRequest(rawMessage);
|
||||||
ApiInvocationContext context = mappedMessage.getPayload();
|
ApiInvocationContext context = mappedMessage.getPayload();
|
||||||
// Ensure query parameters & headers from debug payload are reflected after mapping.
|
// 将调试透传的查询参数、请求头重新合并到上下文,避免映射阶段丢失
|
||||||
mergeDebugMetadata(context, reqVO);
|
mergeDebugMetadata(context, reqVO);
|
||||||
context.getAttributes().put(ATTR_DEBUG_INVOKE, Boolean.TRUE);
|
context.getAttributes().put(ATTR_DEBUG_INVOKE, Boolean.TRUE);
|
||||||
ApiInvocationContext responseContext = dispatch(mappedMessage);
|
ApiInvocationContext responseContext = dispatch(mappedMessage);
|
||||||
@@ -155,7 +157,7 @@ public class ApiGatewayExecutionService {
|
|||||||
builder.setHeader(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
|
builder.setHeader(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
|
||||||
builder.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, HttpMethod.POST.name());
|
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 rawQuery = buildQueryString(reqVO.getQueryParams());
|
||||||
String requestUri = basePath + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
|
String requestUri = basePath + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
|
||||||
if (StringUtils.hasText(rawQuery)) {
|
if (StringUtils.hasText(rawQuery)) {
|
||||||
@@ -169,7 +171,6 @@ public class ApiGatewayExecutionService {
|
|||||||
if (reqVO.getHeaders() != null) {
|
if (reqVO.getHeaders() != null) {
|
||||||
requestHeaders.putAll(reqVO.getHeaders());
|
requestHeaders.putAll(reqVO.getHeaders());
|
||||||
}
|
}
|
||||||
normalizeJwtHeaders(requestHeaders, reqVO.getQueryParams());
|
|
||||||
requestHeaders.putIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
requestHeaders.putIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||||
builder.setHeader(HEADER_REQUEST_HEADERS, requestHeaders);
|
builder.setHeader(HEADER_REQUEST_HEADERS, requestHeaders);
|
||||||
requestHeaders.forEach((key, value) -> {
|
requestHeaders.forEach((key, value) -> {
|
||||||
@@ -223,8 +224,7 @@ public class ApiGatewayExecutionService {
|
|||||||
context.setHttpMethod(HttpMethod.POST.name());
|
context.setHttpMethod(HttpMethod.POST.name());
|
||||||
}
|
}
|
||||||
if (!StringUtils.hasText(context.getRequestPath())) {
|
if (!StringUtils.hasText(context.getRequestPath())) {
|
||||||
String basePath = normalizeBasePath(properties.getBasePath());
|
String path = properties.getBasePath() + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
|
||||||
String path = basePath + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
|
|
||||||
context.setRequestPath(path);
|
context.setRequestPath(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,15 +245,29 @@ public class ApiGatewayExecutionService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizeBasePath(String basePath) {
|
/**
|
||||||
if (!StringUtils.hasText(basePath)) {
|
* 调用前校验凭证白名单,非调试调用需匹配绑定的 appId。
|
||||||
return ApiGatewayProperties.DEFAULT_BASE_PATH;
|
*/
|
||||||
|
private void enforceCredentialAuthorization(ApiInvocationContext context) {
|
||||||
|
if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
String normalized = basePath.startsWith("/") ? basePath : "/" + basePath;
|
ApiDefinitionAggregate aggregate = apiDefinitionService.findByCodeAndVersion(context.getApiCode(), context.getApiVersion())
|
||||||
while (normalized.endsWith("/") && normalized.length() > 1) {
|
.orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND));
|
||||||
normalized = normalized.substring(0, normalized.length() - 1);
|
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<String, Object> queryParams) {
|
private String buildQueryString(Map<String, Object> queryParams) {
|
||||||
@@ -290,36 +304,4 @@ public class ApiGatewayExecutionService {
|
|||||||
builder.queryParam(key, value);
|
builder.queryParam(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void normalizeJwtHeaders(Map<String, Object> headers, Map<String, Object> 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<String, Object> 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<String, Object> headers, String headerName) {
|
|
||||||
if (headers == null || !StringUtils.hasText(headerName)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (String key : headers.keySet()) {
|
|
||||||
if (headerName.equalsIgnoreCase(key)) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_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_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_REMOTE_ADDRESS = org.springframework.integration.http.HttpHeaders.PREFIX + "remoteAddress";
|
||||||
|
private static final String HEADER_CREDENTIAL_ID = "X-Databus-Credential-Id";
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public ApiInvocationContext map(Object payload, Map<String, Object> headers) {
|
public ApiInvocationContext map(Object payload, Map<String, Object> headers) {
|
||||||
@@ -79,18 +80,29 @@ public class ApiGatewayRequestMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> requestHeaders = (Map<String, Object>) headers.get(HEADER_REQUEST_HEADERS);
|
Map<String, Object> requestHeaders = (Map<String, Object>) headers.get(HEADER_REQUEST_HEADERS);
|
||||||
GatewayHeaderUtils.mergeNormalizedHeaders(requestHeaders, context.getRequestHeaders());
|
if (requestHeaders != null) {
|
||||||
|
context.getRequestHeaders().putAll(requestHeaders);
|
||||||
|
}
|
||||||
headers.forEach((key, value) -> {
|
headers.forEach((key, value) -> {
|
||||||
if (isInternalHeader(key)) {
|
if (isInternalHeader(key) || value == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String normalized = GatewayHeaderUtils.normalizeHeaderValue(value);
|
context.getRequestHeaders().putIfAbsent(key, String.valueOf(value));
|
||||||
if (normalized != null) {
|
|
||||||
context.getRequestHeaders().putIfAbsent(key, normalized);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
context.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
|
context.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
|
||||||
context.setClientIp(resolveClientIp(headers, context.getRequestHeaders()));
|
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);
|
captureAccessLogId(context);
|
||||||
populateQueryParams(headers, context, originalRequestUri);
|
populateQueryParams(headers, context, originalRequestUri);
|
||||||
if (properties.isEnableTenantHeader()) {
|
if (properties.isEnableTenantHeader()) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@ public class ApiDefinitionAggregate {
|
|||||||
|
|
||||||
ApiFlowPublication publication;
|
ApiFlowPublication publication;
|
||||||
|
|
||||||
|
List<ApiCredentialBinding> credentialBindings;
|
||||||
|
|
||||||
public List<ApiStepDefinition> getSteps() {
|
public List<ApiStepDefinition> getSteps() {
|
||||||
return steps == null ? Collections.emptyList() : steps;
|
return steps == null ? Collections.emptyList() : steps;
|
||||||
}
|
}
|
||||||
@@ -34,4 +36,8 @@ public class ApiDefinitionAggregate {
|
|||||||
return apiLevelTransforms == null ? Collections.emptyMap() : apiLevelTransforms;
|
return apiLevelTransforms == null ? Collections.emptyMap() : apiLevelTransforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ApiCredentialBinding> getCredentialBindings() {
|
||||||
|
return credentialBindings == null ? Collections.emptyList() : credentialBindings;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ public class ApiInvocationContext {
|
|||||||
|
|
||||||
private String userAgent;
|
private String userAgent;
|
||||||
|
|
||||||
|
private String credentialAppId;
|
||||||
|
|
||||||
|
private Long credentialId;
|
||||||
|
|
||||||
private String httpMethod;
|
private String httpMethod;
|
||||||
|
|
||||||
private String requestPath;
|
private String requestPath;
|
||||||
@@ -72,6 +76,8 @@ public class ApiInvocationContext {
|
|||||||
copy.tenantId = this.tenantId;
|
copy.tenantId = this.tenantId;
|
||||||
copy.clientIp = this.clientIp;
|
copy.clientIp = this.clientIp;
|
||||||
copy.userAgent = this.userAgent;
|
copy.userAgent = this.userAgent;
|
||||||
|
copy.credentialAppId = this.credentialAppId;
|
||||||
|
copy.credentialId = this.credentialId;
|
||||||
copy.httpMethod = this.httpMethod;
|
copy.httpMethod = this.httpMethod;
|
||||||
copy.requestPath = this.requestPath;
|
copy.requestPath = this.requestPath;
|
||||||
copy.requestBody = this.requestBody;
|
copy.requestBody = this.requestBody;
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -104,7 +104,7 @@ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!StringUtils.hasText(value)) {
|
if (!StringUtils.hasText(value)) {
|
||||||
additionalHeaders.remove(name);
|
removeHeader(name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
additionalHeaders.put(name, new ArrayList<>(Collections.singletonList(value)));
|
additionalHeaders.put(name, new ArrayList<>(Collections.singletonList(value)));
|
||||||
@@ -115,7 +115,7 @@ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (CollectionUtils.isEmpty(values)) {
|
if (CollectionUtils.isEmpty(values)) {
|
||||||
additionalHeaders.remove(name);
|
removeHeader(name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
additionalHeaders.put(name, new ArrayList<>(values));
|
additionalHeaders.put(name, new ArrayList<>(values));
|
||||||
@@ -125,7 +125,7 @@ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
|
|||||||
if (!StringUtils.hasText(name) || !StringUtils.hasText(value)) {
|
if (!StringUtils.hasText(name) || !StringUtils.hasText(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
additionalHeaders.compute(name, (key, existing) -> {
|
additionalHeaders.compute(name, (k, existing) -> {
|
||||||
List<String> list = existing == null ? new ArrayList<>() : new ArrayList<>(existing);
|
List<String> list = existing == null ? new ArrayList<>() : new ArrayList<>(existing);
|
||||||
list.add(value);
|
list.add(value);
|
||||||
return list;
|
return list;
|
||||||
@@ -152,23 +152,20 @@ public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Enumeration<String> getHeaders(String name) {
|
public Enumeration<String> getHeaders(String name) {
|
||||||
List<String> combined = new ArrayList<>();
|
|
||||||
if (StringUtils.hasText(name)) {
|
if (StringUtils.hasText(name)) {
|
||||||
List<String> custom = additionalHeaders.get(name);
|
List<String> custom = additionalHeaders.get(name);
|
||||||
|
// 如果自定义头已写入,则直接返回,避免与原始头部合并造成重复
|
||||||
if (!CollectionUtils.isEmpty(custom)) {
|
if (!CollectionUtils.isEmpty(custom)) {
|
||||||
combined.addAll(custom);
|
return Collections.enumeration(custom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Enumeration<String> parent = super.getHeaders(name);
|
return super.getHeaders(name);
|
||||||
while (parent.hasMoreElements()) {
|
|
||||||
combined.add(parent.nextElement());
|
|
||||||
}
|
|
||||||
return Collections.enumeration(combined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Enumeration<String> getHeaderNames() {
|
public Enumeration<String> getHeaderNames() {
|
||||||
Set<String> names = new LinkedHashSet<>();
|
// 使用忽略大小写的集合,避免父请求头名与自定义头名大小写不同而重复
|
||||||
|
Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
Enumeration<String> parent = super.getHeaderNames();
|
Enumeration<String> parent = super.getHeaderNames();
|
||||||
while (parent.hasMoreElements()) {
|
while (parent.hasMoreElements()) {
|
||||||
names.add(parent.nextElement());
|
names.add(parent.nextElement());
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
|
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
|
||||||
};
|
};
|
||||||
|
public static final String HEADER_CREDENTIAL_ID = "X-Databus-Credential-Id";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
@@ -133,6 +134,8 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
// 使用可重复读取的请求包装,供后续过滤器继续消费
|
// 使用可重复读取的请求包装,供后续过滤器继续消费
|
||||||
CachedBodyHttpServletRequest securedRequest = new CachedBodyHttpServletRequest(request, decryptedBody);
|
CachedBodyHttpServletRequest securedRequest = new CachedBodyHttpServletRequest(request, decryptedBody);
|
||||||
|
securedRequest.setHeader(APP_ID_HEADER, credential.getAppId());
|
||||||
|
securedRequest.setHeader(HEADER_CREDENTIAL_ID, credential.getId() != null ? String.valueOf(credential.getId()) : null);
|
||||||
ApiGatewayAccessLogger.propagateLogIdHeader(securedRequest, accessLogId);
|
ApiGatewayAccessLogger.propagateLogIdHeader(securedRequest, accessLogId);
|
||||||
if (StringUtils.hasText(request.getCharacterEncoding())) {
|
if (StringUtils.hasText(request.getCharacterEncoding())) {
|
||||||
securedRequest.setCharacterEncoding(request.getCharacterEncoding());
|
securedRequest.setCharacterEncoding(request.getCharacterEncoding());
|
||||||
@@ -283,7 +286,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
|
|||||||
try {
|
try {
|
||||||
boolean valid = CryptoSignatureUtils.verifySignature(signaturePayload, signatureType);
|
boolean valid = CryptoSignatureUtils.verifySignature(signaturePayload, signatureType);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
// throw new SecurityValidationException(HttpStatus.UNAUTHORIZED, "签名校验失败");
|
throw new SecurityValidationException(HttpStatus.UNAUTHORIZED, "签名校验失败");
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
throw new SecurityValidationException(HttpStatus.INTERNAL_SERVER_ERROR, "签名算法配置异常");
|
throw new SecurityValidationException(HttpStatus.INTERNAL_SERVER_ERROR, "签名算法配置异常");
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefi
|
|||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.*;
|
import com.zt.plat.module.databus.dal.dataobject.gateway.*;
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.*;
|
import com.zt.plat.module.databus.dal.mysql.gateway.*;
|
||||||
import com.zt.plat.module.databus.enums.gateway.ApiStatusEnum;
|
import com.zt.plat.module.databus.enums.gateway.ApiStatusEnum;
|
||||||
|
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.ApiDefinitionAggregate;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiFlowPublication;
|
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiFlowPublication;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
||||||
@@ -40,6 +41,7 @@ import org.springframework.util.StringUtils;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.*;
|
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.*;
|
||||||
|
|
||||||
@@ -55,6 +57,8 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
private final ApiTransformMapper apiTransformMapper;
|
private final ApiTransformMapper apiTransformMapper;
|
||||||
private final ApiPolicyRateLimitMapper apiPolicyRateLimitMapper;
|
private final ApiPolicyRateLimitMapper apiPolicyRateLimitMapper;
|
||||||
private final ApiFlowPublishMapper apiFlowPublishMapper;
|
private final ApiFlowPublishMapper apiFlowPublishMapper;
|
||||||
|
private final ApiDefinitionCredentialMapper apiDefinitionCredentialMapper;
|
||||||
|
private final ApiClientCredentialMapper apiClientCredentialMapper;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final StringRedisTemplate stringRedisTemplate;
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
private final ObjectProvider<ApiVersionService> apiVersionServiceProvider;
|
private final ObjectProvider<ApiVersionService> apiVersionServiceProvider;
|
||||||
@@ -129,6 +133,7 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
validateDuplication(reqVO, null);
|
validateDuplication(reqVO, null);
|
||||||
validateStructure(reqVO);
|
validateStructure(reqVO);
|
||||||
validatePolicies(reqVO);
|
validatePolicies(reqVO);
|
||||||
|
validateCredentials(reqVO.getCredentialIds());
|
||||||
|
|
||||||
ApiDefinitionDO definition = buildDefinitionDO(reqVO, null);
|
ApiDefinitionDO definition = buildDefinitionDO(reqVO, null);
|
||||||
apiDefinitionMapper.insert(definition);
|
apiDefinitionMapper.insert(definition);
|
||||||
@@ -136,6 +141,7 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
|
|
||||||
persistApiLevelTransforms(apiId, reqVO.getApiLevelTransforms());
|
persistApiLevelTransforms(apiId, reqVO.getApiLevelTransforms());
|
||||||
persistSteps(apiId, reqVO.getSteps());
|
persistSteps(apiId, reqVO.getSteps());
|
||||||
|
persistCredentialBindings(apiId, reqVO.getCredentialIds());
|
||||||
|
|
||||||
String operator = SecurityFrameworkUtils.getLoginUserNickname();
|
String operator = SecurityFrameworkUtils.getLoginUserNickname();
|
||||||
String description = String.format("创建 API (%s)", reqVO.getVersion());
|
String description = String.format("创建 API (%s)", reqVO.getVersion());
|
||||||
@@ -154,6 +160,7 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
validateDuplication(reqVO, existing.getId());
|
validateDuplication(reqVO, existing.getId());
|
||||||
validateStructure(reqVO);
|
validateStructure(reqVO);
|
||||||
validatePolicies(reqVO);
|
validatePolicies(reqVO);
|
||||||
|
validateCredentials(reqVO.getCredentialIds());
|
||||||
|
|
||||||
ApiDefinitionDO updateObj = buildDefinitionDO(reqVO, existing);
|
ApiDefinitionDO updateObj = buildDefinitionDO(reqVO, existing);
|
||||||
apiDefinitionMapper.updateById(updateObj);
|
apiDefinitionMapper.updateById(updateObj);
|
||||||
@@ -161,8 +168,10 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion());
|
invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion());
|
||||||
apiTransformMapper.deleteByApiId(existing.getId());
|
apiTransformMapper.deleteByApiId(existing.getId());
|
||||||
apiStepMapper.deleteByApiId(existing.getId());
|
apiStepMapper.deleteByApiId(existing.getId());
|
||||||
|
apiDefinitionCredentialMapper.deleteByApiId(existing.getId());
|
||||||
persistApiLevelTransforms(existing.getId(), reqVO.getApiLevelTransforms());
|
persistApiLevelTransforms(existing.getId(), reqVO.getApiLevelTransforms());
|
||||||
persistSteps(existing.getId(), reqVO.getSteps());
|
persistSteps(existing.getId(), reqVO.getSteps());
|
||||||
|
persistCredentialBindings(existing.getId(), reqVO.getCredentialIds());
|
||||||
invalidateCache(updateObj.getTenantId(), updateObj.getApiCode(), updateObj.getVersion());
|
invalidateCache(updateObj.getTenantId(), updateObj.getApiCode(), updateObj.getVersion());
|
||||||
} finally {
|
} finally {
|
||||||
if (skipSnapshot) {
|
if (skipSnapshot) {
|
||||||
@@ -186,6 +195,7 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion());
|
invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion());
|
||||||
apiTransformMapper.deleteByApiId(id);
|
apiTransformMapper.deleteByApiId(id);
|
||||||
apiStepMapper.deleteByApiId(id);
|
apiStepMapper.deleteByApiId(id);
|
||||||
|
apiDefinitionCredentialMapper.deleteByApiId(id);
|
||||||
apiDefinitionMapper.deleteById(id);
|
apiDefinitionMapper.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,12 +293,14 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
ApiFlowPublication publication = apiFlowPublishMapper.selectActiveByApiId(definition.getId())
|
ApiFlowPublication publication = apiFlowPublishMapper.selectActiveByApiId(definition.getId())
|
||||||
.map(this::convertPublication)
|
.map(this::convertPublication)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
List<ApiCredentialBinding> credentialBindings = loadCredentialBindings(definition.getId());
|
||||||
return ApiDefinitionAggregate.builder()
|
return ApiDefinitionAggregate.builder()
|
||||||
.definition(definition)
|
.definition(definition)
|
||||||
.steps(stepDefinitions)
|
.steps(stepDefinitions)
|
||||||
.apiLevelTransforms(apiTransforms)
|
.apiLevelTransforms(apiTransforms)
|
||||||
.rateLimitPolicy(rateLimitPolicy)
|
.rateLimitPolicy(rateLimitPolicy)
|
||||||
.publication(publication)
|
.publication(publication)
|
||||||
|
.credentialBindings(credentialBindings)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,6 +509,19 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateCredentials(List<Long> credentialIds) {
|
||||||
|
if (CollectionUtils.isEmpty(credentialIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<ApiClientCredentialDO> credentials = apiClientCredentialMapper.selectBatchIds(credentialIds);
|
||||||
|
long validCount = credentials == null ? 0 : credentials.stream()
|
||||||
|
.filter(credential -> credential != null && !Boolean.TRUE.equals(credential.getDeleted()))
|
||||||
|
.count();
|
||||||
|
if (validCount != credentialIds.size()) {
|
||||||
|
throw ServiceExceptionUtil.exception(API_CREDENTIAL_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Long resolveTenantIdentifier() {
|
private Long resolveTenantIdentifier() {
|
||||||
return TenantContextHolder.getTenantId();
|
return TenantContextHolder.getTenantId();
|
||||||
}
|
}
|
||||||
@@ -515,4 +540,74 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
return tenantPart + ":" + apiCode.toLowerCase(Locale.ROOT) + ":" + version;
|
return tenantPart + ":" + apiCode.toLowerCase(Locale.ROOT) + ":" + version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 先删除旧绑定,再对去重后的 credentialIds 批量插入,避免唯一约束冲突。
|
||||||
|
*/
|
||||||
|
private void persistCredentialBindings(Long apiId, List<Long> credentialIds) {
|
||||||
|
// 先逻辑删除当前 API 的旧绑定,保留历史,同时避免重复插入
|
||||||
|
apiDefinitionCredentialMapper.logicDeleteByApiId(apiId);
|
||||||
|
|
||||||
|
if (CollectionUtils.isEmpty(credentialIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去重后再查询有效凭证
|
||||||
|
List<Long> distinctIds = credentialIds.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
if (CollectionUtils.isEmpty(distinctIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ApiClientCredentialDO> credentials = apiClientCredentialMapper.selectBatchIds(distinctIds);
|
||||||
|
if (CollectionUtils.isEmpty(credentials)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ApiClientCredentialDO credential : credentials) {
|
||||||
|
if (credential == null || Boolean.TRUE.equals(credential.getDeleted())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ApiDefinitionCredentialDO relation = new ApiDefinitionCredentialDO();
|
||||||
|
relation.setId(null);
|
||||||
|
relation.setApiId(apiId);
|
||||||
|
relation.setCredentialId(credential.getId());
|
||||||
|
relation.setAppId(credential.getAppId());
|
||||||
|
relation.setDeleted(Boolean.FALSE);
|
||||||
|
apiDefinitionCredentialMapper.insert(relation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ApiCredentialBinding> loadCredentialBindings(Long apiId) {
|
||||||
|
List<ApiDefinitionCredentialDO> relations = apiDefinitionCredentialMapper.selectByApiId(apiId);
|
||||||
|
if (CollectionUtils.isEmpty(relations)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<Long> credentialIds = relations.stream()
|
||||||
|
.map(ApiDefinitionCredentialDO::getCredentialId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
Map<Long, ApiClientCredentialDO> credentialMap = Collections.emptyMap();
|
||||||
|
if (!CollectionUtils.isEmpty(credentialIds)) {
|
||||||
|
List<ApiClientCredentialDO> credentials = apiClientCredentialMapper.selectBatchIds(credentialIds);
|
||||||
|
if (!CollectionUtils.isEmpty(credentials)) {
|
||||||
|
credentialMap = credentials.stream()
|
||||||
|
.filter(credential -> credential != null && !Boolean.TRUE.equals(credential.getDeleted()))
|
||||||
|
.collect(Collectors.toMap(ApiClientCredentialDO::getId, c -> c, (a, b) -> a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<ApiCredentialBinding> bindings = new ArrayList<>(relations.size());
|
||||||
|
for (ApiDefinitionCredentialDO relation : relations) {
|
||||||
|
ApiClientCredentialDO credential = relation.getCredentialId() == null ? null : credentialMap.get(relation.getCredentialId());
|
||||||
|
ApiCredentialBinding binding = ApiCredentialBinding.builder()
|
||||||
|
.credentialId(relation.getCredentialId())
|
||||||
|
.appId(relation.getAppId())
|
||||||
|
.appName(credential != null ? credential.getAppName() : null)
|
||||||
|
.build();
|
||||||
|
bindings.add(binding);
|
||||||
|
}
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,13 +86,8 @@ public class ApiPolicyRateLimitServiceImpl implements ApiPolicyRateLimitService
|
|||||||
private void apply(ApiPolicySaveReqVO reqVO, ApiPolicyRateLimitDO target) {
|
private void apply(ApiPolicySaveReqVO reqVO, ApiPolicyRateLimitDO target) {
|
||||||
target.setName(StrUtil.trim(reqVO.getName()));
|
target.setName(StrUtil.trim(reqVO.getName()));
|
||||||
target.setType(StrUtil.trim(reqVO.getType()));
|
target.setType(StrUtil.trim(reqVO.getType()));
|
||||||
target.setConfig(normalizeNullable(reqVO.getConfig()));
|
target.setConfig(StrUtil.trim(reqVO.getConfig()));
|
||||||
target.setDescription(normalizeNullable(reqVO.getDescription()));
|
target.setDescription(StrUtil.trim(reqVO.getDescription()));
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeNullable(String value) {
|
|
||||||
String trimmed = StrUtil.trim(value);
|
|
||||||
return StrUtil.isEmpty(trimmed) ? null : trimmed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefi
|
|||||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionCompareRespVO;
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionCompareRespVO;
|
||||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionPageReqVO;
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionPageReqVO;
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionCredentialDO;
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
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.dal.dataobject.gateway.ApiTransformDO;
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||||
import com.zt.plat.module.databus.dal.mapper.gateway.ApiVersionMapper;
|
import com.zt.plat.module.databus.dal.mapper.gateway.ApiVersionMapper;
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper;
|
import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper;
|
||||||
|
import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionCredentialMapper;
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper;
|
import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper;
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper;
|
import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper;
|
||||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||||
@@ -47,6 +49,7 @@ public class ApiVersionServiceImpl implements ApiVersionService {
|
|||||||
private final ApiDefinitionMapper apiDefinitionMapper;
|
private final ApiDefinitionMapper apiDefinitionMapper;
|
||||||
private final ApiStepMapper apiStepMapper;
|
private final ApiStepMapper apiStepMapper;
|
||||||
private final ApiTransformMapper apiTransformMapper;
|
private final ApiTransformMapper apiTransformMapper;
|
||||||
|
private final ApiDefinitionCredentialMapper apiDefinitionCredentialMapper;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final ApiDefinitionService apiDefinitionService;
|
private final ApiDefinitionService apiDefinitionService;
|
||||||
|
|
||||||
@@ -191,6 +194,15 @@ public class ApiVersionServiceImpl implements ApiVersionService {
|
|||||||
snapshot.setApiLevelTransforms(ApiDefinitionConvert.INSTANCE.convertTransformList(apiTransforms));
|
snapshot.setApiLevelTransforms(ApiDefinitionConvert.INSTANCE.convertTransformList(apiTransforms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<ApiDefinitionCredentialDO> credentialRelations = apiDefinitionCredentialMapper.selectByApiId(apiId);
|
||||||
|
if (credentialRelations != null && !credentialRelations.isEmpty()) {
|
||||||
|
List<Long> credentialIds = credentialRelations.stream()
|
||||||
|
.map(ApiDefinitionCredentialDO::getCredentialId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
snapshot.setCredentialIds(credentialIds);
|
||||||
|
}
|
||||||
|
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,5 +61,6 @@ public interface GatewayServiceErrorCodeConstants {
|
|||||||
ErrorCode API_VERSION_SNAPSHOT_DESERIALIZE_FAILED = new ErrorCode(1_010_000_052, "API 版本快照反序列化失败");
|
ErrorCode API_VERSION_SNAPSHOT_DESERIALIZE_FAILED = new ErrorCode(1_010_000_052, "API 版本快照反序列化失败");
|
||||||
ErrorCode API_VERSION_ACTIVE_CANNOT_DELETE = new ErrorCode(1_010_000_053, "当前激活版本不允许删除");
|
ErrorCode API_VERSION_ACTIVE_CANNOT_DELETE = new ErrorCode(1_010_000_053, "当前激活版本不允许删除");
|
||||||
ErrorCode API_VERSION_API_MISMATCH = new ErrorCode(1_010_000_054, "两个版本不属于同一 API");
|
ErrorCode API_VERSION_API_MISMATCH = new ErrorCode(1_010_000_054, "两个版本不属于同一 API");
|
||||||
|
ErrorCode API_CREDENTIAL_UNAUTHORIZED = new ErrorCode(1_010_000_055, "当前凭证无权访问该 API");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ spring:
|
|||||||
host: 172.16.46.63 # 地址
|
host: 172.16.46.63 # 地址
|
||||||
port: 30379 # 端口
|
port: 30379 # 端口
|
||||||
database: 0 # 数据库索引
|
database: 0 # 数据库索引
|
||||||
|
username: zt-redis
|
||||||
|
password: P@ssword25
|
||||||
# password: 123456 # 密码,建议生产环境开启
|
# password: 123456 # 密码,建议生产环境开启
|
||||||
|
|
||||||
xxl:
|
xxl:
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ zt:
|
|||||||
- /databus/api/portal/**
|
- /databus/api/portal/**
|
||||||
ignore-tables:
|
ignore-tables:
|
||||||
- databus_api_client_credential
|
- databus_api_client_credential
|
||||||
|
- databus_api_definition_credential
|
||||||
# DataBus 数据同步服务端配置
|
# DataBus 数据同步服务端配置
|
||||||
databus:
|
databus:
|
||||||
sync:
|
sync:
|
||||||
|
|||||||
@@ -28,10 +28,16 @@ import java.util.UUID;
|
|||||||
public final class DatabusApiInvocationExample {
|
public final class DatabusApiInvocationExample {
|
||||||
|
|
||||||
public static final String TIMESTAMP = Long.toString(System.currentTimeMillis());
|
public static final String TIMESTAMP = Long.toString(System.currentTimeMillis());
|
||||||
private static final String APP_ID = "ztmy";
|
|
||||||
private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ=";
|
private static final String APP_ID = "iwork";
|
||||||
|
private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk=";
|
||||||
|
// private static final String APP_ID = "ztmy";
|
||||||
|
// private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ=";
|
||||||
private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
||||||
private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/callback/v1";
|
// private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/callback/v1";
|
||||||
|
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/callback/v1";
|
||||||
|
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/callback/v1";
|
||||||
|
private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456";
|
||||||
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
||||||
.connectTimeout(Duration.ofSeconds(5))
|
.connectTimeout(Duration.ofSeconds(5))
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ spring:
|
|||||||
host: 172.16.46.63 # 地址
|
host: 172.16.46.63 # 地址
|
||||||
port: 30379 # 端口
|
port: 30379 # 端口
|
||||||
database: 1 # 数据库索引
|
database: 1 # 数据库索引
|
||||||
# password: 123456 # 密码,建议生产环境开启
|
username: zt-redis
|
||||||
|
password: P@ssword25
|
||||||
|
|
||||||
--- #################### MQ 消息队列相关配置 ####################
|
--- #################### MQ 消息队列相关配置 ####################
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user