Activiti5.22整合Springboot(画图详解,会签监听事件,附带源码)


前言

此篇文章使用了activiti5.22版本(经典版)来整合Springboot2.3.1.RELEASE,并且介绍了IDEA/Eclipse整合Bpmn制图工具(博主推荐使用Eclipse制图,可后续cp到Idea中),加入会签案例、监听事件、附上源码学习


一、Activiti5.22整合Springboot

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张表
在这里插入图片描述

二、IDEA/Eclipse整合bpmn画图

1.IDEA整合Bpmn

1.1搜索插件
点击菜单【File】–>【Settings…】打开【Settings】窗口。

点击左侧【Plugins】按钮,在右侧输出"actiBPM",点击下面的【Search in repositories】链接会打开【Browse Repositories】窗口。

进入【Browse Repositories】窗口,选中左侧的【actiBPM】,点击右侧的【Install】按钮,开始安装。
代码如下(示例):

在这里插入图片描述
1.2创建bpmn文件
在这里插入图片描述

注意:此处博主不推荐使用Idea画流程图,此插件5年没有更新,诸多bug如流程图数据丢失、查找不到监听事件等

2.Eclipse整合Bpmn

流程如下(示例):

2.1 点击Help -> Install New Software,输入以下站点信息:

在这里插入图片描述
2.2 点击 ->Add 添加站点信息

在这里插入图片描述
2.3 添加Bpmn站点信息

Name: Activiti BPMN 2.0 designer
Location: http://activiti.org/designer/update/

在这里插入图片描述

在这里插入图片描述

因为网络的原因,可能会安装不成功,可以上网找插件包自行安装
2.4 创建Bpmn图
在这里插入图片描述


三、利用Eclipse制作会签流程图

提醒:

新学Activiti的小伙伴要知道,Activiti难的是根据业务制作流程图和编写监听事件。

先放画好的会签流程图,然后再一步一步的解析。

在这里插入图片描述

3.1流程图命名Id

在这里插入图片描述
countersign:会签

Id需要自行设置,部署流程和启动流程实例的时候需要用到。

3.2 任务申请节点配置

在这里插入图片描述
**提示:**Assignee此处是写死由张三进行发起任务,可以使用流程变量进行传入,此处写死,会签节点会使用流程变量示例。

3.3 申请节点 添加监听事件

在这里插入图片描述
注意:依旧是在申请节点配置监听事件。由图可知此监听事件是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);
    }
}

3.4 会签节点配置

在这里插入图片描述
Assignee要和ElementVariable配置的一样。一个${signer},一个signer
在这里插入图片描述
${pass}是用户进行会签审批时的结果,通过true还是驳回false,需要获取记录此结果来判断会签率是否通过。

${signerList}则是传入会签人员的流程变量,就是上面申请节点结束后,编写监听事件传入的数据。

Sequential:false 表示会签三人同时能审批任务。true表示按照顺序进行审批。

3.5 会签监听事件

会签这边一共俩个类一个创建节点时执行,一个节点结束执行。

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)

     * <p>Title: completeTask1</p>

     * <p>Description: </p>

     * @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);

    }
}

绑定监听事件
在这里插入图片描述

3.6 配置排他网关

在这里插入图片描述

可以看到图中一个大X的图标就是排他网关,流程走到此处会进行会签率判断是驳回,还是继续往下走。

配置驳回线路
在这里插入图片描述

配置通过条件

在这里插入图片描述
可以看到此处的通过率设置的为1/3,通过则流程继续,驳回则回到申请节点处,张三重新申请。

3.7 单人审批节点配置

在这里插入图片描述
会签结束后,再来个单人审批节点,此处的审批人也是写死。

四、部署流程并测试会签逻辑

说明:

至此我们的流程图和监听事件都完成并配置好。将此流程图复制到Idea中进行部署,博主测试过程中碰见数据丢失问题,建议转成xml文件进行部署。

4.1流程文件XML准备

在Eclipse中右键将bpmn图使用XML打开,操作如下:
在这里插入图片描述

转化成XML后复制移动到Idea中,需要记得更改监听事件的位置。
将此XML内容复制到Idea中,记得将文件名称改为*.bpmn20.xml,便于部署
在resources文件下创建bpmn文件夹,移动过来的流程XML文件放于此文件夹下,方便读取。

在这里插入图片描述

4.2 流程部署

创建部署类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&amp;characterEncoding=utf8&amp;nullCatalogMeansCurrent=true&amp;useSSL=false&amp;useLegacyDatetimeCode=false&amp;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方法查看数据库:

在这里插入图片描述

4.3 启动实例

编写启动类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方法查看数据库

在这里插入图片描述

启动实例 实际就意味着一条任务开始。

4.4 执行任务

编写执行类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,所以任务向下一节点走,到王五单人审批。可见会签逻辑已成功。

五:源码Gitee地址:

点击获取

总结

Activiti整合Springboot:
以上就是今天要讲的内容,本文仅仅简单介绍了会签流程的案例,而Activiti提供了大量能使我们快速便捷地处理数据的函数和方法。
本人小白文章中也有一些代码是学习时借鉴网上的代码,在里面修改,有什么不对的地方欢迎指正。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值