Merge branch 'dev-klw' into test
* dev-klw: 清理与ztcloud中重复的代码,改为 jar 包方式引用 ztcloud # Conflicts: # zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java # zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsCallbackController.java # zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/sms/SmsSendMessage.java
This commit is contained in:
@@ -1,195 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>zt-module-databus</artifactId>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>zt-module-databus-server</artifactId>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
Databus 模块。
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Cloud 基础 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-env</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 依赖服务 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-module-system-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-module-infra-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-module-databus-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- DataBus Server Starter -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-databus-server</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-biz-data-permission</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 远程调用相关 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-rpc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Registry 注册中心相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Config 配置中心相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 定时任务相关 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-job</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息队列相关 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-mq</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-monitor</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-biz-business</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Integration & Flow Orchestration -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-http</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-scripting</artifactId>
|
||||
</dependency>
|
||||
<!-- Reactive HTTP client for internal REST orchestration -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Expression evaluation & caching utilities -->
|
||||
<dependency>
|
||||
<groupId>com.ibm.jsonata4java</groupId>
|
||||
<artifactId>JSONata4Java</artifactId>
|
||||
<version>2.5.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mvel</groupId>
|
||||
<artifactId>mvel2</artifactId>
|
||||
<version>2.5.2.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing support -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<version>4.12.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zt.plat</groupId>
|
||||
<artifactId>zt-spring-boot-starter-databus-client</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.zt.plat.module.databus.api;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.util.monitor.TracerUtils;
|
||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||
import com.zt.plat.framework.common.util.servlet.ServletUtils;
|
||||
import com.zt.plat.module.databus.api.dto.ApiAccessLogCreateReq;
|
||||
import com.zt.plat.module.databus.api.provider.DatabusAccessLogProviderApi;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiAccessLogService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
*
|
||||
* 2026/1/21 10:05
|
||||
*/
|
||||
@RestController
|
||||
public class DatabusAccessLogProviderApiImpl implements DatabusAccessLogProviderApi {
|
||||
|
||||
@Resource
|
||||
private ApiAccessLogService apiAccessLogService;
|
||||
|
||||
@Override
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> add(Map<String, String> headers, ApiAccessLogCreateReq req) {
|
||||
ApiAccessLogDO logDO = new ApiAccessLogDO();
|
||||
BeanUtils.copyProperties(req, logDO);
|
||||
logDO.setTraceId(TracerUtils.getTraceId());
|
||||
logDO.setClientIp(ServletUtils.getClientIP());
|
||||
logDO.setCreateTime(LocalDateTime.now());
|
||||
logDO.setUpdateTime(LocalDateTime.now());
|
||||
logDO.setCreator("1");
|
||||
logDO.setUpdater("1");
|
||||
|
||||
apiAccessLogService.create(logDO);
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiAccessLogConvert;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogPageReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogRespVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiAccessLogService;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* Databus API 访问日志控制器。
|
||||
*/
|
||||
@Tag(name = "管理后台 - Databus API 访问日志")
|
||||
@RestController
|
||||
@RequestMapping("/databus/gateway/access-log")
|
||||
@Validated
|
||||
public class ApiAccessLogController {
|
||||
|
||||
@Resource
|
||||
private ApiAccessLogService apiAccessLogService;
|
||||
|
||||
@Resource
|
||||
private ApiDefinitionService apiDefinitionService;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取访问日志详情")
|
||||
@Parameter(name = "id", description = "日志编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('databus:gateway:access-log:query')")
|
||||
public CommonResult<ApiAccessLogRespVO> get(@RequestParam("id") Long id) {
|
||||
ApiAccessLogDO logDO = apiAccessLogService.get(id);
|
||||
ApiAccessLogRespVO respVO = ApiAccessLogConvert.INSTANCE.convert(logDO);
|
||||
enrichDefinitionInfo(respVO);
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询访问日志")
|
||||
@PreAuthorize("@ss.hasPermission('databus:gateway:access-log:query')")
|
||||
public CommonResult<PageResult<ApiAccessLogRespVO>> page(@Valid ApiAccessLogPageReqVO pageReqVO) {
|
||||
PageResult<ApiAccessLogDO> pageResult = apiAccessLogService.getPage(pageReqVO);
|
||||
PageResult<ApiAccessLogRespVO> result = ApiAccessLogConvert.INSTANCE.convertPage(pageResult);
|
||||
enrichDefinitionInfo(result.getList());
|
||||
return success(result);
|
||||
}
|
||||
|
||||
private void enrichDefinitionInfo(List<ApiAccessLogRespVO> list) {
|
||||
// 对分页结果批量补充 API 描述,使用本地缓存减少重复查询
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> cache = new HashMap<>(list.size());
|
||||
list.forEach(item -> {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
String cacheKey = buildCacheKey(item.getApiCode(), item.getApiVersion());
|
||||
if (!cache.containsKey(cacheKey)) {
|
||||
cache.put(cacheKey, resolveApiDescription(item.getApiCode(), item.getApiVersion()));
|
||||
}
|
||||
item.setApiDescription(cache.get(cacheKey));
|
||||
});
|
||||
}
|
||||
|
||||
private void enrichDefinitionInfo(ApiAccessLogRespVO item) {
|
||||
// 单条数据同样需要补全描述信息
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
item.setApiDescription(resolveApiDescription(item.getApiCode(), item.getApiVersion()));
|
||||
}
|
||||
|
||||
private String resolveApiDescription(String apiCode, String apiVersion) {
|
||||
if (!StringUtils.hasText(apiCode)) {
|
||||
return null;
|
||||
}
|
||||
String normalizedVersion = StringUtils.hasText(apiVersion) ? apiVersion.trim() : apiVersion;
|
||||
// 通过网关定义服务补全 API 描述,提升页面可读性
|
||||
return apiDefinitionService.findByCodeAndVersionIncludingInactive(apiCode, normalizedVersion)
|
||||
.map(aggregate -> aggregate.getDefinition() != null ? aggregate.getDefinition().getDescription() : null)
|
||||
.filter(StringUtils::hasText)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private String buildCacheKey(String apiCode, String apiVersion) {
|
||||
// 组合唯一键,避免重复查询相同的 API 描述
|
||||
return (apiCode == null ? "" : apiCode) + "#" + (apiVersion == null ? "" : apiVersion);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiClientCredentialConvert;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialPageReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialSaveReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialSimpleRespVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiAnonymousUserService;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
import static org.springframework.util.CollectionUtils.isEmpty;
|
||||
|
||||
@Tag(name = "管理后台 - API 客户端凭证")
|
||||
@RestController
|
||||
@RequestMapping("/databus/gateway/credential")
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class ApiClientCredentialController {
|
||||
|
||||
private final ApiClientCredentialService credentialService;
|
||||
private final ApiAnonymousUserService anonymousUserService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询客户端凭证")
|
||||
public CommonResult<PageResult<ApiClientCredentialRespVO>> page(ApiClientCredentialPageReqVO reqVO) {
|
||||
PageResult<ApiClientCredentialDO> page = credentialService.getPage(reqVO);
|
||||
PageResult<ApiClientCredentialRespVO> respPage = ApiClientCredentialConvert.INSTANCE.convertPage(page);
|
||||
populateAnonymousInfo(respPage.getList());
|
||||
return success(respPage);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "查询凭证详情")
|
||||
public CommonResult<ApiClientCredentialRespVO> get(@RequestParam("id") Long id) {
|
||||
ApiClientCredentialDO credential = credentialService.get(id);
|
||||
ApiClientCredentialRespVO respVO = ApiClientCredentialConvert.INSTANCE.convert(credential);
|
||||
populateAnonymousInfo(List.of(respVO));
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "新增客户端凭证")
|
||||
public CommonResult<Long> create(@Valid @RequestBody ApiClientCredentialSaveReqVO reqVO) {
|
||||
return success(credentialService.create(reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新客户端凭证")
|
||||
public CommonResult<Boolean> update(@Valid @RequestBody ApiClientCredentialSaveReqVO reqVO) {
|
||||
credentialService.update(reqVO);
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除客户端凭证")
|
||||
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
|
||||
credentialService.delete(id);
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@GetMapping("/list-simple")
|
||||
@Operation(summary = "获取启用的凭证列表(精简)")
|
||||
public CommonResult<List<ApiClientCredentialSimpleRespVO>> listSimple() {
|
||||
List<ApiClientCredentialDO> list = credentialService.listEnabled();
|
||||
return success(ApiClientCredentialConvert.INSTANCE.convertSimpleList(list));
|
||||
}
|
||||
|
||||
private void populateAnonymousInfo(List<ApiClientCredentialRespVO> list) {
|
||||
if (isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
list.stream()
|
||||
.filter(item -> Boolean.TRUE.equals(item.getAllowAnonymous()) && item.getAnonymousUserId() != null)
|
||||
.forEach(item -> anonymousUserService.find(item.getAnonymousUserId())
|
||||
.ifPresent(details -> item.setAnonymousUserNickname(details.getNickname())));
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiDefinitionConvert;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionDetailRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionPageReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSummaryRespVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.IntegrationFlowManager;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_DEFINITION_NOT_FOUND;
|
||||
|
||||
@Tag(name = "管理后台 - API 定义管理")
|
||||
@RestController
|
||||
@RequestMapping("/databus/gateway/definition")
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class ApiDefinitionController {
|
||||
|
||||
private final ApiDefinitionService apiDefinitionService;
|
||||
private final IntegrationFlowManager integrationFlowManager;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询 API 定义")
|
||||
public CommonResult<PageResult<ApiDefinitionSummaryRespVO>> getDefinitionPage(@Valid ApiDefinitionPageReqVO reqVO) {
|
||||
PageResult<ApiDefinitionDO> pageResult = apiDefinitionService.getPage(reqVO);
|
||||
return success(ApiDefinitionConvert.INSTANCE.convertPage(pageResult));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取 API 定义详情")
|
||||
public CommonResult<ApiDefinitionDetailRespVO> getDefinition(@PathVariable("id") Long id) {
|
||||
ApiDefinitionAggregate aggregate = apiDefinitionService.findById(id)
|
||||
.orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND));
|
||||
return success(ApiDefinitionConvert.INSTANCE.convert(aggregate));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建 API 定义")
|
||||
public CommonResult<Long> createDefinition(@Valid @RequestBody ApiDefinitionSaveReqVO reqVO) {
|
||||
Long id = apiDefinitionService.create(reqVO);
|
||||
integrationFlowManager.refresh(reqVO.getApiCode(), reqVO.getVersion());
|
||||
return success(id);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Operation(summary = "更新 API 定义")
|
||||
public CommonResult<Boolean> updateDefinition(@Valid @RequestBody ApiDefinitionSaveReqVO reqVO) {
|
||||
ApiDefinitionAggregate before = apiDefinitionService.findById(reqVO.getId())
|
||||
.orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND));
|
||||
apiDefinitionService.update(reqVO);
|
||||
integrationFlowManager.refresh(before.getDefinition().getApiCode(), before.getDefinition().getVersion());
|
||||
integrationFlowManager.refresh(reqVO.getApiCode(), reqVO.getVersion());
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除 API 定义")
|
||||
public CommonResult<Boolean> deleteDefinition(@PathVariable("id") Long id) {
|
||||
ApiDefinitionAggregate aggregate = apiDefinitionService.findById(id)
|
||||
.orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND));
|
||||
apiDefinitionService.delete(id);
|
||||
integrationFlowManager.refresh(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiDefinitionConvert;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionDetailRespVO;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayExecutionService;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.IntegrationFlowManager;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - API 门户")
|
||||
@RestController
|
||||
@RequestMapping("/databus/gateway")
|
||||
@RequiredArgsConstructor
|
||||
public class ApiGatewayController {
|
||||
|
||||
private final ApiGatewayExecutionService executionService;
|
||||
private final ApiDefinitionService apiDefinitionService;
|
||||
private final IntegrationFlowManager integrationFlowManager;
|
||||
|
||||
@PostMapping(value = "/invoke", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "测试调用 API 编排")
|
||||
public ResponseEntity<ApiGatewayResponse> invoke(@RequestBody ApiGatewayInvokeReqVO reqVO) {
|
||||
return executionService.invokeForDebug(reqVO);
|
||||
}
|
||||
|
||||
@GetMapping("/definitions")
|
||||
@Operation(summary = "获取当前已发布 API 配置")
|
||||
public CommonResult<List<ApiDefinitionDetailRespVO>> listDefinitions() {
|
||||
List<ApiDefinitionDetailRespVO> definitions = apiDefinitionService.loadActiveDefinitions().stream()
|
||||
.map(ApiDefinitionConvert.INSTANCE::convert)
|
||||
.collect(Collectors.toList());
|
||||
return success(definitions);
|
||||
}
|
||||
|
||||
@PostMapping("/cache/refresh")
|
||||
@Operation(summary = "刷新 API 缓存")
|
||||
public CommonResult<Boolean> refreshCache() {
|
||||
apiDefinitionService.refreshAllCache();
|
||||
integrationFlowManager.refreshAll();
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiPolicyRateLimitConvert;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySaveReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySimpleRespVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiPolicyRateLimitService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_POLICY_NOT_FOUND;
|
||||
|
||||
@Tag(name = "管理后台 - 网关限流策略")
|
||||
@RestController
|
||||
@RequestMapping("/databus/gateway/policy/rate-limit")
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class ApiPolicyRateLimitController {
|
||||
|
||||
private final ApiPolicyRateLimitService rateLimitService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询限流策略")
|
||||
public CommonResult<PageResult<ApiPolicyRespVO>> getRateLimitPolicyPage(@Valid ApiPolicyPageReqVO reqVO) {
|
||||
PageResult<ApiPolicyRateLimitDO> pageResult = rateLimitService.getPage(reqVO);
|
||||
return success(ApiPolicyRateLimitConvert.INSTANCE.convertPage(pageResult));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "查询限流策略详情")
|
||||
public CommonResult<ApiPolicyRespVO> getRateLimitPolicy(@PathVariable("id") Long id) {
|
||||
ApiPolicyRateLimitDO policy = rateLimitService.get(id)
|
||||
.orElseThrow(() -> ServiceExceptionUtil.exception(API_POLICY_NOT_FOUND));
|
||||
return success(ApiPolicyRateLimitConvert.INSTANCE.convert(policy));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获取限流策略精简列表")
|
||||
public CommonResult<List<ApiPolicySimpleRespVO>> getRateLimitPolicySimpleList() {
|
||||
List<ApiPolicyRateLimitDO> list = rateLimitService.getSimpleList();
|
||||
return success(ApiPolicyRateLimitConvert.INSTANCE.convertSimpleList(list));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "创建限流策略")
|
||||
public CommonResult<Long> createRateLimitPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) {
|
||||
Long id = rateLimitService.create(reqVO);
|
||||
return success(id);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Operation(summary = "更新限流策略")
|
||||
public CommonResult<Boolean> updateRateLimitPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) {
|
||||
rateLimitService.update(reqVO);
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除限流策略")
|
||||
public CommonResult<Boolean> deleteRateLimitPolicy(@PathVariable("id") Long id) {
|
||||
rateLimitService.delete(id);
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiVersionConvert;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionCompareRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionDetailRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionPageReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionRollbackReqVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiVersionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* API 版本历史控制器。
|
||||
*/
|
||||
@Tag(name = "管理后台 - API 版本历史")
|
||||
@RestController
|
||||
@RequestMapping("/databus/gateway/version")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class ApiVersionController {
|
||||
|
||||
@Resource
|
||||
private ApiVersionService apiVersionService;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取 API 版本详情")
|
||||
@Parameter(name = "id", description = "版本编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('databus:gateway:version:query')")
|
||||
public CommonResult<ApiVersionDetailRespVO> getVersion(@RequestParam("id") Long id) {
|
||||
ApiVersionDO versionDO = apiVersionService.getVersion(id);
|
||||
ApiVersionDetailRespVO respVO = ApiVersionConvert.INSTANCE.convertDetail(versionDO);
|
||||
|
||||
// 反序列化快照数据
|
||||
if (versionDO.getSnapshotData() != null) {
|
||||
try {
|
||||
ApiDefinitionSaveReqVO snapshot = objectMapper.readValue(versionDO.getSnapshotData(), ApiDefinitionSaveReqVO.class);
|
||||
respVO.setSnapshotData(snapshot);
|
||||
} catch (JsonProcessingException ex) {
|
||||
log.error("反序列化版本快照失败, versionId={}", id, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询 API 版本列表")
|
||||
@PreAuthorize("@ss.hasPermission('databus:gateway:version:query')")
|
||||
public CommonResult<PageResult<ApiVersionRespVO>> getVersionPage(@Valid ApiVersionPageReqVO pageReqVO) {
|
||||
PageResult<ApiVersionDO> pageResult = apiVersionService.getVersionPage(pageReqVO);
|
||||
return success(ApiVersionConvert.INSTANCE.convertPage(pageResult));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "查询指定 API 的全部版本")
|
||||
@PreAuthorize("@ss.hasPermission('databus:gateway:version:query')")
|
||||
public CommonResult<java.util.List<ApiVersionRespVO>> getVersionList(@RequestParam("apiId") Long apiId) {
|
||||
return success(ApiVersionConvert.INSTANCE.convertList(apiVersionService.getVersionListByApiId(apiId)));
|
||||
}
|
||||
|
||||
@PutMapping("/rollback")
|
||||
@Operation(summary = "回滚到指定版本")
|
||||
@PreAuthorize("@ss.hasPermission('databus:gateway:version:rollback')")
|
||||
public CommonResult<Boolean> rollbackToVersion(@Valid @RequestBody ApiVersionRollbackReqVO reqVO) {
|
||||
apiVersionService.rollbackToVersion(reqVO.getId(), reqVO.getRemark());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/compare")
|
||||
@Operation(summary = "对比两个版本差异")
|
||||
@PreAuthorize("@ss.hasPermission('databus:gateway:version:query')")
|
||||
public CommonResult<ApiVersionCompareRespVO> compareVersions(
|
||||
@RequestParam("sourceId") Long sourceId,
|
||||
@RequestParam("targetId") Long targetId) {
|
||||
return success(apiVersionService.compareVersions(sourceId, targetId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.convert;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogRespVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiAccessLogConvert {
|
||||
|
||||
ApiAccessLogConvert INSTANCE = Mappers.getMapper(ApiAccessLogConvert.class);
|
||||
|
||||
@Mapping(target = "statusDesc", expression = "java(statusDesc(bean.getStatus()))")
|
||||
@Mapping(target = "responseStatusText", expression = "java(resolveHttpStatusText(bean.getResponseStatus()))")
|
||||
ApiAccessLogRespVO convert(ApiAccessLogDO bean);
|
||||
|
||||
List<ApiAccessLogRespVO> convertList(List<ApiAccessLogDO> list);
|
||||
|
||||
default PageResult<ApiAccessLogRespVO> convertPage(PageResult<ApiAccessLogDO> page) {
|
||||
if (page == null) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
PageResult<ApiAccessLogRespVO> result = new PageResult<>();
|
||||
result.setList(convertList(page.getList()));
|
||||
result.setTotal(page.getTotal());
|
||||
return result;
|
||||
}
|
||||
|
||||
default String statusDesc(Integer status) {
|
||||
// 将数字状态码转换为中文描述,方便前端直接展示
|
||||
if (status == null) {
|
||||
return "未知";
|
||||
}
|
||||
return switch (status) {
|
||||
case 0 -> "成功";
|
||||
case 1 -> "客户端错误";
|
||||
case 2 -> "服务端错误";
|
||||
default -> "未知";
|
||||
};
|
||||
}
|
||||
|
||||
default String resolveHttpStatusText(Integer status) {
|
||||
// 统一使用 Spring 的 HttpStatus 解析出标准文案
|
||||
if (status == null) {
|
||||
return null;
|
||||
}
|
||||
HttpStatus resolved = HttpStatus.resolve(status);
|
||||
return resolved != null ? resolved.getReasonPhrase() : null;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.convert;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialSimpleRespVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper
|
||||
public interface ApiClientCredentialConvert {
|
||||
|
||||
ApiClientCredentialConvert INSTANCE = Mappers.getMapper(ApiClientCredentialConvert.class);
|
||||
|
||||
ApiClientCredentialRespVO convert(ApiClientCredentialDO bean);
|
||||
|
||||
List<ApiClientCredentialRespVO> convertList(List<ApiClientCredentialDO> list);
|
||||
|
||||
default PageResult<ApiClientCredentialRespVO> convertPage(PageResult<ApiClientCredentialDO> page) {
|
||||
if (page == null) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
PageResult<ApiClientCredentialRespVO> result = new PageResult<>();
|
||||
result.setList(convertList(page.getList()));
|
||||
result.setTotal(page.getTotal());
|
||||
return result;
|
||||
}
|
||||
|
||||
default List<ApiClientCredentialSimpleRespVO> convertSimpleList(List<ApiClientCredentialDO> list) {
|
||||
return list == null ? List.of() : list.stream().map(item -> {
|
||||
ApiClientCredentialSimpleRespVO vo = new ApiClientCredentialSimpleRespVO();
|
||||
vo.setId(item.getId());
|
||||
vo.setAppId(item.getAppId());
|
||||
vo.setAppName(item.getAppName());
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.convert;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||
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;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiTransformDefinition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
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
|
||||
public interface ApiDefinitionConvert {
|
||||
|
||||
ApiDefinitionConvert INSTANCE = Mappers.getMapper(ApiDefinitionConvert.class);
|
||||
|
||||
ApiDefinitionSummaryRespVO convert(ApiDefinitionDO bean);
|
||||
|
||||
List<ApiDefinitionSummaryRespVO> convertList(List<ApiDefinitionDO> list);
|
||||
|
||||
default PageResult<ApiDefinitionSummaryRespVO> convertPage(PageResult<ApiDefinitionDO> page) {
|
||||
if (page == null) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
PageResult<ApiDefinitionSummaryRespVO> result = new PageResult<>();
|
||||
List<ApiDefinitionSummaryRespVO> list = convertList(page.getList());
|
||||
result.setList(list == null ? new ArrayList<>() : list);
|
||||
result.setTotal(page.getTotal());
|
||||
return result;
|
||||
}
|
||||
|
||||
default ApiDefinitionDetailRespVO convert(ApiDefinitionAggregate aggregate) {
|
||||
if (aggregate == null) {
|
||||
return null;
|
||||
}
|
||||
ApiDefinitionDetailRespVO detail = BeanUtils.toBean(aggregate.getDefinition(), ApiDefinitionDetailRespVO.class);
|
||||
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;
|
||||
}
|
||||
|
||||
default List<ApiDefinitionStepRespVO> convertSteps(List<ApiStepDefinition> steps) {
|
||||
if (CollUtil.isEmpty(steps)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return steps.stream()
|
||||
.sorted(Comparator.comparing(step -> step.getStep().getStepOrder() == null ? Integer.MAX_VALUE : step.getStep().getStepOrder()))
|
||||
.map(step -> {
|
||||
ApiDefinitionStepRespVO resp = BeanUtils.toBean(step.getStep(), ApiDefinitionStepRespVO.class);
|
||||
resp.setTransforms(convertStepTransforms(step.getStep().getApiId(), step.getStep().getId(), step.getTransforms()));
|
||||
return resp;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
default List<ApiDefinitionTransformRespVO> convertTransforms(Long apiId, Collection<ApiTransformDefinition> transforms) {
|
||||
if (CollUtil.isEmpty(transforms)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return transforms.stream()
|
||||
.sorted(Comparator.comparing(ApiTransformDefinition::getPhase, Comparator.nullsLast(String::compareTo)))
|
||||
.map(transform -> {
|
||||
ApiDefinitionTransformRespVO resp = BeanUtils.toBean(transform, ApiDefinitionTransformRespVO.class);
|
||||
resp.setApiId(apiId);
|
||||
resp.setStepId(null);
|
||||
return resp;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
default List<ApiDefinitionTransformRespVO> convertStepTransforms(Long apiId, Long stepId, List<ApiTransformDefinition> transforms) {
|
||||
if (CollUtil.isEmpty(transforms)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return transforms.stream()
|
||||
.sorted(Comparator.comparing(ApiTransformDefinition::getPhase, Comparator.nullsLast(String::compareTo)))
|
||||
.map(transform -> {
|
||||
ApiDefinitionTransformRespVO resp = BeanUtils.toBean(transform, ApiDefinitionTransformRespVO.class);
|
||||
resp.setApiId(apiId);
|
||||
resp.setStepId(stepId);
|
||||
return resp;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
default ApiDefinitionPublicationRespVO convert(ApiFlowPublication publication) {
|
||||
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)
|
||||
*/
|
||||
default List<ApiDefinitionStepSaveReqVO> convertStepList(List<ApiStepDO> steps) {
|
||||
if (CollUtil.isEmpty(steps)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return steps.stream()
|
||||
.sorted(Comparator.comparing(step -> step.getStepOrder() == null ? Integer.MAX_VALUE : step.getStepOrder()))
|
||||
.map(step -> BeanUtils.toBean(step, ApiDefinitionStepSaveReqVO.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换变换列表(DO -> SaveReqVO)
|
||||
*/
|
||||
default List<ApiDefinitionTransformSaveReqVO> convertTransformList(List<ApiTransformDO> transforms) {
|
||||
if (CollUtil.isEmpty(transforms)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return transforms.stream()
|
||||
.map(transform -> BeanUtils.toBean(transform, ApiDefinitionTransformSaveReqVO.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.convert;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySimpleRespVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiPolicyRateLimitConvert {
|
||||
|
||||
ApiPolicyRateLimitConvert INSTANCE = Mappers.getMapper(ApiPolicyRateLimitConvert.class);
|
||||
|
||||
ApiPolicyRespVO convert(ApiPolicyRateLimitDO bean);
|
||||
|
||||
List<ApiPolicyRespVO> convertList(List<ApiPolicyRateLimitDO> list);
|
||||
|
||||
PageResult<ApiPolicyRespVO> convertPage(PageResult<ApiPolicyRateLimitDO> page);
|
||||
|
||||
List<ApiPolicySimpleRespVO> convertSimpleList(List<ApiPolicyRateLimitDO> list);
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.convert;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionDetailRespVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionRespVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API 版本历史 Convert。
|
||||
*/
|
||||
@Mapper
|
||||
public interface ApiVersionConvert {
|
||||
|
||||
ApiVersionConvert INSTANCE = Mappers.getMapper(ApiVersionConvert.class);
|
||||
|
||||
ApiVersionRespVO convert(ApiVersionDO bean);
|
||||
|
||||
PageResult<ApiVersionRespVO> convertPage(PageResult<ApiVersionDO> page);
|
||||
|
||||
List<ApiVersionRespVO> convertList(List<ApiVersionDO> list);
|
||||
|
||||
@Mapping(target = "snapshotData", ignore = true)
|
||||
ApiVersionDetailRespVO convertDetail(ApiVersionDO bean);
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ApiGatewayInvokeReqVO {
|
||||
|
||||
@Schema(description = "API 编码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String apiCode;
|
||||
|
||||
@Schema(description = "API 版本", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String version;
|
||||
|
||||
@Schema(description = "请求头,可选")
|
||||
private Map<String, String> headers = new HashMap<>();
|
||||
|
||||
@Schema(description = "请求参数,可选")
|
||||
private Map<String, Object> queryParams = new HashMap<>();
|
||||
|
||||
@Schema(description = "请求体")
|
||||
private Object payload;
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
/**
|
||||
* Databus API 访问日志分页查询 VO。
|
||||
*/
|
||||
@Schema(description = "管理后台 - Databus API 访问日志分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ApiAccessLogPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "追踪 ID", example = "c8a3d52f-42c8-4b5d-9e26-8c2cc89f6bb5")
|
||||
private String traceId;
|
||||
|
||||
@Schema(description = "API 编码", example = "user.query")
|
||||
private String apiCode;
|
||||
|
||||
@Schema(description = "API 版本", example = "v1")
|
||||
private String apiVersion;
|
||||
|
||||
@Schema(description = "HTTP 方法", example = "POST")
|
||||
private String requestMethod;
|
||||
|
||||
@Schema(description = "响应 HTTP 状态", example = "200")
|
||||
private Integer responseStatus;
|
||||
|
||||
@Schema(description = "访问状态", example = "0")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "客户端 IP", example = "192.168.0.10")
|
||||
private String clientIp;
|
||||
|
||||
@Schema(description = "租户编号", example = "1")
|
||||
private Long tenantId;
|
||||
|
||||
@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;
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Databus API 访问日志 Response VO。
|
||||
*/
|
||||
@Schema(description = "管理后台 - Databus API 访问日志 Response VO")
|
||||
@Data
|
||||
public class ApiAccessLogRespVO {
|
||||
|
||||
@Schema(description = "日志编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "追踪 ID", example = "c8a3d52f-42c8-4b5d-9e26-8c2cc89f6bb5")
|
||||
private String traceId;
|
||||
|
||||
@Schema(description = "API 编码", example = "user.query")
|
||||
private String apiCode;
|
||||
|
||||
@Schema(description = "API 描述", example = "用户查询服务")
|
||||
private String apiDescription;
|
||||
|
||||
@Schema(description = "API 版本", example = "v1")
|
||||
private String apiVersion;
|
||||
|
||||
@Schema(description = "HTTP 方法", example = "POST")
|
||||
private String requestMethod;
|
||||
|
||||
@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;
|
||||
|
||||
@Schema(description = "请求头(JSON)")
|
||||
private String requestHeaders;
|
||||
|
||||
@Schema(description = "请求体(JSON)")
|
||||
private String requestBody;
|
||||
|
||||
@Schema(description = "响应 HTTP 状态", example = "200")
|
||||
private Integer responseStatus;
|
||||
|
||||
@Schema(description = "响应 HTTP 状态说明", example = "OK")
|
||||
private String responseStatusText;
|
||||
|
||||
@Schema(description = "响应提示", example = "OK")
|
||||
private String responseMessage;
|
||||
|
||||
@Schema(description = "响应体(JSON)")
|
||||
private String responseBody;
|
||||
|
||||
@Schema(description = "访问状态", example = "0")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "访问状态展示文案", example = "成功")
|
||||
private String statusDesc;
|
||||
|
||||
@Schema(description = "错误码", example = "DAT-001")
|
||||
private String errorCode;
|
||||
|
||||
@Schema(description = "错误信息", example = "API 调用失败")
|
||||
private String errorMessage;
|
||||
|
||||
@Schema(description = "异常堆栈")
|
||||
private String exceptionStack;
|
||||
|
||||
@Schema(description = "客户端 IP", example = "192.168.0.10")
|
||||
private String clientIp;
|
||||
|
||||
@Schema(description = "User-Agent")
|
||||
private String userAgent;
|
||||
|
||||
@Schema(description = "租户编号", example = "1")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "请求耗时(毫秒)", example = "123")
|
||||
private Long duration;
|
||||
|
||||
@Schema(description = "请求时间")
|
||||
private LocalDateTime requestTime;
|
||||
|
||||
@Schema(description = "响应时间")
|
||||
private LocalDateTime responseTime;
|
||||
|
||||
@Schema(description = "执行步骤(JSON)")
|
||||
private String stepResults;
|
||||
|
||||
@Schema(description = "额外调试信息(JSON)")
|
||||
private String extra;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.credential;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Schema(description = "管理后台 - API 客户端凭证分页查询 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiClientCredentialPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "关键字,匹配 appId 或名称", example = "databus-app")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enabled;
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.credential;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - API 客户端凭证 Response VO")
|
||||
@Data
|
||||
public class ApiClientCredentialRespVO {
|
||||
|
||||
@Schema(description = "记录编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "应用标识", example = "databus-app")
|
||||
private String appId;
|
||||
|
||||
@Schema(description = "应用名称", example = "数据总线默认应用")
|
||||
private String appName;
|
||||
|
||||
@Schema(description = "加密密钥", example = "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=")
|
||||
private String encryptionKey;
|
||||
|
||||
@Schema(description = "加密算法", example = "AES")
|
||||
private String encryptionType;
|
||||
|
||||
@Schema(description = "签名算法", example = "MD5")
|
||||
private String signatureType;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "备注", example = "默认应用凭证")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "允许匿名访问", example = "false")
|
||||
private Boolean allowAnonymous;
|
||||
|
||||
@Schema(description = "匿名访问固定用户 ID", example = "1024")
|
||||
private Long anonymousUserId;
|
||||
|
||||
@Schema(description = "匿名访问固定用户昵称", example = "张三")
|
||||
private String anonymousUserNickname;
|
||||
|
||||
@Schema(description = "是否启用加密", example = "true")
|
||||
private Boolean enableEncryption;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "最后更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.credential;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - API 客户端凭证保存 Request VO")
|
||||
@Data
|
||||
public class ApiClientCredentialSaveReqVO {
|
||||
|
||||
@Schema(description = "记录编号,仅更新时必填", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "应用标识", example = "databus-app")
|
||||
@NotBlank(message = "应用标识不能为空")
|
||||
private String appId;
|
||||
|
||||
@Schema(description = "应用名称", example = "数据总线默认应用")
|
||||
private String appName;
|
||||
|
||||
@Schema(description = "加密密钥", example = "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=")
|
||||
@NotBlank(message = "加密密钥不能为空")
|
||||
private String encryptionKey;
|
||||
|
||||
@Schema(description = "加密算法", example = "AES")
|
||||
@NotBlank(message = "加密算法不能为空")
|
||||
private String encryptionType;
|
||||
|
||||
@Schema(description = "签名算法", example = "MD5")
|
||||
@NotBlank(message = "签名算法不能为空")
|
||||
private String signatureType;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
@NotNull(message = "启用状态不能为空")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "备注", example = "默认应用凭证")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "允许匿名访问", example = "false")
|
||||
@NotNull(message = "匿名访问标识不能为空")
|
||||
private Boolean allowAnonymous;
|
||||
|
||||
@Schema(description = "匿名访问固定用户 ID", example = "1024")
|
||||
private Long anonymousUserId;
|
||||
|
||||
@Schema(description = "是否启用加密", example = "true")
|
||||
@NotNull(message = "启用加密标识不能为空")
|
||||
private Boolean enableEncryption;
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.credential;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - API 客户端凭证精简 Response VO")
|
||||
@Data
|
||||
public class ApiClientCredentialSimpleRespVO {
|
||||
|
||||
@Schema(description = "记录编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "应用标识", example = "databus-app")
|
||||
private String appId;
|
||||
|
||||
@Schema(description = "应用名称", example = "数据总线默认应用")
|
||||
private String appName;
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - API 定义详情 Response VO")
|
||||
public class ApiDefinitionDetailRespVO {
|
||||
|
||||
@Schema(description = "主键", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户标识", example = "1")
|
||||
private String tenantId;
|
||||
|
||||
@Schema(description = "API 编码", example = "order.create")
|
||||
private String apiCode;
|
||||
|
||||
@Schema(description = "API 版本", example = "v1")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "HTTP 方法", example = "POST")
|
||||
private String httpMethod;
|
||||
|
||||
@Schema(description = "状态", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "限流策略编号")
|
||||
private Long rateLimitId;
|
||||
|
||||
@Schema(description = "响应模板(JSON)")
|
||||
private String responseTemplate;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "更新人")
|
||||
private String updater;
|
||||
|
||||
@Schema(description = "API 级别变换列表")
|
||||
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 = "步骤列表")
|
||||
private List<ApiDefinitionStepRespVO> steps = new ArrayList<>();
|
||||
|
||||
@Schema(description = "发布信息")
|
||||
private ApiDefinitionPublicationRespVO publication;
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Schema(description = "管理后台 - API 定义分页查询 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiDefinitionPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "关键字,匹配编码或描述", example = "order")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "API 状态", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "HTTP 方法", example = "POST")
|
||||
private String httpMethod;
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - API 发布信息 Response VO")
|
||||
public class ApiDefinitionPublicationRespVO {
|
||||
|
||||
@Schema(description = "发布记录主键", example = "4001")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "发布标签", example = "release-20231001")
|
||||
private String releaseTag;
|
||||
|
||||
@Schema(description = "快照内容(JSON)")
|
||||
private String snapshot;
|
||||
|
||||
@Schema(description = "状态", example = "RELEASED")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "是否当前生效")
|
||||
private Boolean active;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - API 定义保存 Request VO")
|
||||
public class ApiDefinitionSaveReqVO {
|
||||
|
||||
@Schema(description = "主键", example = "1001")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "API 编码", example = "order.create")
|
||||
@NotBlank(message = "API 编码不能为空")
|
||||
private String apiCode;
|
||||
|
||||
@Schema(description = "API 版本", example = "v1")
|
||||
@NotBlank(message = "API 版本不能为空")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "HTTP 方法", example = "POST")
|
||||
@NotBlank(message = "HTTP 方法不能为空")
|
||||
private String httpMethod;
|
||||
|
||||
@Schema(description = "API 状态", example = "1")
|
||||
@NotNull(message = "API 状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "限流策略编号")
|
||||
private Long rateLimitId;
|
||||
|
||||
@Schema(description = "响应模板(JSON)")
|
||||
private String responseTemplate;
|
||||
|
||||
@Schema(description = "API 级别变换列表")
|
||||
@Valid
|
||||
private List<ApiDefinitionTransformSaveReqVO> apiLevelTransforms = new ArrayList<>();
|
||||
|
||||
@Schema(description = "授权的客户端凭证 ID 列表")
|
||||
private List<Long> credentialIds = new ArrayList<>();
|
||||
|
||||
@Schema(description = "步骤列表")
|
||||
@NotEmpty(message = "编排步骤不能为空")
|
||||
@Valid
|
||||
private List<ApiDefinitionStepSaveReqVO> steps = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - API 编排步骤详情 Response VO")
|
||||
public class ApiDefinitionStepRespVO {
|
||||
|
||||
@Schema(description = "步骤主键", example = "21001")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属 API 主键", example = "1024")
|
||||
private Long apiId;
|
||||
|
||||
@Schema(description = "步骤序号", example = "1")
|
||||
private Integer stepOrder;
|
||||
|
||||
@Schema(description = "并行分组")
|
||||
private String parallelGroup;
|
||||
|
||||
@Schema(description = "步骤类型", example = "HTTP")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "目标端点")
|
||||
private String targetEndpoint;
|
||||
|
||||
@Schema(description = "请求映射表达式(JSON)")
|
||||
private String requestMappingExpr;
|
||||
|
||||
@Schema(description = "响应映射表达式(JSON)")
|
||||
private String responseMappingExpr;
|
||||
|
||||
@Schema(description = "超时时间(毫秒)")
|
||||
private Long timeout;
|
||||
|
||||
@Schema(description = "降级策略(JSON)")
|
||||
private String fallbackStrategy;
|
||||
|
||||
@Schema(description = "条件表达式")
|
||||
private String conditionExpr;
|
||||
|
||||
@Schema(description = "是否出错终止")
|
||||
private Boolean stopOnError;
|
||||
|
||||
@Schema(description = "步骤级变换列表")
|
||||
private List<ApiDefinitionTransformRespVO> transforms = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - API 编排步骤保存 Request VO")
|
||||
public class ApiDefinitionStepSaveReqVO {
|
||||
|
||||
@Schema(description = "步骤主键", example = "21001")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "步骤序号", example = "1")
|
||||
@NotNull(message = "步骤序号不能为空")
|
||||
private Integer stepOrder;
|
||||
|
||||
@Schema(description = "并行分组")
|
||||
private String parallelGroup;
|
||||
|
||||
@Schema(description = "步骤类型", example = "HTTP")
|
||||
@NotBlank(message = "步骤类型不能为空")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "目标端点", example = "https://api.demo.com/order")
|
||||
private String targetEndpoint;
|
||||
|
||||
@Schema(description = "请求映射表达式(JSON)")
|
||||
private String requestMappingExpr;
|
||||
|
||||
@Schema(description = "响应映射表达式(JSON)")
|
||||
private String responseMappingExpr;
|
||||
|
||||
@Schema(description = "超时时间(毫秒),缺省 20000(20s)", example = "20000")
|
||||
private Long timeout;
|
||||
|
||||
@Schema(description = "降级策略(JSON)")
|
||||
private String fallbackStrategy;
|
||||
|
||||
@Schema(description = "条件表达式")
|
||||
private String conditionExpr;
|
||||
|
||||
@Schema(description = "是否出错终止")
|
||||
private Boolean stopOnError;
|
||||
|
||||
@Schema(description = "步骤级变换列表")
|
||||
@Valid
|
||||
private List<ApiDefinitionTransformSaveReqVO> transforms = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - API 定义分页列表 Response VO")
|
||||
public class ApiDefinitionSummaryRespVO {
|
||||
|
||||
@Schema(description = "主键", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "API 编码", example = "order.create")
|
||||
private String apiCode;
|
||||
|
||||
@Schema(description = "API 版本", example = "v1")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "HTTP 方法", example = "POST")
|
||||
private String httpMethod;
|
||||
|
||||
@Schema(description = "状态", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "更新人")
|
||||
private String updater;
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - API 变换详情 Response VO")
|
||||
public class ApiDefinitionTransformRespVO {
|
||||
|
||||
@Schema(description = "变换主键", example = "31001")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属 API 主键", example = "1024")
|
||||
private Long apiId;
|
||||
|
||||
@Schema(description = "所属步骤主键", example = "21001")
|
||||
private Long stepId;
|
||||
|
||||
@Schema(description = "阶段", example = "REQUEST")
|
||||
private String phase;
|
||||
|
||||
@Schema(description = "表达式类型", example = "SPEL")
|
||||
private String expressionType;
|
||||
|
||||
@Schema(description = "表达式内容", example = "#{payload}")
|
||||
private String expression;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.definition;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - API 变换保存 Request VO")
|
||||
public class ApiDefinitionTransformSaveReqVO {
|
||||
|
||||
@Schema(description = "变换主键", example = "31001")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "阶段", example = "REQUEST")
|
||||
@NotBlank(message = "变换阶段不能为空")
|
||||
private String phase;
|
||||
|
||||
@Schema(description = "表达式类型", example = "SPEL")
|
||||
@NotBlank(message = "表达式类型不能为空")
|
||||
private String expressionType;
|
||||
|
||||
@Schema(description = "表达式内容", example = "#{payload}")
|
||||
@NotBlank(message = "表达式内容不能为空")
|
||||
private String expression;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.policy;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Base VO for policy definitions shared by request/response objects.
|
||||
*/
|
||||
@Data
|
||||
public class ApiPolicyBaseVO {
|
||||
|
||||
@Schema(description = "策略名称", example = "JWT")
|
||||
@NotBlank(message = "策略名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "策略类型", example = "JWT")
|
||||
@NotBlank(message = "策略类型不能为空")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "策略配置(JSON)", example = "{\"issuer\":\"iam\"}")
|
||||
private String config;
|
||||
|
||||
@Schema(description = "策略描述", example = "JWT 认证策略")
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.policy;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Policy search conditions with pagination.
|
||||
*/
|
||||
@Schema(description = "管理后台 - 策略分页查询 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiPolicyPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "关键字(名称/描述)", example = "JWT")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "策略类型", example = "JWT")
|
||||
private String type;
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.policy;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Policy detail response VO.
|
||||
*/
|
||||
@Schema(description = "管理后台 - 策略详情 Response VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiPolicyRespVO extends ApiPolicyBaseVO {
|
||||
|
||||
@Schema(description = "策略编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建人", example = "admin")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "修改人", example = "admin")
|
||||
private String updater;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "最后更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.policy;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Policy create/update request VO.
|
||||
*/
|
||||
@Schema(description = "管理后台 - 策略保存 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiPolicySaveReqVO extends ApiPolicyBaseVO {
|
||||
|
||||
@Schema(description = "策略编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.policy;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Policy simple response VO used by dropdowns.
|
||||
*/
|
||||
@Schema(description = "管理后台 - 策略精简 Response VO")
|
||||
@Data
|
||||
public class ApiPolicySimpleRespVO {
|
||||
|
||||
@Schema(description = "策略编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "策略名称", example = "JWT")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "策略类型", example = "JWT")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "策略描述")
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API 版本对比 Response VO。
|
||||
*/
|
||||
@Schema(description = "管理后台 - API 版本对比 Response VO")
|
||||
@Data
|
||||
public class ApiVersionCompareRespVO {
|
||||
|
||||
@Schema(description = "源版本 ID", example = "1001")
|
||||
private Long sourceVersionId;
|
||||
|
||||
@Schema(description = "源版本号", example = "2")
|
||||
private Integer sourceVersionNumber;
|
||||
|
||||
@Schema(description = "源版本描述")
|
||||
private String sourceDescription;
|
||||
|
||||
@Schema(description = "源版本操作人")
|
||||
private String sourceOperator;
|
||||
|
||||
@Schema(description = "源版本创建时间")
|
||||
private LocalDateTime sourceCreateTime;
|
||||
|
||||
@Schema(description = "目标版本 ID", example = "1002")
|
||||
private Long targetVersionId;
|
||||
|
||||
@Schema(description = "目标版本号", example = "3")
|
||||
private Integer targetVersionNumber;
|
||||
|
||||
@Schema(description = "目标版本描述")
|
||||
private String targetDescription;
|
||||
|
||||
@Schema(description = "目标版本操作人")
|
||||
private String targetOperator;
|
||||
|
||||
@Schema(description = "目标版本创建时间")
|
||||
private LocalDateTime targetCreateTime;
|
||||
|
||||
@Schema(description = "源版本快照")
|
||||
private ApiDefinitionSaveReqVO sourceSnapshot;
|
||||
|
||||
@Schema(description = "目标版本快照")
|
||||
private ApiDefinitionSaveReqVO targetSnapshot;
|
||||
|
||||
@Schema(description = "两者是否完全一致")
|
||||
private Boolean same;
|
||||
|
||||
@Schema(description = "字段差异列表")
|
||||
private List<FieldDiff> differences;
|
||||
|
||||
@Data
|
||||
public static class FieldDiff {
|
||||
|
||||
@Schema(description = "差异字段路径", example = "/steps[0]/targetEndpoint")
|
||||
private String path;
|
||||
|
||||
@Schema(description = "源版本值")
|
||||
private String sourceValue;
|
||||
|
||||
@Schema(description = "目标版本值")
|
||||
private String targetValue;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* API 版本历史创建 Request VO。
|
||||
*/
|
||||
@Schema(description = "管理后台 - API 版本历史创建 Request VO")
|
||||
@Data
|
||||
public class ApiVersionCreateReqVO {
|
||||
|
||||
@Schema(description = "API 定义 ID", required = true, example = "1024")
|
||||
@NotNull(message = "API 定义 ID 不能为空")
|
||||
private Long apiId;
|
||||
|
||||
@Schema(description = "版本号", required = true, example = "v1.0.0")
|
||||
@NotBlank(message = "版本号不能为空")
|
||||
private String versionNumber;
|
||||
|
||||
@Schema(description = "版本描述", example = "初始版本")
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 版本详情 Response VO。
|
||||
* 包含完整的 API 定义快照数据。
|
||||
*/
|
||||
@Schema(description = "管理后台 - API 版本详情 Response VO")
|
||||
@Data
|
||||
public class ApiVersionDetailRespVO {
|
||||
|
||||
@Schema(description = "主键", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "API 定义 ID", example = "1001")
|
||||
private Long apiId;
|
||||
|
||||
@Schema(description = "版本号", example = "1")
|
||||
private Integer versionNumber;
|
||||
|
||||
@Schema(description = "版本描述", example = "初始版本")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "是否为当前版本", example = "true")
|
||||
private Boolean isCurrent;
|
||||
|
||||
@Schema(description = "操作人", example = "admin")
|
||||
private String operator;
|
||||
|
||||
@Schema(description = "API 定义快照数据")
|
||||
private ApiDefinitionSaveReqVO snapshotData;
|
||||
|
||||
@Schema(description = "创建者", example = "admin")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "租户编号", example = "1")
|
||||
private Long tenantId;
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
/**
|
||||
* API 版本历史分页查询 VO。
|
||||
*/
|
||||
@Schema(description = "管理后台 - API 版本历史分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ApiVersionPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "API 定义 ID", required = true, example = "1024")
|
||||
private Long apiId;
|
||||
|
||||
@Schema(description = "版本号", example = "1")
|
||||
private Integer versionNumber;
|
||||
|
||||
@Schema(description = "创建时间区间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 版本历史 Response VO。
|
||||
*/
|
||||
@Schema(description = "管理后台 - API 版本历史 Response VO")
|
||||
@Data
|
||||
public class ApiVersionRespVO {
|
||||
|
||||
@Schema(description = "主键", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "API 定义 ID", example = "1001")
|
||||
private Long apiId;
|
||||
|
||||
@Schema(description = "版本号", example = "1")
|
||||
private Integer versionNumber;
|
||||
|
||||
@Schema(description = "版本描述", example = "初始版本")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "是否为当前版本", example = "true")
|
||||
private Boolean isCurrent;
|
||||
|
||||
@Schema(description = "操作人", example = "admin")
|
||||
private String operator;
|
||||
|
||||
@Schema(description = "创建者", example = "admin")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "租户编号", example = "1")
|
||||
private Long tenantId;
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* API 版本回滚 Request VO。
|
||||
*/
|
||||
@Schema(description = "管理后台 - API 版本回滚 Request VO")
|
||||
@Data
|
||||
public class ApiVersionRollbackReqVO {
|
||||
|
||||
@Schema(description = "待回滚的版本 ID", required = true, example = "1024")
|
||||
@NotNull(message = "版本 ID 不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "回滚备注", example = "回滚到版本 v5")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Databus API 访问日志数据对象。
|
||||
*
|
||||
* <p>用于记录 API 编排网关的请求与响应详情,便于审计与问题排查。</p>
|
||||
*/
|
||||
@TableName("databus_api_access_log")
|
||||
@KeySequence("databus_api_access_log_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiAccessLogDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 请求追踪标识,对应 {@link com.zt.plat.framework.common.util.monitor.TracerUtils#getTraceId()}
|
||||
*/
|
||||
private String traceId;
|
||||
|
||||
/**
|
||||
* API 编码
|
||||
*/
|
||||
private String apiCode;
|
||||
|
||||
/**
|
||||
* API 版本
|
||||
*/
|
||||
private String apiVersion;
|
||||
|
||||
/**
|
||||
* HTTP 方法
|
||||
*/
|
||||
private String requestMethod;
|
||||
|
||||
/**
|
||||
* 请求路径
|
||||
*/
|
||||
private String requestPath;
|
||||
|
||||
/**
|
||||
* 调用使用的应用标识
|
||||
*/
|
||||
private String credentialAppId;
|
||||
|
||||
/**
|
||||
* 调用使用的凭证主键
|
||||
*/
|
||||
private Long credentialId;
|
||||
|
||||
/**
|
||||
* 查询参数(JSON 字符串)
|
||||
*/
|
||||
private String requestQuery;
|
||||
|
||||
/**
|
||||
* 请求头信息(JSON 字符串)
|
||||
*/
|
||||
private String requestHeaders;
|
||||
|
||||
/**
|
||||
* 请求体(JSON 字符串)
|
||||
*/
|
||||
private String requestBody;
|
||||
|
||||
/**
|
||||
* 响应 HTTP 状态码
|
||||
*/
|
||||
private Integer responseStatus;
|
||||
|
||||
/**
|
||||
* 响应提示信息
|
||||
*/
|
||||
private String responseMessage;
|
||||
|
||||
/**
|
||||
* 响应体(JSON 字符串)
|
||||
*/
|
||||
private String responseBody;
|
||||
|
||||
/**
|
||||
* 访问状态:0-成功 1-客户端错误 2-服务端错误 3-未知
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 业务错误码
|
||||
*/
|
||||
private String errorCode;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 异常堆栈
|
||||
*/
|
||||
private String exceptionStack;
|
||||
|
||||
/**
|
||||
* 客户端 IP
|
||||
*/
|
||||
private String clientIp;
|
||||
|
||||
/**
|
||||
* User-Agent
|
||||
*/
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 请求耗时(毫秒)
|
||||
*/
|
||||
private Long duration;
|
||||
|
||||
/**
|
||||
* 请求时间
|
||||
*/
|
||||
private LocalDateTime requestTime;
|
||||
|
||||
/**
|
||||
* 响应时间
|
||||
*/
|
||||
private LocalDateTime responseTime;
|
||||
|
||||
/**
|
||||
* 执行步骤结果(JSON 字符串)
|
||||
*/
|
||||
private String stepResults;
|
||||
|
||||
/**
|
||||
* 额外调试信息(JSON 字符串)
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* API 客户端凭证,用于维护 appId 与加解密配置的关联关系。
|
||||
*/
|
||||
@Data
|
||||
@TableName("databus_api_client_credential")
|
||||
@KeySequence("databus_api_client_credential_seq")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiClientCredentialDO extends BaseDO {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private String appId;
|
||||
|
||||
private String appName;
|
||||
|
||||
private String encryptionKey;
|
||||
|
||||
private String encryptionType;
|
||||
|
||||
private String signatureType;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
private String remark;
|
||||
|
||||
private Boolean allowAnonymous;
|
||||
|
||||
private Long anonymousUserId;
|
||||
|
||||
private Boolean enableEncryption;
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.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;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API definition data object describing external API metadata and policies.
|
||||
*/
|
||||
@TableName("databus_api_definition")
|
||||
@KeySequence("databus_api_definition_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiDefinitionDO extends TenantBaseDO {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private String apiCode;
|
||||
|
||||
private String httpMethod;
|
||||
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* API status, see {@code ApiPublishStatusEnum}.
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
private String description;
|
||||
|
||||
private Long rateLimitId;
|
||||
|
||||
private String responseTemplate;
|
||||
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Publication record for API flow snapshots and gray releases.
|
||||
*/
|
||||
@TableName("databus_api_flow_publish")
|
||||
@KeySequence("databus_api_flow_publish_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiFlowPublishDO extends TenantBaseDO {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long apiId;
|
||||
|
||||
private String releaseTag;
|
||||
|
||||
private String snapshot;
|
||||
|
||||
private String status;
|
||||
|
||||
private Boolean active;
|
||||
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Rate limit policy definition stored in database.
|
||||
*/
|
||||
@TableName("databus_policy_rate_limit")
|
||||
@KeySequence("databus_policy_rate_limit_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiPolicyRateLimitDO extends TenantBaseDO {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String type;
|
||||
|
||||
private String config;
|
||||
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* API orchestration step definition.
|
||||
*/
|
||||
@TableName("databus_api_step")
|
||||
@KeySequence("databus_api_step_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiStepDO extends TenantBaseDO {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long apiId;
|
||||
|
||||
private Integer stepOrder;
|
||||
|
||||
private String parallelGroup;
|
||||
|
||||
private String type;
|
||||
|
||||
private String targetEndpoint;
|
||||
|
||||
private String requestMappingExpr;
|
||||
|
||||
private String responseMappingExpr;
|
||||
|
||||
private Long transformId;
|
||||
|
||||
private Long timeout;
|
||||
|
||||
private String fallbackStrategy;
|
||||
|
||||
private String conditionExpr;
|
||||
|
||||
private Boolean stopOnError;
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* API request/response transformation expressions.
|
||||
*/
|
||||
@TableName("databus_api_transform")
|
||||
@KeySequence("databus_api_transform_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiTransformDO extends TenantBaseDO {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
private Long apiId;
|
||||
|
||||
private Long stepId;
|
||||
|
||||
private String phase;
|
||||
|
||||
private String expressionType;
|
||||
|
||||
private String expression;
|
||||
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.dataobject.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* API 版本历史数据对象
|
||||
*
|
||||
* <p>每次修改 API 配置时自动创建新版本记录,支持完整的版本历史追溯和回滚。</p>
|
||||
* <p>版本号自动递增,不可删除,保留完整的历史记录链。</p>
|
||||
*/
|
||||
@TableName("databus_api_version")
|
||||
@KeySequence("databus_api_version_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ApiVersionDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* API 定义 ID
|
||||
*/
|
||||
private Long apiId;
|
||||
|
||||
/**
|
||||
* 版本号(自动递增,从 1 开始)
|
||||
*/
|
||||
private Integer versionNumber;
|
||||
|
||||
/**
|
||||
* API 完整定义快照(JSON 格式,包含 definition、steps、transforms 等)
|
||||
*/
|
||||
private String snapshotData;
|
||||
|
||||
/**
|
||||
* 版本描述/变更说明
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否为当前版本(最新使用的版本)
|
||||
*/
|
||||
private Boolean isCurrent;
|
||||
|
||||
/**
|
||||
* 操作人
|
||||
*/
|
||||
private String operator;
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.mapper.gateway;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionPageReqVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API 版本历史 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ApiVersionMapper extends BaseMapperX<ApiVersionDO> {
|
||||
|
||||
/**
|
||||
* 分页查询版本历史
|
||||
*/
|
||||
default PageResult<ApiVersionDO> selectPage(ApiVersionPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<ApiVersionDO>()
|
||||
.eqIfPresent(ApiVersionDO::getApiId, reqVO.getApiId())
|
||||
.eqIfPresent(ApiVersionDO::getVersionNumber, reqVO.getVersionNumber())
|
||||
.betweenIfPresent(ApiVersionDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(ApiVersionDO::getVersionNumber));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定 API 的所有版本历史
|
||||
*/
|
||||
default List<ApiVersionDO> selectListByApiId(Long apiId) {
|
||||
return selectList(new LambdaQueryWrapperX<ApiVersionDO>()
|
||||
.eq(ApiVersionDO::getApiId, apiId)
|
||||
.orderByDesc(ApiVersionDO::getVersionNumber));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定 API 的当前版本
|
||||
*/
|
||||
default ApiVersionDO selectCurrentByApiId(Long apiId) {
|
||||
return selectOne(new LambdaQueryWrapperX<ApiVersionDO>()
|
||||
.eq(ApiVersionDO::getApiId, apiId)
|
||||
.eq(ApiVersionDO::getIsCurrent, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定 API 的最大版本号
|
||||
*/
|
||||
default Integer selectMaxVersionNumber(Long apiId) {
|
||||
ApiVersionDO maxVersion = selectOne(new LambdaQueryWrapperX<ApiVersionDO>()
|
||||
.eq(ApiVersionDO::getApiId, apiId)
|
||||
.orderByDesc(ApiVersionDO::getVersionNumber)
|
||||
.last("LIMIT 1"));
|
||||
return maxVersion != null ? maxVersion.getVersionNumber() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有版本标记为非当前版本
|
||||
*/
|
||||
default void markAllAsNotCurrent(Long apiId) {
|
||||
UpdateWrapper<ApiVersionDO> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("api_id", apiId)
|
||||
.set("is_current", false);
|
||||
update(null, updateWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定版本
|
||||
*/
|
||||
default ApiVersionDO selectByApiIdAndVersionNumber(Long apiId, Integer versionNumber) {
|
||||
return selectOne(new LambdaQueryWrapperX<ApiVersionDO>()
|
||||
.eq(ApiVersionDO::getApiId, apiId)
|
||||
.eq(ApiVersionDO::getVersionNumber, versionNumber));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.mysql.gateway;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogPageReqVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ApiAccessLogMapper extends BaseMapperX<ApiAccessLogDO> {
|
||||
|
||||
default PageResult<ApiAccessLogDO> selectPage(ApiAccessLogPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ApiAccessLogDO> query = new LambdaQueryWrapperX<ApiAccessLogDO>()
|
||||
.likeIfPresent(ApiAccessLogDO::getTraceId, reqVO.getTraceId())
|
||||
.eqIfPresent(ApiAccessLogDO::getApiCode, reqVO.getApiCode())
|
||||
.eqIfPresent(ApiAccessLogDO::getApiVersion, reqVO.getApiVersion())
|
||||
.eqIfPresent(ApiAccessLogDO::getRequestMethod, reqVO.getRequestMethod())
|
||||
.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) {
|
||||
query.between(ApiAccessLogDO::getRequestTime, reqVO.getRequestTime()[0], reqVO.getRequestTime()[1]);
|
||||
}
|
||||
return selectPage(reqVO, query.orderByDesc(ApiAccessLogDO::getRequestTime)
|
||||
.orderByDesc(ApiAccessLogDO::getId));
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.mysql.gateway;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.credential.ApiClientCredentialPageReqVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Mapper
|
||||
public interface ApiClientCredentialMapper extends BaseMapperX<ApiClientCredentialDO> {
|
||||
|
||||
default Optional<ApiClientCredentialDO> selectByAppId(String appId) {
|
||||
if (StrUtil.isBlank(appId)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
LambdaQueryWrapperX<ApiClientCredentialDO> query = new LambdaQueryWrapperX<>();
|
||||
query.eq(ApiClientCredentialDO::getAppId, appId)
|
||||
.eq(ApiClientCredentialDO::getDeleted, false);
|
||||
return Optional.ofNullable(selectOne(query));
|
||||
}
|
||||
|
||||
default PageResult<ApiClientCredentialDO> selectPage(ApiClientCredentialPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ApiClientCredentialDO> query = new LambdaQueryWrapperX<>();
|
||||
if (StrUtil.isNotBlank(reqVO.getKeyword())) {
|
||||
String keyword = reqVO.getKeyword();
|
||||
query.and(wrapper -> wrapper.like(ApiClientCredentialDO::getAppId, keyword)
|
||||
.or().like(ApiClientCredentialDO::getAppName, keyword));
|
||||
}
|
||||
query.eqIfPresent(ApiClientCredentialDO::getEnabled, reqVO.getEnabled())
|
||||
.eq(ApiClientCredentialDO::getDeleted, false)
|
||||
.orderByDesc(ApiClientCredentialDO::getUpdateTime)
|
||||
.orderByDesc(ApiClientCredentialDO::getId);
|
||||
return selectPage(reqVO, query);
|
||||
}
|
||||
|
||||
default List<ApiClientCredentialDO> selectEnabledList() {
|
||||
return selectList(new LambdaQueryWrapperX<ApiClientCredentialDO>()
|
||||
.eq(ApiClientCredentialDO::getEnabled, true)
|
||||
.eq(ApiClientCredentialDO::getDeleted, false)
|
||||
.orderByAsc(ApiClientCredentialDO::getAppId));
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.mysql.gateway;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionPageReqVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Mapper
|
||||
public interface ApiDefinitionMapper extends BaseMapperX<ApiDefinitionDO> {
|
||||
|
||||
default Optional<ApiDefinitionDO> selectByCodeAndVersion(String apiCode, String version) {
|
||||
return Optional.ofNullable(selectOne(ApiDefinitionDO::getApiCode, apiCode,
|
||||
ApiDefinitionDO::getVersion, version,
|
||||
ApiDefinitionDO::getDeleted, false));
|
||||
}
|
||||
|
||||
default List<ApiDefinitionDO> selectActiveDefinitions(List<Integer> statusList) {
|
||||
return selectList(new LambdaQueryWrapperX<ApiDefinitionDO>()
|
||||
.inIfPresent(ApiDefinitionDO::getStatus, statusList)
|
||||
.eq(ApiDefinitionDO::getDeleted, false));
|
||||
}
|
||||
|
||||
default PageResult<ApiDefinitionDO> selectPage(ApiDefinitionPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ApiDefinitionDO> query = new LambdaQueryWrapperX<>();
|
||||
if (StrUtil.isNotBlank(reqVO.getKeyword())) {
|
||||
String keyword = reqVO.getKeyword();
|
||||
query.and(wrapper -> wrapper.like(ApiDefinitionDO::getApiCode, keyword)
|
||||
.or().like(ApiDefinitionDO::getDescription, keyword));
|
||||
}
|
||||
query.eqIfPresent(ApiDefinitionDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(ApiDefinitionDO::getHttpMethod, reqVO.getHttpMethod())
|
||||
.orderByDesc(ApiDefinitionDO::getUpdateTime)
|
||||
.orderByDesc(ApiDefinitionDO::getId);
|
||||
return selectPage(reqVO, query);
|
||||
}
|
||||
|
||||
default Long selectCountByRateLimitPolicyId(Long policyId) {
|
||||
if (policyId == null) {
|
||||
return 0L;
|
||||
}
|
||||
return selectCount(new LambdaQueryWrapperX<ApiDefinitionDO>()
|
||||
.eq(ApiDefinitionDO::getRateLimitId, policyId)
|
||||
.eq(ApiDefinitionDO::getDeleted, false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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.ApiFlowPublishDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Mapper
|
||||
public interface ApiFlowPublishMapper extends BaseMapperX<ApiFlowPublishDO> {
|
||||
|
||||
default Optional<ApiFlowPublishDO> selectActiveByApiId(Long apiId) {
|
||||
return Optional.ofNullable(selectOne(new LambdaQueryWrapperX<ApiFlowPublishDO>()
|
||||
.eq(ApiFlowPublishDO::getApiId, apiId)
|
||||
.eq(ApiFlowPublishDO::getActive, true)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.zt.plat.module.databus.dal.mysql.gateway;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiPolicyRateLimitMapper extends BaseMapperX<ApiPolicyRateLimitDO> {
|
||||
|
||||
default PageResult<ApiPolicyRateLimitDO> selectPage(ApiPolicyPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ApiPolicyRateLimitDO> query = new LambdaQueryWrapperX<>();
|
||||
if (StrUtil.isNotBlank(reqVO.getKeyword())) {
|
||||
String keyword = reqVO.getKeyword();
|
||||
query.and(wrapper -> wrapper.like(ApiPolicyRateLimitDO::getName, keyword)
|
||||
.or().like(ApiPolicyRateLimitDO::getDescription, keyword));
|
||||
}
|
||||
query.eqIfPresent(ApiPolicyRateLimitDO::getType, reqVO.getType())
|
||||
.eq(ApiPolicyRateLimitDO::getDeleted, false)
|
||||
.orderByDesc(ApiPolicyRateLimitDO::getUpdateTime)
|
||||
.orderByDesc(ApiPolicyRateLimitDO::getId);
|
||||
return selectPage(reqVO, query);
|
||||
}
|
||||
|
||||
default List<ApiPolicyRateLimitDO> selectSimpleList() {
|
||||
return selectList(new LambdaQueryWrapperX<ApiPolicyRateLimitDO>()
|
||||
.eq(ApiPolicyRateLimitDO::getDeleted, false)
|
||||
.orderByDesc(ApiPolicyRateLimitDO::getUpdateTime)
|
||||
.orderByDesc(ApiPolicyRateLimitDO::getId));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
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.ApiStepDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiStepMapper extends BaseMapperX<ApiStepDO> {
|
||||
|
||||
default List<ApiStepDO> selectByApiId(Long apiId) {
|
||||
return selectList(new LambdaQueryWrapperX<ApiStepDO>()
|
||||
.eq(ApiStepDO::getApiId, apiId)
|
||||
.orderByAsc(ApiStepDO::getParallelGroup)
|
||||
.orderByAsc(ApiStepDO::getStepOrder));
|
||||
}
|
||||
|
||||
default void deleteByApiId(Long apiId) {
|
||||
delete(new LambdaQueryWrapperX<ApiStepDO>()
|
||||
.eq(ApiStepDO::getApiId, apiId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
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.ApiTransformDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiTransformMapper extends BaseMapperX<ApiTransformDO> {
|
||||
|
||||
default List<ApiTransformDO> selectByApiId(Long apiId) {
|
||||
return selectList(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||
.eq(ApiTransformDO::getApiId, apiId));
|
||||
}
|
||||
|
||||
default List<ApiTransformDO> selectByStepId(Long stepId) {
|
||||
return selectList(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||
.eq(ApiTransformDO::getStepId, stepId));
|
||||
}
|
||||
|
||||
default List<ApiTransformDO> selectApiLevelTransforms(Long apiId) {
|
||||
return selectList(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||
.eq(ApiTransformDO::getApiId, apiId)
|
||||
.isNull(ApiTransformDO::getStepId));
|
||||
}
|
||||
|
||||
default void deleteByApiId(Long apiId) {
|
||||
delete(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||
.eq(ApiTransformDO::getApiId, apiId));
|
||||
}
|
||||
|
||||
default void deleteByStepId(Long stepId) {
|
||||
delete(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||
.eq(ApiTransformDO::getStepId, stepId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.zt.plat.module.databus.enums.gateway;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* External API publish status enumeration.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ApiStatusEnum {
|
||||
|
||||
DRAFT(0, "草稿"),
|
||||
ONLINE(1, "已上线"),
|
||||
OFFLINE(2, "已下线"),
|
||||
DEPRECATED(3, "已废弃");
|
||||
|
||||
private final int status;
|
||||
private final String label;
|
||||
|
||||
public static boolean isOnline(Integer status) {
|
||||
return status != null && status == ONLINE.status;
|
||||
}
|
||||
|
||||
public static boolean isDeprecated(Integer status) {
|
||||
return status != null && status == DEPRECATED.status;
|
||||
}
|
||||
|
||||
public static ApiStatusEnum fromStatus(Integer status) {
|
||||
if (status == null) {
|
||||
return null;
|
||||
}
|
||||
for (ApiStatusEnum value : values()) {
|
||||
if (value.status == status) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String labelOf(Integer status) {
|
||||
ApiStatusEnum value = fromStatus(status);
|
||||
return value != null ? value.label : "未知";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.zt.plat.module.databus.enums.gateway;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Step types supported by the unified API portal.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ApiStepTypeEnum {
|
||||
|
||||
START,
|
||||
HTTP,
|
||||
RPC,
|
||||
SCRIPT,
|
||||
END;
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.zt.plat.module.databus.enums.gateway;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Supported expression languages for request/response mapping.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ExpressionTypeEnum {
|
||||
|
||||
JSON("json");
|
||||
|
||||
private final String code;
|
||||
|
||||
public static ExpressionTypeEnum fromValue(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String normalized = value.trim().toLowerCase(Locale.ROOT);
|
||||
for (ExpressionTypeEnum type : values()) {
|
||||
if (type.code.equals(normalized) || type.name().equals(normalized.toUpperCase(Locale.ROOT))) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.zt.plat.module.databus.enums.gateway;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Transformation phase enumeration.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum TransformPhaseEnum {
|
||||
|
||||
REQUEST_PRE,
|
||||
REQUEST_POST,
|
||||
RESPONSE_PRE,
|
||||
RESPONSE_POST,
|
||||
ERROR;
|
||||
|
||||
public static TransformPhaseEnum fromCode(String code) {
|
||||
for (TransformPhaseEnum phase : values()) {
|
||||
if (phase.name().equalsIgnoreCase(code)) {
|
||||
return phase;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.config;
|
||||
|
||||
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Configuration properties for the unified API portal.
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "databus.api-portal")
|
||||
public class ApiGatewayProperties {
|
||||
|
||||
public static String APP_ID_HEADER = "ZT-App-Id";
|
||||
public static String TIMESTAMP_HEADER = "ZT-Timestamp";
|
||||
public static String NONCE_HEADER = "ZT-Nonce";
|
||||
public static String SIGNATURE_HEADER = "ZT-Signature";
|
||||
public static final String DEFAULT_BASE_PATH = "/admin-api/databus/api/portal";
|
||||
public static final String LEGACY_BASE_PATH = "/databus/api/portal";
|
||||
|
||||
private String basePath = DEFAULT_BASE_PATH;
|
||||
|
||||
public void setBasePath(String basePath) {
|
||||
if (!StringUtils.hasText(basePath)) {
|
||||
this.basePath = DEFAULT_BASE_PATH;
|
||||
return;
|
||||
}
|
||||
String normalized = basePath.startsWith("/") ? basePath : "/" + basePath;
|
||||
if (LEGACY_BASE_PATH.equals(normalized)) {
|
||||
this.basePath = DEFAULT_BASE_PATH;
|
||||
return;
|
||||
}
|
||||
this.basePath = normalized;
|
||||
}
|
||||
|
||||
public List<String> getAllBasePaths() {
|
||||
Set<String> candidates = new LinkedHashSet<>();
|
||||
candidates.add(basePath);
|
||||
candidates.add(DEFAULT_BASE_PATH);
|
||||
candidates.add(LEGACY_BASE_PATH);
|
||||
return new ArrayList<>(candidates);
|
||||
}
|
||||
|
||||
private List<String> allowedIps = new ArrayList<>();
|
||||
|
||||
private List<String> deniedIps = new ArrayList<>();
|
||||
|
||||
private Security security = new Security();
|
||||
|
||||
private boolean enableTenantHeader = false;
|
||||
|
||||
private String tenantHeader = "ZT-Tenant-Id";
|
||||
|
||||
private boolean enableAudit = true;
|
||||
|
||||
private boolean enableRateLimit = true;
|
||||
|
||||
@Data
|
||||
public static class Security {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private String signatureType = CryptoSignatureUtils.SIGNATURE_TYPE_MD5;
|
||||
|
||||
private String encryptionType = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
||||
|
||||
private long allowedClockSkewSeconds = 300;
|
||||
|
||||
private long nonceTtlSeconds = 600;
|
||||
|
||||
private String nonceRedisKeyPrefix = "databus:gateway:nonce:";
|
||||
|
||||
private boolean requireBodyEncryption = true;
|
||||
|
||||
private boolean encryptResponse = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.config;
|
||||
|
||||
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionEvaluatorRegistry;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.JsonataExpressionEvaluator;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Registers expression evaluators with the registry.
|
||||
*/
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class ExpressionConfiguration {
|
||||
|
||||
private final ExpressionEvaluatorRegistry registry;
|
||||
private final JsonataExpressionEvaluator jsonataExpressionEvaluator;
|
||||
|
||||
@PostConstruct
|
||||
public void registerEvaluators() {
|
||||
registry.register(ExpressionTypeEnum.JSON, jsonataExpressionEvaluator);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.config;
|
||||
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayExecutionService;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.ErrorHandlingStrategy;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewaySecurityFilter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.http.dsl.Http;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
|
||||
/**
|
||||
* Configures the unified API portal inbound gateway and supporting beans.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ApiGatewayProperties.class)
|
||||
@RequiredArgsConstructor
|
||||
public class GatewayIntegrationConfiguration {
|
||||
|
||||
private final ApiGatewayProperties properties;
|
||||
private final ErrorHandlingStrategy errorHandlingStrategy;
|
||||
|
||||
@Bean(name = "apiPortalTaskExecutor")
|
||||
public ThreadPoolTaskExecutor apiPortalTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(8);
|
||||
executor.setMaxPoolSize(32);
|
||||
executor.setQueueCapacity(256);
|
||||
executor.setThreadNamePrefix("api-portal-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessagingTemplate apiPortalMessagingTemplate() {
|
||||
return new MessagingTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<GatewaySecurityFilter> gatewaySecurityFilterRegistration(GatewaySecurityFilter filter) {
|
||||
FilterRegistrationBean<GatewaySecurityFilter> registration = new FilterRegistrationBean<>(filter);
|
||||
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 10);
|
||||
return registration;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow apiGatewayInboundFlow(ApiGatewayExecutionService executionService) {
|
||||
String pattern = properties.getBasePath() + "/{apiCode}/{version}";
|
||||
return IntegrationFlow.from(Http.inboundGateway(pattern)
|
||||
.requestMapping(spec -> spec
|
||||
.methods(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.PATCH))
|
||||
.errorChannel(errorHandlingStrategy.getErrorChannel())
|
||||
.requestPayloadType(String.class)
|
||||
.mappedRequestHeaders("*")
|
||||
.mappedResponseHeaders("*"))
|
||||
.handle(executionService, "mapRequest", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
|
||||
.handle(executionService, "dispatch", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
|
||||
.handle(executionService, "buildResponseEntity", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
|
||||
.get();
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.resources.ConnectionProvider;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
|
||||
@Configuration
|
||||
public class GatewayWebClientConfiguration {
|
||||
|
||||
private final int maxInMemorySize;
|
||||
private final long maxIdleTimeMillis;
|
||||
private final long evictInBackgroundMillis;
|
||||
private final boolean connectionPoolEnabled;
|
||||
private final ReactorClientHttpConnector httpConnector;
|
||||
private static final Logger log = LoggerFactory.getLogger(GatewayWebClientConfiguration.class);
|
||||
|
||||
public GatewayWebClientConfiguration(
|
||||
@Value("${databus.gateway.web-client.max-in-memory-size:20971520}") int maxInMemorySize,
|
||||
@Value("${databus.gateway.web-client.max-idle-time:45000}") long maxIdleTimeMillis,
|
||||
@Value("${databus.gateway.web-client.evict-in-background-interval:20000}") long evictInBackgroundMillis,
|
||||
@Value("${databus.gateway.web-client.connection-pool-enabled:true}") boolean connectionPoolEnabled) {
|
||||
this.maxInMemorySize = maxInMemorySize;
|
||||
this.maxIdleTimeMillis = maxIdleTimeMillis;
|
||||
this.evictInBackgroundMillis = evictInBackgroundMillis;
|
||||
this.connectionPoolEnabled = connectionPoolEnabled;
|
||||
this.httpConnector = buildConnector();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebClientCustomizer gatewayWebClientCustomizer() {
|
||||
// 统一设置 WebClient 连接器与内存限制,避免各处重复配置
|
||||
return builder -> builder
|
||||
.clientConnector(httpConnector)
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(maxInMemorySize));
|
||||
}
|
||||
|
||||
private ReactorClientHttpConnector buildConnector() {
|
||||
if (connectionPoolEnabled) {
|
||||
// 启用连接池,基于配置设置空闲回收参数
|
||||
ConnectionProvider provider = ConnectionProvider.builder("databus-gateway")
|
||||
.maxIdleTime(Duration.ofMillis(maxIdleTimeMillis))
|
||||
.evictInBackground(Duration.ofMillis(evictInBackgroundMillis))
|
||||
.build();
|
||||
log.info("Databus gateway WebClient 已启用连接池 (maxIdleTime={}ms, evictInterval={}ms)",
|
||||
maxIdleTimeMillis, evictInBackgroundMillis);
|
||||
return new ReactorClientHttpConnector(HttpClient.create(provider).compress(true));
|
||||
}
|
||||
// 关闭连接池,每次请求都会重新建立 TCP 连接
|
||||
log.info("Databus gateway WebClient 已禁用连接池,所有请求将使用全新连接");
|
||||
return new ReactorClientHttpConnector(HttpClient.create().compress(true));
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
|
||||
import com.zt.plat.module.databus.enums.gateway.TransformPhaseEnum;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpec;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpecParser;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.GatewayExpressionHelper;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.step.StepHandlerFactory;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.integration.core.GenericHandler;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlowBuilder;
|
||||
import org.springframework.integration.dsl.MessageChannels;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 按 API 定义装配动态集成流程。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiFlowAssembler {
|
||||
|
||||
private final StepHandlerFactory stepHandlerFactory;
|
||||
private final PolicyAdvisorFactory policyAdvisorFactory;
|
||||
private final ErrorHandlingStrategy errorHandlingStrategy;
|
||||
private final MonitoringInterceptor monitoringInterceptor;
|
||||
private final ExpressionExecutor expressionExecutor;
|
||||
@Qualifier("apiPortalTaskExecutor")
|
||||
private final TaskExecutor apiPortalTaskExecutor;
|
||||
|
||||
public ApiFlowRegistration assemble(ApiDefinitionAggregate aggregate) {
|
||||
String inputChannelName = channelName(aggregate);
|
||||
String flowId = flowId(aggregate);
|
||||
IntegrationFlowBuilder builder = IntegrationFlow.from(MessageChannels.direct(inputChannelName)
|
||||
.datatype(ApiInvocationContext.class)
|
||||
.interceptor(monitoringInterceptor))
|
||||
.log(message -> String.format("[API-PORTAL] 进入流程 %s", flowId))
|
||||
.handle(ApiInvocationContext.class,
|
||||
applyTransforms(aggregate, TransformPhaseEnum.REQUEST_PRE),
|
||||
endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()));
|
||||
|
||||
List<FlowSegment> segments = segments(aggregate.getSteps());
|
||||
for (FlowSegment segment : segments) {
|
||||
if (segment instanceof SequentialSegment sequentialSegment) {
|
||||
builder = applySequential(builder, aggregate, sequentialSegment.getStep());
|
||||
} else if (segment instanceof ParallelSegment parallelSegment) {
|
||||
builder = applyParallel(builder, aggregate, parallelSegment);
|
||||
}
|
||||
}
|
||||
|
||||
builder = builder
|
||||
.handle(ApiInvocationContext.class,
|
||||
applyTransforms(aggregate, TransformPhaseEnum.RESPONSE_PRE),
|
||||
endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()))
|
||||
.handle(ApiInvocationContext.class,
|
||||
(payload, headers) -> payload,
|
||||
endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()));
|
||||
|
||||
return ApiFlowRegistration.builder()
|
||||
.flowId(flowId)
|
||||
.inputChannelName(inputChannelName)
|
||||
.flow(builder.get())
|
||||
.build();
|
||||
}
|
||||
|
||||
private GenericHandler<ApiInvocationContext> applyTransforms(ApiDefinitionAggregate aggregate, TransformPhaseEnum phase) {
|
||||
boolean mutateRequest = phase == TransformPhaseEnum.REQUEST_PRE || phase == TransformPhaseEnum.REQUEST_POST;
|
||||
boolean mutateResponse = phase == TransformPhaseEnum.RESPONSE_PRE
|
||||
|| phase == TransformPhaseEnum.RESPONSE_POST
|
||||
|| phase == TransformPhaseEnum.ERROR;
|
||||
return (payload, headers) -> {
|
||||
var transformDefinition = aggregate.getApiLevelTransforms().get(phase.name());
|
||||
if (transformDefinition != null && StringUtils.hasText(transformDefinition.getExpression())) {
|
||||
String rawExpression = transformDefinition.getExpressionType() + "::" + transformDefinition.getExpression();
|
||||
ExpressionSpec spec = ExpressionSpecParser.parse(rawExpression, ExpressionTypeEnum.JSON);
|
||||
try {
|
||||
Object result = expressionExecutor.evaluate(spec, payload, payload.getRequestBody(), headers);
|
||||
GatewayExpressionHelper.applyContextMutations(payload, result, mutateRequest, mutateResponse);
|
||||
} catch (Exception ex) {
|
||||
if (ex instanceof ServiceException serviceException) {
|
||||
throw serviceException;
|
||||
}
|
||||
throw ServiceExceptionUtil.exception(API_TRANSFORM_EVALUATION_FAILED, ex.getMessage());
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
private IntegrationFlowBuilder applySequential(IntegrationFlowBuilder builder, ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) {
|
||||
GenericHandler<ApiInvocationContext> handler = stepHandlerFactory.build(aggregate, stepDefinition);
|
||||
return builder.handle(ApiInvocationContext.class, handler, endpoint -> {
|
||||
endpoint.advice(errorHandlingStrategy.errorForwardingAdvice());
|
||||
Advice[] advices = policyAdvisorFactory.buildAdvices(aggregate, stepDefinition);
|
||||
if (advices.length > 0) {
|
||||
endpoint.advice(advices);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private IntegrationFlowBuilder applyParallel(IntegrationFlowBuilder builder, ApiDefinitionAggregate aggregate, ParallelSegment segment) {
|
||||
return builder.handle(ApiInvocationContext.class,
|
||||
(payload, headers) -> executeParallel(payload, headers, aggregate, segment),
|
||||
endpoint -> {
|
||||
endpoint.advice(errorHandlingStrategy.errorForwardingAdvice());
|
||||
Advice[] advices = policyAdvisorFactory.buildParallelAdvices(aggregate, segment);
|
||||
if (advices.length > 0) {
|
||||
endpoint.advice(advices);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ApiInvocationContext executeParallel(ApiInvocationContext context, MessageHeaders headers,
|
||||
ApiDefinitionAggregate aggregate, ParallelSegment segment) {
|
||||
List<CompletableFuture<ApiInvocationContext>> futures = new ArrayList<>();
|
||||
for (ApiStepDefinition step : segment.getSteps()) {
|
||||
GenericHandler<ApiInvocationContext> handler = stepHandlerFactory.build(aggregate, step);
|
||||
ApiInvocationContext childContext = context.copy();
|
||||
futures.add(CompletableFuture.supplyAsync(() -> {
|
||||
handler.handle(childContext, headers);
|
||||
return childContext;
|
||||
}, apiPortalTaskExecutor));
|
||||
}
|
||||
for (CompletableFuture<ApiInvocationContext> future : futures) {
|
||||
try {
|
||||
ApiInvocationContext child = future.get();
|
||||
context.merge(child);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw ServiceExceptionUtil.exception(API_PARALLEL_INTERRUPTED);
|
||||
} catch (ExecutionException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause instanceof ServiceException serviceException) {
|
||||
throw serviceException;
|
||||
}
|
||||
throw ServiceExceptionUtil.exception(API_PARALLEL_FAILED, cause == null ? ex.getMessage() : cause.getMessage());
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private List<FlowSegment> segments(List<ApiStepDefinition> steps) {
|
||||
return steps.stream()
|
||||
.sorted(Comparator.comparingInt(step -> step.getStep().getStepOrder() == null ? Integer.MAX_VALUE : step.getStep().getStepOrder()))
|
||||
.collect(ArrayList::new, this::consumeStep, this::combineSegments);
|
||||
}
|
||||
|
||||
private void consumeStep(List<FlowSegment> segments, ApiStepDefinition step) {
|
||||
String parallelGroup = step.getStep().getParallelGroup();
|
||||
if (!StringUtils.hasText(parallelGroup)) {
|
||||
segments.add(new SequentialSegment(step));
|
||||
return;
|
||||
}
|
||||
FlowSegment last = segments.isEmpty() ? null : segments.get(segments.size() - 1);
|
||||
if (last instanceof ParallelSegment parallelSegment && parallelGroup.equals(parallelSegment.getGroup())) {
|
||||
parallelSegment.getSteps().add(step);
|
||||
} else {
|
||||
ParallelSegment newSegment = new ParallelSegment(parallelGroup, new ArrayList<>());
|
||||
newSegment.getSteps().add(step);
|
||||
segments.add(newSegment);
|
||||
}
|
||||
}
|
||||
|
||||
private void combineSegments(List<FlowSegment> target, List<FlowSegment> source) {
|
||||
target.addAll(source);
|
||||
}
|
||||
|
||||
private String channelName(ApiDefinitionAggregate aggregate) {
|
||||
return "api.portal.flow." + aggregate.getDefinition().getApiCode().toLowerCase() + "." + aggregate.getDefinition().getVersion();
|
||||
}
|
||||
|
||||
private String flowId(ApiDefinitionAggregate aggregate) {
|
||||
return "apiPortalFlow:" + aggregate.getDefinition().getApiCode() + ":" + aggregate.getDefinition().getVersion();
|
||||
}
|
||||
|
||||
private interface FlowSegment {
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static final class SequentialSegment implements FlowSegment {
|
||||
private final ApiStepDefinition step;
|
||||
|
||||
private SequentialSegment(ApiStepDefinition step) {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static final class ParallelSegment implements FlowSegment {
|
||||
private final String group;
|
||||
private final List<ApiStepDefinition> steps;
|
||||
|
||||
private ParallelSegment(String group, List<ApiStepDefinition> steps) {
|
||||
this.group = group;
|
||||
this.steps = steps;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_FLOW_NOT_FOUND;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_FLOW_NO_REPLY;
|
||||
|
||||
/**
|
||||
* api 分发.
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiFlowDispatcher {
|
||||
|
||||
private final IntegrationFlowManager integrationFlowManager;
|
||||
private final MessagingTemplate messagingTemplate;
|
||||
|
||||
public ApiInvocationContext dispatch(String apiCode, String version, ApiInvocationContext context) {
|
||||
MessageChannel channel = requireInputChannel(apiCode, version);
|
||||
Message<ApiInvocationContext> message = MessageBuilder.withPayload(context)
|
||||
.setHeader("apiCode", apiCode)
|
||||
.setHeader("version", version)
|
||||
.build();
|
||||
Message<?> reply = messagingTemplate.sendAndReceive(channel, message);
|
||||
if (reply == null) {
|
||||
throw ServiceExceptionUtil.exception(API_FLOW_NO_REPLY, apiCode, version);
|
||||
}
|
||||
return (ApiInvocationContext) reply.getPayload();
|
||||
}
|
||||
|
||||
public ApiInvocationContext dispatchWithAggregate(ApiDefinitionAggregate aggregate, ApiInvocationContext context) {
|
||||
IntegrationFlowManager.DebugFlowHandle handle = integrationFlowManager.obtainDebugHandle(aggregate);
|
||||
MessageChannel channel = handle.channel();
|
||||
Message<ApiInvocationContext> message = MessageBuilder.withPayload(context)
|
||||
.setHeader("apiCode", aggregate.getDefinition().getApiCode())
|
||||
.setHeader("version", aggregate.getDefinition().getVersion())
|
||||
.build();
|
||||
try {
|
||||
Message<?> reply = messagingTemplate.sendAndReceive(channel, message);
|
||||
if (reply == null) {
|
||||
throw ServiceExceptionUtil.exception(API_FLOW_NO_REPLY, aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||
}
|
||||
return (ApiInvocationContext) reply.getPayload();
|
||||
} finally {
|
||||
integrationFlowManager.releaseDebugHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageChannel requireInputChannel(String apiCode, String version) {
|
||||
// 未命中时,进行一次兜底补偿查询
|
||||
return integrationFlowManager.locateInputChannel(apiCode, version)
|
||||
.or(() -> {
|
||||
integrationFlowManager.refresh(apiCode, version);
|
||||
return integrationFlowManager.locateInputChannel(apiCode, version);
|
||||
})
|
||||
.orElseThrow(() -> ServiceExceptionUtil.exception(API_FLOW_NOT_FOUND, apiCode, version));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
|
||||
/**
|
||||
* Metadata returned by the assembler for flow registration.
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class ApiFlowRegistration {
|
||||
|
||||
String flowId;
|
||||
|
||||
String inputChannelName;
|
||||
|
||||
IntegrationFlow flow;
|
||||
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.util.monitor.TracerUtils;
|
||||
import com.zt.plat.framework.common.util.servlet.ServletUtils;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.security.CachedBodyHttpServletRequest;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiAccessLogService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 将 API 调用上下文持久化为访问日志。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiGatewayAccessLogger {
|
||||
|
||||
public static final String ATTR_LOG_ID = "ApiAccessLogId";
|
||||
public static final String ATTR_EXCEPTION_STACK = "ApiAccessLogExceptionStack";
|
||||
public static final String HEADER_ACCESS_LOG_ID = "X-Databus-AccessLog-Id";
|
||||
private static final String ATTR_REQUEST_START = "ApiAccessLogRequestStart";
|
||||
|
||||
private static final int MAX_TEXT_LENGTH = 4000;
|
||||
|
||||
private final ApiAccessLogService apiAccessLogService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 在分发前记录请求信息。
|
||||
*/
|
||||
public void onRequest(ApiInvocationContext context) {
|
||||
try {
|
||||
ApiAccessLogDO logDO = buildRequestSnapshot(context);
|
||||
Long existingLogId = getLogId(context);
|
||||
if (existingLogId != null) {
|
||||
logDO.setId(existingLogId);
|
||||
apiAccessLogService.update(logDO);
|
||||
} else {
|
||||
Long logId = apiAccessLogService.create(logDO);
|
||||
context.getAttributes().put(ATTR_LOG_ID, logId);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.warn("记录 API 访问日志开始阶段失败, traceId={}", TracerUtils.getTraceId(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录异常堆栈,便于后续写入日志。
|
||||
*/
|
||||
public void onException(ApiInvocationContext context, Throwable throwable) {
|
||||
if (throwable == null) {
|
||||
return;
|
||||
}
|
||||
context.getAttributes().put(ATTR_EXCEPTION_STACK, buildStackTrace(throwable));
|
||||
}
|
||||
|
||||
/**
|
||||
* 在分发完成后补全日志信息。
|
||||
*/
|
||||
public void onResponse(ApiInvocationContext context) {
|
||||
Long logId = getLogId(context);
|
||||
if (logId == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ApiAccessLogDO update = new ApiAccessLogDO();
|
||||
update.setId(logId);
|
||||
Integer responseStatus = resolveHttpStatus(context);
|
||||
context.setResponseStatus(responseStatus);
|
||||
update.setResponseStatus(responseStatus);
|
||||
String responseMessage = resolveResponseMessage(context, responseStatus);
|
||||
update.setResponseMessage(responseMessage);
|
||||
if (!StringUtils.hasText(context.getResponseMessage()) && StringUtils.hasText(responseMessage)) {
|
||||
context.setResponseMessage(responseMessage);
|
||||
}
|
||||
update.setResponseBody(toJson(context.getResponseBody()));
|
||||
update.setStatus(resolveStatus(responseStatus));
|
||||
update.setErrorCode(extractErrorCode(context.getResponseBody(), responseStatus));
|
||||
update.setErrorMessage(resolveErrorMessage(context, responseStatus));
|
||||
update.setExceptionStack((String) context.getAttributes().get(ATTR_EXCEPTION_STACK));
|
||||
update.setStepResults(toJson(context.getStepResults()));
|
||||
update.setExtra(toJson(buildExtra(context)));
|
||||
update.setResponseTime(LocalDateTime.now());
|
||||
update.setDuration(calculateDuration(context));
|
||||
apiAccessLogService.update(update);
|
||||
} catch (Exception ex) {
|
||||
log.warn("记录 API 访问日志结束阶段失败, traceId={}, logId={}", TracerUtils.getTraceId(), logId, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全过滤阶段的第一时间记录请求元数据,保证被快速拒绝的请求也能查询。
|
||||
*/
|
||||
public Long logEntrance(HttpServletRequest request) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
Object existing = request.getAttribute(ATTR_LOG_ID);
|
||||
if (existing instanceof Long logId) {
|
||||
return logId;
|
||||
}
|
||||
try {
|
||||
ApiAccessLogDO logDO = new ApiAccessLogDO();
|
||||
logDO.setTraceId(TracerUtils.getTraceId());
|
||||
logDO.setRequestMethod(request.getMethod());
|
||||
logDO.setRequestPath(request.getRequestURI());
|
||||
logDO.setRequestQuery(truncate(request.getQueryString()));
|
||||
logDO.setRequestHeaders(toJson(collectHeaders(request)));
|
||||
logDO.setClientIp(ServletUtils.getClientIP(request));
|
||||
logDO.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
|
||||
logDO.setStatus(3);
|
||||
logDO.setRequestTime(LocalDateTime.now());
|
||||
logDO.setCreator("");
|
||||
logDO.setUpdater("");
|
||||
Long logId = apiAccessLogService.create(logDO);
|
||||
request.setAttribute(ATTR_LOG_ID, logId);
|
||||
request.setAttribute(ATTR_REQUEST_START, Instant.now());
|
||||
return logId;
|
||||
} catch (Exception ex) {
|
||||
log.warn("记录入口 API 访问日志失败", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编排前即结束的请求在此补写状态码、耗时等关键信息。
|
||||
*/
|
||||
public void finalizeEarly(HttpServletRequest request, int status, String message) {
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
Object existing = request.getAttribute(ATTR_LOG_ID);
|
||||
if (!(existing instanceof Long logId)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ApiAccessLogDO update = new ApiAccessLogDO();
|
||||
update.setId(logId);
|
||||
update.setResponseStatus(status);
|
||||
update.setResponseMessage(truncate(message));
|
||||
update.setStatus(resolveStatus(status));
|
||||
update.setResponseTime(LocalDateTime.now());
|
||||
update.setDuration(calculateDuration(request));
|
||||
update.setUpdater("1");
|
||||
apiAccessLogService.update(update);
|
||||
} catch (Exception ex) {
|
||||
log.warn("更新入口 API 访问日志失败, logId={}", logId, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将入口阶段生成的 logId 通过请求头继续传递,供后续流程关联合并。
|
||||
*/
|
||||
public static void propagateLogIdHeader(CachedBodyHttpServletRequest requestWrapper, Long logId) {
|
||||
if (requestWrapper == null || logId == null) {
|
||||
return;
|
||||
}
|
||||
requestWrapper.setHeader(HEADER_ACCESS_LOG_ID, String.valueOf(logId));
|
||||
}
|
||||
|
||||
private Long getLogId(ApiInvocationContext context) {
|
||||
Object value = context.getAttributes().get(ATTR_LOG_ID);
|
||||
return value instanceof Long ? (Long) value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编排上下文构建请求侧快照,用于访问日志首段信息。
|
||||
*/
|
||||
private ApiAccessLogDO buildRequestSnapshot(ApiInvocationContext context) {
|
||||
ApiAccessLogDO logDO = new ApiAccessLogDO();
|
||||
logDO.setTraceId(TracerUtils.getTraceId());
|
||||
logDO.setApiCode(context.getApiCode());
|
||||
logDO.setApiVersion(context.getApiVersion());
|
||||
logDO.setRequestMethod(context.getHttpMethod());
|
||||
logDO.setRequestPath(context.getRequestPath());
|
||||
logDO.setRequestQuery(toJson(context.getRequestQueryParams()));
|
||||
logDO.setRequestHeaders(toJson(context.getRequestHeaders()));
|
||||
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()));
|
||||
return logDO;
|
||||
}
|
||||
|
||||
private Long calculateDuration(ApiInvocationContext context) {
|
||||
Instant start = context.getRequestTime();
|
||||
if (start == null) {
|
||||
return null;
|
||||
}
|
||||
return Duration.between(start, Instant.now()).toMillis();
|
||||
}
|
||||
|
||||
private Long calculateDuration(HttpServletRequest request) {
|
||||
Object startAttr = request.getAttribute(ATTR_REQUEST_START);
|
||||
if (startAttr instanceof Instant start) {
|
||||
return Duration.between(start, Instant.now()).toMillis();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer resolveStatus(Integer httpStatus) {
|
||||
if (httpStatus == null) {
|
||||
return 3;
|
||||
}
|
||||
if (httpStatus >= 200 && httpStatus < 400) {
|
||||
return 0;
|
||||
}
|
||||
if (httpStatus >= 400 && httpStatus < 500) {
|
||||
return 1;
|
||||
}
|
||||
if (httpStatus >= 500) {
|
||||
return 2;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
private String resolveErrorMessage(ApiInvocationContext context, Integer responseStatus) {
|
||||
if (!isErrorStatus(responseStatus)) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.hasText(context.getResponseMessage())) {
|
||||
return truncate(context.getResponseMessage());
|
||||
}
|
||||
Object responseBody = context.getResponseBody();
|
||||
if (responseBody instanceof Map<?, ?> map) {
|
||||
Object message = firstNonNull(map.get("errorMessage"), map.get("message"));
|
||||
if (message != null) {
|
||||
return truncate(String.valueOf(message));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String extractErrorCode(Object responseBody, Integer responseStatus) {
|
||||
if (!isErrorStatus(responseStatus)) {
|
||||
return null;
|
||||
}
|
||||
if (responseBody instanceof Map<?, ?> map) {
|
||||
Object errorCode = firstNonNull(map.get("errorCode"), map.get("code"));
|
||||
return errorCode == null ? null : truncate(String.valueOf(errorCode));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer resolveHttpStatus(ApiInvocationContext context) {
|
||||
return context.getResponseStatus();
|
||||
}
|
||||
|
||||
private String resolveResponseMessage(ApiInvocationContext context, Integer responseStatus) {
|
||||
if (responseStatus == null) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.hasText(context.getResponseMessage())) {
|
||||
return truncate(context.getResponseMessage());
|
||||
}
|
||||
HttpStatus resolved = HttpStatus.resolve(responseStatus);
|
||||
return resolved != null ? resolved.getReasonPhrase() : null;
|
||||
}
|
||||
|
||||
private boolean isErrorStatus(Integer responseStatus) {
|
||||
return responseStatus != null && responseStatus >= 400;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildExtra(ApiInvocationContext context) {
|
||||
Map<String, Object> extra = new HashMap<>();
|
||||
if (!CollectionUtils.isEmpty(context.getVariables())) {
|
||||
extra.put("variables", context.getVariables());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(context.getAttributes())) {
|
||||
Map<String, Object> attributes = new HashMap<>(context.getAttributes());
|
||||
attributes.remove(ATTR_LOG_ID);
|
||||
attributes.remove(ATTR_EXCEPTION_STACK);
|
||||
if (!attributes.isEmpty()) {
|
||||
extra.put("attributes", attributes);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isEmpty(extra)) {
|
||||
return null;
|
||||
}
|
||||
return extra;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private <T> T firstNonNull(T... candidates) {
|
||||
if (candidates == null) {
|
||||
return null;
|
||||
}
|
||||
for (T candidate : candidates) {
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String toJson(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof String str) {
|
||||
return truncate(str);
|
||||
}
|
||||
try {
|
||||
return truncate(objectMapper.writeValueAsString(value));
|
||||
} catch (JsonProcessingException ex) {
|
||||
return truncate(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
private String truncate(String text) {
|
||||
if (!StringUtils.hasText(text)) {
|
||||
return text;
|
||||
}
|
||||
if (text.length() <= MAX_TEXT_LENGTH) {
|
||||
return text;
|
||||
}
|
||||
return text.substring(0, MAX_TEXT_LENGTH);
|
||||
}
|
||||
|
||||
private Long parseTenantId(String tenantId) {
|
||||
if (!StringUtils.hasText(tenantId)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(tenantId.trim());
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDateTime toLocalDateTime(Instant instant) {
|
||||
if (instant == null) {
|
||||
return null;
|
||||
}
|
||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
private String buildStackTrace(Throwable throwable) {
|
||||
try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
|
||||
throwable.printStackTrace(pw);
|
||||
return truncate(sw.toString());
|
||||
} catch (Exception ex) {
|
||||
return throwable.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> collectHeaders(HttpServletRequest request) {
|
||||
if (request == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
Enumeration<String> names = request.getHeaderNames();
|
||||
while (names != null && names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
Enumeration<String> values = request.getHeaders(name);
|
||||
if (values == null || !values.hasMoreElements()) {
|
||||
continue;
|
||||
}
|
||||
headers.put(name, values.nextElement());
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_AUTH_UNAUTHORIZED;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_CREDENTIAL_UNAUTHORIZED;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EXCEEDED;
|
||||
|
||||
/**
|
||||
* Shared error processor that maps exceptions into {@link ApiInvocationContext} responses.
|
||||
*/
|
||||
@Component
|
||||
public class ApiGatewayErrorProcessor {
|
||||
|
||||
/**
|
||||
* Applies the given {@link Throwable} to the context, unwrapping {@link ServiceException}
|
||||
* instances when present and falling back to a generic error payload otherwise.
|
||||
*/
|
||||
public void apply(ApiInvocationContext context, Throwable throwable) {
|
||||
ServiceException serviceException = resolveServiceException(throwable);
|
||||
if (serviceException != null) {
|
||||
applyServiceException(context, serviceException);
|
||||
} else {
|
||||
applyUnexpectedException(context, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyServiceException(ApiInvocationContext context, ServiceException ex) {
|
||||
String message = StringUtils.hasText(ex.getMessage()) ? ex.getMessage() : "API invocation failed";
|
||||
context.setResponseStatus(resolveHttpStatus(ex, context));
|
||||
context.setResponseMessage(message);
|
||||
if (context.getResponseBody() == null) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
if (ex.getCode() != null) {
|
||||
body.put("errorCode", ex.getCode());
|
||||
}
|
||||
body.put("errorMessage", message);
|
||||
context.setResponseBody(body);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyUnexpectedException(ApiInvocationContext context, Throwable throwable) {
|
||||
String message = determineUnexpectedMessage(throwable);
|
||||
context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
context.setResponseMessage(message);
|
||||
if (context.getResponseBody() == null) {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("errorMessage", message);
|
||||
body.put("exception", throwable.getClass().getSimpleName());
|
||||
context.setResponseBody(body);
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceException resolveServiceException(Throwable throwable) {
|
||||
Throwable current = throwable;
|
||||
while (current != null) {
|
||||
if (current instanceof ServiceException serviceException) {
|
||||
return serviceException;
|
||||
}
|
||||
current = current.getCause();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int resolveHttpStatus(ServiceException ex, ApiInvocationContext context) {
|
||||
if (context.getResponseStatus() != null) {
|
||||
return context.getResponseStatus();
|
||||
}
|
||||
Integer code = ex.getCode();
|
||||
if (code != null) {
|
||||
if (API_AUTH_UNAUTHORIZED.getCode().equals(code)) {
|
||||
return HttpStatus.UNAUTHORIZED.value();
|
||||
}
|
||||
if (API_RATE_LIMIT_EXCEEDED.getCode().equals(code)) {
|
||||
return HttpStatus.TOO_MANY_REQUESTS.value();
|
||||
}
|
||||
if (API_CREDENTIAL_UNAUTHORIZED.getCode().equals(code)) {
|
||||
return HttpStatus.FORBIDDEN.value();
|
||||
}
|
||||
}
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||
}
|
||||
|
||||
private String determineUnexpectedMessage(Throwable throwable) {
|
||||
Throwable current = throwable;
|
||||
while (current != null) {
|
||||
if (StringUtils.hasText(current.getMessage())) {
|
||||
return current.getMessage();
|
||||
}
|
||||
current = current.getCause();
|
||||
}
|
||||
return "API invocation encountered an unexpected error";
|
||||
}
|
||||
}
|
||||
@@ -1,404 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.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;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 统一处理 API 门户的请求映射、分发与响应构建。
|
||||
* 管理端调试与外部 HTTP 请求共享同一套逻辑,安全校验由 {@link GatewaySecurityFilter} 执行。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiGatewayExecutionService {
|
||||
|
||||
private static final String HEADER_REQUEST_HEADERS = org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders";
|
||||
private static final String HEADER_REQUEST_URI = org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri";
|
||||
private static final String HEADER_REQUEST_PARAMS = org.springframework.integration.http.HttpHeaders.PREFIX + "requestParams";
|
||||
private static final String HEADER_QUERY_STRING = org.springframework.integration.http.HttpHeaders.PREFIX + "queryString";
|
||||
private static final String HEADER_REMOTE_ADDRESS = org.springframework.integration.http.HttpHeaders.PREFIX + "remoteAddress";
|
||||
private static final String LOCAL_DEBUG_REMOTE_ADDRESS = "127.0.0.1";
|
||||
private static final String ATTR_DEBUG_INVOKE = "gatewayDebugInvoke";
|
||||
private static final String ATTR_API_AGGREGATE = "apiDefinitionAggregate";
|
||||
|
||||
private final ApiGatewayRequestMapper requestMapper;
|
||||
private final ApiFlowDispatcher apiFlowDispatcher;
|
||||
private final ApiGatewayErrorProcessor errorProcessor;
|
||||
private final ApiGatewayProperties properties;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ApiGatewayAccessLogger accessLogger;
|
||||
private final ApiDefinitionService apiDefinitionService;
|
||||
|
||||
/**
|
||||
* 将 Spring Integration 提供的原始消息映射为网关上下文消息。
|
||||
*/
|
||||
public Message<ApiInvocationContext> mapRequest(Message<?> message) {
|
||||
ApiInvocationContext context = requestMapper.map(message.getPayload(), message.getHeaders());
|
||||
return MessageBuilder.withPayload(context)
|
||||
.copyHeaders(message.getHeaders())
|
||||
.setHeaderIfAbsent("apiCode", context.getApiCode())
|
||||
.setHeaderIfAbsent("version", context.getApiVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分发 API 调用,并在异常场景套用统一错误处理。
|
||||
*/
|
||||
public ApiInvocationContext dispatch(Message<ApiInvocationContext> message) {
|
||||
ApiInvocationContext context = message.getPayload();
|
||||
accessLogger.onRequest(context);
|
||||
ApiInvocationContext responseContext;
|
||||
ApiDefinitionAggregate aggregate = null;
|
||||
try {
|
||||
enforceCredentialAuthorization(context);
|
||||
if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
|
||||
aggregate = resolveDebugAggregate(context);
|
||||
} else {
|
||||
// 对于非调试调用,也需要获取 aggregate 以便后续应用响应模板
|
||||
aggregate = apiDefinitionService.findByCodeAndVersion(context.getApiCode(), context.getApiVersion())
|
||||
.orElse(null);
|
||||
}
|
||||
// 将 aggregate 存储到上下文中,供响应构建时使用
|
||||
if (aggregate != null) {
|
||||
context.getAttributes().put(ATTR_API_AGGREGATE, aggregate);
|
||||
}
|
||||
if (aggregate != null && Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
|
||||
responseContext = apiFlowDispatcher.dispatchWithAggregate(aggregate, context);
|
||||
} else {
|
||||
responseContext = apiFlowDispatcher.dispatch(context.getApiCode(), context.getApiVersion(), context);
|
||||
}
|
||||
responseContext.setResponseStatus(resolveStatus(responseContext));
|
||||
} catch (ServiceException ex) {
|
||||
errorProcessor.applyServiceException(context, ex);
|
||||
accessLogger.onException(context, ex);
|
||||
log.warn("[API-PORTAL] 分发 apiCode={} version={} 时出现 ServiceException: {}", context.getApiCode(), context.getApiVersion(), ex.getMessage());
|
||||
responseContext = context;
|
||||
} catch (Exception ex) {
|
||||
ServiceException nestedServiceException = errorProcessor.resolveServiceException(ex);
|
||||
if (nestedServiceException != null) {
|
||||
errorProcessor.applyServiceException(context, nestedServiceException);
|
||||
accessLogger.onException(context, nestedServiceException);
|
||||
log.warn("[API-PORTAL] 分发 apiCode={} version={} 时出现 ServiceException(包装异常): {}", context.getApiCode(), context.getApiVersion(), nestedServiceException.getMessage());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("[API-PORTAL] 包装异常堆栈", ex);
|
||||
}
|
||||
} else {
|
||||
errorProcessor.applyUnexpectedException(context, ex);
|
||||
accessLogger.onException(context, ex);
|
||||
log.error("[API-PORTAL] 分发 apiCode={} version={} 时出现未预期异常", context.getApiCode(), context.getApiVersion(), ex);
|
||||
}
|
||||
responseContext = context;
|
||||
} finally {
|
||||
accessLogger.onResponse(context);
|
||||
}
|
||||
return responseContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a HTTP response entity for the external gateway flow.
|
||||
*/
|
||||
public ResponseEntity<ApiGatewayResponse> buildResponseEntity(ApiInvocationContext context) {
|
||||
int status = resolveStatus(context);
|
||||
ApiGatewayResponse envelope = buildResponseEnvelope(context, status);
|
||||
return ResponseEntity.status(status).body(envelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a debug invocation by reusing the same mapping/dispatch pipeline as the public gateway.
|
||||
*/
|
||||
public ResponseEntity<ApiGatewayResponse> invokeForDebug(ApiGatewayInvokeReqVO reqVO) {
|
||||
Message<?> rawMessage = buildDebugMessage(reqVO);
|
||||
Message<ApiInvocationContext> mappedMessage = mapRequest(rawMessage);
|
||||
ApiInvocationContext context = mappedMessage.getPayload();
|
||||
// 将调试透传的查询参数、请求头重新合并到上下文,避免映射阶段丢失
|
||||
mergeDebugMetadata(context, reqVO);
|
||||
context.getAttributes().put(ATTR_DEBUG_INVOKE, Boolean.TRUE);
|
||||
ApiInvocationContext responseContext = dispatch(mappedMessage);
|
||||
return buildResponseEntity(responseContext);
|
||||
}
|
||||
|
||||
private ApiDefinitionAggregate resolveDebugAggregate(ApiInvocationContext context) {
|
||||
Optional<ApiDefinitionAggregate> activeDefinition = apiDefinitionService.findByCodeAndVersion(context.getApiCode(), context.getApiVersion());
|
||||
if (activeDefinition.isPresent()) {
|
||||
return activeDefinition.get();
|
||||
}
|
||||
return apiDefinitionService.findByCodeAndVersionIncludingInactive(context.getApiCode(), context.getApiVersion())
|
||||
.orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND));
|
||||
}
|
||||
|
||||
private Message<?> buildDebugMessage(ApiGatewayInvokeReqVO reqVO) {
|
||||
Object payload = preparePayload(reqVO.getPayload());
|
||||
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload);
|
||||
|
||||
Map<String, Object> uriVariables = Map.of(
|
||||
"apiCode", reqVO.getApiCode(),
|
||||
"version", reqVO.getVersion()
|
||||
);
|
||||
builder.setHeader(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
|
||||
builder.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, HttpMethod.POST.name());
|
||||
|
||||
String basePath = properties.getBasePath();
|
||||
String rawQuery = buildQueryString(reqVO.getQueryParams());
|
||||
String requestUri = basePath + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
|
||||
if (StringUtils.hasText(rawQuery)) {
|
||||
requestUri = requestUri + "?" + rawQuery;
|
||||
}
|
||||
builder.setHeader(HEADER_REQUEST_URI, requestUri);
|
||||
builder.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_URL, requestUri);
|
||||
builder.setHeader(HEADER_REMOTE_ADDRESS, LOCAL_DEBUG_REMOTE_ADDRESS);
|
||||
|
||||
Map<String, Object> requestHeaders = new LinkedHashMap<>();
|
||||
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) -> {
|
||||
if (value != null) {
|
||||
builder.setHeaderIfAbsent(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
if (reqVO.getQueryParams() != null && !reqVO.getQueryParams().isEmpty()) {
|
||||
Map<String, Object> paramsCopy = new LinkedHashMap<>(reqVO.getQueryParams());
|
||||
builder.setHeader(HEADER_REQUEST_PARAMS, paramsCopy);
|
||||
if (StringUtils.hasText(rawQuery)) {
|
||||
builder.setHeader(HEADER_QUERY_STRING, rawQuery);
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.isEnableTenantHeader()) {
|
||||
String tenantHeader = properties.getTenantHeader();
|
||||
Object tenantValue = requestHeaders.get(tenantHeader);
|
||||
if (tenantValue != null) {
|
||||
builder.setHeaderIfAbsent(tenantHeader, tenantValue);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Object preparePayload(Object payload) {
|
||||
if (payload == null) {
|
||||
return "";
|
||||
}
|
||||
if (payload instanceof String) {
|
||||
return payload;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(payload);
|
||||
} catch (JsonProcessingException ex) {
|
||||
log.debug("[API-PORTAL] 将调试请求体序列化为 JSON 失败,使用 toString()", ex);
|
||||
return payload.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeDebugMetadata(ApiInvocationContext context, ApiGatewayInvokeReqVO reqVO) {
|
||||
if (reqVO.getHeaders() != null && !reqVO.getHeaders().isEmpty()) {
|
||||
reqVO.getHeaders().forEach((key, value) -> context.getRequestHeaders().putIfAbsent(key, value));
|
||||
}
|
||||
if (reqVO.getQueryParams() != null && !reqVO.getQueryParams().isEmpty()) {
|
||||
reqVO.getQueryParams().forEach((key, value) -> context.getRequestQueryParams().putIfAbsent(key, value));
|
||||
}
|
||||
if (!StringUtils.hasText(context.getHttpMethod())) {
|
||||
context.setHttpMethod(HttpMethod.POST.name());
|
||||
}
|
||||
if (!StringUtils.hasText(context.getRequestPath())) {
|
||||
String path = properties.getBasePath() + "/" + reqVO.getApiCode() + "/" + reqVO.getVersion();
|
||||
context.setRequestPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
private int resolveStatus(ApiInvocationContext context) {
|
||||
return context.getResponseStatus() != null ? context.getResponseStatus() : HttpStatus.OK.value();
|
||||
}
|
||||
|
||||
private ApiGatewayResponse buildResponseEnvelope(ApiInvocationContext context, int status) {
|
||||
String message = StringUtils.hasText(context.getResponseMessage())
|
||||
? context.getResponseMessage()
|
||||
: HttpStatus.valueOf(status).getReasonPhrase();
|
||||
|
||||
// 尝试从上下文中获取 API 定义聚合对象
|
||||
ApiDefinitionAggregate aggregate = (ApiDefinitionAggregate) context.getAttributes().get(ATTR_API_AGGREGATE);
|
||||
String responseTemplate = aggregate != null ? aggregate.getDefinition().getResponseTemplate() : null;
|
||||
|
||||
// 如果配置了响应模板,则应用模板;否则使用默认格式(向后兼容)
|
||||
if (StringUtils.hasText(responseTemplate)) {
|
||||
return buildResponseWithTemplate(responseTemplate, context, status, message);
|
||||
}
|
||||
|
||||
// 默认响应格式(向后兼容)
|
||||
return ApiGatewayResponse.builder()
|
||||
.code(status)
|
||||
.message(message)
|
||||
.response(context.getResponseBody())
|
||||
.traceId(TracerUtils.getTraceId())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据响应模板构建响应对象
|
||||
* 模板格式示例:{"code": 0, "message": "success", "data": {...}}
|
||||
* 模板中的 data 字段会被实际响应数据替换,其他字段保持用户配置的原始值
|
||||
*/
|
||||
private ApiGatewayResponse buildResponseWithTemplate(String templateJson, ApiInvocationContext context, int status, String message) {
|
||||
try {
|
||||
// 解析模板 JSON
|
||||
Map<String, Object> template = objectMapper.readValue(templateJson, Map.class);
|
||||
|
||||
// 构建最终响应数据,保留模板中的所有字段
|
||||
Map<String, Object> responseData = new LinkedHashMap<>(template);
|
||||
|
||||
// 只替换 data 字段为实际的响应体数据
|
||||
// 其他字段(如 code、message)保持用户配置的原始值
|
||||
if (responseData.containsKey("data")) {
|
||||
responseData.put("data", context.getResponseBody());
|
||||
}
|
||||
|
||||
// 返回使用模板结构的响应
|
||||
// 注意:ApiGatewayResponse 的 code 和 message 是 HTTP 层面的状态
|
||||
// responseData 中的 code 和 message 是业务层面的状态
|
||||
return ApiGatewayResponse.builder()
|
||||
.code(status)
|
||||
.message(message)
|
||||
.response(responseData)
|
||||
.traceId(TracerUtils.getTraceId())
|
||||
.build();
|
||||
|
||||
} catch (JsonProcessingException ex) {
|
||||
log.warn("[API-PORTAL] 解析响应模板失败,使用默认格式: {}", ex.getMessage());
|
||||
// 解析失败时回退到默认格式
|
||||
return ApiGatewayResponse.builder()
|
||||
.code(status)
|
||||
.message(message)
|
||||
.response(context.getResponseBody())
|
||||
.traceId(TracerUtils.getTraceId())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用前校验凭证白名单,非调试调用需匹配绑定的 appId。
|
||||
*/
|
||||
private void enforceCredentialAuthorization(ApiInvocationContext context) {
|
||||
if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildQueryString(Map<String, Object> queryParams) {
|
||||
if (queryParams == null || queryParams.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
|
||||
queryParams.forEach((key, value) -> appendQueryParam(builder, key, value));
|
||||
URI uri = builder.build(true).toUri();
|
||||
return uri.getQuery();
|
||||
}
|
||||
|
||||
private void appendQueryParam(UriComponentsBuilder builder, String key, Object value) {
|
||||
if (!StringUtils.hasText(key)) {
|
||||
return;
|
||||
}
|
||||
if (value == null) {
|
||||
builder.queryParam(key, (Object) null);
|
||||
return;
|
||||
}
|
||||
if (value instanceof Iterable<?> iterable) {
|
||||
for (Object element : iterable) {
|
||||
appendQueryParam(builder, key, element);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (value.getClass().isArray()) {
|
||||
int length = Array.getLength(value);
|
||||
for (int i = 0; i < length; i++) {
|
||||
appendQueryParam(builder, key, Array.get(value, i));
|
||||
}
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayAccessLogger;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Maps inbound HTTP request metadata into {@link ApiInvocationContext} instances.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ApiGatewayRequestMapper {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ApiGatewayProperties properties;
|
||||
|
||||
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<String, Object> headers) {
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
Map<String, Object> uriVariables = (Map<String, Object>) headers.get(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
if (uriVariables != null) {
|
||||
Object apiCode = uriVariables.get("apiCode");
|
||||
Object version = uriVariables.get("version");
|
||||
if (apiCode != null) {
|
||||
context.setApiCode(String.valueOf(apiCode));
|
||||
}
|
||||
if (version != null) {
|
||||
context.setApiVersion(String.valueOf(version));
|
||||
}
|
||||
}
|
||||
Object methodHeader = headers.get(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD);
|
||||
if (methodHeader != null) {
|
||||
context.setHttpMethod(String.valueOf(methodHeader));
|
||||
}
|
||||
Object requestPath = headers.get(HEADER_REQUEST_URI);
|
||||
if (requestPath == null) {
|
||||
requestPath = headers.get(org.springframework.integration.http.HttpHeaders.REQUEST_URL);
|
||||
}
|
||||
String originalRequestUri = requestPath != null ? String.valueOf(requestPath) : null;
|
||||
if (originalRequestUri != null) {
|
||||
context.setRequestPath(originalRequestUri);
|
||||
}
|
||||
if (!StringUtils.hasText(context.getApiCode())) {
|
||||
Object apiCodeHeader = headers.get("apiCode");
|
||||
if (apiCodeHeader != null) {
|
||||
context.setApiCode(String.valueOf(apiCodeHeader));
|
||||
}
|
||||
}
|
||||
if (!StringUtils.hasText(context.getApiVersion())) {
|
||||
Object versionHeader = headers.get("version");
|
||||
if (versionHeader != null) {
|
||||
context.setApiVersion(String.valueOf(versionHeader));
|
||||
}
|
||||
}
|
||||
if (!StringUtils.hasText(context.getApiCode()) || !StringUtils.hasText(context.getApiVersion())) {
|
||||
inferFromRequestPath(context);
|
||||
}
|
||||
|
||||
Map<String, Object> requestHeaders = (Map<String, Object>) headers.get(HEADER_REQUEST_HEADERS);
|
||||
if (requestHeaders != null) {
|
||||
context.getRequestHeaders().putAll(requestHeaders);
|
||||
}
|
||||
headers.forEach((key, value) -> {
|
||||
if (isInternalHeader(key) || value == null) {
|
||||
return;
|
||||
}
|
||||
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()) {
|
||||
Object tenantHeaderValue = context.getRequestHeaders().get(properties.getTenantHeader());
|
||||
if (tenantHeaderValue != null) {
|
||||
context.setTenantId(String.valueOf(tenantHeaderValue));
|
||||
}
|
||||
}
|
||||
if (payload instanceof String body) {
|
||||
if (StringUtils.hasText(body) && isJsonContent(context)) {
|
||||
try {
|
||||
context.setRequestBody(objectMapper.readValue(body, Object.class));
|
||||
} catch (IOException ex) {
|
||||
log.warn("解析请求体为 JSON 失败", ex);
|
||||
context.setRequestBody(body);
|
||||
}
|
||||
} else {
|
||||
context.setRequestBody(body);
|
||||
}
|
||||
} else {
|
||||
context.setRequestBody(payload);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private void captureAccessLogId(ApiInvocationContext context) {
|
||||
String headerValue = GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), ApiGatewayAccessLogger.HEADER_ACCESS_LOG_ID);
|
||||
if (!StringUtils.hasText(headerValue)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Long logId = Long.valueOf(headerValue);
|
||||
context.getAttributes().put(ApiGatewayAccessLogger.ATTR_LOG_ID, logId);
|
||||
} catch (NumberFormatException ex) {
|
||||
// 忽略格式问题,仅在属性中保留原文以便排查
|
||||
context.getAttributes().put(ApiGatewayAccessLogger.ATTR_LOG_ID, headerValue);
|
||||
}
|
||||
context.getRequestHeaders().remove(ApiGatewayAccessLogger.HEADER_ACCESS_LOG_ID);
|
||||
}
|
||||
|
||||
private boolean isInternalHeader(String headerName) {
|
||||
if (!StringUtils.hasText(headerName)) {
|
||||
return true;
|
||||
}
|
||||
if (headerName.startsWith(org.springframework.integration.http.HttpHeaders.PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
if (org.springframework.messaging.MessageHeaders.ID.equals(headerName)
|
||||
|| org.springframework.messaging.MessageHeaders.TIMESTAMP.equals(headerName)) {
|
||||
return true;
|
||||
}
|
||||
return "correlationId".equals(headerName)
|
||||
|| "sequenceNumber".equals(headerName)
|
||||
|| "sequenceSize".equals(headerName)
|
||||
|| "errorChannel".equals(headerName)
|
||||
|| "replyChannel".equals(headerName)
|
||||
|| "replyChannelName".equals(headerName)
|
||||
|| "errorChannelName".equals(headerName);
|
||||
}
|
||||
|
||||
private boolean isJsonContent(ApiInvocationContext context) {
|
||||
String contentType = String.valueOf(context.getRequestHeaders().getOrDefault(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)).toLowerCase(Locale.ROOT);
|
||||
return contentType.contains(MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
|
||||
private void populateQueryParams(Map<String, Object> headers, ApiInvocationContext context, String originalRequestUri) {
|
||||
Map<?, ?> headerParams = findRequestParamsHeader(headers);
|
||||
if (headerParams != null) {
|
||||
mergeParameterMap(headerParams, context.getRequestQueryParams(), true);
|
||||
}
|
||||
|
||||
String queryString = findQueryStringHeader(headers);
|
||||
if (!StringUtils.hasText(queryString) && StringUtils.hasText(originalRequestUri)) {
|
||||
int idx = originalRequestUri.indexOf('?');
|
||||
if (idx >= 0 && idx + 1 < originalRequestUri.length()) {
|
||||
queryString = originalRequestUri.substring(idx + 1);
|
||||
}
|
||||
}
|
||||
if (StringUtils.hasText(queryString)) {
|
||||
mergeQueryString(queryString, context.getRequestQueryParams());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<?, ?> findRequestParamsHeader(Map<String, Object> headers) {
|
||||
if (headers == null) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : headers.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (!StringUtils.hasText(key)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.getValue() instanceof Map<?, ?> map && key.toLowerCase(Locale.ROOT).endsWith("requestparams")) {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String findQueryStringHeader(Map<String, Object> headers) {
|
||||
if (headers == null) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : headers.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (!StringUtils.hasText(key)) {
|
||||
continue;
|
||||
}
|
||||
if (key.toLowerCase(Locale.ROOT).endsWith("querystring") && entry.getValue() != null) {
|
||||
return entry.getValue().toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void mergeQueryString(String queryString, Map<String, Object> target) {
|
||||
try {
|
||||
MultiValueMap<String, String> params = UriComponentsBuilder.newInstance()
|
||||
.query(queryString)
|
||||
.build()
|
||||
.getQueryParams();
|
||||
params.forEach((key, values) -> {
|
||||
if (!StringUtils.hasText(key) || values == null || values.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (target.containsKey(key)) {
|
||||
return;
|
||||
}
|
||||
if (values.size() == 1) {
|
||||
target.put(key, values.get(0));
|
||||
} else {
|
||||
target.put(key, List.copyOf(values));
|
||||
}
|
||||
});
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.debug("解析查询串 '{}' 失败", queryString, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeParameterMap(Map<?, ?> source, Map<String, Object> target, boolean override) {
|
||||
source.forEach((rawKey, rawValue) -> {
|
||||
if (!(rawKey instanceof String key) || !StringUtils.hasText(key)) {
|
||||
return;
|
||||
}
|
||||
List<String> values = normalizeParamValues(rawValue);
|
||||
if (values.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!override && target.containsKey(key)) {
|
||||
return;
|
||||
}
|
||||
if (values.size() == 1) {
|
||||
target.put(key, values.get(0));
|
||||
} else {
|
||||
target.put(key, values);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<String> normalizeParamValues(Object value) {
|
||||
if (value == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (value instanceof CharSequence) {
|
||||
String candidate = value.toString();
|
||||
return candidate.isEmpty() ? List.of("") : List.of(candidate);
|
||||
}
|
||||
if (value instanceof Iterable<?>) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Object element : (Iterable<?>) value) {
|
||||
result.addAll(normalizeParamValues(element));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (value.getClass().isArray()) {
|
||||
int length = Array.getLength(value);
|
||||
List<String> result = new ArrayList<>(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
result.addAll(normalizeParamValues(Array.get(value, i)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return List.of(value.toString());
|
||||
}
|
||||
|
||||
private void inferFromRequestPath(ApiInvocationContext context) {
|
||||
String requestPath = normalizeRequestPath(context.getRequestPath());
|
||||
if (!StringUtils.hasText(requestPath)) {
|
||||
return;
|
||||
}
|
||||
if (!requestPath.equals(context.getRequestPath())) {
|
||||
context.setRequestPath(requestPath);
|
||||
}
|
||||
for (String basePath : properties.getAllBasePaths()) {
|
||||
if (!requestPath.startsWith(basePath)) {
|
||||
continue;
|
||||
}
|
||||
String remainder = requestPath.substring(basePath.length());
|
||||
if (remainder.startsWith("/")) {
|
||||
remainder = remainder.substring(1);
|
||||
}
|
||||
if (!StringUtils.hasText(remainder)) {
|
||||
continue;
|
||||
}
|
||||
String[] segments = remainder.split("/");
|
||||
if (segments.length < 2) {
|
||||
continue;
|
||||
}
|
||||
if (!StringUtils.hasText(context.getApiCode())) {
|
||||
context.setApiCode(segments[0]);
|
||||
}
|
||||
if (!StringUtils.hasText(context.getApiVersion())) {
|
||||
context.setApiVersion(segments[1]);
|
||||
}
|
||||
if (StringUtils.hasText(context.getApiCode()) && StringUtils.hasText(context.getApiVersion())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeRequestPath(String requestPath) {
|
||||
if (!StringUtils.hasText(requestPath)) {
|
||||
return requestPath;
|
||||
}
|
||||
String candidate = requestPath;
|
||||
int queryIndex = candidate.indexOf('?');
|
||||
if (queryIndex >= 0) {
|
||||
candidate = candidate.substring(0, queryIndex);
|
||||
}
|
||||
if (candidate.contains("://")) {
|
||||
try {
|
||||
java.net.URI uri = java.net.URI.create(candidate);
|
||||
if (uri.getPath() != null) {
|
||||
candidate = uri.getPath();
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.debug("将请求路径 '{}' 解析为 URI 失败", candidate, ex);
|
||||
}
|
||||
}
|
||||
if (!candidate.startsWith("/")) {
|
||||
candidate = "/" + candidate;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private String resolveClientIp(Map<String, Object> headers, Map<String, Object> requestHeaders) {
|
||||
String forwarded = GatewayHeaderUtils.findFirstHeaderValue(requestHeaders, "X-Forwarded-For");
|
||||
if (StringUtils.hasText(forwarded)) {
|
||||
int idx = forwarded.indexOf(',');
|
||||
return idx >= 0 ? forwarded.substring(0, idx).trim() : forwarded;
|
||||
}
|
||||
String realIp = GatewayHeaderUtils.findFirstHeaderValue(requestHeaders, "X-Real-IP");
|
||||
if (StringUtils.hasText(realIp)) {
|
||||
return realIp;
|
||||
}
|
||||
Object remote = headers.get(HEADER_REMOTE_ADDRESS);
|
||||
if (remote != null) {
|
||||
String candidate = remote.toString();
|
||||
int slash = candidate.indexOf('/') >= 0 ? candidate.indexOf('/') : candidate.indexOf(':');
|
||||
if (slash > 0) {
|
||||
candidate = candidate.substring(0, slash);
|
||||
}
|
||||
return candidate.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.dsl.MessageChannels;
|
||||
import org.springframework.integration.handler.advice.AbstractHandleMessageAdvice;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ErrorMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 为 API 门户集中管理错误通道与处理器。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ErrorHandlingStrategy {
|
||||
|
||||
private final ApiGatewayErrorProcessor errorProcessor;
|
||||
@Getter
|
||||
private final MessageChannel errorChannel;
|
||||
private final Advice errorForwardingAdvice;
|
||||
|
||||
public ErrorHandlingStrategy(ApiGatewayErrorProcessor errorProcessor) {
|
||||
this.errorProcessor = errorProcessor;
|
||||
DirectChannel channel = MessageChannels.direct("apiPortalErrorChannel").getObject();
|
||||
this.errorChannel = channel;
|
||||
channel.subscribe(this::handleErrorMessage);
|
||||
this.errorForwardingAdvice = new ErrorForwardingAdvice();
|
||||
}
|
||||
|
||||
public Advice errorForwardingAdvice() {
|
||||
return errorForwardingAdvice;
|
||||
}
|
||||
|
||||
private void handleErrorMessage(Message<?> message) {
|
||||
if (message instanceof ErrorMessage errorMessage) {
|
||||
handleError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(ErrorMessage errorMessage) {
|
||||
Throwable throwable = errorMessage.getPayload();
|
||||
Message<?> failedMessage = errorMessage.getOriginalMessage();
|
||||
if (failedMessage != null && failedMessage.getPayload() instanceof ApiInvocationContext context) {
|
||||
errorProcessor.apply(context, throwable);
|
||||
}
|
||||
log.error("[API-PORTAL] 集成流程发生错误", throwable);
|
||||
}
|
||||
|
||||
private class ErrorForwardingAdvice extends AbstractHandleMessageAdvice {
|
||||
|
||||
@Override
|
||||
protected Object doInvoke(MethodInvocation invocation, Message<?> message) throws Throwable {
|
||||
try {
|
||||
return invocation.proceed();
|
||||
} catch (Throwable ex) {
|
||||
ErrorMessage errorMessage = new ErrorMessage(ex, message);
|
||||
try {
|
||||
if (!errorChannel.send(errorMessage)) {
|
||||
log.warn("[API-PORTAL] 无法将错误消息转发到通道 {}", errorChannel);
|
||||
}
|
||||
} catch (Exception sendEx) {
|
||||
log.error("[API-PORTAL] 向错误通道投递消息时出错", sendEx);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility helpers for working with request headers inside the gateway module.
|
||||
*/
|
||||
public final class GatewayHeaderUtils {
|
||||
|
||||
private GatewayHeaderUtils() {
|
||||
}
|
||||
|
||||
public static void mergeNormalizedHeaders(Map<String, Object> source, Map<String, Object> target) {
|
||||
if (source == null || target == null) {
|
||||
return;
|
||||
}
|
||||
source.forEach((key, value) -> {
|
||||
String normalized = normalizeHeaderValue(value);
|
||||
if (normalized != null) {
|
||||
target.put(key, normalized);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static String findFirstHeaderValue(Map<String, Object> headers, String headerName) {
|
||||
if (headers == null || !StringUtils.hasText(headerName)) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : headers.entrySet()) {
|
||||
if (headerName.equalsIgnoreCase(entry.getKey())) {
|
||||
return normalizeHeaderValue(entry.getValue());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String stripBearerPrefix(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return token;
|
||||
}
|
||||
String candidate = token.trim();
|
||||
if (candidate.regionMatches(true, 0, "Bearer ", 0, 7)) {
|
||||
candidate = candidate.substring(7).trim();
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
static String normalizeHeaderValue(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof CharSequence sequence) {
|
||||
String candidate = sequence.toString().trim();
|
||||
return StringUtils.hasText(candidate) ? candidate : null;
|
||||
}
|
||||
if (value instanceof Iterable<?> iterable) {
|
||||
for (Object element : iterable) {
|
||||
String candidate = normalizeHeaderValue(element);
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (value.getClass().isArray()) {
|
||||
int length = Array.getLength(value);
|
||||
for (int i = 0; i < length; i++) {
|
||||
String candidate = normalizeHeaderValue(Array.get(value, i));
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
String candidate = value.toString().trim();
|
||||
return StringUtils.hasText(candidate) ? candidate : null;
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.integration.dsl.context.IntegrationFlowContext;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 管理 API 集成流程的动态注册。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class IntegrationFlowManager {
|
||||
|
||||
private final IntegrationFlowContext integrationFlowContext;
|
||||
private final ApiDefinitionService apiDefinitionService;
|
||||
private final ApiFlowAssembler apiFlowAssembler;
|
||||
|
||||
private final Map<String, IntegrationFlowContext.IntegrationFlowRegistration> activeRegistrations = new ConcurrentHashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void bootstrap() {
|
||||
refreshAll();
|
||||
}
|
||||
|
||||
public void refreshAll() {
|
||||
List<ApiDefinitionAggregate> aggregates = apiDefinitionService.loadActiveDefinitions();
|
||||
Map<String, ApiDefinitionAggregate> desired = new ConcurrentHashMap<>();
|
||||
for (ApiDefinitionAggregate aggregate : aggregates) {
|
||||
desired.put(key(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion()), aggregate);
|
||||
}
|
||||
|
||||
// remove flows that are no longer active
|
||||
activeRegistrations.keySet().stream()
|
||||
.filter(existingKey -> !desired.containsKey(existingKey))
|
||||
.forEach(this::deregisterByKey);
|
||||
|
||||
// register or refresh active flows
|
||||
desired.values().forEach(this::registerFlow);
|
||||
}
|
||||
|
||||
public void refresh(String apiCode, String version) {
|
||||
apiDefinitionService.refresh(apiCode, version)
|
||||
.ifPresentOrElse(this::registerFlow, () -> deregister(apiCode, version));
|
||||
}
|
||||
|
||||
public Optional<MessageChannel> locateInputChannel(String apiCode, String version) {
|
||||
String key = key(apiCode, version);
|
||||
IntegrationFlowContext.IntegrationFlowRegistration registration = activeRegistrations.get(key);
|
||||
if (registration == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(registration.getInputChannel());
|
||||
}
|
||||
|
||||
public DebugFlowHandle obtainDebugHandle(ApiDefinitionAggregate aggregate) {
|
||||
String key = key(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||
IntegrationFlowContext.IntegrationFlowRegistration existing = activeRegistrations.get(key);
|
||||
if (existing != null) {
|
||||
return new DebugFlowHandle(existing.getInputChannel(), existing.getId(), false);
|
||||
}
|
||||
ApiFlowRegistration registration = apiFlowAssembler.assemble(aggregate);
|
||||
String debugId = registration.getFlowId() + "#debug#" + UUID.randomUUID();
|
||||
IntegrationFlowContext.IntegrationFlowRegistration debugRegistration = integrationFlowContext.registration(registration.getFlow())
|
||||
.id(debugId)
|
||||
.register();
|
||||
log.debug("[API-PORTAL] 临时注册调试流程 {} 对应 apiCode={} version={}", debugId, aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||
return new DebugFlowHandle(debugRegistration.getInputChannel(), debugRegistration.getId(), true);
|
||||
}
|
||||
|
||||
public void releaseDebugHandle(DebugFlowHandle handle) {
|
||||
if (handle == null || !handle.temporary) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
integrationFlowContext.remove(handle.registrationId);
|
||||
log.debug("[API-PORTAL] 已移除调试流程 {}", handle.registrationId);
|
||||
} catch (Exception ex) {
|
||||
log.warn("移除调试流程 {} 失败", handle.registrationId, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerFlow(ApiDefinitionAggregate aggregate) {
|
||||
String key = key(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||
deregisterByKey(key);
|
||||
ApiFlowRegistration apiFlowRegistration = apiFlowAssembler.assemble(aggregate);
|
||||
IntegrationFlowContext.IntegrationFlowRegistration registration = integrationFlowContext.registration(apiFlowRegistration.getFlow())
|
||||
.id(apiFlowRegistration.getFlowId())
|
||||
.register();
|
||||
activeRegistrations.put(key, registration);
|
||||
log.info("[API-PORTAL] 已注册流程 {} 对应 apiCode={} version={}", apiFlowRegistration.getFlowId(), aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||
}
|
||||
|
||||
private void deregister(String apiCode, String version) {
|
||||
deregisterByKey(key(apiCode, version));
|
||||
}
|
||||
|
||||
private void deregisterByKey(String key) {
|
||||
IntegrationFlowContext.IntegrationFlowRegistration existing = activeRegistrations.remove(key);
|
||||
if (existing != null) {
|
||||
try {
|
||||
integrationFlowContext.remove(existing.getId());
|
||||
log.info("[API-PORTAL] 已注销流程 {} 对应 key {}", existing.getId(), key);
|
||||
} catch (Exception ex) {
|
||||
log.warn("移除集成流程 {} 失败", existing.getId(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String key(String apiCode, String version) {
|
||||
return (apiCode + ":" + version).toLowerCase();
|
||||
}
|
||||
|
||||
public static final class DebugFlowHandle {
|
||||
private final MessageChannel channel;
|
||||
private final String registrationId;
|
||||
private final boolean temporary;
|
||||
|
||||
private DebugFlowHandle(MessageChannel channel, String registrationId, boolean temporary) {
|
||||
this.channel = channel;
|
||||
this.registrationId = registrationId;
|
||||
this.temporary = temporary;
|
||||
}
|
||||
|
||||
public MessageChannel channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public boolean temporary() {
|
||||
return temporary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 通道拦截器,用于捕获耗时指标并增强日志记录。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MonitoringInterceptor implements ChannelInterceptor {
|
||||
|
||||
private static final String HEADER_START_TIME = "ApiPortalStartTime";
|
||||
|
||||
private final MeterRegistry meterRegistry;
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
return MessageBuilder.fromMessage(message)
|
||||
.setHeader(HEADER_START_TIME, Instant.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
|
||||
Instant start = message.getHeaders().get(HEADER_START_TIME, Instant.class);
|
||||
if (start != null) {
|
||||
Duration duration = Duration.between(start, Instant.now());
|
||||
Object payload = message.getPayload();
|
||||
if (payload instanceof ApiInvocationContext context) {
|
||||
Timer.builder("api.portal.latency")
|
||||
.tag("api", context.getApiCode())
|
||||
.tag("version", context.getApiVersion())
|
||||
.register(meterRegistry)
|
||||
.record(duration);
|
||||
}
|
||||
}
|
||||
if (ex != null) {
|
||||
log.error("[API-PORTAL] 通道发送失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.policy.RateLimitPolicyEvaluator;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_EXECUTION_ERROR;
|
||||
|
||||
/**
|
||||
* Builds advice chains for steps based on configured policies.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PolicyAdvisorFactory {
|
||||
|
||||
private final RateLimitPolicyEvaluator rateLimitPolicyEvaluator;
|
||||
|
||||
public Advice[] buildAdvices(ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) {
|
||||
List<Advice> advices = new ArrayList<>();
|
||||
if (aggregate.getRateLimitPolicy() != null) {
|
||||
advices.add(new RateLimitPolicyAdvice(aggregate));
|
||||
}
|
||||
return advices.toArray(Advice[]::new);
|
||||
}
|
||||
|
||||
public Advice[] buildParallelAdvices(ApiDefinitionAggregate aggregate, Object segment) {
|
||||
// For parallel segments we reuse the same rate-limit advice chain
|
||||
return buildAdvices(aggregate, null);
|
||||
}
|
||||
|
||||
private final class RateLimitPolicyAdvice extends AbstractRequestHandlerAdvice {
|
||||
private final ApiDefinitionAggregate aggregate;
|
||||
|
||||
private RateLimitPolicyAdvice(ApiDefinitionAggregate aggregate) {
|
||||
this.aggregate = aggregate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInvoke(ExecutionCallback callback, Object target, org.springframework.messaging.Message<?> message) {
|
||||
if (aggregate.getRateLimitPolicy() != null) {
|
||||
rateLimitPolicyEvaluator.evaluate(aggregate, (ApiInvocationContext) message.getPayload());
|
||||
}
|
||||
try {
|
||||
return callback.execute();
|
||||
} catch (Exception ex) {
|
||||
if (ex instanceof ServiceException serviceException) {
|
||||
throw serviceException;
|
||||
}
|
||||
if (ex instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
throw ServiceExceptionUtil.exception(API_STEP_EXECUTION_ERROR, ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.domain;
|
||||
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Aggregate representing an API definition with its steps and policies.
|
||||
*/
|
||||
@Value
|
||||
@Builder(toBuilder = true)
|
||||
public class ApiDefinitionAggregate {
|
||||
|
||||
ApiDefinitionDO definition;
|
||||
|
||||
List<ApiStepDefinition> steps;
|
||||
|
||||
Map<String, ApiTransformDefinition> apiLevelTransforms;
|
||||
|
||||
ApiPolicyRateLimitDO rateLimitPolicy;
|
||||
|
||||
ApiFlowPublication publication;
|
||||
|
||||
List<ApiCredentialBinding> credentialBindings;
|
||||
|
||||
public List<ApiStepDefinition> getSteps() {
|
||||
return steps == null ? Collections.emptyList() : steps;
|
||||
}
|
||||
|
||||
public Map<String, ApiTransformDefinition> getApiLevelTransforms() {
|
||||
return apiLevelTransforms == null ? Collections.emptyMap() : apiLevelTransforms;
|
||||
}
|
||||
|
||||
public List<ApiCredentialBinding> getCredentialBindings() {
|
||||
return credentialBindings == null ? Collections.emptyList() : credentialBindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一个 HTTP 步骤的 ID(order 最大的 HTTP 步骤)
|
||||
* 用于判断是否需要设置 responseBody
|
||||
*/
|
||||
public Long getLastHttpStepId() {
|
||||
return getSteps().stream()
|
||||
.filter(stepDef -> "HTTP".equalsIgnoreCase(stepDef.getStep().getType()))
|
||||
.max((s1, s2) -> {
|
||||
Integer order1 = s1.getStep().getStepOrder();
|
||||
Integer order2 = s2.getStep().getStepOrder();
|
||||
if (order1 == null && order2 == null) return 0;
|
||||
if (order1 == null) return -1;
|
||||
if (order2 == null) return 1;
|
||||
return order1.compareTo(order2);
|
||||
})
|
||||
.map(stepDef -> stepDef.getStep().getId())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.domain;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Publication metadata for an API flow.
|
||||
*/
|
||||
@Value
|
||||
@Builder(toBuilder = true)
|
||||
public class ApiFlowPublication {
|
||||
|
||||
Long id;
|
||||
|
||||
String releaseTag;
|
||||
|
||||
String snapshot;
|
||||
|
||||
String status;
|
||||
|
||||
boolean active;
|
||||
|
||||
String description;
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.domain;
|
||||
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Domain representation of an orchestration step.
|
||||
*/
|
||||
@Value
|
||||
@Builder(toBuilder = true)
|
||||
public class ApiStepDefinition {
|
||||
|
||||
ApiStepDO step;
|
||||
|
||||
List<ApiTransformDefinition> transforms;
|
||||
|
||||
Map<String, Object> metadata;
|
||||
|
||||
public List<ApiTransformDefinition> getTransforms() {
|
||||
return transforms == null ? Collections.emptyList() : transforms;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata == null ? Collections.emptyMap() : metadata;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.domain;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Domain representation for transformation expression metadata.
|
||||
*/
|
||||
@Value
|
||||
@Builder(toBuilder = true)
|
||||
public class ApiTransformDefinition {
|
||||
|
||||
Long id;
|
||||
|
||||
String phase;
|
||||
|
||||
String expressionType;
|
||||
|
||||
String expression;
|
||||
|
||||
String description;
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.expression;
|
||||
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Context provided to expression engines when evaluating mappings.
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class ExpressionEvaluationContext {
|
||||
|
||||
ApiInvocationContext invocation;
|
||||
|
||||
Object payload;
|
||||
|
||||
Map<String, Object> variables;
|
||||
|
||||
Map<String, Object> headers;
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.expression;
|
||||
|
||||
/**
|
||||
* Expression evaluator contract.
|
||||
*/
|
||||
public interface ExpressionEvaluator {
|
||||
|
||||
Object evaluate(String expression, ExpressionEvaluationContext context) throws Exception;
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.expression;
|
||||
|
||||
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Registry maintaining expression evaluators per language.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ExpressionEvaluatorRegistry {
|
||||
|
||||
private final Map<ExpressionTypeEnum, ExpressionEvaluator> evaluators = new EnumMap<>(ExpressionTypeEnum.class);
|
||||
|
||||
public void register(ExpressionTypeEnum type, ExpressionEvaluator evaluator) {
|
||||
evaluators.put(type, evaluator);
|
||||
}
|
||||
|
||||
public Optional<ExpressionEvaluator> lookup(ExpressionTypeEnum type) {
|
||||
return Optional.ofNullable(evaluators.get(type));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.expression;
|
||||
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_EXPRESSION_EVALUATION_FAILED;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_EXPRESSION_NO_EVALUATOR;
|
||||
|
||||
/**
|
||||
* Executes expressions using registered evaluators.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ExpressionExecutor {
|
||||
|
||||
private final ExpressionEvaluatorRegistry registry;
|
||||
|
||||
public Object evaluate(ExpressionSpec spec, ApiInvocationContext invocation, Object payload, Map<String, Object> headers) throws Exception {
|
||||
if (spec == null || spec.getExpression() == null) {
|
||||
return null;
|
||||
}
|
||||
ExpressionTypeEnum type = spec.getType();
|
||||
return registry.lookup(type)
|
||||
.orElseThrow(() -> ServiceExceptionUtil.exception(API_EXPRESSION_NO_EVALUATOR, type == null ? "" : type.name()))
|
||||
.evaluate(spec.getExpression(), ExpressionEvaluationContext.builder()
|
||||
.invocation(invocation)
|
||||
.payload(payload)
|
||||
.variables(invocation.getVariables())
|
||||
.headers(headers)
|
||||
.build());
|
||||
}
|
||||
|
||||
public Optional<Object> evaluateOptional(ExpressionSpec spec, ApiInvocationContext invocation, Object payload, Map<String, Object> headers) {
|
||||
try {
|
||||
return Optional.ofNullable(evaluate(spec, invocation, payload, headers));
|
||||
} catch (Exception ex) {
|
||||
if (ex instanceof ServiceException serviceException) {
|
||||
throw serviceException;
|
||||
}
|
||||
throw ServiceExceptionUtil.exception(API_EXPRESSION_EVALUATION_FAILED, ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.expression;
|
||||
|
||||
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Parsed expression specification with language metadata.
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class ExpressionSpec {
|
||||
|
||||
ExpressionTypeEnum type;
|
||||
|
||||
String expression;
|
||||
|
||||
public static ExpressionSpec of(ExpressionTypeEnum type, String expression) {
|
||||
return ExpressionSpec.builder().type(type).expression(expression).build();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.expression;
|
||||
|
||||
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Helper to parse unified expression definitions.
|
||||
*/
|
||||
public final class ExpressionSpecParser {
|
||||
|
||||
private ExpressionSpecParser() {
|
||||
}
|
||||
|
||||
public static ExpressionSpec parse(String rawExpression, ExpressionTypeEnum defaultType) {
|
||||
if (!StringUtils.hasText(rawExpression)) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = rawExpression.trim();
|
||||
int separator = trimmed.indexOf("::");
|
||||
if (separator < 0) {
|
||||
return ExpressionSpec.of(defaultType, trimmed);
|
||||
}
|
||||
String type = trimmed.substring(0, separator);
|
||||
String expression = trimmed.substring(separator + 2);
|
||||
ExpressionTypeEnum expressionTypeEnum = ExpressionTypeEnum.fromValue(type);
|
||||
if (expressionTypeEnum == null) {
|
||||
expressionTypeEnum = defaultType;
|
||||
}
|
||||
return ExpressionSpec.of(expressionTypeEnum, expression);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.expression;
|
||||
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_TRANSFORM_RESPONSE_STATUS_INVALID;
|
||||
|
||||
/**
|
||||
* Utility helpers for mutating {@link ApiInvocationContext} based on expression evaluation results.
|
||||
*/
|
||||
public final class GatewayExpressionHelper {
|
||||
|
||||
private GatewayExpressionHelper() {
|
||||
}
|
||||
|
||||
public static void applyContextMutations(ApiInvocationContext context, Object result,
|
||||
boolean allowRequestMutations, boolean allowResponseMutations) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
if (result instanceof Map<?, ?> map) {
|
||||
applyCommonMutations(context, map);
|
||||
if (allowRequestMutations) {
|
||||
applyRequestMutations(context, map);
|
||||
}
|
||||
if (allowResponseMutations) {
|
||||
applyResponseMutations(context, map);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (allowRequestMutations) {
|
||||
context.setRequestBody(result);
|
||||
} else if (allowResponseMutations) {
|
||||
context.setResponseBody(result);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> snapshotRequest(ApiInvocationContext context) {
|
||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||
if (context.getRequestBody() != null) {
|
||||
snapshot.put("body", context.getRequestBody());
|
||||
}
|
||||
if (context.getRequestQueryParams() != null && !context.getRequestQueryParams().isEmpty()) {
|
||||
snapshot.put("query", new LinkedHashMap<>(context.getRequestQueryParams()));
|
||||
}
|
||||
if (context.getRequestHeaders() != null && !context.getRequestHeaders().isEmpty()) {
|
||||
snapshot.put("headers", new LinkedHashMap<>(context.getRequestHeaders()));
|
||||
}
|
||||
if (snapshot.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public static Map<String, Object> snapshotResponse(ApiInvocationContext context) {
|
||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||
if (context.getResponseBody() != null) {
|
||||
snapshot.put("body", context.getResponseBody());
|
||||
}
|
||||
if (context.getResponseStatus() != null) {
|
||||
snapshot.put("status", context.getResponseStatus());
|
||||
}
|
||||
if (context.getResponseMessage() != null) {
|
||||
snapshot.put("message", context.getResponseMessage());
|
||||
}
|
||||
if (snapshot.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private static void applyCommonMutations(ApiInvocationContext context, Map<?, ?> map) {
|
||||
Object variables = map.get("variables");
|
||||
if (variables instanceof Map<?, ?> variableMap) {
|
||||
context.getVariables().putAll(toObjectMap(variableMap));
|
||||
}
|
||||
Object attributes = map.get("attributes");
|
||||
if (attributes instanceof Map<?, ?> attributeMap) {
|
||||
context.getAttributes().putAll(toObjectMap(attributeMap));
|
||||
}
|
||||
Object tenantId = map.get("tenantId");
|
||||
if (tenantId != null) {
|
||||
context.setTenantId(String.valueOf(tenantId));
|
||||
}
|
||||
Object httpMethod = map.get("httpMethod");
|
||||
if (httpMethod != null) {
|
||||
context.setHttpMethod(String.valueOf(httpMethod));
|
||||
}
|
||||
Object requestPath = map.get("requestPath");
|
||||
if (requestPath != null) {
|
||||
context.setRequestPath(String.valueOf(requestPath));
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyRequestMutations(ApiInvocationContext context, Map<?, ?> map) {
|
||||
// 处理查询参数
|
||||
Object query = firstNonNull(map.get("requestQuery"), map.get("requestQueryParams"), map.get("query"));
|
||||
if (query instanceof Map<?, ?> queryMap) {
|
||||
context.getRequestQueryParams().putAll(toObjectMap(queryMap));
|
||||
}
|
||||
|
||||
// 处理请求头
|
||||
Object headers = map.get("requestHeaders");
|
||||
if (headers instanceof Map<?, ?> headerMap) {
|
||||
context.getRequestHeaders().putAll(toStringMap(headerMap));
|
||||
}
|
||||
|
||||
// 处理 body - 使用和 HTTP 步骤一致的逻辑
|
||||
// 检查是否显式指定了 body 字段
|
||||
boolean explicitBody = containsKeyIgnoreCase(map, "body", "payload", "requestBody");
|
||||
|
||||
if (explicitBody) {
|
||||
// 如果显式指定了 body,提取并与原有 body 合并
|
||||
Object body = firstNonNull(map.get("requestBody"), map.get("body"), map.get("payload"));
|
||||
Object existingBody = context.getRequestBody();
|
||||
|
||||
if (body instanceof Map && existingBody instanceof Map) {
|
||||
// 两者都是 Map,合并以保留未映射的字段
|
||||
Map<String, Object> mergedBody = new LinkedHashMap<>(toObjectMap((Map<?, ?>) existingBody));
|
||||
mergedBody.putAll(toObjectMap((Map<?, ?>) body));
|
||||
context.setRequestBody(mergedBody);
|
||||
} else if (body != null) {
|
||||
// 直接替换
|
||||
context.setRequestBody(body);
|
||||
}
|
||||
} else if (query == null) {
|
||||
// 如果没有显式 body 字段,也没有 query 字段
|
||||
// 说明整个 map 是新的 body,与原有 body 合并
|
||||
Object existingBody = context.getRequestBody();
|
||||
if (existingBody instanceof Map) {
|
||||
Map<String, Object> mergedBody = new LinkedHashMap<>(toObjectMap((Map<?, ?>) existingBody));
|
||||
mergedBody.putAll(toObjectMap(map));
|
||||
context.setRequestBody(mergedBody);
|
||||
} else {
|
||||
// 原有 body 不是 Map,直接替换
|
||||
context.setRequestBody(toObjectMap(map));
|
||||
}
|
||||
}
|
||||
// 如果有 query 字段但没有显式 body 字段,保留原有 body(不做任何操作)
|
||||
}
|
||||
|
||||
private static void applyResponseMutations(ApiInvocationContext context, Map<?, ?> map) {
|
||||
Object body = firstNonNull(map.get("responseBody"), map.get("body"));
|
||||
if (body != null) {
|
||||
context.setResponseBody(body);
|
||||
}
|
||||
Object status = map.get("responseStatus");
|
||||
if (status != null) {
|
||||
context.setResponseStatus(asInteger(status));
|
||||
}
|
||||
Object message = map.get("responseMessage");
|
||||
if (message != null) {
|
||||
context.setResponseMessage(String.valueOf(message));
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> toObjectMap(Map<?, ?> origin) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
origin.forEach((key, value) -> result.put(String.valueOf(key), value));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, String> toStringMap(Map<?, ?> origin) {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
origin.forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
result.put(String.valueOf(key), String.valueOf(value));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Integer asInteger(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Number number) {
|
||||
return number.intValue();
|
||||
}
|
||||
String raw = String.valueOf(value);
|
||||
try {
|
||||
return Integer.parseInt(raw);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw ServiceExceptionUtil.exception(API_TRANSFORM_RESPONSE_STATUS_INVALID, raw);
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static <T> T firstNonNull(T... values) {
|
||||
for (T value : values) {
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean containsKeyIgnoreCase(Map<?, ?> source, String... keys) {
|
||||
if (source == null || source.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (String key : keys) {
|
||||
for (Object entryKey : source.keySet()) {
|
||||
if (key.equalsIgnoreCase(String.valueOf(entryKey))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.expression;
|
||||
|
||||
import com.api.jsonata4java.expressions.*;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_JSONATA_BIND_FAILED;
|
||||
/**
|
||||
* JSONata expression evaluator for JSON payload transformation.
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JsonataExpressionEvaluator implements ExpressionEvaluator {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public Object evaluate(String expression, ExpressionEvaluationContext context) throws ParseException, EvaluateException, JsonProcessingException, IOException {
|
||||
Expressions expressions = Expressions.parse(expression);
|
||||
bindEnvironment(expressions.getEnvironment(), context);
|
||||
JsonNode payloadNode = objectMapper.valueToTree(context.getPayload());
|
||||
JsonNode resultNode = expressions.evaluate(payloadNode);
|
||||
if (resultNode == null || resultNode.isNull()) {
|
||||
return null;
|
||||
}
|
||||
return objectMapper.treeToValue(resultNode, Object.class);
|
||||
}
|
||||
|
||||
private void bindEnvironment(FrameEnvironment environment, ExpressionEvaluationContext context) {
|
||||
if (environment == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
environment.setVariable("vars", objectMapper.valueToTree(context.getVariables() == null ? java.util.Collections.emptyMap() : context.getVariables()));
|
||||
environment.setVariable("headers", objectMapper.valueToTree(context.getHeaders() == null ? java.util.Collections.emptyMap() : context.getHeaders()));
|
||||
environment.setVariable("ctx", objectMapper.valueToTree(context.getInvocation()));
|
||||
|
||||
// 直接绑定查询参数,方便访问
|
||||
if (context.getInvocation() != null && context.getInvocation().getRequestQueryParams() != null) {
|
||||
environment.setVariable("query", objectMapper.valueToTree(context.getInvocation().getRequestQueryParams()));
|
||||
} else {
|
||||
environment.setVariable("query", objectMapper.valueToTree(java.util.Collections.emptyMap()));
|
||||
}
|
||||
} catch (EvaluateRuntimeException e) {
|
||||
throw ServiceExceptionUtil.exception(API_JSONATA_BIND_FAILED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.init;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.stereotype.Component;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 在集成流程启动前执行网关编排所需的幂等数据调整。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("gatewayPolicyMigration")
|
||||
public class GatewayPolicyMigration {
|
||||
|
||||
@PostConstruct
|
||||
public void migrate() {
|
||||
log.info("[API-PORTAL] 跳过网关策略迁移,继续使用标准头部令牌认证");
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Standardized response wrapper returned to external clients.
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class ApiGatewayResponse {
|
||||
|
||||
/**
|
||||
* HTTP status code returned by the gateway. Always aligns with the response status line.
|
||||
*/
|
||||
int code;
|
||||
|
||||
String message;
|
||||
|
||||
Object response;
|
||||
|
||||
String traceId;
|
||||
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Runtime context for an API invocation flowing through the integration pipeline.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ApiInvocationContext {
|
||||
|
||||
private final String requestId;
|
||||
|
||||
private final Instant requestTime;
|
||||
|
||||
private String apiCode;
|
||||
|
||||
private String apiVersion;
|
||||
|
||||
private String tenantId;
|
||||
|
||||
private String clientIp;
|
||||
|
||||
private String userAgent;
|
||||
|
||||
private String credentialAppId;
|
||||
|
||||
private Long credentialId;
|
||||
|
||||
private String httpMethod;
|
||||
|
||||
private String requestPath;
|
||||
|
||||
private Map<String, Object> requestHeaders;
|
||||
|
||||
private Object requestBody;
|
||||
|
||||
private Map<String, Object> requestQueryParams;
|
||||
|
||||
private Map<String, Object> variables;
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private List<ApiStepResult> stepResults;
|
||||
|
||||
private Object responseBody;
|
||||
|
||||
private Integer responseStatus;
|
||||
|
||||
private String responseMessage;
|
||||
|
||||
public ApiInvocationContext() {
|
||||
this.requestId = UUID.randomUUID().toString();
|
||||
this.requestTime = Instant.now();
|
||||
this.variables = new HashMap<>();
|
||||
this.attributes = new HashMap<>();
|
||||
this.stepResults = new ArrayList<>();
|
||||
this.requestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
this.requestQueryParams = new HashMap<>();
|
||||
}
|
||||
|
||||
public static ApiInvocationContext create() {
|
||||
return new ApiInvocationContext();
|
||||
}
|
||||
|
||||
public ApiInvocationContext copy() {
|
||||
ApiInvocationContext copy = new ApiInvocationContext();
|
||||
copy.apiCode = this.apiCode;
|
||||
copy.apiVersion = this.apiVersion;
|
||||
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;
|
||||
copy.requestQueryParams.putAll(this.requestQueryParams);
|
||||
copy.responseBody = this.responseBody;
|
||||
copy.responseStatus = this.responseStatus;
|
||||
copy.responseMessage = this.responseMessage;
|
||||
copy.getRequestHeaders().putAll(this.requestHeaders);
|
||||
copy.getVariables().putAll(this.variables);
|
||||
copy.getAttributes().putAll(this.attributes);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public void addStepResult(ApiStepResult result) {
|
||||
this.stepResults.add(result);
|
||||
}
|
||||
|
||||
public ApiStepResult lastStepResult() {
|
||||
if (stepResults.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return stepResults.get(stepResults.size() - 1);
|
||||
}
|
||||
|
||||
public void merge(ApiInvocationContext other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
this.stepResults.addAll(other.getStepResults());
|
||||
this.variables.putAll(other.getVariables());
|
||||
this.attributes.putAll(other.getAttributes());
|
||||
if (other.getResponseBody() != null) {
|
||||
this.responseBody = other.getResponseBody();
|
||||
}
|
||||
if (other.getResponseStatus() != null) {
|
||||
this.responseStatus = other.getResponseStatus();
|
||||
}
|
||||
if (other.getResponseMessage() != null) {
|
||||
this.responseMessage = other.getResponseMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Result of executing a single orchestration step.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@Builder(toBuilder = true)
|
||||
public class ApiStepResult {
|
||||
|
||||
private Long stepId;
|
||||
|
||||
private String stepType;
|
||||
|
||||
private Object request;
|
||||
|
||||
private Object response;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private Duration elapsed;
|
||||
|
||||
private String errorCode;
|
||||
|
||||
private String errorMessage;
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.policy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EVALUATION_FAILED;
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EXCEEDED;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Redis 支撑的基础限流评估器,支持固定窗口计数。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultRateLimitPolicyEvaluator implements RateLimitPolicyEvaluator {
|
||||
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
|
||||
};
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public void evaluate(ApiDefinitionAggregate aggregate, ApiInvocationContext context) {
|
||||
ApiPolicyRateLimitDO rateLimitDO = aggregate.getRateLimitPolicy();
|
||||
if (rateLimitDO == null || !StringUtils.hasText(rateLimitDO.getConfig())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Map<String, Object> config = objectMapper.readValue(rateLimitDO.getConfig(), MAP_TYPE);
|
||||
long limit = ((Number) config.getOrDefault("limit", 100)).longValue();
|
||||
long windowSeconds = ((Number) config.getOrDefault("windowSeconds", 60)).longValue();
|
||||
String key = String.format("databus:api:rl:%s:%s:%s", aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion(), context.getRequestHeaders().getOrDefault("X-Client-Id", "anonymous"));
|
||||
Long counter = stringRedisTemplate.opsForValue().increment(key);
|
||||
if (counter != null && counter == 1L) {
|
||||
stringRedisTemplate.expire(key, Duration.ofSeconds(windowSeconds));
|
||||
}
|
||||
if (counter != null && counter > limit) {
|
||||
throw ServiceExceptionUtil.exception(API_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
} catch (JsonProcessingException | DataAccessException ex) {
|
||||
log.error("API {} 的限流评估失败", aggregate.getDefinition().getApiCode(), ex);
|
||||
throw ServiceExceptionUtil.exception(API_RATE_LIMIT_EVALUATION_FAILED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.policy;
|
||||
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
|
||||
/**
|
||||
* Applies rate limiting decisions for the invocation.
|
||||
*/
|
||||
public interface RateLimitPolicyEvaluator {
|
||||
|
||||
void evaluate(ApiDefinitionAggregate aggregate, ApiInvocationContext context);
|
||||
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.security;
|
||||
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 简单的报文缓存包装,便于后续处理链重复读取解密后的内容。
|
||||
*/
|
||||
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
|
||||
|
||||
private final byte[] cachedBody;
|
||||
private String characterEncoding;
|
||||
private final Map<String, List<String>> additionalHeaders;
|
||||
|
||||
public CachedBodyHttpServletRequest(HttpServletRequest request, byte[] cachedBody) {
|
||||
super(request);
|
||||
this.cachedBody = cachedBody != null ? cachedBody : new byte[0];
|
||||
this.characterEncoding = request.getCharacterEncoding();
|
||||
this.additionalHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException {
|
||||
if (encoding != null) {
|
||||
super.setCharacterEncoding(encoding);
|
||||
}
|
||||
this.characterEncoding = encoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCharacterEncoding() {
|
||||
return characterEncoding != null ? characterEncoding : StandardCharsets.UTF_8.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() {
|
||||
ByteArrayInputStream delegate = new ByteArrayInputStream(cachedBody);
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return delegate.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
// 保持空实现
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return delegate.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
Charset charset = Charset.forName(getCharacterEncoding());
|
||||
return new BufferedReader(new InputStreamReader(getInputStream(), charset));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
return cachedBody.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
return cachedBody.length;
|
||||
}
|
||||
|
||||
public byte[] getCachedBody() {
|
||||
return cachedBody.clone();
|
||||
}
|
||||
|
||||
public void setHeader(String name, String value) {
|
||||
if (!StringUtils.hasText(name)) {
|
||||
return;
|
||||
}
|
||||
if (!StringUtils.hasText(value)) {
|
||||
removeHeader(name);
|
||||
return;
|
||||
}
|
||||
additionalHeaders.put(name, new ArrayList<>(Collections.singletonList(value)));
|
||||
}
|
||||
|
||||
public void setHeaderValues(String name, List<String> values) {
|
||||
if (!StringUtils.hasText(name)) {
|
||||
return;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(values)) {
|
||||
removeHeader(name);
|
||||
return;
|
||||
}
|
||||
additionalHeaders.put(name, new ArrayList<>(values));
|
||||
}
|
||||
|
||||
public void addHeader(String name, String value) {
|
||||
if (!StringUtils.hasText(name) || !StringUtils.hasText(value)) {
|
||||
return;
|
||||
}
|
||||
additionalHeaders.compute(name, (k, existing) -> {
|
||||
List<String> list = existing == null ? new ArrayList<>() : new ArrayList<>(existing);
|
||||
list.add(value);
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
public void removeHeader(String name) {
|
||||
if (!StringUtils.hasText(name)) {
|
||||
return;
|
||||
}
|
||||
additionalHeaders.remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
if (StringUtils.hasText(name)) {
|
||||
List<String> values = additionalHeaders.get(name);
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
return values.get(0);
|
||||
}
|
||||
}
|
||||
return super.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaders(String name) {
|
||||
if (StringUtils.hasText(name)) {
|
||||
List<String> custom = additionalHeaders.get(name);
|
||||
// 如果自定义头已写入,则直接返回,避免与原始头部合并造成重复
|
||||
if (!CollectionUtils.isEmpty(custom)) {
|
||||
return Collections.enumeration(custom);
|
||||
}
|
||||
}
|
||||
return super.getHeaders(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaderNames() {
|
||||
// 使用忽略大小写的集合,避免父请求头名与自定义头名大小写不同而重复
|
||||
Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||
Enumeration<String> parent = super.getHeaderNames();
|
||||
while (parent.hasMoreElements()) {
|
||||
names.add(parent.nextElement());
|
||||
}
|
||||
names.addAll(additionalHeaders.keySet());
|
||||
return Collections.enumeration(names);
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.GatewayHeaderUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Shared helpers for resolving JWT tokens from incoming payloads so debug flows
|
||||
* and external HTTP requests stay consistent.
|
||||
*/
|
||||
public final class GatewayJwtResolver {
|
||||
|
||||
public static final String HEADER_ZT_AUTH_TOKEN = "ZT-Auth-Token";
|
||||
|
||||
private GatewayJwtResolver() {
|
||||
}
|
||||
|
||||
public static String resolveJwtToken(HttpServletRequest request, ObjectMapper objectMapper) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
String token = extractTokenFromHeader(request.getHeaders(HEADER_ZT_AUTH_TOKEN), objectMapper);
|
||||
if (!StringUtils.hasText(token)) {
|
||||
token = extractTokenFromHeader(request.getHeaders(HttpHeaders.AUTHORIZATION), objectMapper);
|
||||
}
|
||||
if (!StringUtils.hasText(token)) {
|
||||
token = normalizeTokenValue(request.getParameter("token"), objectMapper);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public static String resolveJwtToken(Map<String, Object> headers,
|
||||
Map<String, Object> queryParams,
|
||||
ObjectMapper objectMapper) {
|
||||
String token = extractTokenFromMap(headers, HEADER_ZT_AUTH_TOKEN, objectMapper);
|
||||
if (!StringUtils.hasText(token)) {
|
||||
token = extractTokenFromMap(headers, HttpHeaders.AUTHORIZATION, objectMapper);
|
||||
}
|
||||
if (!StringUtils.hasText(token) && queryParams != null) {
|
||||
token = normalizeTokenValue(queryParams.get("token"), objectMapper);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private static String extractTokenFromHeader(Enumeration<String> values, ObjectMapper objectMapper) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
while (values.hasMoreElements()) {
|
||||
String candidate = normalizeTokenString(values.nextElement(), objectMapper);
|
||||
if (StringUtils.hasText(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String extractTokenFromMap(Map<String, Object> headers, String headerName, ObjectMapper objectMapper) {
|
||||
if (headers == null || !StringUtils.hasText(headerName)) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : headers.entrySet()) {
|
||||
if (!headerName.equalsIgnoreCase(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
String candidate = normalizeTokenValue(entry.getValue(), objectMapper);
|
||||
if (StringUtils.hasText(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String normalizeTokenValue(Object value, ObjectMapper objectMapper) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof String str) {
|
||||
return normalizeTokenString(str, objectMapper);
|
||||
}
|
||||
if (value instanceof Map<?, ?> map) {
|
||||
Object direct = map.get("token");
|
||||
if (direct == null) {
|
||||
direct = map.get("accessToken");
|
||||
}
|
||||
if (direct == null) {
|
||||
direct = map.get("authToken");
|
||||
}
|
||||
if (direct == null) {
|
||||
direct = map.get("jwt");
|
||||
}
|
||||
if (direct != null) {
|
||||
String resolved = normalizeTokenValue(direct, objectMapper);
|
||||
if (StringUtils.hasText(resolved)) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
for (Object entryValue : map.values()) {
|
||||
String resolved = normalizeTokenValue(entryValue, objectMapper);
|
||||
if (StringUtils.hasText(resolved)) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Iterable<?> iterable) {
|
||||
Iterator<?> iterator = iterable.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
String resolved = normalizeTokenValue(iterator.next(), objectMapper);
|
||||
if (StringUtils.hasText(resolved)) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (value.getClass().isArray()) {
|
||||
int length = Array.getLength(value);
|
||||
for (int i = 0; i < length; i++) {
|
||||
String resolved = normalizeTokenValue(Array.get(value, i), objectMapper);
|
||||
if (StringUtils.hasText(resolved)) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return normalizeTokenString(String.valueOf(value), objectMapper);
|
||||
}
|
||||
|
||||
private static String normalizeTokenString(String rawValue, ObjectMapper objectMapper) {
|
||||
if (!StringUtils.hasText(rawValue)) {
|
||||
return null;
|
||||
}
|
||||
String candidate = rawValue.trim();
|
||||
if (candidate.startsWith("\"") && candidate.endsWith("\"") && candidate.length() > 1) {
|
||||
candidate = candidate.substring(1, candidate.length() - 1).trim();
|
||||
}
|
||||
if ((candidate.startsWith("{") && candidate.endsWith("}"))
|
||||
|| (candidate.startsWith("[") && candidate.endsWith("]"))) {
|
||||
candidate = parseStructuredToken(candidate, objectMapper);
|
||||
}
|
||||
if (!StringUtils.hasText(candidate)) {
|
||||
return null;
|
||||
}
|
||||
return GatewayHeaderUtils.stripBearerPrefix(candidate);
|
||||
}
|
||||
|
||||
private static String parseStructuredToken(String candidate, ObjectMapper objectMapper) {
|
||||
if (objectMapper == null) {
|
||||
return candidate;
|
||||
}
|
||||
try {
|
||||
Object parsed = objectMapper.readValue(candidate, Object.class);
|
||||
String resolved = normalizeTokenValue(parsed, objectMapper);
|
||||
return StringUtils.hasText(resolved) ? resolved : null;
|
||||
} catch (IOException ex) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user