Activity7工作流

1.工作流介绍

workflow通过计算机对业务流程自动化管理,用于设计、执行和监控复杂的业务流程

应用领域

企业: 采购流程、合同审核流程

客户: 客户电话投诉处理流程

生活中: 住房贷款审批流程、办理身份证、办理准生手续

行政: 出差审批、报销流程、请假审批、用车流程、会议室申请

银行业: 信贷审批、信用卡发卡审批

人事: 员工培训、绩效考核、职位变动

现实中的例子-出差费用报销

2.工作流引擎

是一种按照预定义规则的[符合bpmn规范]进行部署

将业务节点的流程进行分离的特定形式的关联,实现节点自动流转的工作流框架

1.需要将预定于的流程文件BPMN部署到工作流引擎中,会把节点,路径信息存储到数据库

中.

2.工作流引擎提供了大量的API对流程进行查询处理,细节都是对应用程序屏蔽的,大大提供

开发效率

3.业务逻辑的处理和流程的流转是分离的,是通过BusinessKey进行关联的.

Activity7

Activiti 是一个工作流引擎, activiti 可以将业务系统中复杂的业务流程抽取出来,使

用专门的建模语言(BPMN2.0)进行定义,业务系统按照预先定义的流程进行执行,实现了

业务系统的业务流程由 activiti 进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,

从而提高系统的健壮性,同时也减少了系统开发维护成本。

Activity7内部核心机制

1
2
3
4
5
6
7
8
9
10
1.业务流程图要规范化,需要遵守一套标准。
2.业务流程图本质上就是一个XML文件,而XML可以存放所要的数据。

3.读取业务流程图的过程就是解析XML文件的过程。
4.读取一个业务流程图的结点就相当于解析一个XML的结点,进一步将数据插入到
MySQL表中,形成一条记录。
5.将一个业务流程图的所有节点都读取并存入到MySQL表中。
6.后面只要读取MySQL表中的记录就相当于读取业务流程图的一个节点。
7.业务流程的推进,后面就转换为读取表中的数据,并且处理数据,结束的时候这
一行数据就可以删除了。

image-20240715233900634

BPMN

BPMN(Business Process Model And Notation),业务流程模型和符号

Activit就是使用BPMN2.0进行流程建模、流程执行管理,它包括很多的建模符号。

BPMN是用XML的形式表示流程业务的,我们画的流程图就会转为XML文件,交给Activity解析

Activity使用步骤

如何使用Activity工作流

1.整合Activity

  • Activiti是一个工作流引擎,业务系统使用Activiti来对系统的业务流程进行自动化管理,为了方便业务系统访问(操作)Activiti的接口或功能,通常将Activiti和业务系统的环境集成在一起。

2.业务流程建模

  • 使用Activiti流程建模工具(Activity-designer)定义业务流程(.bpmn文件)。

  • .bpmn文件就是业务流程定义文件,通过xml定义业务流程。

  • 如果使用其他公司开发的工作引擎一般都提供了可视化的建模工具(Process

    Designer)用于生成流程定义文件,建模工具操作直观,一般都支持图形化拖拽方

    式、多窗口的用户界面、丰富的过程图形元素、过程元素拷贝、粘贴、删除等功能。

3.部署业务流程

  • 定义bpmn文件,部署bpmn文件

4.启动流程实例

  • 启动一个流程实例代表开始一次业务流程的执行,张三请假开启一个,李四请假开启一个,互不影响

5.查询待办业务

  • 系统的业务流程已经交给Activity管理,通过Activity就可以查询到当前流程执行到哪里了,交给Activity管理不需要SQL查询

6.处理代办业务

  • 用户查询待办业务后,就执行办理某个任务,执行完毕之后返回给下一个执行人或者流程终止

7.流程结束

集成Activity7

引入Maven依赖,做好配置后,启动Activity自动生成25张表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 <!-- activiti引擎 -->
       <dependency>
           <groupId>org.activiti</groupId>
           <artifactId>activiti-engine</artifactId>
           <version>${activiti.version}</version>
       </dependency>
       <!-- 整合Spring -->
       <dependency>
           <groupId>org.activiti</groupId>
           <artifactId>activiti-spring</artifactId>
           <version>${activiti.version}</version>
       </dependency>
       <!-- bpmn 模型处理 ,把图形翻译成模型对象-->
       <dependency>
           <groupId>org.activiti</groupId>
           <artifactId>activiti-bpmn-model</artifactId>
           <version>${activiti.version}</version>
       </dependency>
       <!-- bpmn 转换 转换成XML语句-->
       <dependency>
           <groupId>org.activiti</groupId>
           <artifactId>activiti-bpmn-converter</artifactId>
           <version>${activiti.version}</version>
       </dependency>
       <!-- bpmn json数据转换 -->
       <dependency>
           <groupId>org.activiti</groupId>
           <artifactId>activiti-json-converter</artifactId>
           <version>${activiti.version}</version>
       </dependency>
       <!-- bpmn 布局 -->
       <dependency>
           <groupId>org.activiti</groupId>
           <artifactId>activiti-bpmn-layout</artifactId>
           <version>${activiti.version}</version>
       </dependency>

启动流程引擎

1.配置数据库

2.Activity的流程引擎配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?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">
   <!--数据库连接池-->
   <bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource">
       <property name="driverClassName"
value="com.mysql.jdbc.Driver" />
       <property name="url" value="jdbc:mysql:///activiti" />
       <property name="username" value="root" />
       <property name="password" value="admin" />
   </bean>
   <!-- 默认id对应的值 为processEngineConfiguration -->
   <bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfigurat
ion">
       <property name="dataSource" ref="dataSource"/>
       <!--
           activiti数据库表处理策略
4.5 测试
创建一个测试类,调用activiti的工具类,生成acitivti需要的数据库表。代码如下:
4.6 数据库表的命名规则
此时我们查看数据库,发现25张表,结果如下所示:
Activiti的表都是以 ACT_ 开头。第二部分是表示表的用途的两个字母标识。用途也和服务
的API对应。
               false(默认值):检查数据库的版本和依赖库的版本,如果不匹配就抛出
异常
               true:构建流程引擎时,执行检查,如果需要就执行更新。如果表不存
在,就创建。
               create-drop:构建流程引擎时创建数据库报表,关闭流程引擎时就删
除这些表。
               drop-create:先删除表再创建表。
               create:构建流程引擎时创建数据库表,关闭流程引擎时不删除这些表
       -->
       <property name="databaseSchemaUpdate" value="true"/>
   </bean>
</beans>

启动流程:

1
2
3
4
5
6
7
8
9
10
11
12
package cn.wolfcode.demo;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.junit.Test;
public class _01TestInit {
   @Test
   public void testInit(){
//初始化流程引擎
       ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
       System.out.println(processEngine);
  }
}

初始化后,数据库会生成25张表,流程引擎所需要的表

QQ_1722388737933

