Flowable 学习章节

官网地址:https://www.flowable.org/ Flowable6.3中文教程:https://tkjohn.github.io/flowable-userguide/ Flowable6.4.2中文教程:http://web.wzhz.xyz/flowable/bpmn/ Flowable6.5.0中文教程: http://www.shareniu.com/flowable6.5_zh_document/bpm/index.html http://jeesite.com/docs/bpm/ https://jeesite.gitee.io/front/flowable/6.5.0/bpmn/ 可以在官网下载对应的jar包在本地部署运行,官方提供了下面的五个应用程序: Flowable Modeler:流程定义管理 Flowable Task:用户任务管理 Flowable IDM:用户组权限管理 Flowable REST API:流程引擎对外提供的API接口 Flowable Admin:后台管理

Activiti与Flowable对比


这里对比 Activiti7Flowable6

对比框架简介

对比框架选择

因此,这里对比activiti7和flowable6

Roadmap对比

Activiti的roadmap

Flowable的roadmap

可以看出:

功能与其他总体对比

公司的个人初步感觉:

结论

附录

Flowable roadmap

Activiti 7 Roadmap

Salaboy

flowable6和activit7的对比中文翻译列表

Flowable6(比activit6多的功能)

Activiti7(比activiti6多的功能)

总结:

  1. Activiti7好像基本叫Activiti Cloud7,专注于cloud的开发,包括与Zuul、Eureka、Zipkin、Sping Cloud、Docker、Kubernetes、ELK、Jenkins(持续集成)等功能;同时还包括一些我们用不到的云方面的功能,包括Cloud Native(云原生?)、KNative(谷歌的serverless开源框架)、Istio(service mesh的一个开源实现)、JHispter(前端的微服务框架?)、AWS(肯定不会与阿里云整合的)等;还有一些我看不懂的云方面的技术名词。

  2. 而Flowable专注于工作流引擎在NoSQL、消息队列的实现,可以完全不用关系型数据库,通过消息队列异步也可以提高效率;还专注于CMMN、DMN等流程规范、规则引擎方面的功能;还有如JUnit5(单元测试)、Jupiter(代码审查)等的功能;未来也打算开发K8s的整合功能。也就是主要专注于工作流引擎核心的功能。

附录:

Activiti7最新开发路线图(中文翻译)

http://www.shareniu.com/article/176.htm

http://www.shareniu.com/article/151.htm

Flowable最新版(6.4/6.3.1/6.3/6.2/6.1.1/6.1) 新特性(中文翻译)

http://www.shareniu.com/article/200.htm

http://www.shareniu.com/article/199.htm

http://www.shareniu.com/article/194.htm

http://www.shareniu.com/article/178.htm

http://www.shareniu.com/article/120.htm

http://www.shareniu.com/article/107.htm

Flowable v5 和v6版本的区别 http://www.shareniu.com/article/85.htm

Flowable 框架


Flowable 框架学习笔记

Flowable 入门介绍

官网地址:flowable.org

Flowable BPMN 用户手册 (v 6.3.0)

Flowable BPMN 用户手册 (v 6.4.2-SNAPSHOT)

Flowable BPMN 用户手册 (v 6.5.0-SNAPSHOT)

可以在官网下载对应的jar包在本地部署运行,官方提供了下面的五个应用程序:

初识Flowable五大引擎

Flowable有五大引擎,每个之间都是相互独立互不影响。

ProcessEngine是里面最核心也是最重要的一个引擎,如果失去它那Flowable也就意义了。

五大引擎

流程引擎使用架构

Flowable引擎在使用前需要先通过配置来初始化ProcessEngine。

初始化ProcessEngineConfiguration一般有两种方式:

  1. 通过Spinrg配置文件进行依赖注入,通过flowable.cfg.xml文件来初始化ProcessEngineConfiguration(这里的文件名必须为flowable.cfg.xml,否则Flowable识别不到)

  2. 通过编写程序的方式来构造ProcessEngineConfiguration对象

流程引擎API架构图

模型图

ProcessEngineConfiguration在初始化过程中会同时初始化数据库,如果数据库已经存在,则不会做创建更新操作,如果数据库不存在,则会默认执行数据库创建脚本。

流程引擎初体验

  1. 简单了解Bpmn

    1. Task任务:

      用户任务(userTask)

      系统任务(serviceTask )

    2. Event事件:

      定时器事件(timerEventDefinition)

    3. Gateway网关:

      排他网关(exclusive gateway)

  2. 目标:实现以下简化版的请假流程

请假流程图(简单版)

Flowable的用户权限体系

在接入Flowable的用户权限体系的时候,有四种方式:

  1. 使用Flowable提供的默认IdmEngine进行用户体系管理,该引擎包含了用户、组的概念。

  2. 集成LDAP,实现轻量级用户权限管理。通过IdentityService进行认证,用于由IdentityService处理所有认证业务的场景。

  3. 实现IdmIdentityService接口,自定义实现用户、组的查询

  4. 接入自定义的权限体系

    用户id => 获取到租户id、角色id集、部门id集

    1. 单用户(assignee="用户id")、多用户(candidateUsers="用户id1,用户id2")

    2. 单角色、多角色(candidateGroups=":角色id1,:角色id2")

    3. 单部门、多部门(candidateGroups="部门id1:,部门id2:")

    4. 角色或部门(candidateGroups="角色id1:, :部门id1")

    5. 角色且部门

使用Flowable工作流引擎的时候,不可避免就需要考虑相应的用户权限,

根据官方文档提供的教程,实现Flowable的用户权限体系总共有两大类:

Flowable提供的IdmEngine身份识别引擎

使用Flowable提供的IdmEngine,也有三种方案:

自定义身份识别引擎

有时候在项目上已经实现了自己的用户体系,接入Flowable的工作流引擎时,就需要考虑如何将自己的用户体系映射到Flowable工作流引擎的数据权限上去。Flowable提供的数据权限体系是用户、组、租户的结构体系,因为这里提供一个映射的设计思路:

在实际项目中,往往用户会存在租户、角色、部门之间的对应关系,因而在对应到Flowable的用户、组、租户的时候可以用以下思路来处理:

用户权限体系映射

具体的案例如下:

用户A(id值为1) - 角色A(id值为1) - 部门A(id值为1) - 区域A(id值为1)(某个租户的用户A是角色A,所在部门为部门A,所属区域为区域A)

根据上述的映射关系转换成如下

将映射关系对应到Flowable中

知道如果映射后,现在的问题就是在哪里去实现这部分的映射关系,以下提供几种实现的思路,目前还没正式验证过,仍处于实验阶段:

其中idmTest.org就是用来自定义返回的id集的方法

数据库介绍(34张表)

ACT_RE_ *:RE代表repository。具有此前缀的表包含静态信息,例如流程定义和流程资源(图像,规则等)。

ACT_RU_ *:RU代表runtime。这些是包含运行时的流程实例,用户任务,变量,作业等的运行时数据的运行时表。Flowable仅在流程实例执行期间存储运行时数据,并在流程实例结束时删除记录。这使运行时表保持小而快。

ACT_HI_ *:HI代表history。这些是包含历史数据的表,例如过去的流程实例,变量,任务等。

ACT_GE_ *:general数据,用于各种用例。

ACT_ID_ *:Idm的用户、组

