腾讯围棋2026
76.33MB · 2026-02-04
在企业级应用开发中,工作流引擎是不可或缺的组件。Flowable作为一个轻量级、开源的工作流和业务流程管理(BPM)平台,被广泛应用于各类业务系统中。然而,传统的流程设计方式需要借助专门的建模工具(如Flowable Modeler、Camunda Modeler等),操作复杂且不够直观。
本文将介绍如何基于 Flowable 6.8.0 和 bpmn-js 构建一个在线流程图绘制系统,实现:
bpmn-js 是一个由 Camunda 开发的 BPMN 2.0 建模工具库,纯 JavaScript 实现,可以直接在浏览器中运行。它提供了:
| 特性 | bpmn-js | Flowable Modeler | Eclipse Plugin |
|---|---|---|---|
| 安装方式 | 无需安装 | 需要部署 | 需要安装 |
| 使用环境 | 浏览器 | 独立应用 | Eclipse IDE |
| 协作支持 | 易于集成 | 困难 | 困难 |
| 定制能力 | 高 | 中 | 低 |
| 学习曲线 | 平缓 | 陡峭 | 陡峭 |
本系统采用前后端分离的架构设计,主要包括以下层次:
表现层(Presentation Layer)
业务服务层(Business Service Layer)
流程引擎层(Process Engine Layer)
数据存储层(Data Storage Layer)
| 软件 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8+ | Java 开发环境 |
| Maven | 3.6+ | 项目构建工具 |
| MySQL | 5.7+ 或 8.0+ | 关系数据库 |
| Node.js | 14+ | 前端开发(可选) |
后端技术栈
前端技术栈
使用 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>
创建数据库和流程定义表:
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)
);
创建流程定义实体类:
@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;
}
使用 MyBatis Plus 的 Mapper 接口:
@Mapper
public interface FlowDefinitionMapper extends BaseMapper<FlowDefinition> {
List<FlowDefinition> selectByProcessKey(@Param("processKey") String processKey);
List<FlowDefinition> selectByCreatedBy(@Param("createdBy") String createdBy);
}
流程设计核心服务:
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
@Service
public 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();
}
}
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);
}
}
在 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"/>
// 创建 BPMN 建模器实例
const bpmnModeler = new BpmnJS({
container: '#canvas',
height: '100%',
keyboard: { bindTo: window }
});
// 导入默认的 BPMN 模板
bpmnModeler.importXML(defaultBpmnTemplate).then(() => {
bpmnModeler.get('canvas').zoom('fit-viewport');
});
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');
}
}
});
});
}
function deployFlowDefinition(id) {
$.ajax({
url: '/api/flow/deploy/' + id,
method: 'POST',
success: function(response) {
if (response.code === 200) {
showToast('部署成功!部署ID: ' + response.data.id, 'success');
}
}
});
}
flow_definition 表
Flowable 核心表(自动创建)
ACT_RE_PROCDEF:流程定义表ACT_RE_DEPLOYMENT:部署表ACT_RU_EXECUTION:运行时执行实例表ACT_RU_TASK:运行时任务表ACT_HI_PROCINST:历史流程实例表ACT_HI_TASKINST:历史任务表ACT_RE_DEPLOYMENT (1) ───→ ACT_RE_PROCDEF (N)
一个部署包含多个流程定义
ACT_RE_PROCDEF (1) ───→ ACT_RU_EXECUTION (N)
一个流程定义可启动多个流程实例
ACT_RU_EXECUTION (1) ───→ ACT_RU_TASK (N)
一个流程实例包含多个任务
下面以一个简单的请假审批流程为例,展示完整的实现过程。
流程需求:
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));
使用 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());
}
}
# 编译项目
mvn clean compile
# 运行测试
mvn test
# 打包(可选)
mvn clean package
# 使用 Maven 启动
mvn spring-boot:run
# 或使用 java -jar 启动
java -jar target/flowable_online-demo-1.0.0.jar
本文介绍了如何基于 Flowable 6.8.0 和 bpmn-js 构建一个在线流程图绘制系统。 该系统采用前后端分离架构,易于扩展和集成。