- application.yml:notice.flow.callback-path 由过期的 /callback/flow 改回 /oapi/notice/flow/callback(/callback/* 已随 X-Flw-* 过滤器下线,旧值会让本地注册的 回调地址 404);新增 yh.open.services.flowcenter,删 notice.flow.base-url。 - NoticeFlowProperties.baseUrl 默认值去掉(localhost:8085 静默回落是隐患); FlowCenterClient 在 enabled=true 且「目录与 legacy 键都缺」时构造期 fail-fast (enabled=false 不校验——显式未接入本就不需要地址)。 - 测试:新增 NoticeFlowCallbackConfigContractTest(反射拼控制器映射,冻结 callback-path/callback-base-path 与控制器/Runtime ctx 的咬合);FlowCenterClientTest 补 fail-fast 与 disabled 两态。68 测试全绿。
This commit is contained in:
@@ -58,6 +58,15 @@ public class FlowCenterClient implements FlowClient {
|
||||
if (directoryResolved) {
|
||||
com.yunhai.platform.openapi.flow.YschFlowOpenApi.setCall(call);
|
||||
}
|
||||
String legacy = properties.getBaseUrl() == null ? "" : properties.getBaseUrl().trim();
|
||||
if (properties.isEnabled() && !directoryResolved && legacy.isEmpty()) {
|
||||
// 目录未配且 legacy 键也空:构造期 fail-fast(本键已无默认值),
|
||||
// 避免静默打 localhost 把"地址没配"伪装成"流程中心不可达"。
|
||||
// enabled=false(显式未接入)时不校验——地址本就不需要。
|
||||
throw new IllegalStateException("流程中心调用地址未配置:请配置服务目录 yh.open.services."
|
||||
+ com.yunhai.platform.openapi.core.PlatformService.FLOWCENTER.key()
|
||||
+ "(推荐,容器直连完整 URL)或回落键 notice.flow.base-url");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,8 +15,13 @@ public class NoticeFlowProperties {
|
||||
/** 是否接入流程中心(false=编辑页选择器隐藏、FlowClient 全部抛业务异常降级) */
|
||||
private boolean enabled = true;
|
||||
|
||||
/** flow-center 基址(Tier 0 目标;本地 http://localhost:8085,生产由 projman 注入) */
|
||||
private String baseUrl = "http://localhost:8085";
|
||||
/**
|
||||
* flow-center 基址的 <b>legacy 回落</b>(Tier 0 ad-hoc TargetSpec 路径)。
|
||||
* 主路径是服务目录 {@code yh.open.services.flowcenter} → {@code YschFlowOpenApi} 门面;
|
||||
* 本键仅迁移期兼容,<b>不再给默认值</b>——两者都空时 {@code FlowCenterClient} 构造期
|
||||
* fail-fast,避免静默打 localhost 把"地址没配"伪装成"流程中心不可达"。
|
||||
*/
|
||||
private String baseUrl = "";
|
||||
|
||||
/**
|
||||
* 本应用对外可达基址(回调注册用):flow-center 探活与事件推送都打这里。
|
||||
|
||||
@@ -54,11 +54,14 @@ sms:
|
||||
notice:
|
||||
flow:
|
||||
enabled: true
|
||||
# flow-center 基址(Tier 0 目标,裸地址直连 base-path 空)
|
||||
base-url: http://localhost:8085
|
||||
# flow-center 地址不再单独配置:见上方 yh.open.services.flowcenter(SDK 门面 YschFlowOpenApi)。
|
||||
# legacy 回落键 notice.flow.base-url 已删,且代码侧不再给默认值——两者都缺时启动即失败
|
||||
# (构造期 fail-fast),不静默打 localhost。
|
||||
# 本应用对外可达基址(flow-center 回调探活/事件推送打这里;本地=后端直连端口)
|
||||
callback-base: http://localhost:8101
|
||||
callback-path: /callback/flow
|
||||
# 回调端点:必须与 OapiFlowCallbackController 映射一致(/callback/* 已随 X-Flw-* 过滤器下线,
|
||||
# 旧值会让本地注册的回调地址 404)。不变式由 NoticeFlowCallbackConfigContractTest 冻结。
|
||||
callback-path: /oapi/notice/flow/callback
|
||||
# Notice 前端基址(apply bizUrl 拼接,审批人点击跳回通知详情)
|
||||
front-base-url: http://localhost:8077
|
||||
# 短信到点补发调度(§3.2 修 BP-R5-2):fixedDelay 5min,扫"已发布+要短信+未发过+已到点"
|
||||
@@ -95,6 +98,10 @@ yh:
|
||||
open:
|
||||
# 四域服务目录零配置:userbase/userclass/usermsg/flowcenter 枚举兜底 = base-host + 默认 ctx
|
||||
base-host: http://base.yunhaixingtong.com
|
||||
# flowcenter 显式指向本地流程中心(默认枚举兜底会打 base 域网关,网关无 /oapi 路由);
|
||||
# 生产改容器直连 http://flow-center-service:10008/flow-center(projman 注入)。
|
||||
services:
|
||||
flowcenter: http://localhost:8085
|
||||
# env-config 前端运行时配置(免登录 GET /api/env-config?target=front,数据源见
|
||||
# config/PropertyEnvConfigSource:聚合 starter 自动装配,有 bean 即注册端点)。
|
||||
# 本地无需配值(前端 dev 走 .env.dev);线上值经环境变量 YH_ENV_CONFIG_SOURCE_FRONT
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -141,6 +142,30 @@ class FlowCenterClientTest {
|
||||
return new FlowCenterClient(props, new FixedProvider<>(openCall), null, objectMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("目录与 legacy 键皆未配且 enabled=true:构造期 fail-fast(不静默打 localhost)")
|
||||
void misconfiguredFailsFast() {
|
||||
NoticeFlowProperties props = new NoticeFlowProperties(); // baseUrl 已无默认值
|
||||
YschOpenCall openCall = new YschOpenCall(new YschCallProperties(), objectMapper,
|
||||
(host, basePath) -> java.util.Collections.emptyList());
|
||||
IllegalStateException e = assertThrows(IllegalStateException.class,
|
||||
() -> new FlowCenterClient(props, new FixedProvider<>(openCall), null, objectMapper));
|
||||
assertTrue(e.getMessage().contains("yh.open.services.flowcenter"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("notice.flow.base-url"), e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("enabled=false 且地址全缺:不 fail-fast(显式未接入,地址本就不需要)")
|
||||
void disabledWithoutAddressIsAllowed() {
|
||||
NoticeFlowProperties props = new NoticeFlowProperties();
|
||||
props.setEnabled(false);
|
||||
YschOpenCall openCall = new YschOpenCall(new YschCallProperties(), objectMapper,
|
||||
(host, basePath) -> java.util.Collections.emptyList());
|
||||
FlowCenterClient client = new FlowCenterClient(props, new FixedProvider<>(openCall), null, objectMapper);
|
||||
BizException ex = assertThrows(BizException.class, () -> client.listFlows(1));
|
||||
assertTrue(ex.getMessage().contains("流程中心未接入"), ex.getMessage());
|
||||
}
|
||||
|
||||
private FlowCenterClient newClient() {
|
||||
return newClient(true);
|
||||
}
|
||||
@@ -198,13 +223,13 @@ class FlowCenterClientTest {
|
||||
void registerCallback_contractFields() {
|
||||
FlowModels.CallbackRegBody body = new FlowModels.CallbackRegBody();
|
||||
body.setFlowGuid("fg-1");
|
||||
body.setCallbackUrl("http://localhost:8101/callback/flow");
|
||||
body.setCallbackUrl("http://localhost:8101/oapi/notice/flow/callback");
|
||||
FlowModels.CallbackRegResultVo result = newClient().registerCallback(1, body);
|
||||
assertEquals("reg-secret-0123456789abcdef", result.getSecret());
|
||||
Captured c = captured.get(0);
|
||||
assertEquals("POST", c.method);
|
||||
assertEquals("/oapi/flow/callback/reg", c.path);
|
||||
assertEquals("http://localhost:8101/callback/flow", c.body.get("callbackUrl").asText());
|
||||
assertEquals("http://localhost:8101/oapi/notice/flow/callback", c.body.get("callbackUrl").asText());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package com.yunhai.notice.service.flow;
|
||||
|
||||
import com.yunhai.notice.controller.oapi.OapiFlowCallbackController;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* 回调路径配置契约测试(防"过期值"回归)。<br><br>
|
||||
* 不变式:{@code notice.flow.callback-path}(注册给 flow-center 的回调地址尾段)必须与
|
||||
* {@link OapiFlowCallbackController} 的真实映射逐字一致——不一致时 flow-center 探活/推送全部
|
||||
* 404,而本地单测因为走伪服务端不会发现。<br>
|
||||
* 背景:{@code /callback/*} 自 X-Flw-* 自定义过滤器下线后已不存在,但本地 {@code application.yml}
|
||||
* 一度仍写着 {@code /callback/flow}(生产 yml 与代码默认值都是 {@code /oapi/notice/flow/callback}),
|
||||
* 导致本地起链时注册到已下线的路径。
|
||||
*/
|
||||
@DisplayName("Notice 回调路径配置 - 与控制器映射咬合")
|
||||
class NoticeFlowCallbackConfigContractTest {
|
||||
|
||||
/** 控制器真实映射(反射拼类级 @RequestMapping + 方法级 @PostMapping) */
|
||||
private static String controllerMapping() {
|
||||
RequestMapping classLevel = OapiFlowCallbackController.class.getAnnotation(RequestMapping.class);
|
||||
assertNotNull(classLevel, "控制器必须有类级 @RequestMapping");
|
||||
String base = classLevel.value()[0];
|
||||
for (Method m : OapiFlowCallbackController.class.getDeclaredMethods()) {
|
||||
PostMapping post = m.getAnnotation(PostMapping.class);
|
||||
if (post != null) {
|
||||
String sub = post.value().length > 0 ? post.value()[0] : "";
|
||||
return base + sub;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("控制器缺少 @PostMapping 回调方法");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Object yamlValue(String path) throws Exception {
|
||||
try (InputStream in = NoticeFlowCallbackConfigContractTest.class.getClassLoader()
|
||||
.getResourceAsStream("application.yml")) {
|
||||
assertNotNull(in, "application.yml 必须在测试类路径上");
|
||||
Map<String, Object> root = new Yaml().load(in);
|
||||
Object cur = root;
|
||||
for (String seg : path.split("\\.")) {
|
||||
if (!(cur instanceof Map)) {
|
||||
return null;
|
||||
}
|
||||
cur = ((Map<String, Object>) cur).get(seg);
|
||||
if (cur == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("application.yml 的 notice.flow.callback-path 与控制器映射一致(缺省=代码默认值)")
|
||||
void callbackPathMatchesControllerMapping() throws Exception {
|
||||
String expected = controllerMapping();
|
||||
assertEquals("/oapi/notice/flow/callback", expected, "控制器路径变了,请同步生产配置与注册契约");
|
||||
|
||||
Object configured = yamlValue("notice.flow.callback-path");
|
||||
String actual = configured == null
|
||||
? new NoticeFlowProperties().getCallbackPath() // 未配置 → 代码默认值
|
||||
: String.valueOf(configured);
|
||||
assertEquals(expected, actual,
|
||||
"notice.flow.callback-path 与控制器映射不一致:flow-center 派发会 404");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("application.yml 的 notice.flow.callback-base-path 与部署 context-path 一致(缺省=空)")
|
||||
void callbackBasePathMatchesContextPath() throws Exception {
|
||||
Object configured = yamlValue("notice.flow.callback-base-path");
|
||||
String actual = configured == null ? "" : String.valueOf(configured);
|
||||
// 本地进程无 context-path(server.servlet.context-path 未设)→ 申报空串;
|
||||
// 生产由 projman 注入 /notice(见 deploy/notice.yaml)。配错的表现是回调 401。
|
||||
Object contextPath = yamlValue("server.servlet.context-path");
|
||||
assertEquals(contextPath == null ? "" : String.valueOf(contextPath), actual);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user