弃坑入门:Activiti 7.1.0-M13+SpringBoot 2.5.0 开发环境搭建

由于Activiti项目团队自身的一些问题,新版本迭代很快,但功能基本上还是基于原来的Activiti 5.x/6.x,加上项目质量管控多少问题,新版本的bug多修复慢。

国内很多activiti的用户还停留在5.x/6.x的阶段,故网上各种教材文章的内容也如此,对Activiti 7.x的资料有点无面,基本上都是就一两个点的记录。

本测试将以尽量最新的环境版本来搭建,记录下备查也供有需之人参考。

1.开发环境
  • MacOS :11.3.1 ,JAVA环境,windows/linux应该也无大差异
  • jdk :Amazon Corretto 11 ,jdk其他发行版应该也无差异
  • maven : 3.8.1 , 最好修改阿里仓库镜像
  • IDE : idea 2021.1.1 ,其他版本或者顺手的IDE也行
2.activiti版本问题
  • activiti的源自jBPM,分支版本有flowable、camunda等

  • 主要经历了三个大版本,分别是 5.x 6.x 7.x。目前(2021-05-21)的最新版本如下:

  • 在maven中央仓库中有的最新版本是7.1.0.M6
    https://mvnrepository.com/artifact/org.activiti/activiti-spring-boot-starter/7.1.0.M6

  • 官网最新版本是7.1.0-M12
    https://activiti.gitbook.io/activiti-7-developers-guide/releases/7.1.0-m12

  • github最新版本是7.1.0-M13
    https://github.com/Activiti/Activiti/releases/tag/7.1.0-M13 要在项目中用的话clone下来make install,本测试就尝试最新的。

    注意后两个版本M前面是短杆不是点,别眼残

3.activiti基本概念
  • bpmn 2.0 概念问度娘;

  • 流程定义文件 ProcessDefinition File
    就是在bpmn2.0规范下用工具建立的流程模板文件,一般是".bpmn"或者".bpmn20.xml",本质上就是个xml文件。

  • 流程定义文件常见编辑工具:
    a. Eclipse+插件,插件地址 http://www.activiti.org/designer/update ,现在可用没细测;

    b. idea早期版本(2020.1之前?)+插件(actibpm.jar),插件很久没维护,有bug新版的idea不可用;

    c. bpmnjs.io,https://bpmn.io/toolkit/bpmn-js/ 可在线编辑,但默认生成的camunda格式流程定义文件与activiti不兼容;

    d.基于bpmnjs修改后的工具之一:https://github.com/activiti/activiti-modeling-app ,没细研究

    e.基于bpmnjs修改后的工具之二:activiti cloud modeler,下面详细介绍。

  • 部署 Deploy
    简单讲就是把流程定义文件加载的数据库中,将xml文件存入数据库中,并解析到相关的数据表中。一次部署操作会在activiti的数据库中保留一条部署记录。

  • 流程定义 ProcessDefinition
    流程定义的xml文件加载到activiti解析后插入table中的流程定义内容,一般一次一个流程定义文件部署会生成一条流程定义记录,一次部署一个zip若有多个定义文件就是多条记录。

    部署与流程定义中的一条记录是逻辑概念,实际上设计到一系列Table记录。

  • 流程实例 ProcessInstance
    就是从特定流程定义记录复制出来的一组记录,可以类比理解为java的类创建的对象。

  • 任务 Task
    通俗点说一个流程中的一个需要处理的节点成为一个任务。

  • 网关与事件暂不展开

4.activiti cloud modeler环境准备

参考:https://activiti.gitbook.io/activiti-7-developers-guide/getting-started/getting-started-activiti-cloud/getting-started-docker-compose

  • 安装docker以及docker-compose,安装最新好了,具体操作简单,略
  • 获得activiti-cloud-examples的docker-compose文件
cd ~
git clone https://github.com/Activiti/activiti-cloud-examples
cd activiti-cloud-examples/docker-compose

