SpringBoot集成Activiti/Flowable与bpmnjs构建可视化流程管理平台
这次我们来看一个 SpringBoot 集成工作流引擎和 bpmnjs 流程编辑器的实战项目。对于需要处理业务流程、审批流转的 Java 后端开发者来说,Activiti、Flowable 这类工作流引擎是绕不开的技术选型。但光有引擎还不够,一个可视化的流程设计器对于业务配置和运维至关重要。bpmnjs 正是这样一个基于 Web 的、功能强大的 BPMN 2.0 标准流程编辑器。
本文将聚焦于如何将 SpringBoot 与工作流引擎(以 Activiti/Flowable 为例)集成,并前端引入 bpmnjs 来构建一个完整的、可用的流程管理模块。重点不是空谈概念,而是解决实际集成中的关键问题:如何快速搭建环境、如何设计前后端交互、如何保存和部署流程定义、以及如何避免常见的坑。如果你正在为审批流、工单系统或任何需要流程驱动的业务寻找技术方案,这篇文章可以直接参考。
我们将从项目核心能力速览开始,明确技术栈和门槛,然后逐步完成环境准备、依赖集成、后端接口开发、前端 bpmnjs 集成,并最终实现一个从绘制流程到启动流程实例的完整闭环。过程中会重点关注接口设计、数据持久化和前后端联调的实际操作。
1. 核心能力速览
在深入代码之前,我们先通过下表快速了解本方案的核心构成和能力边界,这有助于你判断是否适合你的项目。
| 能力项 | 说明 |
|---|---|
| 后端技术栈 | SpringBoot 2.7.x / 3.x, Activiti 7 或 Flowable 6/7, Spring Data JPA / MyBatis-Plus, MySQL |
| 前端技术栈 | bpmn-js (核心库), bpmn-js-properties-panel (属性面板), Vue.js / React / 纯HTML集成 |
| 核心功能 | 1. 基于 bpmnjs 的 Web 端流程设计器 2. 流程定义(BPMN XML)的保存与部署 3. 流程实例的启动、查询与任务处理 4. 用户任务分配与审批流转 |
| 启动方式 | 标准的 SpringBoot 应用启动方式,可通过 IDE (如 IDEA) 或命令行mvn spring-boot:run启动。前端资源通常打包在src/main/resources/static下或独立部署。 |
| 接口能力 | 提供 RESTful API 用于流程模型管理(增删改查、部署)、流程实例操作、任务查询与完成等。 |
| 数据持久化 | 工作流引擎会自动创建数十张表(如 ACT_RE_, ACT_RU_, ACT_HI_*)来存储流程定义、运行时数据、历史数据等。 |
| 适合场景 | 企业内部审批系统(请假、报销)、工单处理系统、订单状态机、任何需要可视化配置和驱动业务流程的场景。 |
| 不适合场景 | 超高性能、每秒数千并发的简单状态流转(可能过于繁重);对引擎生成表结构有严格管控且无法接受的情况。 |
2. 适用场景与使用边界
适用场景:
- 业务审批流:如员工请假、费用报销、采购申请等,需要多级、多角色审批。
- 工单与客服系统:用户提交问题,流转至不同部门的技术支持或客服处理。
- 订单与生产流程:从订单创建、支付、生产、质检到发货的标准化流程。
- DevOps 自动化:代码提交流程,自动触发构建、测试、部署等任务。
使用边界与注意事项:
- 学习曲线:需要理解 BPMN 2.0 的基本元素(如开始事件、用户任务、网关、结束事件)以及工作流引擎的 API。
- 数据库依赖:引擎重度依赖数据库,表结构复杂,上线前需规划好数据库版本管理和备份策略。
- 性能考量:对于极其简单的线性流程,直接使用状态字段可能更轻量。工作流引擎的优势在于复杂流程的可视化、可追溯和灵活性。
- 事务与一致性:流程实例的推进通常涉及业务数据和流程数据的更新,需要合理使用 Spring 事务管理来保证一致性。
- 合规性:流程中若涉及敏感数据(如用户个人信息、财务数据),在设计任务分配和流程变量存储时,需考虑数据脱敏和权限控制。
3. 环境准备与前置条件
开始编码前,请确保你的开发环境满足以下要求。这是项目能跑起来的基础。
1. 基础开发环境:
- JDK: 推荐 JDK 8、11 或 17(根据 SpringBoot 版本选择)。本文以 JDK 11 为例。
- Maven: 3.6.x 或以上版本,用于依赖管理。
- IDE: IntelliJ IDEA(推荐)或 Eclipse。
- 数据库: MySQL 5.7 或 8.0。确保你有创建数据库和表的权限。
2. 创建 SpringBoot 项目:使用 Spring Initializr (https://start.spring.io) 或 IDEA 内置工具创建项目。
- Project: Maven Project
- Language: Java
- Spring Boot: 2.7.18 (一个长期支持版本,生态稳定) 或 3.x(需注意依赖兼容性)
- Project Metadata: 按需填写 Group、Artifact(例如
workflow-demo)。 - Dependencies: 先选择
Spring Web,Spring Data JPA,MySQL Driver。工作流引擎的依赖我们稍后手动添加。
3. 数据库准备:在你的 MySQL 中创建一个空数据库,例如act_db。字符集建议utf8mb4。
CREATE DATABASE `act_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;工作流引擎所需的表会在应用首次启动时自动创建(如果配置了spring.jpa.hibernate.ddl-auto=update)。
4. 依赖集成与基础配置
创建好项目后,我们需要引入工作流引擎和前端库的依赖。
4.1 后端依赖 (pom.xml)这里以集成Activiti 7为例。在pom.xml的<dependencies>部分添加:
<!-- Activiti Spring Boot Starter --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> <!-- 请检查最新稳定版 --> </dependency> <!-- 如果使用 Flowable,依赖如下(二选一) --> <!-- <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>6.8.0</version> </dependency> -->添加后,Maven 会自动引入一系列相关依赖。
4.2 应用配置 (application.yml)配置数据库连接、JPA 以及 Activiti。在src/main/resources/application.yml中:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/act_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: your_username password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: database-platform: org.hibernate.dialect.MySQL8Dialect hibernate: ddl-auto: update # 首次启动自动建表,生产环境建议改为 validate 或 none show-sql: true # 开发时显示SQL,方便调试 # Activiti 配置 activiti: database-schema-update: true # 自动更新数据库表结构 db-history-used: true # 使用历史表 history-level: audit # 历史记录级别:audit 记录所有细节 check-process-definitions: false # 启动时不检查 processes/ 目录下的BPMN文件关键点:activiti.check-process-definitions: false这个配置很重要。它阻止了 SpringBoot 启动时自动扫描并部署resources/processes目录下的 BPMN 文件。因为我们打算通过前端 bpmnjs 设计并动态部署流程,所以不需要静态文件。
4.3 前端资源准备bpmnjs 是一个前端库,我们需要将其引入到项目中。有两种常见方式:
- CDN 引入(简单快速,适合 demo):在 HTML 中直接引入 CDN 链接。
- 本地化引入(推荐,稳定可控):下载相关库文件到项目的
static目录。
这里采用第二种方式。你可以从 bpmn-js 发行版 和 bpmn-js-properties-panel 发行版 下载.zip文件,解压后将其中的dist目录复制到src/main/resources/static/bpmnjs下。最终目录结构类似:
src/main/resources/static/ └── bpmnjs/ ├── bpmn-js/ │ ├── bpmn-modeler.development.js │ └── assets/... └── bpmn-js-properties-panel/ ├── bpmn-js-properties-panel.css ├── bpmn-js-properties-panel.development.js └── assets/...5. 后端核心接口开发
后端需要提供一套 REST API,供前端 bpmnjs 编辑器调用,主要功能是流程定义(模型)的增删改查和部署。
5.1 创建流程模型实体与 Repository虽然 Activiti 有自己的表,但我们通常需要一张自定义表来管理前端保存的流程模型(包含名称、版本、BPMN XML、部署状态等)。
// src/main/java/com/example/workflowdemo/entity/ProcessModel.java @Entity @Table(name = "wf_process_model") @Data public class ProcessModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 模型名称 private String key; // 模型Key,通常与BPMN中的process id一致 private String description; @Lob // 用于存储大文本(BPMN XML) @Column(columnDefinition = "LONGTEXT") private String bpmnXml; // 完整的BPMN 2.0 XML字符串 private Integer version = 1; private Boolean deployed = false; // 是否已部署到引擎 private String deploymentId; // 部署后,引擎返回的部署ID private Date createTime; private Date updateTime; }// src/main/java/com/example/workflowdemo/repository/ProcessModelRepository.java @Repository public interface ProcessModelRepository extends JpaRepository<ProcessModel, Long> { List<ProcessModel> findByKey(String key); }5.2 创建流程模型管理 Controller这是前后端交互的核心,提供保存、查询、部署模型的接口。
// src/main/java/com/example/workflowdemo/controller/ModelController.java @RestController @RequestMapping("/api/model") @Slf4j public class ModelController { @Autowired private ProcessModelRepository modelRepository; @Autowired private RepositoryService repositoryService; // Activiti 的仓库服务 /** * 保存或更新流程模型 */ @PostMapping("/save") public ResponseEntity<?> saveModel(@RequestBody ProcessModel model) { try { if (model.getId() == null) { model.setCreateTime(new Date()); } model.setUpdateTime(new Date()); ProcessModel savedModel = modelRepository.save(model); return ResponseEntity.ok(savedModel); } catch (Exception e) { log.error("保存模型失败", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("保存失败"); } } /** * 根据ID获取模型详情(包含BPMN XML) */ @GetMapping("/{id}") public ResponseEntity<?> getModel(@PathVariable Long id) { return modelRepository.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } /** * 部署流程模型(将BPMN XML部署到Activiti引擎) */ @PostMapping("/deploy/{modelId}") public ResponseEntity<?> deployModel(@PathVariable Long modelId) { try { ProcessModel model = modelRepository.findById(modelId) .orElseThrow(() -> new RuntimeException("模型不存在")); if (StringUtils.isEmpty(model.getBpmnXml())) { throw new RuntimeException("模型XML内容为空"); } // 使用 Activiti API 进行部署 Deployment deployment = repositoryService.createDeployment() .name(model.getName()) .key(model.getKey()) .addString(model.getKey() + ".bpmn20.xml", model.getBpmnXml()) // 资源名称 .deploy(); // 更新模型状态 model.setDeployed(true); model.setDeploymentId(deployment.getId()); modelRepository.save(model); Map<String, Object> result = new HashMap<>(); result.put("deploymentId", deployment.getId()); result.put("deploymentName", deployment.getName()); result.put("deploymentTime", deployment.getDeploymentTime()); return ResponseEntity.ok(result); } catch (Exception e) { log.error("部署流程失败, modelId: {}", modelId, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("部署失败: " + e.getMessage()); } } /** * 查询模型列表 */ @GetMapping("/list") public ResponseEntity<List<ProcessModel>> listModels() { return ResponseEntity.ok(modelRepository.findAll()); } }关键点解析:
RepositoryService是 Activiti 管理流程定义部署的核心服务,由 Spring 自动注入。deploy方法中,.addString(...)将我们存储在数据库中的 BPMN XML 字符串,作为一个部署资源添加到引擎中。资源名称需要以.bpmn20.xml结尾,引擎才会识别为流程定义文件。- 部署成功后,引擎会在
ACT_RE_PROCDEF等表中创建记录,并返回一个唯一的deploymentId。
6. 前端 bpmnjs 集成与界面开发
现在,我们来构建前端页面,集成 bpmnjs 编辑器,并调用刚写好的后端接口。
6.1 创建编辑器页面 (index.html)在src/main/resources/static下创建index.html。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>BPMN 流程设计器</title> <!-- 引入 bpmn-js 及属性面板的 CSS/JS --> <link rel="stylesheet" href="/bpmnjs/bpmn-js-properties-panel/bpmn-js-properties-panel.css"> <script src="/bpmnjs/bpmn-js/bpmn-modeler.development.js"></script> <script src="/bpmnjs/bpmn-js-properties-panel/bpmn-js-properties-panel.development.js"></script> <style> html, body { margin: 0; padding: 0; height: 100%; font-family: Arial, sans-serif; } #header { background: #333; color: white; padding: 10px; display: flex; justify-content: space-between; align-items: center; } #container { display: flex; height: calc(100% - 50px); } #canvas { flex: 1; border-right: 1px solid #ccc; } #properties { width: 300px; overflow-y: auto; padding: 10px; box-sizing: border-box; } button { padding: 8px 15px; margin: 0 5px; cursor: pointer; background: #4CAF50; color: white; border: none; border-radius: 4px; } button:hover { background: #45a049; } #modelList { margin-top: 20px; border-top: 1px solid #eee; padding-top: 10px; } </style> </head> <body> <div id="header"> <h2>流程设计器</h2> <div> <button onclick="newDiagram()">新建</button> <button onclick="saveModel()">保存模型</button> <button onclick="deployModel()">部署流程</button> 模型名称: <input type="text" id="modelName" placeholder="输入模型名称"> 模型Key: <input type="text" id="modelKey" placeholder="输入模型Key"> </div> </div> <div id="container"> <div id="canvas"></div> <div id="properties"></div> </div> <div id="modelList"> <h3>已有模型列表</h3> <ul id="modelListUl"></ul> </div> <script> // BPMN 建模器实例 let bpmnModeler = null; // 当前编辑的模型ID let currentModelId = null; // 页面加载完成后初始化编辑器 window.onload = function() { initBpmnModeler(); loadModelList(); }; function initBpmnModeler() { // 创建建模器实例,并挂载属性面板 bpmnModeler = new BpmnJS({ container: '#canvas', propertiesPanel: { parent: '#properties' }, additionalModules: [ BpmnPropertiesPanelModule // 属性面板模块 ] }); // 打开一个空的流程图 bpmnModeler.createDiagram() .then(() => console.log('Diagram created')) .catch(err => console.error('Failed to create diagram', err)); } // 新建一个空流程图 function newDiagram() { currentModelId = null; document.getElementById('modelName').value = ''; document.getElementById('modelKey').value = ''; bpmnModeler.createDiagram() .then(() => console.log('New diagram created')) .catch(err => console.error('Failed to create new diagram', err)); } // 保存模型到后端 async function saveModel() { const modelName = document.getElementById('modelName').value.trim(); const modelKey = document.getElementById('modelKey').value.trim(); if (!modelName || !modelKey) { alert('请填写模型名称和Key'); return; } try { // 从建模器中导出 BPMN XML const { xml } = await bpmnModeler.saveXML({ format: true }); console.log('导出XML成功,长度:', xml.length); const modelData = { id: currentModelId, // 如果为null,后端会新建 name: modelName, key: modelKey, bpmnXml: xml, version: 1 }; const response = await fetch('/api/model/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modelData) }); const result = await response.json(); if (response.ok) { alert('保存成功!模型ID: ' + result.id); currentModelId = result.id; loadModelList(); // 刷新列表 } else { alert('保存失败: ' + (result.message || '未知错误')); } } catch (error) { console.error('保存失败:', error); alert('保存过程发生错误: ' + error.message); } } // 部署当前模型到Activiti引擎 async function deployModel() { if (!currentModelId) { alert('请先保存模型'); return; } if (!confirm('确定要部署该流程吗?部署后即可启动流程实例。')) { return; } try { const response = await fetch(`/api/model/deploy/${currentModelId}`, { method: 'POST' }); const result = await response.json(); if (response.ok) { alert(`部署成功!部署ID: ${result.deploymentId}`); loadModelList(); // 刷新列表,更新部署状态 } else { alert('部署失败: ' + (result.message || '未知错误')); } } catch (error) { console.error('部署失败:', error); alert('部署过程发生错误: ' + error.message); } } // 从后端加载模型列表 async function loadModelList() { try { const response = await fetch('/api/model/list'); const models = await response.json(); const listEl = document.getElementById('modelListUl'); listEl.innerHTML = ''; models.forEach(model => { const li = document.createElement('li'); li.innerHTML = ` <strong>${model.name}</strong> (Key: ${model.key}) - 版本: ${model.version} - 状态: ${model.deployed ? '<span style="color:green">已部署</span>' : '<span style="color:orange">未部署</span>'} <button onclick="loadModelToEditor(${model.id})">编辑</button> `; listEl.appendChild(li); }); } catch (error) { console.error('加载模型列表失败:', error); } } // 将选中的模型加载到编辑器中 async function loadModelToEditor(modelId) { try { const response = await fetch(`/api/model/${modelId}`); const model = await response.json(); if (!response.ok) throw new Error('模型加载失败'); document.getElementById('modelName').value = model.name; document.getElementById('modelKey').value = model.key; currentModelId = model.id; // 将BPMN XML导入编辑器 await bpmnModeler.importXML(model.bpmnXml); console.log('模型导入编辑器成功'); } catch (error) { console.error('加载模型到编辑器失败:', error); alert('加载失败: ' + error.message); } } </script> </body> </html>关键点解析:
BpmnJS构造函数初始化了编辑器,并集成了属性面板 (BpmnPropertiesPanelModule)。bpmnModeler.saveXML()是核心方法,它将画布上的图形导出为标准 BPMN 2.0 XML 字符串,这正是我们需要保存到后端数据库的内容。bpmnModeler.importXML()则是反向操作,将数据库中的 XML 字符串加载并渲染到画布上。- 前端通过
fetchAPI 与我们的后端 REST 接口 (/api/model/*) 进行通信,实现了模型的增、删、改、查、部署全链路。
7. 功能测试与效果验证
现在,启动项目,验证整个流程是否跑通。
7.1 启动后端服务在 IDEA 中直接运行Application主类,或在项目根目录执行:
mvn spring-boot:run观察控制台日志,如果没有报错,并且看到Tomcat started on port(s): 8080以及 Activiti 自动建表的 SQL 语句,说明后端启动成功。检查数据库,应该能看到大量以ACT_开头的表以及我们自定义的wf_process_model表。
7.2 访问前端页面打开浏览器,访问http://localhost:8080。你应该能看到设计器界面。
7.3 绘制并保存第一个流程
- 在左侧工具栏选择元素(如“开始事件”、“用户任务”、“结束事件”),在画布上拖拽绘制一个简单的请假流程。例如:
开始事件->用户任务 (提交申请)->用户任务 (经理审批)->结束事件。 - 点击画布上的“用户任务”,右侧属性面板可以设置其属性,如
Name(任务名称)、Assignee(办理人,可先填zhangsan)。 - 在顶部输入框填写“模型名称”(如“请假流程”)和“模型Key”(如
leave_process)。 - 点击保存模型按钮。观察浏览器控制台 (F12) 的网络请求,应看到向
/api/model/save发送的 POST 请求成功,并返回模型 ID。同时,页面下方的模型列表会刷新,显示刚保存的模型,状态为“未部署”。 - 检查数据库
wf_process_model表,应有一条新记录,bpmn_xml字段存储了完整的 XML。
7.4 部署流程
- 在模型列表中找到刚保存的模型,点击其旁边的编辑按钮,将其加载回画布(可选,用于确认)。
- 确保当前编辑的模型是目标模型(
currentModelId已设置),点击部署流程按钮。 - 确认后,前端会调用
/api/model/deploy/{id}接口。如果成功,页面会提示部署ID。 - 此时,检查数据库的
ACT_RE_PROCDEF(流程定义表)和ACT_RE_DEPLOYMENT(部署信息表),应该能看到新的记录。模型列表中的状态应变为“已部署”。
7.5 验证部署结果(通过API)部署成功后,流程定义就已进入引擎。我们可以写一个简单的测试类或使用curl来验证。 创建一个测试 Controller:
// src/main/java/com/example/workflowdemo/controller/TestController.java @RestController @RequestMapping("/api/test") public class TestController { @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Autowired private RepositoryService repositoryService; @GetMapping("/start/{processKey}") public String startProcess(@PathVariable String processKey) { // 根据流程定义的Key启动一个流程实例 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processKey); return "流程实例启动成功,ID: " + processInstance.getId(); } @GetMapping("/tasks/{assignee}") public List<Map<String, Object>> getTasks(@PathVariable String assignee) { // 查询某个用户的待办任务 List<Task> tasks = taskService.createTaskQuery().taskAssignee(assignee).list(); return tasks.stream().map(task -> { Map<String, Object> map = new HashMap<>(); map.put("taskId", task.getId()); map.put("taskName", task.getName()); map.put("processInstanceId", task.getProcessInstanceId()); return map; }).collect(Collectors.toList()); } }重启应用后,通过浏览器或curl测试:
- 启动流程实例:访问
http://localhost:8080/api/test/start/leave_process。返回流程实例ID。 - 查询任务:访问
http://localhost:8080/api/test/tasks/zhangsan。应该能看到一个名为“提交申请”的任务。这说明流程引擎已经按照我们设计的 BPMN 图运转起来了。
8. 接口 API 与批量任务进阶
基础的增删改查和部署已经完成。在实际项目中,我们还需要更完善的 API 和批量处理能力。
8.1 完整的流程管理 API 设计一个健壮的流程管理模块至少需要以下接口:
GET /api/process-definition: 查询已部署的流程定义列表。POST /api/process-instance/start: 根据流程定义ID或KEY启动实例,支持传入业务变量。GET /api/task: 查询任务(支持按办理人、候选组、流程实例等过滤)。POST /api/task/{taskId}/complete: 完成任务,支持提交审批意见和流程变量。GET /api/process-instance/{instanceId}: 查询流程实例状态和历史。DELETE /api/deployment/{deploymentId}: 级联删除部署(谨慎使用)。
8.2 批量任务处理对于批量操作,如批量启动流程、批量审批,需要注意:
- 性能:在循环中调用引擎 API 时,考虑使用
@Async进行异步处理,避免阻塞主线程。 - 事务:批量操作可能失败部分,需要根据业务决定是整体回滚还是补偿处理。
- 示例:批量启动流程
@Service public class BatchProcessService { @Autowired private RuntimeService runtimeService; @Async // 需要配置Spring异步任务执行器 @Transactional public void batchStartProcess(List<BusinessRequest> requests) { for (BusinessRequest req : requests) { try { Map<String, Object> variables = new HashMap<>(); variables.put("applicant", req.getApplicant()); variables.put("days", req.getDays()); // 启动流程实例,并设置业务变量 ProcessInstance instance = runtimeService.startProcessInstanceByKey( "leave_process", req.getBusinessKey(), // 业务唯一标识,可与流程实例绑定 variables ); log.info("流程实例启动成功: {}", instance.getId()); } catch (Exception e) { log.error("启动流程失败,业务数据: {}", req, e); // 记录失败,继续处理下一个或抛出异常回滚 } } } }9. 常见问题与排查方法
在集成过程中,你可能会遇到以下问题。这里提供排查思路。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 应用启动失败,数据库连接错误 | 1. 数据库地址/用户名/密码错误 2. MySQL驱动版本不匹配 3. 数据库未启动 | 1. 检查application.yml配置2. 检查 pom.xml中的mysql-connector-java版本3. 使用客户端连接数据库测试 | 1. 修正配置 2. 使用与MySQL版本匹配的驱动 3. 启动数据库服务 |
启动时控制台刷出大量Table ‘act_xxx‘ doesn‘t exist,但表似乎没创建 | JPA 的ddl-auto和 Activiti 的database-schema-update配置冲突或权限不足 | 1. 检查数据库用户是否有 CREATE TABLE 权限 2. 将 spring.jpa.hibernate.ddl-auto设为update,activiti.database-schema-update设为true3. 查看更详细的启动日志 | 1. 授予数据库用户足够权限 2. 确保配置正确,首次启动后可将 ddl-auto改为validate |
| 前端 bpmnjs 编辑器空白,控制台报 JS 404 错误 | 静态资源路径错误,bpmnjs 库文件未正确放置 | 1. 浏览器 F12 查看 Network 面板,哪些.js/.css文件 4042. 核对 index.html中src和href的路径与实际文件位置 | 1. 确保bpmn-js和bpmn-js-properties-panel的dist目录内容已复制到static/bpmnjs/下2. 路径改为绝对路径,如 /bpmnjs/bpmn-js/... |
保存模型时,后端报错org.xml.sax.SAXParseException | 从 bpmnjs 导出的 XML 格式可能有问题,或包含非法字符 | 1. 在前端saveModel函数中,将xml字符串打印到控制台2. 检查 XML 字符串的开头和结尾是否完整 | 1. 确保使用bpmnModeler.saveXML({ format: true })进行格式化2. 在后端接收时,可尝试进行 XML 解析验证 |
部署流程成功,但启动实例时报no processes deployed with key ‘xxx‘ | 1. 流程定义 KEY 不匹配 2. 部署的 BPMN XML 中 process节点的id属性与传入的 KEY 不一致 | 1. 检查数据库ACT_RE_PROCDEF表的KEY_字段2. 用 bpmnjs 打开模型,查看根 process节点的 ID 属性 | 1. 启动流程时,使用的 KEY 必须与 BPMN 中process节点的id属性一致2. 在前端保存模型时,确保 modelKey与流程 ID 同步 |
| 用户任务查询不到 | 1. 任务办理人 (assignee) 设置错误或未设置2. 任务尚未到达该用户节点 3. 查询代码有误 | 1. 检查 BPMN 中用户任务的Assignee属性2. 查看 ACT_RU_TASK表,确认任务是否存在及其ASSIGNEE_字段3. 调试 TaskService.createTaskQuery()代码 | 1. 正确设置任务办理人 2. 确保流程实例已流转到该任务节点 3. 使用 taskCandidateUser查询候选任务 |
10. 最佳实践与使用建议
基于以上实践,总结出以下几点建议,可以帮助你在项目中更稳健地使用该集成方案。
- 模型版本管理:在实际业务中,流程可能需要迭代。我们的
ProcessModel实体已有version字段。最佳实践是:每次保存新版本时创建新记录,并保留旧版本记录用于追溯。部署时,应部署特定版本。 - 流程变量设计:流程变量 (
variables) 是连接业务数据与流程的桥梁。设计时,明确哪些数据需要作为流程变量存储(如applicant,amount,approvalResult),避免将过大或敏感的业务对象直接存入。 - 服务注入与事务:在 Spring Bean(如 Service)中,可以直接
@Autowired注入RuntimeService,TaskService等。涉及业务数据更新和流程操作时,使用@Transactional保证一致性。 - 前端编辑器优化:本文使用的是 bpmnjs 的基础建模器。对于生产环境,你可能需要:
- 集成更多插件,如
bpmn-js-token-simulation(令牌模拟)。 - 自定义属性面板,添加业务相关的扩展属性。
- 实现键盘快捷键、导出图片等功能。
- 集成更多插件,如
- 安全性:所有后端 API 都应添加权限校验(如使用 Spring Security),防止未授权用户创建或部署流程。前端传递的 BPMN XML 也应在后端做基本的合法性检查,防止恶意注入。
- 部署与运维:生产环境务必关闭
spring.jpa.hibernate.ddl-auto=update,改为validate,并使用 Flyway 或 Liquibase 进行数据库版本管理。Activiti/Flowable 的历史数据表会随时间增长,需要制定归档或清理策略。
通过以上步骤,我们完成了一个从零开始的 SpringBoot 集成工作流引擎和 bpmnjs 流程编辑器的可运行示例。这个方案的核心价值在于将可视化的流程设计能力无缝嵌入到你的业务系统中,实现了流程定义与业务逻辑的解耦。你可以在此基础上,继续扩展会签、或签、动态指派、子流程等复杂流程模式,构建出更强大的流程驱动型应用。