表分类表名表说明
一般数据(2)ACT_GE_BYTEARRAY通用的流程定义和流程资源
ACT_GE_PROPERTY系统相关属性
流程历史记录(8)ACT_HI_ACTINST历史的流程实例
ACT_HI_ATTACHMENT历史的流程附件
ACT_HI_COMMENT历史的说明性信息
ACT_HI_DETAIL历史的流程运行中的细节信息
ACT_HI_IDENTITYLINK历史的流程运行过程中用户关系
ACT_HI_PROCINST历史的流程实例
ACT_HI_TASKINST历史的任务实例
ACT_HI_VARINST历史的流程运行中的变量信息
用户用户组表(9)ACT_ID_BYTEARRAY二进制数据表
ACT_ID_GROUP用户组信息表
ACT_ID_INFO用户信息详情表
ACT_ID_MEMBERSHIP人与组关系表
ACT_ID_PRIV权限表
ACT_ID_PRIV_MAPPING用户或组权限关系表
ACT_ID_PROPERTY属性表
ACT_ID_TOKEN系统登录日志表
ACT_ID_USER用户表
流程定义表(3)ACT_RE_DEPLOYMENT部署单元信息
ACT_RE_MODEL模型信息
ACT_RE_PROCDEF已部署的流程定义
运行实例表(10)ACT_RU_DEADLETTER_JOB正在运行的任务表
ACT_RU_EVENT_SUBSCR运行时事件
ACT_RU_EXECUTION运行时流程执行实例
ACT_RU_HISTORY_JOB历史作业表
ACT_RU_IDENTITYLINK运行时用户关系信息
ACT_RU_JOB运行时作业表
ACT_RU_SUSPENDED_JOB暂停作业表
ACT_RU_TASK运行时任务表
ACT_RU_TIMER_JOB定时作业表
ACT_RU_VARIABLE运行时变量表
其他表(2)ACT_EVT_LOG事件日志表
ACT_PROCDEF_INFO流程定义信息

ACT_HI_ACTINST 流程实例的历史运行节点表

ACT_HI_TASKINST 流程实例的历史任务表

ACT_HI_VARINST 流程实例的历史运行节点的变量表

ACT_HI_PROCINST 流程历史部署记录

ACT_RE_DEPLOYMENT 流程部署

ACT_RE_PROCDEF 流程定义表

ACT_RU_EXECUTION 流程实例执行过程的所有节点记录

ACT_RU_TASK 流程实例运行时的任务表

ACT_RU_VARIABLE 流程实例运行时节点的变量表

ACT_GE_BYTEARRAY 资源文件表

Activiti Flowable transient变量

Activiti Flowable transient变量

Activiti6 增加了瞬时变量( transient 变量)。接下来展示如何使用 Activiti6 提供的 transient 变量。​

Activiti6 版本之前所有的变量都会持久化到数据库表( act_ru_variable 或者 act_hi_varinst ),变量存储于 act_hi_varinst 表前提是开启了历史数据的归档开关。瞬时变量使用起来跟持久化的变量一样一样的。唯一的区别是他不会持久化到数据库表。下面对瞬时变量进行一些说明:

瞬时变量可以存活下来知道下一步的等待状态,前提是流程实例的数据可以存储到数据库。

瞬态变量隐藏具有相同名称的持久性变量。

1.1 瞬时变量的操作

void setTransientVariable(String variableName, Object variableValue);
void setTransientVariableLocal(String variableName, Object variableValue);
void setTransientVariables(MaptransientVariables);
void setTransientVariablesLocal(MaptransientVariables);

Object getTransientVariable(String variableName);
Object getTransientVariableLocal(String variableName);

MapgetTransientVariables();
MapgetTransientVariablesLocal();

void removeTransientVariable(String variableName);
void removeTransientVariableLocal(String variableName);

接下来演示瞬时变量的使用过程,操作起来非常的简单。

首先定义一个流程文档(名称process.bpmn20.xml),该流程图如下所示:

部署流程文档如下代码所示:

public void addInputStreamTest() throws Exception {
  //定义的文件信息的流读取
  InputStream inputStream = DeploymentBuilderTest.class.getClassLoader().getResourceAsStream("com/shareniu/activiti6/transientvars/process.bpmn20.xml");
  //流程定义的分类
  String category="variabletypeTest";
  DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().category(category).addInputStream("variabletype.bpmn", inputStream);
  //部署
  Deployment deploy = deploymentBuilder.deploy();
}

启动流程实例时,我们传递的变量是定期的变量。他们将保留和审核历史记录会保留,因为实在没有理由为什么这不应该是这样。

流程实例启动完毕之后,会首先执行‘execute HTTP call’,那么会立刻触发ExecuteHttpCallDelegate类中的逻辑,该类的定义如下所示:

public class ExecuteHttpCallDelegate implements JavaDelegate {
    public void execute(DelegateExecution execution) {
            execution.setTransientVariable("response", "分享牛(shareniu.com)");
            execution.setTransientVariable("responseStatus",200);
    }
}

上述的两个变量response和responseStatus都为瞬时变量。

接下来看一下ProcessResponseExecutionListener类的定义如下所示:

public class ProcessResponseExecutionListener implements ExecutionListener {
    public void notify(DelegateExecution execution) {
    	Listlist=new ArrayList<>();
    	list.add("shareniu1");
    	list.add("shareniu2");
        execution.setTransientVariable("searchResults",list );
    }
 
}

Ok,上述工作完毕之后直接启动新的流程实例如下所示:

public void startProcessInstanceById() throws Exception{
  runtimeService.startProcessInstanceById("transient-vars:1:37504");
}

act_ru_variable表的变化如下所示:

看到上图可知response、responseStatus这两个瞬时变量没有被存储,但是searchResults瞬时变量被存储了,那么问题来了,什么时候瞬时变量会转化为持久化变量呢?关于这一点可以参考随后的章节。

Flowable Activiti 会签 多实例

Flowable Activiti 会签 多实例

在实际的业务中,可能存在存在这么一种情况,当流程运行到某一个环节时,可能需要同时多个人的参与,才可以完成此环节。此时就可以用到activiti的多实例来解决此问题。

将一个节点设置成多实例:

要把一个节点设置为多实例,节点xml元素必须设置一个 multiInstanceLoopCharacteristics 子元素。

isSequential=true 时,表示的顺序执行,即虽然该节点有多条任务,但只有上一条执行完,才可以执行下一条。

isSequential=false 时,表示的并行执行,即该节点下的多条任务可以同时执行。

设置会签环节的参与者

activiti:collection:用于执行该会签环节的参与参与的人,此处是使用的一个名叫pers的流程变量

activiti:elementVariable:此处表示的是每一个分支都有一个名叫per的流程变量,和上方的activiti:assignee结合使用就可以执行该分支应该由谁来处理。

指定会签环节的结束条件

当画红线的部分返回一个true的时候,该会签环节结束。进入下一个流程执行的环节.

completionCondition中写的是juel表达式。其中的mulitiInstance如果是和spring整合了,就是spring管理的bean的id,否则就是流程变量的key.

会签环节中涉及的几个默认的流程变量

1.nrOfInstances 该会签环节中总共有多少个实例

2.nrOfActiveInstances 当前活动的实例的数量,即还没有 完成的实例数量。

3.nrOfCompletedInstances 已经完成的实例的数量

代码

1.分配会签环节的人:

/**
 * 分配下一环节会签的人
 * @author huan
 */  
