说明,本项目案例参考 https://gitee.com/smell2/ruoyi-vue-activiti 开源项目。
一、Activiti流程部署
当定义好流程之后,第一步就是要进行流程的部署操作,主要是采取读取bpmn资源文件的方法。
上传的流程定义:
查看流程图详情:
@Override
public void uploadStreamAndDeployment(MultipartFile file) throws IOException {
// 获取上传的文件名
String fileName = file.getOriginalFilename();
// 获取输入流(字节流)对象
InputStream fileInputStream = file.getInputStream();
// 获取扩展名
String extension = FilenameUtils.getExtension(fileName);
// 初始化流程
if(extension.equals("zip")) {
ZipInputStream zipInputStream = new ZipInputStream(fileInputStream);
repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.deploy();
}else{
repositoryService.createDeployment()
.addInputStream(fileName, fileInputStream)
.deploy();
}
}
流程部署涉及到主要表:
- act_ge_bytearray 二进制数据表
- act_re_deployment 部署信息表
- act_re_procdef 流程定义数据表
act_ge_bytearray 表:
字段 DEPLOYMENT_ID_
关联 act_re_deployment
表的 ID_
字段。
act_re_deployment 表:
act_re_procdef 表:
二、启动流程实例
1、启动实例
新增请假:
/**
* 新增请假
*
* @param workflowLeave 请假
* @return 结果
*/
@Override
public int insertWorkflowLeave(WorkflowLeave workflowLeave) {
String id = UUID.randomUUID().toString();
workflowLeave.setId(id);
workflowLeave.setCreateTime(DateUtils.getNowDate());
String join = StringUtils.join(sysUserService.selectUserNameByPostCodeAndDeptId("se", SecurityUtils.getLoginUser().getUser().getDeptId()), ",");
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
.start()
.withProcessDefinitionKey("leave")
.withName(workflowLeave.getTitle())
.withBusinessKey(id)
.withVariable("deptLeader",join)
.build());
workflowLeave.setInstanceId(processInstance.getId());
workflowLeave.setState("0");
workflowLeave.setCreateName(SecurityUtils.getNickName());
workflowLeave.setCreateBy(SecurityUtils.getUsername());
workflowLeave.setCreateTime(DateUtils.getNowDate());
return workflowLeaveMapper.insertWorkflowLeave(workflowLeave);
}
workflow_leave
业务表:
启动实例ID:
InstanceId = 1dfab580-4218-11eb-ba85-005056c00001
涉及的表:
act_ru_execution
: 代表正在执行的流程实例表,如果当期正在执行的流程实例结束以后,该行在这张表中就被删除掉了,所以该表也是一个临时表act_ru_task
: 代表正在执行的任务表,该表是一个临时表,如果当前任务被完成以后,任务在这张表中就被删除掉了act_hi_actinst
: 流程图上出现的每一个元素都称为activity,流程图上正在执行的元素或者已经执行完成的元素称为activity instanceact_hi_procinst
: 流程历史表- act_hi_taskinst 历史任务表
act_ru_execution 表:
执行到第一个节点:
第一条记录为实例绑定具体的业务BUSINESS_KEY_
,具体的业务名;
第二条记录为实例绑定【部门领导审批】节点,并且绑定节点 ACT_ID_=deptLeaderVer
SELECT
ID_,PROC_INST_ID_,BUSINESS_KEY_,PARENT_ID_,PROC_DEF_ID_,ROOT_PROC_INST_ID_,ACT_ID_,IS_ACTIVE_,IS_SCOPE_,NAME_,START_TIME_
FROM `act_ru_execution`
act_ru_task 表:
act_hi_actinst 表:
说明:
*** act_hi_actinst
* 1、说明
* act:activiti
* hi:history
* actinst:activity instance
* 流程图上出现的每一个元素都称为activity
* 流程图上正在执行的元素或者已经执行完成的元素称为activity instance
* 2、字段
* proc_def_id:pdid
* proc_inst_id:流程实例ID
* execution_id_:执行ID
* act_id_:activity
* act_name
* act_type**
act_hi_procinst 表:
说明:
*** act_hi_procinst
* 1、说明
* procinst:process instance 历史的流程实例
* 正在执行的流程实例也在这张表中
* 如果end_time_为null,说明正在执行,如果有值,说明该流程实例已经结束了**
act_ru_identitylink 表:
act_workflow_formdata 表:
流程图文件:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.kafeitu.me/activiti/leave">
<process id="leave" name="请假流程-普通表单" isExecutable="true">
<documentation>请假流程演示</documentation>
<startEvent id="startevent1" name="Start" />
<userTask id="deptLeaderVerify" name="部门领导审批" activiti:formKey="deptLeaderVerify" activiti:candidateUsers="${deptLeader}">
<extensionElements>
<activiti:formProperty id="FormProperty_3qipis2--__!!radio--__!!审批意见--__!!i--__!!同意--__--不同意" type="string" />
<activiti:formProperty id="FormProperty_0lffpcm--__!!textarea--__!!批注--__!!f__!!null" type="string" />
</extensionElements>
</userTask>
<exclusiveGateway id="exclusivegateway5">
<outgoing>Flow_0q3bbjl</outgoing>
</exclusiveGateway>
<userTask id="hrVerify" name="人事审批" activiti:formKey="hrVerify" activiti:candidateGroups="hr">
<extensionElements>
<activiti:formProperty id="FormProperty_23u95jb--__!!radio--__!!审批意见--__!!i--__!!同意--__--不同意" type="string" />
<activiti:formProperty id="FormProperty_3t7tfkv--__!!textarea--__!!批注--__!!f--__!!null" type="string" />
</extensionElements>
</userTask>
<exclusiveGateway id="exclusivegateway6">
<outgoing>Flow_0p85954</outgoing>
<outgoing>Flow_0ji7qcv</outgoing>
</exclusiveGateway>
<endEvent id="endevent1" name="End">
<incoming>Flow_0p85954</incoming>
<incoming>Flow_0ji7qcv</incoming>
<incoming>Flow_0q3bbjl</incoming>
</endEvent>
<sequenceFlow id="flow2" sourceRef="startevent1" targetRef="deptLeaderVerify" />
<sequenceFlow id="flow3" sourceRef="deptLeaderVerify" targetRef="exclusivegateway5" />
<sequenceFlow id="flow5" name="同意" sourceRef="exclusivegateway5" targetRef="hrVerify">
<conditionExpression xsi:type="tFormalExpression">${FormProperty_3qipis2==0}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" sourceRef="hrVerify" targetRef="exclusivegateway6" />
<sequenceFlow id="Flow_0p85954" sourceRef="exclusivegateway6" targetRef="endevent1">
<extensionElements>
<activiti:executionListener class="com.ruoyi.leave.instener.LeaveEndStateListener" event="take">
<activiti:field name="state">
<activiti:string>1</activiti:string>
</activiti:field>
</activiti:executionListener>
</extensionElements>
<conditionExpression xsi:type="tFormalExpression">${FormProperty_23u95jb==0}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="Flow_0ji7qcv" sourceRef="exclusivegateway6" targetRef="endevent1">
<extensionElements>
<activiti:executionListener class="com.ruoyi.leave.instener.LeaveEndStateListener" event="take">
<activiti:field name="state">
<activiti:string>2</activiti:string>
</activiti:field>
</activiti:executionListener>
</extensionElements>
</sequenceFlow>
<sequenceFlow id="Flow_0q3bbjl" sourceRef="exclusivegateway5" targetRef="endevent1">
<extensionElements>
<activiti:executionListener class="com.ruoyi.leave.instener.LeaveEndStateListener" event="take">
<activiti:field name="state">
<activiti:string>2</activiti:string>
</activiti:field>
</activiti:executionListener>
</extensionElements>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leave">
<bpmndi:BPMNPlane id="BPMNPlane_leave" bpmnElement="leave">
<bpmndi:BPMNEdge id="Flow_0q3bbjl_di" bpmnElement="Flow_0q3bbjl">
<omgdi:waypoint x="260" y="83" />
<omgdi:waypoint x="260" y="140" />
<omgdi:waypoint x="582" y="140" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0ji7qcv_di" bpmnElement="Flow_0ji7qcv">
<omgdi:waypoint x="505" y="83" />
<omgdi:waypoint x="505" y="140" />
<omgdi:waypoint x="582" y="140" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0p85954_di" bpmnElement="Flow_0p85954">
<omgdi:waypoint x="525" y="63" />
<omgdi:waypoint x="600" y="63" />
<omgdi:waypoint x="600" y="122" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow6" bpmnElement="flow6">
<omgdi:waypoint x="453" y="63" />
<omgdi:waypoint x="485" y="63" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow5" bpmnElement="flow5">
<omgdi:waypoint x="280" y="63" />
<omgdi:waypoint x="348" y="63" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="300" y="46" width="22" height="11" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow3" bpmnElement="flow3">
<omgdi:waypoint x="185" y="63" />
<omgdi:waypoint x="240" y="63" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_flow2" bpmnElement="flow2">
<omgdi:waypoint x="35" y="63" />
<omgdi:waypoint x="80" y="63" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="BPMNShape_startevent1" bpmnElement="startevent1">
<omgdc:Bounds x="0" y="46" width="35" height="35" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="5" y="81" width="25" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_deptLeaderVerify" bpmnElement="deptLeaderVerify">
<omgdc:Bounds x="80" y="36" width="105" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_exclusivegateway5" bpmnElement="exclusivegateway5" isMarkerVisible="true">
<omgdc:Bounds x="240" y="43" width="40" height="40" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_hrVerify" bpmnElement="hrVerify">
<omgdc:Bounds x="348" y="36" width="105" height="55" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_exclusivegateway6" bpmnElement="exclusivegateway6" isMarkerVisible="true">
<omgdc:Bounds x="485" y="43" width="40" height="40" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_endevent1" bpmnElement="endevent1">
<omgdc:Bounds x="582" y="122" width="35" height="35" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="590" y="157" width="20" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
三、详细解析Task任务(非常重要)
任务的概念:需要有人进行审批或者申请的为任务
任务的执行人的情况类型:
情况一:当没有进入该节点之前,就可以确定任务的执行人
实例:比如进行“请假申请”的流程时候,最开始执行的就是提交”请假申请“,那么就需要知道,谁提交的“请假”,很明显,在一个系统中,谁登陆到系统里面,谁就有提交“请假任务”的提交人,那么执行人就可以确定就是登录人。
情况二:有可能一个任务节点的执行人是固定的。
实例:比如,在“公司财务报账”的流程中,最后一个审批的人,一定是财务部的最大的BOSS,所以,这样该流程的最后一个节点执行人,是不是就已经确定是为“财务部最大BOSS”了。
情况三:一个节点任务,之前是不存在执行人(未知),只有当符合身份的人,登陆系统,进入该系统,才能确定执行人。
实例:比如,如果当前的流程实例正在执行“自荐信审批”,这个时候,自荐信审批没有任务执行人,因为审批人是可以很多个,无法确定到底是谁,只有当咨询员登录系统以后才能给该任务赋值执行人,即存在只要是咨询员登陆,那么就可以看到所有的“自荐信”。
情况四:一个任务节点有n多人能够执行该任务,但是只要有一个人执行完毕就完成该任务了:组任务
实例:比如,“进入地铁站通道”的流程,我们一般地铁都是有N个安全检查的入口,有很多个人在进行检查,那么我们要想通过检查,那么任意一个检察员只要通过即可,这就是组任务。
详细分析:
情况一:
步骤:
(1)首先构建流程图:(注意区别上面的第一次画的内容)
(2)将bpmn的内容,生成一个png的图片(这个操作方法,上面都已经很详细了,不多说)
(3)代码实现步骤:
1:
/**
* 部署流程
*/
@Test
public void startDeployTest(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRepositoryService()
.createDeployment()
.name("请假流程:情况一")
.addClasspathResource("com/hnu/scw/task/shenqing.bpmn")
.deploy();
}
数据库情况:
2:
/**
* 启动流程实例
* 可以设置一个流程变量
*/
@Test
public void testStartPI(){
/**
* 流程变量
* 给<userTask id="请假申请" name="请假申请" activiti:assignee="#{student}"></userTask>
* 的student赋值
*/
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("student", "小明");
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRuntimeService()
.startProcessInstanceById("shenqing1:1:1304",variables);
}
数据库情况:
分析:如果,我们安装下面的代码执行,那么就出出现如下的错误
/**
* 启动流程实例
* 可以设置一个流程变量
*/
@Test
public void testStartPI(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRuntimeService()
.startProcessInstanceById("shenqing1:1:1304");
}
原因:是否还记得,我们在画流程图的时候,对该请假申请的节点,分配了一个#{student},这个变量,这个其实含义就是说,当我们进行该节点的处理的时候,就需要分配一个执行人,如果没有分配,就会发生上面的错误。然后再回头想一下,是不是就是我们的第一种情况呢?因为,在进行请假的流程的执行开始的时候,其实申请人是已经可以确定了,就是登陆的用户
。
3:后面的代码如下:
/**
* 在完成请假申请的任务的时候,给班主任审批的节点赋值任务的执行人
*/
@Test
public void testFinishTask_Teacher(){
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("teacher", "我是小明的班主任");
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getTaskService()
.complete("1405", variables); //完成任务的同时设置流程变量
}
/**
* 在完成班主任审批的情况下,给教务处节点赋值
*/
@Test
public void testFinishTask_Manager(){
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("manager", "我是小明的教务处处长");
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getTaskService()
.complete("1603", variables); //完成任务的同时设置流程变量
}
/**
* 结束流程实例
*/
@Test
public void testFinishTask(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getTaskService()
.complete("1703");
}
总结:针对情况一,那么我们必须要进入该节点执行前,就要分配一个执行人。
情况二:这个情况的话,这里不多介绍,因为之前的知识点中,都是在画流程图的时候就已经分配这个执行人了。可以回头去看看。
情况三:
步骤:(1)画流程图,这里不多介绍,就说一下需要修改的地方。
(2)编写的TaskListener监听类
package com.hnu.scw.tasklistener;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
/**
* @author Administrator
* @create 2018-01-16 11:10
* @desc tack任务的监听,主要是为了动态分配执行人
**/
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
/**
* 任务的执行人可以动态的赋值
* 1、流程变量
* 可以通过提取流程变量的方式给任务赋值执行人
* 2、可以操作数据库
* 方法一:(必须在web环境) WebApplicationContext ac = WebApplicationContextUtils
* .getWebApplicationContext(ServletActionContext.getServletContext());
xxxxService xxxxService = (xxxxService) ac.getBean("xxxxService");
方法二:通过JDBC来进行数据库操作
*/
//动态分配(这里是从上一节点中的tack变量的map中获取,只有流程没有结束,所有的变量都是可以获取)
/*String value = (String)delegateTask.getVariable("aaa");
delegateTask.setAssignee(value);*/
//静态分配(用于确定该执行人就只有一种情况,是一种固定的)
delegateTask.setAssignee("我是班主任");
}
}
通过这样的方式的话,当有“请假申请”进行提交之后,“班主任”的这个节点,就会自动进行分配执行人。
情况四:
流程图如下:
具体的测试代码:(注意看写的注释内容,就明白了对应的数据库的什么表)
package com.hnu.scw.test;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.Task;
import org.junit.Test;
import java.util.List;
/**
* @author scw
* @create 2018-01-23 15:45
* @desc 关于对于组任务的测试内容
**/
public class GroupTaskTest {
/**
* 主要是对于某些任务流程中,有N个人,但是只需要其中的某一个通过,
* 则该任务就通过了,所以针对这样的业务需求,就有如下的内容
*/
@Test
public void deployTashTest(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRepositoryService()
.createDeployment()
.addClasspathResource("com/hnu/scw/test/task3.bpmn")
.addClasspathResource("com/hnu/scw/test/task3.png")
.name("组任务的测试")
.deploy();
}
/**
* 当启动完流程实例以后,进入了"电脑维修"节点,该节点是一个组任务
* 这个时候,组任务的候选人就会被插入到两张表中
* act_ru_identitylink 存放的是当前正在执行的组任务的候选人
* act_hi_identitylink
*/
@Test
public void processTaskStartTest(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getRuntimeService()
.startProcessInstanceByKey("task3");
}
/**
* 对于act_hi_identitylink表,根据任务ID,即TASK_ID字段查询候选人
*/
@Test
public void testQueryCandidateByTaskId(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
List<IdentityLink> identityLinks = processEngine.getTaskService()
.getIdentityLinksForTask("2104");
for (IdentityLink identityLink : identityLinks) {
System.out.println(identityLink.getUserId());
}
}
/**
* 对于act_hi_identitylink表,根据候选人,即USER_ID_查看组任务
*/
@Test
public void testQueryTaskByCandidate(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
List<Task> tasks = processEngine.getTaskService()
.createTaskQuery()
.taskCandidateUser("工程师1")
.list();
for (Task task : tasks) {
System.out.println(task.getName());
}
}
/**
* 候选人中的其中一个人认领任务
*/
@Test
public void testClaimTask(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getTaskService()
/**
* 第一个参数为taskId
* 第二个参数为认领人
*/
.claim("2104", "工程师2");
}
}
四、附加知识点
1、Activiti关联业务
问题:Activiti里面本身自带有很多的数据表,它里面都是存在着关联关系,那么如何将其本身的表与我们的实际业务中的表进行关联呢?
解惑:其实,这个对于Activiti早已经想到这个问题,就是通过act_ru_exectution这个表中的business_key这个字段来进行关联。
实例分析:比如,针对上面的请假流程,那么,我们肯定在自己的业务中,就需要一张请假的信息表,比如,里面就包含,请假原因,请假人,请假时间等等基本请假信息。然后,我们其他的业务,也会根据这张表的内容,进行不断的扩充,比如,还需要记录对每条请假信息,每个审批节点中每个人的具体描述信息,那么这样就出现了一张“请假审批详细表”,很明显,这两张表就是通过“请假表中的主键ID”来进行关联的,那么就作为“请假详情表”中的外键。。。那么,同理,我们也是一样的,我们就通过对于act_ru_exectution这个数据表的business_key字段来关联着我们的业务主键即可。所以,这样就把我们自身的业务和Activiti进行了关联。
2、Activiti工作流的自带数据表的含义
(1)资源库流程规则表
1)act_re_deployment 部署信息表
2)act_re_model 流程设计模型部署表
3)act_re_procdef 流程定义数据表
(2):运行时数据库表
1)act_ru_execution 运行时流程执行实例表
2)act_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者的相关信息
3)act_ru_task 运行时任务节点表
4)act_ru_variable 运行时流程变量数据表
(3):历史数据库表
1)act_hi_actinst 历史节点表
2)act_hi_attachment 历史附件表
3)act_hi_comment 历史意见表
4)act_hi_identitylink 历史流程人员表
5)act_hi_detail 历史详情表,提供历史变量的查询
6)act_hi_procinst 历史流程实例表
7)act_hi_taskinst 历史任务实例表
8)act_hi_varinst 历史变量表
(4):组织机构表
1)act_id_group 用户组信息表
2)act_id_info 用户扩展信息表
3)act_id_membership 用户与用户组对应信息表
4)act_id_user 用户信息表
这四张表很常见,基本的组织机构管理,关于用户认证方面建议还是自己开发一套,组件自带的功能太简单,使用中有很多需求难以满足
(5):通用数据表
1)act_ge_bytearray 二进制数据表
2)act_ge_property 属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录,
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。