工作流引擎Flowable

1.Flowable基础

官方手册

1.1 入门学习

一、依赖

<dependencies>
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-engine</artifactId>
        <version>6.3.0</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
    </dependency>
    
	<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.21</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.21</version>
    </dependency>

</dependencies>

二、demo

@Test
public void testProcessEngine() {
    // 获取config对象
    ProcessEngineConfiguration config = new StandaloneProcessEngineConfiguration();
    // 设置数据库连接
    config.setJdbcDriver("com.mysql.cj.jdbc.Driver");
    config.setJdbcUsername("root");
    config.setJdbcPassword("root");
    // 注意:要添加nullCatalogMeansCurrent=true属性, 不然会执行报错
    config.setJdbcUrl("jdbc:mysql://localhost:3306/flowable?serverTimezone=UTC&nullCatalogMeansCurrent=true");
    // 如果数据库中的表结构不存在, 就新建
    config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
    // 构建我们需要的对象
    ProcessEngine processEngine = config.buildProcessEngine();
}

三、日志文件
在resources中添加日志文件log4j.properties

log4j.rootLogger=DEBUG, CA

log4j.appender.CA=org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout=org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n

2.流程图设计器

Flowable流程图

  • Eclipse Designer, 一款Eclipse插件, 用于图形化建模, 测试与部署BPMN2.0流程
  • FlowableUI
  • Flowable BPMN visualizer, 一款idea插件

2.1 FlowableUI

从官网下载flowable-6.7.2.zip解压后, 可以看到如下两个文件
在这里插入图片描述
将这两个文件, 扔到tomcat中, 并启动tomcat
在这里插入图片描述
之后我们就可以在网页中访问FlowableUI
访问地址
账户密码:admin/test
在这里插入图片描述

2.2 绘制流程图

一、创建流程
创建一个简单的请假流程
开始 > 提交审核流程(user1) > 总经理审批流程(user2) > 结束
在这里插入图片描述
二、部署
绘制好的流程图, 我们只需要一键导出即可(BPMN文件)
在这里插入图片描述
下载后, 将文件复制到idea中
在这里插入图片描述

private ProcessEngineConfiguration config;

    @Before
    public void before() {
        // 获取config对象
        config = new StandaloneProcessEngineConfiguration();
        // 设置数据库连接
        config.setJdbcDriver("com.mysql.cj.jdbc.Driver");
        config.setJdbcUsername("root");
        config.setJdbcPassword("root");
        // 注意:要添加nullCatalogMeansCurrent=true属性, 不然会执行报错
        config.setJdbcUrl("jdbc:mysql://localhost:3306/flowable?serverTimezone=UTC&nullCatalogMeansCurrent=true");
        // 如果数据库中的表结构不存在, 就新建
        config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
    }

    /**
     * 部署流程
     */
    @Test
    public void testDeploy() {
        // 1.获取ProcessEngine对象
        ProcessEngine processEngine = config.buildProcessEngine();
        // 2.获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 3.完成流程的部署操作
        repositoryService.createDeployment()
                .addClasspathResource("MyHolidayUI.bpmn20.xml")
                .name("请求流程")
                .deploy();
    }
}

执行部署后, 我们回头看看表中的数据
act_re_procdef
act_re_deployment

三、执行流程

@Test
public void testRun() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = config.buildProcessEngine();
    // 2.获取runtimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 3.启动流程实例
    ProcessInstance processInstance = runtimeService.startProcessInstanceById("MyHolidayUI:1:4");
    System.out.println("processDefinitionId = " + processInstance.getProcessDefinitionId());
    System.out.println("activityId = " + processInstance.getActivityId());
    System.out.println("id = " + processInstance.getId());
}

在这里插入图片描述
可以通过act_ru_task查看任务流程
在这里插入图片描述
在这里插入图片描述

四、用户处理
user1对任务进行处理

@Test
public void testCompleteTask() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = config.buildProcessEngine();
    // 2.获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3.获取任务
    Task task = taskService.createTaskQuery()
            .processDefinitionKey("MyHolidayUI")
            .taskAssignee("user1")
            .singleResult();
    // 4.创建流程变量
    HashMap<String, Object> map = new HashMap<>();
    map.put("approved", false);
    // 5.完成任务
    taskService.complete(task.getId(), map);
}

处理完成后, 任务就流转到user2了
在这里插入图片描述
user2对任务进行处理

@Test
public void testCompleteTask() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = config.buildProcessEngine();
    // 2.获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3.获取任务
    Task task = taskService.createTaskQuery()
            .processDefinitionKey("MyHolidayUI")
            .taskAssignee("user2")
            .singleResult();
    // 4.创建流程变量
    HashMap<String, Object> map = new HashMap<>();
    map.put("approved", false);
    // 5.完成任务
    taskService.complete(task.getId(), map);
}