public class AssgineeMultiInstancePer implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) throws Exception {
        System.out.println("设置会签环节的人员.");
        execution.setVariable("pers", Arrays.asList("张三", "李四", "王五", "赵六"));
    }
}

2.多实例判断完成的条件:(注意:有于我没有和spring整合,所以此类要实现Serializable接口

/**
 * 多实例完成的条件判断
 * @author huan
 */
public class MulitiInstanceCompleteTask implements Serializable {
    private static final long serialVersionUID = 1L;
    public boolean completeTask(DelegateExecution execution) {
        System.out.println("总的会签任务数量:" + execution.getVariable("nrOfInstances") + "当前获取的会签任务数量:" + execution.getVariable("nrOfActiveInstances") + " - " + "已经完成的会签任务数量:" + execution.getVariable("nrOfCompletedInstances"));
        System.out.println("I am invoked.");
        return false;
    }
}

3.会签环节中的一个监听器:

/** 
 * 测试会签过程中监听器的执行情况 
 * @author huan 
 */  
public class TestLinstener implements TaskListener {  
    private static final long serialVersionUID = -5754522101489239675L;
    @Override
    public void notify(DelegateTask delegateTask) {
        System.out.print(delegateTask.getId() + " - " + delegateTask.getProcessInstanceId() + " - " + delegateTask.getEventName() + " - " + delegateTask.getTaskDefinitionKey());
    }
}

4.测试代码:

/** 
 * 测试会签 
 * @author huan 
 */  
public class TestMultiInstance {  
    @Test  
    public void testProcess() {  
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
        RepositoryService repositoryService = processEngine.getRepositoryService();  
        RuntimeService runtimeService = processEngine.getRuntimeService();  
        TaskService taskService = processEngine.getTaskService();  
        Deployment deploy = repositoryService.createDeployment()//  
                .name("会签流程测试")//  
                .addInputStream("multiInstances.bpmn", this.getClass().getResourceAsStream("multiInstances.bpmn"))//  
                .addInputStream("multiInstances.png", this.getClass().getResourceAsStream("multiInstances.png"))//  
                .deploy();  
        System.out.println(deploy.getId() + " " + deploy.getName());  
        Map<String, Object> variables = new HashMap<String, Object>();  
        variables.put("mulitiInstance", new MulitiInstanceCompleteTask());  
        ProcessInstance pi = runtimeService.startProcessInstanceByKey("multiInstances",variables);  
        System.out.println(pi.getId() + "  " + pi.getActivityId());  
        Task task1 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("张三").singleResult();  
        System.out.println(task1.getId() + " - " + task1.getAssignee() + " - " + task1.getProcessInstanceId() + " - " + task1.getProcessDefinitionId());  
        Task task2 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("李四").singleResult();  
        System.out.println(task2.getId() + " - " + task2.getAssignee() + " - " + task2.getProcessInstanceId() + " - " + task2.getProcessDefinitionId());  
        Task task3 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("王五").singleResult();  
        System.out.println(task3.getId() + " - " + task3.getAssignee() + " - " + task3.getProcessInstanceId() + " - " + task3.getProcessDefinitionId());  
        Task task4 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("赵六").singleResult();  
        if (task4 != null) {  
            System.out.println(task4.getId() + " - " + task4.getAssignee() + " - " + task4.getProcessInstanceId() + " - " + task4.getProcessDefinitionId());  
        }  
        Task task5 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("钱七").singleResult();  
        System.out.println(task5);  
        taskService.complete(task1.getId());  
        taskService.complete(task2.getId());  
        taskService.complete(task3.getId());  
        Task task6 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("钱七").singleResult();  
        System.out.println(task6);  
        taskService.complete(task4.getId());  
        Task task7 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("钱七").singleResult();  
        System.out.println(task7);  
        taskService.complete(task7.getId());  
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(pi.getId()).singleResult();  
        if (null == processInstance) {  
            System.out.println("流程完成.");  
        }  
    }  
}  

5.流程文件:

<?xml version="1.0" encoding="UTF-8"?>  
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 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" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">  
  <process id="multiInstances" name="流程会签测试" isExecutable="true">  
    <startEvent id="startevent1" name="Start"></startEvent>  
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="A001"></sequenceFlow>  
    <serviceTask id="A001" name="设置下一环节的人" activiti:class="com.huan.activiti.liuyang.会签.AssgineeMultiInstancePer"></serviceTask>  
    <userTask id="B001" name="会签环节" activiti:assignee="${per}">  
      <extensionElements>  
        <activiti:taskListener event="complete" class="com.huan.activiti.liuyang.会签.TestLinstener"></activiti:taskListener>  
      </extensionElements>  
      <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="pers" activiti:elementVariable="per">  
        <completionCondition>${mulitiInstance.completeTask(execution)}</completionCondition>  
      </multiInstanceLoopCharacteristics>  
    </userTask>  
    <sequenceFlow id="flow2" sourceRef="A001" targetRef="B001"></sequenceFlow>  
    <userTask id="C001" name="会签后的环节" activiti:assignee="钱七"></userTask>  
    <sequenceFlow id="flow3" sourceRef="B001" targetRef="C001"></sequenceFlow>  
    <endEvent id="endevent1" name="End"></endEvent>  
    <sequenceFlow id="flow4" sourceRef="C001" targetRef="endevent1"></sequenceFlow>  
  </process>  
  <bpmndi:BPMNDiagram id="BPMNDiagram_multiInstances">  
    <bpmndi:BPMNPlane bpmnElement="multiInstances" id="BPMNPlane_multiInstances">  
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">  
        <omgdc:Bounds height="35.0" width="35.0" x="100.0" y="240.0"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="A001" id="BPMNShape_A001">  
        <omgdc:Bounds height="71.0" width="117.0" x="190.0" y="222.0"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="B001" id="BPMNShape_B001">  
        <omgdc:Bounds height="55.0" width="105.0" x="380.0" y="230.0"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="C001" id="BPMNShape_C001">  
        <omgdc:Bounds height="55.0" width="105.0" x="561.0" y="230.0"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">  
        <omgdc:Bounds height="35.0" width="35.0" x="740.0" y="240.0"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">  
        <omgdi:waypoint x="135.0" y="257.0"></omgdi:waypoint>  
        <omgdi:waypoint x="190.0" y="257.0"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">  
        <omgdi:waypoint x="307.0" y="257.0"></omgdi:waypoint>  
        <omgdi:waypoint x="380.0" y="257.0"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">  
        <omgdi:waypoint x="485.0" y="257.0"></omgdi:waypoint>  
        <omgdi:waypoint x="561.0" y="257.0"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">  
        <omgdi:waypoint x="666.0" y="257.0"></omgdi:waypoint>  
        <omgdi:waypoint x="740.0" y="257.0"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
    </bpmndi:BPMNPlane>  
  </bpmndi:BPMNDiagram>  
</definitions>  

7.流程图:

Flowable6.4 – 会签实现方案

Flowable6.4 – 会签实现方案

之前介绍过多实例,但只是理论上如何实现,本文将介绍一种能够实际应用的会签方案。

前面几篇文章描述过如何为一个UserTask节点增加扩展属性,多实例UserTask节点也可以按照之前的方法增加扩展属性。

但是分配办理人时,与普通的UserTask节点稍有不同,不能直接从UserTask节点的扩展属性内获取办理人信息。

如何获得办理人?

首先, 声明一个辅助处理器,用来帮助获得UserTask节点的办理人和检查是否结束多实例节点的办理。主要代码如下:

/**
 * 获得当前节点的处理者列表
 * @param execution 当前执行实例
 * @return 处理者列表
 */
public List<DealerInfo> getList(DelegateExecution execution) {
    FlowElement flowElement = execution.getCurrentFlowElement();
    UserTask userTask = (UserTask) flowElement;
    UserTaskExtension userTaskExtension = FlowUtil.getUserTaskExtension(userTask);
    //DealerInfo是一个实体,用来表示办理人的具体信息
    //从扩展属性内读取UserTask的办理人信息
    //返回一个集合,该集合的数量影响了UserTask多实例的数量
    return userTaskExtension.getDealers();
}

/**
 * 获取会签是否结束
 * @param execution 当前执行实例
 * @return 是否结束
 */
public boolean getComplete(DelegateExecution execution) {
    Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances");
    Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");
    int agreeCount = 0, rejectCount = 0, abstainCount = 0;
    Map<String, Object> vars = execution.getVariables();
    for (String key : vars.keySet()) {
        //会签投票以SIGN_VOTE+TaskId标识
        //获得会签投票的统计结果
        if (key.contains(FlowConst.SIGN_VOTE) && !key.equals(FlowConst.SIGN_VOTE_RESULT)) {
            Integer value = (Integer) vars.get(key);
            //统计同意、驳回、弃权票数
            //省略代码若干......
        }
    }
    //以下为一段简单的规则,可以按情况实现自己的会签规则
    if (!nrOfCompletedInstances.equals(nrOfInstances)) {
        //必须等所有的办理人都投票
        return false;
    } else {
        //会签全部完成时,使用默认规则结束
        if (rejectCount > 0) {
            //有反对票,则最终的会签结果为不通过
            //移除SIGN_VOTE+TaskId为标识的参数
            removeSignVars(execution);
            //增加会签结果参数,以便之后流转使用
            execution.setVariable(FlowConst.SIGN_VOTE_RESULT, false);
            //会签结束
            return true;
        } else {
            //没有反对票时,则最终的会签结果为通过
            removeSignVars(execution);
            execution.setVariable(FlowConst.SIGN_VOTE_RESULT, true);
            return true;
        }
    }
}

然后, 对流程图内的UserTask节点进行设置:

上图中的Collection设置为:${mutiInstanceHandler.getList(execution)}

上图中的Completion设置为:${mutiInstanceHandler.getComplete(execution)}

一定要注意的是:Element Var,这个参数将决定多实例中每一个UserTask的办理人。

接下来,设置办理人,之前的教程可以参考《Flowable6.4 - 分派办理人》,主要的代码如下:

/**
 * 分配办理人员
 */
@Override
protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution) {
    if (null != this.userTaskExtension) {
        //由扩展属性读取办理人规则
        List<DealerInfo> dealerInfos = getDealerInfo();
        if (null == dealerInfos || dealerInfos.size() == 0) {
            throw new RuntimeException("处理者信息不存在");
        }
        if (hasMultiInstanceCharacteristics())
            //多实例节点分配处理者
            //获得传参,流程图中的Element Var
            Object objDealer = execution.getVariable("dealerInfo");
            if (objDealer instanceof DealUserInfo) {
                DealUserInfo userInfo = (DealUserInfo) objDealer;
                assignee = userInfo.getId();
            } else if (objDealer instanceof DealRoleInfo) {
                DealRoleInfo roleInfo = (DealRoleInfo) objDealer;
                task.addGroupIdentityLink(roleInfo.getId(), "role");
            }
        }

    }
    super.handleAssignments(taskService, assignee, owner, candidateUsers, candidateGroups, task, expressionManager, execution);
}

