refactor(flow): 回调端点收敛为 starter 默认 /oapi/flow/callback + 开漂移自动核对
build / build-push (push) Successful in 1m5s

网关契约 /{target}/oapi/** 的首段已由 target 承担,应用内端点再加一段
<target> 是冗余(Notice 的 /oapi/notice/flow/callback 属历史遗留:其 oapi
Controller 统一建在 /oapi/notice/** 下)。

- application.yml: notice.flow.callback-path 与 yh.flow-callback.path
  由 /oapi/notice/flow/callback 收敛为 /oapi/flow/callback
- application.yml: 摘除显式 callback-url,注册地址改由 starter 推导
  (open-host ?: yh.open.base-host) + /{target ?: base-path} + path
  ⇒ 生产 = http://open-gateway-service:10013/notice/oapi/flow/callback
  回滚绳:把全地址写回 callback-url 即可,无需回滚镜像
- application.yml: 开 reconcile-on-register=true —— 端点/ctx/base-path
  变更后镜像行有效但登记 URL 不一致时自动重注册,替代"删镜像行→重新保存
  绑定"的手工恢复路径(注意:重注册会轮换 secret)
- NoticeFlowProperties: callbackPath 默认值同步
- 测试 4 处同步冻结字面量/硬编码路径/断言

测试:mvn -o test → 114/114 通过(含 NoticeFlowApiIT 9 例、FlowCallbackTransientIT
1 例在真实过滤链上按新端点 200)
This commit is contained in:
chenxing
2026-09-24 11:57:01 +08:00
parent 58aadaa95a
commit a7e93cb22d
6 changed files with 26 additions and 14 deletions
@@ -31,8 +31,10 @@ public class NoticeFlowProperties {
/**
* 回调路径:挂在开放接口域下,由平台应用验签(/oapi/* 强制)保护,不再有独立回调过滤器。
* 取 starter 默认形态 /oapi/flow/callback——网关契约 /{target}/oapi/** 的首段已由 target
* 承担,端点内不再带应用名前缀(2026-09-24 由 /oapi/notice/flow/callback 收敛)。
*/
private String callbackPath = "/oapi/notice/flow/callback";
private String callbackPath = "/oapi/flow/callback";
/**
* 本服务部署 context-path(可选,无则空):注册时随 basePath 一并申报给 flow-center,
+14 -6
View File
@@ -59,9 +59,10 @@ notice:
# (构造期 fail-fast),不静默打 localhost。
# 本应用对外可达基址(flow-center 回调探活/事件推送打这里;本地=后端直连端口)
callback-base: http://localhost:8101
# 回调端点:必须与 OapiFlowCallbackController 映射一致(/callback/* 已随 X-Flw-* 过滤器下线,
# 旧值会让本地注册的回调地址 404)。不变式由 NoticeFlowCallbackConfigContractTest 冻结。
callback-path: /oapi/notice/flow/callback
# 回调端点:网关契约 /{target}/oapi/** 的首段已由 target 承担,端点不再带应用名前缀(2026-09-24
# 由 /oapi/notice/flow/callback 收敛为 starter 默认值);必须与 yh.flow-callback.path 一致,
# 否则 flow-center 探活/推送 404。不变式由 NoticeFlowCallbackConfigContractTest 冻结。
callback-path: /oapi/flow/callback
# Notice 前端基址(apply bizUrl 拼接,审批人点击跳回通知详情)
front-base-url: http://localhost:8077
# 短信到点补发调度(§3.2 修 BP-R5-2):fixedDelay 5min,扫"已发布+要短信+未发过+已到点"
@@ -105,10 +106,17 @@ yh:
flow-callback:
# 流程中心回调接收端 starter(2026-09-20 迁移):端点/幂等/乱序门由 starter 承担,
# 业务映射见 NoticeFlowCallbackHandler;验签仍由上方 /oapi/* 平台强制验签负责(X-Flw 不启用)
path: /oapi/notice/flow/callback
# path 取 starter 默认值 /oapi/flow/callback(2026-09-24 由 /oapi/notice/flow/callback 收敛)
path: /oapi/flow/callback
biz-types: [notice]
# 注册用回调地址=对外基址+端点路径(占位符组合,防两处配置漂移;base-path 本地无 context-path 不填)
callback-url: ${notice.flow.callback-base}${yh.flow-callback.path}
# 漂移自动核对(2026-09-24 开):端点路径/ctx/base-path 变更后,镜像行有效但登记 URL 与当前
# 期望不一致时自动重新注册,替代"删镜像行→重新保存绑定"的手工恢复路径。注意重注册会轮换 secret。
reconcile-on-register: true
# 注册地址不再显式配置(2026-09-24 摘除 callback-url):由 starter 推导
# (open-host ?: yh.open.base-host) + /{target ?: base-path} + path
# ⇒ 生产 = http://open-gateway-service:10013 + /notice + /oapi/flow/callback
# base-path 本地无 context-path 不填(生产由 projman 注入 /notice)。改地址回滚:把全地址写回
# callback-url 即可(回滚绳),无需回滚镜像。
open:
# 四域服务目录零配置:userbase/userclass/usermsg/flowcenter 枚举兜底 = base-host + 默认 ctx
base-host: http://base.yunhaixingtong.com
@@ -105,7 +105,7 @@ class FlowCallbackTransientIT {
// 回调端点挂在 /oapi/* 下:以推送方 flow-center(100019)身份带应用签名投递
return new com.yunhai.notice.support.OapiSignedClient(
"http://localhost:" + port, "100019", "flow-center-dev-secret-100019")
.post("/oapi/notice/flow/callback", body, String.class);
.post("/oapi/flow/callback", body, String.class);
}
private static String uuid() {
@@ -25,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Notice 流程接缝 IT(T2.1/T2.3/T2.5,RANDOM_PORT 真实过滤链 + bsch_notice_test):
* ① 回调端点 <b>/oapi/notice/flow/callback</b> 的应用验签(复用平台能力):以 flow-center 身份
* ① 回调端点 <b>/oapi/flow/callback</b> 的应用验签(复用平台能力):以 flow-center 身份
* (appKey=100019,密钥取 trusted-apps)合法签名 200 推进状态 / 坏签名 401 / 匿名 401 /
* 毒 body(缺字段)200 / ping 200 / 重复 eventId 幂等;
* ② /oapi/notice/getDict 提供方三态(匿名 401 / 合法签名 200 / 错误密钥 401);
@@ -42,7 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class NoticeFlowApiIT {
/** 回调端点(开放接口域;验签由 yh.app-auth enforce-patterns=/oapi/* 强制) */
private static final String CALLBACK_PATH = "/oapi/notice/flow/callback";
private static final String CALLBACK_PATH = "/oapi/flow/callback";
/** 推送方身份:flow-center 的 appKey/secret(Notice trusted-apps 白名单) */
private static final String FLOW_APP_KEY = "100019";
private static final String FLOW_APP_SECRET = "flow-center-dev-secret-100019";
@@ -223,13 +223,13 @@ class FlowCenterClientTest {
void registerCallback_contractFields() {
FlowModels.CallbackRegBody body = new FlowModels.CallbackRegBody();
body.setFlowGuid("fg-1");
body.setCallbackUrl("http://localhost:8101/oapi/notice/flow/callback");
body.setCallbackUrl("http://localhost:8101/oapi/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/oapi/notice/flow/callback", c.body.get("callbackUrl").asText());
assertEquals("http://localhost:8101/oapi/flow/callback", c.body.get("callbackUrl").asText());
}
@Test
@@ -14,15 +14,17 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
* 回调路径配置契约测试(防"过期值"回归;2026-09-20 迁 starter 后改版)。<br><br>
* 不变式:{@code yh.flow-callback.path}(starter 接收端点的真实映射来源)必须与
* {@code notice.flow.callback-path}(回调地址尾段的历史真源)逐字一致且等于冻结字面量
* {@code /oapi/notice/flow/callback}——不一致时 flow-center 探活/推送全部 404,
* {@code /oapi/flow/callback}——不一致时 flow-center 探活/推送全部 404,
* 而本地单测因为走伪服务端不会发现。<br>
* 2026-09-24:字面量由 {@code /oapi/notice/flow/callback} 收敛为 starter 默认值
* (网关契约 {@code /{target}/oapi/**} 首段已由 target 承担,端点内不再带应用名前缀)。<br>
* 迁移背景:端点由 yh-flow-callback-spring-boot-starter 的 FlowCallbackReceiverController
* 提供(@RequestMapping("${yh.flow-callback.path:...}")),不再有本地控制器类可反射。
*/
@DisplayName("Notice 回调路径配置 - 与 starter 端点映射咬合")
class NoticeFlowCallbackConfigContractTest {
private static final String FROZEN_CALLBACK_PATH = "/oapi/notice/flow/callback";
private static final String FROZEN_CALLBACK_PATH = "/oapi/flow/callback";
@SuppressWarnings("unchecked")
private static Object yamlValue(String path) throws Exception {