1. 新增外部系统编码部门编码关联管理

2. 新增统一的 api 对外门户管理
3. 修正各个模块的 api 命名
This commit is contained in:
chenbowen
2025-10-17 17:40:46 +08:00
parent ce8e06d2a3
commit 78bc88b7a6
106 changed files with 4200 additions and 1377 deletions

View File

@@ -0,0 +1,63 @@
package com.zt.plat.module.databus.controller.admin.gateway;
import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayExecutionService;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Map;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(ApiGatewayController.class)
@AutoConfigureMockMvc(addFilters = false)
@TestPropertySource(properties = {
"spring.config.import=optional:",
"spring.cloud.nacos.config.enabled=false",
"spring.cloud.nacos.discovery.enabled=false"
})
class ApiGatewayControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ApiGatewayExecutionService executionService;
@MockBean
private ApiDefinitionService apiDefinitionService;
@Test
void invokeShouldReturnGatewayEnvelope() throws Exception {
ApiGatewayResponse response = ApiGatewayResponse.builder()
.code(200)
.message("OK")
.response(Map.of("code", 0))
.traceId("trace-123")
.build();
when(executionService.invokeForDebug(any(ApiGatewayInvokeReqVO.class)))
.thenReturn(ResponseEntity.ok(response));
mockMvc.perform(post("/databus/gateway/invoke")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"apiCode\":\"demo\",\"version\":\"v1\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.message").value("OK"))
.andExpect(jsonPath("$.response.code").value(0))
.andExpect(jsonPath("$.traceId").value("trace-123"));
}
}

View File

@@ -0,0 +1,105 @@
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 org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.web.servlet.HandlerMapping;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class ApiGatewayRequestMapperTest {
private ApiGatewayRequestMapper mapper;
@BeforeEach
void setUp() {
ApiGatewayProperties properties = new ApiGatewayProperties();
mapper = new ApiGatewayRequestMapper(new ObjectMapper(), properties);
}
@Test
void shouldUseUriTemplateVariablesWhenPresent() {
Map<String, Object> headers = new HashMap<>();
headers.put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map.of("apiCode", "demo.api", "version", "v1"));
headers.put(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, "GET");
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "/admin-api/databus/api/portal/demo.api/v1");
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders", Map.of(org.springframework.http.HttpHeaders.CONTENT_TYPE, org.springframework.http.MediaType.APPLICATION_JSON_VALUE));
ApiInvocationContext context = mapper.map("", headers);
assertThat(context.getApiCode()).isEqualTo("demo.api");
assertThat(context.getApiVersion()).isEqualTo("v1");
assertThat(context.getRequestPath()).isEqualTo("/admin-api/databus/api/portal/demo.api/v1");
assertThat(context.getHttpMethod()).isEqualTo("GET");
}
@Test
void shouldInferFromAbsoluteRequestUrlWhenVariablesMissing() {
Map<String, Object> headers = new HashMap<>();
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "http://localhost:48080/admin-api/databus/api/portal/system.auth.quick-login/v1?foo=bar");
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders", Map.of(org.springframework.http.HttpHeaders.CONTENT_TYPE, org.springframework.http.MediaType.TEXT_PLAIN_VALUE));
ApiInvocationContext context = mapper.map("payload", headers);
assertThat(context.getApiCode()).isEqualTo("system.auth.quick-login");
assertThat(context.getApiVersion()).isEqualTo("v1");
assertThat(context.getRequestPath()).isEqualTo("/admin-api/databus/api/portal/system.auth.quick-login/v1");
}
@Test
void shouldFallbackToHeadersWhenAvailable() {
Map<String, Object> headers = new HashMap<>();
headers.put("apiCode", "override.api");
headers.put("version", "v3");
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "/another/path");
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders", Map.of(org.springframework.http.HttpHeaders.CONTENT_TYPE, org.springframework.http.MediaType.APPLICATION_JSON_VALUE));
ApiInvocationContext context = mapper.map("", headers);
assertThat(context.getApiCode()).isEqualTo("override.api");
assertThat(context.getApiVersion()).isEqualTo("v3");
}
@Test
void shouldNormalizeHeaderValuesAndSupportCaseInsensitiveLookup() {
Map<String, Object> headers = new HashMap<>();
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders", Map.of("ZT-Auth-Token", List.of("token-123")));
ApiInvocationContext context = mapper.map("", headers);
assertThat(context.getRequestHeaders().get("ZT-Auth-Token")).isEqualTo("token-123");
assertThat(context.getRequestHeaders().get("zt-auth-token")).isEqualTo("token-123");
}
@Test
void shouldExtractQueryParamsFromHeaders() {
Map<String, Object> headers = new HashMap<>();
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "/api/demo");
headers.put("http_requestParams", Map.of(
"single", new String[]{"value"},
"multi", List.of("a", "b")
));
ApiInvocationContext context = mapper.map("", headers);
assertThat(context.getRequestQueryParams()).containsEntry("single", "value");
assertThat(context.getRequestQueryParams().get("multi")).isEqualTo(List.of("a", "b"));
}
@Test
void shouldFallbackToQueryStringWhenHeaderMissing() {
Map<String, Object> headers = new HashMap<>();
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "/api/demo?foo=bar&arr=1&arr=2");
ApiInvocationContext context = mapper.map("", headers);
assertThat(context.getRequestQueryParams()).containsEntry("foo", "bar");
assertThat(context.getRequestQueryParams().get("arr")).isEqualTo(List.of("1", "2"));
}
}

