这里写目录标题
- 一、项目搭建
- 二、简单案例
-
- [1、安装Flowable BPMN visualizer插件](#1、安装Flowable BPMN visualizer插件)
- 2、画一个请假流程图
- 3、Java代码
- 3、模拟请假
一、项目搭建
1、项目结构

2、pom.xml文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.demo</groupId>
<artifactId>flowable</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>flowable-demo</name>
<description>flowable</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Flowable 工作流启动器(涵盖所有核心依赖) -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.8.0</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok(可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>cn.demo.flowabledemo.FlowableDemoApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3、application.yml文件
yml
server:
port: 8080 # 服务端口,可按需调整
spring:
datasource:
url: jdbc:mysql://192.168.247.155:3306/flowable?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: 123456 # 按你的实际密码填写
driver-class-name: com.mysql.cj.jdbc.Driver
flowable:
idm:
enabled: false # 禁用Flowable自带用户表,采用自定义用户体系
database-schema-update: true # 启动时自动建表
# 日志建议打开debug,方便排查
logging:
level:
org.flowable: debug
4、启动类
java
@SpringBootApplication
public class FlowableApplication {
public static void main(String[] args) {
SpringApplication.run(FlowableApplication.class, args);
}
}
项目启动后,会在数据库创建相关数据表,一个简单的Flowable项目就创建了。
二、简单案例
1、安装Flowable BPMN visualizer插件
在IDEA上(2024.1)上安装Flowable BPMN visualizer插件,方便画流程图。
2、画一个请假流程图

xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
<process id="ask_leave" name="ask_leave" isExecutable="true">
<startEvent id="sid-068031df-f9fc-4029-ac04-4a94e89923ec" name="请假开始">
<documentation>请假开始</documentation>
</startEvent>
<userTask id="sid-25d6222a-de01-4498-8acc-15138ee214dd" name="提交请假单" flowable:assignee="${leaveUser}">
<documentation>提交请假单</documentation>
</userTask>
<endEvent id="sid-522780b0-d1d7-4845-8efd-a9ba7783bbd3"/>
<userTask id="sid-99d107f9-4f21-4538-b9d6-f980fb5f4e5a" name="组长审核" flowable:assignee="${zuZhangUser}">
<documentation>组长审核</documentation>
</userTask>
<userTask id="sid-a84f88ed-eeb7-42d9-9589-a2cb1c75cbe7" name="经理审批" flowable:assignee="${managerUser}">
<documentation>经理审批</documentation>
</userTask>
<exclusiveGateway id="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed"/>
<sequenceFlow id="sid-c5e0dfe6-a547-4341-b7fa-14ba70850a5f" sourceRef="sid-068031df-f9fc-4029-ac04-4a94e89923ec" targetRef="sid-25d6222a-de01-4498-8acc-15138ee214dd"/>
<sequenceFlow id="sid-1161af13-d962-4a84-927f-de2d050414c9" sourceRef="sid-25d6222a-de01-4498-8acc-15138ee214dd" targetRef="sid-99d107f9-4f21-4538-b9d6-f980fb5f4e5a"/>
<sequenceFlow id="sid-550f0471-28b4-4c44-b09f-790a07c8522c" sourceRef="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb" targetRef="sid-a84f88ed-eeb7-42d9-9589-a2cb1c75cbe7" name="通过">
<conditionExpression xsi:type="tFormalExpression">${zuZhangCheckResult == '通过'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-8599e406-1f17-4ff8-ba74-582bb56afedb" sourceRef="sid-a84f88ed-eeb7-42d9-9589-a2cb1c75cbe7" targetRef="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed">
<conditionExpression/>
</sequenceFlow>
<sequenceFlow id="sid-f07e14ad-767c-4de1-942c-f4384a2273f7" sourceRef="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed" targetRef="sid-522780b0-d1d7-4845-8efd-a9ba7783bbd3" name="通过">
<conditionExpression xsi:type="tFormalExpression">${managerCheckResult == '通过' }</conditionExpression>
</sequenceFlow>
<endEvent id="sid-8297b4c8-a54d-49f8-a05a-7c24455cbaa6"/>
<sequenceFlow id="sid-f2ccaabb-ee9d-48b4-9a23-3102abc4178b" sourceRef="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed" targetRef="sid-8297b4c8-a54d-49f8-a05a-7c24455cbaa6">
<conditionExpression xsi:type="tFormalExpression"/>
</sequenceFlow>
<exclusiveGateway id="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb"/>
<sequenceFlow id="sid-1ba69f13-83bc-4dd9-a9e2-8ee99a430d59" sourceRef="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb" targetRef="sid-8297b4c8-a54d-49f8-a05a-7c24455cbaa6">
<conditionExpression xsi:type="tFormalExpression"/>
</sequenceFlow>
<sequenceFlow id="sid-82a1dce0-ff59-498b-93bc-a11329c20f2b" sourceRef="sid-99d107f9-4f21-4538-b9d6-f980fb5f4e5a" targetRef="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_ask_leave">
<bpmndi:BPMNPlane bpmnElement="ask_leave" id="BPMNPlane_ask_leave">
<bpmndi:BPMNShape id="shape-bff7bf69-eaab-4ee5-b96a-593610d46c8f" bpmnElement="sid-068031df-f9fc-4029-ac04-4a94e89923ec">
<omgdc:Bounds x="-175.0" y="-70.0" width="30.0" height="30.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape-c5235275-7bf6-4e7a-b841-7eb44fcb0643" bpmnElement="sid-25d6222a-de01-4498-8acc-15138ee214dd">
<omgdc:Bounds x="-95.0" y="-95.0" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape-afe63443-5804-417d-8208-b46a5c315c98" bpmnElement="sid-522780b0-d1d7-4845-8efd-a9ba7783bbd3">
<omgdc:Bounds x="525.0" y="-70.0" width="30.0" height="30.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape-3e8b5cef-d42e-4a87-8c40-ad73fe98fae1" bpmnElement="sid-99d107f9-4f21-4538-b9d6-f980fb5f4e5a">
<omgdc:Bounds x="55.0" y="-95.0" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape-f3c92d16-9acd-4309-a5d1-2986ee47220c" bpmnElement="sid-a84f88ed-eeb7-42d9-9589-a2cb1c75cbe7">
<omgdc:Bounds x="285.0" y="-95.0" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="shape-c3d984d0-6771-4284-b7b5-757feb1a01b4" bpmnElement="sid-360aeb6a-a383-411b-be0c-1c5da935f1ed">
<omgdc:Bounds x="435.0" y="-75.0" width="40.0" height="40.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-1bf8a64e-79aa-4dcb-ae95-48b724fba5c7" bpmnElement="sid-c5e0dfe6-a547-4341-b7fa-14ba70850a5f">
<omgdi:waypoint x="-145.0" y="-55.0"/>
<omgdi:waypoint x="-95.0" y="-55.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-d048d099-592a-402e-9462-8456d456c401" bpmnElement="sid-1161af13-d962-4a84-927f-de2d050414c9">
<omgdi:waypoint x="5.0" y="-55.0"/>
<omgdi:waypoint x="55.0" y="-55.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-29c2de16-b663-4c20-ac2d-51b9ebb704a5" bpmnElement="sid-550f0471-28b4-4c44-b09f-790a07c8522c">
<omgdi:waypoint x="229.99998" y="-55.0"/>
<omgdi:waypoint x="285.0" y="-55.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-24766565-b1cd-4134-a2a7-01ab94c3f082" bpmnElement="sid-8599e406-1f17-4ff8-ba74-582bb56afedb">
<omgdi:waypoint x="385.0" y="-55.0"/>
<omgdi:waypoint x="435.0" y="-55.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-645307b8-39f9-43a0-bed8-c97a9f719c77" bpmnElement="sid-f07e14ad-767c-4de1-942c-f4384a2273f7">
<omgdi:waypoint x="475.0" y="-55.0"/>
<omgdi:waypoint x="525.0" y="-55.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-5779484f-f4da-43d2-ab2e-af76be09ee22" bpmnElement="sid-8297b4c8-a54d-49f8-a05a-7c24455cbaa6">
<omgdc:Bounds x="200.00002" y="50.0" width="30.0" height="30.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-c365dafb-0004-4362-9fb4-17a3a19b39e2" bpmnElement="sid-f2ccaabb-ee9d-48b4-9a23-3102abc4178b">
<omgdi:waypoint x="455.0" y="-35.0"/>
<omgdi:waypoint x="455.0" y="57.5"/>
<omgdi:waypoint x="230.00002" y="57.5"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-a28f8193-b421-45cf-87fd-ba93be65fe9d" bpmnElement="sid-37e919a2-399d-4e93-b90e-cb45c63e2ecb">
<omgdc:Bounds x="190.0" y="-75.0" width="40.0" height="40.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-37d4fbfc-f252-4e26-868e-2e8458fe9434" bpmnElement="sid-1ba69f13-83bc-4dd9-a9e2-8ee99a430d59">
<omgdi:waypoint x="210.0" y="-35.0"/>
<omgdi:waypoint x="207.50002" y="50.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-da617bee-5aef-408d-87c6-364dce76dc83" bpmnElement="sid-82a1dce0-ff59-498b-93bc-a11329c20f2b">
<omgdi:waypoint x="155.0" y="-55.0"/>
<omgdi:waypoint x="190.0" y="-55.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
3、Java代码
java
@RestController
public class AskForLeaveController {
@Autowired
RuntimeService runtimeService;
@Autowired
RepositoryService repositoryService;
@Autowired
TaskService taskService;
@Autowired
ProcessEngine processEngine;
/**
* 重新部署流程定义(清除旧版本)
*/
@GetMapping("/redeploy")
public String redeploy() {
// 删除旧的流程定义部署
List<org.flowable.engine.repository.Deployment> deployments = repositoryService.createDeploymentQuery()
.processDefinitionKey("ask_leave")
.list();
for (org.flowable.engine.repository.Deployment deployment : deployments) {
repositoryService.deleteDeployment(deployment.getId(), true);
}
// 重新部署新的流程定义
repositoryService.createDeployment()
.name("请假流程重新部署")
.addClasspathResource("processes/ask_leave.bpmn20.xml")
.deploy();
return "流程重新部署完成,共删除 " + deployments.size() + " 个旧部署";
}
/**
* 查看流程进度图
* @param resp
* @param processId 流程id
* @throws Exception
*/
@GetMapping("/viewFlowChart")
public void viewFlowChart(HttpServletResponse resp, String processId) throws Exception {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
if (pi == null) {
return;
}
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(processId)
.list();
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (Execution exe : executions) {
List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
activityIds.addAll(ids);
}
// 生成流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, false);
OutputStream out = null;
byte[] buf = new byte[1024];
int legth = 0;
try {
out = resp.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
/**
* 开启一个请假流程
*/
@GetMapping("/askForLeave")
public String askForLeave() {
Map<String, Object> map = new HashMap<>();
map.put("leaveUser", "张三");
map.put("name", "ego");
map.put("reason", "出去玩");
map.put("days", 10);
ProcessInstance instance = runtimeService.startProcessInstanceByKey("ask_leave", map);
return "开启请假流程 processId:" + instance.getId();
}
/**
* 员工请假提交给组长审核
*/
@GetMapping("/submitToZuZhang")
public String submitToZuZhang() {
// 员工查找到自己的任务,然后提交给组长审批
List<Task> list = taskService.createTaskQuery().taskAssignee("张三").orderByTaskId().desc().list();
String info = null;
for (Task task: list) {
info = "任务 ID:" + task.getId() + ",任务申请人:" + task.getAssignee() + ",任务是否挂起:" + task.isSuspended();
//提交给组长的时候,需要指定组长的id
Map<String, Object> map = new HashMap<>();
map.put("zuZhangUser", "李四");
taskService.complete(task.getId(), map);
}
return info;
}
/**
* 组长审核请假
*/
@GetMapping("/zuZhangApprove")
public String zuZhangApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee("李四").orderByTaskId().desc().list();
String info = null;
for (Task task: list) {
if ("组长审核".equals(task.getName())) {
info = "任务 ID:" + task.getId() + ",组长审核人:" + task.getAssignee() + ",任务是否挂起:" + task.isSuspended();
// 提交给经理的时候,需要指定经理的id
Map<String, Object> map = new HashMap<>();
map.put("managerUser", "王五");
map.put("zuZhangCheckResult", "通过");
map.put("zuZhangUser", "李四");
try {
taskService.complete(task.getId(), map);
} catch (Exception e) {
e.printStackTrace();
info = "组长审核失败:" + task.getId() + " " + task.getAssignee();
}
}
}
return info;
}
/**
* 经理审核通过
*/
@GetMapping("/managerApprove")
public String managerApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee("王五").orderByTaskId().desc().list();
String info = null;
for (Task task: list) {
info = "经理:" + task.getAssignee() + ",审核通过:" + task.getId() + " 任务";
Map<String, Object> map = new HashMap<>();
map.put("managerCheckResult", "通过");
taskService.complete(task.getId(), map);
}
return info;
}
/**
* 经理审核拒绝
*/
@GetMapping("/managerRefuse")
public String managerRefuse() {
List<Task> list = taskService.createTaskQuery().taskAssignee("王五").orderByTaskId().desc().list();
String info = null;
for (Task task: list) {
info = "经理:" + task.getAssignee() + ",审核拒绝:" + task.getId() + " 任务";
Map<String, Object> map = new HashMap<>();
map.put("managerCheckResult", "拒绝");
taskService.complete(task.getId(), map);
}
return info;
}
}
3、模拟请假
3.1、重新部署流程
url
http://localhost:8080/redeploy

3.2、发起请假流程
url
http://localhost:8080/askForLeave

3.3、查看流程图
url
http://localhost:8080/viewFlowChart?processId=da61bee1-fb5b-11f0-8447-005056c00008

3.4、提交组长审批
url
http://localhost:8080/submitToZuZhang

3.5、组长审批
url
http://localhost:8080/zuZhangApprove

3.6、经理审批
url
http://localhost:8080/managerApprove
