任务
本文中内容和案例节选自贺波老师的《深入Activiti流程引擎:核心原理与高阶实战》一书。这本书中的内容更为全面、详尽,对系统学习和深入掌握Activiti/Flowable的用法非常有帮助,推荐给大家。
QQ交流群1:
633168411已满
QQ交流群2: 582010059
启动后,任务会给到指定的责任人,如果是需要人工审核的任务,那么就需要使用UserTask,也就是用户任务,需要用户完成审批,流程才能继续往下走。
在 Flowable 中,任务可以划分为以下几种类型:
- 用户任务
- 手动任务
- 接收任务
- 脚本任务
- 业务规则任务
- Mule任务
- 发送任务
- 时间发送任务
- 接受事件任务
- 外部工作任务
- Shell脚本任务
用户任务(UserTask)
定义
顾名思义,用户任务是需要人工参与处理的。当流程执行到用户任务节点时,流程引擎会给指定的用户(办理人或候选人)或一组用户(候选组)创建待处理的任务项,等待用户的处理。 用户任务的参与者类型分为两种:⑴分配到一个用户(私有任务);⑵共享给多个用户(共享任务)。大部分的流程场景,一个用户任务通常被具体指派一个用户,在 Flowable 中称为办理人。另外在一些业务处理场景中,一个任务可以被共享给多个人,在 Flowable 中通过指派给多个候选人/ 候选组来实现,这类任务在流程引擎只创建一个任务实例,所有被共享的人都可以查询任务,候选人中的用户有权认领(claim)该任务并且完成该任务,当任务被领取之后,其他候选人即无法再看到此任务。 一个用户任务只允许分配一个办理人,但可以分配多个候选人/候选组。
图形标记
用户任务用左上角有一个小用户图标的标准任务(圆角矩形)表示。
XML标记
用户任务在XML中如下定义。其中id是必须属性,name是可选属性。
<userTask id="theTask" name="Important task" />
也可以为用户任务添加描述(description)。事实上任何BPMN 2.0元素都可以有描述。描述由documentation元素定义。
<userTask id="theTask" name="Schedule meeting" >
<documentation>
这是描述信息
</documentation>
</userTask>
可以使用标准Java方式获取描述文本:task.getDescription()
属性描述
分配给办理人(assignee)
用户任务可以直接分配给一个用户,这个任务只能出现在该用户的个人任务列表中,而不会出现在其他人的任务列表中。只有这个用户能查看和办理这个任务,我们称之为办理人。
<userTask id="Activity_1hpkgvi" name="经理" flowable:assignee="${udept.code}">
<extensionElements>
<flowable:assigneeType>static</flowable:assigneeType>
</extensionElements>
</userTask>
<userTask id="Activity_1hpkgvi" name="经理" flowable:candidateUsers="10000">
<extensionElements>
<flowable:assigneeType>idm</flowable:assigneeType>
<flowable:idmCandidateUsers>
[{"id":"1","name":"易烊千玺","code":"10000","sex":0,"mobile":null,"companyId":"1","companyName":"中国石化","deptId":"27","deptName":"领导班子"}]
</flowable:idmCandidateUsers>
</extensionElements>
</userTask>
到期日期(dueDate)
每个任务都可以使用一个字段标志该任务的到期日期(dueDate)。可以使用查询API,查询在给定日期前或后到期的任务。 可以在任务定义中使用扩展指定表达式,以在任务创建时设定到期日期。该表达式必须解析为java.util.Date,java.util.String (ISO8601格式),ISO8601时间长度(例如PT50M),或者null。例如,可以使用在流程里前一个表单中输入的日期,或者由前一个服务任务计算出的日期。如果使用的是时间长度,则到期日期基于当前时间加上给定长度计算。 例如当dueDate使用“PT30M”时,任务在从现在起30分钟后到期。
<userTask id="Activity_0zhp33g" name="经理" flowable:dueDate="P30M">
</userTask>
也可使用变量设置值:
<userTask id="Activity_0zhp33g" name="经理" flowable:dueDate="${dateVariable}">
</userTask>
任务的到期日期也可以使用TaskService,或者在TaskListener中使用传递的DelegateTask修改。
设置到期时间示例
用户指派
用户任务可以直接指派(assign)给用户。可以定义humanPerformer子元素来实现。humanPerformer需要resourceAssignmentExpression来实际定义用户。目前,只支持formalExpressions。
<process >
...
<userTask id='theTask' name='important task' >
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>kermit</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
</process>
只能指定一个用户作为任务的humanPerformer。在Flowable术语中,这个用户被称作办理人(assignee)。拥有办理人的任务,在其他人的任务列表中不可见,而只能在该办理人的个人任务列表中看到。 可以通过TaskService获取特定用户办理的任务:
List<Task> tasks = taskService.createTaskQuery().taskAssignee("kermit").list();
演示demo
手动任务
定义
手动任务是预期在没有任何业务流程执行引擎或任何应用程序的帮助下执行的任务,它用于建模那些引擎不需要知道的人所 做的工作,以及那些不存在已知系统或 UI 界面的人所做的工作,一般完善流程结构描述,不被引擎执行。例如,电话技术 人员在客户位置安装电话。手工任务就是一个自动执行的过程,流程引擎只是记录相关的流程历史数据, 它用 taskService 查询不到的。
图形标记
手工任务显示为一个普通任务(圆角矩形),左上角是一个手型小图标,如图所示:
XML标记
手动任务的 XML 表示格式如下:
<manualTask id="manualTask1" name="手动任务" />
使用示例
下面我们看一个手动任务的使用示例,设计如图所示流程,这是一个现场兑奖示例流程, 当客户发起申请后,首先到达“管理员审核”用户任务节点,操作完成后流转到“奖品发放”手动 任务节点,自动往下执行到流程结束。
流程对应的 XML 内容如下
<process id="ManualTaskProcess" name="任务-手动任务" isExecutable="true">
<startEvent id="af0d8560213fd4d8b9710b5188cdc6560">
<extensionElements>
<flowable:formData />
</extensionElements>
</startEvent>
<userTask id="a35517e47548f45d49517458e1aab2be1" name="奖品兑换申请">
<extensionElements>
<flowable:formData />
<flowable:assigneeType>static</flowable:assigneeType>
</extensionElements>
</userTask>
<sequenceFlow id="a90f46869ab7e47f5a5e1c11745fcb3d5" sourceRef="af0d8560213fd4d8b9710b5188cdc6560" targetRef="a35517e47548f45d49517458e1aab2be1" />
<endEvent id="Event_1dzfc7c" />
<sequenceFlow id="Flow_0wcdlm3" sourceRef="Activity_0yvfkjj" targetRef="Event_1dzfc7c" />
<manualTask id="Activity_0yvfkjj" name="奖品发放">
<extensionElements>
<flowable:executionListener class="com.dragon.test.bpmn.listener.ManualTaskExecutionListener" event="start" />
<flowable:assigneeType>static</flowable:assigneeType>
<flowable:formData />
</extensionElements>
</manualTask>
<userTask id="Activity_1tmhc8f" name="主办方审核">
<extensionElements>
<flowable:formData />
<flowable:assigneeType>static</flowable:assigneeType>
</extensionElements>
</userTask>
<sequenceFlow id="Flow_05wn51h" sourceRef="a35517e47548f45d49517458e1aab2be1" targetRef="Activity_1tmhc8f" />
<sequenceFlow id="Flow_0f26uqe" sourceRef="Activity_1tmhc8f" targetRef="Activity_0yvfkjj" />
</process>
在以上流程定义中,手动任务 manualTask1 使用 flowable:executionListener 元素定义了一个执行 监听器,该元素并不属于 BPMN2.0 规范,而是 Flowable 扩展出来的元素。在这里设置的执行监听 器会在节点开始(event 属性为 create)时执行。关于执行监听器更详细的介绍和用法。 这里例子中的执行监听器 ManualTaskExecutionListener 内容如下:
@Slf4j
public class ManualTaskExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
//获取当前节点信息
FlowElement currentFlowElement = execution.getCurrentFlowElement();
log.info("到达手动任务,当前节点名称:{},备注:{}", currentFlowElement.getName(), currentFlowElement.getDocumentation());
log.info("处理结果:奖品线下发放完成!");
}
}
流程到达手动任务后,自动执行往下流转了。
演示demo
接收任务
定义
接收任务是一种简单任务,它会等待对应消息的到达。当流程执行到达接收任务时,流程状态会持久化到数据库中,这意味 着该流程将一直处于等待状态,直到引擎接收到一个特定的消息为止,该消息将触发离开接收任务继续往下执行。
图形标记
接收任务显示为一个普通任务(圆角矩形),左上角是一个消息小图标,如图所示:
需要在注意的是,接收任务中的消息图标是白色的(黑色的消息图标表示已经发送的意思)。
XML标记
接收任务的 XML 表示格式如下:
<receiveTask id="receiveTask1" name="接收任务" />
使用示例
流程到达接收任务后,会进入一个等待状态,通常用于由外部完成的但需要耗费一定时间的工作,当完成工作后, 要使得流程继续往下执行,可以调用 runtimeService.trigger(executionId)方法,其中executionId 是执行到此接收任务的执行实例的 id。下面我们看一下接收任务的使用示例,设计如下图所示流程,这是一个账号激活示例流程, 当客户发起申请后,首先到达“管理员审核”用户任务节点,操作完成后流转到“等待激活结果”接收任务节点,直到接收到 对应消息后继续往下执行到流程结束。
流程对应的 XML 内容如下
<process id="receiveTaskProcess" name="任务-接收任务" isExecutable="true">
<startEvent id="a6f37f9b227774dd68879a4cae79865a8" />
<userTask id="a9a3ceaf2c4d64de2af1f861772ac516a" name="账号激活申请">
<extensionElements>
<flowable:formData />
<flowable:assigneeType>static</flowable:assigneeType>
</extensionElements>
</userTask>
<sequenceFlow id="a08c7bb0990b14e6ab2d878a8ec43a303" sourceRef="a6f37f9b227774dd68879a4cae79865a8" targetRef="a9a3ceaf2c4d64de2af1f861772ac516a" />
<receiveTask id="Activity_1uxmlgz" name="等待激活结果">
<extensionElements>
<flowable:executionListener class="com.dragon.test.bpmn.listener.ReceiveTaskExecutionListener" event="end" />
<flowable:assigneeType>static</flowable:assigneeType>
<flowable:formData />
</extensionElements>
</receiveTask>
<userTask id="Activity_1g60ycx" name="管理员审核">
<extensionElements>
<flowable:formData />
<flowable:assigneeType>static</flowable:assigneeType>
</extensionElements>
</userTask>
<endEvent id="Event_1vyjmzt" />
<sequenceFlow id="Flow_0be9ua9" sourceRef="a9a3ceaf2c4d64de2af1f861772ac516a" targetRef="Activity_1g60ycx" />
<sequenceFlow id="Flow_1xf4w9a" sourceRef="Activity_1g60ycx" targetRef="Activity_1uxmlgz" />
<sequenceFlow id="Flow_0v1w6nx" sourceRef="Activity_1uxmlgz" targetRef="Event_1vyjmzt" />
</process>
在以上流程定义中,接收任务 Activity_1uxmlgz 使用 flowable:executionListener 元素定义了一个执行监听器, 该元素并不属于 BPMN2.0 规范,而是 Flowable 扩展出来的元素。在这里设置的执行监听器会在节点结束 (event 属性为 end)时执行。关于执行监听器更详细的介绍和用法,详见第 17 章。这里例子中的执行监 听器 ReceiveTaskExecutionListener 内容如下:
@Slf4j
public class ReceiveTaskExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
FlowElement currentFlowElement = execution.getCurrentFlowElement();
log.info("当前为接收任务,节点名称:{},备注:{}", currentFlowElement.getName(), currentFlowElement.getDocumentation());
String result = (String)execution.getVariable("result");
log.info("接收任务已被触发,处理结果为:{}", result);
}
}
演示demo
脚本任务
定义
脚本任务(Script Task)是一种自动执行的活动。当流程执行到达脚本任务时,会执行相应的 脚本,完毕后继续执行后继路线。脚本任务无须人为参与,可以通过定义脚本实现自定义的业务逻辑。
图形标记
脚本任务显示为一个普通任务(圆角矩形),左上角是一个脚本小图标,如图所示:
XML标记
脚本任务由 scriptTask 元素定义,需要指定 script 和 scriptFormat,例如:
<scriptTask id="scriptTask1" name=" " scriptFormat="groovy">
<script>
sum = 0
for (i in inputArray) {
sum += i
}
</script>
</scriptTask>
其中,scriptFormat 属性表示脚本格式,其值必须兼容 JSR-223(Java 平台的脚本语言),Flowable 支持三种脚本任务类型:javascript、groovy、juel。默认情况下,javascript 已经包含在 JDK 中,因 此不需要额外的依赖。如果想使用其它兼容 JSR-223 的脚本引擎,需要把对应的 jar 包添加到 classpath 下,并使用合适的名称。比如,Flowable 单元测试经常使用 groovy,因为其语法与 Java十 分相似。脚本任务通过 script 子元素配置需要执行的脚本。 需要注意的是,groovy 脚本引擎放在 groovy-all.jar 中。在 groovy 2.0 版本之前,脚本引擎是 groovy jar 的一部分,因此,使用时必须添加如下依赖:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.x.x<version>
</dependency>
界面配置
使用示例
XML内容
<process id="JavaScriptDynamicScriptTaskProcess" name="任务-脚本任务" isExecutable="true">
<startEvent id="a3be4f7ffcaa34d91847e02d1d3ae4506" />
<sequenceFlow id="a5c28b30e0b984b37a14113371149b0ad" sourceRef="a3be4f7ffcaa34d91847e02d1d3ae4506" targetRef="aea9fea5480ca4231a961dd3696580336" />
<scriptTask id="aea9fea5480ca4231a961dd3696580336" name="脚本JavaScript" scriptFormat="JavaScript" flowable:autoStoreVariables="false">
<script>var sum = a + b;
execution.setVariable("sum", sum); </script>
</scriptTask>
<userTask id="Activity_0f27pu9" name="任务审批" flowable:assignee="${applyer}">
<extensionElements>
<flowable:formData />
<flowable:assigneeType>static</flowable:assigneeType>
</extensionElements>
</userTask>
<endEvent id="Event_1m6e3rb" />
<sequenceFlow id="Flow_0fomqtm" sourceRef="Activity_0f27pu9" targetRef="Event_1m6e3rb" />
<sequenceFlow id="Flow_0f808iq" sourceRef="aea9fea5480ca4231a961dd3696580336" targetRef="Activity_0rmiwin" />
<sequenceFlow id="Flow_01o6dpp" sourceRef="Activity_0rmiwin" targetRef="Activity_0ihtdoy" />
<scriptTask id="Activity_0rmiwin" name="脚本Groovy" scriptFormat="groovy" flowable:autoStoreVariables="false">
<script>amass = a * b;
execution.setVariable("amass", amass);</script>
</scriptTask>
<scriptTask id="Activity_0ihtdoy" name="脚本EL" scriptFormat="juel" flowable:resultVariable="reslut" flowable:autoStoreVariables="false">
<script>${amass+sum}</script>
</scriptTask>
<sequenceFlow id="Flow_0vy1824" sourceRef="Activity_0ihtdoy" targetRef="Activity_03detgh" />
<scriptTask id="Activity_03detgh" name="结果逻辑组合" scriptFormat="groovy" flowable:autoStoreVariables="true">
<script>applyer = "";
if(reslut>10000){
applyer = "张三";
}else{
applyer = "李四";
}</script>
</scriptTask>
<sequenceFlow id="Flow_1bgd7y7" sourceRef="Activity_03detgh" targetRef="Activity_0f27pu9" />
</process>