基于Camunda实现bpmn中各种类型任务
Camunda Modeler -为流程设置器(建模工具),用来构建我们的流程模型。Camunda Modeler流程绘图工具,支持三种协议类型流程文件分别为:BPMN、DMN、Form。
Camunda Modeler下载地址:https://camunda.com/download/modeler/
继 springboot集成Camunda审核流程(二):Camunda Modeler设计器设置BPMN流程 继续对流程设计器以及bpmn协议相关知识进行记录扩展。本文章主要记录 bpmn 协议中的各种类型的任务基于Camunda流程控制引擎的实现方式!
在bpmn协议中常用的任务类型主要有:用户任务(user task)、服务任务(service task)、脚本任务(script task)、发送任务(send task)、接收任务(receice task) …等。
备注:其它功能的使用铺垫
一、Camunda 控制台用户功能
Camunda流程引擎内置了用户管理相关的操作,可以通过Camunda内置控制台创建对应的用户组、用户等操作,后续可以直接通过内置的控制台实现对流程的审核过程!
配置依赖见文: springboot集成Camunda审核流程(一):Springboot集成配置
可以通过Camunda 控制台 配置用户信息、用户组信息,同时给对应的用户组分配系统的操作权限等功能。
这里就不详细扩展,需要知道Camunda内置的该功能。后续在测试流程的各种任务时,就可以通过登录创建的不同用户账号对流程进行审核操作(也可以自己写审核接口来实现不同用户对节点的审核操作,但是这里Camunda已经内置了类似的审核平台,就直接使用就不用在写各种审核、驳回等接口)!从而实现对流程的测试。
需要在Camunda控制台创建若干用户,后续登录对应用户,对对应的任务节点进行审核操作!
二、Camunda Modeler部署流程
在Camunda-Modeler 流程设计器中,可以直接将我们设计的流程模板部署到对应的后台程序中,同时也可以通过流程设计器直接启动对应流程模板的实例,就无需手动写接口实现流程模板的部署和流程实例的启动等功能:
1、camunda-Modeler流程设计器中 流程模型的部署功能:
2、camunda-Modeler流程设计器中 启动流程实例的功能
这里通过 Start process instance 操作 就可以 直接启动流程实例,但是需要注意的是,在流程设计器中启动流程实例时,无法给流程中需要的流程变量赋值,所有当流程中需要给预置的流程变量赋值时,就不能在流程设计器中进行启动流程实例。
需要给流程中流程变量赋初始值时可以通过Camunda控制台启动流程实例,或者构建一个简单的流程实例启动接口。 eg:
一、用户任务 user task
需要在项目程序中人为处理干预后才能流转的任务类型
1.1 用户任务受理人设置
User assignment 栏目中主要设置节点对应审核人的参数。
参数的填写可以设置为固定字符串(eg:“xx” ),表示后续该节点的受理人就由 xx 进行受理审核。也可以通过 UEL表达式 / UEL方法表达式 的方式进行填写,后续在程序中给对应的 流程变量Key 赋值即可!
-
使用UEL表达式
设置节点审核人的方式(eg: ${userID})。
当流程实例运行到该节点后,会自动查询该流程实例的流程变量中 该 userID 所对应的数据,就会自动将该Key对应的value值设置为该节点的审核人。
在代码中需要在实例运行到当前节点之前,对对应的流程变量进行赋值,这一步可以通过流程监听器来实现。或者是在流程创建初期,通过制单人信息,将对应的审核人确定好之后对对应流程变量进行赋值!
-
使用 UEL方法表达式
当使用 UEL方法表达式(eg: ${uelMethodListener.getAssignee(execution)})来确定流程节点审核人时。当流程实例审核到该节点后,会解析到该UEL方法表达式。
以示例中的 UEL方法表达式为例,当执行当该节点后,流程引擎就会通过 uelMethodListener对象,调用getAssignee()方法,来获取对应的节点受理人。其中的 execution 是传入的节点相关的信息参数。在后台代码中的具体封装如下:
1.2 流程内置表单的使用
Camunda-bpmn 流程设计器中,可以创建内置的任务表单,当流程执行到相关任务节点时,需要我们人工处理填写创建表单的任务信息。填写完成后,对应的表单数据会自动生成为对应流程实例的流程变量。该流程变量数据就可以用来作为后续流程中的分支条件参数等作用!
-
任务表单实现效果
设置好任务内置表单后,启动流程实例时,运行到对应节点,就会触发内置表单的填写(这里就使用了Camunda内置控制台来对任务进行审核操作。
上文备注内容中介绍了,创建对应节点审核人的账号)在Camunda内置控制台中,登录对应的审核人通过TskList中就能查询到该用户名下对应需要审核的任务节点。
-
审核通过任务表单节点后续效果
将任务表单对应审核人登录到Camunda内置控制台,对表单内容填写后,审核通过该节点的效果展示:
1.3 多实例节点设置
多实例(会签/或签)节点 是指 实现多个用户对一个 用户任务节点进行操作的方式。
在用户节点(user task) 的基础上可设置会签 /或签节点 (多实例节点)。当节点设置为多实例节点后,流程实例中会自动生成相关参数的流程变量用来记录该节点相关数据信息:
nrOfInstances:多实例节点中总共的实例数(实例总数)
nrOfActiviteInstances:当前活动的实例数量,即还没有完成的实例数量对应串行而言该值始终为1
loopCounter :循环计数器,办理人在列表中的索引
nrOfCompletedInstances:已经完成的实例数量
loop cardinality:循环基数。可选项。可以直接填整数,表示会签的人数。
Collection:集合。可选项。会签人数的集合,通常为list,和loop cardinality二选一。
Element variable:元素变量。选择Collection时必选,为collection集合每次遍历的元素。
Completion condition:完成条件。可选。比如设置一个人完成后会签结束,那么其他人的代办任务都会消失
-
串行/并行 多实例节点的设置
串行 和 并行 多实例节点的区别在与,集合中的受理人在对目标节点进行审核是,是否需要按照顺序进行受理审核。
当设置为并行(III) 时,多实例集合中的所有受理人,不需要按照相关顺序执行,可以随意顺序对多实例节点进行审核操作;当设置为串行(三) 多实例节点时,多实例集合中的审核人,就必须按照一定顺序依次一个一个的对目标节点进行审核。
-
会签/或签 节点的设置
会签节点指 多实例任务节点 的 多实例集合中的所有审核人都审核通过,该节点才算完成。
或签节点指 多实例任务节点 的 多实例集合中的审核只需要一个或者多个审核通过后,该多实例任务就算完成。
设置会签/或签节点的方式主要是通过 **多实例设置栏目(Multi-instance)**中的 **节点完成条件(Completion condition)**来体现出来,通过上文注释中的参数加上UEL表达式封装来设置:
会签节点:nrOfCompletedInstances == nrOfInstances (完成实例数 == 实例总数)
或签节点:nrOfCompletedInstances == 1(完成实例数 ==1)
Camunda流程引擎会自动根据设置的多实例节点完成条件来判断,该多实例节点是否结束后跳转到下一任务节点。
-
会签节点审核人动态设置方式
首先在 **多实例设置栏目(Multi-instance)**中的 **多实例集合(Collection)**项中,用 UEL表达式 将多实例集合的Key值设置上(eg: ${leaders} )。
然后在通过项目代码中,动态的为 多实例结合赋值,并传入对应流程实例中即可 完成对多实例节点的审核人动态设置!
1.4 开始节点Initiator的设置
在开始节点中我们可以为 流程发起人Initiator 中设置一个 Key 值,在后续启动流程时调用接口API 设置发起人信息,就会自动生成一个对应Key值的流程变量作为我们流程发起人。然后在第一个节点就可以直接引用该 Key值,作为流程的第一个节点审核人(制单人)。
-
在bpmn流程的开始节点设置发起人Key值
-
在流程任务的第一个用户节点(通常为制单人节点),将审核人设置为对应的Key值
后续通过调用Camunda内置APi即可自动设置流程发起人信息,并会作为流程变量存储到对应的流程数据中。
-
在项目中启动流程时通过调用内置Api即可完成对流程发起人变量的赋值
二、服务任务 service task
服务任务通常是调用业务系统,Camunda中 可以调用 java代码。
服务任务 service task 常见的实现方式主要有四种: External外部任务实现、Java class 指定java类实现、Expression 表达式实现、Delegate expression 代理表达式实现。
选中不同的实现方式,需要做出对应的处理方法,当流程实例执行到服务任务时,会根所设定的实现方式进行对应的处理。
2.1 外部实现方式 External
在service task 服务任务Type选项选用 **外部实现方式 External **时,对应的 Topic中需要自定义一个主题,作为该服务任务的唯一标识。
- 外部任务实现方式一
在客户端系统中(外部系统),会创建外部任务处理的对应类,会实时监听相关任务。 具体的实现方式如下代码:
package cn.zhidasifang.flowmanagement.camundaExternalClient.shopping;import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.client.ExternalTaskClient;
import org.camunda.bpm.engine.variable.Variables;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.Map;/*** @ClassName : SubscribeTask* @Description : 业务任务节点 Service-Task 【External 类型的业务任务】* @Author : AD*/@Component
@Slf4j
public class SubscribeTask {/*** 引擎端url 前缀*/private final static String CAMUNDA_BASE_URL = "http://localhost:9991/engine-rest";private ExternalTaskClient client = null;//封装获取客户端的方法private ExternalTaskClient getClient(){if (ObjectUtil.isNull(client)){client = ExternalTaskClient.create().baseUrl(CAMUNDA_BASE_URL) //监听的camunda客户端(流程引擎端地址)//long polling timeout.asyncResponseTimeout(10000) //异步响应超时时间(轮询时间).build();}return client;}/*** Description:外部任务--订阅到加入购物车的任务节点执行* @param* @return void*/@PostConstructpublic void handleShoppingCart(){getClient().subscribe("shopping_cart") //客户端订阅【对应外部任务主题名称】.processDefinitionKey("Process_shopping") //流程定义Key(ID).lockDuration(2000) //锁定外部任务时间.handler((externalTask, externalTaskService) -> {log.info("333订阅到加入购物车任务");//设置流程实例变量Map<String,Object> goodVariable = Variables.createVariables().putValue("toWhere","shanghai China");// 获取流程实例中已经存在的变量String paramValue = externalTask.getVariable("param");log.info("333shoppingCartId: {}", paramValue);// 完成任务externalTaskService.complete(externalTask,goodVariable);}).open();}
}
- 外部任务实现方式二
除了上述方式外,外部任务的实现方式还可以通过 camunda流程引擎提供的 @ExternalTaskSubscription 注解的方式来实现对应的 外部任务类。
package cn.zhidasifang.flowmanagement.camundaExternalClient.externalTask;import com.sun.org.apache.xpath.internal.operations.Bool;
import org.camunda.bpm.client.spring.annotation.ExternalTaskSubscription;
import org.camunda.bpm.client.task.ExternalTaskHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;/*** @ClassName : SelfRepairService* @Description : 外部任务External的实现类Service* @Author : AD*/@Configuration
public class SelfRepairService {@Bean@ExternalTaskSubscription(topicName = "try_self_repair" //外部任务主题名称,processDefinitionKeyIn = {"Process_external_task"} //流程实例定义Key,lockDuration = 500000) //锁定外部任务时间public ExternalTaskHandler doSelfRepair(){return (externalTask, externalTaskService) -> {System.out.println("666外部任务进入尝试自修!");try {System.out.println("666线程睡眠5秒!");TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}Boolean isFree = (Boolean) externalTask.getVariable("isFree");if (isFree){System.out.println("666免费维修");externalTaskService.complete(externalTask);}else {System.out.println("666维修收费-尝试自修");externalTaskService.handleFailure(externalTask,"自己修理,抛出异常中止后续上门维修!","这里可以打印一次stacktrace",0 //为1会重复重试 为0会抛出异常信息,5000 //重新尝试时间,该时间段内不会被其它轮询获取到该任务);}};}
}
2.2 Java类实现 Java class
外部任务在采用 Java class 方式实现时,需要在 Java class栏目中填写对应的Java类路径和类名称。
当流程实例运行到对应的服务任务时,Camunda流程引擎会自动调用对应类路径下的java类。
在对应的java类中,需要 实现 org.camunda.bpm.engine.delegate.JavaDelegate 同时重写 execute(DelegateExecution execution)方法,在流程引擎执行到对应的服务任务时,会自动执行该类中实现的 execute 方法。
实例代码如下:
2.3 表达式实现方式 Expression
外部任务中,采用Expression表达式方式实现时,Expression栏目中需要填写的 就是UEL方法表达式,于前文 1.1中用户任务受理人设置的实现方式类似。
当流程实例运行到该服务任务节点时,流程引擎会解析对应的UEL方法表达式 ${telCall.doCall(execution)} 。通过该表达式会调用 BeanName为 telCall的对象,同时调用该对象的doCall( ) 方法。
需要注意,在使用 Expression 实现外部任务时,会存在 Result variable 返回值栏目 该栏目中设置对应返回值的Key值,在调用的对应对象方法中,需要返回对应数据。
对应的代码实现部分示例如下:
2.4 代理表达式 Delegate expression
通过 Delegate expression 方式来实现的业务类,需要实现 JavaDelegate接口,并重写 execute 方法。 在Delegate expression栏目中使用UEL表达式将委派类对象填入即可。
当流程实例执行到该节点时,流程引擎会自动调用对应 委派类对象实现的 execute 方法。
委派类的实现代码如下:
三、脚本任务 Script task
脚本任务是一种自动执行的活动。当流程执行到脚本任务时,节点相关的脚本就会自动执行。
脚本任务默认的类型有两种: 外部资源(External resource)、内部脚本(Inline script)
-
内部脚本的方式实现
Script中的内容为:
//通过 execution 获取到流程实例中的流程变量
var person = execution.getVariable("name");
var originDays = execution.getVariable("originDays");
var leaveDay = execution.getVariable("leaveday");//将设置的脚本执行结果Key直接赋值后会自动生成对应的流程实例变量记录存储到流程实例中
leftAnnualDays = originDays-leaveDay;
内部脚本的方式会将填写的脚本内容会被追加到流程BPMN.xml中
-
外部资源的方式实现
外部资源的实现方式 和 内部脚本的区别在于 脚本内容不会追加到流程的BPMN.xml中。
在外部脚本的 Resource栏目中填写脚本的地址即可。
四、发送任务&接收任务
发送任务(Send Task)一般用于发送一个消息;接收任务(Receive Task)一般用来等待发送任务的消息,起到等待作用。一般情况下发送任务和接收任务是成对配合使用的,同时是分开存在两个独立的流程模板中的。
-
接收任务 Receive Task
接收任务需要在Message栏目下配置,等待接收消息的 全局唯一名称。接收任务节点一旦启动之后,就会一直处于等待状态,知道接收到对应名称的截至,该接收任务就会结束。
起到一个通讯作用,可以当作一个流程模板的触发器来使用,在接收任务后面继续添加其它类型的任务,当执行到接收任务处时,一直监听消息,当接收到消息后,就执行完成,在进行流程的后续操作!
注:需要先启动接收任务,处于监听等待状态后,发送任务才能成功的启动或者执行,否则流程引擎将会出现异常!
无法关联消息" MessageName:xxxxx",没有进程定义或执行与参数匹配 !
org.camunda.bpm.engine.MismatchingMessageCorrelationException: Cannot correlate message ‘Message_receive_task’: No process definition or execution matches the parameters
-
发送任务 Send Task
发送任务节点在流程设置器中的配置,Implementation项下可以配置发送任务的实现方式。发送任务的实现方式,在type选项栏下总共有5种:外部任务实现(External)、java类实现(Java class)、表达式实现(Expression)、代理表达式实现(Delegate expression)、Connector实现方式。
这5种实现方式与服务任务(service task) 的采用形式一致,下面就以代理表达式(Delegate expression)的方式来实现发送任务。
在发送任务的实现代码中,需要指定业务标识BusinessKey 来确保只有对应的流程业务实例才能接收到该消息。 该业务标识指定的是 接收任务Receive Task所在的流程实例的业务标识 BusinessKey。
在接收任务所在流程模板中,在启动流程实例时就可以对BusinessKey进行赋值,每个流程实例的业务标识代码都是自定义的,原理上是需要设置不同。