如何追踪Camunda流程执行?从Java API获取执行路径对接bpmn.io展示
Hey there! I’ve tackled similar integration challenges with Camunda embedded in Java apps and bpmn.io for visualization, so let’s break down how to solve this step by step.
1. Fetching Execution Path Data via Camunda Java API
The core challenge here is pulling the right data from Camunda’s runtime and history services to map out completed, active, and failed process elements. Here’s how to approach each piece:
Get Completed Activities & Tasks
Camunda’s HistoryService lets you query finished activities (including user tasks and automated steps like service tasks):
// Fetch completed user tasks List<HistoricTaskInstance> completedTasks = historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) .finished() .list(); // Fetch completed automated activities (service tasks, gateways, etc.) List<HistoricActivityInstance> completedActivities = historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .finished() .excludeTaskInstances() // Exclude tasks we already fetched above .list();
Get Current Active Tasks/Activities
Use RuntimeService to find active execution nodes in the process:
List<Execution> currentExecutions = runtimeService.createExecutionQuery() .processInstanceId(processInstanceId) .active() .list(); // For user tasks specifically, you can also use TaskService List<Task> currentTasks = taskService.createTaskQuery() .processInstanceId(processInstanceId) .active() .list();
Get Failed Tasks/Activities
Identify failed elements using incident and failure status filters:
// Failed user tasks List<HistoricTaskInstance> failedTasks = historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) .failed() .list(); // Automated activities with incidents (e.g., failed service tasks) List<HistoricActivityInstance> failedActivities = historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .withIncidents() .list();
Derive Completed Sequence Flows (Connectors)
Camunda doesn’t directly log sequence flow execution, but you can infer it using the process model and activity order:
- Fetch the BPMN model instance for the process definition
- Sort completed activities by end time to get execution order
- For each consecutive pair of activities, find the sequence flow that connects them in the model
BpmnModelInstance modelInstance = repositoryService.getBpmnModelInstance(processDefinitionId); // Sort completed activities by end time to maintain execution order completedActivities.sort(Comparator.comparing(HistoricActivityInstance::getEndTime)); List<String> completedSequenceFlowIds = new ArrayList<>(); for (int i = 0; i < completedActivities.size() - 1; i++) { HistoricActivityInstance current = completedActivities.get(i); HistoricActivityInstance next = completedActivities.get(i + 1); // Find the outgoing sequence flows from the current activity BaseElement currentElement = modelInstance.getModelElementById(current.getActivityId()); if (currentElement instanceof FlowNode) { Collection<SequenceFlow> outgoingFlows = ((FlowNode) currentElement).getOutgoing(); // Check which flow points to the next activity for (SequenceFlow flow : outgoingFlows) { if (flow.getTargetRef().getId().equals(next.getActivityId())) { completedSequenceFlowIds.add(flow.getId()); break; } } } }
2. Expose Data via a Custom API
Wrap the fetched data into a clean DTO and expose it via a REST endpoint (using Spring Boot as an example):
Define a Response DTO
public class ProcessVisualizationData { private List<String> completedElementIds; private List<String> currentElementIds; private List<String> failedElementIds; private List<String> completedSequenceFlowIds; // Getters and setters }
Create a REST Controller
@RestController @RequestMapping("/api/process") public class ProcessVisualizationController { @Autowired private HistoryService historyService; @Autowired private RuntimeService runtimeService; @Autowired private RepositoryService repositoryService; @GetMapping("/{processInstanceId}/visualization") public ResponseEntity<ProcessVisualizationData> getProcessVisualizationData( @PathVariable String processInstanceId) { ProcessVisualizationData data = new ProcessVisualizationData(); // Populate data using the queries and sequence flow logic from section 1 // ... return ResponseEntity.ok(data); } }
3. Frontend Highlighting with bpmn.io
Once you fetch the data from your API, use bpmn.io’s API to apply color coding to elements:
// Initialize bpmn.io viewer const viewer = new BpmnJS({ container: '#process-canvas' }); // Fetch your BPMN XML and visualization data from the API fetch('/api/process/{processInstanceId}/visualization') .then(res => res.json()) .then(visualizationData => { fetch('/api/process/{processDefinitionId}/bpmn') // Fetch your BPMN XML .then(res => res.text()) .then(bpmnXml => { viewer.importXML(bpmnXml, (err) => { if (!err) { const canvas = viewer.get('canvas'); const elementRegistry = viewer.get('elementRegistry'); // Highlight completed elements and flows visualizationData.completedElementIds.forEach(id => { const element = elementRegistry.get(id); if (element) { canvas.setColor(element, { fill: '#b8e986', stroke: '#86b94b' }); } }); visualizationData.completedSequenceFlowIds.forEach(id => { const flow = elementRegistry.get(id); if (flow) { canvas.setColor(flow, { stroke: '#86b94b', strokeWidth: 2 }); } }); // Highlight current active elements visualizationData.currentElementIds.forEach(id => { const element = elementRegistry.get(id); if (element) { canvas.setColor(element, { fill: '#4a90e2', stroke: '#2563eb' }); canvas.addMarker(element, 'current'); // Add a pulse animation via CSS } }); // Highlight failed elements visualizationData.failedElementIds.forEach(id => { const element = elementRegistry.get(id); if (element) { canvas.setColor(element, { fill: '#f5a623', stroke: '#d39200' }); } }); } }); }); });
Add CSS for extra visual cues:
.bpmn-element.current { animation: pulse 2s infinite; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(37, 99, 235, 0); } 100% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0); } }
Key Notes to Keep in Mind
- Sequence Flow Inference: Since Camunda doesn’t log sequence flows directly, make sure to handle gateways carefully—you may need to check execution variables to determine which path was taken for exclusive/inclusive gateways.
- Element Types: Remember that user tasks and automated activities are stored in different Camunda entities (
HistoricTaskInstancevsHistoricActivityInstance), so your queries need to cover both. - Incident Handling: For failed automated activities, use
withIncidents()to catch elements that threw errors but weren’t marked as "failed" in the task query.
内容的提问来源于stack exchange,提问作者BudJB