无git就安装git或者到 https://github.com/Activiti/activiti-cloud-examples 下载zip放到一个地方解压

  • 修改隐藏文件 .evn
    把 DOCKER_IP 设成Docker本机IP,不能用localhost或者127.0.0.1

  • 拉取并启动 activiti cloud modeler

make modeler
  • 查看logs
make logs
  • 查看结果
make ps
或
docker-compose ps

注意state应该都是 up 就没问题了,注意别跟自己之前的docker镜像混淆,实在不行就找台干净的机器(虚拟机4C8G也行)安装一个docker

  • 打开浏览器,打开
http://$DOCKER_IP/modeling

DOCKER_IP就是刚才.env 中设置的ip

账号/密码:modeler / password

  • 登录后就可以新建项目、流程定义模型,可保存服务器,也可下载到本地
5.springboot 2.5.0+Activiti 7.1.0-M13开发环境
  • activiti 7.1.0-M13编译并安装到本地库
git  clone https://github.com/Activiti/Activiti.git
cd Activiti
git checkout 7.1.0-M13
mvn -T 1C clean install -Dmaven.test.skip=true

上个厕所喝喝水撩撩妹…好了

  • pom.xml
    关键是引入activiti-spring-boot-starter,与其他springboot项目无大差异,依赖一切从简,与activiti核心内容无关的东西比方数据、连接池、丝袜哥…以后再说.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>M13-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>M13-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <!--    先到github上下载 tag M13的源代码,make install到本地库即可使用-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.activiti</groupId>
                <artifactId>activiti-dependencies</artifactId>
                <version>7.1.0-M13</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  • application.yml
    默认用的是H2数据库,把history相关的配置打开。
