本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、工作流
概述
-
借助计算机实现流程的自动化控制
- 比如请假审批、报销审批等
-
传统方式下,一旦流程发生改变,需要修改大量的代码
-
工作流引擎,业务流程发生改变,代码基本上不需要有什么改变
-
工作流能够将业务和流程分离,工作流引擎主要负责流程的流转,使用bpmn文件定义流程
Activiti7
Activiti 是一个工作流引擎, activiti 可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言(BPMN2.0)进行定义,业务系统按照预先定义的流程进行执行,实现了业务系统的业务流程由 activiti 进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
简单说,就是Activiti是一个工作流引擎,使用它能够给我们带来健壮性、减少开销等。用它就对啦!
二、搭建环境
-
创建数据库,用于存放 activiti 启动后生成的表
-
创建项目,添加依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.regex</groupId> <artifactId>day1108_activiti-demo</artifactId> <version>1.0.0</version> <properties> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.12</log4j.version> <activiti.version>7.0.0.SR1</activiti.version> </properties> <dependencies> <!-- 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 转换 --> <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> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</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>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <!--数据库连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> </dependencies> </project> -
定义配置文件(activiti.cfg.xml)
<?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="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> <!-- 配置独立的流程引擎 --> <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <property name="dataSource" ref="dataSource"/> <property name="databaseSchemaUpdate" value="true"/> </bean> </beans> -
日志配置文件(log4j.properties)
# Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal log4j.rootCategory=debug, CONSOLE, LOGFILE # Set the enterprise logger category to FATAL and its only appender to CONSOLE. log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=./activiti.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n -
创建一个测试类,调用activiti的工具类,生成acitivti需要的数据库表(总共25张)
public class TestInit { @Test public void testInit(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); System.out.println(processEngine); } }
三、业务建模
-
安装插件(actiBPM)
-
在Plugins中搜索actiBPM,然后点击Search in repositories,若搜索不到,则需要本地安装
-
从IDEA官网下载actiBPM.jar包,下载地址:plugins.jetbrains.com/plugin/7429…
-
-
绘制业务流程图
四、初阶
-
部署流程定义:使用RepositoryService将文件部署到activiti
-
启动流程实例:RuntimeService
-
查询待办任务:TaskService
- 对于查询xxxService.createXXXQuery()
- list()返回集合,singleResult()返回单个对象
-
完成待办任务:TaskService
taksService.addComment(taskId,processInstanceId); -
添加批注:TaskService
taksService.addComment(taskId,processInstanceId); -
查询历史任务:HistoryService
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery() .activityType("userTask")//过滤任务类型 .finished()//只查询完成任务 .list(); -
查询批注信息TaskService
List<Comment> list = taskService.getComment(taskId);
五、进阶
-
查询流程定义:RepositoryService
-
删除流程定义
repositoryService.deleteDeployment(deploymentId);//如果存在正在运行流程实例,删除抛异常 repositoryService.deleteDeployment(deploymentId,true);//删除流程定义的同时,把正在运行的流程实例也一并删除(级联删除) -
资源的下载(了解)
-
查询流程定义
-
根据流程定义获取资源名称
-
获取输入流
repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName()); -
把文件输入到指定位置
-
-
流程定义和流程实例的关系(类比类和对象的关系)
-
业务Key(BusinessKey):在activities中,业务和流程是分离的,可以通过BusinessKey进行绑定
-
流程定义挂起和激活(应用场景)
- 流程定义挂起之后,不能发起新的流程实例
-
流程实例挂起和激活
- 流程实例挂起后,任务不能往后执行,但不会影响发起新的流程实例
-
任务分配人
- 方式一:固定分配方式(硬编码,不使用)
- 方式二:UEL表达式:在bpmn图中使用${变量名}占位符,在启动流程的时候,将变量传入
- 方式三:监听器(了解):可以监听节点创建的时机,给任务设置负责人
-
任务候选人
- 一个任务可以设置多个候选人,多个候选人都可以看到这个任务
- 候选人需要将任务认领为个人任务,才能将流程继续
-
流程变量:流程变量即存在于流程中的变量,是Activiti用于管理工作流所需要的变量,作为业务系统和Activiti之间的桥梁
-
在节点中设置负责人,使用UEL表达式定义占位符
-
在连线上设置UEL表达式,控制流程走向
-
异常
- 没给传入流程变量,报错
- 所有连线的条件都不满足,报错
- 所有条件都满足,所有连线都会走(导致每个分支都走一遍)
-
网关
-
排他网关
- 只会走分支中的条件为true那条分支
- 如果有多个条件都为true,只会走连线编号最小的那个路径
-
并行网关
- 同时有多个任务并行执行,需要这些任务都同时做完,才能走到下一个节点
-
包含网关
- 排他网关+并行网关
-
-
六、相关API
| Service接口 | 说明 |
|---|---|
| RepositoryService | Activiti的资源管理接口 |
| RuntimeService | Activiti的流程运行管理接口 |
| TaskService | Activiti的任务管理接口 |
| HistoryService | Activiti的历史管理接口 |
| ManagementService | Activiti的引擎管理接口 |
获取service对象:
// 获取流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取资源管理对象
RepositoryService repositoryService = processEngine.getRepositoryService();
// 获取流程运行管理对象
RuntimeService runtimeService = processEngine.getRuntimeService();
// 获取任务管理对象
TaskService taskService = processEngine.getTaskService();
// 获取历史管理对象
HistoryService historyService = processEngine.getHistoryService();
// 获取引擎管理对象
ManagementService managementService = processEngine.getManagementService();
RepositoryService
| API | 说明 |
|---|---|
| createDeployment() | 创建部署对象 |
| createDeploymentQuery() | 创建部署查询 |
| createProcessDefinitionQuery() | 创建流程定义查询 |
| deleteDeployment("部署ID") | 根据部署ID查询部署 |
| addClasspathResource("资源路径") | 添加资源(resources目录下获取) |
| name("名称") | 部署名称 |
| deploy() | 部署到Activiti引擎 |
| getResourceAsStream("部署ID", "文件名称") | 获取部署的bpmn文件输入流 |
| processDefinitionKey("流程定义Key") | 指定流程定义的Key |
| orderByProcessDefinitionVersion() | 根据流程定义对象版本进行排序,必须与desc()或asc()一起使用 |
| Deployment | |
| getId() | 获取流程部署ID |
| getName() | 获取流程部署名称 |
| ProcessDefinition | |
| getId() | 获取流程定义ID |
| getName() | 获取流程定义名称 |
| getKey() | 获取流程定义的Key |
| getVersion() | 获取流程定义的版本 |
| getDeploymentId() | 获取部署ID |
| getResourceName() | 获取部署的bpmn文件的名称 |
| getDiagramResourceName() | 获取部署的png文件的名称 |
@Test
public void testDeploy() {
// 创建流程引擎对象,类似于 Shiro 中的SecurityManagement
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取资源管理对象
RepositoryService repositoryService = processEngine.getRepositoryService();
// 进行部署
Deployment deployment = repositoryService.createDeployment() // 创建部署
.addClasspathResource("leaveProcess.bpmn") // 添加资源
.addClasspathResource("leaveProcess.png")
.name("报销流程")
.deploy();
System.err.println(deployment.getId()); // 流程部署ID:2501
System.err.println(deployment.getName()); // 流程部署名称:报销流程
}
@Test
public void testDownloadResource() throws Exception {
// 获取流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取资源管理对象
RepositoryService repositoryService = processEngine.getRepositoryService();
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery() // 创建流程定义查询对象
.orderByProcessDefinitionVersion() // 根据流程定义对象版本进行排序
.desc()
.list();
ProcessDefinition processDefinition = list.get(0); // 获取最新的一个
String deploymentId = processDefinition.getDeploymentId(); // 获取部署ID
// 获取leaveProcess.bpmn输入流
InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
// 获取图片(leaveProcess.png)输入流
InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
// 设置leaveProcess.bpmn输出流
FileOutputStream bpmnOutput = new FileOutputStream("F:\regex\test\leaveProcess.bpmn");
// 设置图片(leaveProcess.png)输出流
FileOutputStream pngOutput = new FileOutputStream("F:\regex\test\leaveProcess.png");
IOUtils.copy(bpmnInput, bpmnOutput);
IOUtils.copy(pngInput, pngOutput);
}
RuntimeService
| API | 说明 |
|---|---|
| startProcessInstanceByKey("key值", "map") | 根据指定的key创建一个最新版本的流程实例(map可有可无) |
| ProcessInstance | |
| getBusinessKey() | 获取业务标识 |
| getDeploymentId() | 获取部署ID |
| getProcessDefinitionId() | 获取流程定义ID |
| getId() | 获取流程实例ID |
| getName() | 获取流程名称 |
@Test
public void testStartProcess() {
// 获取流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取运行管理对象
RuntimeService runtimeService = processEngine.getRuntimeService();
// 根据流程定义的key启动流程实例,该key为定义bpmn时设置的
ProcessInstance leaveProcess = runtimeService.startProcessInstanceByKey("leaveProcess");
System.err.println("获取BusinessKey:" + leaveProcess.getBusinessKey());
System.err.println("获取部署ID:" + leaveProcess.getDeploymentId());
System.err.println("获取流程定义ID:" + leaveProcess.getProcessDefinitionId()); // leaveProcess:1:2504
System.err.println("获取流程实例ID:" + leaveProcess.getId()); // 5001
System.err.println("获取流程名称:" + leaveProcess.getName());
}
TaskService
| API | 说明 |
|---|---|
| createTaskQuery() | 创建任务查询 |
| processDefinitionKey("流程定义Key") | 根据指定的流程定义key查询任务集合 |
| taskAssignee("任务负责人") | 根据指定的任务负责人名称查询任务集合 |
| list() | 获取任务集合 |
| singleResult() | 获取单个任务 |
| complete("任务ID") | 通过任务ID将指定的任务标记为完成 |
| addComment("任务ID", "流程实例ID", "批注信息") | 在任务完成之前给任务添加批注信息 |
| getTaskComments("任务ID") | 通过任务ID获取任务的评论 |
| claim("任务ID", "用户ID") | 认领任务(用户ID即assignee) |
| Task | |
| getId() | 获取任务ID |
| getProcessInstanceId() | 获取流程实例ID |
| getName() | 获取任务名称 |
| Comment | |
| getFullMessage() | 获取评论的完整信息 |
@Test
public void testAddComment() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
String assignee = "李四";
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("leaveProcess")
.taskAssignee(assignee)
.list();
for (Task task : taskList) {
System.err.println(task);
// 任务ID,流程实例ID
taskService.addComment(task.getId(), task.getProcessInstanceId(), task.getName() + "再日回来");
taskService.complete(task.getId());
}
}
HistoryService
| API | 说明 |
|---|---|
| createHistoricActivityInstanceQuery() | 创建历史活动实例查询 |
| activityType("活动类型") | 根据指定的活动类型查询历史活动实例(比如userTask,act_hi_actinst表) |
| processInstanceId("流程实例ID") | 根据指定的流程实例ID查询历史活动实例 |
| taskAssignee("任务负责人") | 根据指定的任务负责人查询历史活动实例 |
| finished() | 查询已完成的历史活动实例 |
| list() | 获取历史活动实例集合 |
| HistoricActivityInstance | |
| getStartTime() | 获取活动实例开始时间 |
| getEndTime() | 获取活动实例结束时间 |
| getDurationInMillis() | 获取活动实例耗时 |
| getTaskId() | 获取任务ID |
@Test
public void testSelectHistoryTask() {
// 流程实例ID
String processInstanceId = "12501";
// 任务负责人
String assignee = "李四";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
HistoryService historyService = processEngine.getHistoryService();
TaskService taskService = processEngine.getTaskService();
// 通过指定的流程实例ID和任务负责人获取已完成的所有用户任务
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.activityType("userTask")
.processInstanceId(processInstanceId)
.taskAssignee(assignee)
.finished() // 查询已完成
.list();
for (HistoricActivityInstance instance : list) {
System.err.println("list:" + list.size());
System.err.println(instance);
System.err.println("开始时间:" + instance.getStartTime());
System.err.println("结束时间:" + instance.getEndTime());
System.err.println("任务耗时:" + instance.getDurationInMillis());
System.err.println(instance.getTaskId());
// 根据任务ID获取任务的所有评论信息
List<Comment> taskComments = taskService.getTaskComments(instance.getTaskId());
System.out.println("taskComment:" + taskComments.size());
for (Comment taskComment : taskComments) {
// 获取评论的完整信息
String message = taskComment.getFullMessage();
System.err.println(message);
}
}
}