1 什么是工作流
工作流(Workflow)是一种自动化处理业务过程的方法,用于定义、执行和管理业务任务的顺序。
1.1 常见的工作流引擎
-
Activiti:
-
一个流行的开源工作流引擎,支持 BPMN 2.0 标准,适用于企业级应用。
-
Flowable:
-
一个开源的工作流引擎,支持 BPMN 2.0 标准,广泛用于 Java 应用。Flowable 是 Activiti 的一个分支,后来发展成为独立的项目,它在很多方面进行了改进和优化。
-
Camunda:
-
一个开源的业务流程管理平台,支持 BPMN 2.0 和 CMMN 标准,提供强大的社区支持和商业版本。
-
jBPM:
-
一个开源的业务流程管理框架,支持 BPMN 2.0 和其他标准,适用于复杂的业务流程。
1.2 应用场景
工作流广泛应用于各种业务领域,包括但不限于:
-
业务审批:如请假申请、费用报销、合同审批等。
-
生产制造:如订单处理、生产调度、质量控制等。
-
客户服务:如客户投诉处理、服务请求跟踪等。
-
项目管理:如项目计划、任务分配、进度跟踪等。
-
供应链管理:如采购管理、库存管理、物流配送等。
2 flowable-ui
flowable-ui
是理 Flowable 项目的一部分,提供了一套基于 Web 的用户界面,用于管理和监控工作流。它包括多个模块,每个模块负责不同的功能,如任务管、模型设计、管理控制台等。
我们用它主要就是用它的ui界面,绘画流程图,生成xml文件。
2.1 添加依赖
<dependencies>
<!--Springboot项目自带 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Springboot Web项目 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- idm依赖提供身份认证 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-idm</artifactId>
<version>6.7.1</version>
</dependency>
<!-- modeler绘制流程图 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
<version>6.7.1</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.3.0</version>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql驱动版本必须指定为8.0.18,不然报错。 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!-- flowable -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.1</version>
</dependency>
</dependencies>
2.2 配置文件
server:
port: 8088
flowable:
database-schema-update: true # true表示每次启动都会重新创建表,false表示使用已经存在的表
async-executor-activate: false # true表示启动时启动定时任务,false表示不启动
idm:
app:
admin:
# 登录的用户名
user-id: admin
# 登录的密码
password: admin
# 用户的名字
first-name: zhang
last-name: san
spring:
# mysql连接信息
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flowable?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT&nullCatalogMeansCurrent=true
username: root
password: root
liquibase:
enabled: false
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
open-in-view: true
2.3 数据库建库
在数据库建立指定的数据库flowable即可,当第一次启动时,会自动创建flowable所需要的表,大概79个左右。
2.4 启动启动类
启动类第一次启动会时间比较长,因为在创建表。
启动完成后,在本地浏览器输入http://localhost:8088/即可访问ui界面
密码就是配置文件里面的账号密码 admin
2.5 使用ui创建库存审批图
2.5.1 创建流程
2.5.2 创建模型
记住这个key,后面要用到
2.5.3 绘画流程图
这里我直接画了,具体怎么画的可以参考这个文章
https://blog.csdn.net/zhipengfang/article/details/134690012?spm=1001.2014.3001.5506
主要就是实现了对采购单是审批流程。
2.6 导出xml文件
库存审批流程.bpm
3 flowable
使用flowable模拟库存审批流程
3.1 新建项目 导入依赖
<dependencies>
<!--Springboot项目自带 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Springboot Web项目 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!-- flowable -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
3.2 配置文件
server:
port: 8081
spring:
# mysql连接信息
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flowable?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT&nullCatalogMeansCurrent=true
username: root
password: root
flowable:
database-schema-update: true
async-executor-activate: false
process-definition-location-prefix: classpath:/processes/ # 流程定义文件路径
logging:
level:
com:
by: debug
root: error
3.3 导入流程文件
把刚刚下载的xml文件放到resource目录下的processes(没有自己创建一个)里面,一定要和配置文件里面指定目录一致
这里我们可以下一个插件 Flowable BPMN visualizer 有了他就不需要ui界面一直打开了
使用也很简单,右键xml文件,选择打开即可
3.4 Controller
这里我写了一个controller模拟库存审核流程
@RestController
@RequestMapping("/api/inventory")
public class InventoryController {
// RuntimeService用于启动流程定义的新流程实例。
@Autowired
private RuntimeService runtimeService;
// TaskService用于查询和完成任务。
@Autowired
private TaskService taskService;
// RepositoryService用于查询流程定义。
@Autowired
private RepositoryService repositoryService;
// ProcessEngine用于获取RepositoryService、RuntimeService、TaskService等
@Autowired
private ProcessEngine processEngine;
// HistoryService用于查询历史数据。
// 库存申请流程的id
private final static String inventoryId = "inventoryApproval";
/**
* 添加库存申请
* @param inventory 库存申请
* @return 流程id
*/
@PostMapping("/add")
public String add(@RequestBody Inventory inventory) {
Map<String, Object> map = new HashMap<>();
map.put("requester", inventory.getRequester());
map.put("qty", inventory.getQty());
map.put("manager", "经理");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(inventoryId, map);
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
// 判断库存数量是否小于1000
if (inventory.getQty()<=1000){
// 直接通过 无需审批
taskService.complete(task.getId());
、}
System.out.println("流程启动成功,流程Id: " + processInstance.getId());
return "提交成功.流程Id为:" + processInstance.getId();
}
/**
* 获取任务列表
* @param user 用户名(可以是 requester 或 manager)
* @return 任务列表
*/
@GetMapping("/list")
public String list(String user) {
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee(user)
.active() // 只查询未完成的任务
.orderByTaskCreateTime()
.desc()
.list();
-
System.out.println("查询任务列表,用户: " + user + ", 任务数量: " + tasks.size());
for (Task task : tasks) {
System.out.println(task.toString());
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.singleResult();
System.out.println("流程实例状态: " + (processInstance != null ? "活跃" : "已结束"));
}
return tasks.toString();
}
/**
* 审批通过
* @param taskId 任务id
* @return 审批结果
*/
@GetMapping("/apply")
public String apply(String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new RuntimeException("任务不存在");
}
// 通过审核
HashMap<String, Object> map = new HashMap<>();
map.put("outcome", "通过");
taskService.complete(taskId, map);
return "processed ok!";
}
/**
* 驳回
* @param taskId 任务id
* @return 驳回结果
*/
@GetMapping("/reject")
public String reject(String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new RuntimeException("任务不存在");
}
HashMap<String, Object> map = new HashMap<>();
map.put("outcome", "驳回");
taskService.complete(taskId, map);
return "reject";
}
/**
* 生成流程图
* @param httpServletResponse 响应对象
* @param processId 流程id
* @throws Exception 异常
*/
@GetMapping("/genProcessDiagram")
public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
// 流程走完的不显示图
if (pi == null) {
httpServletResponse.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
// 使用流程实例ID,查询正在执行的任务
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
if (task == null) {
httpServletResponse.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
// 使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
String instanceId = task.getProcessInstanceId();
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(instanceId)
.list();
// 得到正在执行的Activity的Id
List<String> activityIds = new ArrayList<>();
for (Execution exe : executions) {
List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
activityIds.addAll(ids);
}
// 获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessDiagramGenerator diagramGenerator = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(
bpmnModel,
"png",
activityIds,
new ArrayList<>(), // flows
1.0, // 缩放比例
true // 是否水平布局
);
httpServletResponse.setContentType("image/png");
OutputStream out = null;
byte[] buf = new byte[1024];
int length = 0;
try {
out = httpServletResponse.getOutputStream();
while ((length = in.read(buf)) != -1) {
out.write(buf, 0, length);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
3.5 演示结果
注意:流程id和任务id不是同一个
3.5.1 添加任务
3.5.2 获取任务列表
因为qty小于1000的任务直接完成 不需要审批 所以列表只有一个任务
3.5.3 审核通过
填入任务id
通过后再查询任务列表就没有了 因为已经审核通过了
3.5.4 审核驳回
再次查询也是没有
3.6 获取流程图(乱码未解决)
会显示现在已经到那个步骤了 会高亮显示。
3.7 测试查看全流程
3.7.1 qty大于1000
3.7.2 小于1000
3.7.3 具体测试代码
@SpringBootTest
@Slf4j
class InventoryTests {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@Test
void Test() {
// 发送库存请求
// 收集的数据作为一个Map实例传递,其中的键就是之后用于获取变量的标识符
Map<String, Object> map = new HashMap<>();
map.put("requester", "小明");
map.put("qty", 500);
map.put("manager", "经理");
// runtimeService根据xml中流程的id启动流程实例
ProcessInstance inventoryManagement = runtimeService.startProcessInstanceByKey("inventoryApproval", map);
// 获取任务 该查询基于刚刚创建的流程实例ID来查找相关的任务。singleResult方法返回查询结果中的第一个任务(
Task task = taskService.createTaskQuery().processInstanceId(inventoryManagement.getId()).singleResult();
// 完成任务
taskService.complete(task.getId());
// 库存审批
List<Task> principalTaskList = taskService.createTaskQuery().taskAssignee("经理").list();
Map<String, Object> principalMap = new HashMap<>();
principalMap.put("outcome", "通过");
for (Task principalTask : principalTaskList) {
taskService.complete(principalTask.getId(), principalMap);
}
// 查看历史
List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(inventoryManagement.getId())
.finished()
.orderByHistoricActivityInstanceEndTime().asc() // 按照活动实例结束时间正序排列
.list();
for (HistoricActivityInstance activity : activities) {
System.out.println(activity.getActivityName());
}
}
}