Activity的表都是以act开头,第二部分表示用途的标识

  • act_re,表示Repository,包含了流程定义和静态资源

  • act_ru,表示runtime,运行时候的表,包含流程实例,任务、变量,异步任务等运行中的数据。Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。这些运行时表可以一直很小并且速度很快。

  • act_hi,history,表包含历史数据,历史流程实例,变量,任务等

  • act_ge,general,通用数据,用于不同场景下

流程引擎API

QQ_1722389507888

1
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

这段代码启动默认的流程引擎,默认读取默认是读取 resource 目录下的 activiti.cfg.xml 文件,这个文件本质上就是spring的配置文件,加载之后会读取配置文件中id名为 processEngineConfiguration

的bean,通过这个配置对象可以获取到流程引擎对象 ProcessEngine

Service层总览

QQ_1722389652455

  • RepositoryService,是Activiti的资源管理接口,提供了管理和控制流程发布包和流

    程定义的操作。使用工作流建模工具设计的业务流程图需要使用此Service将流程定

    义文件的内容部署到计算机中。

  • RuntimeService,是Activiti的流程运行管理接口,可以从这个接口中获取很多关于

    流程执行相关的信息。

  • TaskService,是Activiti的任务管理接口,可以从这个接口中获取任务的信息。

  • HistoryService,是Activiti的历史管理类,可以查询历史信息,执行流程时,引擎

    会包含很多数据(根据配置),比如流程实例启动时间,任务的参与者,完成任务的

    时间,每个流程实例的执行路径,等等。

  • ManagementService,是Activiti的引擎管理接口,提供了对Activiti流程引擎的管

    理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于Activiti系统的

    日常维护。

流程操作表

画好bpmn流程图后,再转为png格式

如果我们把 bpmn 文件部署到Web环境,那么就只能看到xml信息,无法看到类似上面的图形

了.

我们可以再部署之前,根据 bpmn 文件生成 png 文件,然后把 bpmn 和 png 文件同时部署到

Web环境中.如果我们想查看流程图的话,我们就可以通过Activiti把这个 png 文件读取出

来. 接下来我们把 bpmn 导出为 png 文件

QQ_1722392928410

将画的流程图信息,进行部署

1、流程部署

观察日志发现,进行部署会操作如下表

  • ACT_GE_PROPERTY 引擎属性表

1
2
3
Preparing: update ACT_GE_PROPERTY SET REV_ = ?, VALUE_ = ? where
NAME_ = ? and REV_ = ?
Parameters: 2(Integer), 2501(String), next.dbid(String), 1(Integer)
  • ACT_RE_PROCDEF 流程定义表

