SpringBoot集成bpmn-js流程设计器:可视化工作流开发实战
2026/7/22 2:10:09 网站建设 项目流程

在上一篇文章中,我们完成了SpringBoot与工作流引擎(以Flowable为例)的基础集成,并搭建了后端服务。本篇我们将聚焦于前端流程设计器的集成与实战,使用业界流行的bpmn-js库,构建一个功能完整、可嵌入SpringBoot项目的可视化流程编辑器。无论你是需要为OA、审批、工单系统添加自定义流程能力,还是单纯想学习前后端分离模式下工作流的全栈开发,这篇文章都将提供一套可直接复用的解决方案。

1. 核心概念与目标回顾

在深入集成之前,我们有必要明确几个核心概念和本篇文章要达成的目标。

工作流引擎:我们选用 Flowable 作为后端引擎。它是一个轻量级、高性能的BPMN 2.0规范Java实现,负责流程定义部署、流程实例运行、任务分配与历史记录等核心逻辑。

BPMN 2.0:业务流程模型与符号,是一种图形化标准,用于描述业务流程。我们绘制的流程图(.bpmn或.bpmn20.xml文件)就是遵循此标准的XML文件。

bpmn-js:一个基于JavaScript的BPMN 2.0流程图查看与编辑器库。它提供了完整的建模器组件,允许我们在Web页面上以拖拽方式绘制、编辑符合BPMN标准的流程图,并能将图形序列化为后端引擎可识别的XML。

本文目标:在已有的SpringBoot + Flowable后端基础上,集成bpmn-js前端编辑器,实现以下闭环功能:

  1. 前端页面加载并渲染bpmn-js编辑器。
  2. 实现流程图的创建、编辑、保存(将XML传回后端)。
  3. 后端接收XML并部署为可执行的流程定义。
  4. 前端能够查看已部署的流程定义列表及其流程图。

2. 环境与项目结构准备

假设你已经按照上篇教程搭建好了SpringBoot + Flowable的后端环境。我们在此基础上进行前端集成。

后端环境

  • JDK: 11 或 17
  • Spring Boot: 2.7.x 或 3.x (注意Flowable版本兼容性,本文以Spring Boot 2.7.18 + Flowable 7.0.0为例)
  • 构建工具: Maven
  • 数据库: MySQL 8.0
  • IDE: IntelliJ IDEA 或 Eclipse

前端资源准备: 前端部分我们将使用纯静态资源(HTML, JS, CSS)的方式集成到Spring Boot中,通过Thymeleaf模板引擎或直接放在resources/static目录下提供服务。这种方式适合前后端紧耦合的中小型项目。

最终项目结构预览

your-springboot-project/ ├── src/main/java/ │ └── com/example/workflow/ │ ├── controller/ # 新增:流程定义、模型相关控制器 │ ├── service/ # 流程引擎服务 │ ├── entity/ # 实体类 │ └── Application.java ├── src/main/resources/ │ ├── static/ # 存放静态资源 │ │ ├── js/ │ │ │ ├── bpmn-js/ # bpmn-js库文件 │ │ │ └── app.js # 自定义前端逻辑 │ │ ├── css/ │ │ └── lib/ # 其他前端库,如jQuery, Bootstrap │ ├── templates/ # Thymeleaf模板 │ │ └── modeler.html # 流程设计器页面 │ ├── application.yml │ └── ... └── pom.xml

3. 引入 bpmn-js 前端库

bpmn-js可以通过多种方式引入,对于Spring Boot项目,最简便的方式是直接下载其构建好的UMD包,或通过CDN引入。这里我们采用下载到本地static目录的方式,便于离线开发和部署。

步骤1:获取 bpmn-js 资源访问 bpmn-js 发布页面 或使用 npm 构建。更简单的方法是,我们可以从其官方示例或CDN获取关键文件。核心需要两个文件:

  • bpmn-js.development.js:编辑器核心JS。
  • bpmn-js.css:编辑器核心样式。

我们也可以在项目中通过npm install bpmn-js安装,然后将node_modules/bpmn-js/dist下的文件复制到resources/static/js/bpmn-js/。为了简化,本文假设你已经将以下文件放置于src/main/resources/static/js/bpmn-js/

  • bpmn-modeler.development.js(我们使用建模器版本,功能最全)
  • bpmn-js.css
  • diagram-js.css(bpmn-js依赖的底层绘图库样式)

步骤2:准备基础HTML页面src/main/resources/templates/下创建modeler.html

