feat(engine): NOTIFY 抄送 + CONDITION 条件路由 + JOIN scope 修复(两批全栈)
build / build-push (push) Successful in 47s
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:
@@ -3,6 +3,7 @@ package com.yunhai.flowcenter.service;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.yunhai.flowcenter.callback.CallbackAppSigner;
|
||||
import com.yunhai.flowcenter.entity.FlwCallbackReg;
|
||||
import com.yunhai.flowcenter.entity.FlwCc;
|
||||
import com.yunhai.flowcenter.entity.FlwDef;
|
||||
import com.yunhai.flowcenter.entity.FlwTask;
|
||||
import com.yunhai.flowcenter.entity.FlwInstance;
|
||||
@@ -10,6 +11,7 @@ import com.yunhai.flowcenter.engine.FlowEngine;
|
||||
import com.yunhai.flowcenter.enums.FlwEnums;
|
||||
import com.yunhai.flowcenter.model.OapiModels;
|
||||
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.FlwTaskRepository;
|
||||
@@ -62,6 +64,7 @@ public class OapiFlowService {
|
||||
private final FlwDefRepository defRepository;
|
||||
private final FlwInstanceRepository instanceRepository;
|
||||
private final FlwTaskRepository taskRepository;
|
||||
private final FlwCcRepository ccRepository;
|
||||
private final FlwCallbackRegRepository regRepository;
|
||||
private final FlowInstanceService instanceService;
|
||||
private final FlowTaskService taskService;
|
||||
@@ -352,11 +355,17 @@ public class OapiFlowService {
|
||||
boolean hasSubmitRow = rows.stream()
|
||||
.anyMatch(t -> FlwEnums.TaskStatus.NODE_SUBMIT.equals(t.getNodeId()));
|
||||
List<OapiModels.ProgressStepVo> steps = new ArrayList<>();
|
||||
// 步骤发生时间(排序键):任务步=办理人行最晚时间(handleTime?:arriveTime),submit/事件步=动作时间,
|
||||
// 抄送步=最晚 sentTime——抄送步与任务步按真实时间交织,人读时间线不出现"抄送悬在最后"。
|
||||
Map<OapiModels.ProgressStepVo, java.time.LocalDateTime> stepTimes =
|
||||
new java.util.IdentityHashMap<>();
|
||||
// 第 0 步"发起申请"(对外时间线图3/图4口径):2026-09-18 起发起会留痕成真实任务行,
|
||||
// 有真实行时由下面的循环出这一步(可见内容与这里的合成步一致);老办件没有真实行,
|
||||
// 仍按办件快照合成,存量数据对外表现不变。
|
||||
if (!hasSubmitRow) {
|
||||
steps.add(submitStep(instance));
|
||||
OapiModels.ProgressStepVo submit = submitStep(instance);
|
||||
steps.add(submit);
|
||||
stepTimes.put(submit, instance.getSubmitTime());
|
||||
}
|
||||
// 轨迹留痕事件行(6 发起/重新提交、7 撤回)单独成步;步骤 label 直接用事件语义,
|
||||
// 不参与下面的"任一拒绝>任一同意>全失效"汇总(否则会被判成"失效")。
|
||||
@@ -370,6 +379,7 @@ public class OapiFlowService {
|
||||
OapiModels.ProgressStepVo es = eventStep(t);
|
||||
eventSteps.add(es);
|
||||
steps.add(es);
|
||||
stepTimes.put(es, t.getHandleTime() != null ? t.getHandleTime() : t.getArriveTime());
|
||||
continue;
|
||||
}
|
||||
OapiModels.ProgressStepVo step = stepsByNode.computeIfAbsent(t.getNodeId(), k -> {
|
||||
@@ -389,11 +399,77 @@ public class OapiFlowService {
|
||||
}
|
||||
s.setActive(activeNodes.contains(s.getNodeId()));
|
||||
s.setLabel(s.isActive() ? "待审核" : stepLabel(s.getAssignees()));
|
||||
java.time.LocalDateTime latest = null;
|
||||
for (OapiModels.ProgressTaskVo r : s.getAssignees()) {
|
||||
java.time.LocalDateTime t = r.getHandleTime() != null
|
||||
? java.time.LocalDateTime.parse(r.getHandleTime())
|
||||
: (r.getArriveTime() == null ? null : java.time.LocalDateTime.parse(r.getArriveTime()));
|
||||
if (t != null && (latest == null || t.isAfter(latest))) {
|
||||
latest = t;
|
||||
}
|
||||
}
|
||||
stepTimes.put(s, latest);
|
||||
}
|
||||
// 抄送步(有才交织排序):无抄送时保持既有拼装顺序原样输出(存量办件出参零变化);
|
||||
// 有抄送时按 (发生时间, 秩) 排——既有步=0、抄送步=1(同秒 DATETIME 分不出先后,任务步
|
||||
// 恒在前;不给既有步分秩是因为驳回/重提步同秒靠 id 定序即真实先后,秩会颠倒)。
|
||||
List<OapiModels.ProgressStepVo> ccStepList = ccSteps(instance.getId(), stepTimes);
|
||||
if (!ccStepList.isEmpty()) {
|
||||
Map<OapiModels.ProgressStepVo, Integer> ranks = new java.util.IdentityHashMap<>();
|
||||
for (OapiModels.ProgressStepVo s : steps) {
|
||||
ranks.put(s, 0);
|
||||
}
|
||||
for (OapiModels.ProgressStepVo s : ccStepList) {
|
||||
ranks.put(s, 1);
|
||||
}
|
||||
steps.addAll(ccStepList);
|
||||
steps.sort(java.util.Comparator
|
||||
.comparing((OapiModels.ProgressStepVo s) -> stepTimes.get(s),
|
||||
java.util.Comparator.nullsLast(java.util.Comparator.naturalOrder()))
|
||||
.thenComparing(s -> ranks.get(s)));
|
||||
}
|
||||
vo.setSteps(steps);
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 抄送步(2026-09-23 NOTIFY 节点):按 nodeId 聚合(多人并排),label="抄送"、
|
||||
* 参与行 status=1/已抄送(复用"已办过"取值,消费方无需适配新状态);时间取 sentTime,
|
||||
* 与任务步按真实发生时间交织排序。无抄送返回空列表(存量办件出参零变化)。
|
||||
*/
|
||||
private List<OapiModels.ProgressStepVo> ccSteps(Long instanceId,
|
||||
Map<OapiModels.ProgressStepVo, java.time.LocalDateTime> stepTimes) {
|
||||
List<FlwCc> ccs = ccRepository.findByInstanceIdOrderByIdAsc(instanceId);
|
||||
if (ccs.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Map<String, OapiModels.ProgressStepVo> byNode = new java.util.LinkedHashMap<>();
|
||||
for (FlwCc c : ccs) {
|
||||
OapiModels.ProgressStepVo step = byNode.computeIfAbsent(c.getNodeId(), k -> {
|
||||
OapiModels.ProgressStepVo s = new OapiModels.ProgressStepVo();
|
||||
s.setNodeId(c.getNodeId());
|
||||
s.setNodeName(c.getNodeName() == null || c.getNodeName().isEmpty() ? "抄送" : c.getNodeName());
|
||||
s.setActive(false);
|
||||
s.setLabel("抄送");
|
||||
s.setAssignees(new ArrayList<>());
|
||||
stepTimes.put(s, c.getSentTime());
|
||||
return s;
|
||||
});
|
||||
OapiModels.ProgressTaskVo row = new OapiModels.ProgressTaskVo();
|
||||
row.setUserId(c.getUserId());
|
||||
row.setUserName(c.getUserName());
|
||||
row.setStatus(1);
|
||||
row.setStatusLabel("已抄送");
|
||||
row.setArriveTime(c.getSentTime() == null ? null : c.getSentTime().toString());
|
||||
step.getAssignees().add(row);
|
||||
java.time.LocalDateTime t = stepTimes.get(step);
|
||||
if (c.getSentTime() != null && (t == null || c.getSentTime().isAfter(t))) {
|
||||
stepTimes.put(step, c.getSentTime());
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(byNode.values());
|
||||
}
|
||||
|
||||
/** 合成"发起申请"步(老办件:无真实留痕行,取办件快照的提交人/提交时间) */
|
||||
private OapiModels.ProgressStepVo submitStep(FlwInstance instance) {
|
||||
OapiModels.ProgressStepVo submit = new OapiModels.ProgressStepVo();
|
||||
@@ -436,7 +512,7 @@ public class OapiFlowService {
|
||||
return step;
|
||||
}
|
||||
|
||||
/** 任务行 → 对外步骤里的办理人行 */
|
||||
/** 任务行 → 对外步骤里的办理人行(展示时间截断到秒:列已升毫秒精度,对外口径不变) */
|
||||
private OapiModels.ProgressTaskVo taskVo(FlwTask t) {
|
||||
OapiModels.ProgressTaskVo row = new OapiModels.ProgressTaskVo();
|
||||
row.setTaskId(t.getId());
|
||||
@@ -445,8 +521,10 @@ public class OapiFlowService {
|
||||
row.setStatus(t.getStatus());
|
||||
row.setStatusLabel(taskLabel(t.getStatus()));
|
||||
row.setOpinion(t.getOpinion());
|
||||
row.setArriveTime(t.getArriveTime() == null ? null : t.getArriveTime().toString());
|
||||
row.setHandleTime(t.getHandleTime() == null ? null : t.getHandleTime().toString());
|
||||
row.setArriveTime(t.getArriveTime() == null ? null
|
||||
: t.getArriveTime().truncatedTo(java.time.temporal.ChronoUnit.SECONDS).toString());
|
||||
row.setHandleTime(t.getHandleTime() == null ? null
|
||||
: t.getHandleTime().truncatedTo(java.time.temporal.ChronoUnit.SECONDS).toString());
|
||||
row.setEdgeName(t.getEdgeName());
|
||||
return row;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user