1,工作流(
Workflow
),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现。2,工作流管理系统(
Workflow Management System
,WfMS
)是一个软件系统,它完成工作量的定义和管理,并按照在系统中预先定义好的工作流逻辑进行工作流实例的执行。工作流管理系统不是企业的业务系统,而是为企业的业务系统的运行提供了一个软件的支撑环境。3,常见的工作流框架有
Activity
、JBPM
、OSWorkFlow
、WorkFlow
。
1,业务流程图要规范化,要遵守一套标准。
2,业务流程图本质上是一个
xml
文件,这样就可以存入所有数据。3,读取业务流程图的过程就是解析
xml
文件的过程。4,读取一个业务流程图中的节点就相当于是解析一个xml结构,进一步将数据插入到
mysql
的表中,形成一条记录。5,将所有的节点都读取并存入
mysql
表中。6,后面只要读取
mysql
表中的记录就行了,读一条记录就相当于读一个节点。7,业务流程的推进,后面就转化为读表中数据,并且处理数据,结束时这一行数据就可以删除了,删除就代表该业务流程结束。
Eclipse
版本的流程设计器下载地址:链接:https://pan.baidu.com/s/1kgPqXlA75JzBSZ0xvkAG1w 提取码:vgl1
下载完成后解压,然后替换Eclipse
安装目录列表中的相同的两个目录里面的jar
包,也就是移到Eclipse
的安装目录的那两个文件夹,然后重新打开Eclipse
,就已经有流程设计器插件了。
安装actiBPM
插件,并重启idea
安装完成后,在资源文件夹resources
右键新建actiBPMFiles
,然后就可以画流程图了。
Activiti
工作流需要数据库的25张表支撑。
数据库:mysql
创建表方式:通过运行java程序创建表。
过程不用多说
<properties>
<java.version>1.8</java.version>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<!--activiti整合springboot的坐标-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-converter</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti.cloud</groupId>
<artifactId>activiti-cloud-services-api</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>alfresco</id>
<name>Activiti Releases</name>
<url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
这里使用dbcp
连接池连接mysql
数据库,在resources
资源文件夹下新建文件activiti.cfg.xml
文件。
前提是mysql
数据库新建一个库名为activiti
。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--数据源配置dbcp-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/activiti" />
<property name="username" value="root" />
<property name="password" value="1234" />
<property name="maxActive" value="3" />
<property name="maxIdle" value="1" />
</bean>
<!--activiti单独运行的ProcessEngine配置,使用单独启动方式-->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"></property>
<property name="databaseSchemaUpdate" value="true"/>
</bean>
<!--<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/itcast0711activiti"/>
<property name="jdbcUsername" value="root"/>
<property name="jdbcPassword" value="root"/>
<property name="databaseSchemaUpdate" value="true"/>
</bean>-->
</beans>
随便哪个地方写下面的测试类,然后运行,那么数据库中就已经创建了25张表。
//测试activiti所需要的25张表的生成
@Test
public void testGenTable() {
//1,创建ProcessEngineConfiguration对象
ProcessEngineConfiguration configuration=
ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
//2,创建ProcessEngine对象
ProcessEngine processEngine = configuration.buildProcessEngine();
//3,打印processEngine对象
System.out.println(processEngine);
}
Activiti
表都以ACT_
开头。
Activiti
只在流程实例执行过程中保存这些数据,在流程结束后就会删除这些记录。从图中可以看出,通过ProcessEngine
对象可以得到下面的好多Service
,而下面的Service
的作用就是直接对数据库中的25张表进行增删改查操作,很简便,不用再自己写增删改查了。
Activiti
架构图说明
ProcessEngineConfiguration
类,主要作用是加载activiti.cfg.xml
文件。
ProcessEngine
类,作用是帮助我们可以快速得到各个service
接口,并且可以生成activiti
的工作环境,25张表。
Service
接口,作用是可以快速实现数据库25张表的操作。
ResposityService
RuntimeService
TaskService
HistoryService
新建流程holiday.bpmn
,在resources
资源文件夹下新建BPNMFile
文件,并画一个流程,这里举例请假流程。
1,点击白色区域,看左边的id
和name
,每一个流程图都有唯一的id
和name
,可以自定义。
2,点击任务格子,看左边的Assignee
,如下图,是指定该任务分配给谁来执行的。都分配一下,这里分配结果==》填写请假单:zhangsan;部门经理审批:lisi;总经理审批:wangwu。
画完图后,但是并没有生成png
图片,这个时候重命名刚才创建的文件,把后缀改成xml
,然后右键
然后保存到与刚才创建文件的一个文件夹内就可以了
下面是保存按钮
如果流程图是中文乱码的情况,那么在idea
安装目录的下面两个文件添加一句代码。
-Dfile.encoding=UTF-8
然后重新启动idea
就可以了,重新走一遍上面的过程即可。
写一个测试类进行测试流程部署。
//流程定义的部署
@Test
public void testDeploy(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到repositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,进行部署
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday.bpmn") //添加bpmn资源
.addClasspathResource("holiday.png")
.name("请假申请流程")
.deploy();
//4,输出部署的一些信息
System.out.println(deployment.getId());
System.out.println(deployment.getName());
}
打印出
1
请假申请流程
看数据库表act_re_deployment
表新增了一条数据。(该表存放部署信息)
act_re_procdef
表也新增了一条数据。(该表存放流程定义的一些信息)
act_ge_bytearray
新增了两条,这是存储的文件。(该表存放流程定义的bpmn文件及png文件)
总的来说,就是部署一个新的流程时,数据库发生变化的是上面的三张表。
提及一个概念:一个流程定义可以对应多个流程实例。
写一个测试类,启动流程实例。
//启动流程实例:前提是已经完成了流程定义的部署工作
@Test
public void testBeginFlow(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RuntimeService实例
RuntimeService runtimeService = processEngine.getRuntimeService();
//3,创建流程实例,流程定义的key需要知道流程唯一id:holiday
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
//4,输出实例相关信息
System.out.println("实例部署id:"+processInstance.getDeploymentId());
System.out.println("流程实例id:"+processInstance.getId());
System.out.println("活动id:"+processInstance.getActivityId());
}
打印:
实例部署id:null
流程实例id:2501
活动id:null
看数据表act_hi_procinst
新增了一条数据。
启动一个流程实例影响数据库的表:
流程启动后,各个任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。
写一个测试类,查询指定用户下面的待办任务列表。
//查询当前用户的任务列表
@Test
public void query(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3,根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey("holiday")
.taskAssignee("zhangsan")
.list();
//4,任务列表的展示
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:2501
任务id:2505
任务负责人:zhangsan
任务名称:填写请假单
写一个测试类,处理当前用户的待办任务。
//处理任务
@Test
public void chuli(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3,处理任务,结合当前用户任务列表的查询操作,得到任务ID:2505
taskService.complete("2505");
}
运行之后,该任务就完成了,然后就跳到下一个审批的节点了。
看数据库act_hi_taskinst
表的变化,填写请假单有了结束时间,然后新增了一条部门经理审批,并且结束时间为NULL,这就说明跳到下一个节点了。
处理任务操作背后影响的表:
将holiday.bpmn
和holiday.png
压缩成holiday.zip
包进行部署,效果和上面的(标题3)一样。
//流程定义的部署
@Test
public void testDeploy(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到repositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
//转化出ZipInputStream流对象
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("holiday.zip");
//将inputStream转化为ZipInputStream流
ZipInputStream zipInputStream=new ZipInputStream(inputStream);
//3,进行部署
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("请假申请流程")
.deploy();
//4,输出部署的一些信息
System.out.println(deployment.getId());
System.out.println(deployment.getName());
}
这种方法也可以的。
说明:什么是流程定义?流程定义就是启动了的流程图,比方说上面启动了一个请假申请流程,那么该流程定义就是指这个请假申请流程。
写一个测试类,查询流程定义信息。
//流程定义查询
@Test
public void defined(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RepositoryService对象
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,得到ProcessDefinitionQuery对象,可以认为它就是一个查询器
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//4,设置条件,并查询出当前的所有流程定义,查询条件:流程定义的key=holiday
List<ProcessDefinition> holiday = processDefinitionQuery.processDefinitionKey("holiday")
.orderByProcessDefinitionVersion() //设置排序方式,根据流程定义的版本号进行排序
.desc() //降序排列
.list();
//5,输出流程定义信息
for(ProcessDefinition processDefinition:holiday){
System.out.println("流程定义ID:"+processDefinition.getId());
System.out.println("流程定义名称:"+processDefinition.getName());
System.out.println("流程定义的Key:"+processDefinition.getKey());
System.out.println("流程定义的版本号:"+processDefinition.getVersion());
System.out.println("流程部署的ID:"+processDefinition.getDeploymentId());
}
}
打印出了流程定义的所有信息。
流程定义ID:holiday:1:4
流程定义名称:请假流程
流程定义的Key:holiday
流程定义的版本号:1
流程部署的ID:1
删除已经部署成功的流程定义。
@Test
public void del(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RepositoryService对象
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,执行删除流程定义,参数代表流程部署的id
repositoryService.deleteDeployment("1");
}
删除流程定义影响的表和部署流程定义影响的表是一样的,删除是减去,部署是增加,都是同样的三张表。
注意事项:
1,当我们正在执行的这一套流程没有完全审批结束的时候,如果此时要删除流程定义信息就会失败。
2,如果要强制删除,可以使用repositoryService.deleteDeployment("1",true)
;参数true
代表级联删除,此时就会先删除没有完成的流程结点,最后就可以删除流程定义信息,false
值代表不级联删除,不写默认为false
通过流程定义对象获取流程定义资源,获取bpmn
和png
。
真实应用场景:用户想查看这个请假申请流程具体有哪些步骤要走?
技术方案:
Activiti
的api
来实现;jdbc
对blob
类型,clob
类型数据的读取和保存。IO
流转换,使用commons-io.jar
包可以轻松解决IO
操作。需求:
act_ge_bytearray
表中读取两个资源文件,并将两个资源文件保存到路径:D:
写一个测试类实现资源文件保存到本地磁盘。
里面输入流和输出流转换需要用到了commons-io.jar
包,这里引入一个在idea
中快速搜索并选择版本号并导入依赖的方法。
Alt
+Insert
键,选择Dependency
,搜索依赖的名字,并选择版本号,确定导入即可。
//保存资源文件
@Test
public void save() throws IOException {
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RepositoryService对象
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,得到ProcessDefinitionQuery对象,可以认为它就是一个查询器
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//4,设置查询条件
processDefinitionQuery.processDefinitionKey("holiday");//参数是流程定义的key
//5,执行查询操作,查询出想要的流程定义
ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
//6,通过流程定义信息,得到部署ID
String deploymentId = processDefinition.getDeploymentId();
//7,通过repositoryService的方法,实现读取图片信息及bpmn文件信息(输入流)
//getResourceAsStream()方法的参数说明:第一个参数是部署id,第二个参数代表资源名称
//processDefinition.getDiagramResourceName() 获取png图片资源的名称
//processDefinition.getResourceName() 获取bpmn文件的名称
InputStream pngIs = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
InputStream bpmnIs = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//8,构建出OutputStream流
OutputStream pngOs=new FileOutputStream("D:\\工作\\"+processDefinition.getDiagramResourceName());
OutputStream bpmnOs=new FileOutputStream("D:\\工作\\"+processDefinition.getResourceName());
//9,输入流、输出流的转换(commons-io.jar中的方法)
IOUtils.copy(pngIs,pngOs);
IOUtils.copy(bpmnIs,bpmnOs);
//10,关闭流
pngOs.close();
bpmnOs.close();
pngIs.close();
bpmnIs.close();
}
查看本地D:/工作
路径下,有了holiday.png
和holiday.bpmn
文件,已经下载到了本地。
即使流程定义已经删除了,但是流程执行的历史信息依然保存在activiti
的act_hi_*
相关的表中。所以我们还是可以查询流程执行的历史信息,可以通过HistoryService
来查看相关的历史记录。
/查询历史数据
@Test
public void select(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到HistoryService对象
HistoryService historyService = processEngine.getHistoryService();
//3,得到查询器
HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
//4,执行查询
instanceQuery.processInstanceId("2501"); //设置流程实例的id
List<HistoricActivityInstance> list = instanceQuery.orderByHistoricActivityInstanceStartTime().asc().list();//排序
//5,遍历查询结果
for(HistoricActivityInstance instance:list){
System.out.println(instance.getActivityId());
System.out.println(instance.getActivityName());
System.out.println(instance.getProcessDefinitionId());
System.out.println(instance.getProcessInstanceId());
System.out.println("===========================");
}
}
打印
Activiti:数据库中的25张表只是负责Activiti
的工作正常流程,和业务数据没有关系。
业务系统:存放业务数据。
比如请假流程,提交的表单信息是存到业务数据库中,但是要把Activiti
和业务系统关联起来,意思就是,提交一个请假审批,Activiti
怎么知道这个审批流程是谁提交的?因此关联的方法是把业务主键(请假表的id
字段)保存到Activiti
里面。
存放到哪里?
act_ru_execution
表中的bussiness_key
字段就是业务主键(业务标识)。
在上面启动流程实例时,参数是一个,也就是流程id。
//3,创建流程实例,流程定义的key需要知道流程唯一id:holiday
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
但是要把业务表(请假表)的业务标识(请假表的id
)和Activiti
整合起来,需要传入第二个参数businessKey
,第二个参数就是请假表的id
。
@Test
public void bang(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RuntimeService实例
RuntimeService runtimeService = processEngine.getRuntimeService();
//3,创建流程实例,流程定义的key需要知道流程唯一id:holiday
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday","1001");
System.out.println(processInstance.getBusinessKey());
}
打印:1001
观察act_ru_execution
表,已经和业务id
绑定了。
需求:公司制度发生了变化,原本没有批完的流程怎么办?
某些情况可能由于流程变更需要当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行。操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停,并不允许启动新的流程实例。
//流程定义全部挂起
@Test
public void guaqi(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RepositoryService对象
RepositoryService repositoryService = processEngine.getRepositoryService();
//3,查询流程定义的对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
//可以根据流程定义的id查询指定流程定义,不过id要到Activiti数据库表中去看, act_re_procdef表
// .processDefinitionId()
.processDefinitionKey("holiday") //也可以根据流程定义的key查询
.singleResult();
//4,得到当前流程定义的实例是否都为暂停状态
boolean suspended = processDefinition.isSuspended();
//5,如果都为暂停状态,则全部激活
if(suspended){
repositoryService.activateProcessDefinitionById(processDefinition.getId(),true,null);
System.out.println("流程定义:"+processDefinition.getId()+"被激活");
}else{
repositoryService.suspendProcessDefinitionById(processDefinition.getId(),true,null);
System.out.println("流程定义:"+processDefinition.getId()+"被挂起");
}
}
运行测试类,打印
流程定义:holiday:1:4被挂起
再次运行测试类,打印
流程定义:holiday:1:4被激活
processInstanceId("2501"),是通过流程实例id查询到的流程实例对象
//单个流程实例的挂起
@Test
public void single(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RuntimeService对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//3,查询流程实例对象
ProcessInstance holiday = runtimeService.createProcessInstanceQuery().processInstanceId("2501").singleResult();
//4,查看当前流程定义的实例是否为暂停状态
boolean suspended = holiday.isSuspended();
//判断
if(suspended){
runtimeService.activateProcessInstanceById(holiday.getId());
System.out.println("流程实例:"+holiday.getId()+"被激活");
}else{
runtimeService.suspendProcessInstanceById(holiday.getId());
System.out.println("流程实例:"+holiday.getId()+"被挂起");
}
}
打印
流程实例:2501被挂起
上面所述,每个节点设置的Assignee
对应的值为zhangsan
,代表某个人,那就是固定方式分配,但是被定死了,只有张三才能完成该节点,但是肯定是不行的,请假是所有员工都可以申请的。
引出UEL
方式
UEL
方式,也称为统一表达式。
写法:${变量名}
什么时候设置表达式?
在启动流程实例的时候设置流程变量,如下
//启动流程实例,动态设置assignee
@Test
public void dongtai(){
//1,创建processEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2,得到RuntimeService对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//3,设置assignee的取值,用户可以在界面上设置流程的执行人
Map<String,Object> map=new HashMap<>();
map.put("assignee0","zhangsan");
map.put("assignee1","lisi");
map.put("assignee2","wangwu");
//4,启动流程实例,同时还要设置流程定义的assignee的值
ProcessInstance holiday = runtimeService.startProcessInstanceById("holiday", map);
System.out.println(holiday.getName());
}
前提:在界面上要分别设置assignee
的值为assignee0
,assignee1
,assignee2
。
结合项目使用的话,获取到提交请假单的名字或者唯一标识,然后动态赋值,然后存到activiti
数据库中名字就是该设置的名字。
在界面中添加监听器。
定义任务监听类,该类必须实现org.activiti.delegate.TaskListener
接口。
public class MyTaskListener implements TaskListener(){
@Override
public void notify(Delegate delegate){
//这里指定任务负责人
delegate.setAssignee("张三");
}
}
比如说现在有一个贷款流程,发起流程的用户如果贷款数目小于100000,那么业务经理审批完之后就可以到会计节点,然后放贷款,但是如果贷款数目大于100000了,那么就要到总经理节点审批,总经理审批完之后才能到会计节点。
那么问题来了,Activiti怎么知道大于100000就要到总经理审批节点呢?那么就需要用到流程变量了。
global变量:一个任务或一个流程实例。可以理解为全局的,所有节点都可以使用该变量。
local变量:仅仅针对一个任务和一个执行实例范围,范围没有流程实例大。可以理解为局部的,只有一个节点可以使用。
global
变量名不可以重复,如果名字重复,则会被覆盖。
可以在连线上设置UEL表达式,决定流程走向。
比如:${price>100000}和${price<100000},price就是变量名称。
点击流程图的连线,设置流程变量即可。
需求:员工创建请假单,由部门经理审核,部门经理审核通过之后请假3天以下由人事经理直接审核,3天以上先由总经理审核,总经理审核通过之后再由人事经理存档。
定义BPMN文件:
设置请假单申请assignee:zhangsan,部门审批assignee:lisi,总经理审批assignee:wangwu,人事经理存档assignee:zhaoliu。
画好图之后,点击连线(人事经理前面的连线设置为${holiday.num<3}
,总经理前面的连线设置为${holiday.num>3}
),这是UEL
方式赋值,可以对象点出来的属性这种,对象就是下面的实体类。
然后重复上面的动作:改bpmn
文件后缀为xml
文件,然后生成png
文件,然后再把xml
恢复成bpmn
。
其实不生成png
也没啥问题,工作流一点儿影响都没有。
//请假实体类
@Data
public class Holiday implements Serializable{
private Integer id;
private String holidayName;//申请人名字
private Date beginDate;//开始时间
private Date endDate;//结束时间
private Float num;//请假天数
private String reason;//请假事由
private String type;//请假类型
}
注意:如果将实体类存储到流程变量中,必须实现序列化接口seriallizable
,否则在存储这个实体类的时候就会报异常,为了防止由于新增字段无法反序列化,需要生成serialVersionUID
。
//流程变量的测试
public class HolidayTest {
//新的请假流程定义的部署
@Test
public void start(){
//得到ProcessEngine
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//得到RepositoryService
RepositoryService repositoryService = defaultProcessEngine.getRepositoryService();
//部署
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("holiday4.bpmn")
.addClasspathResource("holiday4.png")
.name("请假流程-测试流程变量")
.deploy();
System.out.println(deploy.getId());
System.out.println(deploy.getName());
}
}
运行,这就部署好了该流程定义。
//启动流程实例,同时设置流程变量
@Test
public void begin(){
//得到ProcessEngine
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//得到RuntimeService
RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();
//流程定义的key:myProcess_1
String key="myProcess_1";
Map<String,Object> map=new HashMap<>();
//设置请假天数
Holiday holiday=new Holiday();
holiday.setNum(1F);
map.put("holiday",holiday);
//启动流程实例,并设置流程变量的值
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, map);
System.out.println(processInstance.getName());
System.out.println(processInstance.getProcessDefinitionId());
}
运行,就启动了一个流程实例。
依次调用此方法完成任务,zhangsan,lisi,wangwu/zhaoliu---------判断流程变量的请假天数
@Test
public void redify(){
//得到ProcessEngine
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//得到taskService
TaskService taskService = defaultProcessEngine.getTaskService();
//查询当前用户是否存在任务
String key="myProcess_1";
Task task = taskService.createTaskQuery().processDefinitionKey(key)
.taskAssignee("zhangsan").singleResult();
//如果有任务,结束
if(task!=null){
taskService.complete(task.getId());
System.out.println("任务执行完毕");
}
}
张三执行完之后,看act_hi_actinst
表,张三填写申请已经结束,来到了李四部门经理审批。
然后再执行李四()把zhangsan改成lisi,运行上面的测试类。
运行完之后,李四的任务也执行完毕了,也就是部门审批完了,看act_hi_actinst
表的变化。
发现来到了人事经理存档的节点,是因为上面设置的请假天数是1天,所以直接来到了人事,如果请假天数大于3天,那么会先到总经理审批节点,然后才到人事节点。
同理,把lisi改成zhaoliu,也就是执行人事经理存档,运行,看act_hi_actinst
表的变化。
流程执行完毕。
上面的流程变量的赋值是创建流程实例就有值了,而完成任务设置值是完成了该节点之后然后流程变量才有的值。
流程实例创建好之后可以得到该实例id,然后在启动流程实例的时候通过流程实例id设置流程变量的值。
//启动流程实例,同时设置流程变量
@Test
public void begin(){
//得到ProcessEngine
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
//得到RuntimeService
RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();
//流程定义的key:myProcess_1
String key="myProcess_1";
Map<String,Object> map=new HashMap<>();
//设置请假天数
Holiday holiday=new Holiday();
holiday.setNum(1F);
map.put("holiday",holiday);
//启动流程实例,并设置流程变量的值
//第一个参数:流程实例的id
//第二个参数:流程变量名
//第三个参数:流程变量所对应的值
runtimeService.setVariable("2501","holiday",holiday);
// ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, map);
// System.out.println(processInstance.getName());
// System.out.println(processInstance.getProcessDefinitionId());
}
首先需要得到当前任务的id。
String taskId="1404";
TaskService taskService=processEngine.getTaskService();
//使用taskService来设置
taskService.setVariable(taskId,"holiday",holiday);
需求:前面讲述的,每个节点只能有一个任务负责人,现在要一个节点可以有多个任务负责人。
设置任务候选人:
流程图任务节点的配置中配置candiddate-users
(候选人),多个候选人之间用逗号隔开。
第一步:查询组任务,指定候选人,查询该候选人当前的待办任务。候选人不能办理业务。
第二步:拾取任务,该组任务的所有候选人都可以拾取,将候选人的组任务变成个人任务,原来候选人就变成了该任务的负责人。
如果拾取后不想办理该业务怎么办?
需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。
第四步:办理个人任务。
//查询组任务
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定义的key
String candidate_users="zhangsan";//指定某一个候选人
//查询
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskCandidateUser(candidate_users)//设置候选用户
.list();
//输出
for(Task task:list){
System.out.println(task.getProcessInstanceId());
System.out.println(task.getId());
System.out.println(task.getName());
System.out.println(task.getAssignee());
}
}
其中
System.out.println(task.getAssignee());
打印为null,是因为查到的任务没有具体的负责人,需要拾取任务。说明张三只是候选人,还不是任务负责人。
候选人拾取组任务后该任务变成自己的个人任务。
//查询组任务
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定义的key
String candidate_users="zhangsan";//指定某一个候选人
//查询
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskCandidateUser(candidate_users)
.singleResult();
if(task!=null){
//第一个参数:任务id,第二个参数:候选人
taskService.claim(task.getId(),candidate_users);
System.out.println("任务拾取完毕");
}
}
候选人张三拾取组任务以后,该任务就已经有任务负责人了(assignee),就是老张了。
上面张三已经拾取了组任务,变成了个人任务,现在张三查询自己的个人任务并完成任务。
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定义的key
String candidate_users="zhangsan";
//查询
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(candidate_users)//这里就可以指定具体的任务负责人了
.singleResult();
if(task!=null){
taskService.complete(task.getId());
System.out.println("任务执行完毕");
}
}
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人。
//查询组任务
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定义的key
String candidate_users="zhangsan";
//查询
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(candidate_users)
.singleResult();
if(task!=null){
//如果设置为null,归还组任务,该任务没有负责人
taskService.setAssignee(task.getId(),null);
}
}
说明:建议归还任务前校验该用户是否是该任务的负责人。
也可以通过setAssignee
方法将任务委托给其他用户负责,注意被委托的用户可以不是候选人。
任务交接:任务负责人将任务交给其他候选人办理该业务。
//查询组任务
@Test
public void find(){
ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = defaultProcessEngine.getTaskService();
String key="myProcess_1";//流程定义的key
String candidate_users="zhangsan";
//查询
Task task = taskService.createTaskQuery()
.processDefinitionKey(key)
.taskAssignee(candidate_users)
.singleResult();
if(task!=null){
taskService.setAssignee(task.getId(),"lisi");
System.out.println("交接任务成功");
}
}
排他网关用来在流程中实现决策,当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支。
注意:排他网关智慧选择一个为true
的分支执行,即使有两个分支条件都为true
,排他网关也会只选择一条分支去执行。
这是关键,因为如果两个分支条件都满足,那同时走两个分支是不现实的,要不然流程就全乱套了。
使用方法:在流程图界面拖一个排他网关放在中间就行了。
作用:并行网关把一个流程分成多条分支,并发执行,执行完之后再把这些分支聚合到一起成一个流程,然后继续往下走。
如果有一个分支没有执行完,那么流程不会继续走,并行网关会一直等所有分支都执行完毕,并聚合在一起之后才接着走流程。
并行网关并不会解析条件,即使顺序流中定义了条件,也会被忽略。
这里忽略。
原文:https://www.cnblogs.com/fantongxue/p/12487607.html