View File

@@ -0,0 +1,56 @@
package com.zt.plat.module.databus.framework.integration.gateway.policy;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewayJwtResolver;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class DefaultAuthPolicyEvaluatorTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void shouldResolveTokenFromPrimaryHeader() {
Map<String, Object> headers = new HashMap<>();
headers.put(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, List.of(" token-123 "));
String token = GatewayJwtResolver.resolveJwtToken(headers, Map.of(), objectMapper);
assertThat(token).isEqualTo("token-123");
}
@Test
void shouldFallbackToAuthorizationHeader() {
Map<String, Object> headers = new HashMap<>();
headers.put("Authorization", "Bearer token-456");
String token = GatewayJwtResolver.resolveJwtToken(headers, Map.of(), objectMapper);
assertThat(token).isEqualTo("token-456");
}
@Test
void shouldParseTokenFromStructuredPayload() {
Map<String, Object> headers = new HashMap<>();
headers.put("Authorization", List.of("", "{\"token\":\"abc-789\"}"));
String token = GatewayJwtResolver.resolveJwtToken(headers, Map.of(), objectMapper);
assertThat(token).isEqualTo("abc-789");
}
@Test
void shouldUseQueryParameterAsLastResort() {
Map<String, Object> headers = Map.of();
Map<String, Object> queryParams = Map.of("token", " token-999 ");
String token = GatewayJwtResolver.resolveJwtToken(headers, queryParams, objectMapper);
assertThat(token).isEqualTo("token-999");
}
}

View File

