From 0216d0655cabee2b7f1ced5aa7bc1d12b99954e3 Mon Sep 17 00:00:00 2001 From: chenxing Date: Tue, 29 Sep 2026 11:00:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(msg):=20JumpTarget=20=E6=A0=87=E5=87=86?= =?UTF-8?q?=E5=8C=96=E6=8E=A5=E5=85=A5=E2=80=94=E2=80=94=E4=B8=A4=E5=A4=84?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E6=8D=A2=20SDK=20=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E9=97=A8=E9=9D=A2=EF=BC=88app+jump=20=E9=93=BE=E5=BC=8F/setBiz?= =?UTF-8?q?Jump=EF=BC=89=EF=BC=8CjumpPath=20=E4=B8=8E=20bizUrl=20=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E9=80=90=E5=AD=97=E8=8A=82=E9=9B=B6=E5=B7=AE=E5=BC=82?= =?UTF-8?q?=EF=BC=9B=E9=9A=8F=E8=BD=A6=E8=87=AA=E6=8C=87=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=20Phase2=20=E9=95=9C=E5=83=8F=E6=94=B6=E5=8F=A3=EF=BC=88front-?= =?UTF-8?q?base-url=20=E4=B8=8E=20callback-*=20=E9=94=AE=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E3=80=81=E8=A3=B8=E7=9B=B8=E5=AF=B9=E8=B7=AF=E5=BE=84=E3=80=81?= =?UTF-8?q?jumpApp=20=E5=BD=92=E5=B1=9E=E5=A3=B0=E6=98=8E=EF=BC=8C?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=90=8C=E6=AD=A5=EF=BC=89=EF=BC=9Bapplicati?= =?UTF-8?q?on.yml=20=E6=9C=8D=E5=8A=A1=E7=9B=AE=E5=BD=95=E9=94=AE=20flowce?= =?UTF-8?q?nter=E2=86=92flow-center=20=E5=BD=92=E4=B8=80=EF=BC=88D1=20P3?= =?UTF-8?q?=20=E5=B0=BE=E5=B7=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/flow/NoticeFlowProperties.java | 27 +----------- .../service/flow/NoticeFlowService.java | 9 ++-- .../service/msg/NoticeUserMsgNotifier.java | 8 ++-- src/main/resources/application.yml | 32 +++++++------- .../NoticeFlowCallbackConfigContractTest.java | 43 +++++++++---------- .../service/flow/NoticeFlowPublishTest.java | 3 +- .../msg/NoticeUserMsgNotifierTest.java | 3 ++ 7 files changed, 52 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/yunhai/notice/service/flow/NoticeFlowProperties.java b/src/main/java/com/yunhai/notice/service/flow/NoticeFlowProperties.java index d8153f9..777158a 100644 --- a/src/main/java/com/yunhai/notice/service/flow/NoticeFlowProperties.java +++ b/src/main/java/com/yunhai/notice/service/flow/NoticeFlowProperties.java @@ -5,7 +5,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** - * 通知-流程中心接缝配置(notice.flow.* / notice.front.*)。 + * 通知-流程中心接缝配置(notice.flow.*)。front-base-url 已删(2026-09-26): + * bizUrl 改传裸相对路径,绝对化由接收方按页面归属承担(自指地址优化 Phase 2)。 */ @Data @Component @@ -22,28 +23,4 @@ public class NoticeFlowProperties { * fail-fast,避免静默打 localhost 把"地址没配"伪装成"流程中心不可达"。 */ private String baseUrl = ""; - - /** - * 本应用对外可达基址(回调注册用):flow-center 探活与事件推送都打这里。 - * 默认回落 baseUrl 同端口的本服务直连地址;生产经网关时由 projman 注入(如 http://notice:8070/notice)。 - */ - private String callbackBase = "http://localhost:8101"; - - /** - * 回调路径:挂在开放接口域下,由平台应用验签(/oapi/* 强制)保护,不再有独立回调过滤器。 - * 取 starter 默认形态 /oapi/flow/callback——网关契约 /{target}/oapi/** 的首段已由 target - * 承担,端点内不再带应用名前缀(2026-09-24 由 /oapi/notice/flow/callback 收敛)。 - */ - private String callbackPath = "/oapi/flow/callback"; - - /** - * 本服务部署 context-path(可选,无则空):注册时随 basePath 一并申报给 flow-center, - * 推送方按此从回调 URL 的 path 中剥离后再签 X-Signature,与平台 AppAuthFilter 用 - * request.contextPath 剥离后的 path 对齐。生产 Notice 部署在 /notice 前缀下,须配 /notice; - * 配错的表现是回调 401(推送方签的 target 与本服务验签 target 不一致)。 - */ - private String callbackBasePath = ""; - - /** Notice 前端基址(bizUrl 拼接:{front-base}/#/list/detail?serid={id},审批人点链接回通知详情) */ - private String frontBaseUrl = "http://localhost:8077"; } diff --git a/src/main/java/com/yunhai/notice/service/flow/NoticeFlowService.java b/src/main/java/com/yunhai/notice/service/flow/NoticeFlowService.java index 6866b31..62922e6 100644 --- a/src/main/java/com/yunhai/notice/service/flow/NoticeFlowService.java +++ b/src/main/java/com/yunhai/notice/service/flow/NoticeFlowService.java @@ -12,6 +12,7 @@ import com.yunhai.notice.repository.NoticeRepository; import com.yunhai.notice.repository.UpFileRepository; import com.yunhai.platform.apistd.BizException; import com.yunhai.platform.auth.spring.CurrentBoaUser; +import com.yunhai.platform.openapi.core.JumpTarget; import com.yunhai.platform.utils.exception.ErrorCode; import com.yunhai.platform.utils.model.BoaUser; import lombok.RequiredArgsConstructor; @@ -128,15 +129,17 @@ public class NoticeFlowService { callbackRegistrar.ensureRegistered(notice.getSchId(), notice.getFlowGuid()); } - /** apply 出参组装(契约冻结:bizId=notice.id、bizUrl 前端详情、快照只收摘要不收富文本正文; - * formData 带发文部门(2026-09-24)——条件路由判断端点优先读它免查库,缺字段回落查表同口径) */ + /** apply 出参组装(契约冻结:bizId=notice.id、bizUrl=裸相对路径、快照只收摘要不收富文本正文; + * formData 带发文部门(2026-09-24)——条件路由判断端点优先读它免查库,缺字段回落查表同口径。 + * bizUrl 自 2026-09-26 起为裸相对路径(不含域名/ctx/#,接收方按页面归属拼绝对地址), + * Notice 不再需要配置自己的前端基址(自指地址优化 Phase 2) */ FlowModels.ApplyBody buildApplyBody(Notice notice, BoaUser user) { FlowModels.ApplyBody body = new FlowModels.ApplyBody(); body.setFlowGuid(notice.getFlowGuid()); body.setBizType(FlowCenterClient.BIZ_TYPE); body.setBizId(String.valueOf(notice.getId())); body.setBizTitle(notice.getTitle()); - body.setBizUrl(properties.getFrontBaseUrl() + "/#/list/detail?serid=" + notice.getId()); + body.setBizJump(JumpTarget.page("list/detail").param("serid", notice.getId())); body.setBizSnapshot(buildSnapshot(notice)); Map formData = new LinkedHashMap<>(); formData.put("deptId", notice.getDeptId()); diff --git a/src/main/java/com/yunhai/notice/service/msg/NoticeUserMsgNotifier.java b/src/main/java/com/yunhai/notice/service/msg/NoticeUserMsgNotifier.java index 3cbf790..74e8560 100644 --- a/src/main/java/com/yunhai/notice/service/msg/NoticeUserMsgNotifier.java +++ b/src/main/java/com/yunhai/notice/service/msg/NoticeUserMsgNotifier.java @@ -1,6 +1,7 @@ package com.yunhai.notice.service.msg; import com.yunhai.notice.entity.Notice; +import com.yunhai.platform.openapi.core.JumpTarget; import com.yunhai.platform.openapi.msg.model.SendMsgRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -121,10 +122,11 @@ public class NoticeUserMsgNotifier { request.setMsgNo(msgNo); request.setBizType(bizType); request.setBizId(String.valueOf(notice.getId())); - // 0系统通知;跳转 Notice 前端详情页(type=1 打开即标读),UserHome 侧按 bizType=notice 基址拼绝对地址 + // 0系统通知;跳转 Notice 前端详情页(type=1 打开即标读)。jumpApp=notice 声明页面归属: + // 接收方按它查基址拼绝对地址(bizType=notice.expedite 等细分分类也能命中 notice 应用键) request.setMsgType(0); - request.setJumpType(1); - request.setJumpPath("/list/detail?serid=" + notice.getId() + "&type=1"); + request.app("notice") + .jump(JumpTarget.page("list/detail").param("serid", notice.getId()).param("type", 1)); request.setSchId(notice.getSchId()); return request; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bcc6fad..7ccb0d9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -54,17 +54,13 @@ sms: notice: flow: enabled: true - # flow-center 地址不再单独配置:见上方 yh.open.services.flowcenter(SDK 门面 YschFlowOpenApi)。 - # legacy 回落键 notice.flow.base-url 已删,且代码侧不再给默认值——两者都缺时启动即失败 - # (构造期 fail-fast),不静默打 localhost。 - # 本应用对外可达基址(flow-center 回调探活/事件推送打这里;本地=后端直连端口) - callback-base: http://localhost:8101 - # 回调端点:网关契约 /{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 + # flow-center 地址不再单独配置:见上方 yh.open.services.flow-center(SDK 门面 YschFlowOpenApi)。 + # legacy 回落键 notice.flow.base-url 不配即空,且代码侧不给默认值——与目录配置都缺时构造期 + # fail-fast,不静默打 localhost。 + # 回调地址相关键(callback-base / callback-base-path / callback-path)已于 2026-09-24 删除: + # 注册地址改由 yh.flow-callback 三段推导(见下方该段),这三个键已无任何代码引用。 + # 前端基址 front-base-url 已于 2026-09-26 删除:apply bizUrl 改传裸相对路径 + # (/list/detail?serid=...),绝对化由接收方按页面归属(notice)拼——Notice 零地址类配置。 # 短信到点补发调度(§3.2 修 BP-R5-2):fixedDelay 5min,扫"已发布+要短信+未发过+已到点" # 补发(幂等由 notice.sms_sent_at 原子认领保证)。生产加列见 deploy/sql/20260913_add_sms_sent_at.sql sms: @@ -113,17 +109,19 @@ yh: # 期望不一致时自动重新注册,替代"删镜像行→重新保存绑定"的手工恢复路径。注意重注册会轮换 secret。 reconcile-on-register: true # 注册地址不再显式配置(2026-09-24 摘除 callback-url):由 starter 推导 - # (open-host ?: yh.open.base-host) + /{target ?: base-path} + path + # (open-host ?: yh.open.base-host) + /{前缀} + path + # 前缀三层回落:target → base-path → 运行时自证 context-path(ServletContext 容器级, + # 2026-09-26 起);本地无挂载前缀时空串/"/"视为无前缀,URL 退化为 host+path,不报错 # ⇒ 生产 = http://open-gateway-service:10013 + /notice + /oapi/flow/callback - # base-path 本地无 context-path 不填(生产由 projman 注入 /notice)。改地址回滚:把全地址写回 - # callback-url 即可(回滚绳),无需回滚镜像。 + # base-path 可不填(运行时会自证出与真实 ctx 同源的值;显式配置仅作覆盖/逃生口保留)。 + # 改地址回滚:把全地址写回 callback-url 即可(回滚绳),无需回滚镜像。 open: - # 四域服务目录零配置:userbase/userclass/usermsg/flowcenter 枚举兜底 = base-host + 默认 ctx + # 四域服务目录零配置:userbase/userclass/usermsg/flow-center 枚举兜底 = base-host + 默认 ctx base-host: http://base.yunhaixingtong.com - # flowcenter 显式指向本地流程中心(默认枚举兜底会打 base 域网关,网关无 /oapi 路由); + # flow-center 显式指向本地流程中心(默认枚举兜底会打 base 域网关,网关无 /oapi 路由); # 生产改容器直连 http://flow-center-service:10008/flow-center(projman 注入)。 services: - flowcenter: http://localhost:8085 + flow-center: http://localhost:8085 # usermsg 显式指向本地消息中心(生产部署 UserMsg 后改容器直连,如 http://usermsg-service:8080/usermsg) usermsg: http://localhost:8080 # env-config 前端运行时配置(免登录 GET /api/env-config?target=front,数据源见 diff --git a/src/test/java/com/yunhai/notice/service/flow/NoticeFlowCallbackConfigContractTest.java b/src/test/java/com/yunhai/notice/service/flow/NoticeFlowCallbackConfigContractTest.java index 789e364..0cbe55e 100644 --- a/src/test/java/com/yunhai/notice/service/flow/NoticeFlowCallbackConfigContractTest.java +++ b/src/test/java/com/yunhai/notice/service/flow/NoticeFlowCallbackConfigContractTest.java @@ -9,15 +9,18 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * 回调路径配置契约测试(防"过期值"回归;2026-09-20 迁 starter 后改版)。

- * 不变式:{@code yh.flow-callback.path}(starter 接收端点的真实映射来源)必须与 - * {@code notice.flow.callback-path}(回调地址尾段的历史真源)逐字一致且等于冻结字面量 - * {@code /oapi/flow/callback}——不一致时 flow-center 探活/推送全部 404, - * 而本地单测因为走伪服务端不会发现。
+ * 不变式:{@code yh.flow-callback.path}(starter 接收端点的真实映射来源)必须等于冻结字面量 + * {@code /oapi/flow/callback}——不一致时 flow-center 探活/推送全部 404,而本地单测因为走 + * 伪服务端不会发现。
* 2026-09-24:字面量由 {@code /oapi/notice/flow/callback} 收敛为 starter 默认值 - * (网关契约 {@code /{target}/oapi/**} 首段已由 target 承担,端点内不再带应用名前缀)。
+ * (网关契约 {@code /{target}/oapi/**} 首段已由 target 承担,端点内不再带应用名前缀); + * 同日清理 {@code notice.flow.callback-base} / {@code callback-base-path} / {@code callback-path} + * 三个已无代码引用的历史键——注册地址改由 {@code yh.flow-callback} 三段推导, + * 故本测试不再做双键一致性比对,改为断言历史键不得回流。
* 迁移背景:端点由 yh-flow-callback-spring-boot-starter 的 FlowCallbackReceiverController * 提供(@RequestMapping("${yh.flow-callback.path:...}")),不再有本地控制器类可反射。 */ @@ -47,31 +50,25 @@ class NoticeFlowCallbackConfigContractTest { } @Test - @DisplayName("yh.flow-callback.path 与 notice.flow.callback-path 一致且等于冻结字面量") - void starterPathMatchesLegacyCallbackPath() throws Exception { + @DisplayName("yh.flow-callback.path 等于冻结字面量(starter 端点映射唯一真源)") + void starterPathMatchesFrozenLiteral() throws Exception { Object starterConfigured = yamlValue("yh.flow-callback.path"); String starterPath = starterConfigured == null ? "/oapi/flow/callback" // starter 代码默认值(未配置时端点会映射到这里) : String.valueOf(starterConfigured); - Object legacyConfigured = yamlValue("notice.flow.callback-path"); - String legacyPath = legacyConfigured == null - ? new NoticeFlowProperties().getCallbackPath() // 未配置 → 代码默认值 - : String.valueOf(legacyConfigured); - - assertEquals(FROZEN_CALLBACK_PATH, legacyPath, "回调路径历史真源变了,请同步生产配置与注册契约"); - assertEquals(legacyPath, starterPath, - "yh.flow-callback.path 与 notice.flow.callback-path 不一致:flow-center 派发会 404"); + assertEquals(FROZEN_CALLBACK_PATH, starterPath, + "yh.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); + @DisplayName("历史回调键(callback-base / -base-path / -path)已清理,不得回流") + void legacyCallbackKeysAreGone() throws Exception { + assertNull(yamlValue("notice.flow.callback-base"), + "notice.flow.callback-base 已无代码引用,不应再出现在配置里"); + assertNull(yamlValue("notice.flow.callback-base-path"), + "notice.flow.callback-base-path 已无代码引用,不应再出现在配置里"); + assertNull(yamlValue("notice.flow.callback-path"), + "notice.flow.callback-path 已无代码引用,不应再出现在配置里"); } } diff --git a/src/test/java/com/yunhai/notice/service/flow/NoticeFlowPublishTest.java b/src/test/java/com/yunhai/notice/service/flow/NoticeFlowPublishTest.java index da8a46d..0b7554d 100644 --- a/src/test/java/com/yunhai/notice/service/flow/NoticeFlowPublishTest.java +++ b/src/test/java/com/yunhai/notice/service/flow/NoticeFlowPublishTest.java @@ -117,7 +117,6 @@ class NoticeFlowPublishTest { NoticeFlowProperties props = new NoticeFlowProperties(); props.setEnabled(true); - props.setFrontBaseUrl("http://localhost:8077"); AppAuthProperties appAuth = new AppAuthProperties(); appAuth.setAppKey("100617"); @@ -333,7 +332,7 @@ class NoticeFlowPublishTest { assertEquals("notice", b.getBizType()); assertEquals("77", b.getBizId()); assertEquals("九月开学安排", b.getBizTitle()); - assertEquals("http://localhost:8077/#/list/detail?serid=77", b.getBizUrl()); + assertEquals("/list/detail?serid=77", b.getBizUrl()); assertEquals(201, b.getSubmitterId()); assertEquals("张三", b.getSubmitterName()); // 快照:title + summary(纯文本) + attachmentNames[] diff --git a/src/test/java/com/yunhai/notice/service/msg/NoticeUserMsgNotifierTest.java b/src/test/java/com/yunhai/notice/service/msg/NoticeUserMsgNotifierTest.java index f1ea562..5d73fdc 100644 --- a/src/test/java/com/yunhai/notice/service/msg/NoticeUserMsgNotifierTest.java +++ b/src/test/java/com/yunhai/notice/service/msg/NoticeUserMsgNotifierTest.java @@ -159,6 +159,9 @@ class NoticeUserMsgNotifierTest { ArgumentCaptor captor = ArgumentCaptor.forClass(SendMsgRequest.class); verify(client).sendUserMsg(captor.capture()); assertEquals("notice.expedite", captor.getValue().getBizType()); + // Phase 2:细分分类(notice.expedite)不配基址键,靠归属声明 jumpApp=notice 命中应用键 + assertEquals("notice", captor.getValue().getJumpApp()); + assertEquals("/list/detail?serid=6&type=1", captor.getValue().getJumpPath()); assertTrue(captor.getValue().getMsgNo().startsWith("ntc-exp-6-")); }