文章目录
一、了解工作流
1、什么是工作流
工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。**通俗来讲,就是业务上一个完整的审批流程。**例如员工的请假,出差,外出采购,合同审核等等,这些过程,都是一个工作流。
2、工作流引擎
对于工作流的处理,如果采用原始的方式,我们需要拿着各种文件到各个负责人那里去签字,需要在多个部门之间不断审批,这种方式费时费力。而我们可以借助软件系统来协助我们处理这些审批流程,这样就出现了工作流系统,使用工作流系统后可以极大的提高工作效率。
在学习工作流的过程中,我们肯定看到过这个模型:填写请假单->部门经理审批->总经理审批->人事备案。
(1)要实现上述的流程,我们自己可以通过字段标识来实现这个审批效果,在业务表中加个字段,比如填写请假单用1标识,部门经理用2标识,总经理用3标识,人事备案用4标识,好像看起来没啥问题,也实现了审批效果。可是一旦我们的流程出现了变化,这个时候我们就需要改动我们的代码了,这显然是不可取的,那么有没有专业的方式来实现工作流的管理呢?并且可以做到业务流程变化之后,我们的程序可以不用改变,如果可以实现这样的效果,那么我们的业务系统的适应能力就得到了极大提升。在这样的背景下,就出现了工作流引擎。
为什么使用工作流引擎,能实现业务流程改变,不用修改代码,流程还能自动推进?
(1)我们先来说说为什么流程改变,不用修改代码:我们的工作流引擎都实现了一个规范,这个规范要求我们的流程管理与状态字段无关,始终都是读取业务流程图的下一个节点。当业务更新的时候我们只需要更新业务流程图就行了。这就实现了业务流程改变,不用修改代码。
(2)再来说说流程自动推进,这个原理就更简单了,就拿上面的请假模型来说,工作流引擎会用一张表来记录当前处在的节点。当填写完请假单后肯定是要轮到部门经理来审批了,所以我们一旦完成了请假单填写那么这条记录将会被从这张表删除掉,并且会把下一个节点部门经理的信息插入到这张表中,当我们用部门经理的信息去这张表中查询的时候就能查出部门经理相关的审批的信息了,以此类推,这样层层递进,就实现了流程的自动递交了。
3、常见工作流引擎
主流的框架有:Activiti、jBPM、Camunda 、Flowable 、还有我们国产的盘古BPM、云程
4、Activiti7概述
4.1、Activiti介绍
activiti是一个工作流引擎,可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN进行定义,业务流程按照预先定义的流程进行执行。实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作流量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
4.2、建模语言BPMN
BPM(Business Process Management)即业务流程管理,是一种规范化的构造端到端的业务流程,以持续提高组织业务效率
BPM 软件就是根据企业中业务环境的变化,推进人与人之间、人与系统之间以及系统与系统之间的整理及调整的经营方法与解决方案的 IT 工具。使用 BPM 软件对企业内部及外部的业务流程的整个生命周期进行建模、自动化、管理监控和优化,可以降低企业成本,提高利润
BPMN(Business Process Model AndNotation)即业务流程模型和符号,是一套标准的业务流程建模符号,使用 BPMN 提供的符号可以创建业务流程。Activit 就是使用 BPMN 进行流程建模、流程执行管理的
BPMN2.0 是业务流程建模符号 2.0 的缩写,它由 Business Process Management Initiative 这个非营利协会创建并不断发展。BPMN2.0 是使用一些符号来明确业务流程设计流程图的一套符号规范,能增进业务建模时的沟通效率。目前 BPMN2.0 是最新的版本,它用于在 BPM 上下文中进行布局和可视化的沟通
-
BPMN2.0 的基本符号主要包含:
开始:表示一个流程的开始中间:发生的开始和结束事件之间,影响处理的流程
结束:表示该过程结束
-
活动 Activities
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程;其次,你还可以为活动指定不同的类型。常见活动如下:
网关 GateWay
用于表示流程的分支与合并,有几种常用网关需要了解:
-
排他网关:只有一条路径会被选择
-
包容网关:可以同时执行多条线路,也可以在网关上设置条件
-
事件网关:专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态
流向 Flow
流是连接两个流程节点的连线,常见的流向包含以下几种:
顺序流:用一个带实心箭头的实心线表示,用于指定活动执行的顺序
信息流:用一条带箭头的虚线表示,用于描述两个独立的业务参与者(业务实体/业务角色)之间发送和接受的消息流动
关联:用一根带有线箭头的点线表示,用于将相关的数据、文本和其他人工信息与流对象联系起来。用于展示活动的输入和输出
流程示例:
4.3、Activiti使用流程
第一步: 引入依赖并初始化数据库
既然activiti是一个框架,那么我们肯定是需要引入对应的jar包坐标的,具体参考代码中的。
第二步: 通过工具绘画流程图
使用 activiti 流程建模工具(activity-designer)定义业务流程(.bpmn 文件)
.bpmn 文件就是业务流程定义文件,通过 xml 定义业务流程。
第三步:流程定义部署;
向 activiti 部署业务流程定义(.bpmn 文件),使用 activiti 提供的 api 向 activiti 中部署.bpmn 文件,
通俗来讲,就是让activiti认识要使用的流程
第四步: 启动一个流程实例(ProcessInstance)
启动一个流程实例表示开始一次业务流程的运行,比如员工请假流程部署完成,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响,就好比定义一个java类,实例化两个对象一样,部署的流程就好比java类,启动一个流程实例就好比new一个java对象
第五步: 用户查询待办任务(Task)
因为现在系统的业务流程已经交给 activiti 管理,通过 activiti 就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些 activiti帮我们管理了。实际上我们学习activiti也只是学习它的API怎么使用,因为很多功能activiti都已经封装好了,我们会调用就行了
第六步: 用户办理任务
用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如请假单创建后由部门经理审核,这个过程也是由 activiti 帮我们完成了,不需要我们在代码中硬编码指定下一个任务办理人了
第七步: 流程结束
当任务办理完成没有下一个任务节点了,这个流程实例就完成了。
二、Activiti7
1、Activiti使用
1.1、数据库支持
Activiti 运行必须要有数据库的支持,支持的数据库有:mysql、oracle、postgres、mssql、db2、h2
1.2、Activiti环境
我们直接在当前项目:guigu-oa-parent做Activiti入门讲解
1.2.1、引入依赖
<!--引入activiti的springboot启动器 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
<exclusions>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
说明:Activiti7与SpringBoot整合后,默认集成了SpringSecurity安全框架,当前我们项目已经集成过了SpringSecurity,后续案例设置审批人时都必须是系统用户,Activiti框架会检查用户是否存在,否则会出现异常,后续大家可以在案例中测试。
1.2.2、添加配置
数据源项目已经添加,只需要如下配置即可
spring:
activiti:
# false:默认,数据库表不变,但是如果版本不对或者缺失表会抛出异常(生产使用)
# true:表不存在,自动创建(开发使用)
# create_drop: 启动时创建,关闭时删除表(测试使用)
# drop_create: 启动时删除表,在创建表 (不需要手动关闭引擎)
database-schema-update: true
#监测历史表是否存在,activities7默认不开启历史表
db-history-used: true
#none:不保存任何历史数据,流程中这是最高效的
#activity:只保存流程实例和流程行为
#audit:除了activity,还保存全部的流程任务以及其属性,audit为history默认值
#full:除了audit、还保存其他全部流程相关的细节数据,包括一些流程参数
history-level: full
#校验流程文件,默认校验resources下的process 文件夹的流程文件
check-process-definitions: true
1.2.3、启动项目
启动项目,即可生成项目数据库表
1.2.4、数据库表介绍
Activiti 的运行支持必须要有这 25 张表的支持,主要是在业务流程运行过程中,记录参与流程的用户主体,用户组信息,以及流程的定义,流程执行时的信息,和流程的历史信息等等
1、 表的命名规则和作用
观察创建的表,我们发现 Activiti 的表都以 act_ 开头,紧接着是表示表的用途的两个字母标识,也和 Activiti 所提供的服务的 API 对应:
- ACT_RE:RE 表示 repository,这个前缀的表包含了流程定义和流程静态资源 (图片、规则、等等)
- ACT_RU:RU 表示 runtime,这些表运行时,会包含流程实例、任务、变量、异步任务等流程业务进行中的数据。Activiti 只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。这样表就可以一直保持很小的体积,并且速度很快
- ACT_HI:HI 表示 history,这些表包含一些历史数据,比如历史流程实例、变量、任务等等
- ACT_GE:GE 表示 general,通用数据
2、Activiti 数据表介绍
2、Activiti 数据表介绍
表分类 | 表名 | 解释 |
---|---|---|
一般数据 | ||
[ACT_GE_BYTEARRAY] | 通用的流程定义和流程资源 | |
[ACT_GE_PROPERTY] | 系统相关属性 | |
流程历史记录 | ||
[ACT_HI_ACTINST] | 历史的流程实例 | |
[ACT_HI_ATTACHMENT] | 历史的流程附件 | |
[ACT_HI_COMMENT] | 历史的说明性信息 | |
[ACT_HI_DETAIL] | 历史的流程运行中的细节信息 | |
[ACT_HI_IDENTITYLINK] | 历史的流程运行过程中用户关系 | |
[ACT_HI_PROCINST] | 历史的流程实例 | |
[ACT_HI_TASKINST] | 历史的任务实例 | |
[ACT_HI_VARINST] | 历史的流程运行中的变量信息 | |
流程定义表 | ||
[ACT_RE_DEPLOYMENT] | 部署单元信息 | |
[ACT_RE_MODEL] | 模型信息 | |
[ACT_RE_PROCDEF] | 已部署的流程定义 | |
运行实例表 | ||
[ACT_RU_EVENT_SUBSCR] | 运行时事件 | |
[ACT_RU_EXECUTION] | 运行时流程执行实例 | |
[ACT_RU_IDENTITYLINK] | 运行时用户关系信息,存储任务节点与参与者的相关信息 | |
[ACT_RU_JOB] | 运行时作业 | |
[ACT_RU_TASK] | 运行时任务 | |
[ACT_RU_VARIABLE] | 运行时变量表 |
1.3、Activiti常用Service服务接口
简单介绍一下各个 Service 的实现类:
-
RepositoryService
Activiti 的资源管理类,该服务负责部署流程定义,管理流程资源。在使用 Activiti 时,一开始需要先完成流程部署,即将使用建模工具设计的业务流程图通过 RepositoryService 进行部署
-
RuntimeService
Activiti 的流程运行管理类,用于开始一个新的流程实例,获取关于流程执行的相关信息。流程定义用于确定一个流程中的结构和各个节点间行为,而流程实例则是对应的流程定义的一个执行,可以理解为 Java 中类和对象的关系
-
TaskService
Activiti 的任务管理类,用于处理业务运行中的各种任务,例如查询分给用户或组的任务、创建新的任务、分配任务、确定和完成一个任务
-
HistoryService
Activiti 的历史管理类,可以查询历史信息。执行流程时,引擎会保存很多数据,比如流程实例启动时间、任务的参与者、完成任务的时间、每个流程实例的执行路径等等。这个服务主要通过查询功能来获得这些数据
-
ManagementService
Activiti 的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护
1.4、流程设计工具
IDEA版本小于等于2019,可使用Activiti插件actiBPM,大于该版本的IDEA可使用Activiti BPMN visualizer插件绘制流程设计。
今天我们主角是:Activiti Modeler
Activiti Modeler 是 Activiti 官方提供的一款在线流程设计的前端插件,开发人员可以方便在线进行流程设计,保存流程模型,部署至流程定义等等,后续我们的项目也是集成Activiti Modeler绘制流程定义。
1.4.1、下载activiti-explorer
官网下载:https://www.activiti.org/get-started
1.4.2、解压获取部署包
解压activiti-5.22.0.zip,在activiti-5.22.0\wars目录下获取activiti-explorer.war
1.4.3、部署activiti-explorer.war
将activiti-explorer.war放到tomcat部署目录,启动tomcat
1.4.4、访问activiti-explorer
http://localhost:8080/activiti-explorer
默认登录账号:kermit kermit
上面有很多功能,我们关注流程设计即可,如下图:
点击上图:流程 --> 新建模型 --> 输入模型名称(请假)–> 创建
1.4.5、设置节点属性
指定标签名称:张三审批,节点任务负责人:zhangsan
1.4.6、设置流程定义key
1.4.7、保存流程定义模型,下载模型和图片
1.4.8、将资源文件放入项目
在service-oa模块resources下新建process资源文件夹
将qingjia.bpmn20.xml与qingjia.png放入process目录
二、流程定义部署
将上面在设计器中定义的流程部署到activiti数据库中,就是流程定义部署。通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。
2.1.1、单个文件部署方式
public class ProcessTest {
@Autowired
private RepositoryService repositoryService;
@Test
public void deployProcess() {
// 流程部署
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("process/qingjia.bpmn20.xml")
.addClasspathResource("process/qingjia.png")
.name("请假申请流程")
.deploy();
System.out.println(deploy.getId());
System.out.println(deploy.getName());
}
}
2.1.2、压缩包部署方式
public void deployProcessByZip() {
// 定义zip输入流
InputStream inputStream = this
.getClass()
.getClassLoader()
.getResourceAsStream(
"process/qingjia.zip");
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
// 流程部署
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("请假申请流程")
.deploy();
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
2.1.3、操作数据库表
流程定义部署后操作activiti的3张表如下:
act_re_deployment 流程定义部署表,每部署一次增加一条记录
act_re_procdef 流程定义表,部署每个新的流程定义都会在这张表中增加一条记录
act_ge_bytearray 流程资源表
2.2、启动流程实例
流程定义:将bpmn文件放到activiti的三张表中,好比是java中的一个类
流程实例:好比是java中的一个实例对象(一个流程定义可以对应多个流程实例),张三可以启动一个请假流程实例,李四也可以启动一个请假流程实例,他们互不影响
@Autowired
private RuntimeService runtimeService;
@Test
public void startUpProcess() {
//创建流程实例,我们需要知道流程定义的key
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia");
//输出实例的相关信息
System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例id:" + processInstance.getId());
System.out.println("当前活动Id:" + processInstance.getActivityId());
}
操作数据表
act_hi_actinst 流程实例执行历史
act_hi_identitylink 流程的参与用户历史信息
act_hi_procinst 流程实例历史信息
act_hi_taskinst 流程任务历史信息
act_ru_execution 流程执行信息
act_ru_identitylink 流程的参与用户信息
act_ru_task 任务信息
2.3、查询任务
每个节点都配置了Assignee,流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。
@Autowired
private TaskService taskService;
/**
* 查询当前个人待执行的任务
*/
@Test
public void findPendingTaskList() {
//任务负责人
String assignee = "zhangsan";
List<Task> list = taskService.createTaskQuery()
.taskAssignee(assignee)//只查询该任务负责人的任务
.list();
for (Task task : list) {
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
说明:
流程实例id:一个流程只有一个,标识这个流程
任务id:流程每进行到某个节点,就会给这个节点分配一个任务id
输出结果如下:
流程实例id:d969f534-825e-11ed-95b4-7c57581a7819
任务id:d96c3f28-825e-11ed-95b4-7c57581a7819
任务负责人:zhangsan
任务名称:张三审批
2.4、处理当前任务
任务负责人查询待办任务,选择任务进行处理,完成任务。
/**
* 完成任务
*/
@Test
public void completTask(){
Task task = taskService.createTaskQuery()
.taskAssignee("zhangsan") //要查询的负责人
.singleResult();//返回一条
//完成任务,参数:任务id
taskService.complete(task.getId());
}
完成任务后,任务自动到下一个节点
2.5、查询已处理任务
@Autowired
private HistoryService historyService;
/**
* 查询已处理历史任务
*/
@Test
public void findProcessedTaskList() {
//张三已处理过的历史任务
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().taskAssignee("zhangsan").finished().list();
for (HistoricTaskInstance historicTaskInstance : list) {
System.out.println("流程实例id:" + historicTaskInstance.getProcessInstanceId());
System.out.println("任务id:" + historicTaskInstance.getId());
System.out.println("任务负责人:" + historicTaskInstance.getAssignee());
System.out.println("任务名称:" + historicTaskInstance.getName());
}
}
2.6、其他接口(了解)
/**
* 查询流程定义
*/
@Test
public void findProcessDefinitionList(){
List<ProcessDefinition> definitionList = repositoryService.createProcessDefinitionQuery()
.orderByProcessDefinitionVersion()
.desc()
.list();
//输出流程定义信息
for (ProcessDefinition processDefinition : definitionList) {
System.out.println("流程定义 id="+processDefinition.getId());
System.out.println("流程定义 name="+processDefinition.getName());
System.out.println("流程定义 key="+processDefinition.getKey());
System.out.println("流程定义 Version="+processDefinition.getVersion());
System.out.println("流程部署ID ="+processDefinition.getDeploymentId());
}
}
/**
* 删除流程定义
*/
public void deleteDeployment() {
//部署id
String deploymentId = "82e3bc6b-81da-11ed-8e03-7c57581a7819";
//删除流程定义,如果该流程定义已有流程实例启动则删除时出错
repositoryService.deleteDeployment(deploymentId);
//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式
//repositoryService.deleteDeployment(deploymentId, true);
}