@@ -0,0 +1,159 @@
package com.zt.plat.module.databus.framework.integration.gateway.security;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService;
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
import org.junit.jupiter.api.Test;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.http.HttpStatus;
import java.time.Duration;
import java.util.Collections;
import java.util.Optional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class GatewaySecurityFilterTest {
@Test
void shouldAllowRequestWhenIpPermitted() throws Exception {
ApiGatewayProperties properties = createProperties();
properties.getSecurity().setEnabled(false);
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
request.setRemoteAddr("127.0.0.1");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain();
filter.doFilter(request, response, chain);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(chain.getRequest()).isNotNull();
}
@Test
void shouldRejectRequestWhenIpDenied() throws Exception {
ApiGatewayProperties properties = createProperties();
properties.setDeniedIps(Collections.singletonList("10.0.0.1"));
properties.getSecurity().setEnabled(false);
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
request.setRemoteAddr("10.0.0.1");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain();
filter.doFilter(request, response, chain);
assertThat(response.getStatus()).isEqualTo(403);
assertThat(chain.getRequest()).isNull();
}
@Test
void shouldValidateSecurityHeadersAndEncryptResponse() throws Exception {
ApiGatewayProperties properties = createProperties();
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(valueOperations.setIfAbsent(anyString(), anyString(), any(Duration.class))).thenReturn(Boolean.TRUE);
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
ApiClientCredentialDO credential = new ApiClientCredentialDO();
credential.setAppId("demo-app");
credential.setSignatureType(null);
credential.setEncryptionKey(null);
credential.setEncryptionType(null);
when(credentialService.findActiveCredential("demo-app")).thenReturn(java.util.Optional.of(credential));
properties.getSecurity().setRequireBodyEncryption(false);
properties.getSecurity().setEncryptResponse(false);
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
request.setRemoteAddr("127.0.0.1");
long timestamp = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
String signature = "d41d8cd98f00b204e9800998ecf8427e";
request.addHeader(properties.getSecurity().getAppIdHeader(), "demo-app");
request.addHeader(properties.getSecurity().getTimestampHeader(), String.valueOf(timestamp));
request.addHeader(properties.getSecurity().getNonceHeader(), nonce);
request.addHeader(properties.getSecurity().getSignatureHeader(), signature);
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain();
filter.doFilter(request, response, chain);
assertThat(response.getStatus()).isEqualTo(200);
assertThat(chain.getRequest()).isNotNull();
}
@Test
void shouldEncryptErrorResponseWhenValidationFails() throws Exception {
ApiGatewayProperties properties = createProperties();
properties.getSecurity().setRequireBodyEncryption(false);
properties.getSecurity().setEncryptResponse(true);
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(valueOperations.setIfAbsent(anyString(), anyString(), any(Duration.class))).thenReturn(Boolean.TRUE);
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
ApiClientCredentialDO credential = new ApiClientCredentialDO();
credential.setAppId("demo-app");
credential.setEncryptionKey("demo-secret-key");
credential.setEncryptionType(CryptoSignatureUtils.ENCRYPT_TYPE_AES);
when(credentialService.findActiveCredential("demo-app")).thenReturn(Optional.of(credential));
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
request.setRemoteAddr("127.0.0.1");
long timestamp = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
request.addHeader(properties.getSecurity().getAppIdHeader(), "demo-app");
request.addHeader(properties.getSecurity().getTimestampHeader(), String.valueOf(timestamp));
request.addHeader(properties.getSecurity().getNonceHeader(), nonce);
request.addHeader(properties.getSecurity().getSignatureHeader(), "invalid-signature");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, new MockFilterChain());
assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
String cipherText = response.getContentAsString();
assertThat(cipherText).isNotBlank();
assertThat(cipherText.trim()).doesNotStartWith("{");
String decrypted = CryptoSignatureUtils.decrypt(cipherText, credential.getEncryptionKey(), credential.getEncryptionType());
JsonNode node = new ObjectMapper().readTree(decrypted);
assertThat(node.get("code").asInt()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
assertThat(node.get("message").asText()).isEqualTo("签名校验失败");
}
private ApiGatewayProperties createProperties() {
ApiGatewayProperties properties = new ApiGatewayProperties();
properties.setBasePath("/admin-api/databus/api/portal");
properties.setAllowedIps(Collections.singletonList("127.0.0.1"));
return properties;
}
}

View File