spring:
  activiti:
    database-schema-update: true
    history-level: full
    db-history-used: true
  • 建立bpmn文件
    不会画,就把一下内容存成askleave.bpmn保存在src/main/resources/processes目录下,这个目录下的流程定义文件会自动部署;网上旧教程说还要一个同名的图片文件(svg/gif/png等)非必须。
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:activiti="http://activiti.org/bpmn" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
  <bpmn2:process id="askleave_0521" name="测试请假流程" isExecutable="true" activiti:versionTag="2020-05-21">
    <bpmn2:startEvent id="StartEvent_1">
      <bpmn2:outgoing>Flow_0tygt5u</bpmn2:outgoing>
    </bpmn2:startEvent>
    <bpmn2:sequenceFlow id="Flow_0tygt5u" sourceRef="StartEvent_1" targetRef="Activity_1uc6eb0" />
    <bpmn2:sequenceFlow id="Flow_0ignfmw" sourceRef="Activity_1uc6eb0" targetRef="Activity_09d8n3f" />
    <bpmn2:endEvent id="Event_152h2p6">
      <bpmn2:incoming>Flow_0kpr5l9</bpmn2:incoming>
    </bpmn2:endEvent>
    <bpmn2:sequenceFlow id="Flow_0kpr5l9" sourceRef="Activity_18slzcd" targetRef="Event_152h2p6" />
    <bpmn2:sequenceFlow id="Flow_0bhmsuw" sourceRef="Activity_0b7lmys" targetRef="Activity_18slzcd" />
    <bpmn2:userTask id="Activity_1uc6eb0" name="发起请假" activiti:assignee="user1">
      <bpmn2:extensionElements>
        <activiti:taskListener class="com.example.m13demo.DefaultTaskListener" event="complete" />
      </bpmn2:extensionElements>
      <bpmn2:incoming>Flow_0tygt5u</bpmn2:incoming>
      <bpmn2:outgoing>Flow_0ignfmw</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:userTask id="Activity_09d8n3f" name="经理审核" activiti:assignee="user2">
      <bpmn2:extensionElements>
        <activiti:taskListener class="com.example.m13demo.DefaultTaskListener" event="complete" />
      </bpmn2:extensionElements>
      <bpmn2:incoming>Flow_0ignfmw</bpmn2:incoming>
      <bpmn2:outgoing>Flow_18fmv1a</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:userTask id="Activity_18slzcd" name="人事备案" activiti:assignee="user3">
      <bpmn2:extensionElements>
        <activiti:taskListener class="com.example.m13demo.DefaultTaskListener" event="complete" />
      </bpmn2:extensionElements>
      <bpmn2:incoming>Flow_0bhmsuw</bpmn2:incoming>
      <bpmn2:incoming>Flow_0o85xnk</bpmn2:incoming>
      <bpmn2:outgoing>Flow_0kpr5l9</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:userTask id="Activity_0b7lmys" name="总监加签(&#62;3天加签)" activiti:assignee="user4">
      <bpmn2:extensionElements>
        <activiti:taskListener class="com.example.m13demo.DefaultTaskListener" event="complete" />
      </bpmn2:extensionElements>
      <bpmn2:incoming>Flow_0hgmw2q</bpmn2:incoming>
      <bpmn2:outgoing>Flow_0bhmsuw</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:exclusiveGateway id="Gateway_1jblyzc" name="请假天数判断">
      <bpmn2:incoming>Flow_18fmv1a</bpmn2:incoming>
      <bpmn2:outgoing>Flow_0hgmw2q</bpmn2:outgoing>
      <bpmn2:outgoing>Flow_0o85xnk</bpmn2:outgoing>
    </bpmn2:exclusiveGateway>
    <bpmn2:sequenceFlow id="Flow_18fmv1a" sourceRef="Activity_09d8n3f" targetRef="Gateway_1jblyzc" />
    <bpmn2:sequenceFlow id="Flow_0hgmw2q" sourceRef="Gateway_1jblyzc" targetRef="Activity_0b7lmys">
      <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${days&gt;3}</bpmn2:conditionExpression>
    </bpmn2:sequenceFlow>
    <bpmn2:sequenceFlow id="Flow_0o85xnk" sourceRef="Gateway_1jblyzc" targetRef="Activity_18slzcd">
      <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${days&lt;=3}</bpmn2:conditionExpression>
    </bpmn2:sequenceFlow>
  </bpmn2:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="askleave_0521">
      <bpmndi:BPMNEdge id="Flow_0o85xnk_di" bpmnElement="Flow_0o85xnk">
        <di:waypoint x="875" y="258" />
        <di:waypoint x="1020" y="258" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0hgmw2q_di" bpmnElement="Flow_0hgmw2q">
        <di:waypoint x="850" y="283" />
        <di:waypoint x="850" y="410" />
        <di:waypoint x="920" y="410" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_18fmv1a_di" bpmnElement="Flow_18fmv1a">
        <di:waypoint x="760" y="258" />
        <di:waypoint x="825" y="258" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0bhmsuw_di" bpmnElement="Flow_0bhmsuw">
        <di:waypoint x="1020" y="410" />
        <di:waypoint x="1070" y="410" />
        <di:waypoint x="1070" y="300" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0kpr5l9_di" bpmnElement="Flow_0kpr5l9">
        <di:waypoint x="1120" y="258" />
        <di:waypoint x="1222" y="258" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0ignfmw_di" bpmnElement="Flow_0ignfmw">
        <di:waypoint x="600" y="258" />
        <di:waypoint x="660" y="258" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0tygt5u_di" bpmnElement="Flow_0tygt5u">
        <di:waypoint x="448" y="258" />
        <di:waypoint x="500" y="258" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
        <dc:Bounds x="412" y="240" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Event_152h2p6_di" bpmnElement="Event_152h2p6">
        <dc:Bounds x="1222" y="240" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_1kt3ty0_di" bpmnElement="Activity_1uc6eb0">
        <dc:Bounds x="500" y="218" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_1whlvpd_di" bpmnElement="Activity_09d8n3f">
        <dc:Bounds x="660" y="218" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_1qrfa9a_di" bpmnElement="Activity_18slzcd">
        <dc:Bounds x="1020" y="218" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_11kt2e3_di" bpmnElement="Activity_0b7lmys">
        <dc:Bounds x="920" y="370" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Gateway_1jblyzc_di" bpmnElement="Gateway_1jblyzc" isMarkerVisible="true">
        <dc:Bounds x="825" y="233" width="50" height="50" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="818" y="203" width="66" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn2:definitions>

  • Springboot Security相关简化配置
    Activiti新API以及complete相关操作需要鉴权,Activiti默认集成了Springboot Security
    DemoSecurityConfiguration.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;

