优化flowable流程和节点状态查询

This commit is contained in:
2025-12-01 12:16:17 +08:00
parent 23a586e706
commit d715184ee4
6 changed files with 247 additions and 139 deletions

View File

@@ -4,12 +4,14 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.sdm.common.common.SdmResponse;
import com.sdm.common.feign.inter.flowable.IFlowableFeignClient;
import com.sdm.flowable.delegate.handler.HpcHandler;
import com.sdm.flowable.dto.NodeStructureInfo;
import com.sdm.flowable.dto.ProcessDefinitionDTO;
import com.sdm.flowable.dto.req.AsyncCallbackRequest;
import com.sdm.flowable.dto.req.CompleteTaskReq;
import com.sdm.flowable.dto.req.RetryRequest;
import com.sdm.common.entity.resp.flowable.ProcessInstanceResp;
import com.sdm.flowable.dto.resp.DeployFlowableResp;
import com.sdm.flowable.dto.resp.ProcessInstanceDetailResponse;
import com.sdm.flowable.process.ProcessService;
import com.sdm.flowable.service.IProcessNodeParamService;
import lombok.extern.slf4j.Slf4j;
@@ -81,7 +83,7 @@ public class ProcessController implements IFlowableFeignClient {
* 获取流程定义节点详细信息直接传流程定义ID
*/
@GetMapping("/listNodesByProcessDefinitionId")
public List<Map<String, Object>> listNodesByProcessDefinitionId(
public SdmResponse<List<NodeStructureInfo>> listNodesByProcessDefinitionId(
@RequestParam String processDefinitionId) {
return processService.getNodesByProcessDefinitionId(processDefinitionId);
@@ -140,8 +142,8 @@ public class ProcessController implements IFlowableFeignClient {
* 根据流程实例 ID 查询流程状态以及节点状态
*/
@GetMapping("/getProcessAndNodeDetailByInstanceId")
public Map<String, Object> getProcessAndNodeDetailByInstanceId(@RequestParam String processInstanceId) {
return processService.getProcessAndNodeDetailByInstanceId(processInstanceId);
public SdmResponse<ProcessInstanceDetailResponse> getProcessAndNodeDetailByInstanceId(@RequestParam String processInstanceId) {
return processService.getProcessAndNodeDetailByInstanceIdV2(processInstanceId);
}

View File

@@ -0,0 +1,18 @@
package com.sdm.flowable.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class NodeDetailInfo extends NodeStructureInfo{
private String status; // "active" | "finished" | "pending"
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime;
private Long durationInMillis;
private String durationFormatted;
}

View File

@@ -0,0 +1,14 @@
package com.sdm.flowable.dto;
import lombok.Data;
import java.util.List;
@Data
public class NodeStructureInfo {
protected String id;
protected String name;
protected String type; // 如 "UserTask", "ExclusiveGateway"
protected List<String> nextNodeIds; // 后续节点 ID 列表
protected String executeConfig; // 扩展属性内容
}

View File

@@ -0,0 +1,20 @@
package com.sdm.flowable.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class ProcessInstanceInfo {
private String processInstanceId;
private String processDefinitionId;
private String businessKey;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime;
private Long durationInMillis;
private String durationFormatted;
private String status; // "running" 或 "completed"
}

View File

@@ -0,0 +1,13 @@
package com.sdm.flowable.dto.resp;
import com.sdm.flowable.dto.NodeDetailInfo;
import com.sdm.flowable.dto.ProcessInstanceInfo;
import lombok.Data;
import java.util.List;
@Data
public class ProcessInstanceDetailResponse {
private ProcessInstanceInfo processInfo;
private List<NodeDetailInfo> nodes;
}

View File

@@ -3,9 +3,13 @@ package com.sdm.flowable.process;
import com.sdm.common.common.SdmResponse;
import com.sdm.flowable.constants.FlowableConfig;
import com.sdm.flowable.delegate.UniversalDelegate;
import com.sdm.flowable.dto.NodeDetailInfo;
import com.sdm.flowable.dto.NodeStructureInfo;
import com.sdm.flowable.dto.ProcessDefinitionDTO;
import com.sdm.flowable.dto.ProcessInstanceInfo;
import com.sdm.flowable.dto.req.AsyncCallbackRequest;
import com.sdm.flowable.dto.resp.DeployFlowableResp;
import com.sdm.flowable.dto.resp.ProcessInstanceDetailResponse;
import com.sdm.flowable.enums.FlowElementTypeEnums;
import com.sdm.flowable.util.Dto2BpmnConverter;
import com.sdm.flowable.dto.req.CompleteTaskReq;
@@ -31,6 +35,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -102,64 +107,34 @@ public class ProcessService {
/**
* 根据流程定义ID获取节点信息
*/
public List<Map<String, Object>> getNodesByProcessDefinitionId(String processDefinitionId) {
return getFlowStructureWithExtensions(processDefinitionId);
public SdmResponse<List<NodeStructureInfo>> getNodesByProcessDefinitionId(String processDefinitionId) {
List<FlowNode> orderedNodes = getOrderedFlowNodes(processDefinitionId);
return SdmResponse.success(orderedNodes.stream()
.map(this::buildNodeStructureInfo)
.collect(Collectors.toList()));
}
private NodeStructureInfo buildNodeStructureInfo(FlowNode node) {
NodeStructureInfo info = new NodeStructureInfo();
info.setId(node.getId());
info.setName(node.getName() != null ? node.getName() : "");
info.setType(node.getClass().getSimpleName());
/**
* 带 extensionElements 的流程结构解析
*
* @param processDefinitionId
* @return
*/
private List<Map<String, Object>> getFlowStructureWithExtensions(String processDefinitionId) {
// 后续节点
info.setNextNodeIds(
node.getOutgoingFlows().stream()
.map(SequenceFlow::getTargetRef)
.collect(Collectors.toList())
);
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
if (bpmnModel == null) return Collections.emptyList();
Process process = bpmnModel.getMainProcess();
// 找开始事件
StartEvent startEvent = process.findFlowElementsOfType(StartEvent.class, false)
.stream().findFirst().orElse(null);
if (startEvent == null) return Collections.emptyList();
List<Map<String, Object>> result = new ArrayList<>();
Queue<FlowNode> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
queue.add(startEvent);
while (!queue.isEmpty()) {
FlowNode node = queue.poll();
if (!visited.add(node.getId())) continue;
Map<String, Object> info = new LinkedHashMap<>();
info.put("id", node.getId());
info.put("name", node.getName());
info.put("type", node.getClass().getSimpleName());
// 后继节点
List<String> nextNodeIds = node.getOutgoingFlows()
.stream().map(SequenceFlow::getTargetRef).collect(Collectors.toList());
info.put("next", nextNodeIds);
// ⭐ 添加扩展属性 extensionElements ⭐
info.put("extensionElements", parseExtensionElements(node.getExtensionElements()));
result.add(info);
// BFS 的推进逻辑
for (String nextId : nextNodeIds) {
FlowElement nextElement = process.getFlowElement(nextId);
if (nextElement instanceof FlowNode nextNode) {
queue.add(nextNode);
}
// 扩展属性
if (node.getExtensionElements() != null) {
List<ExtensionElement> extList = node.getExtensionElements().get(FlowableConfig.EXECUTECONFIG);
if (extList != null && !extList.isEmpty()) {
info.setExecuteConfig(extList.get(0).getElementText());
}
}
return result;
return info;
}
// 将 extensionElements 转成可阅读的 map
@@ -218,115 +193,181 @@ public class ProcessService {
}
/**
* 查询流程所有节点状态
*
* @param processInstanceId
* @return
* 查询流程实例及所有节点的详细状态(返回结构化 DTO
*/
public Map<String, Object> getProcessAndNodeDetailByInstanceId(String processInstanceId) {
return buildProcessNodeDetail(processInstanceId);
public SdmResponse<ProcessInstanceDetailResponse> getProcessAndNodeDetailByInstanceIdV2(String processInstanceId) {
ProcessInstanceInfo processInfo = buildProcessInstanceInfo(processInstanceId);
List<NodeDetailInfo> nodes = buildNodeDetails(processInstanceId, processInfo.getProcessDefinitionId());
ProcessInstanceDetailResponse response = new ProcessInstanceDetailResponse();
response.setProcessInfo(processInfo);
response.setNodes(nodes);
return SdmResponse.success(response);
}
/**
* 根据流程实例ID构建流程节点状态支持 UserTask 任务信息和扩展属性)
*/
private Map<String, Object> buildProcessNodeDetail(String processInstanceId) {
// 构建流程实例信息
private ProcessInstanceInfo buildProcessInstanceInfo(String processInstanceId) {
HistoricProcessInstance historicInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
Map<String, Object> result = new HashMap<>();
// 1. 查询流程实例历史信息
HistoricProcessInstance processInstance =
historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
if (processInstance == null) {
if (historicInstance == null) {
throw new RuntimeException("流程实例不存在: " + processInstanceId);
}
String processDefinitionId = processInstance.getProcessDefinitionId();
// 2. 获取 BPMN 模型
BpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
Process process = model.getMainProcess();
Collection<FlowElement> flowElements = process.getFlowElements();
// 3. 查询正在执行的节点
// 判断流程是否正在运行中
boolean isRunning = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult() != null;
.count() > 0;
List<String> activeIds;
Map<String, org.flowable.task.api.Task> activeTaskMap = new HashMap<>();
if (isRunning) {
activeIds = runtimeService.getActiveActivityIds(processInstanceId);
ProcessInstanceInfo info = new ProcessInstanceInfo();
info.setProcessInstanceId(historicInstance.getId());
info.setProcessDefinitionId(historicInstance.getProcessDefinitionId());
info.setBusinessKey(historicInstance.getBusinessKey());
info.setStartTime(historicInstance.getStartTime());
info.setEndTime(historicInstance.getEndTime()); // 可能为 null
// 查询当前活跃任务UserTask
List<org.flowable.task.api.Task> tasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.list();
activeTaskMap = tasks.stream().collect(Collectors.toMap(org.flowable.task.api.Task::getTaskDefinitionKey, task -> task));
} else {
activeIds = Collections.emptyList();
Long duration = historicInstance.getDurationInMillis();
if (duration == null && historicInstance.getStartTime() != null && isRunning) {
duration = System.currentTimeMillis() - historicInstance.getStartTime().getTime();
}
info.setDurationInMillis(duration);
info.setDurationFormatted(duration != null ? formatDuration(duration) : null);
info.setStatus(isRunning ? "running" : "completed");
// 4. 查询已完成节点
List<HistoricActivityInstance> finished =
historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.finished()
.list();
return info;
}
Set<String> finishedIds = finished.stream()
.map(HistoricActivityInstance::getActivityId)
.collect(Collectors.toSet());
//构建节点列表
private List<NodeDetailInfo> buildNodeDetails(String processInstanceId, String processDefinitionId) {
List<FlowNode> orderedNodes = getOrderedFlowNodes(processDefinitionId);
// 5. 组装节点状态
List<Map<String, Object>> nodeList = new ArrayList<>();
// 查询运行时 & 历史数据
List<HistoricActivityInstance> historicActivities = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.list();
for (FlowElement element : flowElements) {
if (!(element instanceof FlowNode)) continue;
Map<String, HistoricActivityInstance> activityMap = historicActivities.stream()
.collect(Collectors.toMap(
HistoricActivityInstance::getActivityId,
Function.identity(),
(a, b) -> a
));
FlowNode node = (FlowNode) element;
List<String> activeActivityIds = isProcessRunning(processInstanceId)
? runtimeService.getActiveActivityIds(processInstanceId)
: Collections.emptyList();
Map<String, Object> nodeInfo = new HashMap<>();
nodeInfo.put("id", node.getId());
nodeInfo.put("name", node.getName());
nodeInfo.put("type", node.getClass().getSimpleName());
// 按有序节点构建详情
return orderedNodes.stream()
.map(node -> buildNodeDetailInfo(node, activityMap, activeActivityIds))
.collect(Collectors.toList());
}
// 节点状态
if (activeIds.contains(node.getId())) {
nodeInfo.put("status", "active");
// --- 判断流程是否运行中 ---
private boolean isProcessRunning(String processInstanceId) {
return runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.count() > 0;
}
// 如果是 UserTask返回任务信息
if (node instanceof UserTask && activeTaskMap.containsKey(node.getId())) {
org.flowable.task.api.Task task = activeTaskMap.get(node.getId());
nodeInfo.put("taskId", task.getId());
nodeInfo.put("taskName", task.getName());
nodeInfo.put("assignee", task.getAssignee()); // 可能为 null
}
} else if (finishedIds.contains(node.getId())) {
nodeInfo.put("status", "finished");
} else {
nodeInfo.put("status", "pending");
}
// --- 构建单个节点详情(返回 NodeDetailInfo---
private NodeDetailInfo buildNodeDetailInfo(
FlowNode node,
Map<String, HistoricActivityInstance> activityMap,
List<String> activeActivityIds) {
// 读取扩展属性executeConfig
if (node.getExtensionElements() != null && node.getExtensionElements().containsKey(FlowableConfig.EXECUTECONFIG)) {
List<ExtensionElement> elements = node.getExtensionElements().get(FlowableConfig.EXECUTECONFIG);
if (elements != null && !elements.isEmpty()) {
nodeInfo.put("executeConfig", elements.get(0).getElementText());
// 先构建静态结构
NodeStructureInfo base = buildNodeStructureInfo(node);
// 再填充动态信息
NodeDetailInfo detail = new NodeDetailInfo();
detail.setId(base.getId());
detail.setName(base.getName());
detail.setType(base.getType());
detail.setNextNodeIds(base.getNextNodeIds());
detail.setExecuteConfig(base.getExecuteConfig());
// 动态状态逻辑
String nodeId = node.getId();
HistoricActivityInstance historicActivity = activityMap.get(nodeId);
boolean isActive = activeActivityIds.contains(nodeId);
boolean isFinished = historicActivity != null && historicActivity.getEndTime() != null;
detail.setStatus(isFinished ? "finished" : (isActive ? "active" : "pending"));
if (historicActivity != null) {
detail.setStartTime(historicActivity.getStartTime());
if (isFinished) {
detail.setEndTime(historicActivity.getEndTime());
detail.setDurationInMillis(historicActivity.getDurationInMillis());
if (historicActivity.getDurationInMillis() != null) {
detail.setDurationFormatted(formatDuration(historicActivity.getDurationInMillis()));
}
}
nodeList.add(nodeInfo);
}
result.put("processInstanceId", processInstanceId);
result.put("nodes", nodeList);
return result;
return detail;
}
// --- 工具方法:格式化耗时(毫秒 → 可读字符串)---
private String formatDuration(long millis) {
long seconds = millis / 1000;
long mins = seconds / 60;
long hours = mins / 60;
long days = hours / 24;
if (days > 0) return String.format("%dd %dh %dm %ds", days, hours % 24, mins % 60, seconds % 60);
if (hours > 0) return String.format("%dh %dm %ds", hours, mins % 60, seconds % 60);
if (mins > 0) return String.format("%dm %ds", mins, seconds % 60);
return String.format("%ds", seconds);
}
/**
* 根据流程定义 ID按流程执行顺序BFS返回所有可达的 FlowNode
*/
private List<FlowNode> getOrderedFlowNodes(String processDefinitionId) {
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
if (bpmnModel == null || bpmnModel.getMainProcess() == null) {
return Collections.emptyList();
}
Process process = bpmnModel.getMainProcess();
// 找开始事件
StartEvent startEvent = process.findFlowElementsOfType(StartEvent.class, false)
.stream()
.findFirst()
.orElse(null);
if (startEvent == null) {
return Collections.emptyList();
}
// BFS 遍历
List<FlowNode> orderedNodes = new ArrayList<>();
Queue<FlowNode> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
queue.offer(startEvent);
visited.add(startEvent.getId());
while (!queue.isEmpty()) {
FlowNode current = queue.poll();
orderedNodes.add(current);
// 对 outgoingFlows 排序可选(保证并行分支顺序稳定)
current.getOutgoingFlows().stream()
.sorted(Comparator.comparing(SequenceFlow::getId)) // 可选:提升顺序稳定性
.forEach(flow -> {
String targetRef = flow.getTargetRef();
FlowElement element = process.getFlowElement(targetRef);
if (element instanceof FlowNode && visited.add(targetRef)) {
queue.offer((FlowNode) element);
}
});
}
return orderedNodes;
}
public void continueServiceTask(@RequestBody CompleteTaskReq req) {