Flowable实战:自定义流程图生成器与高亮渲染进阶

1. 为什么需要自定义流程图生成器

在业务系统开发中,流程图的可视化展示往往需要满足特定的业务需求。比如在审批流程监控场景中,我们可能需要用不同颜色区分"已审批"、"当前处理中"和"被驳回"的节点;在运维流程中,可能需要用特殊图标标记存在风险的环节。Flowable自带的流程图生成器虽然功能完善,但在这些定制化需求面前就显得力不从心了。

我去年参与开发的一个电商风控系统就遇到了这个问题。系统需要实时展示订单审核流程,但默认的流程图生成器只能简单高亮当前节点,无法直观展示各个审核环节的状态(如"初审通过"、"复审拒绝"等)。这给运营人员带来了很大困扰,经常需要反复点击查看每个节点的详情。

2. 核心定制化方案

2.1 继承DefaultProcessDiagramCanvas

Flowable的图形渲染最终都是由DefaultProcessDiagramCanvas完成的。要自定义渲染效果,首先需要继承这个类:

public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas { // 自定义高亮当前活动节点的方法 public void drawActiveHighLight(int x, int y, int width, int height) { // 使用绿色填充当前节点 this.drawHighLight(x, y, width, height, Color.GREEN); } // 自定义挂起节点渲染 public void drawSuspendedActivity(int x, int y, int width, int height) { // 使用黄色填充并添加斜线标记 this.drawHighLight(x, y, width, height, Color.YELLOW); this.drawLine(x, y, x+width, y+height, Color.RED); } }

这里我添加了两个典型场景的渲染方法:

  • drawActiveHighLight用绿色填充当前正在处理的节点
  • drawSuspendedActivity用黄色标记被挂起的节点,并添加红色斜线

2.2 重写DefaultProcessDiagramGenerator

有了自定义的画布后,还需要重写生成器来控制何时使用这些特殊渲染:

public class CustomProcessDiagramGenerator extends DefaultProcessDiagramGenerator { @Override protected void drawActivity(ProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode, List<String> highLightedActivities) { // 先调用父类方法绘制基础图形 super.drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities); // 自定义渲染逻辑 if (highLightedActivities.contains(flowNode.getId())) { GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); // 判断节点状态并调用对应的渲染方法 if(isSuspended(flowNode.getId())) { ((CustomProcessDiagramCanvas)processDiagramCanvas) .drawSuspendedActivity((int)graphicInfo.getX(), (int)graphicInfo.getY(), (int)graphicInfo.getWidth(), (int)graphicInfo.getHeight()); } else if(isActive(flowNode.getId())) { ((CustomProcessDiagramCanvas)processDiagramCanvas) .drawActiveHighLight((int)graphicInfo.getX(), (int)graphicInfo.getY(), (int)graphicInfo.getWidth(), (int)graphicInfo.getHeight()); } } } }

3. 完整实现案例

3.1 后端控制器实现

在Spring Boot中,我们可以这样暴露流程图生成接口:

@GetMapping("/process/diagram") public void generateDiagram(@RequestParam String processInstanceId, HttpServletResponse response) throws IOException { // 获取流程定义和实例信息 ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); // 收集高亮节点 List<String> highLightedActivities = new ArrayList<>(); List<String> highLightedFlows = new ArrayList<>(); // 查询历史活动记录 List<HistoricActivityInstance> activities = historyService .createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .list(); // 区分节点类型 for (HistoricActivityInstance activity : activities) { if ("sequenceFlow".equals(activity.getActivityType())) { highLightedFlows.add(activity.getActivityId()); } else { highLightedActivities.add(activity.getActivityId()); } } // 使用自定义生成器 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); ProcessDiagramGenerator generator = new CustomProcessDiagramGenerator(); try (InputStream is = generator.generateDiagram(bpmnModel, "png", highLightedActivities, highLightedFlows, "宋体", "宋体", "宋体", 1.0, true); OutputStream os = response.getOutputStream()) { // 将图像写入响应流 byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } }

3.2 前端交互增强

为了让流程图更具交互性,可以在前端添加鼠标悬停显示节点详情的功能:

// 假设流程图以<img>标签展示 $('#processDiagram').on('mousemove', function(e) { const x = e.offsetX; const y = e.offsetY; // 遍历所有节点信息 nodes.forEach(node => { if (x > node.x && x < node.x + node.width && y > node.y && y < node.y + node.height) { // 显示节点详情弹窗 showNodeTooltip(node, x, y); } }); }); function showNodeTooltip(node, x, y) { // 动态创建tooltip元素 const tooltip = $('<div class="node-tooltip"></div>') .html(` <h4>${node.name}</h4> <p>状态: ${node.status}</p> <p>处理人: ${node.assignee}</p> <p>耗时: ${node.duration}</p> `) .css({ position: 'absolute', left: x + 'px', top: y + 'px' }); $('#diagramContainer').append(tooltip); }

4. 进阶优化技巧

4.1 性能优化方案

当流程实例很多时,频繁生成流程图可能导致性能问题。我们可以采用以下优化策略:

  1. 缓存机制:对已生成的流程图进行缓存
@Cacheable(value = "processDiagrams", key = "#processInstanceId + '_' + #highlightType") public byte[] generateDiagram(String processInstanceId, String highlightType) { // 生成逻辑 }
  1. 异步生成:使用Spring的@Async实现异步生成
@Async public Future<byte[]> asyncGenerateDiagram(String processInstanceId) { // 生成逻辑 return new AsyncResult<>(diagramBytes); }
  1. 预生成策略:在流程关键节点触发预生成

4.2 动态样式配置

为了更灵活地控制渲染样式,可以引入样式配置:

# application.yml flowable: diagram: colors: active: "#4CAF50" suspended: "#FFC107" completed: "#9E9E9E" fonts: default: "Microsoft YaHei" label: "Microsoft YaHei, 12px"

然后在生成器中使用这些配置:

@Value("${flowable.diagram.colors.active}") private String activeColor; protected void drawActiveNode(ProcessDiagramCanvas canvas, GraphicInfo graphicInfo) { Color color = Color.decode(activeColor); // 使用配置的颜色进行绘制 }

5. 常见问题排查

在实际项目中,可能会遇到以下典型问题:

  1. 中文乱码问题
  • 确保系统安装了中文字体
  • 在生成器配置中明确指定中文字体
generateDiagram(bpmnModel, "png", activities, flows, "Microsoft YaHei", "Microsoft YaHei", "Microsoft YaHei", 1.0, true);
  1. 节点坐标偏移
  • 检查BPMN文件中是否正确定义了graphicInfo
  • 确保没有在自定义渲染时错误修改了坐标计算
  1. 渲染性能低下
  • 避免在draw方法中执行复杂计算
  • 考虑使用缓冲图像技术
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = image.createGraphics(); // 绘制操作 g2d.dispose();
  1. 高亮效果不明显
  • 增加边框宽度
  • 使用更醒目的颜色组合
  • 添加动画效果(前端实现)

通过继承Flowable的核心渲染类,我们可以实现高度定制化的流程图展示效果。这种方案不仅能够满足各种业务场景的可视化需求,还能保持与Flowable引擎的无缝集成。在实际项目中,建议先明确业务对流程图的具体要求,再针对性地设计渲染方案,避免过度设计。