@Configuration
public class DemoSecurityConfiguration {

    private Logger logger = LoggerFactory.getLogger(DemoSecurityConfiguration.class);

    @Bean
    public UserDetailsService myUserDetailsService() {

        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();

        String[][] usersGroupsAndRoles = {
                {"user1", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"user2", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"user3", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"user4", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"user5", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
                {"user6", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
                {"user7", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
                {"system", "password", "ROLE_ACTIVITI_USER"},
                {"admin", "password", "ROLE_ACTIVITI_ADMIN"},
        };

        for (String[] user : usersGroupsAndRoles) {
            List<String> authoritiesStrings = asList(Arrays.copyOfRange(user, 2, user.length));
            logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");
            inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]),
                    authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
        }

        return inMemoryUserDetailsManager;
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

SecurityUtil.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
public class SecurityUtil {

    private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);

    @Autowired
    private UserDetailsService userDetailsService;

    public void logInAs(String username) {

        UserDetails user = userDetailsService.loadUserByUsername(username);
        if (user == null) {
            throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
        }
        logger.info("> Logged in as: " + username);
        SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return user.getAuthorities();
            }

            @Override
            public Object getCredentials() {
                return user.getPassword();
            }

            @Override
            public Object getDetails() {
                return user;
            }

            @Override
            public Object getPrincipal() {
                return user;
            }

            @Override
            public boolean isAuthenticated() {
                return true;
            }

            @Override
            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {

            }

            @Override
            public String getName() {
                return user.getUsername();
            }
        }));
        org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
    }
}

  • 监听器类
    本流程定义文件每个task都加了任务监听器
    DefaultTaskListener.java
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultTaskListener implements TaskListener {
    private Logger logger = LoggerFactory.getLogger(DefaultTaskListener.class);

    @Override
    public void notify(DelegateTask delegateTask) {
        logger.info("任务监听器-流程实例ID: " + delegateTask.getProcessInstanceId()
                + " 执行人: " + delegateTask.getAssignee());
    }

}

  • 完整测试用例–Activiti 6.x的写法
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootTest
public class AskLeaveTest_6x_full {
    private Logger logger = LoggerFactory.getLogger(AskLeaveTest_6x_full.class);

    @Autowired
    TaskService taskService;
    @Autowired
    HistoryService historyService;
    @Autowired
    RepositoryService repositoryService;
    @Autowired
    RuntimeService runtimeService;
    @Autowired
    ManagementService managementService;
    @Autowired
    SecurityUtil securityUtil;

