一、Flowable简介
Flowable 是一个轻量级、开源的业务流程管理(BPM)和工作流引擎,旨在帮助开发者和企业实现业务流程的自动化。它支持 BPMN 2.0 标准,适用于各种规模的企业和项目。Flowable 的核心功能包括流程定义、流程执行、任务管理、历史记录查询等,广泛应用于企业级应用中。
官网:https://www.flowable.com/open-source/docs/
二、Spring Boot3整合
环境:JDK21、Spring Boot3.4.1、Flowable7.1.0
1.引入依赖
<!-- Flowable启动引擎 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>7.1.0</version>
</dependency>
2.yml配置
注意本文数据源、数据库是MySQL和Druid,各位码友请根据实际情况调整
spring:
#配置数据源
datasource:
#MySQL
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/foleable?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
flowable:
# 开启定时任务JOB
async-executor-activate: true
# 在引擎启动时,会自动更新数据库架构
database-schema-update: true
3.线程池配置
1.提供一个全局的默认线程池
2.提供一个folwable依赖的线程池,注意Bean的名称一定要是applicationTaskExecutor
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 统一线程池管理
*
* @author xie
**/
@Configuration
public class ThreadPoolTaskConfig {
@Bean("applicationTaskExecutor")
public ThreadPoolTaskExecutor applicationTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//此方法返回可用处理器的虚拟机的最大数量; 不小于1
int core = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(core);//设置核心线程数
executor.setMaxPoolSize(core * 2 + 1);//设置最大线程数
executor.setKeepAliveSeconds(120);//除核心线程外的线程存活时间
executor.setQueueCapacity(120);//如果传入值大于0,底层队列使用的是LinkedBlockingQueue,否则默认使用SynchronousQueue
executor.setThreadNamePrefix("thread-default-execute");//线程名称前缀
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//设置拒绝策略,抛出 RejectedExecutionException来拒绝新任务的处理。
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//设置拒绝策略,使用主线程
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());//设置拒绝策略,直接丢弃掉
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());//设置拒绝策略,丢弃最早的未处理的任务请求。
return executor;
}
}
不配置线程池可能出现以下错误
三、流程定义
官方流程设计器
docker run -p 9096:8080 -d --name flow flowable/flowable-ui:6.8.0
访问IP加启动容器的端口
默认账户/密码:admin/test
进入设计器
创建流程并设计
创建测试流程
这里先简单画个流程 后期写个详细流程图的绘画
确定后进入设计界面
添加用活动后设置名称
点击用户任务设置用户任务的执行人
这里先简单设置一个固定的人,分配用户ID为:user1
再重复添加一个用户任务,添加一下结束事件
最后就依次连线就行了,选中开始节点然后连接到用户一
最后的效果图
点击保存
选择查看刚刚创建的流程
导出XML
四、部署流程
1.把导出的xml放入我们工程的resources目录
2.部署流程(这里先把我们后面用到的查询服务都注入)
@Resource
private RepositoryService repositoryService;
@Resource
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Resource
private HistoryService historyService;
@Resource
IdentityService identityService;
/**
* 初始化流程
*
* @method: initFlow
* @return:
* @author: xie
* @date: 2024/12/24 上午9:26
**/
@GetMapping("initFlow")
@Transactional(rollbackFor = Exception.class)
public void initFlow() {
// 获取bpmn文件夹的所有.bpmn20.xml文件
ClassPathResource bpmnFolder = new ClassPathResource("bpmn/");
var files = bpmnFolder.getFile().listFiles((dir, name) -> name.endsWith(".bpmn20.xml"));
if (files != null && files.length > 0) {
// 创建部署对象
var deploymentBuilder = repositoryService.createDeployment();
for (var file : files) {
// 添加BPMN文件到部署
deploymentBuilder.addInputStream(file.getName(), file.toURI().toURL().openStream());
}
// 执行部署
Deployment deployment = deploymentBuilder.deploy();
}
}
五、查询部署的全部流程
当然这里也可以进行分页查询已经写在下面注释了 这里为了测试 我就简单写了
/***
* 查询所有的流程实例
* @method: queryAllDeployedProcesses
* @return:
* @author: xie
* @date: 2024/12/20 下午1:12
**/
@GetMapping("/queryAllDeployedProcesses")
public List<JSONObject> queryAllDeployedProcesses() {
List<JSONObject> jsonObjects = new ArrayList<>();
// 查询所有流程定义
List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()
.orderByProcessDefinitionKey().asc() // 按流程定义的 Key 排序
.latestVersion() // 只查询每个流程定义的最新版本
.list();
// 打印所有已部署的流程的 key 和 name
for (ProcessDefinition processDefinition : processDefinitions) {
System.out.println("Process ID: " + processDefinition.getId());
System.out.println("Process Key: " + processDefinition.getKey());
System.out.println("Process Name: " + processDefinition.getName());
System.out.println("Process Version: " + processDefinition.getVersion());
JSONObject object = new JSONObject();
object.put("id", processDefinition.getId());
object.put("key", processDefinition.getKey());
object.put("name", processDefinition.getName());
object.put("version", processDefinition.getVersion());
jsonObjects.add(object);
}
//分页查询
// 创建查询对象
// ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery()
// .latestVersion() // 只查询最新版本的流程定义
// .orderByProcessDefinitionKey().asc(); // 按流程定义的 Key 升序排序
//
// // 获取总条数
// long totalCount = query.count();
//
// // 分页查询流程定义
// List<ProcessDefinition> processDefinitions = query.listPage((pageNum - 1) * pageSize, pageSize);
return jsonObjects;
}
看看界面的效果吧,这个简单的前端页面展示方便查询
六、发起审批
/**
* 是否被拒绝标记
*/
public static final String REFUSEFLAG = "refuseFlag";
/**
* 发起流程
*
* @param key : 流程信息
* @method: startFlow
* @return:
* @author: xie
* @date: 2024/12/20 下午1:43
**/
@GetMapping("/startFlow")
@Transactional(rollbackFor = Exception.class)
public String startFlow(@RequestParam("key") String key) {
Map<String, Object> map = Map.of(
"businessType", "业务类型(业务审批、请假、出差等)",
"day", 1,
REFUSEFLAG, false
);
//发起人用户ID
String userId = SysConstan.USER_ID;
//订单号
String businessKey = "PO00001";
//参数一 流程key
//参数二 页面单据类型(比如请假流水号、订单号等)
//参数三 运行时变量信息比如请假天数
//设置发起人
identityService.setAuthenticatedUserId(userId);
//启动流程变量
ProcessInstance processInstanceByKey = runtimeService.startProcessInstanceByKey(key, businessKey, map);
log.info("流程实例id-{}", processInstanceByKey.getId());
// 清除当前用户
identityService.setAuthenticatedUserId(null);
return processInstanceByKey.getId();
}
注意:
参数key:指的是我们上一步部署流程后得到的key也就是页面查询的key
map对象是设置的变量集,比如我们请假的时候可能要根据不同的天数走不同的分支,这里的变量集后期可以自己封装一个对象指定一些必须传入的变量信息
拒绝标记:由于Flowable并不会记录流程是否是被拒绝而结束的流程实例
userId:Flowable默认是无状态的可以通过identityService临时设置发起人
businessKey:业务订单号,可以把它看做是审批的单据号后面审批通过了需要通知业务系统的时候需要用到
七、查询所有发起的流程及展示状态
这里包含了:我的发起、我参与审批的流程、根据业务单号查询审批流程等查询功能
/**
* 查询所有流程实例
*
* @method: queryAllprocess
* @return:
* @author: xie
* @date: 2024/12/20 下午3:35
**/
@GetMapping("queryAllprocess")
public List<JSONObject> queryAllprocess() {
// 获取所有活跃的流程实例(包括已结束的)
List<HistoricProcessInstance> allProcessInstances = historyService.createHistoricProcessInstanceQuery()
//查询用户参与过的流程实例
//.involvedUser("1")
//查询发起人用户
//.startedBy(SysConstan.USER_ID)
//根据变量查询(自己设的变量)
//.variableValueEquals("businessType", "查询的业务类型")
//根据订单号模糊查询
//.processInstanceBusinessKeyLikeIgnoreCase("orderCode")
.orderByProcessInstanceStartTime().asc() // 可以按ID排序,便于调试
.list(); // 查询所有的流程实例,包括历史的和活跃的
List<JSONObject> jsonObjects = new ArrayList<>();
for (HistoricProcessInstance processInstance : allProcessInstances) {
String processInstanceId = processInstance.getId();
JSONObject json = new JSONObject();
if (processInstance.getEndTime() == null) {
json.put("status", "审批中");
} else {
json.put("status", "审批完成");
}
json.put("id", processInstance.getProcessDefinitionId());
json.put("processInstanceId", processInstanceId);
json.put("startUser", processInstance.getStartUserId());
json.put("key", processInstance.getProcessDefinitionKey());
json.put("businessKey", processInstance.getBusinessKey());
json.put("name", processInstance.getProcessDefinitionName());
json.put("deleteReason", processInstance.getDeleteReason());
json.put("startTime", DateUtil.format(processInstance.getStartTime(), "yyyy-MM-dd HH:mm:ss"));
json.put("endTime", processInstance.getEndTime() != null ? DateUtil.format(processInstance.getEndTime(), "yyyy-MM-dd HH:mm:ss") : "");
// 获取与任务相关的所有变量
// 获取该流程实例的变量
List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.list();
if(CollUtil.isNotEmpty(variables)){
for (HistoricVariableInstance variable : variables) {
json.put(variable.getVariableName(), variable.getValue());
if (REFUSEFLAG.equals(variable.getVariableName()) && variable.getValue() != null && variable.getValue().toString().equalsIgnoreCase("true")) {
json.put("status", "审批驳回");
}
}
}
jsonObjects.add(json);
}
return jsonObjects;
}
这里就用到了发起流程时是否驳回这个变量,判断当前业务单据是否被驳回
查询流程时可以有多种条件查询比如查询我发起的审批流程上面代码已经列出查询条件
对应的页面简单展示,发起后进入到用户一审批
八、我的代办任务
这里注释了用户ID条件查询,实际业务系统应该是要设置当前用户的ID查询
/**
* 获取代办列表 (这里暂时查看所有的)
*
* @method: getTasks
* @return:
* @author: xie
* @date: 2024/12/20 下午1:44
**/
@GetMapping("/allTasks")
public List<JSONObject> getTasks() {
List<Task> taskList = taskService
.createTaskQuery()
//查询业务类型为指定的任务
//.processInstanceBusinessKey("LEAVE")
//查询所有zhangsan用户代办的任务
//.taskAssignee(SysConstan.USER_ID)
.list();
List<JSONObject> jsonObjects = new ArrayList<>();
for (Task task : taskList) {
JSONObject json = new JSONObject();
json.put("id", task.getId());
json.put("name", task.getName());
json.put("user", task.getAssignee());
json.put("processDefinitionId", task.getProcessDefinitionId());
json.put("processInstanceId", task.getProcessInstanceId());
// 获取与任务相关的所有变量
Map<String, Object> taskVariables = taskService.getVariables(task.getId());
// 打印出任务的变量
json.putAll(taskVariables);
jsonObjects.add(json);
}
return jsonObjects;
}
九、完成任务
这里的任务ID为上面我的代办任务接口查询出来Task的ID
/**
* 完成任务
*
* @method: testComplete
* @return:
* @author: xie
* @date: 2024/12/20 下午1:44
**/
@GetMapping("/testComplete")
@Transactional(rollbackFor = Exception.class)
public boolean testComplete(@RequestParam("id") String taskId) { // 获取任务对应的流程实例ID
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 检查任务是否为空
if (task == null) {
System.out.println("任务不存在");
return false;
}
String processInstanceId = task.getProcessInstanceId();
String orderCode = (String) runtimeService.getVariable(processInstanceId, "orderCode");
log.info("业务单据:{}", orderCode);
// 1. 设置新执行者
//taskService.setAssignee(taskId, newAssigneeId); // 任务的执行者被替换为新的用户
taskService.addComment(taskId, processInstanceId, "备注信息test");
// 完成任务
taskService.complete(taskId);
// 查询当前流程实例的所有活动任务
boolean isFinish = processInstanceFinished(processInstanceId);
log.debug("流程是否完成L:{}", isFinish);
return isFinish;
}
/**
* 校验当前流程是否结束了
* @method: isProcessInstanceFinished
* @param processInstanceId :
* @return:
* @author: xie
* @date: 2024/12/23 下午1:57
**/
public boolean processInstanceFinished(String processInstanceId) {
// 获取当前流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
// 如果 processInstance 为空,表示该流程实例已结束
return processInstance == null;
}
十、驳回任务
processInstanceId指的是发起流程的实例ID
/**
* 驳回流程
* @method: stopFlow
* @param processInstanceId :
* @return:
* @author: xie
* @date: 2024/12/24 下午4:38
**/
@GetMapping("stopFlow")
@Transactional(rollbackFor = Exception.class)
public void stopFlow(@RequestParam("id") String processInstanceId) {
// 在删除前设置拒绝变量
runtimeService.setVariable(processInstanceId, "refuseFlag", true);
//拒绝 后一个参数是拒绝的原因
runtimeService.deleteProcessInstance(processInstanceId, "驳回任务备注原因");
}
十一、 完成效果展示
1.BPMN-JS插件渲染的连接线不起效果
2.Flowable引擎自带构建的图片没有经过的节点