BPMN
一个软件协议,此协议用于约定了流程框架的开发规范。比如说,我们去绘制流程图的图标的规范。
基于这个规范,很方便地学习基于此规范的其它的流程框架。
flowable是一个服务平台
等一系列功能
BPMN
DMN决策表(树)
CMMN Case管理引擎
用户管理
微服务API
功能比acitviti更加强大。如果有activiti的基础,那么可以很容易地学习与使用flowable。
如果会使用开源版本的flowable,那么商业版的flowable就无缝衔接了。
public abstract class ProcessEngineConfigurationImpl extends ProcessEngineConfiguration implements ScriptingEngineAwareEngineConfiguration {
public ProcessEngine buildProcessEngine() {
// this表示绑定当前对象,即绑定ProcessEngineConfigurationImpl
this.init();
ProcessEngineImpl processEngine = new ProcessEngineImpl(this);
this.postProcessEngineInitialisation();
return processEngine;
}
}
this.initConfigurators(); this.configuratorsBeforeInit(); this.initProcessDiagramGenerator(); this.initHistoryLevel(); this.initFunctionDelegates(); this.initDelegateInterceptor(); this.initExpressionManager(); this.initAgendaFactory(); if (this.usingRelationalDatabase) { this.initDataSource(); //如何关联数据为的? this.initDbSchemaManagers(); } this.initHelpers(); this.initVariableTypes(); this.initBeans(); this.initFormEngines(); this.initFormTypes(); this.initScriptingEngines(); this.initClock(); this.initBusinessCalendarManager(); this.initCommandContextFactory(); this.initTransactionContextFactory(); this.initCommandExecutors(); this.initServices(); this.initIdGenerator(); this.initWsdlImporterFactory(); this.initBehaviorFactory(); this.initListenerFactory(); this.initBpmnParser(); this.initProcessDefinitionCache(); this.initProcessDefinitionInfoCache(); this.initAppResourceCache(); this.initKnowledgeBaseCache(); this.initJobHandlers(); this.initHistoryJobHandlers(); this.initTransactionFactory(); if (this.usingRelationalDatabase) { this.initSqlSessionFactory(); //初始化SqlSessionFactory } this.initSessionFactories(); this.initDataManagers(); //初始化好多个管理器 this.initEntityManagers(); this.initCandidateManager(); this.initHistoryManager(); this.initDynamicStateManager(); this.initJpa(); this.initDeployers(); this.initEventHandlers(); this.initFailedJobCommandFactory(); this.initEventDispatcher(); this.initProcessValidator(); this.initFormFieldHandler(); this.initDatabaseEventLogging(); this.initFlowable5CompatibilityHandler(); this.initVariableServiceConfiguration(); this.initIdentityLinkServiceConfiguration(); this.initTaskServiceConfiguration(); this.initJobServiceConfiguration(); this.initAsyncExecutor(); this.initAsyncHistoryExecutor(); this.configuratorsAfterInit(); this.afterInitTaskServiceConfiguration();
-------------------------------------------------------------------------------------------------------------------------------
ACT_RE :'RE'表示 repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
ACT_RU:'RU'表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Flowable只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
ACT_HI:'HI'表示 history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACT_GE: GE 表示 general。 通用数据, 用于不同场景下
ACT_ID: ’ID’表示identity(组织机构)。这些表包含标识的信息,如用户,用户组,等等。
package com.bobo.flowable.test;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;
public class Test01 {
/**
* 获取流程引擎对象
*/
public static void testProcessEngine(){
// 获取流程引擎配置类对象:ProcessEngineConfiguration
ProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration();
// 配置数据库连接信息
configuration.setJdbcUrl("jdbc:mysql://10.203.5.185:3306/fLearn?serverTimezone=UTC&nullCatalogMeansCurrent=true")
.setJdbcUsername("root")
.setJdbcPassword("MySQL#567890")
.setJdbcDriver("com.mysql.cj.jdbc.Driver")
// 如果数据库中的表结构不存在就新建
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
// 通过ProcessEngineConfiguration构建我们需要的流程引擎对象ProcessEngine
ProcessEngine ProcessEngine = configuration.buildProcessEngine();
System.out.println("ProcessEngine:"+ProcessEngine);
}
public static void main(String[] args) {
testProcessEngine();
}
}
log4j.rootLogger=DEBUG, CA
log4j.appender.CA=org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout=org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n
第c步:重新运行单元测试方法
第d步:在控制台中看到多了很多日志。如果之前没有创建对应的表结构(30张),那么就会看到创建这30张表的建表语句。
第二步:部署流程定义
ps:创建流程,一般第1步:通过流程设计器绘制流程图。但为了把流程操作的完整流程演示出来,在此先不用流程设计器,而是使用一个写好的xml格式的流程文件(官网案例)。
package com.bobo.flowable.test;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.flowable.engine.repository.Deployment;
import org.junit.Before;
import org.junit.Test;
public class Test01 {
ProcessEngineConfiguration configuration = null;
/**
* 初始化流程引擎配置对象
*/
@Before
public void before(){
// 获取流程引擎配置类对象:ProcessEngineConfiguration
configuration = new StandaloneProcessEngineConfiguration();
// 配置数据库连接信息
configuration.setJdbcUrl("jdbc:mysql://10.203.5.185:3306/fLearn?serverTimezone=UTC&nullCatalogMeansCurrent=true")
.setJdbcUsername("root")
.setJdbcPassword("MySQL#567890")
.setJdbcDriver("com.mysql.cj.jdbc.Driver")
// 如果数据库中的表结构不存在就新建
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
}
/**
* 部署流程
*/
@Test
public void testProcessEngine(){
// 创建流程引擎对象:通过ProcessEngineConfiguration构建我们需要的流程引擎对象ProcessEngine
ProcessEngine processEngine = configuration.buildProcessEngine();
// 创建RepositoryService对象
// 类似地还有:RepositoryService、RuntimeService、TaskService、ManagementService、HistoryService,都是flowable提供的关于流程操作的核心服务类(service))
RepositoryService repositoryService = processEngine.getRepositoryService();
// 创建流程部署对象Deployment:
// (1)添加流程部署文件
// (2)设置部署流程的名称
// (3)执行部署操作
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.name("请假流程")
.deploy();
System.out.println("deployment.getId() = " + deployment.getId());
System.out.println("deployment.getName() = " + deployment.getName());
}
}
第3步:运行测试方法
第4步:验证
第三步:操作CRUD流程
查看完成部署的流程定义的信息:// 获取流程定义对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId("2501")
.singleResult();
删除完成部署的流程实例:
// 删除完成部署的流程实例 // 删除部署的流程 // (1)第一个参数是id。如果部署的流程启动了就不允许删除了 // (2)第二个参数是级联删除,如果为true,那么删除流程的同时启动了相关的任务也一并会被删除外 repositoryService.deleteDeployment("1");
// 启动流程实例 // (1)第一个参数是流程定义的id(主键) // (2)第二个参数是提交的表单数据 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holidayRequest", variables); // 输出相关的流程实例信息 System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例的ID:" + processInstance.getId()); System.out.println("当前活动的ID:" + processInstance.getActivityId());
流程定义的ID:holidayRequest:1:3 流程实例的ID:2501 当前活跃的ID:null
上面员工发起了一个请假流程,接下来就会流转到总经理这儿来处理,之前我们没有指定候选人经理这的处理人,我们可以加一个:
此时,如果任务流转到总经理(lisi)这里,那么lisi登录进来就可以看到zhansan请假的审批在这里,等待审批。
第2步:删除旧的流程:repositoryService.deleteDeployment("1",true);
验证:查看数据库表
第3步:重新启部署流程:Deployment deployment = repositoryService.createDeployment()// 创建Deployment对象
.addClasspathResource("holiday-request.bpmn20.xml") // 添加流程部署文件
.name("请假流程") // 设置部署流程的名称
.deploy(); // 执行部署操作
deployment.getId() = 5001 //流程编号
deployment.getName() = 请假流程 //流程名称
第4步:重启启动流程实例(张三请假)
第5步:代码实现,任务查看。即在实际开发中,张三的司令登录进来后会在前端页面看到流转到他的流程审批任务。
Listlist = taskService.createTaskQuery() .processDefinitionKey("holidayRequestNew") //指定查询的流程编号 .taskAssignee("张三的司令")//查询这个任务的处理人(流转到) .list();
第6步:验证:
task.getProcessDefinitionId() = holidayRequest:1:5003
task.getId() = 10008
task.getAssignee() = 张三的司令
task.getName() = 同意或者拒绝请假
task.getProcessDefinitionId() = holidayRequest:1:5003
task.getId() = 7508
task.getAssignee() = 张三的司令
task.getName() = 同意或者拒绝请假
第2步:添加Java委托类
package org.flowable;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
public class SendRejectionMail implements JavaDelegate {
/**
* 这是一个Flowable中的触发器
* @param delegateExecution
*/
@Override
public void execute(DelegateExecution delegateExecution) {
// 触发执行的逻辑,按照我们在流程中的定义,应该给被拒绝的员工发送邮件
// 扩展:短信、邮件、订单回退、删除订单等
System.out.println("请假被拒绝,,,安心工作吧");
}
}
第3步:单元测试,完成任务
@Test
public void testCompleteTask(){
// 获取流程引擎对象
ProcessEngine processEngine = configuration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.processDefinitionKey("holidayRequest2")
.taskAssignee("张司令")
.singleResult();
// 添加流程变量
Map variables = new HashMap();
variables.put("approved",false); // 拒绝请假
// 完成任务
taskService.complete(task.getId(),variables);
}
第七步:查看历史信息
代码实现:
/** * 查看历史 */ @Test public void testQueryHistory(){ // 获取流程引擎对象 ProcessEngine processEngine = configuration.buildProcessEngine(); HistoryService historyService = processEngine.getHistoryService(); Listlist = historyService.createHistoricActivityInstanceQuery() .processDefinitionId("holidayRequest2:1:2503") //从数据库表ACT_RE_PROCDEF中获取定义id .finished() //查询的历史记录的状态是已经完成 .orderByHistoricActivityInstanceEndTime().asc()//结束时间排序 .list(); for (HistoricActivityInstance historicActivityInstance : list) { System.out.println(historicActivityInstance.getActivityId() + " took " + historicActivityInstance.getDurationInMillis() + " milliseconds"); } }
ACT_RU_EXECUTION 运行时流程执行实例
BUSINESS_KEY_:绑定的业务主键,如订单号
PARENT_ID_:空代表流程实例的第一个任务
ROOT_PROC_INST_ID_:当前流程实例根节点的编号
Xxx_COUNT_:统计信息
注意事项:
同一个流程会有多条数据,每条数据用ROOT_PROC_INST_ID_进行关联
ACT_RU_IDENTITYLINK 运行时用户关系信息
ACT_RU_TASK 运行时任务表:记录当前流程实例所处的流程节点信息,比如现在到张三审批了;张三审批完,又记录现在是李四审批了。
ASSIGNEE_:指派谁去处理
FORM_KEY_:在flowable中是可以绑定表单的,FORM_KEY_就是用于记录绑定的表单
ACT_RU_VARIABLE 运行时变量信息表
ACT_HI_ACTINST 历史的流程实例
ACT_RU_IDENTITYLINK 运行时用户关系信息,IDENTITYLINK中会记录每次流程操作的信息
每对流程做一次处理,ACT_RU_IDENTITYLINK 就会多1条记录
ACT_RU_TASK 运行时任务表,记录了当前流程实例所运行的节点。
审批完成进入下一个环节,ACT_RU_TASK 会删除1条记录(旧任务),新增1条新记录(新任务)。
ACT_RU_VARIABLE 运行时变量表:Map集合
如果之前没有绑定流程变量,那么就多1条记录,是处理流程时绑定的流程变量。
如果之前有绑定过流程变量,那么就把这条数据中的数据更新为最新数据。
首先我们会发现
ACT_RU_EXECUTION 运行时流程执行实例
ACT_RU_IDENTITYLINK 运行时用户关系信息
ACT_RU_TASK 运行时任务表
ACT_RU_VARIABLE 运行时变量表
这四张表中对应的数据都没有了,也就是这个流程已经不是运行中的流程了。然后在对应的历史表中我们可以看到相关的信息:
ACT_HI_ACTINST 历史的流程实例
ACT_HI_ATTACHMENT 历史的流程附件
ACT_HI_COMMENT 历史的说明性信息
ACT_HI_DETAIL 历史的流程运行中的细节信息
ACT_HI_IDENTITYLINK 历史的流程运行过程中用户关系
ACT_HI_PROCINST 历史的流程实例
ACT_HI_TASKINST 历史的任务实例
ACT_HI_VARINST 历史的流程运行中的变量信息
在我们上面的处理流程的过程中设计到的历史表有
值表达式2:${myBean.myProperty}
首先,和spring(springboot)整合。
其次,从spring容器中获取相应的bean对象。
最后,通过bean对象获取属性值。
其次,从spring容器中获取相应的bean对象。
最后,通过bean对象调用方法获取值。
/** * 自定义的监听器 */ public class MyTaskListener implements TaskListener { /** * 监听器触发的方法 * @param delegateTask */ @Override public void notify(DelegateTask delegateTask) { System.out.println("MyTaskListener监听器触发了,触发的事件是:" + delegateTask.getName()); // 满足触发条件的事件,那么我们就来设置assignee if("提交请假".equals(delegateTask.getName()) && "create".equals(delegateTask.getEventName())){ // 指定任务的负责人 delegateTask.setAssignee("小明"); }else { delegateTask.setAssignee("小张"); } } }
/** * 演示:局部变量 * 根据Task编号来更新流程变量 * 比如,原先我申请出差2天,但由于疫情原因只能多请3天,就是说要出差5天。但流程已经在跑了,通过此方法可以在审批完成之前把出差天数更新了。 */ @Test public void updateVariableLocal(){ // 获取流程引擎对象 ProcessEngine processEngine = configuration.buildProcessEngine(); TaskService taskService = processEngine.getTaskService(); Task task = taskService.createTaskQuery() .processDefinitionId("40001") .taskAssignee("李四") .singleResult(); // 获取所有的流程变量 MapprocessVariables = task.getProcessVariables(); // 新增/更新num processVariables.put("num",5); // 设置局部变量 taskService.setVariablesLocal(task.getId(),processVariables); }
// 再次设置(更新)全局变量 @Test public void updateVariable(){ // 获取流程引擎对象 ProcessEngine processEngine = configuration.buildProcessEngine(); TaskService taskService = processEngine.getTaskService(); Task task = taskService.createTaskQuery() .processDefinitionId("40001") .taskAssignee("李四") .singleResult(); // 获取所有的流程变量 // 在局部变量和全局变量都有(名称相同)的情况下,这儿取出来的是局部变量 MapprocessVariables = task.getProcessVariables(); // 新增/更新num processVariables.put("num",7); // 设置全局变量 taskService.setVariables(task.getId(),processVariables); }
variables.put("candidate1","候选1") ; variables.put("candidate2","候选2"); variables.put("candidate3","候选3") ;
/**
* 根据登录的用户查询对应的可以拾取的任务
*
*/
@Test
public void queryTaskCandidate(){
// 获取流程引擎对象
ProcessEngine processEngine = configuration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
List list = taskService.createTaskQuery()
.processInstanceId("50001")
//.processDefinitionId("houXuanRen:1:47504")
.taskCandidateUser("候选1")
.list();
for (Task task : list) {
System.out.println("task.getId() = " + task.getId());
System.out.println("task.getName() = " + task.getName());
}
}
第三步:候选人对任务的拾取、归还、交接、完成等操作
拾取
编码:
/**
* 拾取任务
* 一个候选人拾取了这个任务之后其他的用户就没有办法拾取这个任务了
* 所以如果一个用户拾取了任务之后又不想处理了,那么可以退还
*/
@Test
public void claimTaskCandidate(){
// 获取流程引擎对象
ProcessEngine processEngine = configuration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.processInstanceId("50001")
//.processDefinitionId("houXuanRen:1:47504")
.taskCandidateUser("候选1")
.singleResult();
if(task != null){
// 拾取对应的任务
taskService.claim(task.getId(),"候选1");
System.out.println("任务拾取成功");
}
}
效果:
拾取规则:
1个任务被某个候选人拾取以后,其它的所有人都不能再去拾取(看到)这个任务了。
1个候选人拾取了这个任务之后 ,其他的用户就没有办法拾取(看到)这个任务了。
如果1个用户拾取了任务之后,又不想处理了,那么可以归还。
归还
编码:
/**
* 退还任务
* 一个候选人拾取了这个任务之后其他的用户就没有办法拾取这个任务了
* 所以如果一个用户拾取了任务之后又不想处理了,那么可以退还
*/
@Test
public void unclaimTaskCandidate(){
// 获取流程引擎对象
ProcessEngine processEngine = configuration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.processInstanceId("50001")
//.processDefinitionId("houXuanRen:1:47504")
.taskAssignee("候选1")
.singleResult();
if(task != null){
// 归还对应的任务
taskService.unclaim(task.getId());
System.out.println("归还拾取成功");
}
}
效果:
归还规则:
拾取以后,是可以归还的
交接(其中一个候选人必须先拾取任务,这样他才会有交换的权限)
编码:
/**
* 任务的交接:
* 如果我获取了任务,但是不想执行,那么我可以把这个任务交接给其他的用户
*/
@Test
public void taskCandidate(){
// 获取流程引擎对象
ProcessEngine processEngine = configuration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.processInstanceId("50001")
//.processDefinitionId("houXuanRen:1:47504")
.taskAssignee("候选1")
.singleResult();
if(task != null){
// 任务的交接
taskService.setAssignee(task.getId(),"候选2");
System.out.println("任务交接给了'候选2'");
}
}
效果:
act_ru_task表中assignee_字段,从”候选1“变成了”候选2“。
候选2可以登录系统,并处理这个任务了。
交换规则:先拾取,才有权交接出去
完全任务
编码:
/**
* 完成任务
*/
@Test
public void completeTaskHouXuan(){
// 获取流程引擎对象
ProcessEngine processEngine = configuration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.processInstanceId("50001")
//.processDefinitionId("houXuanRen:1:47504")
.taskAssignee("候选2")
.singleResult();
if(task != null){
// 完成任务
taskService.complete(task.getId());
System.out.println("完成Task");
}
}
效果:
任务流转到下一个审批环节了
act_ru_task表中assignee_字段,从”候选2“变成了”ww“
事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同时,会为每个外出顺序流创建相对的事件订阅。
事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。 要考虑以下条件:
事件网关必须有两条或以上外出顺序流;
事件网关后,只能使用intermediateCatchEvent类型(activiti不支持基于事件网关后连接ReceiveTask)
连接到事件网关的中间捕获事件必须只有一个入口顺序流。
ps:事件网关肯定是跟相关的事件有关联的,在事件章节里再具体去学习它。
@SpringBootTest
class FlowableSpringbootApplicationTests {
/*
springboot程序在启动的时候,会自动完成流程引擎ProcessEngine processEngine的注入。
ProcessEngine processEngine相关的XxxService,也会自动被注入。
因此XxxService可以直接从spring容器中获取,或者通过ProcessEngine processEngine点(".")出来。
*/
@Autowired
private ProcessEngine processEngine;
@Test
void contextLoads() {
System.out.println("processEngine="+processEngine);
}
}
processes目录下的任何BPMN 2.0流程定义都会被自动部署。创建processes目录,并在其中创建示例流程定义(命名为one-task-process.bpmn20.xml)。
cases目录下的任何CMMN 1.1事例都会被自动部署。
forms目录下的任何Form定义都会被自动部署。
@SpringBootTest
class FlowableSpringBoot28ApplicationTests {
@Autowired
private ProcessEngine processEngine;
@Autowired
private RepositoryService repositoryService;
@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
/**
* Deploy
*/
@Test
void testDeploy() {
//RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("请假流程.bpmn20.xml")
.name("holiday")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
}
/**
* start process
*/
@Test
void startFlow(){
Map map = new HashMap();
map.put("assignee0","zhangsan");
map.put("assignee1","zhangsan");
runtimeService.startProcessInstanceById("holiday28:2:3653a34e-ae45-11ec-969d-c03c59ad2248",map);
}
/**
* complete Task
*/
@Test
void completeTask(){
Task task = taskService.createTaskQuery()
.processInstanceId("fb166cd8-ae45-11ec-92c4-c03c59ad2248")
.taskAssignee("zhangsan")
.singleResult();
if(task != null){
taskService.complete(task.getId());
System.out.println("complete ....");
}
}