@@ -0,0 +1,100 @@
package com.zt.plat.module.databus.framework.integration.gateway.step.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.zt.plat.framework.common.exception.ServiceException;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
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.ExpressionEvaluatorRegistry;
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor;
import com.zt.plat.module.databus.framework.integration.gateway.expression.JsonataExpressionEvaluator;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.integration.core.GenericHandler;
import org.springframework.messaging.MessageHeaders;
import java.util.Collections;
import java.util.Map;
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_END_EXECUTION_FAILED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class EndStepHandlerTest {
private EndStepHandler handler;
private ApiDefinitionAggregate aggregate;
private static MessageHeaders emptyHeaders() {
return new MessageHeaders(Collections.emptyMap());
}
private static ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
@BeforeEach
void setUp() {
ExpressionEvaluatorRegistry registry = new ExpressionEvaluatorRegistry();
registry.register(ExpressionTypeEnum.JSON, new JsonataExpressionEvaluator(createObjectMapper()));
handler = new EndStepHandler(new ExpressionExecutor(registry));
aggregate = ApiDefinitionAggregate.builder().build();
}
@Test
void shouldApplyResponseMappingsToInvocationContext() {
ApiStepDO stepDO = new ApiStepDO();
stepDO.setId(303L);
stepDO.setType("END");
stepDO.setResponseMappingExpr("JSON::{\"responseBody\": {\"final\": $.raw}, \"responseStatus\": 201, \"responseMessage\": \"accepted\"}");
ApiStepDefinition stepDefinition = ApiStepDefinition.builder().step(stepDO).build();
ApiInvocationContext context = ApiInvocationContext.create();
context.setResponseBody(Map.of("raw", Map.of("value", 1)));
context.setResponseStatus(200);
context.setResponseMessage("ok");
GenericHandler<ApiInvocationContext> genericHandler = handler.build(aggregate, stepDefinition);
ApiInvocationContext result = (ApiInvocationContext) genericHandler.handle(context, emptyHeaders());
assertThat(result).isSameAs(context);
assertThat(result.getResponseBody()).isInstanceOf(Map.class);
@SuppressWarnings("unchecked")
Map<String, ?> responseBody = (Map<String, ?>) result.getResponseBody();
assertThat(responseBody).containsKey("final");
assertThat(result.getResponseStatus()).isEqualTo(201);
assertThat(result.getResponseMessage()).isEqualTo("accepted");
assertThat(result.getStepResults()).hasSize(1);
assertThat(result.getStepResults().get(0).isSuccess()).isTrue();
assertThat(result.getStepResults().get(0).getStepId()).isEqualTo(303L);
}
@Test
void shouldRecordFailureWhenExpressionInvalid() {
ApiStepDO stepDO = new ApiStepDO();
stepDO.setId(404L);
stepDO.setType("END");
stepDO.setResponseMappingExpr("JSON::{broken}");
ApiStepDefinition stepDefinition = ApiStepDefinition.builder().step(stepDO).build();
ApiInvocationContext context = ApiInvocationContext.create();
GenericHandler<ApiInvocationContext> genericHandler = handler.build(aggregate, stepDefinition);
assertThatThrownBy(() -> {
genericHandler.handle(context, emptyHeaders());
})
.isInstanceOf(ServiceException.class)
.extracting(ex -> ((ServiceException) ex).getCode())
.isEqualTo(API_STEP_END_EXECUTION_FAILED.getCode());
assertThat(context.getStepResults()).hasSize(1);
assertThat(context.getStepResults().get(0).isSuccess()).isFalse();
assertThat(context.getStepResults().get(0).getStepId()).isEqualTo(404L);
}
}

View File

