概念
工作流(Work flow)
是指是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。
示例(请假申请):
创造假期申请——》部门主管审批——》经理审批——》财务审批——》流程结束.
还有订单、报价处理、合同审核、供应链管理等众多适用场景。
BPMN2.0规范
BPMN(Business Process Model Notation——业务流程建模符号)是由BPMI(The Business Process Management Initiative)开发的一套标准的业务流程建模符号。使用BPMN提供的符号可创建所需的业务流程。——百度百科
中文版链接
Activiti 7.0
简介
Activiti是领先的轻量级、以 Java 为中心的开源 BPMN 引擎,支持现实世界的流程自动化需求。即是一个工作流引擎, Activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由Activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
历史
Alfresco软件在2010年5月17日宣布Activiti业务流程管理(BPM)开源项目的正式启动,其首席架构师由业务流程管理BPM的专家 Tom Baeyens担任,Tom Baeyens就是原来jbpm的架构师,而jbpm是一个非常有名的工作流引擎。
流程使用步骤
- 搭建Activiti部署环境:引入相应jar包,简单配置后使用Activiti的API在数据库创建表结构。
- 流程定义:使用流程建模工具定义业务流程(存储文件后缀.bpmn)。
- 流程定义部署: 使用Activiti提供的API把流程定义内容存储到数据库相关表中,后续操作Activiti可直接查询业务流程。
- 启动一个流程实例:即一次业务流程的运行。
- 完成用户任务:完成一个任务节点。
- 流程结束:当任务办理完成后没有下一个任务结点时,该流程结束。
准备
下载actiBPM插件
用于流程定义,新版IDEA找不到可直接去官网插件下载。装成功后新建会有BPMN File选项。
数据库准备
Activiti运行需要有数据库的支持,使用25张表,把流程定义节点内容读取到数据库表中。支持的数据库有:h2, MySQL, oracle, postgres, mssql, db2。这里使用MySQL。
Activiti数据表介绍
表分类 |
表名 |
解释 |
一般数据 |
|
|
|
[ACT_GE_BYTEARRAY] |
通用的流程定义和流程资源 |
|
[ACT_GE_PROPERTY] |
系统相关属性 |
流程历史记录 |
|
|
|
[ACT_HI_ACTINST] |
流程实例执行的历史信息 (任务+事件(如:startEvent、endEvent)) |
|
[ACT_HI_ATTACHMENT] |
历史的流程附件 |
|
[ACT_HI_COMMENT] |
历史的说明性信息 |
|
[ACT_HI_DETAIL] |
历史的流程运行中的细节信息 |
|
[ACT_HI_IDENTITYLINK] |
历史的流程运行过程中用户关系 |
|
[ACT_HI_PROCINST] |
历史的流程实例 |
|
[ACT_HI_TASKINST] |
历史的流程任务实例 |
|
[ACT_HI_VARINST] |
历史的流程运行中的变量信息 |
流程定义表 |
|
|
|
[ACT_RE_DEPLOYMENT] |
部署单元信息 |
|
[ACT_RE_MODEL] |
模型信息 |
|
[ACT_RE_PROCDEF] |
已部署的流程定义 |
运行实例表 |
|
|
|
[ACT_RU_EVENT_SUBSCR] |
运行时事件 |
|
[ACT_RU_EXECUTION] |
运行时流程执行实例 |
|
[ACT_RU_IDENTITYLINK] |
运行时用户关系信息,存储任务节点与参与者的相关信息 (如:组管理中任务候选人信息) |
|
[ACT_RU_JOB] |
运行时作业 |
|
[ACT_RU_TASK] |
运行时任务 |
|
[ACT_RU_VARIABLE] |
运行时变量表 |
工作流程引擎(ProcessEngine)创建
创建数据库
1
| CREATE DATABASE activiti DEFAULT CHARACTER SET utf8;
|
引入maven依赖
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
| <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-engine</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency>
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>
|
创建流程引擎的配置类(ProcessEngineConfiguration)
可通过StandaloneProcessEngineConfiguration
(默认)或SpringProcessEngineConfiguration
与Spring整合两种方式创建ProcessEngineConfiguration
。通过ProcessEngineConfiguration
可以创建工作流引擎ProceccEngine
,
- 在resourse目录下创建activiti.cfg.xml配置文件
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
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <property name="databaseSchemaUpdate" value="true"/> <property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///activiti?serverTimezone=Asia/Shanghai"/> <property name="jdbcUsername" value="root"/> <property name="jdbcPassword" value="123456"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/activiti?serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> </beans>
|
- 创建流程引擎
1 2 3 4 5 6 7
| public class CreateTables { public static void main(String[] args) { ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); System.out.println(processEngine); } }
|
- maven依赖
1 2 3 4 5 6
| <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring</artifactId> <version>${activiti.version}</version> </dependency>
|
- 在资源目录下创建配置文件
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| <?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd "> <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> <property name="dataSource" ref="dataSource" /> <property name="transactionManager" ref="transactionManager" />
<property name="databaseSchemaUpdate" value="true" /> </bean> <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean"> <property name="processEngineConfiguration" ref="processEngineConfiguration" /> </bean> <bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" /> <bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" /> <bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" /> <bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" /> <bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" /> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/activiti?serverTimezone=Asia/Shanghai" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="maxActive" value="3" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.czm.service..*.*(..))" /> </aop:config> </beans>
|
- 创建流程引擎
1 2 3 4 5 6 7 8 9 10 11
| @Slf4j public class CreateTables { public static void main(String[] args) { ProcessEngineConfiguration configuration = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource ("MyActivitiConfig.xml","processEngineConfiguration"); ProcessEngine processEngine = configuration.buildProcessEngine(); } }
|
流程定义
- 创建bpmn文件,设置流程定义Key为vacation,指定任务代理人(审批人)assignee。
生成png流程图:
将bpmn文件后缀改为xml,右键选择Diagrams生成并导出png图片,再将原来文件后缀改回bpmn。
注:
Activiti支持使用EL表达式对每个任务的assignee进行设置,在开启流程实例时可通过Map集合将流程变量传入,存储在ACT_RU_VARIABLE表中。也可用监听器的形式设置任务负责人——下面介绍。
可能遇到问题:
- 中文乱码:在IDEA的bin目录下的idea64.exe.vmoptions和idea.exe.vmoptions文件丽添加配置
-Dfile.encoding=UTF-8
。
- 生产流程png文件时右键xml文件无Diagrams选项。再装个
JBoss jBPM
插件。
- 点击bpmn文件BPMN Editor未显示,修改idea主题为Light,再次点击即可显示。
Service服务接口
简介
Service是工作流引擎提供用于进行工作流部署、执行、管理的服务接口,我们使用这些接口可以就是操作服务对应的数据表
Service总览
service名称 |
service作用 |
RepositoryService |
Activiti的资源管理类,可管理和控制流程发布包和流程定义 |
RuntimeService |
Activiti的流程运行管理类,可获取很多关于流程执行相关的信息 |
TaskService |
Activiti的任务管理类,可获取任务的信息。 |
HistoryService |
Activiti的历史管理类,查询历史信息,比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实例的执行路径等。 |
ManagerService |
Activiti的引擎管理类,对 Activiti 流程引擎的管理和维护,主要用于 Activiti 系统的日常维护。 |
流程部署
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| @Slf4j public class TestBase { private ProcessEngine processEngine;
@Before public void getProcessEngine() { ProcessEngineConfiguration configuration = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource ("MyActivitiConfig.xml","processEngineConfiguration"); this.processEngine = configuration.buildProcessEngine(); log.info("执行引擎processEngine:{}", processEngine); }
@Test public void testDeployment(){ RepositoryService repositoryService = processEngine.getRepositoryService(); Deployment deployment = repositoryService.createDeployment() .addClasspathResource("bpmn/vacation.bpmn") .addClasspathResource("bpmn/vacation.png") .name("请假申请流程") .deploy(); log.info("流程部署id:" + deployment.getId()); log.info("流程部署名称:" + deployment.getName()); }
@Test public void deployProcessByZip() { InputStream inputStream = this .getClass() .getClassLoader() .getResourceAsStream( "bpmn/bpmn.zip"); ZipInputStream zipInputStream = new ZipInputStream(inputStream); RepositoryService repositoryService = processEngine .getRepositoryService(); Deployment deployment = repositoryService.createDeployment() .addZipInputStream(zipInputStream) .deploy(); log.info("流程部署id:" + deployment.getId()); log.info("流程部署名称:" + deployment.getName()); }
@Test public void deleteDeployment() { RepositoryService repositoryService = processEngine .getRepositoryService(); ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("vacation") .singleResult(); repositoryService.deleteDeployment(processDefinition.getDeploymentId()); } }
|
在ACT_RE_PROCDEF表中可看到压缩包里的两个流程
流程实例
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
|
@Test public void testStartProcess(){ RuntimeService runtimeService = processEngine.getRuntimeService(); ProcessInstance instance = runtimeService.startProcessInstanceByKey("vacation");
log.info("流程定义ID:{}",instance.getProcessDefinitionId()); log.info("流程实例ID:{}",instance.getId()); log.info("当前活动的ID:{}",instance.getActivityId()); }
@Test public void completTask(){ TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery() .processDefinitionKey("vacation") .taskAssignee("张三") .singleResult();
log.info("流程实例id={}",task.getProcessInstanceId()); log.info("任务Id={}",task.getId()); log.info("任务负责人={}",task.getAssignee()); log.info("任务名称={}",task.getName()); taskService.complete(task.getId()); }
@Test public void testFindPersonalTaskList(){ TaskService taskService = processEngine.getTaskService(); List<Task> taskList = taskService.createTaskQuery() .processDefinitionKey("vacation") .taskAssignee("张三") .list(); for (Task task : taskList) { log.info("流程实例id={}",task.getProcessInstanceId()); log.info("任务Id={}",task.getId()); log.info("任务负责人={}",task.getAssignee()); log.info("任务名称={}",task.getName()); } }
|
历史信息
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 findHistoryInfo(){ HistoryService historyService = processEngine.getHistoryService(); HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery(); instanceQuery.processDefinitionId(processEngine.getRepositoryService().createProcessDefinitionQuery().processDefinitionKey("vacation").singleResult().getId()); instanceQuery.orderByHistoricActivityInstanceStartTime().asc(); List<HistoricActivityInstance> activityInstanceList = instanceQuery.list(); for (HistoricActivityInstance hi : activityInstanceList) { log.info(hi.getActivityId()); log.info(hi.getActivityName()); log.info(hi.getProcessDefinitionId()); log.info(hi.getProcessInstanceId()); log.info("<==========================>"); } }
|
资源下载
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
|
@Test public void getDeployment() throws IOException { RepositoryService repositoryService = processEngine.getRepositoryService(); ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("vacation") .singleResult(); String deploymentId = processDefinition.getDeploymentId(); String pngName = processDefinition.getDiagramResourceName(); InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, pngName); String bpmnName = processDefinition.getResourceName(); InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, bpmnName); File pngFile = new File("d:/vacation.png"); File bpmnFile = new File("d:/vacation.bpmn"); FileOutputStream pngOutStream = new FileOutputStream(pngFile); FileOutputStream bpmnOutStream = new FileOutputStream(bpmnFile); IOUtils.copy(pngInput,pngOutStream); IOUtils.copy(bpmnInput,bpmnOutStream); pngOutStream.close(); bpmnOutStream.close(); pngInput.close(); bpmnInput.close(); }
|
流程负责人设置(三种方式)
固定方式:前面已有。
EL表达式
监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.czm.listener; import lombok.extern.slf4j.Slf4j; import org.activiti.engine.delegate.DelegateTask; import org.activiti.engine.delegate.TaskListener; import java.util.Objects;
@Slf4j public class TestAssigneeListener implements TaskListener {
@Override public void notify(DelegateTask delegateTask) { log.info("delegateTask:{}", Objects.toString(delegateTask)); if("testListener".equals(delegateTask.getName()) && "create".equals(delegateTask.getEventName())) { delegateTask.setAssignee("通过listener设置"); } } }
|
idea插件不知为啥设置不了,只能编辑文件配置监视器。
测试
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 setAssagnee(){ RepositoryService repositoryService = processEngine.getRepositoryService(); Deployment deployment = repositoryService.createDeployment() .addClasspathResource("bpmn/testListener.bpmn") .name("测试设置负责人") .deploy(); log.info("流程部署id:" + deployment.getId()); log.info("流程部署名称:" + deployment.getName()); RuntimeService runtimeService = processEngine.getRuntimeService(); Map<String, Object> variables = new HashMap<String, Object>(); variables.put("assigneeEL", "通过EL设置"); ProcessInstance instance = runtimeService.startProcessInstanceByKey("testListener", "businessKey", variables); log.info("流程定义ID:{}",instance.getProcessDefinitionId()); log.info("流程实例ID:{}",instance.getId()); log.info("当前活动的ID:{}",instance.getActivityId());
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery().processInstanceId(instance.getId()).list(); for (Task task : taskList) { log.info("流程实例id={}",task.getProcessInstanceId()); log.info("任务Id={}",task.getId()); log.info("任务负责人={}",task.getAssignee()); log.info("任务名称={}",task.getName()); taskService.complete(task.getId()); } }
|
网关
作用:用来控制流程走向。
分类
- 排他网关(
ExclusiveGateway
):只会选择条件为true的分支执行,若有多条true分支,选择id较小的执行。若都为false,则系统抛出异常。
- 并行网关(
ParallelGateway
):将流程分成多条分支,也可以把多条分支汇聚到一起,再执行下一任务。注:并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略。
- 事件网关(
EventGateway
):网关的每个外出顺序流都要连接到一个中间捕获事件。根据它所连接的中间Catching事件来决定流程的走向。
- 包含网关(
InclusiveGateway
):排他和并行网关的结和,只执行条件为true的分支(与排他的区别是可以执行多条分支)。多条true分支汇聚到一起,再会执行下一任务。
END