Files
flow-center-service/src/main/java/com/yunhai/flowcenter/engine/RouteJudgeClient.java
T

161 lines
7.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.flowcenter.engine;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.platform.openapi.core.TargetSpec;
import com.yunhai.platform.openapi.core.YschOpenCall;
import com.yunhai.flowcenter.entity.FlwRouter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 条件路由判断调用客户端(2026-09-24 §4.3):POST {目录解析 host}{basePath}{path}?schId=,
* 身份即本应用 appKey(100019)——签名拦截器由 {@link YschOpenCall} 装配链注入,与回调推送同构。
* 寻址:service_key 走服务目录三级解析(targets > services > 枚举兜底);
* flw_router.base_path 非空时覆盖目录条目的 context-path(网关形态目录无前缀、实际服务有的场景),
* 漏填必 401(规范 §4.2 同款坑)。<b>direct_host 非空的自注册行跳过目录解析直连</b>(2026-10-12 收口)。
* 信封解析复用 SDK({code,msg,data})。
*
* <p>重试纪律:同步 3s×3 次退避重试(500ms/1500ms),业务方义务=幂等纯读轻实现。
* 全部失败抛 {@link JudgeUnavailableException}(由调用方落 outlet=NULL 留痕并挂起)。
*/
@Slf4j
@Service
public class RouteJudgeClient {
/** 判断接口单次超时(毫秒)与重试次数/退避(设计 §4.4:总预算 3s×3 退避重试) */
static final int TIMEOUT_MS = 3_000;
static final int MAX_ATTEMPTS = 3;
private static final long[] BACKOFF_MS = {500L, 1500L};
private final YschOpenCall openCall;
private final ObjectMapper objectMapper;
public RouteJudgeClient(YschOpenCall openCall, ObjectMapper objectMapper) {
this.openCall = openCall;
this.objectMapper = objectMapper;
}
/** 判断请求体(契约冻结:query 带 schId,业务方按 outlets 返回 {outlet};submitterId 2026-10-10 增补,老实现忽略即可) */
public static class JudgeBody {
public final Long schId;
public final Long instanceId;
public final Integer instanceVersion;
public final String bizType;
public final String bizId;
public final Integer submitterId;
public final String nodeId;
public final String nodeName;
public final String routerKey;
public final List<String> outlets;
public final Map<String, Object> variables;
public JudgeBody(Long schId, Long instanceId, Integer instanceVersion, String bizType, String bizId,
Integer submitterId, String nodeId, String nodeName, String routerKey,
List<String> outlets, Map<String, Object> variables) {
this.schId = schId;
this.instanceId = instanceId;
this.instanceVersion = instanceVersion;
this.bizType = bizType;
this.bizId = bizId;
this.submitterId = submitterId;
this.nodeId = nodeId;
this.nodeName = nodeName;
this.routerKey = routerKey;
this.outlets = outlets;
this.variables = variables;
}
}
/** 判断结果:出口名 + 原始返回(data 节点序列化,审计落 flw_route_log.raw_response) */
public static class JudgeResult {
public final String outlet;
public final String rawResponse;
public JudgeResult(String outlet, String rawResponse) {
this.outlet = outlet;
this.rawResponse = rawResponse;
}
}
/** 判断服务不可用(重试耗尽/网络/信封错误);outlet 未命中由调用方判定,不走本异常 */
public static class JudgeUnavailableException extends RuntimeException {
public JudgeUnavailableException(String message, Throwable cause) {
super(message, cause);
}
}
@SuppressWarnings("unchecked")
public JudgeResult call(FlwRouter router, JudgeBody body) {
TargetSpec spec = resolveTarget(router);
Map<String, String> query = new HashMap<>();
query.put("schId", String.valueOf(body.schId));
RuntimeException last = null;
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
try {
Map<String, Object> data = openCall.post(spec, router.getPath(), query, body, Map.class);
String raw = objectMapper.writeValueAsString(data);
Object outlet = data == null ? null : data.get("outlet");
if (outlet == null || String.valueOf(outlet).trim().isEmpty()) {
throw new IllegalStateException("判断接口未返回 outlet:" + raw);
}
return new JudgeResult(String.valueOf(outlet).trim(), raw);
} catch (RuntimeException e) {
last = e;
log.warn("[flow-center] 路由判断调用失败(第 {}/{} 次):router={} {}",
attempt, MAX_ATTEMPTS, router.getRouterKey(), e.toString());
if (attempt < MAX_ATTEMPTS) {
sleep(BACKOFF_MS[attempt - 1]);
}
} catch (Exception e) {
last = new IllegalStateException(e.toString(), e);
log.warn("[flow-center] 路由判断调用异常(第 {}/{} 次):router={} {}",
attempt, MAX_ATTEMPTS, router.getRouterKey(), e.toString());
if (attempt < MAX_ATTEMPTS) {
sleep(BACKOFF_MS[attempt - 1]);
}
}
}
throw new JudgeUnavailableException("判断服务调用失败(已重试 " + MAX_ATTEMPTS + " 次)", last);
}
/**
* 目标解析:
* <ul>
* <li>{@code direct_host} 非空(2026-10-12 starter 自注册行)→ {@link TargetSpec#fromUrl(String)}
* 拆基址直连,<b>跳过服务目录三级解析</b>(service_key/base_path 手工核对坑消失);</li>
* <li>否则(存量手工行):目录三级 + flw_router.base_path 覆盖(空=沿用目录条目前缀),行为逐字不变。</li>
* </ul>
* 两分支同设判断专用 3s 超时。
*/
private TargetSpec resolveTarget(FlwRouter router) {
String directHost = router.getDirectHost();
if (directHost != null && !directHost.trim().isEmpty()) {
return withJudgeTimeout(TargetSpec.fromUrl(directHost.trim()));
}
TargetSpec resolved = openCall.resolveTarget(router.getServiceKey());
String basePath = resolved.getBasePath();
if (router.getBasePath() != null && !router.getBasePath().trim().isEmpty()) {
basePath = router.getBasePath().trim();
}
return withJudgeTimeout(TargetSpec.of(resolved.getBaseHost(), basePath));
}
private static TargetSpec withJudgeTimeout(TargetSpec spec) {
spec.setConnectTimeoutMs((long) TIMEOUT_MS);
spec.setReadTimeoutMs((long) TIMEOUT_MS);
return spec;
}
private static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}