feat(openapi): FlowCenterClient 切 SDK appCode 寻址(显式>legacy>fallback),application.yml 出厂 base-host 默认内部网关
build / build-push (push) Successful in 1m18s

This commit is contained in:
chenxing
2026-10-22 17:14:03 +08:00
parent e51201134a
commit f816fc390e
3 changed files with 67 additions and 21 deletions
@@ -13,7 +13,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -26,9 +25,9 @@ import java.util.Map;
*
* <p>主路径走 SDK 类型化门面 {@code YschFlowOpenApi}(服务目录 key=flow-center:
* yh.call.targets &gt; yh.open.services &gt; 枚举 base-host 兜底,《子应用配置优化方案》§4.2
* 零专属地址配置);目录未显式配置时回落 legacy 键 {@code svchall.flow.base-url}(ad-hoc
* 零专属地址配置);目录未显式配置且提供 legacy 时回落 legacy 键 {@code svchall.flow.base-url}(ad-hoc
* TargetSpec,可配带/不带 context-path 两种形态,构造时拆成 host+basePath,迁移期兼容)。
* 两条路径的 per-target base-path 剥离语义一致(同 {@link CmsSiteDirectoryImpl})。
* 无 legacy 时使用 SDK 可解析目标。两条路径的 per-target base-path 剥离语义一致(同 {@link CmsSiteDirectoryImpl})。
*/
@Slf4j
@Component
@@ -38,7 +37,7 @@ public class FlowCenterClient implements FlowClient {
private final ObjectMapper objectMapper;
private final boolean enabled;
private final TargetSpec flowSpec;
/** 服务目录显式配置了 flow-center(targets/services)则主路径走门面命名目标 */
/** 显式目录或无 legacy 时 SDK 可解析 flow-center,则走门面命名目标 */
private final boolean directoryResolved;
public FlowCenterClient(YschOpenCall openCall, ObjectMapper objectMapper,
@@ -48,8 +47,11 @@ public class FlowCenterClient implements FlowClient {
this.objectMapper = objectMapper;
this.enabled = enabled;
String base = trimTrailing(baseUrl == null ? "" : baseUrl.trim());
this.directoryResolved = openCall != null && openCall.hasTarget(
com.yunhai.platform.openapi.core.PlatformService.FLOW_CENTER.key());
// 保持 explicit > legacy;没有 legacy 时允许 SDK 枚举/标准网关解析。
this.directoryResolved = openCall != null && (openCall.hasTarget(
com.yunhai.platform.openapi.core.PlatformService.FLOW_CENTER.key())
|| (base.isEmpty() && openCall.canResolve(
com.yunhai.platform.openapi.core.PlatformService.FLOW_CENTER.key())));
if (directoryResolved) {
com.yunhai.platform.openapi.flow.YschFlowOpenApi.setCall(openCall);
}
@@ -58,12 +60,9 @@ public class FlowCenterClient implements FlowClient {
// enabled=false(显式未接入)时不校验——地址本就不需要。
throw new IllegalStateException("流程中心调用地址未配置:请配置服务目录 yh.open.services."
+ com.yunhai.platform.openapi.core.PlatformService.FLOW_CENTER.key()
+ "(推荐,容器直连完整 URL)或回落键 svchall.flow.base-url");
+ ",或平台网关 yh.open.base-host,或回落键 svchall.flow.base-url");
}
URI uri = URI.create(base);
String host = uri.getScheme() + "://" + uri.getRawAuthority();
String path = uri.getRawPath() == null ? "" : uri.getRawPath();
this.flowSpec = TargetSpec.of(host, path);
this.flowSpec = !enabled || directoryResolved || base.isEmpty() ? null : TargetSpec.fromUrl(base);
}
@Override
+5 -10
View File
@@ -86,23 +86,18 @@ yh:
"100019": "flow-center-dev-secret-100019"
open:
# 身份密钥唯一来源 = yh.app-auth.app-key/app-secret(本文件 app-auth 段),open.* 副本已删
base-host: http://base.yunhaixingtong.com
# 服务目录(子应用配置优化方案 §4.2):跨服务出站唯一地址来源,命名目标走 SDK 门面 /
# YschOpenCall。cms 未晋升 PlatformService 枚举(按名直调);flow-center 已入枚举。
# 完整 URL 拆 host+ctx,per-target base-path 与提供方 context-path 对齐 → 签名天然一致。
# 生产改容器直连:flow-center=http://flow-center-service:10008/flow-center、
# cms=http://yschcms-service:10005/yschcms。
# (base-path /userbase 已删 2026-10-05,对齐 Notice 形态:该键代码默认值即 /userbase,删键无行为变化,
# 回调前缀推导读的是 yh.flow-callback.target/base-path 三层回落,与本键无关)
base-host: ${YH_OPEN_BASE_HOST:http://open-gateway-service:10013}
# 标准后端按 appCode 走统一内部 open-gateway,无需逐应用 URL。
# 本地直连只在运行时显式覆盖 yh.open.services.{appCode};不要写入出厂 jar。
services:
# 生产零配置:不配则枚举兜底 = base-host(网关)+/flow-center;此处显式覆盖仅本地开发用
# (Notice 同款)。生产如仍被平台层注入容器直连值,属平台配置项(projman yh.open.services 段)。
flow-center: http://localhost:8085
# flow-center: http://localhost:8085
cms: http://base.yunhaixingtong.com:10005/yschcms
# form-center 刻意<b>不写本地默认值</b>(TASK_1 交付说明第 2 条的同一取舍):本文件里写
# http://localhost:80xx 会被打进 jar,部署实例就去连容器内的 localhost(2026-09-18 事故)。
# 本机跨应用联调请运行时注入:YH_OPEN_SERVICES_FORMCENTER=http://localhost:<FC 实际端口>;
# 生产不注入即走目录第 3 级兜底(base-host 网关 + PlatformService.FORM_CENTER 的 ctx)。
# 不配条目则走标准 appCode fallback:base-host 网关 + /form-center(无需枚举)。
# form-center: ${YH_OPEN_SERVICES_FORMCENTER:}
# 流程中心回调接收端 starter(2026-09-20 迁移;2026-10-05 收敛 Notice 形态):
@@ -0,0 +1,52 @@
package com.yunhai.svchall.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.platform.openapi.core.*;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.jupiter.api.Assertions.*;
class FlowCenterClientRoutingTest {
private YschOpenCall call(String gateway) {
OpenApiProperties open = new OpenApiProperties();
open.setBaseHost(gateway);
return new YschOpenCall(new YschCallProperties(), open, new ObjectMapper(), null);
}
@Test
void enabledWithoutExplicitTargetUsesSdk() {
YschOpenCall call = call("http://gateway:10013");
assertFalse(call.hasTarget("flow-center"));
FlowCenterClient client = new FlowCenterClient(call, new ObjectMapper(), true, "");
assertTrue(client.enabled());
assertEquals(true, ReflectionTestUtils.getField(client, "directoryResolved"));
assertNull(ReflectionTestUtils.getField(client, "flowSpec"));
assertEquals("http://gateway:10013/form-center", call.resolveTarget("form-center").resolveBaseUrl());
assertDoesNotThrow(() -> new FormCenterCatalogClient(call));
assertDoesNotThrow(() -> new FcSubmissionClient(call));
assertDoesNotThrow(() -> new FormSyncClient(call));
}
@Test
void explicitThenLegacyThenSdkPriority() {
YschCallProperties props = new YschCallProperties();
props.getTargets().put("flow-center", TargetSpec.of("http://explicit", "/flow-center"));
YschOpenCall explicit = new YschOpenCall(props, new ObjectMapper(), null);
FlowCenterClient client = new FlowCenterClient(explicit, new ObjectMapper(), true, "http://legacy/ctx");
assertEquals(true, ReflectionTestUtils.getField(client, "directoryResolved"));
client = new FlowCenterClient(call("http://gateway"), new ObjectMapper(), true, "http://legacy/ctx");
assertEquals(false, ReflectionTestUtils.getField(client, "directoryResolved"));
TargetSpec spec = (TargetSpec) ReflectionTestUtils.getField(client, "flowSpec");
assertEquals("http://legacy/ctx", spec.resolveBaseUrl());
}
@Test
void enabledWithoutResolvableAddressFailsButDisabledDoesNot() {
assertThrows(IllegalStateException.class,
() -> new FlowCenterClient(call(null), new ObjectMapper(), true, ""));
assertThrows(IllegalStateException.class,
() -> new FlowCenterClient(call("http://gateway/open-gateway"), new ObjectMapper(), true, ""));
assertFalse(new FlowCenterClient(call(null), new ObjectMapper(), false, "").enabled());
assertFalse(new FlowCenterClient(call(null), new ObjectMapper(), false, "unused-legacy-value").enabled());
}
}