<!DOCTYPE html> <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>BPMN 2.0 流程设计器</title> <!-- 引入Bootstrap CSS (可选,用于美化页面布局) --> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet"> <!-- 引入 bpmn-js 样式 --> <link rel="stylesheet" href="/js/bpmn-js/bpmn-js.css"> <link rel="stylesheet" href="/js/bpmn-js/diagram-js.css"> <style> html, body { margin: 0; padding: 0; height: 100%; font-family: Arial, sans-serif; } #header { background: #333; color: white; padding: 10px 20px; display: flex; justify-content: space-between; align-items: center; } #container { height: calc(100% - 60px); display: flex; } #canvas { flex: 1; border-right: 1px solid #ccc; } #properties-panel { width: 300px; overflow-y: auto; padding: 10px; box-sizing: border-box; } .btn-group { margin-right: 10px; } </style> </head> <body> <div id="header"> <h4 style="margin:0;">流程设计器</h4> <div> <div class="btn-group" role="group"> <button id="create-diagram" class="btn btn-primary btn-sm">新建</button> <button id="open-diagram" class="btn btn-secondary btn-sm">打开...</button> <button id="save-diagram" class="btn btn-success btn-sm">保存</button> <button id="deploy-diagram" class="btn btn-warning btn-sm">部署</button> </div> <span id="status">就绪</span> </div> </div> <div id="container"> <!-- 绘图区域 --> <div id="canvas"></div> <!-- 属性面板区域 (需要额外引入 properties-panel 模块) --> <!-- <div id="properties-panel"></div> --> </div> <!-- 引入依赖库 --> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script> <!-- 引入 bpmn-js 建模器 --> <script src="/js/bpmn-js/bpmn-modeler.development.js"></script> <!-- 引入自定义JS --> <script th:src="@{/js/app.js}"></script> </body> </html>

4. 编写前端核心逻辑 (app.js)

这是集成的核心,负责初始化编辑器、绑定按钮事件、与后端API通信。

// 文件路径:src/main/resources/static/js/app.js $(document).ready(function() { // 1. 初始化 BPMN 建模器 const bpmnModeler = new BpmnJS({ container: '#canvas' // 可以在此配置更多选项,例如启用附加模块 // additionalModules: [ propertiesPanelModule ] }); // 尝试创建并渲染一个空的流程图 async function createNewDiagram() { try { const result = await bpmnModeler.createDiagram(); $('#status').text('已创建新流程图'); } catch (err) { console.error('创建流程图失败:', err); $('#status').text('创建失败'); } } // 2. 打开一个BPMN XML字符串并渲染 async function openDiagram(xml) { try { await bpmnModeler.importXML(xml); $('#status').text('流程图加载成功'); } catch (err) { console.error('渲染流程图失败:', err); $('#status').text('渲染失败: ' + err.message); } } // 3. 导出当前图为BPMN XML async function saveDiagram() { try { const { xml } = await bpmnModeler.saveXML({ format: true }); return xml; } catch (err) { console.error('导出XML失败:', err); $('#status').text('导出失败'); return null; } } // 4. 绑定按钮事件 $('#create-diagram').click(createNewDiagram); $('#open-diagram').click(function() { // 这里可以扩展为从服务器获取流程定义列表,选择后加载 // 示例:打开一个预设的简单XML const sampleXml = `<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" targetNamespace="http://bpmn.io/schema/bpmn"> <process id="Process_1" isExecutable="true"> <startEvent id="StartEvent_1"/> <userTask id="Task_1" name="用户审批"/> <endEvent id="EndEvent_1"/> <sequenceFlow id="Flow_1" sourceRef="StartEvent_1" targetRef="Task_1"/> <sequenceFlow id="Flow_2" sourceRef="Task_1" targetRef="EndEvent_1"/> </process> </definitions>`; openDiagram(sampleXml); }); $('#save-diagram').click(async function() { const xml = await saveDiagram(); if (xml) { // 将XML发送到后端保存(例如保存为模型文件,而非直接部署) $.ajax({ url: '/api/model/save', type: 'POST', contentType: 'application/json', data: JSON.stringify({ bpmnXml: xml, name: '未命名流程' }), success: function(response) { alert('模型保存成功!模型ID:' + response.modelId); $('#status').text('已保存'); }, error: function(xhr) { alert('保存失败: ' + xhr.responseText); } }); } }); $('#deploy-diagram').click(async function() { const xml = await saveDiagram(); if (xml) { // 将XML发送到后端进行部署 $.ajax({ url: '/api/process-definition/deploy', type: 'POST', contentType: 'application/json', data: JSON.stringify({ bpmnXml: xml, processName: '我的业务流程' }), success: function(response) { alert('流程部署成功!定义ID:' + response.definitionId); $('#status').text('已部署'); }, error: function(xhr) { alert('部署失败: ' + xhr.responseText); } }); } }); // 5. 页面加载时创建一个默认空图 createNewDiagram(); });

5. 完善后端控制器与服务

前端需要与后端交互,主要涉及两个功能:保存流程模型部署流程定义。我们在上篇的ProcessDefinitionController基础上进行扩展。

