Flowable在线流程图绘制:从入门到实战
前言
在企业级应用开发中,工作流引擎是不可或缺的组件。Flowable作为一个轻量级、开源的工作流和业务流程管理(BPM)平台,被广泛应用于各类业务系统中。然而,传统的流程设计方式需要借助专门的建模工具(如Flowable Modeler、Camunda Modeler等),操作复杂且不够直观。
本文将介绍如何基于 Flowable 6.8.0 和 bpmn-js 构建一个在线流程图绘制系统,实现:
- 在线绘制 BPMN 2.0 流程图
- 流程定义保存到 MySQL 数据库
- 一键部署流程到 Flowable 引擎
- 完整的前后端分离架构
一、为什么选择 bpmn-js?
1.1 bpmn-js 简介
bpmn-js 是一个由 Camunda 开发的 BPMN 2.0 建模工具库,纯 JavaScript 实现,可以直接在浏览器中运行。它提供了:
- 完整的 BPMN 2.0 支持:支持所有 BPMN 2.0 元素和属性
- 轻量级:无需安装任何插件或软件
- 可定制:支持自定义样式和行为
- 开源免费:采用开源协议,可商用
1.2 bpmn-js vs 传统建模工具
特性 bpmn-js Flowable Modeler Eclipse Plugin 安装方式 无需安装 需要部署 需要安装 使用环境 浏览器 独立应用 Eclipse IDE 协作支持 易于集成 困难 困难 定制能力 高 中 低 学习曲线 平缓 陡峭 陡峭
二、系统架构设计
2.1 整体架构
本系统采用前后端分离的架构设计,主要包括以下层次:
整体架构
表现层(Presentation Layer)
- Vue.js 3.x 前端框架
- bpmn-js 流程设计器
- Element Plus UI 组件库
业务服务层(Business Service Layer)
- Spring Boot 2.7 应用框架
- 流程设计服务、部署服务、实例服务
- RESTful API 接口层
流程引擎层(Process Engine Layer)
- Flowable 6.8.0 流程引擎
- 流程定义引擎、任务引擎、历史引擎
数据存储层(Data Storage Layer)
- MySQL 8.0 存储流程定义、实例、任务数据
- Redis 6.x 缓存、会话管理
- MinIO 对象存储(可选)
2.2 核心交互流程
核心交互流程
- 用户在前端界面点击"新建流程"
- 前端请求后端 API 创建流程定义
- 后端调用 Flowable 的 RepositoryService
- Flowable 引擎将流程定义保存到数据库
- 返回流程定义 ID 给前端
- 用户使用 bpmn-js 绘制流程图
- 保存时将 BPMN XML 发送到后端
- 后端更新数据库中的流程定义
三、环境准备
3.1 开发环境要求
软件 版本 说明 JDK 1.8+ Java 开发环境 Maven 3.6+ 项目构建工具 MySQL 5.7+ 或 8.0+ 关系数据库 Node.js 14+ 前端开发(可选)
3.2 技术栈版本
技术栈架构
后端技术栈
- Spring Boot 2.7.18
- Flowable 6.8.0
- MyBatis Plus 3.5.2
- MySQL Connector 8.0.28
前端技术栈
- bpmn-js 14.0.0
- jQuery 3.6.0
- Bootstrap 5(可选)
四、项目初始化
4.1 创建 Maven 项目
使用 Spring Initializr 创建项目,或手动创建 pom.xml:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version></parent><dependencies> <!-- Flowable Starter --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>6.8.0</version> </dependency> <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <!-- MySQL Driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency></dependencies>4.2 数据库初始化
创建数据库和流程定义表:
CREATE DATABASE flowable_online CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;USE flowable_online;CREATE TABLE flow_definition ( id BIGINT AUTO_INCREMENT PRIMARY KEY, process_key VARCHAR(255) NOT NULL, process_name VARCHAR(255) NOT NULL, version INT NOT NULL DEFAULT 1, bpmn_xml LONGTEXT NOT NULL, description TEXT, category VARCHAR(255), created_by VARCHAR(100), deployed TINYINT DEFAULT 0, deployment_id VARCHAR(64), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT DEFAULT 0, UNIQUE KEY uk_process_key_version (process_key, version, deleted));五、后端实现
5.1 实体类设计
创建流程定义实体类:
@Data@TableName("flow_definition")public class FlowDefinition { @TableId(type = IdType.AUTO) private Long id; private String processKey; private String processName; private Integer version; private String bpmnXml; private String description; private String category; private String createdBy; private Integer deployed; private String deploymentId; private LocalDateTime createTime; private LocalDateTime updateTime; @TableLogic private Integer deleted;}5.2 数据访问层
使用 MyBatis Plus 的 Mapper 接口:
@Mapperpublic interface FlowDefinitionMapper extends BaseMapper<FlowDefinition> { List<FlowDefinition> selectByProcessKey(@Param("processKey") String processKey); List<FlowDefinition> selectByCreatedBy(@Param("createdBy") String createdBy);}5.3 服务层实现
流程设计核心服务:
package com.example.flowable.service;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.flowable.entity.FlowDefinition;import com.example.flowable.entity.Result;import com.example.flowable.exception.FlowableException;import com.example.flowable.repository.FlowDefinitionMapper;import lombok.extern.slf4j.Slf4j;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.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import org.springframework.util.StringUtils;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.nio.charset.StandardCharsets;import java.time.LocalDateTime;import java.util.List;import java.util.zip.ZipInputStream;/** * 流程设计服务实现类 * 提供流程定义的保存、查询、删除、部署等功能 * @version 1.0.0 */@Slf4j@Servicepublic class FlowDesignService extends ServiceImpl<FlowDefinitionMapper, FlowDefinition> { @Autowired private FlowDefinitionMapper flowDefinitionMapper; @Autowired private RepositoryService repositoryService; /** * 保存流程定义 * * @param flowDefinition 流程定义实体 * @return 保存结果 */ @Transactional(rollbackFor = Exception.class) public Result<FlowDefinition> saveFlowDefinition(FlowDefinition flowDefinition) { try { // 验证必填字段 if (!StringUtils.hasText(flowDefinition.getProcessKey())) { return Result.fail("流程Key不能为空"); } if (!StringUtils.hasText(flowDefinition.getProcessName())) { return Result.fail("流程名称不能为空"); } if (!StringUtils.hasText(flowDefinition.getBpmnXml())) { return Result.fail("BPMN XML内容不能为空"); } // 检查流程Key是否已存在(同版本) QueryWrapper<FlowDefinition> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("process_key", flowDefinition.getProcessKey()) .eq("version", flowDefinition.getVersion() != null ? flowDefinition.getVersion() : 1) .eq("deleted", 0); Long count = flowDefinitionMapper.selectCount(queryWrapper); if (count > 0) { return Result.fail("流程Key和版本号已存在,请修改版本号"); } // 设置默认值 if (flowDefinition.getVersion() == null) { flowDefinition.setVersion(1); } if (flowDefinition.getCreateTime() == null) { flowDefinition.setCreateTime(LocalDateTime.now()); } flowDefinition.setUpdateTime(LocalDateTime.now()); flowDefinition.setDeleted(0); flowDefinition.setDeployed(0); // 保存到数据库 flowDefinitionMapper.insert(flowDefinition); log.info("保存流程定义成功: id={}, key={}, name={}", flowDefinition.getId(), flowDefinition.getProcessKey(), flowDefinition.getProcessName()); return Result.success("流程定义保存成功", flowDefinition); } catch (Exception e) { log.error("保存流程定义失败: {}", e.getMessage(), e); return Result.fail("保存流程定义失败: " + e.getMessage()); } } /** * 更新流程定义 * * @param flowDefinition 流程定义实体 * @return 更新结果 */ @Transactional(rollbackFor = Exception.class) public Result<FlowDefinition> updateFlowDefinition(FlowDefinition flowDefinition) { try { if (flowDefinition.getId() == null) { return Result.fail("流程定义ID不能为空"); } FlowDefinition existing = flowDefinitionMapper.selectById(flowDefinition.getId()); if (existing == null) { return Result.fail("流程定义不存在"); } // 已部署的流程不能修改 if (existing.getDeployed() == 1) { return Result.fail("已部署的流程不能修改,请新建版本"); } flowDefinition.setUpdateTime(LocalDateTime.now()); int rows = flowDefinitionMapper.updateById(flowDefinition); if (rows > 0) { log.info("更新流程定义成功: id={}", flowDefinition.getId()); return Result.success("流程定义更新成功", flowDefinition); } else { return Result.fail("更新流程定义失败"); } } catch (Exception e) { log.error("更新流程定义失败: {}", e.getMessage(), e); return Result.fail("更新流程定义失败: " + e.getMessage()); } } /** * 删除流程定义 * * @param id 流程定义ID * @return 删除结果 */ @Transactional(rollbackFor = Exception.class) public Result<Void> deleteFlowDefinition(Long id) { try { FlowDefinition flowDefinition = flowDefinitionMapper.selectById(id); if (flowDefinition == null) { return Result.fail("流程定义不存在"); } // 逻辑删除 flowDefinition.setDeleted(1); flowDefinition.setUpdateTime(LocalDateTime.now()); flowDefinitionMapper.updateById(flowDefinition); log.info("删除流程定义成功: id={}", id); return Result.success("流程定义删除成功"); } catch (Exception e) { log.error("删除流程定义失败: {}", e.getMessage(), e); return Result.fail("删除流程定义失败: " + e.getMessage()); } } /** * 根据ID查询流程定义 * * @param id 流程定义ID * @return 查询结果 */ public Result<FlowDefinition> getFlowDefinitionById(Long id) { try { FlowDefinition flowDefinition = flowDefinitionMapper.selectById(id); if (flowDefinition == null) { return Result.fail("流程定义不存在"); } return Result.success(flowDefinition); } catch (Exception e) { log.error("查询流程定义失败: {}", e.getMessage(), e); return Result.fail("查询流程定义失败: " + e.getMessage()); } } /** * 查询所有流程定义列表 * * @return 流程定义列表 */ public Result<List<FlowDefinition>> getAllFlowDefinitions() { try { QueryWrapper<FlowDefinition> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("deleted", 0) .orderByDesc("create_time"); List<FlowDefinition> list = flowDefinitionMapper.selectList(queryWrapper); return Result.success(list); } catch (Exception e) { log.error("查询流程定义列表失败: {}", e.getMessage(), e); return Result.fail("查询流程定义列表失败: " + e.getMessage()); } } /** * 部署流程到Flowable引擎 * * @param id 流程定义ID * @return 部署结果 */ @Transactional(rollbackFor = Exception.class) public Result<Deployment> deployFlowDefinition(Long id) { try { FlowDefinition flowDefinition = flowDefinitionMapper.selectById(id); if (flowDefinition == null) { return Result.fail("流程定义不存在"); } if (flowDefinition.getDeployed() == 1) { return Result.fail("该流程已经部署过了"); } // 部署到Flowable引擎 String deploymentName = flowDefinition.getProcessName() + ".bpmn20.xml"; Deployment deployment = repositoryService.createDeployment() .name(flowDefinition.getProcessName()) .category(flowDefinition.getCategory()) .addBytes(deploymentName, flowDefinition.getBpmnXml().getBytes(StandardCharsets.UTF_8)) .deploy(); // 更新部署状态 flowDefinitionMapper.updateDeployStatus(id, 1, deployment.getId()); log.info("部署流程成功: id={}, deploymentId={}", id, deployment.getId()); return Result.success("流程部署成功", deployment); } catch (Exception e) { log.error("部署流程失败: {}", e.getMessage(), e); return Result.fail("部署流程失败: " + e.getMessage()); } } /** * 获取Flowable流程定义列表 * * @return 流程定义列表 */ public Result<List<ProcessDefinition>> getFlowableProcessDefinitions() { try { List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery() .orderByProcessDefinitionVersion() .desc() .list(); return Result.success(list); } catch (Exception e) { log.error("获取Flowable流程定义列表失败: {}", e.getMessage(), e); return Result.fail("获取流程定义列表失败: " + e.getMessage()); } } /** * 根据流程Key获取最新版本的流程定义 * * @param processKey 流程Key * @return 流程定义 */ public Result<ProcessDefinition> getLatestProcessDefinitionByKey(String processKey) { try { ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey(processKey) .latestVersion() .singleResult(); if (processDefinition == null) { return Result.fail("流程定义不存在"); } return Result.success(processDefinition); } catch (Exception e) { log.error("获取流程定义失败: {}", e.getMessage(), e); return Result.fail("获取流程定义失败: " + e.getMessage()); } } /** * 导出流程定义XML * * @param id 流程定义ID * @return BPMN XML字符串 */ public Result<String> exportFlowDefinitionXml(Long id) { try { FlowDefinition flowDefinition = flowDefinitionMapper.selectById(id); if (flowDefinition == null) { return Result.fail("流程定义不存在"); } return Result.success(flowDefinition.getBpmnXml()); } catch (Exception e) { log.error("导出流程定义失败: {}", e.getMessage(), e); return Result.fail("导出流程定义失败: " + e.getMessage()); } } /** * 获取流程定义的XML资源 * * @param processDefinitionId 流程定义ID * @return XML资源内容 */ public Result<String> getProcessDefinitionResource(String processDefinitionId) { try { ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionId(processDefinitionId) .singleResult(); if (processDefinition == null) { return Result.fail("流程定义不存在"); } String resourceName = processDefinition.getResourceName(); InputStream inputStream = repositoryService.getResourceAsStream( processDefinition.getDeploymentId(), resourceName); byte[] bytes = readAllBytes(inputStream); return Result.success(new String(bytes, StandardCharsets.UTF_8)); } catch (Exception e) { log.error("获取流程定义资源失败: {}", e.getMessage(), e); return Result.fail("获取流程定义资源失败: " + e.getMessage()); } } /** * 读取输入流的所有字节(Java 8 兼容版本) * * @param inputStream 输入流 * @return 字节数组 * @throws IOException 读取异常 */ private byte[] readAllBytes(InputStream inputStream) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[4096]; while ((nRead = inputStream.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); return buffer.toByteArray(); }}5.4 控制器层
RESTful API 接口:
package com.example.flowable.controller;import com.example.flowable.entity.FlowDefinition;import com.example.flowable.entity.Result;import com.example.flowable.service.FlowDesignService;import lombok.extern.slf4j.Slf4j;import org.flowable.engine.repository.Deployment;import org.flowable.engine.repository.ProcessDefinition;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import javax.validation.Valid;import java.util.List;/** * 流程设计控制器 * 提供流程设计的REST API接口 * @version 1.0.0 */@Slf4j@RestController@RequestMapping("/api/flow")@CrossOrigin(origins = "*")public class FlowDesignController { @Autowired private FlowDesignService flowDesignService; /** * 保存流程定义 * * @param flowDefinition 流程定义实体 * @return 保存结果 */ @PostMapping("/save") public Result<FlowDefinition> saveFlowDefinition(@Valid @RequestBody FlowDefinition flowDefinition) { log.info("保存流程定义: key={}, name={}", flowDefinition.getProcessKey(), flowDefinition.getProcessName()); return flowDesignService.saveFlowDefinition(flowDefinition); } /** * 更新流程定义 * * @param flowDefinition 流程定义实体 * @return 更新结果 */ @PostMapping("/update") public Result<FlowDefinition> updateFlowDefinition(@Valid @RequestBody FlowDefinition flowDefinition) { log.info("更新流程定义: id={}", flowDefinition.getId()); return flowDesignService.updateFlowDefinition(flowDefinition); } /** * 删除流程定义 * * @param id 流程定义ID * @return 删除结果 */ @DeleteMapping("/delete/{id}") public Result<Void> deleteFlowDefinition(@PathVariable Long id) { log.info("删除流程定义: id={}", id); return flowDesignService.deleteFlowDefinition(id); } /** * 根据ID查询流程定义 * * @param id 流程定义ID * @return 查询结果 */ @GetMapping("/get/{id}") public Result<FlowDefinition> getFlowDefinitionById(@PathVariable Long id) { return flowDesignService.getFlowDefinitionById(id); } /** * 获取所有流程定义列表 * * @return 流程定义列表 */ @GetMapping("/list") public Result<List<FlowDefinition>> getAllFlowDefinitions() { return flowDesignService.getAllFlowDefinitions(); } /** * 部署流程到Flowable引擎 * * @param id 流程定义ID * @return 部署结果 */ @PostMapping("/deploy/{id}") public Result<Deployment> deployFlowDefinition(@PathVariable Long id) { log.info("部署流程: id={}", id); return flowDesignService.deployFlowDefinition(id); } /** * 获取Flowable流程定义列表 * * @return 流程定义列表 */ @GetMapping("/deployed/list") public Result<List<ProcessDefinition>> getDeployedFlowDefinitions() { return flowDesignService.getFlowableProcessDefinitions(); } /** * 根据流程Key获取最新版本的流程定义 * * @param processKey 流程Key * @return 流程定义 */ @GetMapping("/getByKey/{processKey}") public Result<ProcessDefinition> getProcessDefinitionByKey(@PathVariable String processKey) { return flowDesignService.getLatestProcessDefinitionByKey(processKey); } /** * 导出流程定义XML * * @param id 流程定义ID * @return BPMN XML字符串 */ @GetMapping("/export/{id}") public Result<String> exportFlowDefinitionXml(@PathVariable Long id) { log.info("导出流程定义XML: id={}", id); return flowDesignService.exportFlowDefinitionXml(id); } /** * 获取流程定义的XML资源 * * @param processDefinitionId 流程定义ID * @return XML资源内容 */ @GetMapping("/resource/{processDefinitionId}") public Result<String> getProcessDefinitionResource(@PathVariable String processDefinitionId) { return flowDesignService.getProcessDefinitionResource(processDefinitionId); }}六、前端实现
6.1 集成 bpmn-js
在 HTML 页面中引入 bpmn-js 库:
<script src="https://unpkg.com/bpmn-js@14.0.0/dist/bpmn-modeler.development.js"></script><link rel="stylesheet" href="https://unpkg.com/bpmn-js@14.0.0/dist/assets/diagram-js.css"/>6.2 初始化建模器
// 创建 BPMN 建模器实例const bpmnModeler = new BpmnJS({ container: '#canvas', height: '100%', keyboard: { bindTo: window }});// 导入默认的 BPMN 模板bpmnModeler.importXML(defaultBpmnTemplate).then(() => { bpmnModeler.get('canvas').zoom('fit-viewport');});6.3 保存流程定义
function saveFlowDefinition() { // 获取 BPMN XML bpmnModeler.saveXML({ format: true }).then(result => { const bpmnXml = result.xml; // 发送到后端保存 $.ajax({ url: '/api/flow/save', method: 'POST', contentType: 'application/json', data: JSON.stringify({ processKey: $('#processKey').val(), processName: $('#processName').val(), bpmnXml: bpmnXml }), success: function(response) { if (response.code === 200) { showToast('保存成功', 'success'); } } }); });}6.4 部署流程
function deployFlowDefinition(id) { $.ajax({ url: '/api/flow/deploy/' + id, method: 'POST', success: function(response) { if (response.code === 200) { showToast('部署成功!部署ID: ' + response.data.id, 'success'); } } });}七、流程部署完整流程
流程部署完整流程

7.1 绘制阶段
- 用户打开流程设计器
- 使用 bpmn-js 拖拽组件绘制流程图
- 配置各元素的属性(任务名称、处理人、监听器等)
7.2 保存阶段
- 点击"保存"按钮
- 前端将 BPMN XML 发送到后端
- 后端验证数据并保存到 MySQL
- 返回保存结果
7.3 部署阶段
- 用户选择要部署的流程
- 点击"部署"按钮
- 后端调用 Flowable 的 RepositoryService
- Flowable 引擎解析 BPMN XML
- 创建部署对象并存储到数据库
- 更新流程定义的部署状态
八、数据库设计
数据库设计
8.1 核心表结构
flow_definition 表
- 存储用户绘制的流程定义信息
- 包含 BPMN XML、版本号、部署状态等字段
Flowable 核心表(自动创建)
- ACT_RE_PROCDEF:流程定义表
- ACT_RE_DEPLOYMENT:部署表
- ACT_RU_EXECUTION:运行时执行实例表
- ACT_RU_TASK:运行时任务表
- ACT_HI_PROCINST:历史流程实例表
- ACT_HI_TASKINST:历史任务表
8.2 表关系说明
ACT_RE_DEPLOYMENT (1) ───→ ACT_RE_PROCDEF (N) 一个部署包含多个流程定义ACT_RE_PROCDEF (1) ───→ ACT_RU_EXECUTION (N) 一个流程定义可启动多个流程实例ACT_RU_EXECUTION (1) ───→ ACT_RU_TASK (N) 一个流程实例包含多个任务九、完整业务流程
完整业务流程
9.1 用户操作流程
- 登录系统:用户登录在线流程设计系统
- 创建流程:点击"新建流程"按钮
- 绘制流程图:使用 bpmn-js 设计器绘制 BPMN 2.0 流程图
- 配置属性:为各元素配置属性(名称、处理人等)
- 保存流程:将流程定义保存到 MySQL 数据库
9.2 设计师操作流程
- 绘制流程图:拖拽 BPMN 组件到画布
- 连接元素:使用顺序流连接各个元素
- 配置属性:设置任务名称、候选组、到期时间等
- 保存流程:将设计好的流程保存到数据库
9.3 系统处理流程
- 验证 XML:系统验证 BPMN XML 的格式和有效性
- 部署流程:将流程部署到 Flowable 引擎
- 存储数据:将流程数据持久化到数据库
十、实战案例
10.1 请假审批流程
下面以一个简单的请假审批流程为例,展示完整的实现过程。
流程需求:
- 员工提交请假申请
- 部门经理审批
- 如果请假天数 > 3 天,需要 HR 复审
- 审批通过后,通知员工
BPMN 设计:
<?xml version="1.0" encoding="UTF-8"?><bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"> <bpmn:process id="leaveRequest" name="请假审批流程" isExecutable="true"> <bpmn:startEvent id="start"/> <bpmn:userTask id="managerApproval" name="经理审批" flowable:assignee="${manager}"/> <bpmn:exclusiveGateway id="gateway"/> <bpmn:userTask id="hrApproval" name="HR审批" flowable:candidateGroups="hr"/> <bpmn:endEvent id="end"/> <!-- 流转 --> <bpmn:sequenceFlow sourceRef="start" targetRef="managerApproval"/> <bpmn:sequenceFlow sourceRef="managerApproval" targetRef="gateway"> <bpmn:conditionExpression xsi:type="tFormalExpression">${approved != null}</bpmn:conditionExpression> </bpmn:sequenceFlow> <bpmn:sequenceFlow sourceRef="gateway" targetRef="hrApproval"> <bpmn:conditionExpression xsi:type="tFormalExpression">${days > 3}</bpmn:conditionExpression> </bpmn:sequenceFlow> <bpmn:sequenceFlow sourceRef="gateway" targetRef="end"> <bpmn:conditionExpression xsi:type="tFormalExpression">${days <= 3}</bpmn:conditionExpression> </bpmn:sequenceFlow> <bpmn:sequenceFlow sourceRef="hrApproval" targetRef="end"/> </bpmn:process></bpmn:definitions>部署和使用:
// 启动流程实例ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( "leaveRequest", businessKey, variables);// 完成经理审批任务Task task = taskService.createTaskQuery() .processInstanceId(processInstance.getId()) .singleResult();taskService.complete(task.getId(), Collections.singletonMap("approved", true));十一、单元测试
11.1 测试服务层
使用 JUnit 5 和 Mockito 进行单元测试:
@ExtendWith(MockitoExtension.class)class FlowDesignServiceTest { @Mock private FlowDefinitionMapper flowDefinitionMapper; @Mock private RepositoryService repositoryService; @InjectMocks private FlowDesignService flowDesignService; @Test @DisplayName("测试保存流程定义") void testSaveFlowDefinition() { // Given FlowDefinition flow = new FlowDefinition(); flow.setProcessKey("test"); flow.setProcessName("测试流程"); flow.setBpmnXml(bpmnXml); // When Result<FlowDefinition> result = flowDesignService.saveFlowDefinition(flow); // Then assertTrue(result.isSuccess()); verify(flowDefinitionMapper).insert(any()); }}十二、部署和运行
12.1 编译打包
# 编译项目mvn clean compile# 运行测试mvn test# 打包(可选)mvn clean package12.2 启动应用
# 使用 Maven 启动mvn spring-boot:run# 或使用 java -jar 启动java -jar target/flowable_online-demo-1.0.0.jar12.3 访问系统
- 首页:http://localhost:8080
- 流程设计器:http://localhost:8080/designer
十三、总结
本文介绍了如何基于 Flowable 6.8.0 和 bpmn-js 构建一个在线流程图绘制系统。 该系统采用前后端分离架构,易于扩展和集成。