fix(msg): 消息 jump_app 换源 apply 调用方 app_code 快照,替代 bizType 冒充
build / build-push (push) Successful in 49s
build / build-push (push) Successful in 49s
P1/P2 联合修复(消息归属 + jump_app 值域污染): - FlwInstance 新增 apply_app_key/apply_app_code 双列快照(自动 DDL) - apply 从验签上下文(yh.app.auth)取调用方身份,by-key 一次换得 app_code (域间边界只在 apply 转换一次;查无/不可达降级 null,不阻断 apply) - UserMsgNotifier 两处 jumpApp 改源 applyAppCode——bizType 是消息分类 不是路由键;降级空时接收方按发送方回落(现状行为) - FlowDefAdminService.save appKey 必填化(2026-10-12 拍板):新建必须 显式传;升版本/直改可继承源行,源行为空必须补填(存量 19 条 NULL 随编辑收敛) - 单测:SaveTest 新增必填拦截 3 用例、BizUrlTest 换源断言+降级用例(19 绿) - E2E(8085+UserBase):100617->apply_app_code=notice 正向落库、 100018(app_code NULL)->降级成功、错签名 401,三断言全过
This commit is contained in:
@@ -102,7 +102,7 @@ class FlowEngineIT extends FlowTestSupport {
|
||||
SCH, flowGuid, 1).orElseThrow(IllegalStateException::new);
|
||||
FlowInstanceService.ApplyCommand cmd = new FlowInstanceService.ApplyCommand(
|
||||
SCH, def, "svc_item", bizId, "张三的测试办件", "http://biz/1", "{\"k\":\"v\"}",
|
||||
submitterId, submitterName, null);
|
||||
submitterId, submitterName, null, "100001", "notice");
|
||||
return instanceService.apply(cmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,11 @@ import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 待办通知 jumpPath/jumpApp 语义(自指地址优化 Phase 2):办件 bizUrl 非空=直达业务方详情页,
|
||||
* 页面归属 jumpApp=instance.bizType(接收方按它查基址);空/空白=回落流程中心办理页
|
||||
* /task/{taskId},页面属自己(jumpApp=null,接收方按 bizType 回落)。
|
||||
* 待办通知 jumpPath/jumpApp 语义(自指地址优化 Phase 2;2026-10-12 换源):
|
||||
* 办件 bizUrl 非空=直达业务方详情页,页面归属 jumpApp=instance.applyAppCode(apply 时由验签
|
||||
* 身份按 by-key 换得的 app_code——bizType 是消息分类,不是路由键,不再冒充);
|
||||
* applyAppCode 空(存量行/UserBase 不可达降级)→ jumpApp=null,接收方按发送方回落;
|
||||
* bizUrl 空/空白=回落流程中心办理页 /task/{taskId},页面属自己。
|
||||
*/
|
||||
class UserMsgNotifierBizUrlTest {
|
||||
|
||||
@@ -73,6 +75,8 @@ class UserMsgNotifierBizUrlTest {
|
||||
instance.setSchId(1L);
|
||||
instance.setBizTitle("张三的事项");
|
||||
instance.setBizType("notice");
|
||||
instance.setApplyAppKey("100001");
|
||||
instance.setApplyAppCode("notice");
|
||||
instance.setBizUrl("/list/detail?serid=27");
|
||||
when(taskRepository.findById(77L)).thenReturn(Optional.of(task));
|
||||
when(instanceRepository.findById(5L)).thenReturn(Optional.of(instance));
|
||||
@@ -83,6 +87,28 @@ class UserMsgNotifierBizUrlTest {
|
||||
assertEquals("notice", userMsg.lastJumpApp);
|
||||
}
|
||||
|
||||
/** 降级兼容:applyAppCode 空(存量行/UserBase 不可达)→ jumpApp=null,接收方按发送方回落 */
|
||||
@Test
|
||||
void applyAppCodeBlank_jumpAppNull_evenWithBizUrl() {
|
||||
FlwTask task = new FlwTask();
|
||||
task.setId(81L);
|
||||
task.setAssigneeId(101);
|
||||
task.setInstanceId(9L);
|
||||
FlwInstance instance = new FlwInstance();
|
||||
instance.setId(9L);
|
||||
instance.setSchId(1L);
|
||||
instance.setBizTitle("赵六的事项");
|
||||
instance.setBizType("svc_item");
|
||||
instance.setBizUrl("/hall/detail?id=3");
|
||||
when(taskRepository.findById(81L)).thenReturn(Optional.of(task));
|
||||
when(instanceRepository.findById(9L)).thenReturn(Optional.of(instance));
|
||||
|
||||
notifier.onFlowEvent(FlowEvent.of(FlowEvent.Type.TASK_CREATED, 9L, 81L, 101, "丁", null));
|
||||
|
||||
assertEquals("/hall/detail?id=3", userMsg.lastJumpPath);
|
||||
assertNull(userMsg.lastJumpApp);
|
||||
}
|
||||
|
||||
/** 存量兼容:业务方尚未升级仍传绝对 URL 时原样透传,jumpApp 照常按归属声明 */
|
||||
@Test
|
||||
void bizUrlLegacyAbsolute_passedThroughWithOwner() {
|
||||
@@ -95,6 +121,7 @@ class UserMsgNotifierBizUrlTest {
|
||||
instance.setSchId(1L);
|
||||
instance.setBizTitle("王五的事项");
|
||||
instance.setBizType("notice");
|
||||
instance.setApplyAppCode("notice");
|
||||
instance.setBizUrl("http://base.yunhaixingtong.com/notice/#/list/detail?serid=27");
|
||||
when(taskRepository.findById(79L)).thenReturn(Optional.of(task));
|
||||
when(instanceRepository.findById(7L)).thenReturn(Optional.of(instance));
|
||||
|
||||
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.when;
|
||||
/**
|
||||
* save() 分派语义(2026-10-10 唯一键改 (sch,flow_guid,version) 后):
|
||||
* 新建=永远新线 v1(编码只是标签);升版本=复制行 maxByGuid+1;唯一键冲突转友好 BizException。
|
||||
* 2026-10-12 拍板 appKey 必填:新建必须显式传;升版本/直改可继承源行,源行为空必须补填。
|
||||
* 注:@Nested 在 surefire 2.22 的 -Dtest 过滤下发现不了,保持拍平写法。
|
||||
*/
|
||||
class FlowDefAdminServiceSaveTest {
|
||||
@@ -70,7 +71,7 @@ class FlowDefAdminServiceSaveTest {
|
||||
@Test
|
||||
void 新建_永远新开流程线_版本从1起() {
|
||||
ArgumentCaptor<FlwDef> cap = ArgumentCaptor.forClass(FlwDef.class);
|
||||
FlwDef saved = service.save(SCH, null, "work-leave", "请假", JSON, null, null);
|
||||
FlwDef saved = service.save(SCH, null, "work-leave", "请假", JSON, null, "notice");
|
||||
verify(defRepository).save(cap.capture());
|
||||
assertEquals(cap.getValue(), saved);
|
||||
assertEquals(1, saved.getVersion());
|
||||
@@ -81,8 +82,8 @@ class FlowDefAdminServiceSaveTest {
|
||||
|
||||
@Test
|
||||
void 连续新建同编码_各得独立流程线() {
|
||||
FlwDef a = service.save(SCH, null, "work-leave", "产假", JSON, null, null);
|
||||
FlwDef b = service.save(SCH, null, "work-leave", "丧假", JSON, null, null);
|
||||
FlwDef a = service.save(SCH, null, "work-leave", "产假", JSON, null, "notice");
|
||||
FlwDef b = service.save(SCH, null, "work-leave", "丧假", JSON, null, "notice");
|
||||
assertEquals(1, a.getVersion());
|
||||
assertEquals(1, b.getVersion());
|
||||
assertNotEquals(a.getFlowGuid(), b.getFlowGuid());
|
||||
@@ -90,8 +91,10 @@ class FlowDefAdminServiceSaveTest {
|
||||
|
||||
@Test
|
||||
void baseGuid_在线上开新版本草稿_max加1且保持flowGuid() {
|
||||
FlwDef base = def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode());
|
||||
base.setAppKey("notice");
|
||||
when(defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(SCH, "G"))
|
||||
.thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||
.thenReturn(Optional.of(base));
|
||||
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(6);
|
||||
|
||||
FlwDef saved = service.save(SCH, null, "work-leave", "请假", JSON, "G", null);
|
||||
@@ -104,7 +107,9 @@ class FlowDefAdminServiceSaveTest {
|
||||
|
||||
@Test
|
||||
void 已发布行带id保存_复制升版本() {
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||
FlwDef published = def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode());
|
||||
published.setAppKey("notice");
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(published));
|
||||
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(4);
|
||||
|
||||
FlwDef saved = service.save(SCH, 9L, "work-leave", "请假", JSON, null, null);
|
||||
@@ -118,6 +123,7 @@ class FlowDefAdminServiceSaveTest {
|
||||
@Test
|
||||
void 草稿行带id保存_直改本行() {
|
||||
FlwDef draft = def(9L, "G", "work-leave", 5, FlwEnums.DefStatus.DRAFT.getCode());
|
||||
draft.setAppKey("notice");
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(draft));
|
||||
|
||||
FlwDef saved = service.save(SCH, 9L, "work-leave", "新名字", JSON, null, null);
|
||||
@@ -136,9 +142,15 @@ class FlowDefAdminServiceSaveTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 新建_归属应用直接落库_空白规整为null() {
|
||||
assertEquals("notice", service.save(SCH, null, "work-leave", "请假", JSON, null, "notice").getAppKey());
|
||||
assertNull(service.save(SCH, null, "work-leave", "请假", JSON, null, " ").getAppKey());
|
||||
void 新建_归属应用直接落库_空白规整生效() {
|
||||
assertEquals("notice", service.save(SCH, null, "work-leave", "请假", JSON, null, " notice ").getAppKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void 新建_appKey不传_必填被拦() {
|
||||
BizException e = assertThrows(BizException.class,
|
||||
() -> service.save(SCH, null, "work-leave", "请假", JSON, null, " "));
|
||||
assertTrue(e.getMessage().contains("归属应用必填"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -152,6 +164,18 @@ class FlowDefAdminServiceSaveTest {
|
||||
assertEquals("crm", service.save(SCH, null, "work-leave", "请假", JSON, "G", "crm").getAppKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void 升版本_源线appKey为空且不传_必填被拦() {
|
||||
// 存量 NULL 线(D2 不兜底的 19 条):第一次升版本被强制补填——随编辑自然收敛
|
||||
when(defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(SCH, "G"))
|
||||
.thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(4);
|
||||
|
||||
BizException e = assertThrows(BizException.class,
|
||||
() -> service.save(SCH, null, "work-leave", "请假", JSON, "G", null));
|
||||
assertTrue(e.getMessage().contains("归属应用必填"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void 草稿直改_传appKey覆盖_不传保留() {
|
||||
FlwDef draft = def(9L, "G", "work-leave", 5, FlwEnums.DefStatus.DRAFT.getCode());
|
||||
@@ -162,9 +186,21 @@ class FlowDefAdminServiceSaveTest {
|
||||
assertEquals("crm", service.save(SCH, 9L, "work-leave", "请假", JSON, null, "crm").getAppKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void 草稿直改_源行appKey为空且不传_必填被拦() {
|
||||
FlwDef draft = def(9L, "G", "work-leave", 5, FlwEnums.DefStatus.DRAFT.getCode());
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(draft));
|
||||
|
||||
BizException e = assertThrows(BizException.class,
|
||||
() -> service.save(SCH, 9L, "work-leave", "请假", JSON, null, null));
|
||||
assertTrue(e.getMessage().contains("归属应用必填"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void 并发升版本撞唯一键_转友好提示而非内部服务错误() {
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||
FlwDef published = def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode());
|
||||
published.setAppKey("notice");
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(published));
|
||||
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(4);
|
||||
when(defRepository.save(any())).thenThrow(new DataIntegrityViolationException("dup"));
|
||||
|
||||
@@ -183,12 +219,14 @@ class FlowDefAdminServiceSaveTest {
|
||||
|
||||
@Test
|
||||
void appKey不传_白名单校验不触发() {
|
||||
// 升版本继承源线(cleanAppKey=null)不查白名单——存量线升版本不受切换影响
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||
// 升版本继承源线(cleanAppKey=null)不查白名单——继承值是既成事实,应用停用不卡升版本
|
||||
FlwDef published = def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode());
|
||||
published.setAppKey("notice");
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(published));
|
||||
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(4);
|
||||
when(defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(SCH, "G")).thenReturn(Optional.empty());
|
||||
FlwDef saved = service.save(SCH, 9L, "work-leave", "请假", JSON, null, null);
|
||||
org.mockito.Mockito.verify(appRepository, org.mockito.Mockito.never()).isEnabledApp(any(), any());
|
||||
assertNull(saved.getAppKey());
|
||||
assertEquals("notice", saved.getAppKey());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user