user2处理完成后, 任务就结束了, 在task表中就删除了这条记录
在这里插入图片描述
五、查看历史信息

@Test
public void testHistory() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = config.buildProcessEngine();
    // 2.获取historyService
    HistoryService historyService = processEngine.getHistoryService();
    // 3.获取任务
    List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
            .processDefinitionId("MyHolidayUI:1:4")
            .finished() // 查询已完成的历史记录
            .orderByHistoricActivityInstanceEndTime().asc() // 指定排序的字段
            .list();

    for (HistoricActivityInstance history : list) {
        System.out.println(history.getActivityName() + ":" + history.getAssignee() + "--"
                + history.getActivityId() + ":" + history.getDurationInMillis() + "毫秒");
    }
}

3.Flowable高级

3.2 启动流程

/**
 * 部署流程
*/
@Test
public void testRunInstance() {
   // 1.获取ProcessEngine对象
   ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
   // 2.获取runtimeService, 通过runtimeService来启动流程实例
   RuntimeService runtimeService = engine.getRuntimeService();
   // 3.启动流程实例
   Map<String, Object> variables = new HashMap<>();
   variables.put("name", "张三"); // 谁申请请假
   variables.put("days", 3); // 请几天
   variables.put("reason", "世界这么大, 我想出去转转");   // 请假理由

   ProcessInstance processInstance = runtimeService.startProcessInstanceById("MyHolidayUI:1:4", "order10001", variables);
   System.out.println("processDefinitionId = " + processInstance.getProcessDefinitionId());
   System.out.println("activityId = " + processInstance.getActivityId());
   System.out.println("id = " + processInstance.getId());
}
  • ACT_RU_EXECUTION 运行时流程执行实例
  • ACT_RU_IDENTITYLINK 运行时用户关系信息
  • ACT_RU_TASK 运行时任务表
  • ACT_RU_VARIABLE 运行时变量表

一、ACT_RU_EXECUTION
(1) 启动伊始, 会初始化两条记录, 其中第一条记录的PARENT_ID_为空, 这意味着这条记录是流程实例的第一个节点, ROOT_PROC_INST_ID_会记录这个节点的ID_, BUSINESS_KEY_是运行时添加的参数在这里插入图片描述
二、ACT_RU_IDENTITYLINK
(1) USER_ID_ 代表当前节点用户
(2) TYPE_ 为participant代表用户为参与者

  • assignee 指派人
  • candidate 候选人
  • owner 拥有者
  • starter 启动者
  • participant 参与者
    在这里插入图片描述
    三、ACT_RU_VARIABLE
    流程变量信息
    在这里插入图片描述

3.3 处理流程

@Test
public void testCompleteTask() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3.获取任务
    Task task = taskService.createTaskQuery()
            .processInstanceId("20001")
            .taskAssignee("user1")
            .singleResult();

    // 获取当前流程实例绑定的流程变量
    Map<String, Object> processVariables = task.getProcessVariables();
    for (String key : processVariables.keySet()) {
        System.out.println(key + ":" + processVariables.get(key));
    }

    // 4.创建流程变量
    processVariables.put("approved", true);
    // 覆盖之前的流程变量
    processVariables.put("days", 10);

    // 5.完成任务
    taskService.complete(task.getId(), processVariables);
}

因为我们一个流程定义启动了两个流程实例, 所以我们如果通过流程定义的key(MyHolidayUI)来处理的话, 就会处理所有流程定义下的进程

正确的方式: 我们找到想要处理的流程实例, 获取到流程实例的唯一编号(ROOT_PROC_INST_ID_), 然后指定相应用户处理流程
在这里插入图片描述
在这里插入图片描述

3.4 流程结束

@Test
public void testCompleteTask() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3.获取任务
    Task task = taskService.createTaskQuery()
            .processInstanceId("20001")
            .taskAssignee("user2")
            .singleResult();
            
    // 4.结束任务
    taskService.complete(task.getId(), processVariables);
}

结束任务之后, 在_RU表中, 所有跟此流程实例的相关的数据都会删除

  • ACT_RU_EXECUTION 运行时流程执行实例
  • ACT_RU_IDENTITYLINK 运行时用户关系信息
  • ACT_RU_TASK 运行时任务表
  • ACT_RU_VARIABLE 运行时变量表

那么如果我们想要查看历史记录, 怎么办

  • act_hi_taskinst
  • act_hi_varinst
  • act_hi_identitylink
  • act_hi_procinst
  • act_hi_actinst