首先,创建模型保存的实体和控制器

// 文件路径:src/main/java/com/example/workflow/entity/ModelEntity.java package com.example.workflow.entity; import lombok.Data; import javax.persistence.*; import java.util.Date; @Entity @Table(name = "wf_model") @Data public class ModelEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String modelId; // 可对应Flowable的模型ID,或自定义唯一标识 private String name; @Lob @Column(columnDefinition = "LONGTEXT") private String bpmnXml; // 存储BPMN XML内容 private String createBy; @Temporal(TemporalType.TIMESTAMP) private Date createTime; @Temporal(TemporalType.TIMESTAMP) private Date updateTime; // 省略 getter/setter }
// 文件路径:src/main/java/com/example/workflow/controller/ModelController.java package com.example.workflow.controller; import com.example.workflow.entity.ModelEntity; import com.example.workflow.repository.ModelRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; @RestController @RequestMapping("/api/model") public class ModelController { @Autowired private ModelRepository modelRepository; // 需创建对应的JPA Repository @PostMapping("/save") public ResponseEntity<Map<String, Object>> saveModel(@RequestBody Map<String, String> payload) { String bpmnXml = payload.get("bpmnXml"); String name = payload.get("name"); ModelEntity model = new ModelEntity(); model.setModelId("model_" + UUID.randomUUID().toString().replace("-", "")); model.setName(name); model.setBpmnXml(bpmnXml); model.setCreateBy("admin"); // 实际应从安全上下文获取 model.setCreateTime(new Date()); model.setUpdateTime(new Date()); ModelEntity savedModel = modelRepository.save(model); Map<String, Object> result = new HashMap<>(); result.put("success", true); result.put("modelId", savedModel.getModelId()); result.put("message", "模型保存成功"); return ResponseEntity.ok(result); } @GetMapping("/{modelId}/xml") public ResponseEntity<String> getModelXml(@PathVariable String modelId) { ModelEntity model = modelRepository.findByModelId(modelId); if (model != null) { return ResponseEntity.ok(model.getBpmnXml()); } else { return ResponseEntity.notFound().build(); } } }

然后,增强流程部署的控制器

// 文件路径:src/main/java/com/example/workflow/controller/ProcessDefinitionController.java (部分新增) package com.example.workflow.controller; import org.flowable.engine.RepositoryService; import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.ProcessDefinition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/process-definition") public class ProcessDefinitionController { @Autowired private RepositoryService repositoryService; // 部署流程定义 (接收前端传来的BPMN XML字符串) @PostMapping("/deploy") public ResponseEntity<Map<String, Object>> deployByXml(@RequestBody Map<String, String> payload) { String bpmnXml = payload.get("bpmnXml"); String processName = payload.get("processName"); if (bpmnXml == null || bpmnXml.trim().isEmpty()) { throw new RuntimeException("BPMN XML内容不能为空"); } // 使用RepositoryService进行部署 Deployment deployment = repositoryService.createDeployment() .name(processName + "_部署") .addString(processName + ".bpmn20.xml", bpmnXml) // 资源名称 .deploy(); ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .deploymentId(deployment.getId()) .singleResult(); Map<String, Object> result = new HashMap<>(); result.put("success", true); result.put("deploymentId", deployment.getId()); result.put("definitionId", processDefinition.getId()); result.put("definitionKey", processDefinition.getKey()); result.put("definitionName", processDefinition.getName()); result.put("message", "流程部署成功"); return ResponseEntity.ok(result); } // 获取已部署的流程定义列表 (供前端选择打开) @GetMapping("/list") public ResponseEntity<?> listDefinitions() { // 实现查询逻辑,返回列表 // ... return ResponseEntity.ok(...); } // 根据流程定义ID获取其BPMN XML @GetMapping("/{definitionId}/xml") public ResponseEntity<String> getDefinitionXml(@PathVariable String definitionId) { // 实现查询逻辑,从引擎中获取XML // ... return ResponseEntity.ok(...); } }

6. 运行与功能验证

  1. 启动SpringBoot应用:确保你的应用能正常启动,数据库连接正确。
  2. 访问设计器页面:打开浏览器,访问http://localhost:8080/modeler(你需要创建一个简单的ModelerController来返回modeler.html视图)。
  3. 测试核心功能
    • 新建/绘图:点击“新建”按钮,在画布上拖拽左侧面板(bpmn-js自带)的元素(如开始事件、用户任务、结束事件)并连接。
    • 保存模型:绘制后点击“保存”,观察浏览器控制台网络请求,确认/api/model/save被调用并成功,数据库wf_model表应有记录。
    • 部署流程:点击“部署”,观察网络请求,确认/api/process-definition/deploy成功。检查Flowable的ACT_RE_PROCDEF表应有新的流程定义。
    • 打开流程:你可以先实现一个简单的流程定义列表页面,点击后调用/api/process-definition/{id}/xml获取XML,再通过openDiagram(xml)渲染。

