此篇文章使用了activiti5.22版本(经典版)来整合Springboot2.3.1.RELEASE,并且介绍了IDEA/Eclipse整合Bpmn制图工具(博主推荐使用Eclipse制图,可后续cp到Idea中),加入会签案例、监听事件、附上源码学习
1、引入maven包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- activiti -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>5.22.0</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mybatis-plus 根据自己需求来 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
<!-- hutool工具包 根据自己需求来 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.7</version>
</dependency>
</dependencies>
注意点:注意我用的Springboot版本2.3.1.RELEASE。不清楚的可以直接去下面拉去项目源码
2、Springboot配置文件 application.yml
server:
port: 8081
spring:
activiti:
check-process-definitions: false #自动检查、部署流程定义文件
# database-schema-update: true #自动更新数据库结构
# process-definition-location-prefix: classpath:/processes/ #流程定义文件存放目录
datasource:
# 注意的你数据库版本 我的数据库是8.0 数据库不同 配置会有些不一样 具体请自行搜索
driver-class-name: com.mysql.cj.jdbc.Driver
# activit_test是你本地的库名 项目启动会在此库中创建activiti需要的表
url: jdbc:mysql://localhost:3306/activiti_test?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: 199700
#配置mybatis-plus需要指定mapper和entity路径,文件夹需要自己创建
mybatis-plus:
mapper-locations: classpath:com.aplid.activiti.mapper/*Mapper.xml #指定映射文件
type-aliases-package: com.aplid.activiti.entity #别名包
configuration:
map-underscore-to-camel-case: true #开启驼峰命名
#会把activiti操作表的sql打印出来
logging:
level:
org.activit.engine.impl.persistence.entity: trace
3、Springboot启动项加注解 2.x以后需要
package com.cn.activiti;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(exclude = {
org.activiti.spring.boot.SecurityAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class
})
public class SpringbootActivitApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootActivitApplication.class, args);
}
}
4、启动
注意:启动成功以后,打开数据库 在你配置的库下会自动生成25张表
1.1搜索插件
点击菜单【File】–>【Settings…】打开【Settings】窗口。
点击左侧【Plugins】按钮,在右侧输出"actiBPM",点击下面的【Search in repositories】链接会打开【Browse Repositories】窗口。
进入【Browse Repositories】窗口,选中左侧的【actiBPM】,点击右侧的【Install】按钮,开始安装。
代码如下(示例):
注意:此处博主不推荐使用Idea画流程图,此插件5年没有更新,诸多bug如流程图数据丢失、查找不到监听事件等
流程如下(示例):
2.1 点击Help -> Install New Software,输入以下站点信息:
Name: Activiti BPMN 2.0 designer
Location: http://activiti.org/designer/update/
因为网络的原因,可能会安装不成功,可以上网找插件包自行安装
2.4 创建Bpmn图
提醒:
新学Activiti的小伙伴要知道,Activiti难的是根据业务制作流程图和编写监听事件。
先放画好的会签流程图,然后再一步一步的解析。
Id需要自行设置,部署流程和启动流程实例的时候需要用到。
**提示:**Assignee此处是写死由张三进行发起任务,可以使用流程变量进行传入,此处写死,会签节点会使用流程变量示例。
注意:依旧是在申请节点配置监听事件。由图可知此监听事件是Complete在申请节点结束后执行,是为了给下一会签节点,传入流程变量(就是会签由哪几个人审批)。
编写监听事件类MyCompeteistener
★★★需继承TaskListener类,重写notify方法。
/**
* 申请节点结束后指定审批节点的会签审批人
*
* @author whr
* @date 2020/8/5 9:14
*/
public class MyCompeteistener implements TaskListener {
//★★★
//设置在会签节点审批的人为3人,为A1、A2、A3,而signerList就是流程变量,此字段在会签节点进行设置,下面会讲。
@Override
public void notify(DelegateTask arg0) {
List<String> userList = new ArrayList<String>();
userList.add("A1");
userList.add("A2");
userList.add("A3");
arg0.setVariable("signerList", userList);
}
}
Assignee要和ElementVariable配置的一样。一个${signer},一个signer
${pass}是用户进行会签审批时的结果,通过true还是驳回false,需要获取记录此结果来判断会签率是否通过。
${signerList}则是传入会签人员的流程变量,就是上面申请节点结束后,编写监听事件传入的数据。
Sequential:false 表示会签三人同时能审批任务。true表示按照顺序进行审批。
会签这边一共俩个类一个创建节点时执行,一个节点结束执行。
MutiGroupsListener类(创建节点时执行)类型:Create
/**
*
*
* @author whr
* @date 2020/8/5 9:17
*/
public class MutiGroupsListener implements TaskListener {
@Override
public void notify(DelegateTask arg0) {
//将通过人数,未通过人数,总数,重新置为0,退回的时候才能重新计算
arg0.setVariable("passCount", "0");
arg0.setVariable("totalCount", "0");
arg0.setVariable("noPassCount", "0");
//为每一个任务设置处理人
String signer = (String)arg0.getVariable("signer");
arg0.setAssignee(signer);
}
}
JointlySign类 结束执行统计结果 Complete
/**
* 会签监听器
*
* @author whr
* @date 2020/8/6 10:22
*/
public class JointlySign implements Serializable, TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
//获取流程id
String exId = delegateTask.getExecutionId();
//获取流程参数pass,会签人员完成自己的审批任务时会添加流程参数pass,false为拒绝,true为同意
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
boolean pass = (boolean) runtimeService.getVariable(exId, "pass");
//流程实例id
String procInstId = delegateTask.getProcessInstanceId();
//任务id
String taskId = delegateTask.getId();
//variables 流程变量
Map<String,Object> variables = delegateTask.getVariables();
//调用方法统计
completeTask1(procInstId,taskId,variables, String.valueOf(pass),delegateTask);
}
/**
* 审批节点有人执行任务时,需要统计执行情况
* @param procInstId
* @param taskId
* @param variables
* @param passflag
*/
/* (非 Javadoc)
* Title: completeTask1
* Description:
* @param procInstId 流程实例id
* @param taskId 任务id
* @param variables 流程变量
* @param passflag 审批同意标志(yes/no)
* @see com.ylkj.base.activiti.service.ActivitiService#completeTask1(java.lang.String, java.lang.String, java.util.Map, java.lang.String)
*/
public void completeTask1(String procInstId,String taskId, Map<String, Object> variables,String passflag,DelegateTask delegateTask) {
//获取当前节点的task_def_key_,此段代码主要用于区分多个节点时,审批通过记录数与总数
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
Task taskQuery = processEngine.getTaskService().createTaskQuery().taskId(taskId).singleResult();
List<Task> tasks = processEngine.getTaskService().createTaskQuery().taskName(taskQuery.getName()).processInstanceId(procInstId).list();
int passCount = 0;//审批同意人数
int noPassCount = 0;//审批不同意人数
int totalCount = 0;//任务总人数
//当前的执行情况
String tmpPassCount = processEngine.getRuntimeService().getVariable(procInstId, taskQuery.getTaskDefinitionKey()+"#passCount")+"";
String tmpNoPassCount = processEngine.getRuntimeService().getVariable(procInstId, taskQuery.getTaskDefinitionKey()+"#noPassCount")+"";
String tmpTotal = processEngine.getRuntimeService().getVariable(procInstId, taskQuery.getTaskDefinitionKey()+"#totalCount")+"";
if(!tmpPassCount.equals("null") && !tmpPassCount.trim().equals("")){
passCount = Integer.parseInt(tmpPassCount);
}
if(!tmpNoPassCount.equals("null") && !tmpNoPassCount.trim().equals("")){
noPassCount = Integer.parseInt(tmpNoPassCount);
}
if(tmpTotal.equals("null") || tmpTotal.trim().equals("")){
totalCount = tasks.size();
} else if(!tmpTotal.equals("null") && !tmpTotal.trim().equals("")){
totalCount = Integer.parseInt(tmpTotal);
}
for (Task tmp:tasks) {
if(passflag.equals("true") && tmp.getId().equals(taskId)){
//选择通过则通过人数+1
passCount++;
}
if(passflag.equals("false") && tmp.getId().equals(taskId)){
//选择不通过则不通过人数+1
noPassCount++;
}
}
//变量回写记录
variables.put("passCount", passCount);
variables.put("noPassCount", noPassCount);
variables.put("totalCount", totalCount);
variables.put(taskQuery.getTaskDefinitionKey()+"#passCount", passCount);
variables.put(taskQuery.getTaskDefinitionKey()+"#noPassCount", noPassCount);
variables.put(taskQuery.getTaskDefinitionKey()+"#totalCount", totalCount);
// ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
runtimeService.setVariable(delegateTask.getExecutionId(), "passCount", passCount);
runtimeService.setVariable(delegateTask.getExecutionId(), "noPassCount", noPassCount);
runtimeService.setVariable(delegateTask.getExecutionId(), "totalCount", totalCount);
runtimeService.setVariable(delegateTask.getExecutionId(), taskQuery.getTaskDefinitionKey()+"#passCount", passCount);
runtimeService.setVariable(delegateTask.getExecutionId(), taskQuery.getTaskDefinitionKey()+"#noPassCount",noPassCount);
runtimeService.setVariable(delegateTask.getExecutionId(), taskQuery.getTaskDefinitionKey()+"#totalCount", totalCount);
// processEngine.getTaskService().complete(taskId,variables);
}
}
可以看到图中一个大X的图标就是排他网关,流程走到此处会进行会签率判断是驳回,还是继续往下走。
配置通过条件
可以看到此处的通过率设置的为1/3,通过则流程继续,驳回则回到申请节点处,张三重新申请。
说明:
至此我们的流程图和监听事件都完成并配置好。将此流程图复制到Idea中进行部署,博主测试过程中碰见数据丢失问题,建议转成xml文件进行部署。
在Eclipse中右键将bpmn图使用XML打开,操作如下:
转化成XML后复制移动到Idea中,需要记得更改监听事件的位置。
将此XML内容复制到Idea中,记得将文件名称改为*.bpmn20.xml,便于部署
在resources文件下创建bpmn文件夹,移动过来的流程XML文件放于此文件夹下,方便读取。
创建部署类DeploymentProcess
/**
* 部署流程
*
* @author whr
* @date 2020/8/4 13:32
*/
public class DeploymentProcess {
public static void main(String[] args) {
startProcess();
}
/**
* 部署流程
*/
private static void startProcess() {
//创建ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//得到RepositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
DeploymentBuilder deployment = repositoryService.createDeployment();
//创建一个发布对象
deployment.addClasspathResource("bpmn/process_1.bpmn20.xml");
//添加流程文件路径
deployment.name("会签流程");
//添加流程部署名称
deployment.category("会签分类");
Deployment deploy = deployment.deploy();
}
}
添加配置activiti.cfg.xml文件,放入resources文件夹下,数据库需配置开始生成25张表的库。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--配置数据源,dbcp-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/activiti_test?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="199700" />
</bean>
<!--activiti单独运行的processEngine配置,获取processEngineConfiguration对象-->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"></property>
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
运行main方法查看数据库:
编写启动类StartInstance
/**
* 启动实例
*
* @author whr
* @date 2020/8/4 14:09
*/
public class StartInstance {
public static void main(String[] args) {
startInstance();
}
/**
* 启动流程实例
*/
private static void startInstance() {
//得到ProcessEnginge对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//得到RunService对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//创建流程实例 流程定义的key: bpmn id
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("countersign");
//输出相关信息
System.out.println("流程部署id" + processInstance.getDeploymentId());
System.out.println("流程定义id" + processInstance.getProcessDefinitionId());
System.out.println("流程实例id" + processInstance.getId());
System.out.println("活动id" + processInstance.getActivityId());
}
}
注意:一开始流程图让你定义的Id就是在此时用到,启动流程实例一定依旧流程启动,这个Id就是这个流程的Key 例如代码中的“countersign”。
执行main方法查看数据库
启动实例 实际就意味着一条任务开始。
编写执行类CarryTask
/**
* 执行任务
*
* @author whr
* @date 2020/8/17 11:08
*/
public class CarryTask {
public static void main(String[] args) {
activitiCompleteTask();
}
private static void activitiCompleteTask() {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.查询当前用户的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("countersign")
.taskAssignee("张三")
.singleResult();
//4.处理任务,结合当前用户任务列表的查询操作的话,任务ID:task.getId()
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("pass", true);
MutiGroupsListener mutiGroupsListener = new MutiGroupsListener();
taskService.complete(task.getId(), variables);
//5.输出任务的id
System.out.println(task.getId());
}
}
可以看到启动流程实例以后,数据库任务表中显示张三执行申请任务,而这和我们一开始配置流程图正好匹配,所以上图代码taskAssignee(“张三”),张三执行。
执行main方法查看数据库。
可以看到此时变成A1、A2、A3同时审批任务。
更改执行任务的代码,将执行人更改为A1,传入审批结果为false。
代码如下:
/**
* 执行任务
*
* @author whr
* @date 2020/8/17 11:08
*/
public class CarryTask {
public static void main(String[] args) {
activitiCompleteTask();
}
private static void activitiCompleteTask() {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.查询当前用户的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("countersign")
//此处更改任务执行人
.taskAssignee("A1")
.singleResult();
//4.处理任务,结合当前用户任务列表的查询操作的话,任务ID:task.getId()
Map<String, Object> variables = new HashMap<String, Object>();
//此处更改审批结果
variables.put("pass", false);
MutiGroupsListener mutiGroupsListener = new MutiGroupsListener();
taskService.complete(task.getId(), variables);
//5.输出任务的id
System.out.println(task.getId());
}
}
执行main方法查看数据库。
可得:A1执行完任务,并且是没有同意,通过率没有到大1/3。
继续A2执行main方法,A2同意此任务。
/**
* 执行任务
*
* @author whr
* @date 2020/8/17 11:08
*/
public class CarryTask {
public static void main(String[] args) {
activitiCompleteTask();
}
private static void activitiCompleteTask() {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.查询当前用户的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("countersign")
.taskAssignee("A2")
.singleResult();
//4.处理任务,结合当前用户任务列表的查询操作的话,任务ID:task.getId()
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("pass", true);
MutiGroupsListener mutiGroupsListener = new MutiGroupsListener();
taskService.complete(task.getId(), variables);
//5.输出任务的id
System.out.println(task.getId());
}
}
A2执行完,任务已经通过,通过率大于1/3,所以任务向下一节点走,到王五单人审批。可见会签逻辑已成功。
点击获取
Activiti整合Springboot:
以上就是今天要讲的内容,本文仅仅简单介绍了会签流程的案例,而Activiti提供了大量能使我们快速便捷地处理数据的函数和方法。
本人小白文章中也有一些代码是学习时借鉴网上的代码,在里面修改,有什么不对的地方欢迎指正。