当当网站建设优点美国seo薪酬
一、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:#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.DruidDataSourceflowable:# 开启定时任务JOBasync-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();//此方法返回可用处理器的虚拟机的最大数量; 不小于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();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 和 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());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);//发起人用户IDString 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) { // 获取任务对应的流程实例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引擎自带构建的图片没有经过的节点