分享一下手把手教你如何玩转Activiti工作流
还未完善
场景:学校
主角:阿毛 , 班主任 ,教务处处长
问题:有一天,阿毛到学校,感觉到身体不舒服,然后想跟班主任请假,然后班主任告诉阿毛说,你想请假,那么就必须要请假条,这个上面必须要我同意,然后再拿到教务处去盖章,然后交给我,这样才可以进行请假。。阿毛,想着,怎么请个假都这么麻烦,这么多层次处理问题,能不能简便一点。。。。好烦好烦~!!~~
分析:从上面的小例子,我们可以很明显的得到一个结论,就是:
请假流程:阿毛------》提交申请-----》班主任审批-----》教务处审批-----》请假成功
也就是说,这种问题就是一种流式的控制管理,当然,这是最简单的,因为里面没有包含着回馈,相当于只是一个方向。其实,到这里,Activiti的定义就已经出来了。。。。。
大纲:
之前做过一些项目没有用到工作流,都是以状态控制整个流程,现在系统化的将工作流分享一下
使用activiti-designer-5.18.0.zip
2.创建SpringBoot工程
①编辑POM文件
<!-- 继承SpringBoot官方指定的父工程 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.12.RELEASE</version> </parent> <dependencies> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.5</version> </dependency> <!-- MyBatis场景启动器 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <!-- Activiti场景启动器 --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>5.21.0</version> </dependency> <!-- 基本场景启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- SpringBoot测试支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies>
特别说明:请把mybatis-spring-boot-starter的依赖放在activiti-spring-boot-starter-basic的前面以避免jar包冲突问题。jar包冲突的表现是:java.lang.ClassNotFoundException: org.apache.ibatis.annotations.Mapper。
依赖顺序和依赖传递的关系: mybatis-spring-boot-starter依赖mybatis-3.4.0.jar,而activiti-spring-boot-starter-basic依赖mybatis-3.3.0.jar,对于这种情况,Maven以顺序靠前的为准。现在如果导入的是mybatis-3.3.0.jar就会抛出异常,而mybatis-3.4.0.jar是正确的,所以要让mybatis-spring-boot-starter的依赖放在activiti-spring-boot-starter-basic的前面。
②主启动类
@SpringBootApplication public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } }
③测试类
@RunWith(SpringRunner.class) @SpringBootTest public class ProcessTest { @Autowired private ProcessEngine processEngine; @Test public void createTable() { System.out.println(processEngine); } }
④yml配置
spring: datasource: name: mydb type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/db_activiti username: root password: root driver-class-name: com.mysql.jdbc.Driver
⑤特别操作
在src/main/java目录下创建processes目录,否则将抛出异常:java.io.FileNotFoundException: class path resource [processes/] cannot be resolved to URL because it does not exist。processes目录的作用是存放bpmn流程定义文件,并对bpmn文件进行自动部署。如果创建到src/main/resources目录下则要求processes目录下至少存在一个文件(任意)。
三、流程操作
1.创建第一个流程图
2.流程部署
①Java代码
注意:如果放在processes目录下会被自动部署,下面的Java代码是手动部署。
@RunWith(SpringRunner.class) @SpringBootTest public class ProcessTest { @Autowired private RepositoryService repositoryService; @Test public void test01ProcessDefinitionDeployment() { //部署:将流程定义文件中流程定义信息存入数据库 repositoryService .createDeployment() //创建部署构建器对象 .addClasspathResource("MyProcess.bpmn") //添加要部署的流程定义文件名 .deploy(); //执行部署 } }
②数据库表分析
[1]act_ge_bytearray表
二进制数据表,存储了流程定义图形的XML文件和图片信息
保存流程定义的xml信息
保存流程定义的图片
[2]act_re_deployment表
部署信息表,存储了部署的相关信息(部署时间)
[3]act_re_procdef表
流程定义数据表,存储了当前流程图形的相关信息(id,name,版本号)
③解决乱码问题
import org.activiti.spring.SpringProcessEngineConfiguration; import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer; import org.springframework.context.annotation.Configuration; @Configuration public class ActivitiConfig implements ProcessEngineConfigurationConfigurer { @Override public void configure(SpringProcessEngineConfiguration processEngineConfiguration) { processEngineConfiguration.setActivityFontName("宋体"); processEngineConfiguration.setLabelFontName("宋体"); } }
3.流程查询
①第一步:创建ProcessDefinitionQuery对象
@Test public void test02ProcessDefinitionQuery() { //1.创建流程定义查询对象 ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); //2.调用对应的方法执行查询 List<ProcessDefinition> list = query.list(); //3.遍历集合 for (ProcessDefinition processDefinition : list) { String key = processDefinition.getKey(); String name = processDefinition.getName(); int version = processDefinition.getVersion(); System.err.println("key="+key); System.err.println("name="+name); System.err.println("version="+version); } }
注意2:创建了新的流程定义需要再次执行部署,再次启动流程。
6.查询任务和7.完成任务
@Test public void test03TaskQuery() { //创建任务查询对象 TaskQuery query = taskService.createTaskQuery(); //根据流程定义key和委托人查询待办任务 List<Task> list = query.processDefinitionKey("MyProcess").taskAssignee("zuzu").list(); for (Task task : list) { String assignee = task.getAssignee(); String taskId = task.getId(); String taskName = task.getName(); //taskService需要使用@Autowired注解装配 taskService.complete(task.getId());//完成任务,使任务进入下一步 System.err.println("assignee="+assignee); System.err.println("taskId="+taskId); System.err.println("taskName="+taskName); } }
8.查询流程实例历史
//1.创建流程实例的历史查询对象,historyService使用@Autowired注解注入 HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery(); HistoricProcessInstance instance = query.processInstanceId("7501") //流程实例的id .finished()//调用这个方法后未完成的流程实例会返回null .singleResult(); System.out.println(instance);
9.将任务指派给一个组
注意1:创建的新的流程定义要指定一个新的id。
注意2:创建了新的流程定义需要再次执行部署,再次启动流程。
10.领取任务
@Test public void test04Claim() { //1.创建任务查询对象 TaskQuery taskQuery = taskService.createTaskQuery(); //2.根据任务委托组查询任务列表 List<Task> list = taskQuery.taskCandidateGroup("jingli").list(); //3.遍历 for (Task task : list) { String taskId = task.getId(); String userId = "jingli"; //3.指派任务 taskService.claim(taskId, userId); } }
提示:可以根据任务委托人在领取任务前和领取任务后分别查询任务,之前没有之后有即为正确。
TaskQuery query = taskService.createTaskQuery();//创建任务查询对象 List<Task> list = query.processDefinitionKey("MyProcess") //指定流程定义 .taskAssignee("jingli")//指定委托人 .list();//执行查询 for (Task task : list) { System.out.println(task); }
11.流程变量
①在创建流程定义时指定流程变量
例如:用变量的方式指定委托人
创建MyProcess02.bpmn
创建后正常部署
@Test public void test01ProcessDefinitionDeployment() { //部署:将流程定义文件中流程定义信息存入数据库 repositoryService .createDeployment() //创建部署构建器对象 .addClasspathResource("MyProcess02.bpmn") //添加要部署的流程定义文件名 .deploy(); //执行部署 }
②启动
@Test public void test05StartProcessInstance() { //1.查询流程定义对象 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("MyProcess02") .latestVersion() .singleResult(); //2.获取流程定义的id String processDefinitionId = processDefinition.getId(); //3.使用RunTimeService根据流程定义id启动流程定义 ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId); //4.打印processInstance对象 System.err.println(processInstance); }
带有变量的流程启动时,如果第一个任务中就存在变量,那么启动的时候就必须给出变量值。否则将抛出异常:org.activiti.engine.impl.javax.el.PropertyNotFoundException: Cannot resolve identifier 'erzi'
@Test public void test06StartProcessInstanceWithVariable() { //1.查询流程定义对象 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("MyProcess02") .latestVersion() .singleResult(); //2.获取流程定义的id String processDefinitionId = processDefinition.getId(); //※为第一个任务指定流程变量 Map<String,Object> variables = new HashMap<String, Object>(); variables.put("erzi", "zhangsan"); //3.使用RunTimeService根据流程定义id启动流程定义 ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, variables); //4.打印processInstance对象 System.err.println(processInstance); }
③完成任务
如果完成当前任务时,下一个任务中包含变量,那么此时也必须给出下一个任务的变量值
@Test public void test7CompleteTaskWithVariables() { List<Task> list = taskService.createTaskQuery() //创建任务查询对象 .processDefinitionKey("MyProcess02")//指定流程定义 .taskAssignee("zhangsan")//指定委托人 .list();//执行查询 for (Task task : list) { String taskId = task.getId(); //在完成当前任务时,为下一个任务指定流程变量 Map<String,Object> variables = new HashMap<String, Object>(); variables.put("sunzi", "lisi"); //完成当前任务时为下一个任务指定变量值 taskService.complete(taskId, variables);//完成任务,使任务进入下一步 //Unknown property used in expression: ${sunzi} //taskService.complete(taskId); } }
12.排他网关
①创建流程定义
部署、启动
@Test public void test01ProcessDefinitionDeployment() { //部署:将流程定义文件中流程定义信息存入数据库 repositoryService .createDeployment() //创建部署构建器对象 .addClasspathResource("MyProcess03.bpmn") //添加要部署的流程定义文件名 .deploy(); //执行部署 }
@Test public void test05StartProcessInstance() { //1.查询流程定义对象 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("MyProcess03") .latestVersion() .singleResult(); //2.获取流程定义的id String processDefinitionId = processDefinition.getId(); //3.使用RunTimeService根据流程定义id启动流程定义 ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId); //4.打印processInstance对象 System.err.println(processInstance); }
②完成第一个任务时为排他网关指定变量值
@Test public void test7CompleteTaskWithVariables() { List<Task> list = taskService.createTaskQuery() //创建任务查询对象 .processDefinitionKey("MyProcess03")//指定流程定义 .taskAssignee("zhangsan")//指定委托人 .list();//执行查询 for (Task task : list) { String taskId = task.getId(); //在完成当前任务时,为下一个任务指定流程变量 Map<String,Object> variables = new HashMap<String, Object>(); variables.put("day", "5"); //完成当前任务时为下一个任务指定变量值 taskService.complete(taskId, variables);//完成任务,使任务进入下一步 //Unknown property used in expression: ${sunzi} //taskService.complete(taskId); } }
[3]创建流程监听器类
import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.ExecutionListener; public class YesListener implements ExecutionListener { private static final long serialVersionUID = 1L; @Override public void notify(DelegateExecution execution) throws Exception { System.err.println("YesListener触发了!!!"); } }
import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.ExecutionListener; public class NoListener implements ExecutionListener { private static final long serialVersionUID = 1L; @Override public void notify(DelegateExecution execution) throws Exception { System.err.println("NoListener触发了!!!"); } }
[4]绑定监听器
④操作
- 部署
/** * 部署 */ @Test public void test01ProcessDefinitionDeployment() { //部署:将流程定义文件中流程定义信息存入数据库 repositoryService .createDeployment() //创建部署构建器对象 .addClasspathResource("MyProcess04.bpmn") //添加要部署的流程定义文件名 .deploy(); //执行部署 }
- 启动
/** * 启动流程 */ @Test public void test05StartProcessInstance() { //1.查询流程定义对象 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("MyProcess04") .latestVersion() .singleResult(); //2.获取流程定义的id String processDefinitionId = processDefinition.getId(); //3.使用RunTimeService根据流程定义id启动流程定义 ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId); //4.打印processInstance对象 System.err.println(processInstance); }
- 完成任务时为排他网关指定flag变量值
/** * 完成任务 */ @Test public void test7CompleteTaskWithVariables() { List<Task> list = taskService.createTaskQuery() //创建任务查询对象 .processDefinitionKey("MyProcess04")//指定流程定义 .taskAssignee("zhangsan")//指定委托人 .list();//执行查询 for (Task task : list) { String taskId = task.getId(); //在完成当前任务时,为下一个任务指定流程变量 Map<String,Object> variables = new HashMap<String, Object>(); variables.put("flag", "true"); //完成当前任务时为下一个任务指定变量值 taskService.complete(taskId, variables);//完成任务,使任务进入下一步 //Unknown property used in expression: ${sunzi} //taskService.complete(taskId); } }
- 观察监听器执行情况