在Flowable中,Task是整个工作流的“执行单元”,是流程得以具体实施和向前推进的基石。
可以通俗地理解,流程图(BPMN)是计划,而Task就是这个计划中需要被完成的具体待办事项。
一、 Task 的主要类型
在BPMN规范中,Task有多种类型,但对于应用开发者来说,最核心、最常用的是以下两种:
1. 用户任务 (User Task)
图标:一个左上角有“小人”图标的圆角矩形。
定义:代表需要人工参与和处理的工作环节。这是工作流应用中交互最频繁的部分。
示例:
“经理审批报销单”
“申请人填写请假表单”
“仓库管理员确认库存”
核心:当流程执行到用户任务节点时,它会在数据库的 ACT_RU_TASK 表中创建一条任务记录,然后暂停,等待外部(通常是用户)的指令来完成它。
2. 服务任务 (Service Task)
图标:一个带有“齿轮”图标的圆角矩形。
定义:代表由系统自动执行的后台工作,无需人工干预。
示例:
“调用外部API发送短信通知”
“根据订单数据生成PDF发票”
“更新数据库中的订单状态”
核心:当流程执行到服务任务节点时,它会立即、自动地执行预定义的逻辑(如调用一个Java类或执行一个表达式),执行完毕后自动向前推进,整个过程对用户是透明的。
其他任务类型:还包括脚本任务(Script Task)、业务规则任务(Business Rule Task)、邮件任务(Mail Task)等,它们都是服务任务的特定变种,用于执行特定类型的自动化逻辑。
接下来的内容,我们将重点聚焦于与开发者关系最密切的用户任务 (User Task)。
二、 用户任务 (User Task) 的完整生命周期
理解一个用户任务从诞生到消亡的全过程,是掌握Flowable开发的关键。
1. 创建 (Creation)
时机:当流程实例的执行流到达BPMN模型中的一个用户任务节点时,Flowable引擎会自动在数据库中创建一条任务记录。
结果:在 ACT_RU_TASK 表中新增一行数据,包含了任务的ID、名称、办理人(如果已指定)、创建时间等信息。
2. 分配 (Assignment)
任务创建后,必须明确由谁来处理。这就是任务分配,主要有三种策略:
| 策略 | BPMN 属性 | 说明 |
|---|---|---|
| 直接办理人 | flowable:assignee | 将任务直接分配给一个用户。只有该用户能看到并处理此任务。 |
| 候选用户 | lowable:candidateUsers | 指定多个可以处理此任务的用户。他们都能看到任务,但需要先“认领”。 |
| 候选组 | flowable:candidateGroups | 指定一个或多个用户组。该组下的所有成员都是任务的候选人。 |
动态分配:在实际应用中,这些属性的值通常不是写死的,而是使用EL表达式(如 ${initiator})或方法表达式(如 ${myBean.findManager(execution)})来动态计算得出。
3. 查询 (Querying)
用户需要一个“待办事项列表”来查看分配给他们的任务。这通过 TaskService 的查询API实现。
核心API:taskService.createTaskQuery()
常用查询条件:
.taskAssignee(userId): 查询直接分配给某用户的任务。
.taskCandidateUser(userId): 查询某用户作为候选人的任务。
.taskCandidateGroup(groupName): 查询某用户组作为候选组的任务。
.processInstanceId(processInstanceId): 查询某个流程实例下的所有活动任务。
示例代码:
// 查询用户'manager-jack'的所有待办任务(包括直接分配和作为候选人的)
List<Task> tasks = taskService.createTaskQuery()
.taskCandidateOrAssigned("manager-jack") // 常用组合查询
.orderByTaskCreateTime().desc()
.list();
4. 认领 (Claiming) - 仅针对候选任务
对于候选任务(即只有candidateUsers或candidateGroups的任务),它没有明确的办理人(assignee字段为null)。在处理之前,必须有一个候选人将其认领,表示“这件事我来跟进”。
核心API:taskService.claim(String taskId, String userId)
作用:
将任务的assignee字段设置为认领人的ID。
任务从所有候选人的“公共待办池”中消失,只出现在认领人的“个人待办”列表中。
防止同一个任务被多人重复处理。
取消认领:taskService.unclaim(String taskId),将任务退回公共池。
5. 完成 (Completion)
这是推动流程前进的核心动作。当用户处理完任务后,调用complete方法。
核心API:taskService.complete(String taskId, Map<String, Object> variables)
作用:
从 ACT_RU_TASK 表中删除该任务记录。
在 ACT_HI_TASKINST (历史任务) 表中更新该任务的状态为“已完成”。
将传入的variables(流程变量)更新到流程实例中。
流程引擎根据BPMN模型,计算并执行下一步的流转。
三、 Task 的高级特性
1. 任务监听器 (Task Listener)
你可以在任务生命周期的特定事件点(create, assignment, complete, delete)挂载自定义的Java逻辑。
用途:实现与核心业务无关的“切面”逻辑,如:
任务创建时:发送邮件/钉钉通知给办理人。
任务分配时:记录分配日志。
任务完成时:更新外部系统的状态。
任务监听器(Task Listener)是实现业务与流程解耦、添加“非核心”业务逻辑的利器。下面我将为你设计一个非常贴合企业日常开发的Demo,并提供完整的代码和配置说明。
智能的“经理审批”任务通知系统
业务需求:
在一个请假流程中,当“经理审批”任务产生时,我们希望系统能实现以下智能化的通知功能,而不是简单地发个邮件。
任务创建时 (create event):
工作时间(周一至周五,9:00-18:00):立即通过钉钉/企业微信发送一条消息给经理,提醒他有新审批任务。
非工作时间:为了不打扰经理休息,只发送一封邮件作为备忘。
任务分配时 (assignment event):
这个场景通常用于会签或转派。当任务的assignee发生变化时(比如经理A把任务转给了经理B),在操作日志中记录一条:“任务 [任务ID] 已由 [原办理人] 转派给 [新办理人]”。
任务完成时 (complete event):
计算该审批任务的处理时长,如果超过24小时,标记为“审批超时”,并记录到审批效率分析表中,用于后续的流程优化分析。
实现步骤
步骤 1:创建任务监听器 Java 类
我们需要创建一个或多个实现了 org.flowable.task.service.delegate.TaskListener 接口的Java类。为了结构清晰,我们为每个事件创建一个单独的监听器。
1.1. 任务创建通知监听器 (TaskCreateNotificationListener)
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.delegate.TaskListener;
import org.springframework.stereotype.Component;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
// 注册为Spring Bean,以便在BPMN中通过表达式引用
@Component("taskCreateNotificationListener")
public class TaskCreateNotificationListener implements TaskListener {
// 假设这是注入的钉钉和邮件服务
// @Autowired private DingTalkService dingTalkService;
// @Autowired private EmailService emailService;
@Override
public void notify(DelegateTask delegateTask) {
String assignee = delegateTask.getAssignee();
String taskName = delegateTask.getName();
String processInstanceId = delegateTask.getProcessInstanceId();
// 模拟获取流程发起人
String initiator = (String) delegateTask.getVariable("initiator");
// 核心逻辑:判断是否为工作时间
if (isWorkingHours()) {
// 工作时间,发钉钉
String dingTalkMessage = String.format(
"Hi %s, 您有一个新的审批任务需要处理!\n- 任务名称: %s\n- 申请人: %s\n- 点击链接处理: http://myapp.com/tasks/%s",
assignee, taskName, initiator, delegateTask.getId()
);
System.out.println("【钉钉发送】: " + dingTalkMessage);
// dingTalkService.send(assignee, dingTalkMessage);
} else {
// 非工作时间,发邮件
String emailMessage = String.format(
"【邮件提醒】您有一个新的审批任务 '%s' (来自: %s) 已进入您的待办列表,请在工作时间处理。",
taskName, initiator
);
System.out.println("【邮件发送】: " + emailMessage);
// emailService.send(assignee, "新审批任务提醒", emailMessage);
}
}
private boolean isWorkingHours() {
LocalDateTime now = LocalDateTime.now();
DayOfWeek day = now.getDayOfWeek();
LocalTime time = now.toLocalTime();
// 周一到周五
if (day.getValue() >= 1 && day.getValue() <= 5) {
// 9:00 - 18:00
return !time.isBefore(LocalTime.of(9, 0)) && !time.isAfter(LocalTime.of(18, 0));
}
return false;
}
}
1.2. 任务分配日志监听器 (TaskAssignmentLogListener)
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.delegate.TaskListener;
import org.springframework.stereotype.Component;
@Component("taskAssignmentLogListener")
public class TaskAssignmentLogListener implements TaskListener {
// 假设这是注入的操作日志服务
// @Autowired private OperationLogService logService;
@Override
public void notify(DelegateTask delegateTask) {
String taskId = delegateTask.getId();
String originalAssignee = delegateTask.getOriginalAssignee(); // 获取原始办理人
String newAssignee = delegateTask.getAssignee();
// 只有在办理人确实发生变化时才记录
if (originalAssignee != null && !originalAssignee.equals(newAssignee)) {
String logMessage = String.format(
"任务转派记录:任务 '%s' (%s) 已由用户 '%s' 转派给 '%s'。",
taskId, delegateTask.getName(), originalAssignee, newAssignee
);
System.out.println("【操作日志】: " + logMessage);
// logService.addLog(logMessage);
}
}
}
1.3. 任务完成效率分析监听器 (TaskCompletionAnalysisListener)、
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.delegate.TaskListener;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
@Component("taskCompletionAnalysisListener")
public class TaskCompletionAnalysisListener implements TaskListener {
// 假设这是注入的效率分析服务
// @Autowired private EfficiencyAnalyticsService analyticsService;
@Override
public void notify(DelegateTask delegateTask) {
Date createTime = delegateTask.getCreateTime();
Date completeTime = new Date(); // 任务完成时,当前时间就是完成时间
long processingMillis = Duration.between(createTime.toInstant(), completeTime.toInstant()).toMillis();
long processingHours = processingMillis / (1000 * 60 * 60);
System.out.println(String.format("【效率分析】: 任务 '%s' 处理耗时 %d 小时。", delegateTask.getId(), processingHours));
// 如果处理时长超过24小时
if (processingHours > 24) {
String analysisRecord = String.format(
"审批超时:任务 '%s', 办理人 '%s', 耗时 %d 小时。",
delegateTask.getName(), delegateTask.getAssignee(), processingHours
);
System.out.println("【效率分析】记录到数据库: " + analysisRecord);
// analyticsService.recordTimeout(delegateTask.getId(), delegateTask.getAssignee(), processingHours);
}
}
}
步骤 2:在 BPMN 中配置监听器
现在,打开你的BPMN流程图(例如 leave-process.bpmn20.xml),找到“经理审批”这个用户任务节点,并为其添加监听器配置。
你可以在 Flowable Modeler 的属性面板中,找到 “监听器 (Listeners)” 这一项,然后添加。
对应的 XML 看起来是这样的:
<userTask id="managerApprovalTask" name="经理审批" flowable:assignee="${managerId}">
<extensionElements>
<!-- 1. 配置 'create' 事件的监听器 -->
<flowable:taskListener
event="create"
class="com.yourcompany.flowable.listeners.TaskCreateNotificationListener">
</flowable:taskListener>
<!-- 2. 配置 'assignment' 事件的监听器,使用表达式方式 -->
<flowable:taskListener
event="assignment"
type="expression"
expression="${taskAssignmentLogListener.notify(task)}">
</flowable:taskListener>
<!-- 3. 配置 'complete' 事件的监听器 -->
<flowable:taskListener
event="complete"
type="delegateExpression"
expression="${taskCompletionAnalysisListener}">
</flowable:taskListener>
</extensionElements>
</userTask>
配置方式详解:
event: 指定监听器触发的事件,如 create, assignment, complete, delete。
class: 直接指定监听器类的完全限定名。Flowable会直接new一个实例,无法使用Spring注入。
expression: 执行一段EL表达式。这里我们调用了Spring Bean的方法,并手动传入了task对象。task是Flowable在执行此表达式时提供的内置变量,代表DelegateTask。
delegateExpression (推荐): 指定一个实现了TaskListener接口的Spring Bean的ID。Flowable会从Spring容器中获取这个Bean,并调用其notify方法。这是与Spring集成的最佳方式,因为它支持依赖注入。
2. 任务局部变量 (Task-Local Variables)
如之前讨论的,这是只属于单个任务的变量,任务完成后即销毁。非常适合存储仅用于当前任务UI展示或临时计算的、不想污染全局流程变量的数据。
3. 表单 (Forms)(本章节不做详细说明,后续章节会有详细介绍)
用户任务是与用户交互的载体,因此它通常需要关联一个表单来收集或展示数据。
BPMN 属性:flowable:formKey
作用:将一个在Flowable Form Modeler中创建的表单,或一个外部URL(如你的前端页面路由)与该任务关联起来。当用户处理任务时,系统可以根据formKey为他渲染正确的界面。
总结
Task 是Flowable工作流的核心执行单元,它定义了“谁,在何时,需要做什么事”。
用户任务 (User Task) 是流程中的人工处理节点,它的生命周期包括创建、分配、查询、认领、完成等一系列标准化操作,这些操作都通过强大的 TaskService API来完成。
服务任务 (Service Task) 则是流程中的自动化处理节点,由系统在后台自动完成。
1108

被折叠的 条评论
为什么被折叠?