3.5 任务分配

之前我们任务分配, 是在流程定义时, 就将用户写死了, 例如user1、user2, 在实际开发过程中, 我们并不会使用这个方式

3.5.1 表达式分配

Flowable使用UEL进行表达式解析, Flowable支持两种表达式:

  • UEL-value (值表达式)
  • UEL-method (方法表达式)

在这里插入图片描述
在启动流程实例时, 添加参数

@Test
public void testDeploy() {
    // 1.获取processEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取repositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成流程的部署操作
    Deployment deploy = repositoryService.createDeployment()
            .addClasspathResource("HolidayNew.bpmn20.xml")
            .name("请求流程")
            .deploy();
    System.out.println("deploy.getId() = " + deploy.getId());
    System.out.println("deploy.getName() = " + deploy.getName());
}

@Test
public void testRun() {
    // 1.获取ProcessEngine对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取runtimeService, 通过runtimeService来启动流程实例
    RuntimeService runtimeService = engine.getRuntimeService();

    Map<String, Object> variables = new HashMap<>();
    variables.put("assignee0", "张三");
    variables.put("assignee1", "李四");
	
	// 开启流程实例时, 设置参数
    ProcessInstance processInstance = runtimeService.startProcessInstanceById("holiday-new:1:27504", variables);
    System.out.println("processDefinitionId = " + processInstance.getProcessDefinitionId());
    System.out.println("activityId = " + processInstance.getActivityId());
    System.out.println("id = " + processInstance.getId());
}

在这里插入图片描述

3.5.2 监听器分配

在这里插入图片描述
配置监听器
在这里插入图片描述

3.6 流程变量

支持的流程变量有两类

  • 全局变量: 主要跟流程实例相关
  • 局部变量: 主要跟task相关

3.6.1 实例关系

  • 一个流程定义可以创建多个流程实例
    在这里插入图片描述

3.6.2 出差流程图

一、全局变量
全部变量, 设置相同名称的变量后, 后设置的会覆盖前面设置的变量值

二、局部变量
local变量由于在不同的任务或者不同的执行实例中, 作用域互不影响, 变量名可以相同没有影响, local变量名也可以和全局变量名相同

3.6.3 案例

  • 如果${num>=3}则需要总经理审批
  • 如果${num<3}则不需要总经理审批

在这里插入图片描述
一、流程定义

@Test
public void testDeploy() {
    // 1.获取processEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取repositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成流程的部署操作
    Deployment deploy = repositoryService.createDeployment()
            .addClasspathResource("出差申请单.bpmn20.xml")
            .name("出差申请单")
            .deploy();
    System.out.println("deploy.getId() = " + deploy.getId());
    System.out.println("deploy.getName() = " + deploy.getName());
}

流程定义表: act_re_procdef
在这里插入图片描述

二、创建流程实例

@Test
public void testRun() {
    // 1.获取ProcessEngine对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取runtimeService, 通过runtimeService来启动流程实例
    RuntimeService runtimeService = engine.getRuntimeService();

    Map<String, Object> variables = new HashMap<>();
    variables.put("assignee0", "张三");
    variables.put("assignee1", "李四");
    variables.put("assignee2", "王五");
    variables.put("assignee3", "赵六");

    ProcessInstance processInstance = runtimeService.startProcessInstanceById("evection:1:32504", variables);
    System.out.println("processDefinitionId = " + processInstance.getProcessDefinitionId());
    System.out.println("activityId = " + processInstance.getActivityId());
    System.out.println("id = " + processInstance.getId());
}

查看当前节点走到哪儿
在这里插入图片描述

3.7 候选人

3.7.1 流程定义和部署

在这里插入图片描述
在这里插入图片描述

@Test
public void testRun() {
    // 1.获取ProcessEngine对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取runtimeService, 通过runtimeService来启动流程实例
    RuntimeService runtimeService = engine.getRuntimeService();

    Map<String, Object> variables = new HashMap<>();
    variables.put("candidate1", "张三");
    variables.put("candidate2", "李四");
    variables.put("candidate3", "王五");

    ProcessInstance processInstance = runtimeService.startProcessInstanceById("holiday-candidate:1:40004", variables);
    System.out.println("processDefinitionId = " + processInstance.getProcessDefinitionId());
    System.out.println("activityId = " + processInstance.getActivityId());
    System.out.println("id = " + processInstance.getId());
}

启动项目后, 发现在task表中并没有assignee, 这是因为候选人并非处理人(指派人)
在这里插入图片描述

在这里插入图片描述
虽然我们并没有指派处理人, 但我们知道有三个候选者, 在ru_variable表中也能看到
在这里插入图片描述

