SpringBoot集成bpmn-js流程设计器:可视化工作流开发实战
在上一篇文章中我们完成了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前端编辑器实现以下闭环功能前端页面加载并渲染bpmn-js编辑器。实现流程图的创建、编辑、保存将XML传回后端。后端接收XML并部署为可执行的流程定义。前端能够查看已部署的流程定义列表及其流程图。2. 环境与项目结构准备假设你已经按照上篇教程搭建好了SpringBoot Flowable的后端环境。我们在此基础上进行前端集成。后端环境JDK: 11 或 17Spring Boot: 2.7.x 或 3.x (注意Flowable版本兼容性本文以Spring Boot 2.7.18 Flowable 7.0.0为例)构建工具: Maven数据库: MySQL 8.0IDE: 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.xml3. 引入 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.cssdiagram-js.css(bpmn-js依赖的底层绘图库样式)步骤2准备基础HTML页面在src/main/resources/templates/下创建modeler.html。!DOCTYPE html html langzh-CN xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleBPMN 2.0 流程设计器/title !-- 引入Bootstrap CSS (可选用于美化页面布局) -- link hrefhttps://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css relstylesheet !-- 引入 bpmn-js 样式 -- link relstylesheet href/js/bpmn-js/bpmn-js.css link relstylesheet 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 idheader h4 stylemargin:0;流程设计器/h4 div div classbtn-group rolegroup button idcreate-diagram classbtn btn-primary btn-sm新建/button button idopen-diagram classbtn btn-secondary btn-sm打开.../button button idsave-diagram classbtn btn-success btn-sm保存/button button iddeploy-diagram classbtn btn-warning btn-sm部署/button /div span idstatus就绪/span /div /div div idcontainer !-- 绘图区域 -- div idcanvas/div !-- 属性面板区域 (需要额外引入 properties-panel 模块) -- !-- div idproperties-panel/div -- /div !-- 引入依赖库 -- script srchttps://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js/script script srchttps://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 /html4. 编写前端核心逻辑 (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 version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL targetNamespacehttp://bpmn.io/schema/bpmn process idProcess_1 isExecutabletrue startEvent idStartEvent_1/ userTask idTask_1 name用户审批/ endEvent idEndEvent_1/ sequenceFlow idFlow_1 sourceRefStartEvent_1 targetRefTask_1/ sequenceFlow idFlow_2 sourceRefTask_1 targetRefEndEvent_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 ResponseEntityMapString, Object saveModel(RequestBody MapString, 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); MapString, Object result new HashMap(); result.put(success, true); result.put(modelId, savedModel.getModelId()); result.put(message, 模型保存成功); return ResponseEntity.ok(result); } GetMapping(/{modelId}/xml) public ResponseEntityString 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 ResponseEntityMapString, Object deployByXml(RequestBody MapString, 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(); MapString, 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 ResponseEntityString getDefinitionXml(PathVariable String definitionId) { // 实现查询逻辑从引擎中获取XML // ... return ResponseEntity.ok(...); } }6. 运行与功能验证启动SpringBoot应用确保你的应用能正常启动数据库连接正确。访问设计器页面打开浏览器访问http://localhost:8080/modeler(你需要创建一个简单的ModelerController来返回modeler.html视图)。测试核心功能新建/绘图点击“新建”按钮在画布上拖拽左侧面板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:1或height: 100%。3. 查看控制台是否有JS错误。点击“保存”或“部署”按钮后端返回400或415错误前端发送的请求格式与后端接收不匹配。1. 检查前端$.ajax的contentType是否为application/json。2. 检查后端控制器方法参数是否使用RequestBody接收JSON。3. 使用浏览器开发者工具查看Request Payload确认发送的数据格式正确。部署成功但在Flowable表中查不到记录部署的BPMN XML不符合规范或不是可执行流程。1. 检查BPMN XML的根元素definitions和process的isExecutable属性是否为true。2. 将前端导出的XML保存为.bpmn20.xml文件用Flowable Modeler或在线BPMN验证工具检查有效性。3. 查看应用日志部署时是否有WARN或ERROR信息。属性面板不显示未引入和配置bpmn-js-properties-panel模块。bpmn-js的核心库不包含属性面板。需要额外引入properties-panel和bpmn-js-properties-panel库并在建模器初始化时通过additionalModules配置。这是一个进阶功能初次集成可暂不添加。跨域问题 (CORS)前端页面地址与后端API地址不同源。如果前端独立部署如使用Vue/React需要在Spring Boot后端配置CORS。在RestController类或方法上添加CrossOrigin注解或使用全局WebMvcConfigurer配置。8. 进阶优化与最佳实践模块化与属性面板引入bpmn-js-properties-panel和camunda-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 } });流程定义版本管理Flowable自动管理版本。每次部署相同Key的流程版本号会递增。前端列表展示时应清晰显示版本。回滚到旧版本需要特殊处理。模型与定义分离本文示例将“保存模型”和“部署定义”分开。这是一种好实践。模型是设计态可以多次修改保存部署是运行态一旦部署就会生成流程实例。生产环境中通常需要模型审批流程后才能部署。前端框架集成本文使用jQuery和原生JS是为了简化演示。在实际Vue或React项目中可以将bpmnModeler实例封装为组件使用响应式数据管理状态并通过框架的生命周期管理资源的创建与销毁。安全性API权限控制部署、保存等写操作接口必须添加权限校验如Spring Security防止未授权访问。XML校验后端接收XML后应进行基本的XML解析和安全校验防止恶意注入或非法格式导致引擎异常。文件上传限制如果支持文件上传部署需严格限制文件大小和类型。错误处理与用户体验前端应增强错误处理例如网络异常、后端业务错误如流程Key重复的友好提示。保存和部署操作可添加加载状态如按钮禁用、显示Loading图标。导入/导出除了从后端加载可以增加从本地XML文件导入的功能。导出功能除了XML还可以支持导出为SVG或PNG图片。通过以上步骤你已经成功将强大的bpmn-js流程编辑器集成到Spring Boot项目中实现了从流程设计、保存到部署的完整闭环。这套组合为你构建自定义工作流平台或为现有系统添加流程编排能力提供了坚实的技术基础。接下来你可以继续探索流程实例的启动、用户任务查询与完成、历史数据查看等运行时API构建出端到端的业务流程管理系统。