feat(engine): NOTIFY 抄送 + CONDITION 条件路由 + JOIN scope 修复(两批全栈)
build / build-push (push) Successful in 47s

- NOTIFY:TYPE_NOTIFY 瞬时抄送(flw_cc 逐人留痕 + CC_SENT 事件 + 站内通知不加待办角标)、/api/flow/cc/my、轨迹 kind=CC 与 oapi progress 抄送步、示意图青框卡片
- CONDITION:flw_router 判断服务注册 + flw_route_log 决策留痕(REQUIRES_NEW,reroute 回滚不丢失败行)+ 同步判断调用(服务目录寻址 + 平台签名,3s×3 退避重试)+ 失败挂起/管理端重跑(不配默认出口)+ 可选事件 flow.route.decided
- JOIN scope:branch_key 路径前缀编码 + scope 级到齐放行 + DefValidator SPLIT/JOIN 配对与键长守卫(存量无点号键零迁移,历史直连图回退实例级口径)
- 管理端:/api/flow/admin/router/* CRUD + POST /api/flow/admin/instance/{id}/reroute
- 生产 SQL:sql/20260923_create_flw_cc.sql(含 flw_task 时间列升 DATETIME(3))、20260924_create_flw_router_and_route_log.sql
- 测试:全量 125 绿(FlowEngineIT 24,含条件/嵌套并行/退回重走用例)
This commit is contained in:
chenxing
2026-09-24 08:58:26 +08:00
parent f2126cdf32
commit d2bef20e13
34 changed files with 2472 additions and 97 deletions
@@ -4,11 +4,16 @@ import com.yunhai.flowcenter.entity.FlwAssigneeMap;
import com.yunhai.flowcenter.entity.FlwCallbackLog;
import com.yunhai.flowcenter.entity.FlwCallbackReg;
import com.yunhai.flowcenter.entity.FlwDef;
import com.yunhai.flowcenter.entity.FlwRouteLog;
import com.yunhai.flowcenter.entity.FlwRouter;
import com.yunhai.flowcenter.repository.FlwAssigneeMapRepository;
import com.yunhai.flowcenter.repository.FlwCallbackLogRepository;
import com.yunhai.flowcenter.repository.FlwCallbackRegRepository;
import com.yunhai.flowcenter.repository.FlwCcRepository;
import com.yunhai.flowcenter.repository.FlwDefRepository;
import com.yunhai.flowcenter.repository.FlwInstanceRepository;
import com.yunhai.flowcenter.repository.FlwRouteLogRepository;
import com.yunhai.flowcenter.repository.FlwRouterRepository;
import com.yunhai.flowcenter.repository.FlwTaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,10 +39,19 @@ public abstract class FlowTestSupport {
protected FlwCallbackRegRepository regRepository;
@Autowired
protected FlwCallbackLogRepository logRepository;
@Autowired
protected FlwCcRepository ccRepository;
@Autowired
protected FlwRouterRepository routerRepository;
@Autowired
protected FlwRouteLogRepository routeLogRepository;
protected void cleanDb() {
logRepository.deleteAll();
regRepository.deleteAll();
ccRepository.deleteAll();
routeLogRepository.deleteAll();
routerRepository.deleteAll();
taskRepository.deleteAll();
instanceRepository.deleteAll();
defRepository.deleteAll();
@@ -100,6 +114,95 @@ public abstract class FlowTestSupport {
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/** 抄送主线流程:START → cc(USERS [301,302]) → n1(deptA) → END */
protected String notifyMainlineDef(String deptA) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"抄送主线测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"cc1\",\"type\":\"NOTIFY\",\"name\":\"抄送校领导\",\"receivers\":{\"type\":\"USERS\",\"userIds\":[301,302]}},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptA + "\"}},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"cc1\"},{\"from\":\"cc1\",\"to\":\"n1\"},"
+ "{\"from\":\"n1\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/** 抄送并行流程:START → split → {n1(deptA), cc(USERS [301])} → join → END(抄送支瞬时到达汇聚) */
protected String notifyParallelDef(String deptA) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"抄送并行测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"split1\",\"type\":\"PARALLEL_SPLIT\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptA + "\"}},"
+ "{\"id\":\"cc1\",\"type\":\"NOTIFY\",\"name\":\"抄送\",\"receivers\":{\"type\":\"USERS\",\"userIds\":[301]}},"
+ "{\"id\":\"join1\",\"type\":\"PARALLEL_JOIN\",\"waitFor\":\"ALL\"},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"split1\"},"
+ "{\"from\":\"split1\",\"to\":\"n1\"},{\"from\":\"split1\",\"to\":\"cc1\"},"
+ "{\"from\":\"n1\",\"to\":\"join1\"},{\"from\":\"cc1\",\"to\":\"join1\"},"
+ "{\"from\":\"join1\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/**
* 条件分支流程(2026-09-24 CONDITION):START → cond1(routerKey) --"甲线"--> n1(deptA) → END
* └-"乙线"--> n2(deptB) → END
*/
protected String conditionDef(String routerKey, String deptA, String deptB) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"条件分支测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"cond1\",\"type\":\"CONDITION\",\"name\":\"条件判断\",\"routerKey\":\"" + routerKey + "\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"甲线审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptA + "\"}},"
+ "{\"id\":\"n2\",\"type\":\"APPROVAL\",\"name\":\"乙线审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptB + "\"}},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"cond1\"},"
+ "{\"from\":\"cond1\",\"to\":\"n1\",\"name\":\"甲线\"},"
+ "{\"from\":\"cond1\",\"to\":\"n2\",\"name\":\"乙线\"},"
+ "{\"from\":\"n1\",\"to\":\"end\"},{\"from\":\"n2\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/**
* 主线条件分支(退回重走重判用):START → n0(deptMain) → cond1(routerKey)
* --"甲线"--> n1(deptA) → END;--"乙线"--> END(乙线直达终点)
*/
protected String mainlineConditionDef(String routerKey, String deptMain, String deptA) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"主线条件测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"n0\",\"type\":\"APPROVAL\",\"name\":\"入口\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptMain + "\"}},"
+ "{\"id\":\"cond1\",\"type\":\"CONDITION\",\"name\":\"条件判断\",\"routerKey\":\"" + routerKey + "\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"甲线审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptA + "\"}},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n0\"},"
+ "{\"from\":\"n0\",\"to\":\"cond1\"},"
+ "{\"from\":\"cond1\",\"to\":\"n1\",\"name\":\"甲线\"},"
+ "{\"from\":\"cond1\",\"to\":\"end\",\"name\":\"乙线\"},"
+ "{\"from\":\"n1\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/** 注册判断服务(测试库直接落行;serviceKey/path 随意——RouteJudgeClient 已被测试桩替换) */
protected FlwRouter registerRouter(String routerKey) {
FlwRouter router = new FlwRouter();
router.setRouterKey(routerKey);
router.setName("测试判断");
router.setServiceKey("notice");
router.setPath("/oapi/notice/flow/route/test-judge");
router.setBasePath("");
router.setStatus(1);
return routerRepository.save(router);
}
private static String codeOf() {
return "tc_" + UUID.randomUUID().toString().replace("-", "").substring(0, 8);
}
@@ -134,6 +237,62 @@ public abstract class FlowTestSupport {
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/**
* 嵌套并行(2026-09-24 §4.6 scope 修复的主用例):
* START → n1(deptMain) → s1 → {ta(deptA), tb(deptB)};tb → s2 → {tc(deptC), td(deptD)} → join2 → join1;ta → join1 → END。
* 修复前 join2 的放行计数是实例级(外层 tb 行仍在进行)→ 内层到齐也永不到齐,死锁。
*/
protected String nestedParallelDef(String deptMain, String deptA, String deptB,
String deptC, String deptD) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"嵌套并行测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"入口\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptMain + "\"}},"
+ "{\"id\":\"s1\",\"type\":\"PARALLEL_SPLIT\"},"
+ "{\"id\":\"ta\",\"type\":\"APPROVAL\",\"name\":\"甲科审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptA + "\"}},"
+ "{\"id\":\"tb\",\"type\":\"APPROVAL\",\"name\":\"乙科审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptB + "\"}},"
+ "{\"id\":\"s2\",\"type\":\"PARALLEL_SPLIT\"},"
+ "{\"id\":\"tc\",\"type\":\"APPROVAL\",\"name\":\"丙科审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptC + "\"}},"
+ "{\"id\":\"td\",\"type\":\"APPROVAL\",\"name\":\"丁科审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptD + "\"}},"
+ "{\"id\":\"join2\",\"type\":\"PARALLEL_JOIN\",\"waitFor\":\"ALL\"},"
+ "{\"id\":\"join1\",\"type\":\"PARALLEL_JOIN\",\"waitFor\":\"ALL\"},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"s1\"},"
+ "{\"from\":\"s1\",\"to\":\"ta\"},{\"from\":\"s1\",\"to\":\"tb\"},"
+ "{\"from\":\"ta\",\"to\":\"join1\"},{\"from\":\"tb\",\"to\":\"s2\"},"
+ "{\"from\":\"s2\",\"to\":\"tc\"},{\"from\":\"s2\",\"to\":\"td\"},"
+ "{\"from\":\"tc\",\"to\":\"join2\"},{\"from\":\"td\",\"to\":\"join2\"},"
+ "{\"from\":\"join2\",\"to\":\"join1\"},{\"from\":\"join1\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/**
* 出口分支内并行段(2026-09-24:CONDITION 分支内并行的前置形态——多出口审批的某出口里套并行段):
* START → n1(deptMain,出口"甲线"→END / "乙线"→s2) → s2 → {p1(deptA), p2(deptB)} → join2 → END
*/
protected String outletParallelDef(String deptMain, String deptA, String deptB) {
return "{"
+ "\"code\":\"" + codeOf() + "\",\"name\":\"出口并行测试流程\",\"start\":\"start\","
+ "\"nodes\":["
+ "{\"id\":\"start\",\"type\":\"START\",\"name\":\"开始\"},"
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"分流审核\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptMain + "\"}},"
+ "{\"id\":\"s2\",\"type\":\"PARALLEL_SPLIT\"},"
+ "{\"id\":\"p1\",\"type\":\"APPROVAL\",\"name\":\"并行甲\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptA + "\"}},"
+ "{\"id\":\"p2\",\"type\":\"APPROVAL\",\"name\":\"并行乙\",\"assignee\":{\"type\":\"DEPT_KEY\",\"deptKey\":\"" + deptB + "\"}},"
+ "{\"id\":\"join2\",\"type\":\"PARALLEL_JOIN\",\"waitFor\":\"ALL\"},"
+ "{\"id\":\"end\",\"type\":\"END\"}"
+ "],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"n1\"},"
+ "{\"from\":\"n1\",\"to\":\"end\",\"name\":\"甲线\"},"
+ "{\"from\":\"n1\",\"to\":\"s2\",\"name\":\"乙线\"},"
+ "{\"from\":\"s2\",\"to\":\"p1\"},{\"from\":\"s2\",\"to\":\"p2\"},"
+ "{\"from\":\"p1\",\"to\":\"join2\"},{\"from\":\"p2\",\"to\":\"join2\"},"
+ "{\"from\":\"join2\",\"to\":\"end\"}],"
+ "\"settings\":{\"rejectPolicy\":\"END_REJECTED\"}}";
}
/** 主线带并行段:START → n1(deptA) → split → {ta(ka), tb(kb)} → join → n3(deptA) → END(退回重入用) */
protected String mainlineParallelTailDef(String deptA, String deptMain) {
return "{"