1、什么是工作流&工作流引擎
1.1、工作流介绍
# 工作流
工作的一个流程,事物发展的一个过程。
比如说:
我们请假的流程就是一个工作流:
1、填写申请单
2、部门经理审批
3、人事存档
那么这种工作流在传统的来说,就是给你一张纸纸,你填写申请信息,然后给经理审核,再给人事存档。
那么我们互联网时代,都说的是无纸化办公、自动化控制,也就是这样:线上填写申请单--线上审批--完成请假
1.2、工作流引擎
# 工作流引擎
为了实现我们的无纸化办公、自动化控制,那么就需要工作流引擎来完成。
工作流引擎提供了一套工作流的解决方案,如果不使用那么我们全程都需要自己来编写,极其的恶心和麻烦,有了工作流引擎以后,就为我们提供了很多解决方案供我们来使用,我们只需要关心其业务,而不需要去关心其工作流的实现。
1.3、工作流系统
# 工作流系统
如果一个系统具备流程的自动化管理的功能,那么这个系统就可以被称为**工作流系统**
# 那么工作流系统可以如何来实现呢?举例一个请假的流程
流程定义:填写请假单->部门经理审批->人事经理审批->总经理审批->人事存档->请假成功
现在我们的流程定义弄出来了,我们如何设计表呢?
其实设计这个请假表的基础字段并不难,例如工号、部门、姓名等,重要的是除了这些基础字段
以外,我们还需要一个状态(state)字段来决定当前请假的状态,例如:
0:填写未提交,1:填写已提交
2:部门经理审批通过,3:部门经理审批未通过
4:总经理审批通过,5:总经理审批未通过
..........
这个时候我们如果要完成整个流程的代码实现,那么就需要在各个模块的代码里面来判断其state字段。
判断state字段后才知道当前流程到哪了,该谁来处理,这种方法肯定可以实现工作流。
但是手动编码实现会有一些问题:
1:如果流程中出现分支的情况(例如请假大于3天需要总经理审批,否则不需要经过总经理),那么处理较为繁琐。
2:如果流程中出现两个操作同时完成后再进入下一个操作的情况,编码实现较为复杂。
# 工作流系统适用行业
消费品行业,制造业,电信服务业,银证险等金融服务业,物流服务业,物业服务业,物业管理,大中型进出口贸易公司,政府事业机构,研究院所及教育服务业等,特别是大的跨国企业和集团公司。
# 具体有哪些应用的场景
1. 关键业务流程:订单、报价处理、合同审核、客户电话处理、供应链管理等
2. 行政管理类:出差申请、加班申请、请假申请、用车申请、各种办公用品申请、购买申请、日报周报等凡是原来手工流转处理的行政表单。
3. 人事管理类:员工培训安排、绩效考评、职位变动处理、员工档案信息管理等。
4. 财务相关类:付款请求、应收款处理、日常报销处理、出差报销、预算和计划申请等。
5. 客户服务类:客户信息管理、客户投诉、请求处理、售后服务管理等。
6. 特殊服务类: ISO 系列对应流程、质量管理对应流程、产品数据信息管理、贸易公司报关处理、物流公司货物跟踪处理等各种通过表单逐步手工流转完成的任务均可应用工作流软件自动规范地实施。
1.4、工作流的实现方式
# 在没有工作流引擎之前
在没有工作流引擎之前,我们只有靠一个state字段来判断当前流程状态,全程的逻辑都要紧紧依靠这个state字段,如果流程复杂,有分支,有共同操作这种,就很恼火了,不仅编码难度提高,且一旦更改需求,维护极其的困难。
# 有了工作流引擎之后
核心的流程控制交给工作流引擎来完成,我们不需要关心他到底是何如完成流程控制的,我们只管业务。
2、什么是Activiti7
2.1、Activiti介绍
# 介绍:
Alfresco软件在2010年5月17日宣布Activiti业务流程管理(BPM)开源项目的正式启动,其首席架构师由业务流程管理BPM的专家Tom Baeyens担任,Tom Baeyens就是原来jbpm 的架构师,而jbpm是一个非常有名的工作流引擎,当然Activiti也是一个工作流引擎。Activiti是一个工作流引擎,Activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言(BPMN2.0)进行定义,业务系统按照预先定义的流程进行执行,实现了业务系统的业务流程由Activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
# 官网:
https://www.activiti.org/
# 前面提到业务流程管理(BPM),那么BPM是什么
BPM(Business Process Management),即业务流程管理,是一种以规范化的构造端到端的卓越业务流程为中心,以持续的提高组织业务绩效为目的系统化方法,常见商业管理教育如 EMBA、MBA等均将BPM包含在内。
# 那么BPM软件是啥
BPM 软件就是根据企业中业务环境的变化,推进人与人之间、人与系统之间以及系统与系统之间的整合及调整的经营方法与解决方案的IT工具。通常以Internet方式实现信息传递、数据同步、业务监控和企业业务流程的持续升级优化,从而实现跨应用、跨部门、跨合作伙伴与客户的企业运作。通过 BPM 软件对企业内部及外部的业务流程的整个生命周期进行建模、自动化、管理监控和优化,使企业成本降低,利润得以大幅提升。BPM 软件在企业中应用领域广泛,凡是有业务流程的地方都可以BPM软件进行管理,比如企业人事办公管理、采购流程管理、公文审批流程管理、财务管理等。
# 上面提到BPMN2.0建模语言,那么它是啥
BPMN(Business Process Model And Notation)-业务流程模型和符号是由BPMI(BusinessProcess Management Initiative)开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。2004 年5月发布了BPMN1.0规范.BPMI于2005年9月并入OMG(The Object Management Group 对象管理组织)组织。OMG于2011年1月发布BPMN2.0的最终版本。
BPMN图形其实是用XML来存储表示的。
大概有这些图形:
https://img2020.cnblogs.com/blog/1597149/202008/1597149-20200816125024968-1065928745.png
不用每个图形非要知道的很详细的意思,慢慢用了就知道了。
2.2、如何去使用Activiti
# 1、部署Activiti
Activiti 是一个工作流引擎(其实就是一堆jar包API),业务系统使用 activiti 来对系统的业务流程进行自动化管理,为了方便业务系统访问(操作)activiti的接口或功能,通常将activiti环境与业务系统的环境集成在一起。
# 2、流程定义
使用Activiti流程建模工具(activity-designer)定义业务流程(.bpmn 文件)。
.bpmn文件就是业务流程定义文件,通过xml定义业务流程。
建模工具不只是这一个工具,其他的都可以,反正都会支持拖拽方式的操作。
# 3、流程部署
向Activiti部署业务流程文件(.bpmn文件)。
使用Activiti提供的API交给Activiti我们的bpmn文件(一般情况还得交一个png文件,png不交也行)。
# 4、启动一个流程实例(ProcessInstance)
启动一个流程实例表示开始一次业务流程的运行,比如员工请假流程部署完成,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响,就好比定义一个java类,实例化两个对象一样,部署的流程就好比java类,启动一个流程实例就好比new一个java对象。
# 5、用户查询待办任务(Task)
因为现在系统的业务流程已经交给activiti管理,通过activiti就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些activiti帮我们管理了,而不像上边需要我们在sql语句中的where条件中判断状态(state)值是多少。
# 6、用户办理任务
用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如采购单创建后由部门经理审核,这个过程也是由activiti帮我们完成了,不需要我们在代码中硬编码指定下一个任务办理人了。
# 7、流程结束
当任务办理完成没有下一个任务/结点了,这个流程实例就完成了。
3、环境准备
3.1、基本开发环境
# JDK1.8、MySQL5
3.2、Activiti环境
# 0、可以查看activiti示例工程学习
github地址:https://github.com/Activiti/activiti-examples
clone:https://github.com/Activiti/activiti-examples.git
# 1、环境依赖
依赖文件地址:files.cnblogs.com/files/daihang2366/activitidemo1.zip
# 2、需要数据库的支持
activiti运行需要有数据库的支持,支持的数据库有:h2,mysql,oracle,postgres,mssql,db2等.
我们这个案例使用MySQL,在数据库中建立表**activitidemo**
# 3、加入配置文件放置到resources下
文件地址:files.cnblogs.com/files/daihang2366/demo.zip
# 4、IDEA安装插件
插件名称:actiBPM,提示:2019.3的版本好像搜不到这个玩意,解决方案参考网上的一篇博客:
https://blog.csdn.net/weixin_44467567/article/details/103876746?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param
3.3、使用Activiti前提
3.3.1、Activiti支持的数据库
3.3.2、简述项目配置&创建表方式
简述配置:
首先哈,一定要准备一个数据库,我这里的名称叫做:activitidemo
然后我们安装上面的操作完成环境的搭建后。
简单的查看activiti.cfg.xml配置文件的内容,一个是配置连接池,一个是 processEngineConfiguration (流程引擎配置)对象,它用来创建ProcessEngine对象,在创建ProcessEngine的时候会执行数据库的操作。然后我们看里面配置了一个databaseSchemaUpdate的值为true,代表着如果数据库中没有activiti的表,那么将自动创建,关于databaseSchemaUpdate的取值有这些:
# false(默认):检查数据库表的版本和依赖库的版本, 如果版本不匹配就抛出异常。
# true: 构建流程引擎时,执行检查,如果需要就执行更新。 如果表不存在,就创建。
# create-drop: 构建流程引擎时创建数据库表, 关闭流程引擎时删除这些表。
# drop-create:先删除表再创建表。
# create: 构建流程引擎时创建数据库表, 关闭流程引擎时不删除这些表
编写代码来创建表:
public static void main(String[] args) {
/*
* 使用ProcessEngineConfiguration.createProcessEngineConfigurationFromResource方法加载配置文件
* 得到ProcessEngineConfiguration对象,然后调用buildProcessEngine去创建表这些操作
* */
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.
createProcessEngineConfigurationFromResource("activiti.cfg.xml");
/**
* 得到流程引擎对象
*/
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
System.out.println(processEngine);
}
/*
1、运行以上的程序即可完成activiti数据库的表工作,通过改变配置文件中配置processEngineConfiguration 的databaseSchemaUpdate的值来改变执行不同的数据库表策略。
2、上面在执行createProcessEngineConfigurationFromResource方法的时候,会去寻找id为processEngineConfiguration的bean,如果我们不想要bean的名称为processEngineConfiguration,也可以在其方法后加入一个String参数,加入的参数即为去寻找的bean的名称。
否则在执行程序的时候就会报错没有bean的名称为processEngineConfiguration 。
*/
执行上面的代码以后,如果没有报错,那么数据库将会多出25张表。
3.3.3、数据库表简述
# 注意
activiti中的表都是以act开头的。
第二部分是表示表的用途的两个字母标识。用途也和服务的API对应
# 表分别的用处
ACT_RE_*:'RE'表示repository。这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等).
ACT_RU_*:'RU'表示runtime。这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti,只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。这样运行时 表可以一直很小速度很快。
ACT_HI_*:'HI'表示history。这些表包含历史数据,比如历史流程实例,变量,任务等等。
ACT_GE_*:GE表示general。通用数据,用于不同场景下。
4、Activiti结构
最上面的就是流程引擎配置对象,它需要配置文件,配置文件默认名称为activiti.cfg.xml,然后下面的就是流程引擎,然后我们实际用的时候,就是运用各种Service
注意:在新版本中IdentityService和FormService都被删了。
4.1、ProcessEngineConfiguration&activiti.cfg.xml
4.1.1、 StandaloneProcessEngineConfiguration
通过该对象我们的Activiti可以单独运行,使用它去创建ProcessEngine,Activiti会自己处理事务。
4.1.2、 SpringProcessEngineConfiguration
使用 SpringProcessEngineConfiguration 和Spring整合。
4.1.3、创建ProcessEngineConfiguration
指定配置文件:
ProcessEngineConfiguration configuration =ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml")
4.2、ProcessEngine
工作流引擎,相当于一个门面接口,通过ProcessEngineConfiguration创建processEngine,通过ProcessEngine创建各个service接口。
一般我们都直接使用流程引擎配置对象来获取
public static void main(String[] args) {
/*
* 使用ProcessEngineConfiguration.createProcessEngineConfigurationFromResource方法加载配置文件
* 得到ProcessEngineConfiguration对象,然后调用buildProcessEngine去创建表这些操作
* */
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
/**
* 得到流程引擎对象
*/
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
System.out.println(processEngine);
}
如果我们不指定配置文件:
那么其就会去我们的resources下找activiti.cfg.xml配置文件,且配置文件中必须有一个ProcessEngineConfiguration对象,且id为processEngineConfiguration,然后代码就是这样的:
public static void main(String[] args) {
/**
* 使用默认配置,直接获取流程引擎对象
*/
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine);
}
4.3、Service
Service是工作流引擎提供用于进行工作流部署,执行,管理的服务接口。
4.3.1、Service创建方式
通过ProcessEngine来获得Service,具体使用的方法是这样的:
processEngine.getXxxxxService(),这个Xxx就是具体的Service名称。
例如获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
4.3.2、Service总览
RepositoryService | Activiti的资源管理类 |
---|---|
RuntimService | Activiti的流程运行管理类 |
TaskService | Activiti的任务管理类 |
HistoryService | Activiti的历史管理类 |
ManagerService | Activiti引擎的管理类 |
注:Activiti7中去除了IdentityService和FormService。然后ManagerService为不常用的Service,其他的四个为常用的Service。
4.3.3、RepositoryService
Repository是Activiti的资源管理类,提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此Service将流程定义文件的内容部署到计算机。
除了部署流程定义外还可以做:
查询引擎中的发布包和流程定义。
暂停或激活发布包,对应全部和特定流程定义。暂停意味着它不能再执行任何操作了,激活时对应的反向操作。
获得多种资源,比如包含在发布包里的文件,或引擎自动生成的流程图。
获得流程定义的Pojo版本,可以用来通过Java解析流程,而不必通过XML
4.3.4、RuntimeService
RuntimeService是流程运行管理类。可以从这个类中获取很多关于流程执行相关的信息。
4.3.5、TaskService
TaskService是任务管理类。可以从这个类中获取任务的信息。
4.3.6、HistoryService
HistoryService是历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(可以配置修改),比如流程实例启动时间,任务的参与者,完成任务的时间,每个流程实例的执行路径等等。这个类主要用来查询这些数据。
4.3.7、ManagementService
ManagementService是引擎管理类,提供了对Activiti流程引擎的管理和维护工作,这些功能不在工作流驱动的应用程序中使用,主要是用于Activiti系统的日常维护。
5、Activiti入门体验
5.1、流程定义
我们要来体验一把Activiti的话,那么我们肯定要先来定义流程,比如说请假这种,先去干啥,后去干啥,这些都要进行定义,这里我们使用IDEA的acitBPM来做
5.1.1、使用工具建模
新建流程holiday,扩展名默认为bpmn:
然后出现如下界面:
左边的是当前选中节点的信息,中间是主界面,右边是流程定义使用的图形。
流程定义图形的大概介绍:
Connection—连接
Event---事件
Task---任务
Gateway---网关
Container—容器
Boundary event—边界事件
Intermediate event- -中间事件
基本使用:
我们肯定是开始,那么就需要一个StartEvent代表开始,然后使用UserTask代表一个任务,然后将StartEvent连接到UserTask上,然后再一个一个连接上,连接的方法就是用鼠标碰到StartEvent然后会有一个小黑点,然后按住拖拽到需要连接的节点上即可完成连接,最后当完成后需要一个EndEvent来结尾,我们来看一个很简单的绘制:
当然我们显示的文字也可以更改,双击即可更改,修改后可以变成这样:
5.1.2、绘制流程
上面已经说了绘制的方法,我们这里直接贴绘制好的样子:
bpmn文件:
png文件:
png文件的生成方式为:先将bpmn文件修改后缀为xml,然后点击 Desiger ,再点击上角的 Export to file 即可导出为png文件。
5.1.3、指定流程Key
流程定义 key 即流程定义的标识,在 eclipse 中通过 properties 视图查看流程的 key
建议:相同的业务流程,流程定义的 key 名字定义一样,比如,如果需要创建新的业务流程,请假流程则使用新的 key。
随便点击一个空白的地方即可设置流程的Key和Name,不同的流程定义最好不要设置为相同的Key。
5.1.4、指定任务的负责人
每一个人物都应该有一个自己的负责人,比如说填写请假申请单是zhangsan,部门经理审核是lisi这种,但是在我们的实际应用中,这些负责人应该是根据业务来指定的,这里我们为了体验一把,就直接写死了
我们选中一个节点,然后在左侧的Assignee中填写负责人名称,我们这里依次填入zhangsan、lisi、wangwu、zhaoliu。
5.2、部署流程定义
部署流程定义就是将上面绘制的图形,也就是流程定义(bpmn文件)部署到activiti工作流引擎中去,这里我们使用RepositoryService来完成部署的操作
首先我们定义一个方法用来获取ProcessEngine对象,所以我们后面这种代码我们就不在每次都手写了
public static ProcessEngine getProcessEngine(){
/*
使用这个方法的条件:
1、resources下游activiti.cfg.xml文件
2、配置了一个bean的名称为processEngineConfiguration
*/
return ProcessEngines.getDefaultProcessEngine();
}
然后就是我们的流程定义的部署代码
public static void main(String[] args) {
ProcessEngine processEngine = getProcessEngine();
/**
* 使用ProcessEngine对象来获得RepositoryService对象
* */
RepositoryService repositoryService = processEngine.getRepositoryService();
/**
* 使用RepositoryService来获取部署(Deployment)对象,然后指定我们的bpmn和png文件位置,指定流程名称,然后deploy方法完成部署
* */
Deployment deployment = repositoryService.createDeployment() //创建Deployment对象
.addClasspathResource("process/holiday.bpmn") // 加入我们的bpmn文件
.addClasspathResource("process/holiday.png") // 加入我们的png图片文件,也可以不见
.disableSchemaValidation() // 设置不进行框架校验,防止有时候bpmn文件报错
.name("请假流程") // 设置流程定义名称
.deploy(); // 部署
System.out.println("流程部署ID:" + deployment.getId()); // 获得流程部署ID
System.out.println("流程部署名称:" + deployment.getName()); // 获得流程部署名称
}
5.3、启动一个流程实例
流程定义部署在Activiti后就可以通过工作流管理业务流程,也就是说上面部署的流程可以使用了。
针对流程,我们需要启动一个流程来表示发起一个请假单,这就相当于Java类和Java对象的区别。
流程定义就好比一个模板,而流程实例则是根据此模板创建的具体实例
代码:
public static void main(String[] args) {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*获得RuntimeService*/
RuntimeService runtimeService = processEngine.getRuntimeService();
/*根据流程定义Key来启动流程实例,此处我们设置的key可以在表:act_re_procdef流程定义表中找到*/
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
System.out.println("流程定义iID:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例ID:" + processInstance.getId());
}
运行后我们在act_hi_actinst表中发现有两条记录,一个是开始请假流程,一个是填写请假申请,两个钟后者没有结束时间,代表还没做完,我们还可以在act_ru_task表中发现当前需要执行的任务。
5.4、任务查询
实例启动后,各个人物的负责人就可以查询到当前自己需要处理的任务,查询出来的任务都是该用户的待办任务
代码:
public static void main(String[] args) {
/*任务执行人名称*/
String assigne = "zhangsan";
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*获得任务的Service*/
TaskService taskService = processEngine.getTaskService();
/*通用TaskService获取查询对象后再查询到我们想要的任务信息*/
List<Task> holidayTaskList = taskService.createTaskQuery() // 创建任务查询对象
.processDefinitionKey("holiday") // 加入条件指定流程定义Key
.taskAssignee(assigne) // 加入条件指定任务执行人名称
.list(); // 返回list结果
/*循环遍历*/
for (Task task : holidayTaskList) {
System.out.println(task);
}
}
我们查询完成打印出来后我们会发现,这些信息都是对应着act_ru_task表的。
5.5、任务处理
使用TaskService的complete方法根据任务ID来处理任务
public static void main(String[] args) {
/*前面我们查询到的任务信息:Task[id=2505, name=填写请假申请]*/
String taskId = "2505";
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*获得任务的Service*/
TaskService taskService = processEngine.getTaskService();
/*处理任务ID*/
taskService.complete(taskId);
}
这里执行后我们看act_hi_actinst表:
我们可以看到。现在已经完成了填写请假申请的任务,然后到了部门经理审核的步骤。当我们全部的任务都走完后,就是这个样子:
初体完了后,我们就开始后续的学习
6、流程定义
6.1、流程定义
6.1.1、什么是流程定义
流程定义是线下按照bpmn2.0标准去描述业务流程,通常使用专门的工具进行绘制,但是都会遵守BPMN2.0标准。本文的案例都是使用IDEA插件进行绘制。我们一般绘制完.bpmn文件后都会生成png文件,不同工具生成png的方式不同,具体自行百度。
6.1.2、.bpmn文件
.bpmn文件是流程定义的源文件,实际上就是一个xml,其中保存了流程的所有内容,我们部署的时候就是将此文件部署到activiti中,activiti就会自动解析此文件。
6.1.3、.png图片文件
我们可以将.bpmn文件导出为png文件,导出后就是一张普通的图片,不同的工具具体导出的方法不一样,这个可以自行百度。
6.2、流程定义部署
6.2.1、什么是流程定义部署
将线下定义的流程部署到activiti数据库中,这就是流程定义部署,通过调用activiti的api将流程定义的bpmn和png两个文件添加部署到activiti中,也可以将这个两个文件打成zip包进行部署。
6.2.2、单个文件部署方式
分别将bpmn和png文件进行部署:
public static void main(String[] args) {
ProcessEngine processEngine = getProcessEngine();
/**
* 使用ProcessEngine对象来获得RepositoryService对象
* */
RepositoryService repositoryService = processEngine.getRepositoryService();
/**
* 使用RepositoryService来获取部署(Deployment)对象,然后指定我们的bpmn和png文件位置,指定流程名称,然后deploy方法完成部署
* */
Deployment deployment = repositoryService.createDeployment() // 创建Deployment对象
.addClasspathResource("process/holiday.bpmn") // 加入我们的bpmn文件
.addClasspathResource("process/holiday.png") // 加入我们的png图片文件,也可以不见
.disableSchemaValidation() // 设置不进行框架校验,防止有时候bpmn文件报错
.name("请假流程") // 设置流程定义名称
.deploy(); // 部署
System.out.println("流程部署ID:" + deployment.getId()); // 获得流程部署ID
System.out.println("流程部署名称:" + deployment.getName()); // 获得流程部署名称
}
6.2.3、压缩包部署方式
首先将这bpmn和png压缩到zip中:
public static void main(String[] args) {
ProcessEngine processEngine = getProcessEngine();
/**
* 使用ProcessEngine对象来获得RepositoryService对象
* */
RepositoryService repositoryService = processEngine.getRepositoryService();
/**
* 获取holiday.zip文件为ZipInputStream
* */
ZipInputStream zipInputStream = new ZipInputStream(Test6.class.getClassLoader().getResourceAsStream("process/holidayZip.zip"));
/**
* */
Deployment deployment = repositoryService.createDeployment() // 创建Deployment对象
.addZipInputStream(zipInputStream) // 将输入流加入到Activiti
.disableSchemaValidation() // 设置不进行框架校验,防止有时候bpmn文件报错
.name("请假流程") // 设置流程定义名称
.deploy(); // 部署
System.out.println("流程部署ID:" + deployment.getId()); // 获得流程部署ID
System.out.println("流程部署名称:" + deployment.getName()); // 获得流程部署名称
}
6.2.4、操作数据表
流程定义部署后操作的表有这几个:
SELECT * FROM act_re_deployment; # 流程定义部署表,记录流程部署信息
SELECT * FROM act_re_procdef; # 流程定义表,记录流程定义信息
SELECT * FROM act_ge_bytearray; # 资源表
注意deployment和procdef是一对多关系,就是说一次部署可以部署多个流程定义,那么如果一次部署多个流程定义,那么deploymen中只会有一条记录,而procdef因为多个流程定义所以多个表。
但是我们通常都是一次部署一个流程定义,尽量一对一。
6.3、流程定义查询
查询部署的流程定义:
public static void main(String[] args) {
/*流程定义的Key,这个key可以在act_re_procdef表中找到*/
String processDefinitionKey = "holiday";
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*获得RepositoryService*/
RepositoryService repositoryService = processEngine.getRepositoryService();
/*获得查询流程定义的对象*/
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
/*查询结果,一个结果是一个流程定义对象*/
List<ProcessDefinition> processDefinitionList = processDefinitionQuery
.processDefinitionKey(processDefinitionKey) // 指定流程定义key
.orderByProcessDefinitionVersion() // 按照流程定义版本来排序
.desc() // 指定排序规则为desc降序
.list(); // 返回list结果
/*循环遍历结果*/
for (ProcessDefinition processDefinition : processDefinitionList) {
System.out.println("流程定义ID:"+processDefinition.getId());
System.out.println("流程定义名称:"+processDefinition.getName());
System.out.println("流程定义版本:"+processDefinition.getVersion());
System.out.println("流程定义资源文件:"+processDefinition.getResourceName());
System.out.println("流程定义PNG文件:"+processDefinition.getDiagramResourceName());
}
}
6.4、流程定义删除
删除已经部署的流程:
public static void main(String[] args) {
/*部署ID*/
String deploymentId = "2501";
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*如果该流程实例没有启动,则可以直接使用这个方法*/
// processEngine.getRepositoryService().deleteDeployment(deploymentId);
/*如果流程实例已经启动那么删除的时候就会报错,提前外键啥玩意的,那么这个时候就需要级联删除,使用这个方法,在后面加一个true*/
processEngine.getRepositoryService().deleteDeployment(deploymentId,true);
}
6.5、流程定义资源查询
通过流程定义对象获取流程定义资源,获取bpmn和png文件:
public static void main(String[] args) throws Exception {
/*流程定义ID*/
String processDefinitionKey = "holiday";
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*获取到RepositoryService*/
RepositoryService repositoryService = processEngine.getRepositoryService();
/*获得流程定义对象*/
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey(processDefinitionKey)
.singleResult();
/*获取bpmn文件名称和png文件名称*/
String bpmnFileName = processDefinition.getResourceName();
String pngFileName = processDefinition.getDiagramResourceName();
/*调用repositoryService.getResourceAsStream的方法传入部署ID和资源名,可以获得对应资源的输入流*/
InputStream bpmnFileInputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), bpmnFileName);
InputStream pngFileInputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), pngFileName);
/*然后定义两个输出流,将这两个文件写入到我们的磁盘中去*/
FileOutputStream bpmnFileOutputStream = new FileOutputStream("D:\\" + bpmnFileName);
FileOutputStream pngFileOutputStream = new FileOutputStream("D:\\" + pngFileName);
/*为了限制代码长度,写入的代码我们直接调用方法,此处调用方法的代码就不贴出来了,相信你也会写*/
copy(bpmnFileInputStream, bpmnFileOutputStream);
copy(pngFileInputStream, pngFileOutputStream);
}
其实不管你怎么做,只要你能拿到该流程定义的部署ID和文件名,那你就可以拿文件。以上的操作方式并不是唯一拿到部署ID的方法。
6.6、流程历史信息的查看
即使流程定义已经删除了,流程执行的历史信息通过前面的分析,依然保存在 activiti 的 act_hi_*相 关的表中。所以我们还是可以查询流程执行的历史信息,可以通过 HistoryService 来查看相关的历史 记录。
public static void main(String[] args) throws Exception {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*流程实例ID*/
String instanceId = "10001";
/*获得历史信息查询对象*/
HistoricActivityInstanceQuery historicActivityInstanceQuery = processEngine.getHistoryService().createHistoricActivityInstanceQuery()
.processInstanceId(instanceId);
/*获得查询结果*/
List<HistoricActivityInstance> historicActivityInstanceList = historicActivityInstanceQuery.list();
/*遍历*/
for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
System.out.println(historicActivityInstance);
}
}
当然我们不仅仅只能用流程实例ID来查询,也可以使用流程定义Key来查询,具体翻翻HistoricActivityInstanceQuery的API就知道了,此处不作详细解释。
7、流程实例
7.1、什么是流程实例
参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是流程实例。它是动态的。
7.2、启动流程实例
流程定义部署在Activiti后,就可以在系统中通过Activiti去管理该流程的执行,执行流程表示流程的一次执行。
比如部署系统请假流程后,如果某个用户要申请请假这时就需要执行执行这个流程(也就是启动一个流程实例),如果另一个用户也要请假,那么也需要执行这个流程。不同实例之前互不影响,都是独立的。
启动流程实例的的代码:
public static void main(String[] args) throws Exception {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key*/
String processDefinitionKey = "holiday";
/*获取RuntimeService*/
RuntimeService runtimeService = processEngine.getRuntimeService();
/*根据流程定义Key来启动流程实例*/
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey);
System.out.println("流程定义Key:"+processInstance.getProcessDefinitionId());
System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
System.out.println("当前活动ID:"+processInstance.getActivityId());
}
7.3、BusinessKey(业务标识)
BusinessKey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源与业务系统。业务标识就是根据业务标识来关联查询业务系统的数据。
比如我们启动一个请假的流程实例以后,我们业务系统中肯定要存储该流程的一些信息,那么我们就可以将我们业务系统中请假表的id放置到businessKey当中去。
public static void main(String[] args) throws Exception {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key*/
String processDefinitionKey = "holiday";
/*BusineeKey*/
String busineeKey = "4399";
/*获取RuntimeService*/
RuntimeService runtimeService = processEngine.getRuntimeService();
/*根据流程定义Key来启动流程实例,第一个参数是流程定义id,第二个是我们的自定义的业务id(BusineeKey)*/
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey,busineeKey);
System.out.println("流程定义Key:"+processInstance.getProcessDefinitionId());
System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
System.out.println("当前活动ID:"+processInstance.getActivityId());
}
此时我们查看act_ru_execution表,
7.4、操作数据库表
启动流程实例后,操作如下表:
1- SELECT * FROM act_ru_execution; # 流程实例执行表,记录当前流程实例的执行情况
流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键id和流程实例id相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例id不相同的记录。不论当前有几个分支总会有一条记录的执行表的主键和流程实例id相同一个流程实例运行完成,此表中与流程实例相关的记录删除。
2- SELECT * FROM act_ru_task; # 任务执行表,记录当前执行的任务
说明:启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除。
3- SELECT * FROM act_ru_identitylink; # 任务参与表,记录当前参与任务的用户或组
4- SELECT * FROM act_hi_procinst; # 流程实例历史表
流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。
5- SELECT * FROM act_hi_taskinst; # 任务历史表,记录所有任务
开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务 id,任务完成此表记录不删除。
6- SELECT * FROM act_hi_actinst; # 活动历史表,记录所有活动
活动包括任务,所以此表中不仅记录了任务,还记录了流程执行过程的其它活动,比如开始事件、结束事件。
7.5、查询流程实例
查询流程实例的状态,当前运行的结点信息:
public static void main(String[] args) throws Exception {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key*/
String processDefinitionKey = "holiday";
/*获取RuntimeService*/
RuntimeService runtimeService = processEngine.getRuntimeService();
/*
* 使用RuntimeService获取流程实例查询器,然后加入条件指定流程定义key,然后list结果
* */
List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).list();
for (ProcessInstance processInstance : processInstanceList) {
System.out.println("流程实例ID:" + processInstance.getProcessInstanceId());
System.out.println("流程定义ID:" + processInstance.getProcessDefinitionId());
System.out.println("是否已经完成:" + processInstance.isEnded());
System.out.println("是否暂停" + processInstance.isSuspended());
System.out.println("当前活动标识:" + processInstance.getActivityId());
System.out.println("当前的BusinessKey:" + processInstance.getBusinessKey());
}
}
7.5.1、关联BusinessKey
需求:
在activiti实际应用时,查询流程实例列表时可能要显示出业务系统的一些相关信息,比如查询当前运行的请假流程列表需要将请假单名称、请假天数等信息显示出来,请假天数等信息在业务系统中存在,而没有在activiti数据库中和存在,所以无法通过activiti的api查询到请假天数等信息。
实现:
在查询流程实例时,可以通过businessKey[业务标识]关联查询业务系统的请假单表,查询出请假天数等信息。
我们的ProcessInstance对象也可以获得businessKey,我们可以通过这个字段开进行与业务系统的外键关联,达到通过activiti中数据关联业务系统数据的功能。
businessKey这个字段存在于act_ru_execution这个表当中。
7.6、挂起、激活流程实例
某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行(这就是挂起),然后我们还可以将流程继续执行(这就是激活)。
7.6.1、全部流程实例挂起/激活
/**
* @author 全部流程实例挂起/激活
* <p>
* 全部流程实例挂起也就是直接将流程定义给暂停了
*/
public class Test14 {
public static void main(String[] args) throws Exception {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key,如果查询全部的流程定义,那么直接空串即可*/
String processDefinitionKey = "holiday";
/*通过流程定义key查询到该流程定义*/
ProcessDefinition processDefinition = processEngine.getRepositoryService().createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey).singleResult();
/*这个标识就是代表着该流程实例是否已经暂停*/
boolean suspended = processDefinition.isSuspended();
/*如果已经暂停了,那么我们给激活*/
if (suspended) {
/*激活一下,使用RepositoryService来进行激活
* 参数一:需要激活的流程定义id
* 参数二:是否激活的标志,如果true,代表激活
* 参数三:操作时间
* */ processEngine.getRepositoryService().activateProcessDefinitionById(processDefinition.getId(), true, null);
System.out.println("流程定义:" + processDefinition.getId() + " 激活");
} else { /*如果已经激活了,那么我们给暂停*/
/*暂停一下
* 参数一:需要激活的流程定义id
* 参数二:是否暂停的标志,如果true,代表激活
* 参数三:操作时间
* */
processEngine.getRepositoryService().suspendProcessDefinitionById(processDefinition.getId(), true, null);
System.out.println("流程定义:" + processDefinition.getId() + " 暂停");
}
}
public static ProcessEngine getProcessEngine() {
return ProcessEngines.getDefaultProcessEngine();
}
}
7.6.2、单个流程实例挂起/激活
/**
* @author 单个流程实例挂起/激活
* <p>
* 将指定流程实例将其暂停/激活(使用的是流程实例ID作为区分,我们这里先查询出来流程实例)
*/
public class Test15 {
public static void main(String[] args) throws Exception {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key,如果查询全部的流程定义,那么直接空串即可*/
String processDefinitionKey = "holiday";
/*这里查询的是流程实例,运行的时候的东西,所以我们使用RuntimeService*/
/*我们现在这里流程定义key为holiday的流程实例只有一个,ID为2501,所以我们直接singleResult*/
ProcessInstance processInstance = processEngine.getRuntimeService().createProcessInstanceQuery().processDefinitionKey(processDefinitionKey).singleResult();
/*是否暂停*/
boolean suspended = processInstance.isSuspended();
if (suspended){ /*如果暂停了,那么我们现在就激活*/
/*针对某个流程实例的话,我们直接传入实例ID就行了*/
processEngine.getRuntimeService().activateProcessInstanceById(processInstance.getProcessInstanceId());
System.out.println("激活:" + processInstance.getProcessInstanceId());
}else { /*如果激活了,那么我们现在就暂停*/
processEngine.getRuntimeService().suspendProcessInstanceById(processInstance.getProcessInstanceId());
System.out.println("暂停:" + processInstance.getProcessInstanceId());
}
}
public static ProcessEngine getProcessEngine() {
return ProcessEngines.getDefaultProcessEngine();
}
}
注意:我们暂停了的流程是无法再继续的,如果继续,则会报错:Cannot complete a suspended task
8、个人任务
我们的流程需要人来执行,那么这个时候我们就可以给每一个节点都分配自己的责任人(执行人)。
8.1、分配任务责任人
8.1.1、固定分配
在进行业务流程建模时指定固定的任务责任人。
Assignee就是受理人的意思,也就是负责人。
8.1.1.1、注意事项
固定分配的方式,任务一步一步执行下去的时候,指定到某一个节点则会按照这个节点的配置去分配任务负责人。
8.1.2、表达式分配
8.1.2.1、UEL表达式
Activiti使用UEL表达式,UEL是JavaEE6规范的一部分,UEL(Unified Expression Language)即统一表达式语言,Activiti支持两个UEL表达式,即UEL-value和UEL-method。
UEL-value的定义如下示例
:
assignee这个变量是activiti的一个流程变量。或者如下
user是activiti的一个流程变量,user.name表示通过调用user的getName方法获取值。
UEL-method的定义如下示例:
调用方法来获取一个值。
UEL-method和UEL-value的结合的示例:
Assignee:${user.getNameById(name)}
user和name都是流程变量,然后调用user的getNameById方法,传入参数name,获得结果值。
其他:
其实这个地方还可以书写表达式,也就是说可以做条件判断,如下:
${user.age > admin.age},这个值就是一个boolean值
8.1.2.2、使用流程变量分配
我们给第一个流程的assignee指定为${name}。然后我们部署流程定义、启动流程实例(这时设置流程变量):
public static void main(String[] args) {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*获得RuntimeService*/
RuntimeService runtimeService = processEngine.getRuntimeService();
/*此Map的内容作为流程变量*/
Map<String,Object> map = new HashMap<>();
map.put("name","zhangsan");
/*根据流程定义Key来启动流程实例,然后第二个参数就是我们的流程变量*/
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday",map);
System.out.println("流程定义iID:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例ID:" + processInstance.getId());
}
8.1.2.3、注意事项
如果使用了表达式分配,那么就必须将我们表达式中使用到的流程变量设置进去,否则就会出现错误。
8.1.3、监听器分配
任务监听器是发生对应的任务相关事件时执行自定义Java代码或者表达式。
任务监听事件包括:
Create:任务创建后触发
Assignment:任务分配后触发
Delete:任务完成后触发
All:所有事件发生都触发
Java逻辑或表达式:
表达式参考上面的UEL表达式,这里来看监听器的使用。
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setAssignee("ssss"); // 设置任务负责人
}
}
8.1.3.1、注意事项
我们的监听器类要实现org.activiti.engine.delegate.TaskListener接口,使用监听器分配方式,按照监听事件去执行监听类的notify方法,方法如果不能正常执行(例如抛出异常)也会影响任务的执行。
8.2、查询任务
查询负责人待办的任务:
public static void main(String[] args) throws Exception {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key,如果查询全部的流程定义,那么直接空串即可*/
String processDefinitionKey = "holiday";
/*查询任务使用TaskService*/
TaskQuery taskQuery = processEngine.getTaskService().createTaskQuery();
/*查询*/
List<Task> taskList = taskQuery.processDefinitionKey(processDefinitionKey).taskAssignee("zhangsan").list();
for (Task task : taskList) {
System.out.println("流程实例ID:" + task.getProcessInstanceId());
System.out.println("任务ID:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
8.3、办理任务
public static void main(String[] args) throws Exception {
/*获得流程引擎*/
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key,如果查询全部的流程定义,那么直接空串即可*/
String processDefinitionKey = "holiday";
/*任务执行人*/
String assignee = "zhangsan";
/*查询任务使用TaskService*/
TaskQuery taskQuery = processEngine.getTaskService().createTaskQuery();
/*查询,指定任务执行人和*/
Task task = taskQuery.processDefinitionKey(processDefinitionKey).taskAssignee(assignee).singleResult();
/* 使用TaskService来执行/办理任务 */
processEngine.getTaskService().complete(task.getId());
}
实际应用中,我们可以将任务执行人指定为我们业务系统中的id这种,然后根据执行人和流程定义来查询任务,如果有任务那么就可以进行执行任务,否则就没有任务。
9、流程变量
9.1、什么是流程变量
流程变量在activiti中是一个非常重要的角色,流程运转时需要靠流程变量,业务系统和activiti结合时少不了流程变量,流程变量就是activit在管理工作流时根据管理需要而设置的变量。
比如在请假流程流转时如果请假天数大于3天泽由总经理审核,否则由人事直接审核,那么这个时候我们就要判断请假天数,但是请假天数并不在我们activit的数据表中,那么这个时候就可以开始请假流程的时候,往activiti中存一个天数为流程变量,这个时候流程变量就起了作用。
注意:虽然流程变量中可以存储业务数据,然后通过activiti的ap查询流程变量,从而实现业务数据,但是我们不建议这样搞,因为业务数据是由业务系统负责的,activiti的流程变量只是为了流程执行需要所创建的。
9.2、流程变量类型
注意:如果需要存储pojo,那么该pojo需要实现序列化接口,最好生成serialVersionID。
9.3、流程变量作用域
流程变量的作用域默认是一个流程实例(ProcessInstance),也可以是一个任务(Task)或一个执行实例(Execution),这三个作用域中流程实例的范围最大,被称为**global**变量,任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大,称为**local**变量。
变量名称的重名问题:
global变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。
local变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同,没有影响。
local变量名和可以global变量名相同,没有影响。
9.4、流程变量的使用方法
# 1、在画流程图的时候,设置流程变量。
> 例如:在节点上${name} 代表着从流程变量中获取name这个值
> 例如:在连线上${price > 1000}这种表达式,代表着从流程变量中获取price,并且判断大于1000。这种连线上表达式的结果决定流程的最终走向。
# 2、在启动流程实例的时候,存入流程变量
Map<String,Object> map = new HashMap<>();
map.put("name","zhangsan");
/*根据流程定义Key来启动流程实例,然后第二个参数就是我们的流程变量*/
ProcessInstance processInstance = runtimeService.
startProcessInstanceByKey("holiday",map);
9.5、使用Global变量控制流程
9.5.1、需求
员工创建请假申请单,由部门经理审核,部门经理审核通过后请假3天及以下由人事经理直接审核,3天以上先由总经理审核,总经理审核通过再由人事经理存档
9.5.2、流程定义
描述一下两条线:
第一条部门经理-->人事经理审核
:
我们点击这个连线后就可以在左边的窗口上的Condition这一列中书写表达式,当表达式成立的时候这条线对应的流程才会执行。
第二条部门经理-->总经理审核
:
当这个表达式成立后,才会执行部门经理到总经理的这个流程。
9.5.3、设置global流程变量
在部门经理审核前设置流程变量,变量值为请假单信息(我们这里呢就是请假天数num),部门经理审核后根据流程变量的值决定流程走向
9.5.3.1、启动流程时设置
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key*/
String processDefinitionKey = "holiday";
/*流程变量参数*/
Map<String,Object> variables = new HashMap<>();
/*放置我们的参数*/
variables.put("num",4);
/*
* 使用RuntimeService来启动一个流程实例
* 传入参数流程定义Key以及我们的流程参数
* */
ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey, variables);
System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
}
注意:
我们启动流程实例是使用startProcessInstanceByKey方法,传入流程定义Key,以及我们的流程参数Map。
然后我们去查看act_ru_variable表,即可找到我们刚才设置的变量值。
9.5.3.2、任务办理时设置
在完成任务时设置流程变量,该流程变量只有在该任务完成后其他节点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的key在流程实例中已存在相同的名字,则后设置的变量替换前面设置的变量。
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key*/
String assignee = "zhangsan";
/*流程变量参数*/
Map<String,Object> variables = new HashMap<>();
/*放置我们的参数*/
variables.put("num",5);
/*查询任务*/
Task task = processEngine.getTaskService().createTaskQuery().taskAssignee(assignee).singleResult();
/*执行任务,同时给一个Map作为流程变量*/
processEngine.getTaskService().complete(task.getId(),variables);
}
这时查看act_ru_variable表,值变成了5.
9.5.3.3、通过当前流程实例设置
给当前流程实例设置流程变量。比如说我们流程启动的时候,忘记设置流程变量了,那么我们能就可以单独的给流程实例设置流程变量。
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*使用RuntimeServic创建查询器设置条件为流程实例ID*/
Execution execution = processEngine.getRuntimeService().createExecutionQuery().executionId("2501").singleResult();
/*
* 使用RuntimeService的setVariable方法给流程实例设置变量
* 参数一:流程实例ID
* 参数二:变量名
* 参数三:变量值
*
* 我们也可以直接传入Map,一样的
* */
processEngine.getRuntimeService().setVariable(execution.getId(),"num",123);
}
注意:该流程实例不能是已经执行完成了的,只能是未结束的。我们也可以使用RuntimService的getVariable方法获取流程变量数据
这个时候我们再去看act_ru_variable表,就会发现值又改变了。
9.5.3.4、通过当前任务设置
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
TaskService taskService = processEngine.getTaskService();
/*根据任务id来查询任务*/
Task task = taskService.createTaskQuery().taskId("5002").singleResult();
/*使用TaskService来做*/
taskService.setVariable(task.getId(),"num",10);
}
注意:这个任务的ID必须要是在act_ru_task表中存在,如果不存在,则会报错找不到任务id。
9.5.4、测试
正常测试:
设置流程变量的值大于3天
设置流程变量的值小于或等于3天
异常测试:
流程变量不存在。
流程变量值为NULL。
9.5.5、注意事项
1、流程变量值需要使用的一定要存在。
2、如果值为null,则会出现异常。
3、如果都不符合条件,则会流程结束。
9.6、操作数据库
设置流程变量会在当前执行流程变量表插入记录,同时也会在历史流程变量表中插入记录。
SELECT * FROM act_ru_variable; # 当前流程变量记录表
记录当前运行流程实例可使用的流程变量,包括global和local变量
ID_:主键
TYPE_:变量类型
NAME_:变量名称
EXCETUION_ID_:所属流程实例执行ID,global和local变量都存储
PROC_INST_ID_:所属流程实例ID,global和local变量都存储
TASK_ID_:所属任务ID,local变量存储
BYTEARRAY_:serialzable类型变量存储对应act_ge_bytearray表的id
DOUBLE_:double类型变量值
LONG_:long类型变量值
TEXT_:text类型变量值
SELECT * FROM act_hi_varinst; # 历史流程变量表
记录所有已创建的流程变量,包括global和local变量
字段意义参考当前流程变量表
9.7、设置local流程变量
9.7.1、任务办理时设置
任务办理时设置local流程变量,当前运行的流程实例只能在该任务(节点)结束前使用,任务结束该变量无法在当前实例使用,可以通过查询流程任务查询。
public static void main(String[] args){
/*任务ID*/
String taskId = "";
TaskService taskService = processEngine.getTaskService();
Map<String,Object> variables = new HashMap<>();
/*自定义的对象*/
Holiday holiday = new Holiday();
holiday.setNum(3);
/*定义流程变量*/
variables.put("num",holiday.getNum());
/*设置流程变量,作用域为该任务*/
taskService/setVariablesLocal(taskId,variables);
/*执行任务*/
taskService.complete(taskId);
}
说明:设置作用域为任务的local变量,每个任务都可以设置同名的变量,互不影响。
9.7.2、通过当前任务设置
public static void main(String[] args){
/*任务ID*/
String taskId = "";
TaskService taskService = processEngine.getTaskService();
/*自定义的对象*/
Holiday holiday = new Holiday();
holiday.setNum(3);
taskService.setVariablesLocal(taskId,"num",holiday.getNum());
}
注意:任务ID必须是当前待办任务ID,任务在act_ru_task中存在。
9.7.3、Local变量测试1
如果上边例子中设置global变量改为设置local变量是否可行?为什么?
local变量在任务结束后无法在当前流程实例执行中使用,如果后续的流程执行需要用到此变量的话,那么会报错找不到该变量。
9.7.4、Local变了测试2
在部门审核、总经理审核、人事经理审核时设置local变量,可通过historyService查询每个历史人物时候将流程变量的值也查询出来。
代码如下:
// 创建历史任务查询对象
HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
// 查询结果包括local变量
List<HistoricTaskInstance> list = historicTaskInstanceQuery.includeTaskLocalVariables().list();
// 遍历结果
for(HistoricTaskInstance historicTaskInstance : list){
System.out.println("================================");
System.out.println("任务ID:"+historicTaskInstance.getId());
System.out.println("任务名称:"+historicTaskInstance.getName());
System.out.println("任务负责人:"+historicTaskInstance.getAssignee());
System.out.println("任务local变量:"+historicTaskInstance.getTaskLocalVariables());
}
注意:查询历史流程变量,特别是查询pojo变量需要经过反序列化,不推荐使用(但是方便呀!)
10、组任务
10.1、Candidata-users候选人
10.1.1、需求
在流程定义中任务节点的assignee固定设置任务责任人,在流程定义时将参与者固定设置在.bpmn文件中,如果临时任务负责人变更则需要修改流程定义,系统扩展性差。
针对这种情况可以给任务设置多个获选人,可以从候选人中选择参与者来完成任务。
10.1.2、设置任务候选人
在流程图中任务节点的配置中设置candidate-users(候选人),多个候选人之间用逗号隔开。
BPMN文件:
<userTask id="_3" name="填写请假单" activiti:candidateUsers="zhangsan,lisi.wangwu" />
<userTask id="_4" name="部门经理审批" activiti:assignee="zhaoliu">
我们可以看到,填写请假单这个我们设置为了candidateUsers="zhangsan,lisi,wangwu"。
10.1.3、办理组任务
10.1.3.1、组任务办理流程
# 第一步:查询组任务
指定候选人,查询该候选人当前的待办任务。
候选人不能办理任务。
# 第二步:拾取(claim)任务
将组任务的所有候选人都能拾取。
将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。
如果拾取后不想办理该任务怎么办?
需要将已经拾取的个人任务归还到组里边,将个人任务变成组任务。
# 第三步:查询个人任务
查询方式同个人任务部分,根据assignee查询用户负责的个人任务。
# 第四步:办理个人任务
task.complete....
10.1.3.2、用户查询组任务
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*流程定义Key*/
String processDefinitionKey = "holiday";
/*任务候选人*/
String candidateUser = "zhangsan"; // zhangsan、lisi、王五
/*获得任务Service*/
TaskService taskService = processEngine.getTaskService();
/*查询结果*/
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.taskCandidateUser(candidateUser) /*根据候选人查询*/
.list();
/*遍历结果*/
for (Task task : taskList) {
System.out.println("任务实例ID:" + task.getProcessInstanceId());
System.out.println("任务ID:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
10.1.3.3、用户拾取组任务
候选人员拾起任务后该任务变成自己的个人任务
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*任务候选人*/
String candidateUser = "zhangsan"; // zhangsan、lisi、王五
/*任务ID*/
String taskId = "2505";
TaskService taskService = processEngine.getTaskService();
/*拾取任务
* 参数一:任务id
* 参数二:候选人
* */
taskService.claim(taskId,candidateUser);
}
这个时候去看act_ru_task表的信息,里面的assign就变成了张三了
10.1.3.4、用户查询个人待办任务
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*任务负责人*/
String assignee = "zhangsan"; // zhangsan、lisi、王五
/*流程定义Key*/
String processDefinitionKey = "holiday";
/*查询到指定负责人的所有任务*/
List<Task> taskList = processEngine.getTaskService().createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.taskAssignee(assignee)
.list();
for (Task task : taskList) {
System.out.println(task);
}
}
10.1.3.5、用户办理个人任务
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*任务id*/
String taskId = "2505";
processEngine.getTaskService().complete(taskId);
}
注意:任务完成之前,先要去判断该用户是否为该任务的负责人,比如说我们用你用户去查询任务,查询到了再完成任务。
10.1.3.6、归还组任务
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*先拿到zhangsan的个人任务,再归还为组任务*/
Task task = processEngine.getTaskService().createTaskQuery().taskAssignee("zhangsan").singleResult();
/*将指定任务id的执行人设置为null,那么则归还了组任务*/
processEngine.getTaskService().setAssignee(task.getId(),null);
}
注意:在归还任务的时候,最好是先通过负责人拿到任务,然后再去归还。
10.1.3.7、任务交接
public static void main(String[] args) throws Exception {
ProcessEngine processEngine = getProcessEngine();
/*先拿到zhangsan的个人任务,再归还为组任务*/
Task task = processEngine.getTaskService().createTaskQuery().taskAssignee("zhangsan").singleResult();
/*将指定任务id的执行人设置为其他人,那么就完成了任务的交接*/
processEngine.getTaskService().setAssignee(task.getId(),"lisi");
}
注意:我们任务交接这种、归还任务这种操作,说白了就是对assign值的改变,设置为null则是归还任务,设置为其他人则是交接,这里我们交接的人可以不是我们候选人中的任意一人,但是不建议这样做。
10.1.3.8、数据库操作
SELECT * FROM act_ru_task; # 任务执行表
任务执行表,记录当前执行的任务,由于当前该任务是组任务,那么没有被拾取以前,assignee字段为空,拾取后就是负责任的内容。
SELECT * FROM act_ru_identitylink; # 任务参与者
任务参与者,如果人物设置了候选人,那么有几个候选人就会插入几条纪录。
11、网关
11.1、排他网关
11.1.1、什么是排他网关
排他网关(也叫异或网关):
比如我们有多个分支的的表达式结果都为true,那么这个时候就会出现多个分支都被执行的情况,那么排他网关的作用就是制止多个分支同时被执行,然后只允许一个分支被执行,那么多个分支都为true的情况下,会默认走第一条分支,如果没有为true的分支,那么会报错。
看图:
当请假大于3天的时候,人事经理审核,大于5天总经理审核,那么当请天假数为6天的时候哦,人事经理和总经理都会同时审核,那么就出现问题,如果人事经理审核完后,讲道理就直接到结束了,但是可能我们的总经理还没有审核,那么就出现了问题,这个时候排他网关就派上作用了。我们来看看:
并行网关叫exclusivegateway,加入了它的作用,如果我们分支的两个条件同时成立,那么默认走第一条分支,如果都不成立,则报错(并行网关有且只有一条分支走)!
11.1.2、流程定义
在Gateway中叫做exclusivegateway,原本的条件是直接任务直接连接任务,这里就改变一下,具体参照上面的图,特别注意:并行网关有且只有一条分支走。
11.1.3、测试
自己定义一个流程,就跟上面的一样,然后代码部署,启动实例,注意启动实例的时候流程变量不要忘了,然后自己试试是不是那回事就行。
我们测试可以发现,如果流程变量num为6,那么则会走总经理审核,如果num为4,那么走人事经理审核,如果为1,那么会报错无路可走。
11.2、并行网关
11.2.1、什么是并行网关
并行网关:
如果有需求,要求人事经理和总经理同时审核完成后才能结束,那么我们该如何知道他们都完成任务了呢,是否有一个东西来判断,当他们都完成任务后,那么自动到达结束节点,这个东西就是并行网关。
上图:
我们组长审核和经理审核是并行的,必须要两个节点都完成任务才会到达人事经理存档这一步,如果组长审批完成汇聚到并行网关,但是经理审批没完成,那么会停留在并行网关中。
注意:如果我们给并行网关那里设置条件啥玩意的,是没有效果的,会被忽略。
11.2.2、流程定义
并行网关在Gateway的ParallelGateway,使用方法参考上面的图那样子。
11.2.3、测试
参照上图搞一个来试试,看看是不是只有组长审核和经理审核都完成后才会进入人事经理存档。
当我们填写请假单走完后,会发现act_ru_task表中有两条记录,一个是组长审批,一个是经理审批。
11.3、包含网关
11.3.1、什么是包含网关
包含网关:
可以看作排他网关和并行网关的结合体,唯一不同就是不同于排他网关,排他网关只允许一条分支走,而包含网关允许多条分支走。
所以我们可以知道,包含网关具有排他网关类似的作用,只是多分支和单分支的区别,不仅如此还具有并行网关的汇总分支的功能。
上图:
此处为体检流程,我们设定type为员工类型,1则是普通员工,2是领导,那么我们此处如果type为2,那么所有节点都会走,如果type为1,那么只走上面的两层节点,此处我们前面使用包含网关的话,就允许多个节点走,最后汇总所有节点后结束那里也使用了包含网关。
11.3.2、流程定义
包含网关在Gateway的InclusiveGateway,使用方法参考上面的图那样子。
11.3.3、测试
参考上面的图,我们来试试type为1和2的情况下,分支的执行情况。然后看看最后的分支汇总到包含网关后结束的情况。
12、总结
什么是工作流?
就是通过计算机对业务流程进行自动化管理,实现多个参与者按照预定义的流程去自动执行业务流程。
什么是Activiti?
Activiti是一个工作流的引擎,开源的架构,基本bpmn2.0标准进行流程定义,它的是前身是jbpm。Activiti通过是要嵌入到业务系统开发使用。
如何使用Activiti开发?
第一步:
部署 activiti 的环境。
环境包括: jar 包和数据库(25 张表)
业务系统通过spring和activiti整合进行开发。
第二步:
使用 activiti 提供流程设计器(和idea或eclipse集成的designer)工具进行流程定义
流程定义生成两个文件:.bpmn和.png(不是必须的)。
第三步;
将流程定义文件部署到activiti的数据库
SELECT * FROM act_re_deployment # 流程定义部署表,一次部署插入一条记录,记录流程定义的部署信息 SELECT * FROM act_re_procdef # 流程定义表,一次部署流程定义信息,如果一次部署两个流程定义,插入两条记录
建议:一次部署只部署一个流程定义,这样act_re_deployment和act_re_procdef一对一关系
常用两个方法:单个文件部署和 zip 文件部署。建议单个文件部署
第四步:
启动一个流程实例业务系统就可以按照流程定义去执行业务流程,执行前需要启动一个流程实例,根据流程定义来启动一个流程实例。
指定一个流程定义的 key 启动一个流程实例,activiti 根据key找最新版本的流程定义。
指定一个流程定义的 id 启动一个流程实例。
启动一个实例需要指定 businesskey(业务标识),businessKey是activiti和业务系统整合时桥梁。
比如:请假流程,businessKey就是请假单id。
启动一个实例还可以指定流程变量,流程变量是全局变量(生命期是整个流程实例,流程实例结束,变量就消失)
第五步:
查询个人任务:使用taskService,根据assignee查询该用户当前的待办任务。
查询组任务:使用taskService,根据candidateuser查询候选用户当前的待办组任务
第六步:
办理个人任务:调用 taskService的complete方法完成任务。
如果是组任务,需要先拾取任务,调用taskService的claim方法拾取任务,拾取任务之后组任务就变成了个人任务(该任务就有负责人)。
网关是什么?
排他网关:任务执行之后的分支,经过排他网关分支只有一条有效。
并行网关:任务执行后,可以多条分支,多条分支总会汇聚,汇聚完成,并行网关结束。
包含网关:是排他网关和并行网关结合体。