task 活动用来处理涉及人机交互的活动。它的功能在 jBPM 乃至整个工作流的应用中都具有极其重要的意义,因为处理人工任务、电子表单是工作流应用中最繁琐与细致的工作。
1 任务的分配者
利用 task 活动的 assignee 属性(分配者属性)可以把一个任务分配给指定的用户。
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
assignee | 表达式 | 无 | 可选 | 被分配到任务的用户 ID |
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process key="TaskAssignee" name="TaskAssignee" xmlns="http://jbpm.org/4.4/jpdl">
<start g="168,181,48,48" name="start1">
<transition to="审核"/>
</start>
<task assignee="#{order.owner}" g="307,179,92,52" name="审核">
<transition to="等待"/>
</task>
<state g="456,182,92,52" name="等待"/>
</process>
这里演示了任务分配的两个方面:
- assignee 属性定义了一个账号,即负责完成任务的人。
- assignee 属性默认以 EL 表达式来执行,示例中从任务对应的流程变量中查找 order 对象内的 owner 值。
order 对象的类定义:
public class Order implements Serializable {
String owner;
public Order(String owner) {
this.owner = owner;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
测试代码:
Map<String, Object> variables = new HashMap<>();
variables.put("order", new Order("deniro"));
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("TaskAssignee", variables);
//获取 deniro 的任务列表
List<Task> taskList = taskService.findPersonalTasks("deniro");
assertTrue(taskList.size() > 0);
注意: assignee 属性也可以是纯文本。
2 任务的候选者
也可以将任务分配给一组候选用户,组中的每一个用户可以接受这个任务并完成它,这就是任务的候选者机制。
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
candidate-groups | 表达式 | 无 | 可选 | 使用逗号分隔的用户组 ID 列表。所有组中的用户将会成为任务的候选者。 |
candidate-users | 表达式 | 无 | 可选 | 使用逗号分隔的用户 ID 列表。所有列表中的用户将会成为任务的候选者。 |
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process key="TaskCandidates" name="TaskCandidates" xmlns="http://jbpm.org/4.4/jpdl">
<start g="168,181,48,48" name="start1">
<transition to="审核"/>
</start>
<task candidate-groups="sales-dept" g="307,179,92,52" name="审核">
<transition to="等待"/>
</task>
<state g="456,182,92,52" name="等待"/>
</process>
测试代码:
//创建组
identityService.createGroup("sales-dept");
//创建用户
identityService.createUser("deniro", "deniro", "li");
identityService.createUser("lily", "deniro", "lily");
//加入组
identityService.createMembership("deniro", "sales-dept");
identityService.createMembership("lily", "sales-dept");
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("TaskCandidates");
//review 任务出现在用户的分组任务列表中
assertFalse(taskService.findGroupTasks("deniro").isEmpty());
assertFalse(taskService.findGroupTasks("lily").isEmpty());
//deniro 接受了任务
String taskId = taskService.findGroupTasks("deniro").get(0).getId();
taskService.takeTask(taskId, "deniro");
//任务从所有候选者的任务列表中消失
assertTrue(taskService.findGroupTasks("deniro").isEmpty());
assertTrue(taskService.findGroupTasks("lily").isEmpty());
//出现在 deniro 的已分配任务列表中
assertFalse(taskService.findPersonalTasks("deniro").isEmpty());
- 这里创建了两个账号并把他们加入了一个组(使用 IdentityService 身份认证服务)。
- 候选者在处理任务之前,必须先接受任务。
- 一个候选者任务如果被某个候选者接受后,它就会从所有的候选者任务列表中消失,并只出现在这个候选者的已分配任务列表中。
注意:在客户端设计中,账号应该只允许在他们的个人任务列表中操作。
3 任务分配处理器(AssignmentHandler)
可以通过开发 AssignmentHandler 来计算任务的分配者和候选者。
任务分配处理器要实现 AssignmentHandler 接口:
public interface AssignmentHandler extends Serializable {
// 在 assignable 对象中设置分配者和候选者
/** sets the actorId and candidates for the given task. */
void assign(Assignable assignable, OpenExecution execution) throws Exception;
}
注意:Assignable 类型是任务和泳道的通用接口。所以任务分配处理器既可以作为任务活动的元素,又可以作为泳道元素(下面会讲到)的子元素。
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process key="TaskAssignmentHandler" name="TaskAssignmentHandler" xmlns="http://jbpm.org/4.4/jpdl">
<start g="168,181,48,48" name="start1">
<transition to="审阅"/>
</start>
<task g="307,179,92,52" name="审阅">
<assignment-handler class="net.deniro.jbpm.test.task.AssignTask">
<field name="assignee">
<string value="deniro"/>
</field>
</assignment-handler>
<transition to="等待"/>
</task>
<state g="456,182,92,52" name="等待"/>
</process>
AssignTask 任务分配处理器:
public class AssignTask implements AssignmentHandler {
/**
* jPDL 定义中注入的
*/
String assignee;
@Override
public void assign(Assignable assignable, OpenExecution execution) throws Exception {
assignable.setAssignee(assignee);
}
}
assign 方法的 execution 参数对象可以获得流程上下文和变量,所以可以结合其它的 API 来计算出任务的分配者和候选者。
测试代码:
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("TaskAssignmentHandler");
//出现在 deniro 的已分配任务列表中
final List<Task> taskList = taskService.findPersonalTasks("deniro");
assertEquals(1, taskList.size());//断言有一个任务
Task task = taskList.get(0);
assertEquals("审阅", task.getName());
assertEquals("deniro", task.getAssignee());
4 任务泳道
在实际的业务应用中,经常会遇到这样一种场景:流程定义中的多个任务需要被分配或候选给同一组用户。那么我们可以统一将这个 “同一组的用户” 定义为 “一个泳道”。泳道作为流程定义的直接子元素被整个流程定义所知,因此同一流程定义中的任何一个任务都可以引用泳道。属于同一个泳道的任务将会被分配或者候选给这个泳道中的所有用户。
泳道可以理解为流程定义的 “全局用户组”,它可以被当做一个流程规则来使用。
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
swimlane | 泳道名称字符串 | 无 | 可选 | 引用一个在流程中定义的泳道 |
swimlane 属性是任务活动对泳道的引用,泳道本身是作为 process 流程定义的子元素被定义在整个流程范围内的。
泳道(swimlane)元素的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
name | 泳道名称字符串 | 无 | 必需 | 这个名词将在任务的泳道属性中被引用。 |
assignee | 表达式 | 无 | 可选 | 引用的单个用户ID |
candidate-groups | 表达式 | 无 | 可选 | 使用逗号分隔的用户组 ID 列表,此组中的所有用户将作为引用此泳道任务的候选人。 |
candidate-users | 表达式 | 无 | 可选 | 使用逗号分隔的用户 ID 列表,此列表中的所有用户将作为引用此泳道任务的候选人。 |
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process key="TaskSwimlane" name="TaskSwimlane" xmlns="http://jbpm.org/4.4/jpdl">
<swimlane candidate-groups="sales-dept" name="sales representative"/>
<start g="188,247,48,48" name="start1">
<transition to="输入订单数据"/>
</start>
<task g="327,245,132,52" name="输入订单数据" swimlane="sales representative">
<transition to="计算定额"/>
</task>
<task g="524,244,153,52" name="计算定额" swimlane="sales representative"/>
</process>
测试代码:
//创建用户并加入组
identityService.createGroup("sales-dept");
identityService.createUser("deniro", "deniro", "li");
identityService.createMembership("deniro", "sales-dept");
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("TaskSwimlane");
final List<Task> tasks = taskService.findGroupTasks("deniro");//deniro
// 是这个泳道所在组中的唯一候选者
assertFalse(tasks.isEmpty());
String taskId = tasks.get(0).getId();
taskService.takeTask(taskId, "deniro");//接受任务
taskService.completeTask(taskId);//完成任务
List<Task> taskList = taskService.findPersonalTasks("deniro");
assertEquals(1, taskList.size());//断言 deniro 拿到一个任务
Task task = taskList.get(0);
assertEquals("计算定额", task.getName());
assertEquals("deniro", task.getAssignee());
5 任务变量
任务可以读取、更新流程变量。也可以定义任务自由的变量,即任务变量。一般来说,任务变量的主要作用是作为任务表单的数据容器 -- 任务表单负责展示来自任务和流程的变量数据;同时用户通过任务表单录入的数据则会被设置为任务变量,任务变量根据需要也可以被输出成为流程变量。
假设有这样一个流程定义:
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process key="TaskVariables" name="TaskVariables" xmlns="http://jbpm.org/4.4/jpdl">
<start g="241,192,48,48" name="start1">
<transition to="审核"/>
</start>
<task assignee="deniro" g="372,189,92,52" name="审核">
<transition to="等待"/>
</task>
<state g="533,187,92,52" name="等待"/>
</process>
测试代码:
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("TaskVariables");
final List<Task> tasks = taskService.findPersonalTasks("deniro");//deniro
String taskId = tasks.get(0).getId();
//获取任务变量集
Set<String> variableNames = taskService.getVariableNames(taskId);
//取得所有任务变量
Map<String, Object> variables = taskService.getVariables(taskId, variableNames);
//新增或更新某个任务变量,任务变量支持任何可序列化的 Java Object
variables.put("category", "book");
variables.put("num", 1000);
//任务变量放入任务中(持久化操作)
taskService.setVariables(taskId, variables);
6 任务提醒邮件
可以为任务的分配者设置电子邮件提醒:
- 当一个任务出现在某个任务列表中时立即提醒。
- 指定时间间隔进行反复提醒。
电子邮件的内容是根据模板生成的,默认使用 jBPM 内置的模板。
元素 | 数目 | 描述 |
---|---|---|
notification | 0..1 | 当一个任务被分配时立即发送一封提醒邮件。如果没有指定模板,邮件会使用默认的 task-notification 模板。 |
reminder | 0..1 | 根据指定的时间间隔发送提醒邮件。如果没有指定模板,邮件会使用默认的 task-reminder 模板。 |
任务活动中与电子邮件相关元素:
元素 | 数目 | 描述 |
---|---|---|
notification | 0..1 | 当一个任务被分配时立即发送一封提醒邮件。如果没有指定模板,邮件会使用默认的 task-notification 模板。 |
reminder | 0..1 | 根据指定的时间间隔发送提醒邮件。如果没有指定模板,邮件会使用默认的 task-reminder 模板。 |
上述的模板都定义在 jbpm.mail.templates.xml 中。
task-notification 模板定义如下:
<mail-template name='task-notification'>
<to users="${task.assignee}"/>
<subject>${task.name}</subject>
<text><![CDATA[Hi ${task.assignee},
Task "${task.name}" has been assigned to you.
${task.description}
Sent by jBPM]]></text>
</mail-template>
task-reminder 模板定义如下:
<mail-template name="rectify-template">
<to addresses="${addressee}" />
<cc users="bb" groups="innerparty" />
<bcc groups="thinkpol" />
<subject>rectify ${newspaper}</subject>
<text>${newspaper} ${date} ${details}</text>
</mail-template>
notification 元素的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
continue | 字符串枚举 {sync、async、exclusive} | sync | 可选 | 以同步、同步还是独占模式发送 notification 提醒邮件。 |
reminder 元素的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
duedate | 延迟时间(可包含表达式的字符串) | 无 | 必需 | reminder 提醒电子邮件在任务产生后延迟多少时间后发送。 |
repeat | 间隔时间(可包含表达式的字符串) | 无 | 可选 | reminder 提醒电子邮件每隔多少时间再发送一次,直到任务被办理。 |
continue | 字符串枚举 {sync、async、exclusive} | sync | 可选 | 以同步、同步还是独占模式发送 reminder 提醒邮件。 |
以后我们会 jBPM 的邮件能力进行详细介绍,敬请期待哦O(∩_∩)O~