7. 常见问题与排查思路

问题现象可能原因排查步骤与解决方案
前端页面空白,控制台报BpmnJS is not definedbpmn-modeler.development.js未正确加载或路径错误。1. 检查浏览器开发者工具Network标签,确认JS文件是否成功加载(状态200)。
2. 检查HTML中<script>标签的src路径是否正确指向static/js/bpmn-js/目录下的文件。
3. 确保引入顺序,依赖库在前。
绘图面板不显示或元素无法拖拽bpmn-js CSS文件未加载,或容器#canvas的尺寸异常。1. 检查CSS文件是否加载。
2. 检查#canvas的CSS样式,确保其有明确的高度和宽度(如flex:1height: 100%)。
3. 查看控制台是否有JS错误。
点击“保存”或“部署”按钮,后端返回400或415错误前端发送的请求格式与后端接收不匹配。1. 检查前端$.ajaxcontentType是否为'application/json'
2. 检查后端控制器方法参数是否使用@RequestBody接收JSON。
3. 使用浏览器开发者工具查看Request Payload,确认发送的数据格式正确。
部署成功,但在Flowable表中查不到记录部署的BPMN XML不符合规范或不是可执行流程。1. 检查BPMN XML的根元素definitionsprocessisExecutable属性是否为"true"
2. 将前端导出的XML保存为.bpmn20.xml文件,用Flowable Modeler或在线BPMN验证工具检查有效性。
3. 查看应用日志,部署时是否有WARN或ERROR信息。
属性面板不显示未引入和配置bpmn-js-properties-panel模块。bpmn-js的核心库不包含属性面板。需要额外引入properties-panelbpmn-js-properties-panel库,并在建模器初始化时通过additionalModules配置。这是一个进阶功能,初次集成可暂不添加。
跨域问题 (CORS)前端页面地址与后端API地址不同源。如果前端独立部署(如使用Vue/React),需要在Spring Boot后端配置CORS。在@RestController类或方法上添加@CrossOrigin注解,或使用全局WebMvcConfigurer配置。

8. 进阶优化与最佳实践

  1. 模块化与属性面板:引入bpmn-js-properties-panelcamunda-bpmn-moddle(如果需要Camunda扩展属性)来启用右侧属性面板,允许编辑任务分配人、表单Key等业务属性。

    // 示例:引入属性面板模块 import BpmnModeler from 'bpmn-js/lib/Modeler'; import propertiesPanelModule from 'bpmn-js-properties-panel'; import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'; import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'; const bpmnModeler = new BpmnModeler({ container: '#canvas', propertiesPanel: { parent: '#properties-panel' }, additionalModules: [ propertiesPanelModule, propertiesProviderModule ], moddleExtensions: { camunda: camundaModdleDescriptor } });
  2. 流程定义版本管理:Flowable自动管理版本。每次部署相同Key的流程,版本号会递增。前端列表展示时应清晰显示版本。回滚到旧版本需要特殊处理。

  3. 模型与定义分离:本文示例将“保存模型”和“部署定义”分开。这是一种好实践。模型是设计态,可以多次修改保存;部署是运行态,一旦部署就会生成流程实例。生产环境中,通常需要模型审批流程后才能部署。

  4. 前端框架集成:本文使用jQuery和原生JS是为了简化演示。在实际Vue或React项目中,可以将bpmnModeler实例封装为组件,使用响应式数据管理状态,并通过框架的生命周期管理资源的创建与销毁。

  5. 安全性

    • API权限控制:部署、保存等写操作接口必须添加权限校验(如Spring Security),防止未授权访问。
    • XML校验:后端接收XML后,应进行基本的XML解析和安全校验,防止恶意注入或非法格式导致引擎异常。
    • 文件上传限制:如果支持文件上传部署,需严格限制文件大小和类型。
  6. 错误处理与用户体验:前端应增强错误处理,例如网络异常、后端业务错误(如流程Key重复)的友好提示。保存和部署操作可添加加载状态(如按钮禁用、显示Loading图标)。

  7. 导入/导出:除了从后端加载,可以增加从本地XML文件导入的功能。导出功能除了XML,还可以支持导出为SVG或PNG图片。

通过以上步骤,你已经成功将强大的bpmn-js流程编辑器集成到Spring Boot项目中,实现了从流程设计、保存到部署的完整闭环。这套组合为你构建自定义工作流平台或为现有系统添加流程编排能力提供了坚实的技术基础。接下来,你可以继续探索流程实例的启动、用户任务查询与完成、历史数据查看等运行时API,构建出端到端的业务流程管理系统。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询