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)