1.什么是Activiti
在解释activiti之前我们看一下什么是工作流。
工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。
我的理解是,工作流将一套大的业务逻辑分解成业务逻辑段, 并统一控制这些业务逻辑段的执行条件,执行顺序以及相互通信。 实现业务逻辑的分解和解耦。
Activiti是一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调度。
BPMN即业务流程建模与标注(Business Process Model and Notation,BPMN) ,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram)。
BPMN的流程图长这样子
activiti5.13使用了23张表支持整个工作流框架,底层使用mybatis操作数据库。这些数据库表为
1)ACT_RE_*: 'RE'表示repository。 这个前缀的表包含了流程定义相关的静态资源(图片,规则等)。
2)ACT_RU_*: 'RU'表示runtime。 运行时表,包含流程实例,任务,变量,异步任务等运行中的数据。流程结束时这些记录会被删除。
3)ACT_ID_*: 'ID'表示identity。 这些表包含用户和组的信息。
4)ACT_HI_*: 'HI'表示history。 这些表包含历史数据,比如历史流程实例,变量,任务等。
5)ACT_GE_*: 通用数据,bytearray表保存文件等字节流对象。
工作流进行的基本过程如下:
定义流程(框架外) -> 部署流程定义 -> 启动流程实例, 框架移动到任务1 -> 拾取组任务 -> 办理个人任务, 框架移动到任务2 -> 拾取组任务 -> 办理个人任务...
组任务是多个用户都可以完成的任务。没有组任务直接办理个人任务; 有组任务需先通过拾取将组任务变成个人任务, 然后再办理。
个人任务/组任务在表中的区别
个人任务: 表act_ru_task的ASSIGNEE段即指定的办理人
组任务: 表act_ru_task的ASSIGNEE段为null, 相关信息在表act_ru_identitylink中, 组任务1见userid段; 组任务2见groupid段, 当然还需查询act_id_xxx表才能精确到人.
2.Activiti的使用
2.1 创建processEngine
processEngine控制着工作流整个流程
public class processEngine {
@Test
public void createProcessEngine1() {
String resource = "activiti-context.xml"; // 配置文件
String beanName = "processEngineConfiguration"; // 配置文件中bean name
// 从配置文件创建配置对象
ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(resource, beanName);
// 根据配置创建引擎对象
ProcessEngine processEngine = config.buildProcessEngine();
} /**
* 一条语句创建processEngine, 要求:
* 1、配置文件必须在classpath根目录下
* 2、配置文件名必须为activiti-context.xml或activiti.cfg.xml
* 3、工厂对象的id必须为processEngine
*/
@Test
public void createProcessEngine2() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 配置 -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///test_activiti"/>
<property name="jdbcUsername" value="root"/>
<property name="jdbcPassword" value="root"/>
<!-- 创建processEngine时, activiti自动创建23张表 -->
<property name="databaseSchemaUpdate" value="true"/>
</bean>
<!-- 使用配置创建引擎对象 -->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration"/>
</bean>
</beans>
当然, 可以与spring进一步整合, 使用spring方式获取processEngine. applicationContext.xml如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///activiti_day2" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 流程引擎配置对象 -->
<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>
</beans>
2.2 部署流程定义
流程是由用户通过bpmn等文件(底层xml)定义的, 即上面列举的的bpmn流程图
定义好的流程需要部署给activiti才能被其使用
/**
* 部署流程定义
* 一套定义文件只有一个流程定义Key, 但可以被部署多次形成多个版本(部署表里多个id和流程定义表里多个id)
* 涉及的表:act_re_deployment(部署表)、act_re_procdef(流程定义表)、act_ge_bytearray(二进制表)
*/
@Test
public void test() throws FileNotFoundException {
DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();
// 逐个文件部署
// deploymentBuilder.addClasspathResource("qjlc.bpmn");
// deploymentBuilder.addClasspathResource("qjlc.png");
// 压缩文件打包部署, 推荐
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(new File("d:\\processDef.zip")));
deploymentBuilder.addZipInputStream(zipInputStream ); Deployment deployment = deploymentBuilder.deploy();
}
2.3 启动流程实例
/**
* 启动一个流程实例
* 涉及的表:
* act_ru_execution(流程实例表), 管理流程进度
* act_ru_task(任务表), 进行到哪一个流程的哪一个任务, 该由谁完成
*/
@Test
public void test() throws Exception{
String processDefinitionKey = "qjlc";
//方式一:根据流程定义id启动流程实例
//String processDefinitionId = "qjlc:6:904";
//ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceById(processDefinitionId); //方式二:根据流程定义Key启动流程实例 推荐!流程定义有多个版本时会选择最新版本
ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey);
}
2.4 办理任务
/**
* 办理任务, 办理后框架自动移动到下一任务
* 涉及的表: act_ru_execution(流程实例表)、act_ru_task(任务表)
*/
@Test
public void test() throws Exception{
String taskId = "1304";
processEngine.getTaskService().complete(taskId);
}
2.5 其他操作
/**
* 查询流程定义
* 涉及的表:act_re_procdef
*/
@Test
public void test(){
ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
// 查询条件过滤
query.processDefinitionKey("qjlc");
query.orderByProcessDefinitionVersion().asc();
List<ProcessDefinition> list = query.listPage(0, 10);
for (ProcessDefinition processDefinition : list) {
System.out.println(processDefinition.getId());
}
}
activiti中查询的套路: processEngine.getXXXService().createXXXQuery().list()/singleResult()
processEngine.getRepositoryService().createDeploymentQuery().list(); // 查询部署
processEngine.getRuntimeService().createProcessInstanceQuery().list(); // 查询流程实例
processEngine.getTaskService().createTaskQuery().list(); // 查询个人任务
processEngine.getIdentityService().createUserQuery().list(); // 查询用户
processEngine.getHistoryService().createHistoricActivityInstanceQuery().list(); //查询历史
过滤条件
查询个人任务 query.taskAssignee()
查询组任务 query.taskCandidate()
几个javabean(和表对应):
Deployment------act_re_deployment
ProcessDefinition-----act_re_procdef
ProcessInstance------act_ru_execution
Task-----act_ru_task
几个Query对象:
DeploymentQuery------act_re_deployment
ProcessDefinitionQuery-----act_re_procdef
ProcessInstanceQuery------act_ru_execution
TaskQuery-----act_ru_task
几个Service:
RepositoryService----操作部署表、流程定义表等静态资源信息表
RuntimeService----操作流程实例表、任务表等动态信息表
TaskService-----操作任务表
HistoryService----操作历史表
IdentityService----操作用户表、组表、关系表
// 删除流程定义
@Test
public void test1(){
String deploymentId = "101"; //部署id
boolean cascade = false; // 级联删除, 设置为true的话, 有正在跑的流程实例及任务也会被删除
processEngine.getRepositoryService().deleteDeployment(deploymentId, cascade);
}
// 删除流程实例
@Test
public void test2() throws Exception{
String processInstanceId = "1201";
String deleteReason = "不请假了"; // 可以添加删除原因
processEngine.getRuntimeService().deleteProcessInstance(processInstanceId, deleteReason);
}
// 根据部署id, 获取定义文件
@Test
public void test3() throws Exception{
String deploymentId = "201"; //部署id
// 先获得定义文件的名字
List<String> names = processEngine.getRepositoryService().getDeploymentResourceNames(deploymentId);
for (String name : names) {
InputStream in = processEngine.getRepositoryService().getResourceAsStream(deploymentId, name);
FileUtils.copyInputStreamToFile(in, new File("d:\\"+name));
in.close();
}
}
// 根据流程定义id, 获取定义文件
@Test
public void test4() throws Exception{
String processDefinitionId = "qjlc:6:904"; //流程定义id
InputStream pngStream = processEngine.getRepositoryService().getProcessDiagram(processDefinitionId);
FileUtils.copyInputStreamToFile(pngStream, new File("d:\\abc.png"));
}
通过javabean能访问到某些需要的字段, 例如
processInstance.getActivityId() -> 当前执行的任务名
processDefinition.getDiagramResourceName() -> 定义文件中图片的名字
2.6 流程变量
多个任务间可以通过流程变量通信.
流程变量以key-value形式存放, 存于表 act_ru_variable. 在同一流程实例里, 不同方式设置变量, key相同时会覆盖
// 启动流程实例时 设置流程变量
@Test
public void test1() {
String processDefinitionKey = "bxlc";
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("key", "value");
ProcessInstance pi = processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey, variables);
}
// 办理任务时 设置流程变量, 更实用!
@Test
public void test2() {
String taskId = "206";
Map<String, Object> variables = new HashMap<>();
variables.put("key", "value");
processEngine.getTaskService().complete(taskId, variables);
}
// 通过RuntimeService 设置流程变量
@Test
public void test3() {
String executionId = "201"; // 流程实例id
Map<String, Object> variables = new HashMap<>();
variables.put("key", "value");
//processEngine.getRuntimeService().setVariable(executionId, variableName, value);
processEngine.getRuntimeService().setVariables(executionId, variables);
}
// 通过TaskService 设置流程变量
@Test
public void test4() {
String taskId = "304";
String key = "key";
Object value = "value";
processEngine.getTaskService().setVariable(taskId , key, value);
}
// 通过RuntimeService 获取流程变量
@Test
public void test5() {
String executionId = "201";
Object value = processEngine.getTaskService().getVariable(executionId, "user");
System.out.println(value);
}
// 通过TaskService 获取流程变量
@Test
public void test6() {
String taskId = "304";
Object value = processEngine.getTaskService().getVariable(taskId, "user");
System.out.println(value);
}
流程变量还可以通过在定义流程用表达式${}. 框架在该段任务执行前从act_ru_variable表里动态获取
另外, 启动流程实例还有一个重载函数, 除了流程变量variables还能指定业务主键businessKey
processEngine.getRuntimeService().startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
businessKey一般设置为业务表的主键值, 在使用activiti的时候, 通过查询业务表主键, 能方便地查询出业务的最新状态
2.7 组任务
组任务1
// 查询组任务
@Test
public void test1() {
TaskQuery query = processEngine.getTaskService().createTaskQuery();
// 使用候选人查询组任务
String candidateUser = "财务二";
query.taskCandidateUser(candidateUser);
List<Task> list = query.list();
for (Task task : list) {
System.out.println(task.getId());
}
}
// 拾取组任务
@Test
public void test2() {
String taskId = "1102";
processEngine.getTaskService().claim(taskId , "财务二");
}
// 办理组任务, 无需指定办理人
@Test
public void test3() throws Exception{
String taskId = "1102";
processEngine.getTaskService().complete(taskId);
}
组任务2
// activiti使用自己的用户与组的权限表, 因此需要设置. 但需注意要与框架外用户/组同步设置
@Test
public void test2() {
// 创建组
Group group = new GroupEntity();
group.setId("财务组");
processEngine.getIdentityService().saveGroup(group);
// 创建用户
User user = new UserEntity();
user.setId("2");
processEngine.getIdentityService().saveUser(user);
// 维护用户与组的关系
processEngine.getIdentityService().createMembership("2", "财务组");
}
// 查询组任务
@Test
public void test2() {
TaskQuery query = processEngine.getTaskService().createTaskQuery();
String candidateUser = "2";
// 使用候选人过滤
query.taskCandidateUser(candidateUser);
// 使用组过滤
//query.taskCandidateGroup("财务组");
List<Task> list = query.list();
for (Task task : list) {
System.out.println(task.getId());
}
}
// 拾取组任务
@Test
public void test3() {
String taskId = "1902";
processEngine.getTaskService().claim(taskId , "2");
}
// 办理组任务略
2.8 排他网关
设置分支条件
3. 一些使用经验
1)
考虑到工作流中的一个任务, 对应一个业务段, 可以将taskDefinitionKey设置成strus action类的method, 使之具有一定的通用性
2)
两种对流程定义的查询, 后者能获得更多定义的细节信息 processDefinitionEntity.findActivity(taskId) 工作流中某任务的信息
repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult()
(ProcessDefinitionEntity) repositoryService.getProcessDefinition(processDefinitionId)