3.7.2 候选任务查询

根据当前登录的用户查询对应的, 可以拾取的任务

 @Test
public void queryTaskCandidate() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3.获取任务
    List<Task> list = taskService.createTaskQuery()
            .processInstanceId("42501")
            // 候选人
            .taskCandidateUser("张三")
            .list();

    for (Task task : list) {
        System.out.println("task.getId() = " + task.getId());
        System.out.println("task.getName() = " + task.getName());
    }
}

在这里插入图片描述

3.7.3 拾取、归还、交接等操作

使用张三拾取任务

@Test
public void claimTask() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3.获取任务
    Task task = taskService.createTaskQuery()
            .processInstanceId("42501")
            .taskAssignee("张三")
            .singleResult();

    if (task != null) {
        taskService.claim(task.getId(), "张三");
        System.out.println("任务拾取成功");
    }
}

在这里插入图片描述

  • 一个候选人拾取了这个任务之后, 其他用户就没有办法拾取这个任务了
  • 所以如果一个用户拾取了任务之后不想处理了, 那么可以退还
@Test
public void unclaimTask() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3.获取任务
    Task task = taskService.createTaskQuery()
            .processInstanceId("42501")
            .taskAssignee("张三")
            .singleResult();

    if (task != null) {
        taskService.unclaim(task.getId());
        System.out.println("归还成功");
    }
}
  • 如果我获取了任务, 但是不想执行, 我可以把这个任务交接给其他用户(任务交接)
@Test
public void handoverTask() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3.获取任务
    Task task = taskService.createTaskQuery()
            .processInstanceId("42501")
            .taskAssignee("张三")
            .singleResult();

    if (task != null) {
        taskService.setAssignee(task.getId(), "赵六");
        System.out.println("交接成功");
    }
}

在这里插入图片描述

3.8 候选人组

当候选人很多的情况下, 我们可以分组来处理

流程:
1.先创建组
2.把用户分配到组中

@Test
public void createUser() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 通过identityService完成相关的用户和组的管理
    IdentityService identityService = processEngine.getIdentityService();
    User user = identityService.newUser("田佳");
    user.setFirstName("田");
    user.setLastName("佳");
    user.setEmail("844915283@qq.com");
    identityService.saveUser(user);
}

@Test
public void createGroup() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 通过identityService完成相关的用户和组的管理
    IdentityService identityService = processEngine.getIdentityService();
    // 创建group对象, 并指定相关的信息
    Group group = identityService.newGroup("group1");
    group.setName("销售部");
    group.setType("type1");
    // 创建group对应的表结构数据
    identityService.saveGroup(group);
}

3.8.1 将用户分配给对应的组

@Test
public void userGroup() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    IdentityService identityService = processEngine.getIdentityService();
    // 根据组的编号, 找到对应的group
    Group group = identityService.createGroupQuery().groupId("group1").singleResult();
    List<User> list = identityService.createUserQuery().list();
    list.stream().forEach(t -> {
        identityService.createMembership(t.getId(), group.getId());
    });
}

3.8.2 部署和运行

在这里插入图片描述

@Test
public void testRun() {
    // 1.获取ProcessEngine对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 2.获取runtimeService, 通过runtimeService来启动流程实例
    RuntimeService runtimeService = engine.getRuntimeService();

    // 获取组
    IdentityService identityService = engine.getIdentityService();
    Group group = identityService.createGroupQuery().groupId("group1").singleResult();

    Map<String, Object> variables = new HashMap<>();
    // 给流程定义中的uel表达式赋值
    variables.put("g1", group.getId());

    ProcessInstance processInstance = runtimeService.startProcessInstanceById("group:1:50004", variables);
    System.out.println("processDefinitionId = " + processInstance.getProcessDefinitionId());
    System.out.println("activityId = " + processInstance.getActivityId());
    System.out.println("id = " + processInstance.getId());
}

3.8.3 任务查询拾取处理

拾取处理

@Test
public void queryTaskCandidateGroup() {
    String userId = "王杰";
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    IdentityService identityService = processEngine.getIdentityService();
    // 获取组
    Group group = identityService.createGroupQuery().groupMember(userId).singleResult();

    // 3.获取任务
    TaskService taskService = processEngine.getTaskService();
    Task task = taskService.createTaskQuery()
            .processInstanceId("52501")
            // 候选人
            .taskCandidateGroup(group.getId())
            .singleResult();

    if (task != null) {
        taskService.claim(task.getId(), userId);
    }
}

3.9 网关

控制流程的走向

3.9.1 排他网关

3.9.2 并行网关

3.9.3 包容网关

3.9.4