feat(gateway): 实现API网关响应模板和请求映射增强功能
- 添加getLastHttpStepId方法获取最后HTTP步骤ID用于响应体设置判断 - 实现响应模板功能支持自定义JSON响应格式通过responseTemplate配置 - 增强请求映射逻辑支持查询参数、请求头和请求体的灵活映射与合并 - 优化HTTP步骤处理器中的请求载荷转换和响应映射逻辑 - 在START节点中支持公共参数自动继承到后续HTTP步骤 - 添加查询参数环境变量绑定便于JSONata表达式访问 - 实现向后兼容的默认响应格式保持现有功能不变
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -40,4 +40,23 @@ public class ApiDefinitionAggregate {
|
|||||||
return credentialBindings == null ? Collections.emptyList() : credentialBindings;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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("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("headers", objectMapper.valueToTree(context.getHeaders() == null ? java.util.Collections.emptyMap() : context.getHeaders()));
|
||||||
environment.setVariable("ctx", objectMapper.valueToTree(context.getInvocation()));
|
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) {
|
} catch (EvaluateRuntimeException e) {
|
||||||
throw ServiceExceptionUtil.exception(API_JSONATA_BIND_FAILED);
|
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.Duration;
|
||||||
import java.time.Instant;
|
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;
|
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_START_EXECUTION_FAILED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for START orchestration nodes.
|
* Handler for START orchestration nodes.
|
||||||
|
*
|
||||||
|
* <p>增强功能:START 节点的映射结果会存入 context.requestBody,
|
||||||
|
* 使得后续 HTTP 步骤可以自动继承这些公共参数,避免重复配置。
|
||||||
|
*
|
||||||
|
* <p>注意:不将参数存入 context.variables,避免与后续步骤的响应结果冲突。
|
||||||
|
* variables 应该保留给步骤执行结果使用。
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -40,11 +49,22 @@ public class StartStepHandler implements ApiStepHandler {
|
|||||||
Instant start = Instant.now();
|
Instant start = Instant.now();
|
||||||
Object snapshotBefore = GatewayExpressionHelper.snapshotRequest(payload);
|
Object snapshotBefore = GatewayExpressionHelper.snapshotRequest(payload);
|
||||||
try {
|
try {
|
||||||
ExpressionSpec spec = ExpressionSpecParser.parse(stepDefinition.getStep().getRequestMappingExpr(), ExpressionTypeEnum.JSON);
|
ExpressionSpec spec = ExpressionSpecParser.parse(
|
||||||
|
stepDefinition.getStep().getRequestMappingExpr(),
|
||||||
|
ExpressionTypeEnum.JSON
|
||||||
|
);
|
||||||
if (spec != null) {
|
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);
|
GatewayExpressionHelper.applyContextMutations(payload, evaluated, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.addStepResult(ApiStepResult.builder()
|
payload.addStepResult(ApiStepResult.builder()
|
||||||
.stepId(stepDefinition.getStep().getId())
|
.stepId(stepDefinition.getStep().getId())
|
||||||
.stepType(stepDefinition.getStep().getType())
|
.stepType(stepDefinition.getStep().getType())
|
||||||
@@ -70,4 +90,26 @@ public class StartStepHandler implements ApiStepHandler {
|
|||||||
return payload;
|
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