@@ -0,0 +1,99 @@
package com.zt.plat.module.databus.framework.integration.gateway.step.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.zt.plat.framework.common.exception.ServiceException;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
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.ExpressionEvaluatorRegistry;
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor;
import com.zt.plat.module.databus.framework.integration.gateway.expression.JsonataExpressionEvaluator;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.integration.core.GenericHandler;
import org.springframework.messaging.MessageHeaders;
import java.util.Collections;
import java.util.Map;
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_START_EXECUTION_FAILED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class StartStepHandlerTest {
private StartStepHandler handler;
private ApiDefinitionAggregate aggregate;
private static MessageHeaders emptyHeaders() {
return new MessageHeaders(Collections.emptyMap());
}
private static ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
@BeforeEach
void setUp() {
ExpressionEvaluatorRegistry registry = new ExpressionEvaluatorRegistry();
registry.register(ExpressionTypeEnum.JSON, new JsonataExpressionEvaluator(createObjectMapper()));
handler = new StartStepHandler(new ExpressionExecutor(registry));
aggregate = ApiDefinitionAggregate.builder().build();
}
@Test
void shouldApplyRequestMappingsToInvocationContext() {
ApiStepDO stepDO = new ApiStepDO();
stepDO.setId(101L);
stepDO.setType("START");
stepDO.setRequestMappingExpr("JSON::{\"requestHeaders\": {\"x-trace\": \"trace-1\"}, \"body\": {\"wrapped\": $}, \"requestQuery\": {\"flag\": \"Y\"}} ");
ApiStepDefinition stepDefinition = ApiStepDefinition.builder().step(stepDO).build();
ApiInvocationContext context = ApiInvocationContext.create();
context.setRequestBody(Map.of("original", "value"));
context.getRequestHeaders().put("tenant-id", "42");
GenericHandler<ApiInvocationContext> genericHandler = handler.build(aggregate, stepDefinition);
ApiInvocationContext result = (ApiInvocationContext) genericHandler.handle(context, emptyHeaders());
assertThat(result).isSameAs(context);
assertThat(result.getRequestHeaders()).containsEntry("x-trace", "trace-1");
assertThat(result.getRequestQueryParams()).containsEntry("flag", "Y");
assertThat(result.getRequestBody()).isInstanceOf(Map.class);
@SuppressWarnings("unchecked")
Map<String, ?> mappedBody = (Map<String, ?>) result.getRequestBody();
assertThat(mappedBody).containsKey("wrapped");
assertThat(result.getStepResults()).hasSize(1);
assertThat(result.getStepResults().get(0).isSuccess()).isTrue();
assertThat(result.getStepResults().get(0).getStepId()).isEqualTo(101L);
}
@Test
void shouldRecordFailureWhenExpressionInvalid() {
ApiStepDO stepDO = new ApiStepDO();
stepDO.setId(202L);
stepDO.setType("START");
stepDO.setRequestMappingExpr("JSON::{invalid}");
ApiStepDefinition stepDefinition = ApiStepDefinition.builder().step(stepDO).build();
ApiInvocationContext context = ApiInvocationContext.create();
GenericHandler<ApiInvocationContext> genericHandler = handler.build(aggregate, stepDefinition);
assertThatThrownBy(() -> {
genericHandler.handle(context, emptyHeaders());
})
.isInstanceOf(ServiceException.class)
.extracting(ex -> ((ServiceException) ex).getCode())
.isEqualTo(API_STEP_START_EXECUTION_FAILED.getCode());
assertThat(context.getStepResults()).hasSize(1);
assertThat(context.getStepResults().get(0).isSuccess()).isFalse();
assertThat(context.getStepResults().get(0).getStepId()).isEqualTo(202L);
}
}

View File

