SpringBoot 3.x 集成 Flowable 7.x:流程设计、部署、启动及完成实战
一、Flowable 简介 Flowable 是一个轻量级、开源的业务流程管理(BPM)和工作流引擎,旨在帮助开发者和企业实现业务流程的自动化。它支持 BPMN 2.0 标准,适用于各种规模的企业和项目。Flowable 的核心功能包括流程定义、流程执行、任务管理、历史记录查询等,广泛应用于企业级应用中。
官网:https://www.flowable.com/open-source/docs/
二、Spring Boot3 整合环境:JDK21、Spring Boot3.4.1、Flowable7.1.0
spring:#配置数据源 datasource:#MySQLdriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/foleable?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSource
flowable:
开启定时任务 JOB
async-executor-activate: true
在引擎启动时,会自动更新数据库架构
database-schema-update: true3.线程池配置 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**/@Configurationpublic class ThreadPoolTaskConfig {
@Bean("applicationTaskExecutor")public ThreadPoolTaskExecutor applicationTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//此方法返回可用处理器的虚拟机的最大数量; 不小于 1int core = Runtime.getRuntime().availableProcessors();executor.setCorePoolSize(core);//设置核心线程数 executor.setMaxPoolSize(core * 2 + 1);//设置最大线程数 executor.setKeepAliveSeconds(120);//除核心线程外的线程存活时间 executor.setQueueCapacity(120);//如果传入值大于 0,底层队列使用的是 LinkedBlockingQueue,否则默认使用 SynchronousQueueexecutor.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.部署流程(这里先把我们后面用到的查询服务都注入)@Resourceprivate RepositoryService repositoryService;
@Resourceprivate RuntimeService runtimeService;
@Autowiredprivate TaskService taskService;
@Resourceprivate HistoryService historyService;
@ResourceIdentityService 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();
}}五、查询部署的全部流程当然这里也可以进行分页查询已经写在下面注释了,这里为了测试,我就简单写了
/***
查询所有的流程实例
@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 和 namefor (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());
}
//分页查询// 创建查询对象// 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";
注意:
参数 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") : "");
}
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());
}
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) { // 获取任务对应的流程实例 IDTask 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 引擎自带构建的图片没有经过的节点图片图片







    
评论