
上周接手了一个内部审批系统的改造原本以为只是简单的CRUD增删改查结果发现核心逻辑全卡在“流程”上。一个请假申请从提交到审批通过中间要经过组长、经理、HR等多个节点每个节点都可能通过、驳回或转交。更麻烦的是不同的申请类型如报销、采购流程还不一样。如果全靠硬编码if-else来串联状态代码很快就会变成一团乱麻而且每次业务调整流程开发都要跟着改代码、发版本。这就是工作流引擎要解决的问题把“业务流程”从具体的业务代码中抽离出来变成一个可以独立设计、可视化配置、动态调整的“引擎”。而bpmn-js作为基于 BPMN 2.0 标准的 Web 流程编辑器则是连接业务人员和开发者的桥梁——让非技术人员也能通过拖拽画出流程图并直接生成引擎可执行的流程定义文件。很多人一听到“集成工作流引擎”就觉得是架构升级的大工程下意识想找现成的 SaaS 或低代码平台。但对于很多已有成熟业务系统、只是需要将部分模块流程化的团队来说在 Spring Boot 项目中嵌入一个轻量级引擎如 Flowable、Activiti并搭配前端编辑器往往是更可控、成本也更低的方案。今天我们就来彻底拆解这个方案的“上集”如何将一个可视化的流程设计器bpmn-js无缝集成到你的 Spring Boot 后台服务中并为后续的引擎驱动打下坚实基础。1. 为什么是“流程编辑器”先行而不是先写后端逻辑在集成工作流时一个常见的误区是先埋头研究 Flowable 或 Activiti 的 Java API写一堆RuntimeService、TaskService的调用代码试图用程序逻辑去“拼凑”出一个流程。这相当于还没画图纸就开始砌墙很容易导致前后端认知不一致流程逻辑散落在代码各处难以维护。更合理的路径是“设计驱动开发”先定义流程业务方、产品经理和开发一起使用可视化工具明确流程的节点、路径、审批人、表单和规则。再实现引擎将设计好的流程定义文件部署到引擎中引擎负责驱动流程实例的流转。最后对接业务开发具体的业务接口如提交申请、审批任务这些接口内部调用引擎的 API。bpmn-js扮演的就是第一步中的“设计工具”角色。它是一个基于 BPMN 2.0 标准的 JavaScript 库能让你在浏览器里画出专业的流程图就像 Visio 或 ProcessOn并且这个图背后是标准的 XML 文件.bpmn 或 .bpmn20.xml。这个 XML 文件就是工作流引擎如 Flowable能直接“读懂”并执行的“源代码”。所以集成 bpmn-js 的本质是为你的 Spring Boot 应用添加一个“流程设计中心”。这个中心负责流程的创建、编辑、保存和版本管理。后续引擎集成时只需要从这个中心获取流程定义文件进行部署即可。2. 理解核心BPMN 2.0 标准与 bpmn-js 的定位在动手之前需要先建立两个关键认知BPMN 2.0 (Business Process Model and Notation)这是一套由 OMG 组织维护的、描述业务流程的全球通用标准。它定义了一套丰富的图形元素如事件、活动、网关、顺序流和对应的 XML 模式XSD。它的最大价值在于“可视化与可执行性统一”。你用 BPMN 画出的图不仅能给人看还能被符合标准的引擎Flowable, Activiti, Camunda等直接解析和执行。这就消除了流程图和实际代码之间的“翻译”成本。bpmn-js它是 Camunda 公司也是 Flowable 项目的重要贡献者开源的一个工具包用于在 Web 应用中渲染和编辑 BPMN 2.0 图表。你可以把它理解为一个“BPMN 的富文本编辑器”。它不关心你的后端是 Java 还是 Python也不关心你用哪个工作流引擎。它只负责两件事将 BPMN XML 渲染成可交互的流程图。将用户在界面上的拖拽操作同步更新到底层的 BPMN XML。因此我们的集成目标非常清晰在 Spring Boot 后端提供一个文件存储和管理的服务在前端 Vue/React 页面中嵌入 bpmn-js 编辑器并实现前后端关于 BPMN XML 文件的增删改查同步。3. 环境搭建与基础集成从前端编辑器到后端文件服务假设我们有一个基础的 Spring Boot 2.7 Vue 3 的前后端分离项目。集成 bpmn-js 主要在前端完成后端主要负责模型文件的持久化。3.1 前端嵌入 bpmn-js 编辑器首先在前端项目中安装 bpmn-js 及其相关依赖# 在你的 Vue/React 项目目录下 npm install bpmn-js bpmn-js-properties-panel camunda-bpmn-moddle --savebpmn-js: 核心编辑器库。bpmn-js-properties-panel: 右侧属性面板用于编辑选中元素的属性如任务名称、办理人表达式。camunda-bpmn-moddle: 扩展包使编辑器支持 Flowable/Activiti/Camunda 等引擎的扩展属性如flowable:assignee,flowable:candidateUsers。接下来创建一个流程设计器组件如BpmnModeler.vuetemplate div classcontainer div classheader el-button clickhandleCreateNew新建/el-button el-button clickhandleSave保存/el-button el-button clickhandleDeploy部署/el-button el-select v-modelcurrentModelId placeholder选择流程模型 changeloadModel el-option v-formodel in modelList :keymodel.id :labelmodel.name :valuemodel.id / /el-select /div div classcontent div classcanvas refcanvas/div div classproperties-panel idjs-properties-panel/div /div /div /template script setup import { ref, onMounted, onBeforeUnmount } from vue; 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.json; import axios from axios; const canvas ref(null); const currentModelId ref(); const modelList ref([]); let bpmnModeler null; // 初始化编辑器 const initBpmnModeler () { bpmnModeler new BpmnModeler({ container: canvas.value, propertiesPanel: { parent: #js-properties-panel }, additionalModules: [ propertiesPanelModule, propertiesProviderModule ], moddleExtensions: { camunda: camundaModdleDescriptor } }); // 加载一个空的默认流程图 createNewDiagram(); }; // 创建一个空的、带基本结构的BPMN图 const createNewDiagram async () { const xml ?xml version1.0 encodingUTF-8? bpmn2:definitions xmlns:bpmn2http://www.omg.org/spec/BPMN/20100524/MODEL xmlns:bpmndihttp://www.omg.org/spec/BPMN/20100524/DI xmlns:dchttp://www.omg.org/spec/DD/20100524/DC xmlns:dihttp://www.omg.org/spec/DD/20100524/DI xmlns:flowablehttp://flowable.org/bpmn idsample-diagram targetNamespacehttp://flowable.org/bpmn bpmn2:process idProcess_1 isExecutabletrue bpmn2:startEvent idStartEvent_1 / /bpmn2:process bpmndi:BPMNDiagram idBPMNDiagram_1 bpmndi:BPMNPlane idBPMNPlane_1 bpmnElementProcess_1 bpmndi:BPMNShape idStartEvent_1_di bpmnElementStartEvent_1 dc:Bounds x150 y100 width36 height36 / /bpmndi:BPMNShape /bpmndi:BPMNPlane /bpmndi:BPMNDiagram /bpmn2:definitions; await bpmnModeler.importXML(xml); }; // 从后端加载模型列表 const loadModelList async () { const response await axios.get(/api/bpmn/models); modelList.value response.data; }; // 加载指定模型的XML const loadModel async (modelId) { if (!modelId) return; const response await axios.get(/api/bpmn/model/${modelId}/xml); await bpmnModeler.importXML(response.data.xml); }; // 保存当前模型到后端 const handleSave async () { const { xml } await bpmnModeler.saveXML({ format: true }); const modelName 流程模型_${new Date().getTime()}; await axios.post(/api/bpmn/model, { name: modelName, xml: xml }); // 保存后刷新列表 await loadModelList(); }; // 部署流程这里只是触发后端部署下篇详述 const handleDeploy async () { const { xml } await bpmnModeler.saveXML({ format: true }); await axios.post(/api/bpmn/deploy, { xml: xml }); }; onMounted(() { initBpmnModeler(); loadModelList(); }); onBeforeUnmount(() { if (bpmnModeler) { bpmnModeler.destroy(); } }); /script style scoped .container { height: 100vh; display: flex; flex-direction: column; } .header { padding: 10px; border-bottom: 1px solid #eee; } .content { flex: 1; display: flex; overflow: hidden; } .canvas { flex: 1; border-right: 1px solid #eee; } .properties-panel { width: 300px; overflow-y: auto; } /style这个组件完成了编辑器初始化、创建空白图、从后端加载已有模型、保存模型XML等核心功能。属性面板允许你编辑任务节点的详细信息。3.2 后端提供模型管理的 REST API前端编辑器需要和后端交互进行模型的增删改查。我们在 Spring Boot 中创建相应的控制器和实体。首先定义一个流程模型实体用于在数据库中存储模型的基本信息和XML内容// BpmnModel.java Data Entity Table(name bpmn_model) public class BpmnModel { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String name; // 模型名称 private String key; // 模型Key通常与流程定义Key一致 private String description; Lob // 大文本字段用于存储XML Column(columnDefinition LONGTEXT) private String xmlContent; private String version; // 模型版本 private Long createUserId; private String createUserName; private LocalDateTime createTime; private LocalDateTime updateTime; private Boolean deployed false; // 是否已部署到引擎 }然后创建对应的 Repository 和 Service 层。这里提供一个简单的控制器示例// BpmnModelController.java RestController RequestMapping(/api/bpmn) public class BpmnModelController { Autowired private BpmnModelService bpmnModelService; // 获取模型列表 GetMapping(/models) public ResultListBpmnModelVO listModels() { ListBpmnModel models bpmnModelService.listAll(); ListBpmnModelVO vos models.stream().map(this::convertToVO).collect(Collectors.toList()); return Result.success(vos); } // 根据ID获取模型详情含XML GetMapping(/model/{id}/xml) public ResultBpmnModelXmlVO getModelXml(PathVariable Long id) { BpmnModel model bpmnModelService.getById(id); if (model null) { return Result.error(模型不存在); } BpmnModelXmlVO vo new BpmnModelXmlVO(); vo.setId(model.getId()); vo.setName(model.getName()); vo.setXml(model.getXmlContent()); return Result.success(vo); } // 创建或更新模型 PostMapping(/model) public ResultLong saveModel(RequestBody SaveModelRequest request) { BpmnModel model new BpmnModel(); model.setName(request.getName()); model.setXmlContent(request.getXml()); model.setCreateTime(LocalDateTime.now()); model.setUpdateTime(LocalDateTime.now()); // 可以从XML中解析出key和版本等信息 // String processId parseProcessIdFromXml(request.getXml()); // model.setKey(processId); Long savedId bpmnModelService.saveOrUpdate(model); return Result.success(savedId); } // 触发部署此处预留接口实际部署逻辑在下篇与引擎集成时实现 PostMapping(/deploy) public ResultString deployModel(RequestBody DeployRequest request) { // 1. 将XML保存为临时文件或直接传入引擎 // 2. 调用 Flowable 的 RepositoryService 进行部署 // 3. 更新模型状态为已部署 // 具体实现见下篇 return Result.success(部署请求已接收具体实现需集成Flowable引擎); } // 省略 VO 和 Request 类定义... }至此一个最基础的“流程设计中心”骨架就搭建完成了。前端可以画图、保存、加载后端负责存储。但这只是万里长征第一步一个可用于生产环境的设计器还需要解决一系列工程化问题。4. 从“能用”到“好用”编辑器集成的关键细节与避坑指南如果只是把 bpmn-js 的官方示例跑通你会觉得集成很简单。但一旦投入实际项目以下几个问题会立刻浮现4.1 自定义 Palette工具栏只留下业务需要的元素默认的 bpmn-js 工具栏包含了 BPMN 2.0 全量的元素如各种事件、网关、活动。但对于大多数审批流场景业务人员可能只需要“开始事件”、“结束事件”、“用户任务”、“并行网关”、“排他网关”等少数几个。过多的选项反而会造成困惑。你需要自定义 Palette隐藏不必要的元素// 自定义Palette模块 const customPaletteModule { paletteProvider: [type, function(palette, create, elementFactory, globalConnect) { // 创建一个“仅包含必要元素”的分组 palette.registerProvider(custom-palette, function() { return { getPaletteEntries: function(element) { return { // 隐藏默认的“工具”分组 tool-separator: { group: tools, separator: true }, // 创建我们自己的分组 custom-start-event: { group: custom, className: bpmn-icon-start-event-none, title: 创建开始节点, action: { dragstart: createStart, click: createStart } }, custom-user-task: { group: custom, className: bpmn-icon-user-task, title: 创建用户任务, action: { dragstart: createUserTask, click: createUserTask } }, custom-exclusive-gateway: { group: custom, className: bpmn-icon-gateway-xor, title: 创建排他网关, action: { dragstart: createExclusiveGateway, click: createExclusiveGateway } }, custom-end-event: { group: custom, className: bpmn-icon-end-event-none, title: 创建结束节点, action: { dragstart: createEnd, click: createEnd } } }; } }; }); }] }; // 在初始化Modeler时加入自定义模块 bpmnModeler new BpmnModeler({ container: canvas.value, propertiesPanel: { parent: #js-properties-panel }, additionalModules: [ propertiesPanelModule, propertiesProviderModule, customPaletteModule // 加入自定义模块 ], moddleExtensions: { camunda: camundaModdleDescriptor } });4.2 属性面板的深度定制绑定业务数据默认的属性面板只能编辑 BPMN 标准属性。但在实际业务中我们需要为“用户任务”节点设置审批人flowable:assignee、候选组flowable:candidateGroups、表单Keyflowable:formKey等引擎扩展属性。更进一步的我们可能希望直接在下拉框中选择系统中的角色或用户而不是手动输入表达式。这需要对属性面板的 Provider 进行扩展。以下是一个简化示例展示如何添加一个“审批人”自定义字段// 自定义属性提供者 import { is } from bpmn-js/lib/util/ModelUtil; function CustomPropertiesProvider(propertiesPanel, translate) { // 调用父类构造函数 propertiesPanel.BaseProvider.call(this); // 为“用户任务”提供额外的属性组 this.getGroups function(element) { return function(groups) { // 只针对 UserTask 类型 if (is(element, flowable:UserTask)) { // 添加一个“审批设置”分组 groups.push(createApprovalGroup(element, translate)); } return groups; }; }; } // 创建“审批设置”属性组 function createApprovalGroup(element, translate) { return { id: approval, label: translate(审批设置), entries: [ { id: assignee, label: translate(指定审批人), modelProperty: assignee, widget: textField, // 可以改为 select 并绑定用户列表 get: function(element) { const bo getBusinessObject(element); return { assignee: bo.get(flowable:assignee) }; }, set: function(element, values) { const bo getBusinessObject(element); return bo.set(flowable:assignee, values.assignee || ); } }, { id: candidateGroups, label: translate(候选组), modelProperty: candidateGroups, widget: textField, get: function(element) { const bo getBusinessObject(element); return { candidateGroups: bo.get(flowable:candidateGroups) }; }, set: function(element, values) { const bo getBusinessObject(element); return bo.set(flowable:candidateGroups, values.candidateGroups || ); } } ] }; } // 注册自定义属性提供者 propertiesPanelModule.__init__ [ propertiesProvider, CustomPropertiesProvider ];在实际项目中widget: select的数据源需要从后端 API 动态获取角色和用户列表这需要更复杂的前后端交互。4.3 流程图的导出与导入版本管理与协作导出bpmnModeler.saveXML({ format: true })可以获取格式化后的 XML 字符串。你可以将其提供为.bpmn文件下载方便离线存档或与其他工具交换。导入除了从后端数据库加载还应支持用户直接上传本地的.bpmn或.bpmn20.xml文件通过bpmnModeler.importXML()加载到编辑器中。这是实现流程版本迭代和跨团队协作的基础。图片导出bpmnModeler.saveSVG()可以导出当前流程图的 SVG 格式用于生成审批单上的流程图或在流程监控界面显示。注意 SVG 可能包含大量细节需要后端进行压缩或转换为 PNG。4.4 后端存储的优化不仅仅是存 XML元数据分离不要只存一个巨大的 XML 字段。应将流程的key,name,version等元数据单独存储并建立索引方便快速检索和列表展示。版本控制每次保存应生成新版本而不是覆盖旧版本。可以借鉴 Git 的思想记录版本号、创建人和备注支持回滚到历史版本。模型解析与校验在后端保存 XML 前应尝试用引擎的BpmnXMLConverter进行解析校验确保 XML 语法正确且符合引擎要求。避免存储无法部署的无效流程。大字段处理对于超大的流程图 XML考虑使用对象存储如 MinIO、OSS存储文件数据库中只存文件地址。5. 集成不是终点为后续引擎驱动铺平道路集成 bpmn-js 编辑器看似只是一个前端功能实则是在为整个工作流体系搭建“设计层”。这个设计层的质量直接决定了后续引擎集成的顺畅度。在完成本部分集成后你应该能清晰地回答以下问题这也是为下一篇《SpringBoot集成工作流引擎Flowable/Activiti驱动下》做的准备流程定义从哪里来- 从我们刚建好的“流程设计中心”来通过部署接口下发。流程图的节点属性审批人、表单如何与业务系统关联- 在属性面板定制时我们已经将flowable:assignee等表达式与编辑器绑定这些表达式会在引擎运行时被解析。如何保证设计出的流程一定能被引擎执行- 通过后端的 XML 校验和引擎的部署前检查。流程变更后如何平滑升级- 依靠我们设计的版本管理机制新部署的流程定义会自动作用于新的流程实例旧的实例通常继续按原定义走取决于引擎配置。当你拥有了一个稳定、易用、可定制的流程设计器后工作流项目最难的部分——“如何将业务需求可视化、结构化地定义出来”——就已经解决了大半。剩下的引擎集成、任务查询、审批接口开发更像是按照设计好的“图纸”BPMN XML去组装“机器”流程实例虽然也有不少细节但路径是清晰的。在下一篇文章中我们将把这张“图纸”交给 Flowable 引擎让它真正运转起来并实现启动流程、查询待办、完成任务、追踪进度等核心业务接口。你会发现前期的编辑器集成工作越扎实后面的引擎驱动开发就越像是一场按图索骥的愉快旅程。