索尔老师之前接到过一个需求,为某公司内部系统设计一个工作流引擎来解决公司内部的各种自动化流程审批业务。当时,索尔老师就在想,对于流程审批来说,每个申请必然会存在复杂的流程,而每个流程又会需要多方的参与来完成。比如一个员工的请假申请,首先需要部门领导审批、其次是人事审批,最后是总经理审批,这个流程只有完成这三步,请假才算是申请成功。可想而知,这样的流程处理的功能,在我们的后台系统中是非常常见的,那么有没有一个框架,能帮我们解决流程处理方面的问题呢?答案是肯定的,那就是我们今天介绍的Activiti框架。
工作流定义
在介绍Activiti框架之前,索尔老师先跟大家聊一聊工作流。什么是工作流?工作流是将一组任务组织起来,目的是为了完成某个有序的过程。这个过程定义了任务的触发条件和触发顺序,而且每一个任务都可以由一个或多个软件系统或人来完成。简言之,工作流就是业务流程的自动化实现。使用工作流,可以将效率低下的纸质审批、传递审批、一级级的见面审批等传统的流程审批方式,转换为计算机系统的自动化流程审批。审批人只需要登录系统,就能看到他需要审批的业务。整个流程会随着审批人的审批或拒绝等操作,自动地向下流转。以此来提供企业办公的效率。
Activiti起源
在介绍完工作流定义后,索尔老师带大家来了解Activiti框架的由来。工作流的开源框架Activiti创始者Tom Baeyens在jBoss公司担任JBPM4工作流引擎的首席架构师,在离开jBoss公司后,推出了基于JBPM4工作流引擎的开源框架Activiti。
BPMN建模符号
Activiti框架基于BPM(Bussiness Process Manage)规范,通过BPMN(Bussiness Process Modeling Notation)进行建模。BPMN2.0规范作为一种标准,定义了流程图的标准符号系统,用于对业务流程进行建模。我们来看一些BPMN定义的符号。
这些符号用于表示流程中的关键环节,比如开始流程、结束流程、任务节点、网关节点、事件节点等等。
可以使用这些流程图符号来定义流程图。可以为每个节点指定参数、表达式等,实现对每个节点的控制。
Activiti通过绘制的流程图BPMN文件部署到数据库汇中,生成流程定义信息。再根据流程定义信息创建流程实例,每一个流程实例就是一个审批流程业务。
Activiti框架
在掌握了BPMN相关概念后,索尔老师来带大家看看Activiti框架的核心工作流程。
Activiti框架的工作流程是通过读取activiti.cfg.xml配置文件来获得工作流引擎,再通过引擎来创建和操作流程实例。在创建流程实例的过程中,Activiti 会根据配置,在数据库中生成25张表。这些表存储了Activiti运行过程中所需要的信息。包括通用数据、历史数据、身份信息、静态数据及运行时数据等。这些数据,也是某一个工作流实例在运转的过程中产生及需要获取的数据,比如一个请假流程,流程的定义、流程实例、流程达到哪个任务节点等数据,都会保存在这些表中。
同时,Activiti为我们提供了操作数据的多个Service,这些Service已封装了操作流程的大部分功能,我们直接拿来使用即可。
// 获取流程引擎ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 运行时服务,提供流程运行时的管理
RuntimeService runtimeService = processEngine.getRuntimeService();
// 资源服务,进行资源管理
RepositoryService repositoryService = processEngine.getRepositoryService();
// 任务服务,提供任务管理
TaskService taskService = processEngine.getTaskService();
// 历史信息服务,提供提示里新的管理
HistoryService historyService = processEngine.getHistoryService();
// 表单服务,提供表单信息的管理
FormService formService = processEngine.getFormService();
Activiti运行流程
Activiti给我们提供了多个Service,让我们可以在不同流程运行的不同阶段来使用,接下来,索尔老师带大家看下一个流程运行要经历的各个阶段。
整个运行流程如下:
获得ProcessEngine流程引擎
使用RepositoryService部署流程获得流程定义
使用流程定义获得流程实例
使用TaskService进行流程实例的任务操作
完成所有任务节点操作,流程结束
在研究了流程运行的过程之后,接下来我们详细来看,如何使用Java代码完成流程的各个阶段。
绘制流程图
使用idea绘制流程图插件绘制一个简单的请假流程图。
请假流程发起后,需要请假人创建请假申请,然后交由部门领导审批、总经理审批,最后财务审批,这个流程结束。
流程部署
在使用Activiti进行流程管理之前,需要先将绘制好的流程图部署到数据库中。使用RepositoryService进行流程部署,生成流程部署信息和流程定义。其中,流程定义可用于生成流程实例。
/*
部署流程图
*/
@Test
public void test1(){
//1.获取ProcessEngine对象
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//2.获取用于部署的RepositoryService
RepositoryService repositoryService = engine.getRepositoryService();
//3.执行部署
Deployment deploy = repositoryService.createDeployment()
//添加资源
.addClasspathResource("bpmn/leave.bpmn")
.addClasspathResource("bpmn/leave.png")
.name("请假申请流程")
.deploy();
System.out.println("流程部署的id:"+deploy.getId());
System.out.println("流程name:"+deploy.getName());
}
涉及的数据库表:
表名 |
描述 |
ACT_GE_BYTEARRAY |
存放流程图文件 |
ACT_RE_DEPLOYMENT |
每次部署会生成一条部署记录 |
ACT_RE_PROCDEF |
存放流程定义,部署时会生成流程定义 |
创建流程实例
在进行流程部署时,获得了流程定义。此时还不能够操作流程,因为流程定义只是一个概念,真正的流程内容需要封装在流程实例。也就是使用流程定义来创建流程实例,这个逻辑大家听起来似乎有点熟悉,没错,流程定义和流程实例之间的关系就是“类”和“对象”的关系。怎么样,索尔老师这么一描述,是不是瞬间就明白了。
一个流程定义可以创建多个流程实例,比如请假申请是一个流程定义,而小明可以创建小明的请假申请,小王可以创建小王的请假申请,小明和小王创建的请假申请就是具体的流程实例。
使用RuntimeService来获得一个流程实例。
/*
创建流程实例
*/
@Test
public void test2(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
//根据部署id创建流程定义
RuntimeService runtimeService = engine.getRuntimeService();
ProcessDefinition def = repositoryService.createProcessDefinitionQuery().
deploymentId("2501").singleResult();
ProcessInstance processInstance = runtimeService.startProcessInstanceById(def.getId());
System.out.println("流程定义的id:"+processInstance.getProcessDefinitionId());
System.out.println("实例id:"+processInstance.getId());
}
创建完流程实例后,整个流程的内容就会在数据库表中得以体现:
表名 |
描述 |
ACT_RU_EXECTION |
流程实例执行表,记录当前流程实例的执行情况 |
ACT_RU_TASK |
任务执行表,记录当前执行的任务 |
ACT_RU_IDENTITYLINK |
任务参与者表,记录当前参与任务的用户或组信息 |
ACT_HI_PROCINST |
流程实例历史表,记录流程实例的历史信息 |
ACT_HI_TASKINST |
任务历史表,记录任务的历史信息 |
ACT_HI_ACTINST |
活动历史表,记录比如开始事件、结束事件等所有活动信息 |
任务管理
流程创建后,流程就开始正常工作了。此时按照流程图的设计,流程会进入到指定的任务节点。这些任务节点,可能是用户任务节点,需要用户来完成任务。有的是脚本节点,会自动执行某一段脚本。
执行任务
当前登录系统的用户,通过TaskService来查询自己是否有被指派的任务,如果有,则通过操作来执行任务,让流程继续往下走。
/*
任务的处理
*/
@Test
public void test4(){
//1.获得engine
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//2.获得TaskService
TaskService taskService = engine.getTaskService();
//3.查询出王工的任务
Task task = taskService.createTaskQuery()
.processDefinitionId("myProcess_1:1:2504")
.taskAssignee("王工")
.singleResult();
//完成任务
taskService.complete(task.getId());
}
拾取任务
有的时候,也可以是一个候选组里的多个候选人中的某个候选人来主动拾取任务,即A、B、C中的某一人,只要有一人完成该任务,该流程就能继续往下走。比如请假审批中,部门经理或者总经理,只要有一人审批即可,此时部门经理或总经理就是任务候选人,需要主动去拾取任务。
/*
拾取任务
*/
@Test
public void test4(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
String taskId = "67505";
String user = "李工";
//拾取任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskCandidateUser(user)
.singleResult();
if(task!=null){
taskService.claim(taskId,user);
System.out.println("完成拾取");
}
}
拾取完任务后,该任务才会到被指派人身上,被指派人可以选择执行任务或者归还任务。执行任务的逻辑我们在上面已经介绍了,下面我们看一下被指派人如何不执行任务,而是归还任务。
归还任务:
/*
归还任务
*/
@Test
public void test5(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
String taskId = "67505";
String user = "李工";
Task task = taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(user)
.singleResult();
if(task!=null){
//归还任务
taskService.setAssignee(taskId,null);
}
}
转交任务
被指派人除了能归还任务外,还能将任务移交给其他人,由指定的人来完成该任务。
/*
转交任务给其他人
*/
@Test
public void test6(){
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = engine.getTaskService();
String taskId = "67505";
String user = "李工";
String targetUser = "王工";
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(user)
.singleResult();
if(task!=null){
//转交任务
taskService.setAssignee(taskId,targetUser);
}
}
结语
在流程定义中所有的任务节点都结束后,该流程实例才算是完成了。