How to Visualize Activiti Workflow Progress in Real-Time with Spring Boot
This guide shows how to extend a Spring Boot application with Activiti to query historic process data, generate highlighted process diagrams, and expose endpoints that display real‑time workflow progress, including code snippets for service methods, configuration, utilities, and a controller for image rendering.
This article demonstrates how to view the current workflow progress in real time by extending a previous tutorial on integrating the Activiti workflow engine with Spring Boot.
Adding methods to HolidayService
<code>/**
* Query historic process instance
* @param instanceId Instance ID
* @return HistoricProcessInstance
*/
public HistoricProcessInstance queryHistory(String instanceId) {
return historyService.createHistoricProcessInstanceQuery()
.processInstanceId(instanceId)
.singleResult();
}
/**
* Get historic activity instance query by instance ID
* @param instanceId Instance ID
* @return HistoricActivityInstanceQuery
*/
public HistoricActivityInstanceQuery getHistoryActivity(String instanceId) {
return historyService.createHistoricActivityInstanceQuery()
.processInstanceId(instanceId);
}
</code>Configure process diagram generator bean
<code>@Configuration
public class ActivitiConfig {
@Bean
@ConditionalOnMissingBean
public ProcessDiagramGenerator processDiagramGenerator() {
return new DefaultProcessDiagramGenerator();
}
}
</code>Utility to obtain highlighted flows
<code>public class ActivitiUtils {
/**
* Get highlighted flows (lines) that have been traversed
*/
public static List<String> getHighLightedFlows(BpmnModel bpmnModel,
ProcessDefinitionEntity processDefinitionEntity,
List<HistoricActivityInstance> historicActivityInstances) {
List<String> highFlows = new ArrayList<>();
if (historicActivityInstances == null || historicActivityInstances.isEmpty()) {
return highFlows;
}
// ... (logic omitted for brevity)
return highFlows;
}
private static FlowNode getNextFlowNode(BpmnModel bpmnModel,
List<HistoricActivityInstance> historicActivityInstances,
int i,
HistoricActivityInstance activityImpl_) {
// ... (logic omitted for brevity)
}
}
</code>Controller for displaying the process diagram
<code>@RestController
@RequestMapping("/view")
public class ProcessViewController {
private static Logger logger = LoggerFactory.getLogger(ProcessViewController.class);
@Resource
private HolidayService holidayService;
@Resource
private RepositoryService repositoryService;
@Resource
private ProcessDiagramGenerator processDiagramGenerator;
@ResponseBody
@GetMapping("/image")
public void showImg(String instanceId, HttpServletResponse response) throws Exception {
response.setContentType("text/html;charset=utf-8");
if (StringUtils.isEmpty(instanceId)) {
response.getWriter().write("error");
return;
}
HistoricProcessInstance processInstance = holidayService.queryHistory(instanceId);
if (processInstance == null) {
logger.error("Process instance ID:{} not found!", instanceId);
response.getWriter().write("error instance not exists");
return;
}
BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
HistoricActivityInstanceQuery historyInstanceQuery = holidayService.getHistoryActivity(instanceId);
List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery
.orderByHistoricActivityInstanceStartTime().asc().list();
if (historicActivityInstanceList == null || historicActivityInstanceList.isEmpty()) {
logger.error("Process instance ID: {}, no historic nodes!", instanceId);
outputImg(response, bpmnModel, null, null);
return;
}
List<String> executedActivityIdList = historicActivityInstanceList.stream()
.map(item -> item.getActivityId())
.collect(Collectors.toList());
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
.getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
List<String> flowIds = ActivitiUtils.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);
outputImg(response, bpmnModel, flowIds, executedActivityIdList);
}
private void outputImg(HttpServletResponse response, BpmnModel bpmnModel,
List<String> flowIds, List<String> executedActivityIdList) {
InputStream imageStream = null;
try {
imageStream = processDiagramGenerator.generateDiagram(bpmnModel,
executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", true, "png");
byte[] buffer = new byte[10 * 1024];
int len;
while ((len = imageStream.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, len);
}
response.getOutputStream().flush();
} catch (Exception e) {
logger.error("Process diagram output error!", e);
} finally {
if (imageStream != null) {
try { imageStream.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
}
</code>Testing steps
Start a new holiday process: /holidays/start?processDefinitionId=holiday:1:...&userId=10000
List tasks for the user to obtain the instance ID: /holidays/tasks?userId=10000
View the current process diagram: /view/image?instanceId=...
Proceed to the next step: /holidays/apply?days=3&mgr=10001&explain=ill&instanceId=...
View the updated diagram again.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.