1
2
3
4
5
6
7
8
9
10
Preparing: insert into ACT_RE_PROCDEF(ID_, REV_, CATEGORY_, NAME_,
KEY_, VERSION_, DEPLOYMENT_ID_, RESOURCE_NAME_, DGRM_RESOURCE_NAME_,
DESCRIPTION_, HAS_START_FORM_KEY_, HAS_GRAPHICAL_NOTATION_ ,
SUSPENSION_STATE_, TENANT_ID_, ENGINE_VERSION_) values (?, 1, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Parameters: leaveProcess:1:4(String),
http://www.activiti.org/test(String), 请假流程(String),
leaveProcess(String), 1(Integer), 1(String), bpmn/leave.bpmn(String),
bpmn/leave.png(String), null, false(Boolean), true(Boolean),
1(Integer), (String), null
  • ACT_RE_DEPLOYMENT 流程部署表

1
2
3
4
5
Preparing: insert into ACT_RE_DEPLOYMENT(ID_, NAME_, CATEGORY_, KEY_,
TENANT_ID_, DEPLOY_TIME_, ENGINE_VERSION_) values(?, ?, ?, ?, ?, ?,
?)
Parameters: 1(String), 请假流程(String), null, null, (String), 2021-06-
02 11:01:44.838(Timestamp), null
  • ACT_GE_BYTEARRAY 二进制资源表

1
2
3
4
5
6
7
8
Preparing: INSERT INTO ACT_GE_BYTEARRAY(ID_, REV_, NAME_, BYTES_,
DEPLOYMENT_ID_, GENERATED_) VALUES (?, 1, ?, ?, ?, ?) , (?, 1, ?, ?,
?, ?)
Parameters: 2(String), bpmn/leave.png(String),
java.io.ByteArrayInputStream@7fe7c640(ByteArrayInputStream),
1(String), false(Boolean), 3(String), bpmn/leave.bpmn(String),
java.io.ByteArrayInputStream@4c4748bf(ByteArrayInputStream),
1(String), false(Boolean)

2、启动流程实例

  • 流程定义部署在Activiti中之后就可以通过工作流管理业务流程了。

  • 针对该流程,启动一个流程表示发起一个新的请假申请单,这就相当于Java类和Java对象的关系,类定义好之后需要new创建一个对象使用,当然,也可以new多个对象。

  • 对于请假申请流程,张三发起一个请假申请单需要启动一个流程实例,李四发起一个请求申请单也需要启动一个流程实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testStartProcess(){
//创建ProcessEngine对象
ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
//获取RuntimeService对象
RuntimeService runtimeService =
processEngine.getRuntimeService();
//根据流程定义的key启动流程实例,这个key是在定义bpmn的时候设置的
ProcessInstance instance = runtimeService.

startProcessInstanceByKey("leaveProcess");
//获取流程实例的相关信息
System.out.println("流程定义的id = " +
instance.getProcessDefinitionId());
System.out.println("流程实例的id = " + instance.getId());
}

观察日志发现,启动流程会操作如下表:

  • ACT_HI_TASKINST 历史任务表

1
2
3
4
5
6
7
8
9
10
Preparing: insert into ACT_HI_TASKINST ( ID_, PROC_DEF_ID_,
PROC_INST_ID_, EXECUTION_ID_, NAME_, PARENT_TASK_ID_, DESCRIPTION_,
OWNER_, ASSIGNEE_, START_TIME_, CLAIM_TIME_, END_TIME_, DURATION_,
DELETE_REASON_, TASK_DEF_KEY_, FORM_KEY_, PRIORITY_, DUE_DATE_,
CATEGORY_, TENANT_ID_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 2505(String), leaveProcess:1:4(String), 2501(String),
2502(String), 部门经理审批(String), null, null, null, 李四(String),
2021-06-02 16:34:52.827(Timestamp), null, null, null, null,
_4(String), null, 50(Integer), null, null, (String)
  • ACT_HI_PROCINST 历史流程实例表

1
2
3
4
5
6
7
8
Preparing: insert into ACT_HI_PROCINST ( ID_, PROC_INST_ID_,
BUSINESS_KEY_, PROC_DEF_ID_, START_TIME_, END_TIME_, DURATION_,
START_USER_ID_, START_ACT_ID_, END_ACT_ID_,
SUPER_PROCESS_INSTANCE_ID_, DELETE_REASON_, TENANT_ID_, NAME_ )
values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 2501(String), 2501(String), null,
leaveProcess:1:4(String), 2021-06-02 16:34:52.798(Timestamp), null,
null, null, _2(String), null, null, null, (String), null
  • ACT_HI_ACTINST 历史活动信息表

1
2
3
4
5
6
7
8
9
10
11
12
13
Preparing: insert into ACT_HI_ACTINST ( ID_, PROC_DEF_ID_,
PROC_INST_ID_, EXECUTION_ID_, ACT_ID_, TASK_ID_, CALL_PROC_INST_ID_,
ACT_NAME_, ACT_TYPE_, ASSIGNEE_, START_TIME_, END_TIME_, DURATION_,
DELETE_REASON_, TENANT_ID_ ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?) , (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Parameters: 2503(String), leaveProcess:1:4(String), 2501(String),
2502(String), _2(String), null, null, StartEvent(String),
startEvent(String), null, 2021-06-02 16:34:52.815(Timestamp), 2021-
06-02 16:34:52.816(Timestamp), 1(Long), null, (String), 2504(String),
leaveProcess:1:4(String), 2501(String), 2502(String), _4(String),
2505(String), null, 部门经理审批(String), userTask(String), 李四
(String), 2021-06-02 16:34:52.817(Timestamp), null, null, null,
(String)
1
2
3
4
Preparing: insert into ACT_HI_IDENTITYLINK (ID_, TYPE_, USER_ID_,
GROUP_ID_, TASK_ID_, PROC_INST_ID_) values (?, ?, ?, ?, ?, ?)
Parameters: 2506(String), participant(String), 李四(String), null,
null, 2501(String)
  • ACT_RU_EXECUTION 运行时执行实例表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
insert into ACT_RU_EXECUTION (ID_, REV_, PROC_INST_ID_,
BUSINESS_KEY_, PROC_DEF_ID_, ACT_ID_, IS_ACTIVE_, IS_CONCURRENT_,
IS_SCOPE_,IS_EVENT_SCOPE_, IS_MI_ROOT_, PARENT_ID_, SUPER_EXEC_,
ROOT_PROC_INST_ID_, SUSPENSION_STATE_, TENANT_ID_, NAME_,
START_TIME_, START_USER_ID_, IS_COUNT_ENABLED_, EVT_SUBSCR_COUNT_,
TASK_COUNT_, JOB_COUNT_, TIMER_JOB_COUNT_, SUSP_JOB_COUNT_,
DEADLETTER_JOB_COUNT_, VAR_COUNT_, ID_LINK_COUNT_) values (?, 1, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?) , (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?)
Parameters: 2501(String), 2501(String), null,
leaveProcess:1:4(String), null, true(Boolean), false(Boolean),
true(Boolean), false(Boolean), false(Boolean), null, null,
2501(String), 1(Integer), (String), null, 2021-06-02
16:34:52.798(Timestamp), null, false(Boolean), 0(Integer),
0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer),
0(Integer), 0(Integer), 2502(String), 2501(String), null,
leaveProcess:1:4(String), _4(String), true(Boolean), false(Boolean),
false(Boolean), false(Boolean), false(Boolean), 2501(String), null,
2501(String), 1(Integer), (String), null, 2021-06-02
16:34:52.813(Timestamp), null, false(Boolean), 0(Integer),
0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer),
0(Integer), 0(Integer)
  • ACT_RU_TASK 运行时任务表

1
2
3
4
5
6
7
8
9
10
Preparing: insert into ACT_RU_TASK (ID_, REV_, NAME_,
PARENT_TASK_ID_, DESCRIPTION_, PRIORITY_, CREATE_TIME_, OWNER_,
ASSIGNEE_, DELEGATION_, EXECUTION_ID_, PROC_INST_ID_, PROC_DEF_ID_,
TASK_DEF_KEY_, DUE_DATE_, CATEGORY_, SUSPENSION_STATE_, TENANT_ID_,
FORM_KEY_, CLAIM_TIME_) values (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 2505(String), 部门经理审批(String), null, null,
50(Integer), 2021-06-02 16:34:52.817(Timestamp), null, 李四(String),
null, 2502(String), 2501(String), leaveProcess:1:4(String),
_4(String), null, null, 1(Integer), (String), null, null
1
2
3
4
5
Preparing: insert into ACT_RU_IDENTITYLINK (ID_, REV_, TYPE_,
USER_ID_, GROUP_ID_, TASK_ID_, PROC_INST_ID_, PROC_DEF_ID_) values
(?, 1, ?, ?, ?, ?, ?, ?)
Parameters: 2506(String), participant(String), 李四(String), null,
null, 2501(String), null

3、任务查询

流程启动后,各个任务的负责人就可以查询自己当前需要处理的任务,查询出来的任

务都是该用户的待办任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testSelectTodoTaskList(){
   //任务负责人
   String assignee = "李四";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取TaskService
   TaskService taskService = processEngine.getTaskService();
   //获取任务集合
   List<Task> taskList = taskService.createTaskQuery()
          .processDefinitionKey("leaveProcess")
          .taskAssignee(assignee)
          .list();
   //遍历任务列表
   for(Task task:taskList){
       System.out.println("流程定义id = " +
task.getProcessDefinitionId());
       System.out.println("流程实例id = " +
task.getProcessInstanceId());
       System.out.println("任务id = " + task.getId());
       System.out.println("任务名称 = " + task.getName());
  }
}

观察日志发现,查询任务会操作如下表:

  • ACT_RU_TASK 运行时任务表
  • ACT_RE_PROCDEF 流程定义表
1
2
3
4
5
Preparing: select distinct RES.* from ACT_RU_TASK RES inner join
ACT_RE_PROCDEF D on RES.PROC_DEF_ID_ = D.ID_ WHERE RES.ASSIGNEE_ = ?
and D.KEY_ = ? order by RES.ID_ asc LIMIT ? OFFSET ?
Parameters: 李四(String), leaveProcess(String), 2147483647(Integer),
0(Integer)

4、任务处理

任务负责人查询待办任务,选择任务进行处理,完成任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testCompleteTask(){
   //任务负责人
   String assignee = "李四";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取TaskService
   TaskService taskService = processEngine.getTaskService();
   //获取任务集合
   List<Task> taskList = taskService.createTaskQuery()
          .processDefinitionKey("leaveProcess")
          .taskAssignee(assignee)
          .list();
   //遍历任务列表
   for(Task task:taskList){
       taskService.complete(task.getId());
  }
}

观察日志发现,查询任务会操作如下表.

  • ACT_GE_PROPERTY 引擎属性表
1
2
3
Preparing: update ACT_GE_PROPERTY SET REV_ = ?, VALUE_ = ? where
NAME_ = ? and REV_ = ?
Parameters: 4(Integer), 7501(String), next.dbid(String), 3(Integer)
  • ACT_HI_TASKINST 历史任务表
1
2
3
4
5
6
7
8
9
10
Preparing: insert into ACT_HI_TASKINST ( ID_, PROC_DEF_ID_,
PROC_INST_ID_, EXECUTION_ID_, NAME_, PARENT_TASK_ID_, DESCRIPTION_,
OWNER_, ASSIGNEE_, START_TIME_, CLAIM_TIME_, END_TIME_, DURATION_,
DELETE_REASON_, TASK_DEF_KEY_, FORM_KEY_, PRIORITY_, DUE_DATE_,
CATEGORY_, TENANT_ID_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 5002(String), leaveProcess:1:4(String), 2501(String),
2502(String), 人事复批(String), null, null, null, 王五(String), 2021-
06-02 16:39:19.036(Timestamp), null, null, null, null, _5(String),
null, 50(Integer), null, null, (String)
  • ACT_HI_ACTINST 历史活动信息表
1
2
3
4
5
6
7
8
9
Preparing: insert into ACT_HI_ACTINST ( ID_, PROC_DEF_ID_,
PROC_INST_ID_, EXECUTION_ID_, ACT_ID_, TASK_ID_, CALL_PROC_INST_ID_,
ACT_NAME_, ACT_TYPE_, ASSIGNEE_, START_TIME_, END_TIME_, DURATION_,
DELETE_REASON_, TENANT_ID_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ? )
Parameters: 5001(String), leaveProcess:1:4(String), 2501(String),
2502(String), _5(String), 5002(String), null, 人事复批(String),
userTask(String), 王五(String), 2021-06-02 16:39:19.025(Timestamp),
null, null, null, (String)
  • ACT_HI_IDENTITYLINK 历史身份连接表
1
2
3
4
Preparing: insert into ACT_HI_IDENTITYLINK (ID_, TYPE_, USER_ID_,
GROUP_ID_, TASK_ID_, PROC_INST_ID_) values (?, ?, ?, ?, ?, ?)
Parameters: 5003(String), participant(String), 王五(String), null,
null, 2501(String)
  • ACT_RU_TASK 运行时任务表
1
2
3
4
5
6
7
8
9
10
Preparing: insert into ACT_RU_TASK (ID_, REV_, NAME_,
PARENT_TASK_ID_, DESCRIPTION_, PRIORITY_, CREATE_TIME_, OWNER_,
ASSIGNEE_, DELEGATION_, EXECUTION_ID_, PROC_INST_ID_, PROC_DEF_ID_,
TASK_DEF_KEY_, DUE_DATE_, CATEGORY_, SUSPENSION_STATE_, TENANT_ID_,
FORM_KEY_, CLAIM_TIME_) values (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 5002(String), 人事复批(String), null, null, 50(Integer),
2021-06-02 16:39:19.025(Timestamp), null, 王五(String), null,
2502(String), 2501(String), leaveProcess:1:4(String), _5(String),
null, null, 1(Integer), (String), null, null
  • ACT_RU_IDENTITYLINK 运行时身份连接表
1
2
3
4
5
Preparing: insert into ACT_RU_IDENTITYLINK (ID_, REV_, TYPE_,
USER_ID_, GROUP_ID_, TASK_ID_, PROC_INST_ID_, PROC_DEF_ID_) values
(?, 1, ?, ?, ?, ?, ?, ?)
Parameters: 5003(String), participant(String), 王五(String), null,
null, 2501(String), null
  • ACT_RU_EXECUTION 运行时执行实例表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Preparing: update ACT_RU_EXECUTION set REV_ = ?, BUSINESS_KEY_ = ?,
PROC_DEF_ID_ = ?, ACT_ID_ = ?, IS_ACTIVE_ = ?, IS_CONCURRENT_ = ?,
IS_SCOPE_ = ?, IS_EVENT_SCOPE_ = ?, IS_MI_ROOT_ = ?, PARENT_ID_ = ?,
SUPER_EXEC_ = ?, ROOT_PROC_INST_ID_ = ?, SUSPENSION_STATE_ = ?, NAME_
= ?, IS_COUNT_ENABLED_ = ?, EVT_SUBSCR_COUNT_ = ?, TASK_COUNT_ = ?,
JOB_COUNT_ = ?, TIMER_JOB_COUNT_ = ?, SUSP_JOB_COUNT_ = ?,
DEADLETTER_JOB_COUNT_ = ?, VAR_COUNT_ = ?, ID_LINK_COUNT_ = ? where
ID_ = ? and REV_ = ?
Parameters: 2(Integer), null, leaveProcess:1:4(String), _5(String),
true(Boolean), false(Boolean), false(Boolean), false(Boolean),
false(Boolean), 2501(String), null, 2501(String), 1(Integer), null,
false(Boolean), 0(Integer), 0(Integer), 0(Integer), 0(Integer),
0(Integer), 0(Integer), 0(Integer), 0(Integer), 2502(String),
1(Integer)
  • ACT_HI_TASKINST 历史任务表
1
2
3
4
5
6
7
8
9
Preparing: update ACT_HI_TASKINST set PROC_DEF_ID_ = ?, EXECUTION_ID_
= ?, NAME_ = ?, PARENT_TASK_ID_ = ?, DESCRIPTION_ = ?, OWNER_ = ?,
ASSIGNEE_ = ?, CLAIM_TIME_ = ?, END_TIME_ = ?, DURATION_ = ?,
DELETE_REASON_ = ?, TASK_DEF_KEY_ = ?, FORM_KEY_ = ?, PRIORITY_ = ?,
DUE_DATE_ = ?, CATEGORY_ = ? where ID_ = ?
Parameters: leaveProcess:1:4(String), 2502(String), 部门经理审批
(String), null, null, null, 李四(String), null, 2021-06-02
16:39:18.995(Timestamp), 266168(Long), null, _4(String), null,
50(Integer), null, null, 2505(String)
  • ACT_HI_TASKINST 历史任务表
1
2
3
4
Preparing: update ACT_HI_ACTINST set EXECUTION_ID_ = ?, ASSIGNEE_ =
?, END_TIME_ = ?, DURATION_ = ?, DELETE_REASON_ = ? where ID_ = ?
Parameters: 2502(String), 李四(String), 2021-06-02
16:39:19.009(Timestamp), 266192(Long), null, 2504(String)
  • ACT_RU_TASK 运行时任务表
1
2
Preparing: delete from ACT_RU_TASK where ID_ = ? and REV_ = ?
Parameters: 2505(String), 1(Integer)

5、添加审批意见

在执行任务之前可以给该任务添加审批意见,会存储在历史表中,我们后续可以审批历史中查看到该意见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testAddComment(){
   //任务负责人
   String assignee = "王五";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取TaskService
   TaskService taskService = processEngine.getTaskService();
   //获取任务集合
   List<Task> taskList = taskService.createTaskQuery()
          .processDefinitionKey("leaveProcess")
          .taskAssignee(assignee)
          .list();
   //遍历任务列表
   for(Task task:taskList){
       //在任务执行之前任务添加批注信息
     
taskService.addComment(task.getId(),task.getProcessInstanceId(),task
.getName()+"审批通过");
       taskService.complete(task.getId());
  }
}

观察日志发现,其余操作和任务处理的表是一致的,但是添加批注会往如下表中插入记录:

1
2
3
4
5
6
7
Preparing: insert into ACT_HI_COMMENT (ID_, TYPE_, TIME_, USER_ID_,
TASK_ID_, PROC_INST_ID_, ACTION_, MESSAGE_, FULL_MSG_) values (?, ?,
?, ?, ?, ?, ?, ?, ?)
Parameters: 7501(String), comment(String), 2021-06-02
16:43:09.13(Timestamp), null, 5002(String), 2501(String),
AddComment(String), 人事复批审批通过(String),
java.io.ByteArrayInputStream@1b765a2c(ByteArrayInputStream)

6、查看历史审批

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Test
public void testSelectHistoryTask(){
   //流程实例ID
   String processInstanceId = "2501";
   //任务审核人
   String taskAssignee = "王五";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取historyService
   HistoryService historyService =
processEngine.getHistoryService();
   //获取taskService
   TaskService taskService = processEngine.getTaskService();
   //获取历史审核信息
   List<HistoricActivityInstance> list = historyService
          .createHistoricActivityInstanceQuery()
          .activityType("userTask")//只获取用户任务
          .processInstanceId(processInstanceId)
          .taskAssignee(taskAssignee)
          .finished()
          .list();
   for(HistoricActivityInstance instance:list){
       System.out.println("任务名称:"+instance.getActivityName());
       System.out.println("任务开始时间:"+instance.getStartTime());
       System.out.println("任务结束时间:"+instance.getEndTime());
       System.out.println("任务耗时:"+instance.getDurationInMillis());
       //获取审核批注信息
       List<Comment> taskComments =
taskService.getTaskComments(instance.getTaskId());
       if(taskComments.size()>0){
           System.out.println("审批批
注:"+taskComments.get(0).getFullMessage());
      }
  }
}

8、流程定义查询

查询流程相关信息,包含流程定义,流程部署,流程定义版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testDefinitionQuery(){
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取仓库服务
   RepositoryService repositoryService =
processEngine.getRepositoryService();
   //获取流程定义集合
   List<ProcessDefinition> processDefinitionList = repositoryService
          .createProcessDefinitionQuery()
          .processDefinitionKey("leaveProcess")
          .list();
   //遍历集合
   for (ProcessDefinition definition:processDefinitionList){
       System.out.println("流程定义ID:"+definition.getId());
       System.out.println("流程定义名称:"+definition.getName());
       System.out.println("流程定义key:"+definition.getKey());
       System.out.println("流程定义版本:"+definition.getVersion());
       System.out.println("流程部署ID:"+definition.getDeploymentId());
       System.out.println("====================");
  }
}

9、流程资源下载

现在我们的流程资源文件已经上传到数据库了,如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载到本地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Test
public void testDownloadResource() throws Exception {
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取仓库服务
   RepositoryService repositoryService =
processEngine.getRepositoryService();
   //获取流程定义集合
   List<ProcessDefinition> list = repositoryService
          .createProcessDefinitionQuery()
          .processDefinitionKey("leaveProcess")
          .orderByProcessDefinitionVersion()//按照版本排序
          .desc()//降序
          .list();
7.1.3 流程定义删除
根据部署Id删除对应的流程定义
说明:
   //获取最新那个
   ProcessDefinition definition =list.get(0);
   //获取部署ID
   String deploymentId = definition.getDeploymentId();
   //获取bpmn的输入流
   InputStream bpmnInput = repositoryService.getResourceAsStream(
                                       deploymentId,
                                     
definition.getResourceName());
   //获取png的输入流
   InputStream pngInput = repositoryService.getResourceAsStream(
                                       deploymentId,
                                     
definition.getDiagramResourceName());
   //设置bpmn输入
   FileOutputStream bpmnOutPut = new
FileOutputStream("D:/leave.bpmn");
   //设置png输入
   FileOutputStream pngOutPut = new
FileOutputStream("D:/leave.png");
   IOUtils.copy(bpmnInput,bpmnOutPut);
   IOUtils.copy(pngInput,pngOutPut);
}

9、流程定义删除

根据部署Id删除对应的流程定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testDeleteDeploy(){
   //流程部署Id
   String deploymentId = "10001";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取仓库服务
   RepositoryService repositoryService =
processEngine.getRepositoryService();
   //删除流程定义,如果该流程定义已有流程实例启动则删除时出错
   repositoryService.deleteDeployment(deploymentId);
   //设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false
非级别删除方式,如果流程
   //repositoryService.deleteDeployment(deploymentId,true);
}
  1. 如果该流程定义下没有正在运行的流程,则可以用普通删除。
  2. 如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流

程及相关记录全部删除。

  1. 项目开发中级联删除操作一般只开放给超级管理员使用.

流程实例相关

用户按照程序定义内容发起一个流程,就是一个流程实例

QQ_1722482654864

那么申请人的请假信息【请假时间、请假理由】是如何绑定到流程中的呢?

业务标识:BusinessKey

  • 启动流程实例时,指定的businessKey,就会在act_run_execution表中存储businessKey。

  • BusinessKey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。比如:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到Activiti中,将来查询Activiti的流程实例信息就可以获取请假单的id从而关联查询业务

    系统数据库得到请假单信息。

QQ_1722494589407

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testStartProcess(){
   String businessKey = "8001";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取RuntimeService对象
   RuntimeService runtimeService =
processEngine.getRuntimeService();
   //根据流程定义的key启动流程实例,这个key是在定义bpmn的时候设置的
   //在启动流程的时候将业务key加入进去
   ProcessInstance instance = runtimeService
          .startProcessInstanceByKey("leaveProcess",businessKey);
   //获取流程实例的相关信息
   System.out.println("流程定义的id = " +
instance.getProcessDefinitionId());
   System.out.println("流程实例的id = " + instance.getId());
}

观察数据库可以发现,在activiti的act_ru_execution表,字段BUSINESS_KEY就是存放业务KEY的。

  • 在用户执行任务的时候如何获取 BusinessKey 并关联对应的业务信息呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
public void testGetBusinessKey(){
   //任务负责人
   String assignee = "李四";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取TaskService
   TaskService taskService = processEngine.getTaskService();
   //获取RuntimeService
   RuntimeService runtimeService =
processEngine.getRuntimeService();
   //获取任务集合
   List<Task> taskList = taskService.createTaskQuery()
          .processDefinitionKey("leaveProcess")
          .taskAssignee(assignee)
          .list();
   //遍历任务列表
   for(Task task:taskList){
       System.out.println("流程定义id = " +
task.getProcessDefinitionId());
       System.out.println("流程实例id = " +
task.getProcessInstanceId());
       System.out.println("任务id = " + task.getId());
       System.out.println("任务名称 = " + task.getName());
       //根据任务上的流程实例Id查询出对应的流程实例对象,从流程实例对象中获取
BusinessKey
       ProcessInstance instance = runtimeService
              .createProcessInstanceQuery()
              .processInstanceId(task.getProcessInstanceId())
              .singleResult();
       System.out.println("业务key:"+instance.getBusinessKey());
       System.out.println("===================");
  }
}

流程实例挂起/激活

场景:例如公司制度改变过程中的流程, 总经理更换过程中的流程,有100个人的流程, 70个人

已经完成,30个人流程正好在总经理更换中,就需要挂起.就是暂停

  • 【C节点】的业务逻辑需要和外部接口交互,刚好外部接口出问题了,如果剩下的流程都走

到【C节点】,执行【C节点】的业务逻辑,那都会报错,我们就可以把流程挂起,等待外

部接口可用之后再重新激活流程.

  • 业务流程发生改变,已经发起的流程实例继续按照旧的流程走,如果新发起的流程就按照新的业务流程走.这时候我们就需要挂起流程定义,但是不挂起流程实例.。

    1.操作流程定义为挂起状态,该操作定义下面的所有的流程实例将全部暂停。

    2.流程定义为挂起状态,该流程定义下将不允许启动新的流程实例,同时该流程定义下

    的所有流程实例将全部挂起暂停执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test
public void testSuspendAllProcessInstance(){
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取RepositoryService
   RepositoryService repositoryService =
processEngine.getRepositoryService();
   //获取流程定义对象
   ProcessDefinition processDefinition = repositoryService
          .createProcessDefinitionQuery()
          .processDefinitionKey("leaveProcess")
          .singleResult();
   boolean suspended = processDefinition.isSuspended();
   //输出流程定义状态
   System.out.println("流程定义状态:"+(suspended ?"已挂起":"已激活"));
   String processDefinitionId = processDefinition.getId();
   if(suspended){
       //如果是挂起,可以执行激活操作 ,参数1 :流程定义id ,参数2:是否激活流程
实例,参数3:激活时间
     
repositoryService.activateProcessDefinitionById(processDefinitionId,
true,null);
       System.out.println("流程ID:"+processDefinitionId+",已激活");
  }else{
       //如果是激活,可以执行挂起操作 ,参数1 :流程定义id ,参数2:是否暂停流程
实例,参数3:激活时间
     
repositoryService.suspendProcessDefinitionById(processDefinitionId,t
rue,null);
       System.out.println("流程ID:"+processDefinitionId+",已挂起");
  }
}

单个流程实例挂起场景

评分流程:可设置多级评分,评分流程会按照从上往下的顺序,依次评分;评分人必须在

评分截至时间内完成评分,否则不允许继续评分,流程将会挂起,停止流转;

  • 操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再执

    行,完成该流程实例的当前任务将报异常。

查询所有的流程实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testQueryProcessInstance(){
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取RepositoryService
   RuntimeService runtimeService =
processEngine.getRuntimeService();
   List<ProcessInstance> processInstanceList = runtimeService
          .createProcessInstanceQuery()
          .processDefinitionKey("leaveProcess")
          .list();
   for(ProcessInstance processInstance:processInstanceList){
       System.out.println("流程实例Id:"+processInstance.getId()+",状
态:"+(processInstance.isSuspended()?"已挂起":"已激活"));
  }
}

挂起某个流程实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void testSuspendSingleProcessInstance(){
   //流程实例Id
   String processInstanceId = "2501";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取RepositoryService
   RuntimeService runtimeService =
processEngine.getRuntimeService();
   //根据流程实例Id获取流程实例对象
   ProcessInstance processInstance = runtimeService
          .createProcessInstanceQuery()
          .processInstanceId(processInstanceId)
          .singleResult();
   //状态
   boolean suspended = processInstance.isSuspended();
   System.out.println("流程实例ID:"+processInstanceId+",状态:"+
(suspended?"已挂起":"已激活"));
   if(suspended){
     
runtimeService.activateProcessInstanceById(processInstanceId);
       System.out.println("流程实例ID:"+processInstanceId+",状态修改为已
激活");
  }else{
       runtimeService.suspendProcessInstanceById(processInstanceId);
       System.out.println("流程实例ID:"+processInstanceId+",状态修改为已
挂起");
  }
}

任务分配负责人

  • 固定分配

在进行业务流程建模的时候指定固定的任务负责人

Assignee 张三

  • UEL表达式分配

Activiti 使用 UEL 表达式, UEL 是 java EE6 规范的一部分, UEL(Unified Expression

Language)即 统一表达式语言。

Assignee:${assignee0}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testStartProcess(){
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取RuntimeService对象
   RuntimeService runtimeService =
processEngine.getRuntimeService();
   Map<String,Object> variables  = new HashMap<String, Object>();
   variables.put("assignee0","zhangsan");
   variables.put("assignee1","lisi");
   //根据流程定义的key启动流程实例,这个key是在定义bpmn的时候设置的
   ProcessInstance instance = runtimeService
          .startProcessInstanceByKey("leaveProcess",variables);
   //获取流程实例的相关信息
   System.out.println("流程定义的id = " +
instance.getProcessDefinitionId());
   System.out.println("流程实例的id = " + instance.getId());
}

动态的设置assignee的值

  • 监听器分配

任务监听器是发生对应的任务相关事件时执行自定义的Java逻辑或表达式

1
2
3
4
5
6
7
8
9
Event:

Create:任务创建后触发。

Assignment:任务分配后触发。

Delete:任务完成后触发。

All:所有事件发生都触发。

自定义一个任务监听器类,然后此类必须实现org.activiti.engine.delegate.TaskListener接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.wolfcode;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
/**
* Created by wolfcode
*/
public class AssigneeTaskListener implements TaskListener {
   public void notify(DelegateTask delegateTask) {
       if(delegateTask.getName().equals("部门经理审批")){
           delegateTask.setAssignee("赵六");
      }else if(delegateTask.getName().equals("部门经理审批")){
           delegateTask.setAssignee("孙七");
      }
  }
}

然后去配置监听器

QQ_1722524110104

在实际开发中,一般也不使用监听器分配方式,太麻烦了。

流程变量

定义

流程变量在Activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和Activiti结合时少不了流程变量,流程变量就是Activiti在管理工作流时根据管理需要而设置的变量。

比如在请假流程流转时如果请假天数>3天则有总经理审批,否则由人事直接审批,请假天数就可以设置流程变量,在流程流转时使用。

流程变量的使用方法

  • 在属性上使用UEL表达式

可以在 assignee 处设置 UEL 表达式,表达式的值为任务的负责人,比如:${assignee}, assignee 就是一个流程变量名称。

Activiti获取UEL表达式的值,即流程变量assignee的值 ,将assignee的值作为任务的负责人进行任务分配

  • 在连线上使用UEL表达式

可以在连线上设置UEL表达式,决定流程走向。

比如:${price<10000} 。price就是一个流程变量名称,uel表达式结果类型为布尔类型。如果UEL表达式是true,要决定 流程执行走向。

QQ_1722527474822

任务候选人

在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定

设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性

差。

针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。

QQ_1722527730519

部署启动流程

1
2
3
4
5
6
7
8
9
        //进行部署
       Deployment deployment = repositoryService.createDeployment()
              .addClasspathResource("bpmn/leave-candidate.bpmn")
              .name("请假流程-候选人")
              .deploy();
       //输出部署的一些信息
       System.out.println("流程部署ID:"+deployment.getId());
       System.out.println("流程部署名称:"+deployment.getName());
  }

查询候选人任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//查询候选任务
@Test
public void testSelectCandidateTaskList(){
   //任务负责人
   String candidateUser = "李四";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取TaskService
   TaskService taskService = processEngine.getTaskService();
   //获取任务集合
   List<Task> taskList = taskService.createTaskQuery()
          .processDefinitionKey("leaveCandidateProcess")
          .taskCandidateUser(candidateUser)
          .list();
   //遍历任务列表
   for(Task task:taskList){
       System.out.println("流程定义id = " +
task.getProcessDefinitionId());
       System.out.println("流程实例id = " +
task.getProcessInstanceId());
       System.out.println("任务id = " + task.getId());
       System.out.println("任务名称 = " + task.getName());
  }
}

领取候选人任务

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testClaimTask(){
   //任务ID
   String taskId = "2505";
   String assignee = "张三";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取TaskService
   TaskService taskService = processEngine.getTaskService();
   //领取任务
   taskService.claim(taskId,assignee);
}

完成任务

如果候选任务没有进行领取就直接完成的话,那么在历史记录中就不会记录是哪个用户执

行了这个任务.

所以对于这种候选人的任务,我们需要先领取再完成.

1
2
3
4
5
6
7
8
9
10
11
12
//执行任务
@Test
public void testCompleteTask(){
   //任务ID
   String taskId = "2505";
   //创建ProcessEngine对象
   ProcessEngine processEngine =
ProcessEngines.getDefaultProcessEngine();
   //获取TaskService
   TaskService taskService = processEngine.getTaskService();
   taskService.complete(taskId);
}

网关

排他网关

排他网关(ExclusiveGateway)(异或网关或基于数据的排他网关),用来在流程中实现决策。当流程执行到这个网关的时候,所有分支都会判断条件是否为true,如果为true则执行该分支。

1
2
3
注意:
排他网关只会选择一个为true的分支执行(即使有两个分支条件都为true,排他网关也只会选
择一条分支去执行,现在序号小的路径执行)。

QQ_1722530250214

并行网关

并行网关(ParallelGateway)允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出的顺序流的。

  • 并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略

QQ_1722530307943

包含网关

包含网关(InclusiveGateway)可以看做是排他网关和并行网关的结合体。

需求:出差申请大于3天需要由项目经理审批,小于3等于天由技术经理审批,出差申请必须经过人事助理审批。

QQ_1722530368112

华信Activity开发

  1. 掌握Activity流程开发技术
  2. 掌握概念:工作流引擎、流程实例、流程任务、流程变量
  3. 流程图设计工具,Eclipse Activiti插件,或者IDEA插件 Activiti BPMN visualizer
  4. 流程使用图示

image-20240802090834530

​ 5.Activity常用表

​ act_ge_bytearray 存放流程定义文件

​ act_ru_task 流程任务运行表

​ act_hi_actinst 流程任务历史表

流程设计

使用流程图工具绘制流程图,保存.bpmn文件

整体流程图如下:

QQ_1722561970653

设置流程信息

studentExample

设置节点信息

QQ_1722566694075

解释:

Documentation配置用于记录节点相关信息和参数

1
2
3
4
Documentation: {
"NODE_NAME": "起草人编制",
"USER_STRATEGY_CLASS": "com.hii.acws.sample.activiti.service.InitiatorUserStrategy"
}
  • NODE_NAME:用于记录节点名称(路由名称)
  • USER_STRATEGY_CLASS:指定这个节点的用户策略类(路由对应策略,哪儿些人可以审批)

这个字段指向一个Java类,该类实现了特定的用户策略,用于确定在这个节点上该由谁来处理任务

Assignee:${assignee}流程变量:处理人

​ 用于动态的指定任务的受理人:流程运行时,根据流程变量 assignee 的值,动态确定该任务的办理人。例如,assignee 变量的值为 manager123,那么该任务会分配给ID为 manager123 的用户。

Form key: /example/studentprocessedit

​ 用于指定任务关联的表单页面的url:用户接到任务后,系统会引导其到路径为 /example/leaveApproval 的表单页面,在该页面上可以填写和提交请假审批的相关信息。

设置网关和路由信息

QQ_1722580043717

其他的ID名称什么的,随便设置,流程来源和目标什么的,线连起来就会自动有的

order1和order2是设置的路由排序

节点配置信息详解

1
2
3
4
5
6
7
8
9
10
/**
* @description:自由流人员选择策略
* taskId 任务ID,非必选
* processDefKey 流程定义key,非必选
* appId 业务ID,非必选
* transitionModel 输入路由信息,必选
* baseModel 任务信息,必选
* @author: fangyuan
* @date: 2024/8/2 15:50
**/

起草人节点

1
2
3
4
Documentation: {
"NODE_NAME": "起草人编制",
"USER_STRATEGY_CLASS": "com.hii.acws.sample.activiti.service.InitiatorUserStrategy"
}

这个类就是获取流程起草人的策略类,继承UserStrategy(自由流人员选择策略类)

实现获取人员列表List方法

QQ_1722615065918

获取起草人列表的逻辑就是:

  1. 查询任务
  2. 查询该任务下的实例列表
  3. 用实例列表的Assignee字段去用户表中查询当前用户

QQ_1722615859528

审批人节点:

1
2
3
4
5
Documentation:{
"NODE_NAME":"部门经理审批",
"USER_STRATEGY_CLASS":
"com.huaxin.acws.bpm.service.impl.RoleAssigneesStrategyServiceImpl",
"ROLE_IDS":"10001"}

因为这个类是封装在别的jar包中的,我们要将这个对象交给spring容器管理

1
2
3
4
@Bean(name ="com.huaxin.acws.bpm.service.impl.RoleAssigneesStrategyServiceImpl")
public RoleAssigneesStrategyServiceImpl roleAssigneesStrategyServiceImpl(){
return new RoleAssigneesStrategyServiceImpl();
}

获取审批人节点的逻辑就是:

  1. 在任务信息中获取我们documentation中封装的ROLE_IDS字段10001对应的角色
  2. 将roleIds转为角色ID数组
  3. 遍历数组,对每一个角色ID,创建查询对象,设置查询限制,将查到的用户添加到用户列表返回
  4. (这谁写的封装的代码,真的臭!!!设置这么多额外多余的变量干什么?)

QQ_1722617167631

后端代码

后端在serviceimpl实现层去实现BpmTransitionFilter接口

QQ_1722582001718

去实现两个方法,分别是判断路由信息是否需要过滤,和过滤路由信息

QQ_1722582242660

同时设置两个监听器,分别在流程启动和流程结束的时候调用方法

分别去监听两个事件

当事件被发布时,Spring会遍历所有使用 @EventListener 注解的方法,并检查它们是否监听该事件类型。

QQ_1722583595488

QQ_1722584229516

两个方法的主要作用还是去设置当前对象的工作状态,保存到数据库中

然后发送待阅通知

Activity流程很多都是自动实现的,后端写的东西很少,基本上就是配置流程图,配置好获取起草人的类 ,审批的类,过滤网关就行,其实写不写监听器问题都不大,也就是修改实习生流程中的一个字段,写过滤网管的代码也没必要,直接流程图不画那条线就行了呗,写过滤只是为了让我们熟悉还是有过滤这个功能的

前端代码

流程图后端基本上都是Activity自动部署的,页面显示主要还是看前端

前端的话,主要是写了两个vue页面,一个ProcessEdit流程编辑页面,一个ProcessApprove审批页面,在这两个页面中,引入了已有的自定义组件

这里记一下引入自定义组件的使用和传值的一些逻辑吧,vue还不是很了解

导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { getStudent, saveStudent } from "@/api/example";//引入方法
import ProcessBox from "@/components/Process/ProcessBox.vue";//引入组件

//export default 相当于导出当前vue组件,在其它引入当前组件时可以使用当前组件中的方法和变量。
export default {
//用于注册局部组件,使得在这个组件中使用
components: { Sticky, FileUpload, FileList, ProcessBox,ProcessSuggest },
//可以通过 this 访问 data 中定义的属性
//data 选项用于定义组件的响应式数据对象
//Vue 会监视这些数据对象中的属性,当它们发生变化时,Vue 会自动更新 DOM
data() {
return {
activeNames["baseInfo","detailInfo","filelistInfo","processSuggest"],
student: {
new:false,
id: "",
sex: "1"
},
}

传值

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
我是子组件:
<input type="text" :value="msg" @input="changeValFn" />
</div>
</template>

<script>
export default {
name: 'child',
props: ['msg'],
methods: {
changeValFn(e) {
this.$emit('changeMsg', e.target.value)
},
},
}
</script>

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="parent">
<h1>我是父组件:{{ msg }}</h1>
<child :msg="msg" @changeMsg="changeMsgFn"></child>
</div>
</template>

<script>
import child from './child'
export default {
name: 'parent',
components: {
child,
},
data() {
return {
msg: 'hello!',
}
},
methods: {
changeMsgFn(value) {
this.msg = value
},
},
}
</script>

父组件向子组件传值props

1
2
3
4
5
6
//子组件里用props属性去接受父组件的值
props: {
afterProcess: { type: Function, default: function() {} }
},
//指定afterProcess的类型必须是一个函数,如果没有传值,默认是空函数,什么都不执行

QQ_1722819399722

子组件向父组件传值,emit自定义事件

在子组件中,定义一个传值click事件,

this.$emit(父组件事件名称,传递数据)

QQ_1722821170000

QQ_1722821284040

自定义流程组件

  • FlowChart.vue流程图组件

59ef4d1f90dca05b65de8184b528acc6

  • ProcessBox.vue流程审批组件

cc3eeb564f9ff136c4335c757820b2e7

  • ProcessMonitor.vue流程监视组件

59ef4d1f90dca05b65de8184b528acc6

  • ProcessSuggest.vue审批意见组件

QQ_1722826843400

ProcessEdit页面

e8b19781ce16c45131f0a6cf1b428bc0

按照按钮来说功能

1
2
3
4
5
<el-button type="primary" @click="saveData" :disabled="!canOperate">保存</el-button>
<el-button type="primary" @click="saveData('submit')" :disabled="!canOperate">提交</el-button>
<el-button type="primary" @click="withdrawTask" :disabled="!canWithdraw">撤回</el-button>
<el-button type="primary" @click="processMonitor" :disabled="!instanceId">流程监控</el-button>
<el-button type="primary" @click="closeMe" v-if="this.$parent.hideMenu">关闭</el-button>

保存&提交:根据是否带参数判断保存还是提交

部分代码已注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
saveData(oper) {
this.$refs["student"].validate(valid => {
if (valid) {
saveStudent(this.student).then(response => {
this.student.id = response.id;
this.student.new = false;
if (oper == "submit") {
//校验附件是否上传
if (this.$refs["ZW"].fileList.length == 0) {
this.$message({
message: "请上传附件!",
type: "warning"
});
return;
}
this.submitProcess();
} else {
this.$notify({
title: "成功",
message: "修改成功",
type: "success"
});
}
});
}
});
}
//提交流程
submitProcess() {
var jso = {};
jso.processDefKey = "studentExample"; // 流程标识符
jso.instanceName = "示例流程";
jso.taskId = this.taskId;
jso.method = "submit"; //submit和audit
jso.businessData = { appId: this.student.id }; //业务数据
// 初始化审批插件
//$refs是一个特殊的属性,用于访问已注册的子组件实例或DOM元素。这里,它正在访问名为"processbox"的子组件实例或DOM元素,调用Processbox的initialData方法初始化数据
this.$refs["processbox"].initialData(jso);
this.processBoxVisible = true;
}

撤回:API请求在后台撤回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    //撤回任务
withdrawTask() {
withdraw(this.taskId).then(response => {
this.canWithdraw = false;
this.$notify({
title: "成功",
message: "撤回成功!",
type: "success",
offset: 70,
duration: 2000
});
});
}

//撤回API
/**
* 撤回任务
* @param {*} data
*/
export function withdraw(taskId) {
return request({
url: '/rest/tasks/' + taskId + '/actions/withdraw',
method: 'post',
data: {}
})
}

流程监控:跳转到流程监控页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   //流程监控,传递了一个名为instanceId实例的查询参数
//你可以在该路由的组件中访问this.$route.query.instanceId来获取传递的值。
processMonitor() {
this.$router.push({
name: "ProcessMonitor",
query: {
instanceId : this.instanceId
}
});
}

//路由router配置
{
//流程监控
path:'processmonitor',
component: () => import('@/components/Process/ProcessMonitor'),
name: 'ProcessMonitor',
meta: {title: '流程监控' , icon: 'ProcessMonitor'}
}

//在子组件中用this.$route.query.instanceId来获取传递的值
data() {
return {
instanceId: this.$route.query.instanceId,
//表格数据
dataList: [],
};
},
//在子组件中传入流程实例ID给流程图组件
<flow-chart :instanceId= "instanceId"></flow-chart>
//流程图组件中使用 props:{instanceId: ''}接受
//再根据instanceId查询到流程实例,显示在页面上

关闭

1
2
//只有在其父组件hideMenu为真时候,才显示       
<el-button type="primary" @click="closeMe" v-if="this.$parent.hideMenu">关闭</el-button>

ProcessApprove审批页面

QQ_1722826958921