最后, 在Complete流程的时候,按照调用方传回的信息,设置投票的值即可:

//设置会签投票结果
variables.put(FlowConst.SIGN_VOTE + "_" + taskId, signVoteType.getCode());

如何使用会签结果?

在需要判断会签结果的Sequence Flow Condition中直接设置:

Flowable6.4 - 分派办理人

Flowable6.4 - 分派办理人

这次分享的是从一个开源项目的代码里面学来的,有兴趣的同学可以去阅读一下该项目的源码,还是有很多可以参考的地方,项目地址如下:

https://gitee.com/threefish/NutzFw.git

首先, 存储办理人的表:act_ru_identitylink。如果想为一个Task分配办理人,可以使用以下的API:

task.addCandidateGroup(String groupId);
task.addCandidateUser(String userId);

task.addCandidateGroups(Collection<String> candidateGroups);
task.addCandidateUsers(Collection<String> candidateUsers);

如果使用以上的API增加一个办理人,会在act_ru_identitylink表中增加一条记录,如下:

使用上述API设置的人员或者组,表中的TYPE_为“candidate”。如果阅读Flowable的源代码,就会发现原因:

//设置人员
public IdentityLinkEntity addCandidateUser(String taskId, String userId) {
    return this.addTaskIdentityLink(taskId, userId, (String)null, "candidate");
}
//设置组
public IdentityLinkEntity addCandidateGroup(String taskId, String groupId) {
    return this.addTaskIdentityLink(taskId, (String)null, groupId, "candidate");
}

但是, 有时候这种固定的属性无法满足我们的业务需要。比如,有时候期望设置部门、岗位、角色。这时候,就需要使用其它的API进行设置,如下:

通过上面的“identityLinkTyp”,就可以自定义TYPE_的值,如下图所示:

之后,就可以进入本次的主要内容了,如何为UserTask节点分配办理人。这里提供的一个方案是通过重写UserTaskActivityBehavior来实现。

关于如何重写UserTaskActivityBehavior,可以参考之前的文章,链接如下:

Flowable6.4 - Behavior使用初探

这里需要重写UserTaskActivityBehavior内的handleAssignments方法,主要的代码如下:

public class ExtUserTaskActivityBehavior extends UserTaskActivityBehavior {

    private static final long serialVersionUID = 7711531472879418236L;

    public ExtUserTaskActivityBehavior(UserTask userTask) {
        super(userTask);
    }

    /**
     * 分配办理人员
     */
    @Override
    protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution) {

        //此处可以根据业务逻辑自定义

        super.handleAssignments(taskService, assignee, owner, candidateUsers, candidateGroups, task, expressionManager, execution);
    }
}

比如NutzFW这个开源项目就是通过如下的过程设置的:

通过扩展UserTask节点属性,设置办理人。

当触发handleAssignments方法时,读取UserTask节点属性。

根据节点属性设置办理人。

主要的设置代码如下:

case SINGLE_USER:
    //单人情况下,直接设置办理人
    assignee = taskExtensionDTO.getAssignee();
    break;
case MULTIPLE_USERS:
    //多人情况下,设置candidateUsers 
    candidateUsers = taskExtensionDTO.getCandidateUsers().stream().map(CandidateUsersDTO::getUserName).collect(Collectors.toList());
    break;
case USER_ROLE_GROUPS:
    //角色时,设置group
    candidateGroups = taskExtensionDTO.getCandidateGroups().stream().map(CandidateGroupsDTO::getRoleCode).collect(Collectors.toList());
    break;

Flowable6.4 – 加签和减签

Flowable6.4 – 加签和减签

趁着旅游归来的短暂休息,了解一下Flowable中的加签和减签操作。主要是以下两个方法来实现:

runtimeService.addMultiInstanceExecution(String activityId, String parentExecutionId, Map<String, Object> executionVariables)

runtimeService.deleteMultiInstanceExecution(String executionId, boolean executionIsCompleted)

依然是先上流程图:

其中会签节点是多实例节点,此流程的关键xml片段如下:

<process id="TestMutiTask" isExecutable="true">
    <startEvent id="Start1" name="开始"></startEvent>
    <userTask id="UserTask1" name="处理"></userTask>
    <sequenceFlow id="sid-D14A5BC6-A61E-461F-AD33-0042E91B8B13" sourceRef="Start1" targetRef="UserTask1"></sequenceFlow>
    <userTask id="UserTask2" name="会签">
      <multiInstanceLoopCharacteristics isSequential="false" flowable:collection="${subProcessHelper.getUserNames()}" flowable:elementVariable="assignee">
        <completionCondition>${subProcessHelper.isComplete(execution)}</completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="sid-E6847EF6-F54F-409B-AF8B-DCA62ECDC76F" sourceRef="UserTask1" targetRef="UserTask2"></sequenceFlow>
    <userTask id="UserTask3" name="审批"></userTask>
    <sequenceFlow id="sid-AF828B84-DDAA-4056-88C5-9D4F6EA9F725" sourceRef="UserTask2" targetRef="UserTask3"></sequenceFlow>
    <endEvent id="End1" name="结束"></endEvent>
    <sequenceFlow id="sid-C49B6256-0827-4CF5-8A47-5860A107142A" sourceRef="UserTask3" targetRef="End1"></sequenceFlow>
  </process>

subProcessHelper.getUserNames():会返回一个List<String>集合,会签节点会根据此集合的数量生成相对应的实例。

subProcessHelper.isComplete(execution):用来判断会签节点是否完成,这里设置的条件为“已完成数量/总数量>2/3”。

调用加签和减签的方法如下:

    /**
     * 增加流程执行实例
     * @param nodeId
     * @param proInstId
     * @param assigneeStr 以逗号隔开的字符串
     */
    @RequestMapping(value = "addExecution/{nodeId}/{proInstId}/{assignees}")
    public void addExecution(@PathVariable("nodeId") String nodeId,
                             @PathVariable("proInstId") String proInstId,
                             @PathVariable("assignees") String assigneeStr) {
        String[] assignees = assigneeStr.split(",");
        for (String assignee : assignees) {
            runtimeService.addMultiInstanceExecution(nodeId, proInstId, Collections.singletonMap("assignee", (Object) assignee));
        }
    }

    /**
     * 删除流程执行实例
     * @param excutionId
     * @param complated 是否完成此流程执行实例
     */
    @RequestMapping(value = "delExecution/{excutionId}/{complated}")
    public void delExecution(@PathVariable("excutionId") String excutionId,
                             @PathVariable("complated") Boolean complated) {
        runtimeService.deleteMultiInstanceExecution(excutionId, complated);
    }

启动流程,将流程跳转至会签节点,如下图所示:

act_ru_task

act_ru_execution

此时请求加签方法:

http://localhost:8080/flowabledemo/flow/addExecution/UserTask2/55001/test004  

流程会增加一个新的子实例,并且会增加相对应的参数,如下:

act_ru_task

act_ru_execution

act_ru_variable

此时如果请求减签的方法:

http://localhost:8080/flowabledemo/flow/delExecution/55034/0

流程中相对应的Task和Variable会被删除:

act_ru_task

以上,就是本次试验的内容,需要注意的是,在减签时,如果Task正好是该多实例节点中的最后一个,将导致流程无法继续流转。下次可以分析一下源码,看看为何会这样。

Flowable6.4 – 加签和减签的源码解析

Flowable6.4 – 加签和减签的源码解析

上一篇简单实现了一下加签和减签的操作,这次主要是看看Flowable是如何实现加签和减签的。

首先,加签。

Flowable实现加签主要是通过下面的方法实现的:

runtimeService.addMultiInstanceExecution(String activityId, String parentExecutionId, Map<String, Object> executionVariables)

跟踪代码进入其方法体,发现执行了下面这个命令:

AddMultiInstanceExecutionCmd(activityId, parentExecutionId, executionVariables)

这里的三个参数所代表的的意义是:

activityId:流程节点的标识。

parentExecutionId:流程执行实例标识,proInstId。

executionVariables:所要传入的参数。

查看AddMultiInstanceExecutionCmd这个类的源码,主要关注方法execute(),此方法就是加签操作实现的关键。

关键的代码加注释,如下:

@Override
public Execution execute(CommandContext commandContext) {
    ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
    //获得multi instance execution,即IS_MI_ROOT
    ExecutionEntity miExecution = searchForMultiInstanceActivity(activityId, parentExecutionId, executionEntityManager);

    if (miExecution == null) {
        throw new FlowableException("No multi instance execution found for activity id " + activityId);
    }

    if (Flowable5Util.isFlowable5ProcessDefinitionId(commandContext, miExecution.getProcessDefinitionId())) {
        throw new FlowableException("Flowable 5 process definitions are not supported");
    }
    //创建新的流程执行实例
    ExecutionEntity childExecution = executionEntityManager.createChildExecution(miExecution);
    childExecution.setCurrentFlowElement(miExecution.getCurrentFlowElement());
    //获得BPMN模型中节点的配置信息
    BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(miExecution.getProcessDefinitionId());
    Activity miActivityElement = (Activity) bpmnModel.getFlowElement(miExecution.getActivityId());
    MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = miActivityElement.getLoopCharacteristics();
    //设置流程参数nrOfInstances
    Integer currentNumberOfInstances = (Integer) miExecution.getVariable(NUMBER_OF_INSTANCES);
    miExecution.setVariableLocal(NUMBER_OF_INSTANCES, currentNumberOfInstances + 1);
    //设置子流程执行实例的参数
    if (executionVariables != null) {
        childExecution.setVariablesLocal(executionVariables);
    }
    //如果是并行,需要执行操作,生成Task记录
    if (!multiInstanceLoopCharacteristics.isSequential()) {
        miExecution.setActive(true);
        miExecution.setScope(false);

        childExecution.setCurrentFlowElement(miActivityElement);
        CommandContextUtil.getAgenda().planContinueMultiInstanceOperation(childExecution, miExecution, currentNumberOfInstances);
    }

    return childExecution;
}

需要注意的就是最后部分的操作,因为节点有并行和串行的区分,所以需要不同的处理。

再看,减签。

Flowable实现加签主要是通过下面的方法实现的:

runtimeService.deleteMultiInstanceExecution(String executionId, boolean executionIsCompleted)

跟踪代码进入其方法体,发现执行了下面这个命令:

DeleteMultiInstanceExecutionCmd(executionId, executionIsCompleted)

这里的两个参数所代表的的意义是:

executionId:需要删除的流程执行实例标识。

executionIsCompleted:是否完成此流程执行实例。查看

DeleteMultiInstanceExecutionCmd这个类的源码,主要关注方法execute(),此方法就是加签操作实现的关键。

关键的代码加注释,如下:

