Merge remote-tracking branch 'base-version/test' into dev
This commit is contained in:
@@ -53,6 +53,20 @@ public class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> eqIfNotBlank(SFunction<T, ?> column, String val) {
|
||||
if (StringUtils.hasText(val)) {
|
||||
return (LambdaQueryWrapperX<T>) super.eq(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> neIfNotBlank(SFunction<T, ?> column, String val) {
|
||||
if (StringUtils.hasText(val)) {
|
||||
return (LambdaQueryWrapperX<T>) super.ne(column, val);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public LambdaQueryWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
|
||||
if (val != null) {
|
||||
return (LambdaQueryWrapperX<T>) super.gt(column, val);
|
||||
|
||||
@@ -50,6 +50,7 @@ public class ApiGatewayExecutionService {
|
||||
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;
|
||||
@@ -78,14 +79,22 @@ public class ApiGatewayExecutionService {
|
||||
ApiInvocationContext context = message.getPayload();
|
||||
accessLogger.onRequest(context);
|
||||
ApiInvocationContext responseContext;
|
||||
ApiDefinitionAggregate debugAggregate = null;
|
||||
ApiDefinitionAggregate aggregate = null;
|
||||
try {
|
||||
enforceCredentialAuthorization(context);
|
||||
if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
|
||||
debugAggregate = resolveDebugAggregate(context);
|
||||
aggregate = resolveDebugAggregate(context);
|
||||
} else {
|
||||
// 对于非调试调用,也需要获取 aggregate 以便后续应用响应模板
|
||||
aggregate = apiDefinitionService.findByCodeAndVersion(context.getApiCode(), context.getApiVersion())
|
||||
.orElse(null);
|
||||
}
|
||||
if (debugAggregate != null) {
|
||||
responseContext = apiFlowDispatcher.dispatchWithAggregate(debugAggregate, context);
|
||||
// 将 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);
|
||||
}
|
||||
@@ -240,6 +249,17 @@ public class ApiGatewayExecutionService {
|
||||
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)
|
||||
@@ -248,6 +268,47 @@ public class ApiGatewayExecutionService {
|
||||
.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。
|
||||
*/
|
||||
|
||||
@@ -40,4 +40,23 @@ public class ApiDefinitionAggregate {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -96,18 +96,50 @@ public final class GatewayExpressionHelper {
|
||||
}
|
||||
|
||||
private static void applyRequestMutations(ApiInvocationContext context, Map<?, ?> map) {
|
||||
Object body = firstNonNull(map.get("requestBody"), map.get("body"));
|
||||
if (body != null) {
|
||||
context.setRequestBody(body);
|
||||
}
|
||||
Object headers = map.get("requestHeaders");
|
||||
if (headers instanceof Map<?, ?> headerMap) {
|
||||
context.getRequestHeaders().putAll(toStringMap(headerMap));
|
||||
}
|
||||
// 处理查询参数
|
||||
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) {
|
||||
@@ -165,4 +197,18 @@ public final class GatewayExpressionHelper {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,13 @@ public class JsonataExpressionEvaluator implements ExpressionEvaluator {
|
||||
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);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,10 +18,19 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_START_EXECUTION_FAILED;
|
||||
|
||||
/**
|
||||
* Handler for START orchestration nodes.
|
||||
*
|
||||
* <p>增强功能:START 节点的映射结果会存入 context.requestBody,
|
||||
* 使得后续 HTTP 步骤可以自动继承这些公共参数,避免重复配置。
|
||||
*
|
||||
* <p>注意:不将参数存入 context.variables,避免与后续步骤的响应结果冲突。
|
||||
* variables 应该保留给步骤执行结果使用。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -40,11 +49,22 @@ public class StartStepHandler implements ApiStepHandler {
|
||||
Instant start = Instant.now();
|
||||
Object snapshotBefore = GatewayExpressionHelper.snapshotRequest(payload);
|
||||
try {
|
||||
ExpressionSpec spec = ExpressionSpecParser.parse(stepDefinition.getStep().getRequestMappingExpr(), ExpressionTypeEnum.JSON);
|
||||
ExpressionSpec spec = ExpressionSpecParser.parse(
|
||||
stepDefinition.getStep().getRequestMappingExpr(),
|
||||
ExpressionTypeEnum.JSON
|
||||
);
|
||||
if (spec != null) {
|
||||
Object evaluated = expressionExecutor.evaluate(spec, payload, payload.getRequestBody(), headers);
|
||||
// 将查询参数合并到 payload 中,使表达式可以通过 $.query.k 访问
|
||||
// 这样和 HTTP 步骤的映射逻辑保持一致
|
||||
Object enrichedPayload = enrichPayloadWithQuery(payload.getRequestBody(), payload.getRequestQueryParams());
|
||||
|
||||
Object evaluated = expressionExecutor.evaluate(spec, payload, enrichedPayload, headers);
|
||||
|
||||
// 应用上下文变更(包括 requestBody、requestQuery 等)
|
||||
// 支持字段级映射,将外部字段名转换为内部字段名
|
||||
GatewayExpressionHelper.applyContextMutations(payload, evaluated, true, false);
|
||||
}
|
||||
|
||||
payload.addStepResult(ApiStepResult.builder()
|
||||
.stepId(stepDefinition.getStep().getId())
|
||||
.stepType(stepDefinition.getStep().getType())
|
||||
@@ -70,4 +90,26 @@ public class StartStepHandler implements ApiStepHandler {
|
||||
return payload;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将查询参数合并到 payload 中,使表达式可以通过 $.query.k 访问
|
||||
* 这样可以避免 JSONata 环境变量访问的限制
|
||||
*/
|
||||
private Object enrichPayloadWithQuery(Object originalPayload, Map<String, Object> queryParams) {
|
||||
if (queryParams == null || queryParams.isEmpty()) {
|
||||
return originalPayload;
|
||||
}
|
||||
|
||||
Map<String, Object> enriched = new LinkedHashMap<>();
|
||||
|
||||
// 如果原始 payload 是 Map,复制所有字段
|
||||
if (originalPayload instanceof Map) {
|
||||
enriched.putAll((Map<String, Object>) originalPayload);
|
||||
}
|
||||
|
||||
// 添加查询参数到 query 字段
|
||||
enriched.put("query", new LinkedHashMap<>(queryParams));
|
||||
|
||||
return enriched;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user