@@ -8,12 +8,10 @@ import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefi
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionStepSaveReqVO;
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionTransformSaveReqVO;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO;
import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper;
import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyAuthMapper;
import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyRateLimitMapper;
import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper;
import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper;
@@ -60,8 +58,6 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
@Resource
private ApiTransformMapper apiTransformMapper;
@Resource
private ApiPolicyAuthMapper apiPolicyAuthMapper;
@Resource
private ApiPolicyRateLimitMapper apiPolicyRateLimitMapper;
@MockBean
@@ -84,9 +80,8 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
@Test
void testCreate_success() {
TenantContextHolder.setTenantId(1L);
Long authId = insertAuthPolicy();
Long rateId = insertRateLimitPolicy();
ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, authId, rateId);
ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, rateId);
Long definitionId = apiDefinitionService.create(reqVO);
ApiDefinitionDO definition = apiDefinitionMapper.selectById(definitionId);
@@ -98,16 +93,18 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
assertEquals(reqVO.getDescription(), definition.getDescription());
List<ApiStepDO> steps = apiStepMapper.selectByApiId(definitionId);
assertEquals(1, steps.size());
ApiStepDO step = steps.get(0);
assertEquals(1, step.getStepOrder());
assertEquals("HTTP", step.getType());
assertEquals(3, steps.size());
assertEquals("START", steps.get(0).getType());
assertEquals(Integer.valueOf(1), steps.get(0).getStepOrder());
assertEquals("HTTP", steps.get(1).getType());
assertEquals(Integer.valueOf(2), steps.get(1).getStepOrder());
assertEquals("END", steps.get(2).getType());
assertEquals(Integer.valueOf(3), steps.get(2).getStepOrder());
List<ApiTransformDO> apiLevelTransforms = apiTransformMapper.selectApiLevelTransforms(definitionId);
assertEquals(1, apiLevelTransforms.size());
assertEquals("REQUEST_PRE", apiLevelTransforms.get(0).getPhase());
assertEquals(0, apiLevelTransforms.size());
List<ApiTransformDO> stepTransforms = apiTransformMapper.selectByStepId(step.getId());
List<ApiTransformDO> stepTransforms = apiTransformMapper.selectByStepId(steps.get(1).getId());
assertEquals(1, stepTransforms.size());
assertEquals("RESPONSE_PRE", stepTransforms.get(0).getPhase());
}
@@ -121,11 +118,10 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
definition.setApiCode("order.create");
definition.setVersion("v1");
definition.setHttpMethod("POST");
definition.setUriPattern("/order/create");
definition.setStatus(ApiStatusEnum.ONLINE.getStatus());
apiDefinitionMapper.insert(definition);
ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, null, null);
ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, null);
ServiceException exception = assertThrows(ServiceException.class, () -> apiDefinitionService.create(reqVO));
assertEquals(GatewayServiceErrorCodeConstants.API_DEFINITION_DUPLICATE.getCode(), exception.getCode());
@@ -134,7 +130,6 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
@Test
void testUpdate_replaceSteps() {
TenantContextHolder.setTenantId(1L);
Long authId = insertAuthPolicy();
Long rateId = insertRateLimitPolicy();
ApiDefinitionDO definition = new ApiDefinitionDO();
definition.setTenantId(1L);
@@ -142,7 +137,6 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
definition.setApiCode("order.update");
definition.setVersion("v1");
definition.setHttpMethod("POST");
definition.setUriPattern("/order/update");
definition.setStatus(ApiStatusEnum.ONLINE.getStatus());
apiDefinitionMapper.insert(definition);
@@ -164,20 +158,21 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
oldTransform.setDeleted(false);
apiTransformMapper.insert(oldTransform);
ApiDefinitionSaveReqVO reqVO = buildSaveReq(definition.getId(), authId, rateId);
ApiDefinitionSaveReqVO reqVO = buildSaveReq(definition.getId(), rateId);
reqVO.setApiCode("order.update");
reqVO.setVersion("v2");
reqVO.getSteps().get(0).setStepOrder(2);
apiDefinitionService.update(reqVO);
List<ApiStepDO> steps = apiStepMapper.selectByApiId(definition.getId());
assertEquals(1, steps.size());
assertEquals(2, steps.get(0).getStepOrder());
assertEquals(3, steps.size());
assertEquals("START", steps.get(0).getType());
assertEquals("HTTP", steps.get(1).getType());
assertEquals("END", steps.get(2).getType());
List<ApiTransformDO> transforms = apiTransformMapper.selectByApiId(definition.getId());
assertThat(transforms)
.extracting(ApiTransformDO::getPhase)
.containsExactlyInAnyOrder("REQUEST_PRE", "RESPONSE_PRE");
.containsExactlyInAnyOrder("RESPONSE_PRE");
}
@Test
@@ -189,7 +184,6 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
definition.setApiCode("order.delete");
definition.setVersion("v1");
definition.setHttpMethod("DELETE");
definition.setUriPattern("/order/delete");
definition.setStatus(ApiStatusEnum.ONLINE.getStatus());
apiDefinitionMapper.insert(definition);
@@ -219,26 +213,24 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
assertThat(apiTransformMapper.selectByApiId(definition.getId())).isEmpty();
}
private ApiDefinitionSaveReqVO buildSaveReq(Long id, Long authId, Long rateId) {
private ApiDefinitionSaveReqVO buildSaveReq(Long id, Long rateId) {
ApiDefinitionSaveReqVO reqVO = new ApiDefinitionSaveReqVO();
reqVO.setId(id);
reqVO.setApiCode("order.create");
reqVO.setVersion("v1");
reqVO.setHttpMethod("POST");
reqVO.setUriPattern("/order/create");
reqVO.setStatus(ApiStatusEnum.ONLINE.getStatus());
reqVO.setDescription("create order");
reqVO.setAuthPolicyId(authId);
reqVO.setRateLimitId(rateId);
ApiDefinitionTransformSaveReqVO apiTransform = new ApiDefinitionTransformSaveReqVO();
apiTransform.setPhase("REQUEST_PRE");
apiTransform.setExpressionType("JSON");
apiTransform.setExpression("{}");
reqVO.getApiLevelTransforms().add(apiTransform);
ApiDefinitionStepSaveReqVO start = new ApiDefinitionStepSaveReqVO();
start.setStepOrder(1);
start.setType("START");
start.setRequestMappingExpr("JSON::{}");
reqVO.getSteps().add(start);
ApiDefinitionStepSaveReqVO step = new ApiDefinitionStepSaveReqVO();
step.setStepOrder(1);
step.setStepOrder(2);
step.setType("HTTP");
step.setTargetEndpoint("https://api.example.com/order");
@@ -247,20 +239,14 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
stepTransform.setExpressionType("JSON");
stepTransform.setExpression("{}");
step.getTransforms().add(stepTransform);
reqVO.getSteps().add(step);
return reqVO;
}
private Long insertAuthPolicy() {
ApiPolicyAuthDO policy = new ApiPolicyAuthDO();
policy.setName("auth");
policy.setType("BASIC");
policy.setConfig("{}");
policy.setTenantId(1L);
policy.setDeleted(false);
apiPolicyAuthMapper.insert(policy);
return policy.getId();
ApiDefinitionStepSaveReqVO end = new ApiDefinitionStepSaveReqVO();
end.setStepOrder(3);
end.setType("END");
end.setResponseMappingExpr("JSON::{}");
reqVO.getSteps().add(end);
return reqVO;
}
private Long insertRateLimitPolicy() {

View File

@@ -1,7 +1,6 @@
DELETE FROM "databus_api_transform";
DELETE FROM "databus_api_step";
DELETE FROM "databus_api_definition";
DELETE FROM "databus_policy_auth";
DELETE FROM "databus_policy_rate_limit";
DELETE FROM "databus_policy_audit";
DELETE FROM "databus_api_flow_publish";

View File

@@ -1,18 +1,14 @@
CREATE TABLE IF NOT EXISTS databus_api_definition (
id BIGINT PRIMARY KEY,
api_code VARCHAR(255) NOT NULL,
uri_pattern VARCHAR(512),
http_method VARCHAR(16),
version VARCHAR(64),
status INT,
description VARCHAR(1024),
auth_policy_id BIGINT,
rate_limit_id BIGINT,
audit_policy_id BIGINT,
response_template CLOB,
cache_strategy VARCHAR(255),
updated_at TIMESTAMP,
grey_released BOOLEAN,
tenant_id BIGINT,
create_time TIMESTAMP,
update_time TIMESTAMP,
@@ -32,7 +28,6 @@ CREATE TABLE IF NOT EXISTS databus_api_step (
response_mapping_expr VARCHAR(1024),
transform_id BIGINT,
timeout BIGINT,
retry_strategy VARCHAR(255),
fallback_strategy VARCHAR(255),
condition_expr VARCHAR(1024),
stop_on_error BOOLEAN,
@@ -60,20 +55,6 @@ CREATE TABLE IF NOT EXISTS databus_api_transform (
deleted BOOLEAN
);
CREATE TABLE IF NOT EXISTS databus_policy_auth (
id BIGINT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
type VARCHAR(64),
config CLOB,
description VARCHAR(512),
tenant_id BIGINT,
create_time TIMESTAMP,
update_time TIMESTAMP,
creator VARCHAR(64),
updater VARCHAR(64),
deleted BOOLEAN
);
CREATE TABLE IF NOT EXISTS databus_policy_rate_limit (
id BIGINT PRIMARY KEY,
name VARCHAR(255) NOT NULL,