@Override
public Void execute(CommandContext commandContext) {
    ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
    ExecutionEntity execution = executionEntityManager.findById(executionId);
    //获得BPMN模型中节点的配置信息
    BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId());
    Activity miActivityElement = (Activity) bpmnModel.getFlowElement(execution.getActivityId());
    MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = miActivityElement.getLoopCharacteristics();

    if (miActivityElement.getLoopCharacteristics() == null) {
        throw new FlowableException("No multi instance execution found for execution id " + executionId);
    }

    if (!(miActivityElement.getBehavior() instanceof MultiInstanceActivityBehavior)) {
        throw new FlowableException("No multi instance behavior found for execution id " + executionId);
    }

    if (Flowable5Util.isFlowable5ProcessDefinitionId(commandContext, execution.getProcessDefinitionId())) {
        throw new FlowableException("Flowable 5 process definitions are not supported");
    }
    //删除指定的流程执行实例和与其关联的数据
    ExecutionEntity miExecution = getMultiInstanceRootExecution(execution);
    executionEntityManager.deleteChildExecutions(execution, "Delete MI execution", false);
    executionEntityManager.deleteExecutionAndRelatedData(execution, "Delete MI execution", false);
    //获得循环的索引值,以便之后重新设置
    int loopCounter = 0;
    if (multiInstanceLoopCharacteristics.isSequential()) {
        //如果是串行,则获得当前的索引值
        SequentialMultiInstanceBehavior miBehavior = (SequentialMultiInstanceBehavior) miActivityElement.getBehavior();
        loopCounter = miBehavior.getLoopVariable(execution, miBehavior.getCollectionElementIndexVariable());
    }
    //如果设置为流程执行实例已经完成,则已完成数量+1,并且索引值也+1
    //如果设置为流程执行实例未完成,则流程实例数量-1,索引值不变
    if (executionIsCompleted) {
        Integer numberOfCompletedInstances = (Integer) miExecution.getVariable(NUMBER_OF_COMPLETED_INSTANCES);
        miExecution.setVariableLocal(NUMBER_OF_COMPLETED_INSTANCES, numberOfCompletedInstances + 1);
        loopCounter++;

    } else {
        Integer currentNumberOfInstances = (Integer) miExecution.getVariable(NUMBER_OF_INSTANCES);
        miExecution.setVariableLocal(NUMBER_OF_INSTANCES, currentNumberOfInstances - 1);
    }
    //生成一个新的流程执行实例(个人觉得这个是专为串行准备的)
    ExecutionEntity childExecution = executionEntityManager.createChildExecution(miExecution);
    childExecution.setCurrentFlowElement(miExecution.getCurrentFlowElement());
    //如果是串行,需要执行一次生成Task,并且设置正确的loopCounter
    if (multiInstanceLoopCharacteristics.isSequential()) {
        SequentialMultiInstanceBehavior miBehavior = (SequentialMultiInstanceBehavior) miActivityElement.getBehavior();
        miBehavior.continueSequentialMultiInstance(childExecution, loopCounter, childExecution);
    }

    return null;
}

所以,通过分析上述源码,如果是并行的多实例节点,并且删除了最后一个流程执行实例,会发现没有了Task,导致整个流程中断。

Flowable6.4 – 绘制流程图

Flowable6.4 – 绘制流程图

一般需要流程图的场景:

  1. 发起流程时,需要从全局了解整体情况、所涉及的经办人,便于必要时进行催办或者发起线下沟通。

  2. 流程运行时或结束后,查看流程所经历的办理过程。

在Flowable中,流程图的绘制可以参见:

org.flowable.image.impl.DefaultProcessDiagramGenerator

本文将分成两部分,简单介绍一下流程图的绘制和办理节点的高亮现实。

首先,如何绘制流程图。

总共三个步骤:

  1. 获得流程定义的BpmnModel。

  2. 根据BpmnModel获得图片流。

  3. 输出图片流。

直接上代码了,基本上都是Flowable Api的使用:

/**
 * @param type 输入的图片类型(png或jpg)
 * @param modelId 流程模型标识
 * @param response HttpServletResponse
 */
