Flowable快速入门指南
在工作第一年,由于业务涉及审批流程,首次接触到工作流,经过一番资料查找,了解到流程引擎的一套规范BPMN2.0,市面上所有可以接触到的开源工作流引擎大差不差或者或多或少的遵循规范。关于市面上的常见的流程引擎有jBPM
、Activiti
、Flowable
、Camuda
等。最终选择了Flowable
,关于选择,并没有过多的理由,老派的jBPM
首先排除,Flowable
、Camuda
都是基于Activiti
的延续,在API
使用上都极大的相似,从生态上貌似都大差不差,可能就Flowable
更好些(官网高大上些😂)。在这里总结Flowable
关于流程引擎部分快速入坑的几点事,如果想更多更全面的了解Flowable
,再从Flowable官网学习~
本文介绍的是更常规的面向实际业务流程图编程,就是根据业务需求进行流程图建模生成bpmn20.xml
文件,用Flowable
提供的API
对实际流程图的流转进行控制,当然Flowable
可以实现对流程本身编程,可以通过代码动态生成流程图,通过设计通用API
控制流程流转,是一层更高的抽象,能够在一定框架内以不变应万变,这往往需要业务有一定的积累或者有一定的经验才能做到更好的抽象,只有慢慢的体会。
本文想从以下几个主题进行阐述:
入门第一步,流程建模工具
Flowable
提供了流程建模的工具,使得更好的将业务流程呈现出来。有了流程图,才能进行下一步业务流程功能实现,即对业务流程图的业务流转控制。
只需以下几步操作就可以以默认配置使用建模的工具。
1.启动flowable-ui.war
。
nohup java -jar flowable-ui.war > ui.out 2>&1 &
2.浏览器访问http://localhost:8080/flowable-ui
,以默认账号admin/test
登录。
3.点击建模器应用程序,创建流程就可以画出如下的流程图,之后导出bpmn20.xml
文件在工程使用。
一个Flowable的栗子
Flowable
通过RuntimeService
、RepositoryService
、TaskService
、HistoryService
、ManagementService
、DynamicBpmnService
、ProcessMigrationService
、FormService
、IdentityService
等服务API
,链式风格的对流程进行操作,具体的API
学习可以见官网。
一个流程的常见操作:流程部署、流程启动、流程删除、流程终止、任务池列表、任务领取、代办任务列表、任务完成、任务退回、任务转办、已办任务列表。
下面通过一个栗子🌰,对上述常规操作做一个简单的阐述。更多复杂的操作也可以通过学习Flowable
提供的API
组合完成。
Julius
设计了一个零食申请的流程,决定将流程部署。java// 部署德发的零食申请流程 Deployment deploy = repositoryService.createDeployment().addClasspathResource("processes/DefaSnackApply.bpmn20.xml").deploy(); // 查询德发的零食申请流程定义 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult(); System.out.println(processDefinition.getKey()); // defa-snack-apply
Defa
想吃零食,决定启动一个零食申请的流程。javaMap<String,Object> variables = new HashMap<>(); // 流程图里的变量,必须设置 variables.put("ownerGroup","Julius' family"); variables.put("catMessage","德发想吃零食~"); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("defa-snack-apply", variables); System.out.println(processInstance.getId()); // 7543d0b8-902d-11ed-ae03-005056c00008
Defa
想形象的看看流程的状态,决定生成一个流程图。java// 查询当前的流程实例 ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId("7543d0b8-902d-11ed-ae03-005056c00008") .singleResult(); // 查询当前的流程模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); // 获取流程图生成器 ProcessDiagramGenerator processDiagramGenerator = processEngineConfiguration.getProcessDiagramGenerator(); // 获取流程中活跃的活动Id集合 List<String> activeActivityIds = runtimeService.createActivityInstanceQuery() .processInstanceId("7543d0b8-902d-11ed-ae03-005056c00008") .list() .stream() .map(ActivityInstance::getActivityId) .collect(Collectors.toList()); // 获取生成图的输入流 InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, "png", activeActivityIds, Collections.emptyList(),"宋体","宋体","宋体",null,1.0, true); // 当前路径输出图 long copy = Files.copy(inputStream,Paths.get("defa-snack-apply.png")); System.out.println(copy);
查询
Julius' family
组下审核列表。javaList<Task> taskList = taskService .createTaskQuery() .processDefinitionKey("defa-snack-apply") .taskCandidateGroup("Julius' family") .includeProcessVariables() .list(); List<Map<String, Object>> taskMapList = taskList.stream().map(task -> { Map<String, Object> map = new HashMap<>(); map.put("processInstanceId", task.getProcessInstanceId()); map.put("taskId",task.getId()); map.put("taskName",task.getName()); map.putAll(task.getProcessVariables()); return map; }).collect(Collectors.toList()); System.out.println(taskMapList.toString()); // [{processInstanceId=7543d0b8-902d-11ed-ae03-005056c00008, catMessage=德发想吃零食~,ownerGroup=Julius' family, taskName=主人审核申请, taskId=755758bf-902d-11ed-ae03-005056c00008}]
Julius
领取审核任务。javataskService.claim("755758bf-902d-11ed-ae03-005056c00008","Julius");
查看
Julius
的代办任务列表。javaList<Task> taskList = taskService .createTaskQuery() .processDefinitionKey("defa-snack-apply") .taskAssignee("Julius") .includeProcessVariables() .list(); List<Map<String, Object>> taskMapList = taskList.stream().map(task -> { Map<String, Object> map = new HashMap<>(); map.put("processInstanceId", task.getProcessInstanceId()); map.put("taskId",task.getId()); map.put("taskName",task.getName()); map.putAll(task.getProcessVariables()); return map; }).collect(Collectors.toList()); System.out.println(taskMapList.toString()); // [{processInstanceId=7543d0b8-902d-11ed-ae03-005056c00008, catMessage=德发想吃零食~, ownerGroup=Julius' family, taskName=主人审核申请, taskId=755758bf-902d-11ed-ae03-005056c00008}]
Julius完成审核。
java// 会持久化的变量variables Map<String,Object> variables = new HashMap<>(); variables.put("ownerComment","德发选择你喜欢的零食吧~"); // 无需持久化的变量transientVariables Map<String, Object> transientVariables = new HashMap<>(); // 流程图里的变量,必须设置 transientVariables.put("catName","Defa"); transientVariables.put("pass_flag",true); taskService.complete("755758bf-902d-11ed-ae03-005056c00008",variables,transientVariables);
查看
Defa
的代办任务列表。javaList<Task> taskList = taskService .createTaskQuery() .processDefinitionKey("defa-snack-apply") .taskAssignee("Defa") .includeProcessVariables() .list(); List<Map<String, Object>> taskMapList = taskList.stream().map(task -> { Map<String, Object> map = new HashMap<>(); map.put("processInstanceId", task.getProcessInstanceId()); map.put("taskId",task.getId()); map.put("taskName",task.getName()); map.putAll(task.getProcessVariables()); return map; }).collect(Collectors.toList()); System.out.println(taskMapList.toString()); // [{processInstanceId=7543d0b8-902d-11ed-ae03-005056c00008, catMessage=德发想吃零食~, ownerComment=德发选择你喜欢的零食吧~, ownerGroup=Julius' family, taskName=猫猫选择零食, taskId=f2827e35-91c0-11ed-b1c9-005056c00008}]
Defa
完成零食选择。java// 会持久化的变量variables Map<String,Object> variables = new HashMap<>(); variables.put("catComment","德发选择了猫草~"); // 无需持久化的变量transientVariables Map<String, Object> transientVariables = new HashMap<>(); // 流程图里的变量,必须设置 transientVariables.put("owner2","Julius' Mom"); transientVariables.put("selected_snack","catnip"); taskService.complete("f2827e35-91c0-11ed-b1c9-005056c00008",variables,transientVariables);
Julius' Mom
查看代办任务。javaList<Task> taskList = taskService .createTaskQuery() .processDefinitionKey("defa-snack-apply") .taskAssignee("Julius' Mom") .includeProcessVariables() .list(); List<Map<String, Object>> taskMapList = taskList.stream().map(task -> { Map<String, Object> map = new HashMap<>(); map.put("processInstanceId", task.getProcessInstanceId()); map.put("taskId",task.getId()); map.put("taskName",task.getName()); map.putAll(task.getProcessVariables()); return map; }).collect(Collectors.toList()); System.out.println(taskMapList.toString()); // [{processInstanceId=7543d0b8-902d-11ed-ae03-005056c00008, catMessage=德发想吃零食~, catComment=德发选择了猫草~, ownerComment=德发选择你喜欢的零食吧~, ownerGroup=Julius' family, taskName=主人投喂猫草, taskId=6f9884ae-91c3-11ed-bbee-005056c00008}]
Julius' Mom
没有空,将任务指定给Julius
处理。javataskService.setVariable("6f9884ae-91c3-11ed-bbee-005056c00008","momComment","没有空,Julius你自己去喂一下~"); taskService.setOwner("6f9884ae-91c3-11ed-bbee-005056c00008","Julius"); taskService.setAssignee("6f9884ae-91c3-11ed-bbee-005056c00008","Julius");
Julius发现猫草没有了,退回给猫猫重新选择。
javaList<ActivityInstance> userTaskList = runtimeService.createActivityInstanceQuery() .processInstanceId("7543d0b8-902d-11ed-ae03-005056c00008") .activityType("userTask") .orderByActivityInstanceStartTime() .desc() .list(); runtimeService .createChangeActivityStateBuilder() .processInstanceId("7543d0b8-902d-11ed-ae03-005056c00008") .moveActivityIdTo(userTaskList.get(0).getActivityId(),userTaskList.get(1).getActivityId()) .localVariable(userTaskList.get(1).getActivityId(),"catName","Defa") .changeState();
Defa
表示没有想法了,决定终止流程。①方式一
java// 查询当前的流程实例 ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId("7543d0b8-902d-11ed-ae03-005056c00008") .singleResult(); Task task = taskService.createTaskQuery().taskId("80ad36d5-91ca-11ed-8685-005056c00008").singleResult(); // 查询当前的流程模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); // 查询流程 Process process = bpmnModel.getProcesses().get(0); // 查询流程中的EndEvent元素 List<String> endEventActivityIds = process .getFlowElements() .stream() .filter(flowElement -> flowElement instanceof EndEvent) .map(BaseElement::getId) .collect(Collectors.toList()); // 添加一个终止流程的理由变量 taskService.setVariable("80ad36d5-91ca-11ed-8685-005056c00008","stopComment","德发不想吃了~"); // 改变当前的节点到结束节点 runtimeService .createChangeActivityStateBuilder() .processInstanceId("7543d0b8-902d-11ed-ae03-005056c00008") .moveActivityIdTo(task.getTaskDefinitionKey(),endEventActivityIds.get(0)) .changeState();
②方式二
javaruntimeService.deleteProcessInstance("7543d0b8-902d-11ed-ae03-005056c00008", "德发不想吃了~");
Defa
查看自己的已办任务。javaList<HistoricTaskInstance> historicTaskInstances = historyService .createHistoricTaskInstanceQuery() .processDefinitionKey("defa-snack-apply") .taskAssignee("Defa") .includeProcessVariables() .list(); List<Map<String, Object>> taskMapList = historicTaskInstances.stream().map(task -> { Map<String, Object> map = new HashMap<>(); map.put("processInstanceId", task.getProcessInstanceId()); map.put("taskId",task.getId()); map.put("taskName",task.getName()); map.putAll(task.getProcessVariables()); return map; }).collect(Collectors.toList()); System.out.println(taskMapList.toString());// [{processInstanceId=7543d0b8-902d-11ed-ae03-005056c00008, catMessage=德发想吃零食~, catComment=德发选择了猫草~, ownerComment=德发选择你喜欢的零食吧~, ownerGroup=Julius' family, taskName=猫猫选择零食, momComment=没有空,Julius你自己去喂一下~, stopComment=德发不想吃了~, taskId=80ad36d5-91ca-11ed-8685-005056c00008}, {processInstanceId=7543d0b8-902d-11ed-ae03-005056c00008, catMessage=德发想吃零食~, catComment=德发选择了猫草~, ownerComment=德发选择你喜欢的零食吧~, ownerGroup=Julius' family, taskName=猫猫选择零食, momComment=没有空,Julius你自己去喂一下~, stopComment=德发不想吃了~, taskId=f2827e35-91c0-11ed-b1c9-005056c00008}]
Julius
觉得没有服务好Defa
,决删除申请流程,眼不见心不烦。javahistoryService.deleteHistoricProcessInstance("7543d0b8-902d-11ed-ae03-005056c00008");
全部代码见sdefaa-code-snippet
的flowable-snippet
部分。
Flowable中的概念和表结构
流程相关的主要概念和数据库表
这里阐述流程相关主要的概念和表,基本满足理解和使用,还有一些额外的概念和表这里不重点赘述,例如关于用户权限相关的,表名以ACT_ID_
开始的数据库表。这些额外的在使用时候通过官网或者API
练习熟悉。
流程
在bpmn20.xml
文件中定义例如:
<process id="defa-snack-apply" name="DefaSnackApply" isExecutable="true">
...
</process>
processDefinitionId
:流程定义编号。processDefinitionName
:流程定义名称,例如这里的DefaSnackApply
。processDefinitionKey
:流程定义的键,例如这里的defa-snack-apply
。processInstanceId
:流程实例编号,一个流程启动后,整个流程中全局唯一。deploymentId
:流程定义部署编号。
涉及的表:
ACT_RE_DEPLOYMENT
:流程部署表。ACT_GE_BYTEARRAY
:流程部署附属表。ACT_RE_PROCDEF
:流程定义表。ACT_HI_PROCINST
: 流程实例历史表。
活动
流程的组成部分,类型有许多,例如startEvent
、sequenceFlow
、userTask
、endEvent
等。
在bpmn20.xml
文件中启动事件定义例如:
<startEvent id="sid-F3A2B62C-E3F2-4F95-B3E3-CBB1ECCD6B80" flowable:formFieldValidation="true"></startEvent>
activityId
:活动编号,例如这里的sid-F3A2B62C-E3F2-4F95-B3E3-CBB1ECCD6B80
。executionId
:活动执行编号。
涉及的表:
ACT_RU_ACTINST
:运行时活动表。ACT_HI_ACTINST
:活动历史表。ACT_RU_EXECUTION
:活动执行表。
任务
任务也是活动的一种,任务的种类有许多,在bpmn20.xml
文件中用户任务定义例如:
<userTask id="sid-F3C4FC7E-AB7E-4F1D-8F7D-7A39294033D6" name="主人审核申请" flowable:candidateGroups="${ownerGroup}" flowable:formFieldValidation="true"></userTask>
taskDefinitionKey
:任务的键,例如sid-F3C4FC7E-AB7E-4F1D-8F7D-7A39294033D6
。taskName
:任务名称,例如这里的主人审核申请
。taskId
:任务编号,每一次流转到任务节点都会生成新的任务编号。
涉及的表:
ACT_RU_TASK
:运行时任务表。ACT_HI_TASKINST
:任务历史表。
变量
一个流程流转过程中产生的非transient
变量信息都会持久化。
涉及的表:
ACT_RU_VARIABLE
:运行时变量表。ACT_HI_VARINST
:变量历史表。
身份链接
对于用户任务的流程,一般会指定候选人组或者候选人,所以需要维护用户任务与候选组或人的关系。
涉及的表:
ACT_RU_IDENTITYLINK
:运行时身份链接表。ACT_HI_IDENTITYLINK
:身份链接历史表。
对应关系
从上可见,流程存在运行时和历史状态,在数据库都会体现两张表。运行时表只存放流程运行时的信息,流程结束,运行表里的数据就会消失,而历史表里无论在什么状态都会保留信息。
流程定义和流程实例的关系、任务定义和任务实例的关系可以类比理解为类与对象的关系,一个流程定义可以启动多个流程实例,一个流程实例中对应的流程图中包含的任务定义可以多次创建任务实例。
Flowable与Springboot
Flowable
提供了与Springboot
整合的starter
,针对流程部分只需添加以下maven
依赖:
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>6.8.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
通过ProcessEngineAutoConfiguration
向容器注入SpringProcessEngineConfiguration
配置类实例和其他异步执行器实例。
SpringProcessEngineConfiguration
配置类实例化后随着容器的生命周期启动,通过FlowableProperties
属性配置类定义的processDefinitionLocationPrefix
流程定义路径属性(默认classpath*:/processes/
)和processDefinitionLocationSuffixes
流程定义后缀名属性(默认**.bpmn20.xml
和**.bpmn
)两者确定的bpmn
资源, 以deploymentMode
(default
)匹配部署策略DefaultAutoDeploymentStrategy
进行部署名deploymentName
(默认SpringBootAutoDeployment
)的流程自动部署。
通过自动配置类ProcessEngineServicesAutoConfiguration
向容器注入了ProcessEngine
实例和Flowable
的相关的API
类实例RuntimeService
、RepositoryService
、TaskService
、HistoryService
、ManagementService
、DynamicBpmnService
、ProcessMigrationService
、FormService
、IdentityService
。
业务流程与SOP
在学习业务流程建模的时候,了解到另一个概念SOP(Standard Operating Procedure
标准作业程序),业务流程和SOP两者可以说是共生关系,两者的区别就是业务流程用来解决what
的问题,而SOP是用来解决how
的问题。业务需求人员在定义一个业务流程,往往需要明确流程中每个步骤或者任务活动的SOP,使得整个流程变得清晰明了,流程执行才会准确无误。
在业务流程建模中,SOP是将业务流程分解成一系列明确的步骤,SOP可以统一操作流程,提高业务效率和质量。
在代码抽象中,SOP用于定义代码中的程序应该如何工作,并规定了代码的执行步骤和顺序。使用SOP可以有效地降低代码的复杂度,方便代码维护和扩展,也可以帮助开发人员对代码实现的抽象了解。