其实activiti被springboot整合后用起来简单很多,但还是非常推荐各位先去学习下activiti的原生基础运用,因为这样才能真正明白整合里面都干了些什么。废话就不多说了,直接来!
开发的版本: springboot 版本 2.0 + activiti 版本 6.0
开发工具:IDEA
1.引入依赖
这里用的mybatis链接数据库,还需要引入mysql的驱动(注意:mysql链接驱动的版本)
特别注意:跟 activiti6.0 匹配的mysql驱动版本不能太高!例如springboot2.0以后的默认使用的mysql驱动版本就太高了,版本不兼容会发生各种头疼且解决不了的问题。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
2.修改springboot的application.yml配置文件
数据源的信息是mybatis连接数据库的,你可以这样,配置详细的 activiti 的参数,注意 activiti 配置下的datasource 信息要一样
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: 123
url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
activiti:
check-process-definitions: true #自动检查、部署流程定义文件
database-schema-update: true #自动更新数据库结构
#流程定义文件存放目录
process-definition-location-prefix: classpath:/processes/
#process-definition-location-suffixes: #流程文件格式
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: 123
initsize: 10
maxActive: 20
minIdle: 10
maxWait: 120000
poolPreparedStatements: false
maxOpenPreparedStatements: -1
validationQuery: select 1
testOnborrow: true
testOnReturn: true
testWhileIdle: true
timeBetweenEvictionRunsMillis: 120000
你也可以只配置 datasource 的配置,这是项目连接的数据源,springboot 整合 activiti 后默认就是去读的这个数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: 123
url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
3.生成 activiti 所用到的所有表
直接运行下面的这个main方法,即可生成28张表(activiti6.0是28张表),每张表的作用这里就不介绍了,网上很多。
没有报错的前提是你的activiti版本跟mysql链接驱动的版本匹配,并且已经在配置文件指定数据源!
package com.liqiye.springbootdemo.test.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
// 运行生成activiti流程依赖的表
public class ActivitiTable {
public static void main(String[] args) {
// 引擎配置
ProcessEngineConfiguration pec=ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
pec.setJdbcDriver("com.mysql.jdbc.Driver");
pec.setJdbcUrl("jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
pec.setJdbcUsername("root");
pec.setJdbcPassword("123");
/**
* false 不能自动创建表
* create-drop 先删除表再创建表
* true 自动创建和更新表
*/
pec.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
// 获取流程引擎对象
ProcessEngine processEngine=pec.buildProcessEngine();
}
}
4.画bpmn 图
想要在IDEA 里进行 activiti的开发,必须要安装 actiBPM 插件 ,下图安装即可。
springboot 整合 activiti ,默认是启动项目后,在resources目录下的 processes 目录下读取 bpmn 流程文件,然后自动部署的,没错!不需要再自己写什么部署的方法了,项目自动帮我们完成。
注意:必须是 processes 目录,并且里面一定要有 bpmn 文件,不然项目启动报错!
在新建的processes目录下右键,new—BpmnFile ,起名leave.bpmn
然后画图 ,在界面的右边拖动流程的每一个模块,到中间组成一个流程图,点击对应的组件,在左边填写里面的id,审核人等的信息
这是我画的流程图,以及整个流程图的信息,注意:这里的id在后面开启流程的时候会用到,起名尽量规则且记住
点击申请请假的绿色框,在左边填写 Assignee 的值为 ${user},记住这个标识,到后面启动流程或者审核流程需要传入参数。其实学习过activiti基础操作的同学应该明白在这里设置这个跟在xml里面设置参数是一样的。
同样的,我们在部门领导、公司领导那里也要设置审核人,那里我们就设置 ${users}
画完bpmn图了,因为idea没有给我们提供直接生成png图的功能,我们需要手动修改文件名后缀成xml,然后再右键这个leave.xml 文件 — Diagrams — Show BPMN2.0 Designer...
然后点击下图的位置,选中项目processes目录下,生成png图片
其实springboot 整合 activiti 后,项目启动,会自动到processes目录下扫描,部署里面的bpmn文件,并且自动根据bpmn文件生成png图片的数据保存在数据库,没错,并不需要我们手动生成png图片!到时候可以直接从数据库查询并且显示流程图。
所以上面说了这么久如何生成png图片是做什么?没什么,就是学多点东西而已。
下面放上面流程图对应的xml文件,你可以直接复制到 processes 目录下,然后改后缀名为 bpmn 即可。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1557979450845" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="leave" isClosed="false" isExecutable="true" name="请假流程" processType="None">
<startEvent id="_2" name="开始"/>
<userTask activiti:candidateUsers="${users}" activiti:exclusive="true" id="_3" name="公司领导"/>
<endEvent id="_4" name="结束"/>
<sequenceFlow id="_6" sourceRef="_3" targetRef="_4"/>
<userTask activiti:assignee="${userrs}" activiti:candidateUsers="${users}" activiti:exclusive="true" id="_5" name="部门领导"/>
<sequenceFlow id="_8" sourceRef="_5" targetRef="_3"/>
<userTask activiti:assignee="${user}" activiti:exclusive="true" id="_7" name="申请请假"/>
<sequenceFlow id="_9" sourceRef="_2" targetRef="_7"/>
<sequenceFlow id="_10" sourceRef="_7" targetRef="_5"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="leave">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="195.0" y="20.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="55.0" width="85.0" x="170.0" y="295.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="32.0" width="32.0" x="195.0" y="390.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="55.0" width="85.0" x="170.0" y="205.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_7" id="Shape-_7">
<omgdc:Bounds height="55.0" width="85.0" x="170.0" y="115.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_3" targetElement="_4">
<omgdi:waypoint x="211.0" y="350.0"/>
<omgdi:waypoint x="211.0" y="390.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_5" targetElement="_3">
<omgdi:waypoint x="212.5" y="260.0"/>
<omgdi:waypoint x="212.5" y="295.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_9" id="BPMNEdge__9" sourceElement="_2" targetElement="_7">
<omgdi:waypoint x="211.0" y="52.0"/>
<omgdi:waypoint x="211.0" y="115.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_10" id="BPMNEdge__10" sourceElement="_7" targetElement="_5">
<omgdi:waypoint x="212.5" y="170.0"/>
<omgdi:waypoint x="212.5" y="205.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
在启动项目之前,要先在springboot的启动类上面加上下面这个注解,因为activiti6.0比springboot2.0早很多,那时候还没有这个类,在启动时要排除他,不然就会报找不到该bean的错误。
@SpringBootApplication(exclude = SecurityAutoConfiguration.class) // springboot2.0集成activiti6要加后面这个,因为6比较老版本
上面整合完 activiti 了,画完流程图,并且启动了项目,我们可以发现在之前数据库生成的 act_ge_bytearray 表中 多了两条数据,看名字就可以知道一条是部署的bpmn文件,一条是对应的png图片
4.编写 activiti 的功能接口
接下来我们编写接口来发起流程,审核流程,指定用户查看对应任务,指定发起者查看对应的流程,还有显示流程执行的图
先在controller注入要用到的 activiti 的工具
@Autowired
private ActivityService activityService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private IdentityService identityService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
1)发起流程
注意:如果你在流程图指定了 Assignee ,这里当你启动流程时,必须要传进参数,不然报错。这里参数一般是传用户id进数据库保存
// 发起流程
@RequestMapping("/initiationProcess")
@ResponseBody
public String initiationProcess(){
System.out.println("method startActivityDemo begin....");
System.out.println( "调用流程存储服务,已部署流程数量:"
+ repositoryService.createDeploymentQuery().count());
Map<String,Object> map = new HashMap<String,Object>();
// 流程图里写的${user} ,这里传进去user
map.put("user","liqiye");
//流程启动
identityService.setAuthenticatedUserId("liqiye"); // 指定流程的发起者 不指定发起者的字段就为空,注意跟审核人分开
ExecutionEntity pi = (ExecutionEntity) runtimeService.startProcessInstanceByKey("leave",map);
System.out.println("启动流程成功!");
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("任务ID: "+task.getId());
System.out.println("任务的办理人: "+task.getAssignee());
System.out.println("任务名称: "+task.getName());
System.out.println("任务的创建时间: "+task.getCreateTime());
System.out.println("流程实例ID: "+task.getProcessInstanceId());
Map<String,Object> map2 = new HashMap<String,Object>();
map2.put("users","lisi,wangwu");
taskService.complete(task.getId(),map2); // 开启后,环节会走到发起请假请求,要完成这个环节,才能到下一个审核环节
System.out.println("method startActivityDemo end....");
return "success";
}
2)审核任务
把上面打印出来的taskId 记下来,然后发起下面的接口链接,即可审核任务
// 根据 taskid 审核任务
@RequestMapping("/audit")
@ResponseBody
public String audit(String taskId){
Map<String,Object> map = new HashMap<String,Object>();
// 流程图里写的${users} ,这里传进去users
map.put("users","lisi,wangwu");
taskService.complete(taskId,map);
return "success";
}
3)指定用户查看对应任务
// 通过用户名查询该用户的所有任务
@RequestMapping("/checkByUser")
@ResponseBody
public String checkByUser(String user){
List<Task> tasks = taskService//与任务相关的Service
.createTaskQuery()//创建一个任务查询对象
.taskAssignee(user)
.list();
if(tasks !=null && tasks.size()>0){
for(Task task:tasks){
System.out.println("任务ID:"+task.getId());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
}
}
return "success";
}
4)指定发起者查看对应的流程
// 通过发起者查询该用户发起的所有任务
@RequestMapping("/checkByInitiator")
@ResponseBody
public String checkByInitiator(String user){
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().startedBy(user).list(); //获取该用户发起的所有流程实例
// System.out.println(list.toString());
for (ProcessInstance processInstance : list) {
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).list();
if(tasks !=null && tasks.size()>0){
for(Task task:tasks){
System.out.println("任务ID:"+task.getId());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
}
}
}
return "success";
}
5)显示流程执行的图
直接发起这个接口就可以显示对应的流程图
/**
* 获取流程图 执行到哪里高亮显示
* @param procDefId 部署的流程id 在 act_re_procdef 这张表里
* @param execId 要查询的流程执行的id(开启了一个流程就会生成一条执行的数据) 在 act_ru_execution 这张表里(该表下PROC_DEF_ID_字段可以判断哪个流程)
* @param response
* @throws Exception
*/
@RequestMapping("/getActPic/{procDefId}/{execId}")
public void getActPic(@PathVariable("procDefId") String procDefId,
@PathVariable("execId") String execId, HttpServletResponse response)throws Exception {
InputStream imageStream = activityService.tracePhoto(procDefId, execId);
// 输出资源内容到相应对象
byte[] b = new byte[1024];
int len;
while ((len = imageStream.read(b, 0, 1024)) != -1) {
response.getOutputStream().write(b, 0, len);
}
}
ActivitiService 里的方法
// 获取流程图 执行到哪里红色显示
public InputStream tracePhoto(String processDefinitionId, String executionId) {
// ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(executionId).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
List<String> activeActivityIds = new ArrayList();
if (runtimeService.createExecutionQuery().executionId(executionId).count() > 0){
activeActivityIds = runtimeService.getActiveActivityIds(executionId);
}
// 不使用spring请使用下面的两行代码
// ProcessEngineImpl defaultProcessEngine = (ProcessEngineImpl)ProcessEngines.getDefaultProcessEngine();
// Context.setProcessEngineConfiguration(defaultProcessEngine.getProcessEngineConfiguration());
// 使用spring注入引擎请使用下面的这行代码
Context.setProcessEngineConfiguration(processEngineFactory.getProcessEngineConfiguration());
// return ProcessDiagramGenerator.generateDiagram(bpmnModel, "png", activeActivityIds);
return processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator()
.generateDiagram(bpmnModel, "png", activeActivityIds);
}
我这里只是测试了一个非常简单的流程,当然,实际开发中基本会在流程图里用到互斥网关,或者并行网关什么的,开发起来也差不多,无非是复杂点,这里就不一一介绍了。