其实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 插件 ,下图安装即可。

springboot3 整合 activiti springboot2集成activiti_springboot

springboot 整合 activiti ,默认是启动项目后,在resources目录下的 processes 目录下读取 bpmn 流程文件,然后自动部署的,没错!不需要再自己写什么部署的方法了,项目自动帮我们完成。

注意:必须是 processes 目录,并且里面一定要有 bpmn 文件,不然项目启动报错!

在新建的processes目录下右键,new—BpmnFile ,起名leave.bpmn

springboot3 整合 activiti springboot2集成activiti_spring_02

然后画图 ,在界面的右边拖动流程的每一个模块,到中间组成一个流程图,点击对应的组件,在左边填写里面的id,审核人等的信息

这是我画的流程图,以及整个流程图的信息,注意:这里的id在后面开启流程的时候会用到,起名尽量规则且记住

springboot3 整合 activiti springboot2集成activiti_springboot_03

springboot3 整合 activiti springboot2集成activiti_spring_04

点击申请请假的绿色框,在左边填写 Assignee 的值为 ${user},记住这个标识,到后面启动流程或者审核流程需要传入参数。其实学习过activiti基础操作的同学应该明白在这里设置这个跟在xml里面设置参数是一样的。

springboot3 整合 activiti springboot2集成activiti_mysql_05

同样的,我们在部门领导、公司领导那里也要设置审核人,那里我们就设置 ${users}

springboot3 整合 activiti springboot2集成activiti_xml_06

画完bpmn图了,因为idea没有给我们提供直接生成png图的功能,我们需要手动修改文件名后缀成xml,然后再右键这个leave.xml 文件 — Diagrams — Show BPMN2.0 Designer... 

springboot3 整合 activiti springboot2集成activiti_spring_07

然后点击下图的位置,选中项目processes目录下,生成png图片

springboot3 整合 activiti springboot2集成activiti_xml_08

其实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的错误。

springboot3 整合 activiti springboot2集成activiti_spring_09

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

 

我这里只是测试了一个非常简单的流程,当然,实际开发中基本会在流程图里用到互斥网关,或者并行网关什么的,开发起来也差不多,无非是复杂点,这里就不一一介绍了。