流程追踪图需要把三类信息合在一起:BPMN 模型提供节点和坐标,运行时提供当前活动,历史记录提供已经发生的执行。先把这些数据分清楚,再决定画什么颜色,比直接遍历一张历史表可靠。
不要把最后一条历史记录当成当前节点
按历史活动实例 ID 排序,不等于按执行时间排序。即使按时间排序,最后一条也不一定是唯一当前节点:并行分支可以同时运行;循环可以再次进入已完成过的节点;流程结束后则不再有当前节点。
对仍在运行的实例,优先从运行时读取活动 ID。历史记录用于判断曾发生过什么,不负责单独推导“现在在哪里”。
下面是获取节点高亮数据的局部代码,适用 Activiti 7.1.0.M6 引擎接口,假设三个服务和实例 ID 已准备好:
List<org.activiti.engine.history.HistoricActivityInstance> history =
historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.list();
Set<String> completedIds = new HashSet<>();
for (org.activiti.engine.history.HistoricActivityInstance activity : history) {
if (activity.getEndTime() != null) {
completedIds.add(activity.getActivityId());
}
}
org.activiti.engine.runtime.ProcessInstance running =
runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
Set<String> activeIds = new HashSet<>();
if (running != null) {
activeIds.addAll(runtimeService.getActiveActivityIds(processInstanceId));
}
这里的“completed”表示历史活动已经结束,是否正常完成还要结合结束原因和业务语义;若页面需要区分取消,应增加对应状态。多实例、子流程的展示也应按节点类型选择哪些活动需要画框。
先生成默认高亮图
根据历史流程实例取得定义 ID,再读取同一版本的 BPMN 模型。不能直接取该 key 的最新定义,否则已运行实例可能被画到另一张图上。
org.activiti.engine.history.HistoricProcessInstance instance =
historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
if (instance == null) {
throw new IllegalArgumentException("没有可用的流程历史记录");
}
org.activiti.bpmn.model.BpmnModel model =
repositoryService.getBpmnModel(instance.getProcessDefinitionId());
Set<String> highlightedIds = new HashSet<>(completedIds);
highlightedIds.addAll(activeIds);
org.activiti.image.ProcessDiagramGenerator generator =
new org.activiti.image.impl.DefaultProcessDiagramGenerator();
try (InputStream image = generator.generateDiagram(
model, new ArrayList<>(highlightedIds), Collections.emptyList(),
"SansSerif", "SansSerif", "SansSerif")) {
byte[] buffer = new byte[4096];
int length;
while ((length = image.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
代码需要 Java IO、集合类型以及相应 Activiti 依赖;outputStream 由调用方管理,不在这里关闭。7.1.0.M6 的默认生成器是 SVG 生成实现,HTTP 响应类型应与实际输出一致,不能只把扩展名写成 PNG。模型还需要带图形坐标信息,字体也要在运行环境中可用。生成器接口
红色表示当前,绿色表示已结束
默认生成器只接收高亮 ID 列表,不能仅凭一个列表区分两种颜色。需要定制绘图逻辑,将状态集合传到每次绘制使用的生成器或画布,而不是把状态存在静态共享变量中。
节点颜色的判断可以独立为:
Color highlightColor(String nodeId, Set<String> active, Set<String> completed) {
if (active.contains(nodeId)) {
return Color.RED;
}
if (completed.contains(nodeId)) {
return Color.GREEN;
}
return null; // 无高亮
}
循环中同一节点可能既有完成记录,又正在执行;上述规则让红色优先,页面图例应说明这一点。
定制入口分成两层:在 DefaultProcessDiagramGenerator 的活动绘制逻辑中决定节点状态,在 DefaultProcessDiagramCanvas 中按所选颜色画高亮边框。仅修改一个全局 HIGHLIGHT_COLOR 会把所有高亮改成同一种颜色,无法表达两种状态。扩展时应以所用版本的生成器源码与画布源码为准。
不要给节点 ID 加 # 等后缀来偷偷传状态:那会把真实模型标识和显示协议混在一起。用独立集合或明确的状态对象更容易维护。
连线高亮要有实际路径依据
示例中连线列表故意为空。两条相邻的历史活动,不必然通过同一条 SequenceFlow 连接;并行、网关和重复进入会让简单推断失效。
如果系统记录了可信的连线事件,可以按流程实例汇总实际经过的连线 ID,再交给生成器。只有节点记录、没有路径证据时,显示节点状态比画出一条看似精确却错误的执行路线更合适。