[ysch-flow] S2.2 结果/退回消息改走 bizUrl 优先策略(F8 缺口①)

notifyResult/notifyReturned 此前把 jumpPath 硬编码 /instance/{id}、jumpApp 硬编码 null,
消息归属因此落在流程中心,四类消息(待办/抄送/结果/退回)口径不一致。现与 task/cc
同策略并抽出 jumpPath/jumpApp 两个共用判定:bizUrl 非空→直达业务方详情页且归属申报
给 apply 调用方 applyAppCode;bizUrl 空(存量办件)→仍回落中心页且归属留空。
单测 8 例(新增 4 例:结果带/不带 bizUrl、退回、抄送)。
This commit is contained in:
chenxing
2026-10-17 23:00:59 +08:00
parent 775b2392fa
commit 81d9a075e8
2 changed files with 99 additions and 9 deletions
@@ -20,6 +20,9 @@ import java.util.Collections;
* - 任务被办理:todo -1(办理人)
* - 完成/拒绝/撤回:msgType=1、bizType=flow.result、content 必填(结果+意见)、msgNo 带 version
* (重提重启多轮终态互不幂等吞掉)
* - 退回:bizType=flow.returned、msgNo 带 version,通知被退回节点的新待办人
* 结果/退回与待办/抄送同跳转策略(2026-10-17 F8 缺口①):bizUrl 优先 + jumpApp=applyAppCode,
* 空回落中心页且归属留空——四类消息的页面归属口径从此一致。
* - 催办:bizType=flow.urge、msgNo=urge:{taskId}:{第N次}
* 全部在 AFTER_COMMIT 监听器里发送,失败不阻断(UserMsgClient 内部容错)。
*/
@@ -77,9 +80,8 @@ public class UserMsgNotifier {
// 按_by-key 换得的 app_code,接收方按它查基址拼绝对地址);applyAppCode 空(老数据/
// UserBase 不可达)或未传 bizUrl 回落流程中心页(/task/{taskId}、/instance/{id}),
// 页面属自己(jumpApp 留空按发送方回落)。bizType 是消息分类,不是路由键,不再冒充。
boolean useBizUrl = instance.getBizUrl() != null && !instance.getBizUrl().trim().isEmpty();
String jumpPath = useBizUrl ? instance.getBizUrl() : "/task/" + task.getId();
String jumpApp = useBizUrl ? instance.getApplyAppCode() : null;
String jumpPath = jumpPath(instance, "/task/" + task.getId());
String jumpApp = jumpApp(instance);
userMsgClient.sendUserMsg(instance.getSchId().intValue(),
Collections.singletonList(task.getAssigneeId()), 2,
"flow.task", String.valueOf(instance.getId()), title, content,
@@ -124,7 +126,8 @@ public class UserMsgNotifier {
userMsgClient.sendUserMsg(instance.getSchId().intValue(), targets, 1,
"flow.returned", String.valueOf(instance.getId()),
"办件被退回:" + safe(instance.getBizTitle()), content.toString(),
"/instance/" + instance.getId(), null,
// F8 缺口①:退回消息与待办/抄送同策略——业务方详情页优先、归属申报给 apply 调用方
jumpPath(instance, "/instance/" + instance.getId()), jumpApp(instance),
"returned:" + instance.getId() + ":" + instance.getVersion(), 30);
}
@@ -147,14 +150,11 @@ public class UserMsgNotifier {
? "" : "(" + nodeName + ")");
// 跳转与任务到达同策略:bizUrl(业务方详情页,页面归属=apply 调用方 applyAppCode)优先,
// 空回落办件详情(属自己)
boolean useBizUrl = instance.getBizUrl() != null && !instance.getBizUrl().trim().isEmpty();
String jumpPath = useBizUrl ? instance.getBizUrl() : "/instance/" + instance.getId();
String jumpApp = useBizUrl ? instance.getApplyAppCode() : null;
userMsgClient.sendUserMsg(instance.getSchId().intValue(),
Collections.singletonList((Integer) ccUserId), 1,
"flow.cc", String.valueOf(instance.getId()),
"您有新的抄送:" + bizTitle, content,
jumpPath, jumpApp, "cc:" + ccId, 30);
jumpPath(instance, "/instance/" + instance.getId()), jumpApp(instance), "cc:" + ccId, 30);
}
private void notifyResult(FlowEvent event) {
@@ -188,7 +188,9 @@ public class UserMsgNotifier {
Collections.singletonList(instance.getSubmitterId()), 1,
"flow.result", String.valueOf(instance.getId()),
"办件状态更新:" + bizTitle, content.toString(),
"/instance/" + instance.getId(), null,
// F8 缺口①:结果消息与待办/抄送同策略——业务方详情页优先(子应用详情页 iframe
// 显终态进度),jumpApp 申报给 apply 调用方;bizUrl 空仍回落中心办件详情
jumpPath(instance, "/instance/" + instance.getId()), jumpApp(instance),
"result:" + instance.getId() + ":" + instance.getVersion(), 30);
}
@@ -209,6 +211,23 @@ public class UserMsgNotifier {
"/task/" + task.getId(), null, "urge:" + task.getId() + ":" + count, 7);
}
/**
* 四类消息(待办/抄送/结果/退回)共用的跳转路径策略:办件 bizUrl 优先,空/空白回落中心页。
* bizUrl 自 2026-09-26 起是裸相对路径(不含域名/ctx/#),接收方按 jumpApp 查基址拼绝对地址。
*/
private static String jumpPath(FlwInstance instance, String centerFallback) {
return hasBizUrl(instance) ? instance.getBizUrl() : centerFallback;
}
/** 页面归属:走 bizUrl 时申报给 apply 调用方(applyAppCode),回落中心页时留空按发送方归属 */
private static String jumpApp(FlwInstance instance) {
return hasBizUrl(instance) ? instance.getApplyAppCode() : null;
}
private static boolean hasBizUrl(FlwInstance instance) {
return instance.getBizUrl() != null && !instance.getBizUrl().trim().isEmpty();
}
private FlwInstance instanceOf(Long instanceId) {
return instanceId == null ? null : instanceRepository.findById(instanceId).orElse(null);
}
@@ -151,4 +151,75 @@ class UserMsgNotifierBizUrlTest {
assertEquals("/task/78", userMsg.lastJumpPath);
assertNull(userMsg.lastJumpApp);
}
/**
* F8 缺口①:结果消息(completed/rejected/withdrawn)不再硬编码归流程中心,
* 与待办/抄送同策略——bizUrl 优先 + jumpApp=applyAppCode。
*/
@Test
void resultMessage_followsBizUrlStrategy() {
FlwInstance instance = instance(11L, "/leave/detail?id=88", "notice");
when(instanceRepository.findById(11L)).thenReturn(Optional.of(instance));
notifier.onFlowEvent(FlowEvent.of(FlowEvent.Type.COMPLETED, 11L, null,
java.util.Collections.singletonMap("result", "通过")));
assertEquals("/leave/detail?id=88", userMsg.lastJumpPath);
assertEquals("notice", userMsg.lastJumpApp);
}
/** 存量兼容:老办件无 bizUrl 时结果消息仍回落中心办件详情、归属留空 */
@Test
void resultMessageWithoutBizUrl_fallsBackToCenterInstancePage() {
FlwInstance instance = instance(12L, " ", "notice");
when(instanceRepository.findById(12L)).thenReturn(Optional.of(instance));
notifier.onFlowEvent(FlowEvent.of(FlowEvent.Type.REJECTED, 12L));
assertEquals("/instance/12", userMsg.lastJumpPath);
assertNull(userMsg.lastJumpApp);
}
/** F8 缺口①:退回消息同策略(四类消息归属口径从此一致) */
@Test
void returnedMessage_followsBizUrlStrategy() {
FlwInstance instance = instance(13L, "/hall/detail?id=9", "svc-hall");
when(instanceRepository.findById(13L)).thenReturn(Optional.of(instance));
java.util.Map<String, Object> data = new java.util.HashMap<>();
data.put("targetUserIds", java.util.Arrays.asList(201, 202));
data.put("fromNodeName", "科室初审");
data.put("opinion", "请补材料");
notifier.onFlowEvent(FlowEvent.of(FlowEvent.Type.RETURNED, 13L, null, data));
assertEquals("/hall/detail?id=9", userMsg.lastJumpPath);
assertEquals("svc-hall", userMsg.lastJumpApp);
}
/** 抄送消息(原已同策略)纳入同一断言口径,防后续回归 */
@Test
void ccMessage_followsBizUrlStrategy() {
FlwInstance instance = instance(14L, "/notice/detail?serid=3", "notice");
when(instanceRepository.findById(14L)).thenReturn(Optional.of(instance));
java.util.Map<String, Object> data = new java.util.HashMap<>();
data.put("ccUserId", 301);
data.put("ccId", 55);
data.put("nodeName", "抄送分管领导");
notifier.onFlowEvent(FlowEvent.of(FlowEvent.Type.CC_SENT, 14L, null, data));
assertEquals("/notice/detail?serid=3", userMsg.lastJumpPath);
assertEquals("notice", userMsg.lastJumpApp);
}
private FlwInstance instance(long id, String bizUrl, String applyAppCode) {
FlwInstance instance = new FlwInstance();
instance.setId(id);
instance.setSchId(1L);
instance.setBizTitle("测试办件");
instance.setSubmitterId(101);
instance.setBizUrl(bizUrl);
instance.setApplyAppCode(applyAppCode);
return instance;
}
}