    @Test
    @Tag("test")
    public void askLeaveTest() {
        // 外系统的关联单号
        String businessKey = "ASK00001";
        //流程定义的key
        String processDefinitionKey = "askleave_0521";

        String days = "4";
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("days", days);

        //启动一个流程实例
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(processDefinitionKey, businessKey,variables);
        logger.info(processInstance + " 流程启动成功");

        //user1 查找任务
        days = "2";
        String assignee = "user1";
        securityUtil.logInAs(assignee);
        Task task = taskService
                .createTaskQuery()
                .processDefinitionKey(processDefinitionKey)
                .taskAssignee(assignee)
                .list().get(0);
        //user1 处理任务,即填写请假单,把天数变量赋值 >3 天总监要加签
        variables.put("days", days);
        taskService.complete(task.getId(), variables); //set variables有效 !!
        logger.info(task.getId() + " 请假单填写完成,请教天数:" + days);

        //主管 查找并处理任务
        assignee = "user2";
        securityUtil.logInAs(assignee);
        List<Task> taskList = taskService
                .createTaskQuery()
                .processInstanceId(processInstance.getId()) //指定实例ID
                .taskAssignee(assignee) //指定用户
                .list();
        if (taskList.size() > 0) {
            task = taskList.get(0);
            taskService.complete(task.getId(), variables);
            logger.info(task.getId() + " 主管审批完成");
        } else {
            logger.info("主管无任务可审核");
        }

        //总监 查找并处理任务 判断是否需要加签
        assignee = "user4";
        securityUtil.logInAs(assignee);
        taskList = taskService
                .createTaskQuery()
                .processInstanceId(processInstance.getId()) //指定实例ID
                .taskAssignee(assignee) //指定用户
                .list();
        if (taskList.size() > 0) {
            task = taskList.get(0);
            taskService.complete(task.getId(), variables);
            logger.info(task.getId() + " 总监加签完成");
        } else {
            logger.info("总监无任务可加签");
        }

        //人事 查找并处理任务 归档
        assignee = "user3";
        securityUtil.logInAs(assignee);
        taskList = taskService
                .createTaskQuery()
                .processInstanceId(processInstance.getId()) //指定实例ID
                .taskAssignee(assignee) //指定用户
                .list();
        if (taskList.size() > 0) {
            task = taskList.get(0);
            taskService.complete(task.getId(), variables);
            logger.info(task.getId() + " 人事归档完成");
        } else {
            logger.info("人事无任务可归档");
        }

        //查看历史记录
        String processInstanceId = processInstance.getId();
        List<HistoricTaskInstance> HisList = historyService
                .createHistoricTaskInstanceQuery()
                .orderByHistoricTaskInstanceEndTime().desc()
                .processInstanceId(processInstanceId) //流程实例ID条件
                .list();
        logger.info("");
        for (HistoricTaskInstance hi : HisList) {
            logger.info("=============================================================");
            logger.info("getId                 :" + hi.getId());
            logger.info("getProcessDefinitionId:" + hi.getProcessDefinitionId());
            logger.info("getProcessInstanceId  :" + hi.getProcessInstanceId());
            logger.info("getName               :" + hi.getName());
            logger.info("getStartTime          :" + hi.getStartTime());
            logger.info("getEndTime            :" + hi.getEndTime());
        }
    }


}


  • 完整测试用例–Activiti 7.x的写法
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootTest
public class AskLeaveTest_7x_full {
    private Logger logger = LoggerFactory.getLogger(AskLeaveTest_7x_full.class);

    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private ProcessRuntime processRuntime;
    @Autowired
    private TaskRuntime taskRuntime;
    @Autowired
    HistoryService historyService;