@ResponseBody
@RequestMapping(value = "diagram/{type}/{modelId}")
public void getJpgDiagram(@PathVariable(value = "type") String type,
                          @PathVariable(value = "modelId") String modelId,
                          HttpServletResponse response) {
    try {
        //根据modelId或者BpmnModel
        Model modelData = repositoryService.getModel(modelId);
        ExtBpmnJsonConverter jsonConverter = new ExtBpmnJsonConverter();
        byte[] modelEditorSource = repositoryService.getModelEditorSource(modelData.getId());
        JsonNode editorNode = new ObjectMapper().readTree(modelEditorSource);
        BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
        //获得图片流
        DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        InputStream inputStream = diagramGenerator.generateDiagram(
                bpmnModel,
                type,
                Collections.emptyList(),
                Collections.emptyList(),
                "宋体",
                "宋体",
                "宋体",
                null,
                1.0,
                false);
        //输出为图片
        IOUtils.copy(inputStream, response.getOutputStream());
        response.setHeader("Content-Disposition", "attachment; filename=" + bpmnModel.getMainProcess().getId() + "." + type);
        response.flushBuffer();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

需要注意的是,如果未指定文字信息,中文就会以方框显示。如果设定了文字,但是输出还是以方框显示,可以尝试在设置Configuration时,加入:

configuration.setActivityFontName("宋体");
configuration.setLabelFontName("宋体");

效果如下:

然后,如何高亮显示已经办理的节点。

除了上面的三步之外,需要预先提取高亮的节点和Sequence Flow。即设置上文中的diagramGenerator.generateDiagram方法中的两个emptyList。

依然是直接上代码:

/**
 * @param type     输入的图片类型(png或jpg)
 * @param modelId  流程模型标识
 * @param instId   流程实例标识
 * @param response HttpServletResponse
 */
@ResponseBody
@RequestMapping(value = "acitvityDiagram/{type}/{modelId}/{instId}")
public void getJpgActivityDiagram(@PathVariable(value = "type") String type,
                                  @PathVariable(value = "modelId") String modelId,
                                  @PathVariable(value = "instId") String instId,
                                  HttpServletResponse response) {
    //获得已经办理的历史节点
    List<HistoricActivityInstance> activityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(instId).orderByHistoricActivityInstanceStartTime().asc().list();
    List<String> activties = new ArrayList<>();
    List<String> flows = new ArrayList<>();
    for (HistoricActivityInstance activityInstance : activityInstances) {
        if ("sequenceFlow".equals(activityInstance.getActivityType())) {
            //需要高亮显示的连接线
            flows.add(activityInstance.getActivityId());
        } else {
            //需要高亮显示的节点
            activties.add(activityInstance.getActivityId());
        }
    }

    try {
        //根据modelId或者BpmnModel
        Model modelData = repositoryService.getModel(modelId);
        ExtBpmnJsonConverter jsonConverter = new ExtBpmnJsonConverter();
        byte[] modelEditorSource = repositoryService.getModelEditorSource(modelData.getId());
        JsonNode editorNode = new ObjectMapper().readTree(modelEditorSource);
        BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
        //获得图片流
        DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        InputStream inputStream = diagramGenerator.generateDiagram(
                bpmnModel,
                type,
                activties,
                flows,
                "宋体",
                "宋体",
                "宋体",
                null,
                1.0,
                false);
        //输出图片
        IOUtils.copy(inputStream, response.getOutputStream());
        response.setHeader("Content-Disposition", "attachment; filename=" + bpmnModel.getMainProcess().getId() + "." + type);
        response.flushBuffer();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

最终的效果如下:

flowable 任务节点多实例使用

flowable 任务节点多实例使用

我们在使用Flowable 工作流引擎的时候,最常用的肯定是任务节点,因为在OA系统、审批系统、办公自动化系统中核心的处理就是流程的运转,在流程运转的时候,可能我们有这样的一个需求,在一个任务节点的时候,我们需要多个人对这个节点进行审批,比如实际中这样一个例子,假如是一个部门的投票,这个部门有5个人,那么当5个人都投票的时候大概分为如下几种:

  1. 部门所有人都去投票,当所有人都投票完成的时候,这个节点结束,流程运转到下一个节点。(所有的人都需要投票)

  2. 部门所有人都去投票,只要有任意2/3的人同意,这个节点结束,流程运转到下一个节点。(部分人投票只要满足条件就算完成)。

  3. 部门中有一个部门经理,只要部门经理投票过了,这个节点结束,流程运转到下一个节点(一票否决权)。

  4. 部门中根据职位不同,不同的人都不同的权重,当满足条件的时候,这个节点结束,流程运转到下一个节点。比如说所有的人员权重加起来是1,a有0.2的权重,其他的四个人分别是0.1的权重,我们可以配置权重达到0.3就可以走向下一个节点,换言之a的权重是其他人的2倍,那就是a的投票相当于2个人投票。这种需求还是很常见的。

  5. 部门所有人都去投票,a投票结束到b,b开始投票结束到c,一直如此,串行执行。最终到最后一个人再统计结果,决定流程的运转。

上面的五种情况,我们可以提取出来一些信息,我们的activiti 工作流引擎,必须支持如下功能,才能满足上面的需求:

  1. 任务节点可以配置自定义满足条件。

  2. 任务节点必须支持串行、并行。

  3. 任务节点必须支持可以指定候选人或者候选组。

  4. 任务节点必须支持可以循环的次数。

  5. 任务节点必须支持可以自定义权重。

  6. 任务节点必须支持加签、减签。(就是动态的修改任务节点的处理人)

因为实际上的需求可能比上面的几种情况更加的复杂,上面的6个满足条件,工作流支持前4个,后面的2个条件是不支持的,所以我们必须要扩展activiti 工作流引擎才能使用5、6等的功能。下面我们将详细的介绍前四种条件的使用,在掌握基本使用之后,我们在后面的章节中将详细的介绍,5、6这两种功能以及可能更加复杂的操作。

串行、并行配置

为了演示如何使用,我们采用由浅入深的使用,结合流程图、流程定义xml、以及代码和数据库的变化来阐释每一个配置的使用以及含义。

流程的详细定义如下图所示:

流程的详细定义xml如下:

<?xml version="1.0" encoding="UTF-8"?>

<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.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" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">

  <process id="multiInstance" name="multiInstance" isExecutable="true">

    <documentation>multiInstance</documentation>

    <startEvent id="startEvent1"></startEvent>

    <userTask id="A" name="多实例测试" flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext="" shareniu:shareniuextboolen="false">

      <multiInstanceLoopCharacteristics isSequential="true">

        <loopCardinality>2</loopCardinality>

      </multiInstanceLoopCharacteristics>

    </userTask>

    <sequenceFlow id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" sourceRef="startEvent1" targetRef="A"></sequenceFlow>

    <userTask id="shareniu-B" name="shareniu-B" xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext="" shareniu:shareniuextboolen="false"></userTask>

    <sequenceFlow id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" sourceRef="A" targetRef="shareniu-B"></sequenceFlow>

    <endEvent id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>

    <sequenceFlow id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" sourceRef="shareniu-B" targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>

  </process>

  <bpmndi:BPMNDiagram id="BPMNDiagram_multiInstance">

    <bpmndi:BPMNPlane bpmnElement="multiInstance" id="BPMNPlane_multiInstance">

      <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">

        <omgdc:Bounds height="30.0" width="30.0" x="100.0" y="130.0"></omgdc:Bounds>

      </bpmndi:BPMNShape>

      <bpmndi:BPMNShape bpmnElement="A" id="BPMNShape_A">

        <omgdc:Bounds height="80.0" width="100.0" x="165.0" y="105.0"></omgdc:Bounds>

      </bpmndi:BPMNShape>

      <bpmndi:BPMNShape bpmnElement="shareniu-B" id="BPMNShape_shareniu-B">

        <omgdc:Bounds height="80.0" width="100.0" x="315.0" y="120.0"></omgdc:Bounds>

      </bpmndi:BPMNShape>

      <bpmndi:BPMNShape bpmnElement="sid-4AC81F5B-49F8-4135-B68E-1C182D004080" id="BPMNShape_sid-4AC81F5B-49F8-4135-B68E-1C182D004080">

        <omgdc:Bounds height="28.0" width="28.0" x="442.0" y="146.0"></omgdc:Bounds>

      </bpmndi:BPMNShape>

      <bpmndi:BPMNEdge bpmnElement="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" id="BPMNEdge_sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47">

        <omgdi:waypoint x="414.9499999999902" y="160.0"></omgdi:waypoint>

        <omgdi:waypoint x="442.0" y="160.0"></omgdi:waypoint>

      </bpmndi:BPMNEdge>

      <bpmndi:BPMNEdge bpmnElement="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" id="BPMNEdge_sid-BA8FC337-40DC-493B-805C-F213B7C4A17D">

        <omgdi:waypoint x="264.95000000000005" y="145.0"></omgdi:waypoint>

        <omgdi:waypoint x="290.0" y="145.0"></omgdi:waypoint>

        <omgdi:waypoint x="290.0" y="160.0"></omgdi:waypoint>

        <omgdi:waypoint x="314.99999999998477" y="160.0"></omgdi:waypoint>

      </bpmndi:BPMNEdge>

      <bpmndi:BPMNEdge bpmnElement="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" id="BPMNEdge_sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D">

        <omgdi:waypoint x="129.94999817301806" y="145.0"></omgdi:waypoint>

        <omgdi:waypoint x="165.0" y="145.0"></omgdi:waypoint>

      </bpmndi:BPMNEdge>

    </bpmndi:BPMNPlane>

  </bpmndi:BPMNDiagram>


</definitions>

xml配置文件的部分含义如下:

  1. flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" 这个节点可以4个人审核。
  2. <loopCardinality>2</loopCardinality> 循环2次结束。
  3. <multiInstanceLoopCharacteristics isSequential="true"> 串行并行的配置。

串行的配置

修改<multiInstanceLoopCharacteristics isSequential="true"> 中的isSequential为true是串行,isSequential为false是并行。我们测试串行。下面的代码展示启动流程因为是以一个节点所以部署启动后,直接进入多实例任务。

流程的部署

        @Test

	public void addBytes() {

		byte[] bytes = IoUtil.readInputStream(

				ProcessengineTest.class.getClassLoader()

						.getResourceAsStream("com/shareniu/shareniu_flowable_study/bpmn/ch3/multiInstance.bpmn20.xml"),

				"multiInstance.bpmn20.xml");

		String resourceName = "multiInstance.bpmn";

		Deployment deployment = repositoryService.createDeployment().addBytes(resourceName, bytes).deploy();

		System.out.println(deployment);

	}

流程的启动

        @Test

	public void startProcessInstanceByKey() {

		String processDefinitionKey = "multiInstance";

		ProcessInstance startProcessInstanceByKey = runtimeService.startProcessInstanceByKey(processDefinitionKey);

		System.out.println(startProcessInstanceByKey);

	}

我们按照上面的步骤启动一个流程看一下数据库的变化。

ACT_RU_TASK表的数据有了如下图所示:

ACT_RU_IDENTITYLINK表的数据有了如下图所示:

ACT_RU_IDENTITYLINK权限表中,确实把我们设置的人员信息设置进去了,shareniu1,shareniu2,shareniu3,shareniu4现在就有代办信息了。

ACT_RU_VARIABLE表的数据有了如下图示:

上面重要的变量需要解释一下,要不然还真不好理解,多任务是怎么运转的。

  1. nrOfInstances 实例总数。

  2. nrOfCompletedInstances 当前还没有完成的实例 nr是number单词缩写 。

  3. loopCounter 已经循环的次数。

  4. nrOfActiveInstances 已经完成的实例个数。

下面我们结束一个任务看一下,流程走到那个节点了。

完成任务

        @Test

	public void complete() {

		String taskId="15011";

		taskService.complete(taskId);

	}

接下来看一下数据库表的变化。

ACT_RU_VARIABLE表的数据有了如下图所示:

上面我们仔细的发现,可以看到

nrOfCompletedInstances、loopCounter、nrOfActiveInstances都加1了,确实多任务就是参考这几个值的变化进行判断的。

因为我们设置了循环2次,所以我们看看ACT_RU_IDENTITYLINK还有一个任务,因为我们是并行处理的。

所以我们在结束新的任务看一下流程是不是真的结束了,如果结束了,那么我们循环次数的配置就是正确的。

完成任务

        @Test

	public void complete() {

		String taskId="17502";

		taskService.complete(taskId);

	}

下面看一下ACT_RU_TASK,里面没有任务信息了,所以侧面证明循环次数的配置就是正确的。

接下来我们测试并行任务。除了isSequential="false",其他的配置是一样的。

并行的配置测试

除了isSequential="false",其他的配置跟上面的串行是一样一样的。

重新部署测试,部署后我们启动一个新的流程测试。

ACT_RU_TASK表如下:

一次性的有2个任务需要处理,因为我们循环的是2次,所以直接就是2个。

ok串行、并行就讲解到这里。

串行、并行总结

我们配置的是循环2次,看以看到不管是并行还是串行,两个代办任务结束之后,流程直接跳转到下一个状态,但是

我们并没有配置结束条件,所以上面的例子,也可以看出来,如果不配置默认的通过条件,则默认条件是1,后面的源码章节会给大家说明这一点的。

通过条件的配置

在上面的串行、并行实例中,我们没有设置通过条件,但是程序按照配置的循环的次数,然后跳转到了下一个状态,可以侧面印证,如果不配置通过条件则默认值就是1.

下面的代码详细的介绍通过条件的配置,具体的配置代码如下:

<process id="multiInstance" name="multiInstance"
isExecutable="true">

		<documentation>multiInstance</documentation>

		<startEvent id="startEvent1"></startEvent>

		<userTask id="A" name="多实例测试"

			flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4">

			<multiInstanceLoopCharacteristics

				isSequential="true">

				<loopCardinality>4</loopCardinality>

				<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>

			</multiInstanceLoopCharacteristics>

		</userTask>

		<sequenceFlow

			id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" sourceRef="startEvent1"

			targetRef="A"></sequenceFlow>

		<userTask id="shareniu-B" name="shareniu-B"

			xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext=""

			shareniu:shareniuextboolen="false"></userTask>

		<sequenceFlow

			id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" sourceRef="A"

			targetRef="shareniu-B"></sequenceFlow>

		<endEvent id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>

		<sequenceFlow

			id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" sourceRef="shareniu-B"

			targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>

	</process>


配置描述

<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>

nrOfCompletedInstances、nrOfInstances 变量描述上面已经描述了,我们这里设置的条件是大于1/4的人完成任务,任务就结束了。下面我们代码部署流程,启动流程后进行测试:

ACT_RU_TASK表如下:

我们随便结束一个任务,看一下ACT_RU_TASK表变化。

        @Test

	public void complete() {

		String taskId="40003";

		taskService.complete(taskId);

	}

执行上面的代码,我们很神奇的发现,ACT_RU_TASK表中的其他任务没有了,因为我们配置了4个人,通过条件是1/4,所以任意一个人结束了,流程就结束了。这里我们测试的是并行,串行也是一样的,读者可以自行测试验证。

动态的配置

上面的几种方式,我们定义xml的时候,循环的次数是固定写在xml中的,也就是说我们配置的是循环2次,那么所有的流程实例都是循环2次,这样就不灵活了,程序当然是灵活了比较好,所以在实际开发中,我们可以使用下面的这种方式操作,使程序更加的灵活。

程序的xml配置如下所示:

<process id="multiInstance" name="multiInstance"

		isExecutable="true">

		<documentation>multiInstance</documentation>

		<startEvent id="startEvent1"></startEvent>

		<userTask id="A" name="多实例测试" flowable:assignee="${assignee}">

			<multiInstanceLoopCharacteristics

				isSequential="false" flowable:collection="assigneeList"

				flowable:elementVariable="assignee">

				<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}

				</completionCondition>

			</multiInstanceLoopCharacteristics>

		</userTask>

		<sequenceFlow

			id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" sourceRef="startEvent1"

			targetRef="A"></sequenceFlow>

		<userTask id="shareniu-B" name="shareniu-B"

			xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext=""

			shareniu:shareniuextboolen="false"></userTask>

		<sequenceFlow

			id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" sourceRef="A"

			targetRef="shareniu-B"></sequenceFlow>

		<endEvent id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>

		<sequenceFlow

			id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" sourceRef="shareniu-B"

			targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>

</process>

动态配置如下所示:

<userTask id="usertask1" name="多实例任务" flowable:assignee="${assignee}">

      <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="assigneeList" activiti:elementVariable="assignee">

        <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>

      </multiInstanceLoopCharacteristics>

</userTask>

参数说明:

三个参数结合决定了,当前节点的处理人来自assigneeList集合,注意这里是集合信息而不是字符串,所以程序的运行时候变量的赋值,如下所示:

        @Test

	public void startProcessInstanceByKey() {

		Map<String, Object> vars = new HashMap<>();

		String[] v = { "shareniu1", "shareniu2", "shareniu3", "shareniu4" };

		vars.put("assigneeList", Arrays.asList(v));

		String processDefinitionKey = "multiInstance";

		ProcessInstance startProcessInstanceByKey = runtimeService.startProcessInstanceByKey(processDefinitionKey,

				vars);

		// Sys

ok了,测试一下,确实程序如预期的所示,大功告成。

总结

参数的使用总结

  1. flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" 这个节点可以4个人审核。

  2. loopCardinality>2</loopCardinality> 循环2次结束。

  3. <multiInstanceLoopCharacteristics isSequential="true"> 串行并行的配置。 <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition> 完成条件的配置。

这里我们还可以得出一个结论:

如果使用串行方式操作nrOfActiveInstances 变量始终是1,因为并行的时候才会去+1操作。

遗留点

上面的程序已经解决了常用的问题,关于会签、加签、减签、退签、权重配置、自定义通过条件配置(条件自定义通过)

这些问题,这个章节还没有具体的实现,关于这些问题,由于本章内容有限,我们就后续章节讲解吧。