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
/**
* 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);
}Configure process diagram generator bean
@Configuration
public class ActivitiConfig {
@Bean
@ConditionalOnMissingBean
public ProcessDiagramGenerator processDiagramGenerator() {
return new DefaultProcessDiagramGenerator();
}
}Utility to obtain highlighted flows
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)
}
}Controller for displaying the process diagram
@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(); }
}
}
}
}Testing steps
Start a new holiday process:
/holidays/start?processDefinitionId=holiday:1:...&userId=10000List 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