    @Test
    @Tag("test")
    public void askLeaveTest() {
        // 外系统的关联单号
        String businessKey = "ASK00001";
        String days = "5";
        //流程定义的key
        String processDefinitionKey = "askleave_0521";
        //启动一个流程实例
        ProcessInstance processInstance = processRuntime.start(
                ProcessPayloadBuilder.start()
                        .withProcessDefinitionKey(processDefinitionKey) //key多次部署可能重复,需要加限定条件
                        .withName(processDefinitionKey + "流程实例名称")
                        .withVariable("days", 0) //初始化参数0,若流程定义中没有默认值会出错
                        .withBusinessKey(businessKey)
                        .build()
        );

        //user1 查找任务
        String assignee = "user1";

        securityUtil.logInAs(assignee);
        Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 100));

        if (tasks.getContent().size() > 0) {
            Task task = tasks.getContent().get(0);
            //user1 处理任务,即填写请假单,把天数变量赋值 >3 天总监要加签
            //null 则为 候选任务,需要现claim
            if (task.getAssignee() == null) {
                taskRuntime.claim(TaskPayloadBuilder.claim()
                        .withTaskId(task.getId())
                        .build());
            }
            //处理任务并设置变量

            taskRuntime.complete(TaskPayloadBuilder
                    .complete()
                    .withTaskId(task.getId())
                    .build());
        } else {
            logger.info(assignee + " 无任务可审核");
        }

        //user2 主管查找任务并处理任务
        assignee = "user2";
        securityUtil.logInAs(assignee);
        tasks = taskRuntime.tasks(Pageable.of(0, 100));

        if (tasks.getContent().size() > 0) {
            Task task = tasks.getContent().get(0);
            //null 则为 候选任务,需要现claim
            if (task.getAssignee() == null) {
                taskRuntime.claim(TaskPayloadBuilder.claim()
                        .withTaskId(task.getId())
                        .build());
            }
            //处理任务
            Map<String, Object> variables = new HashMap<String, Object>();
            variables.put("days", days);

            /**
             * bug 怀疑是 TaskRuntimeImpl.java 164行 那个 true应该是false
             */
            taskRuntime.complete(TaskPayloadBuilder
                    .complete()
                    .withTaskId(task.getId())
                    .withVariable("days", days) //bug?无效?
                    //.withVariables(variables) //bug?无效?
                    .build());

        } else {
            logger.info(assignee + " 无任务可审核");
        }

        //user4 总监查找任务并处理任务
        assignee = "user4";
        securityUtil.logInAs(assignee);
        tasks = taskRuntime.tasks(Pageable.of(0, 100));

        if (tasks.getContent().size() > 0) {
            Task task = tasks.getContent().get(0);
            //null 则为 候选任务,需要现claim
            if (task.getAssignee() == null) {
                taskRuntime.claim(TaskPayloadBuilder.claim()
                        .withTaskId(task.getId())
                        .build());
            }

            //处理任务
            taskRuntime.complete(TaskPayloadBuilder
                    .complete()
                    .withTaskId(task.getId())
                    .build());
        } else {
            logger.info(assignee + " 无任务可审核");
        }

        //user3 人事查找任务并处理任务
        assignee = "user3";
        securityUtil.logInAs(assignee);
        tasks = taskRuntime.tasks(Pageable.of(0, 100));

        if (tasks.getContent().size() > 0) {
            Task task = tasks.getContent().get(0);
            //null 则为 候选任务,需要现claim
            if (task.getAssignee() == null) {
                taskRuntime.claim(TaskPayloadBuilder.claim()
                        .withTaskId(task.getId())
                        .build());
            }
            //处理任务
            taskRuntime.complete(TaskPayloadBuilder
                    .complete()
                    .withTaskId(task.getId())
                    .build());
        } else {
            logger.info(assignee + " 无任务可审核");
        }

        //查看历史记录
        String processInstanceId = processInstance.getId();
        List<HistoricTaskInstance> HisList = historyService
                .createHistoricTaskInstanceQuery()
                .orderByHistoricTaskInstanceEndTime().desc()
                .processInstanceId(processInstanceId) //流程实例ID条件
                .list();
        logger.info("");
        for (HistoricTaskInstance hi : HisList) {
            logger.info("=============================================================");
            logger.info("getId                 :" + hi.getId());
            logger.info("getProcessDefinitionId:" + hi.getProcessDefinitionId());
            logger.info("getProcessInstanceId  :" + hi.getProcessInstanceId());
            logger.info("getName               :" + hi.getName());
            logger.info("getStartTime          :" + hi.getStartTime());
            logger.info("getEndTime            :" + hi.getEndTime());
        }
    }


}
5.完整源代码

https://github.com/dgatiger/activiti_m13_springboot_2.5_demo.git
大家愉快地去玩吧~~

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值