在真实的工作流应用中,除了线性的“同意”和“驳回”,灵活的流程控制能力(如撤销、撤回、退回)是必不可少的。
下面,我将详细解释这四种常见流程操作的概念、实现思路以及在 Flowable + Spring Boot 项目中的具体代码实现。
首先,我们必须明确这几个词在工作流上下文中的精确含义,因为混淆它们会导致错误的实现。
区别
操作 | 中文名称 | 含义解释 | 操作发起人 | 实现方式 |
---|---|---|---|---|
Reject | 驳回 | 审批人不同意,让流程按照预设的“失败”路径继续执行,通常直接流向结束。 | 审批人 | BPMN标准建模 |
Cancel | 撤销 | 流程发起人或管理员决定彻底终止整个流程实例,不再继续。 | 发起人/管理员 | 调用API |
Withdraw | 撤回 | 任务提交者在下一步处理前,主动将已提交的任务收回,流程回到自己的节点。 | 任务提交者 | 调用API |
Return | 退回 | 审批人将当前任务退还给上一个节点的处理人,要求其修改或补充信息。 | 审批人 | BPMN建模 或 调用API |
1. 驳回 (Reject)
这是最简单、最标准的操作,完全通过BPMN建模实现。
实现思路:
在审批节点后,使用一个排他网关 (Exclusive Gateway),根据审批人提交的流程变量(如 approved 或 outcome)来决定流程走向。
BPMN 模型示例:
经理审批 (Manager Approval) 节点:审批人完成任务时,需要提交一个名为 outcome 的流程变量,其值为 approve 或 reject。
审批决定 (Decision) 网关:
连接到“结束(批准)”的顺序流,条件为:
o
u
t
c
o
m
e
=
=
′
a
p
p
r
o
v
e
′
连接到“结束(驳回)”的顺序流,条件为:
{outcome == 'approve'} 连接到“结束(驳回)”的顺序流,条件为:
outcome==′approve′连接到“结束(驳回)”的顺序流,条件为:{outcome == ‘reject’}
Java 代码实现:
在完成任务时,传入对应的流程变量即可。
@Service
public class LeaveProcessService {
@Autowired
private TaskService taskService;
/**
* 审批人完成任务
* @param taskId 任务ID
* @param outcome 决策结果 ("approve" 或 "reject")
* @param comment 审批意见
*/
public void completeManagerTask(String taskId, String outcome, String comment) {
Map<String, Object> variables = new HashMap<>();
variables.put("outcome", outcome);
variables.put("managerComment", comment);
taskService.complete(taskId, variables);
}
}
2. 撤销 (Cancel / Revoke)
指强行终止整个流程实例。
实现思路:
这不属于流程的正常路径,而是一个外部管理操作。它通过调用 RuntimeService 的 API 来实现。
关键 API: RuntimeService.deleteProcessInstance(String processInstanceId, String deleteReason)
Java 代码实现:
通常由流程发起人或有权限的管理员调用。
@Service
public class ProcessControlService {
@Autowired
private RuntimeService runtimeService;
/**
* 撤销流程实例
* @param processInstanceId 流程实例ID
* @param reason 撤销原因
*/
public void cancelProcessInstance(String processInstanceId, String reason) {
// 可以在这里加权限校验,例如:只有发起人或管理员才能撤销
// ...
runtimeService.deleteProcessInstance(processInstanceId, reason);
}
}
注意:
此操作会删除所有与该流程实例相关的运行时数据。
历史数据(ACT_HI_ 表)中,该流程实例的 DELETE_REASON_ 字段会被更新为传入的 reason,END_TIME_ 也会被设置。*
3. 撤回 (Withdraw)
指任务提交者将已到下一环节的任务收回。例如,员工提交请假申请后,经理还未审批,员工自己发现填错了,想撤回重新编辑。
实现思路:
这是一个“逆向”操作,需要改变流程的当前活动节点。Flowable 提供了强大的 ChangeActivityState API 来实现此功能。
关键 API: RuntimeService.createChangeActivityStateBuilder()
Java 代码实现:
这个逻辑相对复杂,需要以下步骤:
找到当前活动的任务(如“经理审批”)。
找到要撤回到的目标节点(如“提交请假申请”)。
使用 ChangeActivityStateBuilder 执行“移动”。
@Service
public class ProcessControlService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@Autowired
private RepositoryService repositoryService;
/**
* 撤回任务
* @param taskId 当前任务的ID (例如,经理审批任务的ID)
*/
@Transactional
public void withdrawTask(String taskId) {
// 1. 校验:任务是否存在,是否可以被撤回等
Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult();
if (currentTask == null) {
throw new RuntimeException("任务不存在,无法撤回!");
}
// 2. 获取要撤回到的目标节点信息
// 思路:查找该流程实例的历史活动,找到上一个用户任务
HistoricActivityInstance previousTaskActivity = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(currentTask.getProcessInstanceId())
.activityType("userTask") // 查找用户任务
.finished() // 查找已完成的
.orderByHistoricActivityInstanceEndTime().desc() // 按结束时间倒序
.list().get(0); // 第一个就是最近完成的用户任务
String targetActivityId = previousTaskActivity.getActivityId();
// 3. 执行“移动”操作
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(currentTask.getProcessInstanceId())
.moveActivityIdTo(currentTask.getTaskDefinitionKey(), targetActivityId) // 将当前活动节点移动到目标节点
.changeState();
}
}
注意:
这是一个原子操作,moveActivityIdTo 会删除当前任务,并在目标节点创建一个新任务。
上述代码是通用思路,实际项目中可能需要更精细的逻辑来确定“上一个节点”究竟是哪一个。
4. 退回 (Return / Send Back)
指审批人将任务退还给上一个节点。例如,经理发现员工提交的材料不全,退回让他补充。
实现思路:
有两种实现方式,强烈推荐第一种。
方式一:BPMN建模(推荐)
在模型中明确画出“退回”的路径。这使得流程图非常清晰,易于理解和维护。
BPMN 模型示例:
在“经理审批”后的网关,增加第三条出口路径,命名为“退回”,并让它指向上一个节点“提交请假申请”。
完成任务时,传入的 outcome 变量可以有三个值:approve, reject, return。
网关根据 ${outcome == ‘return’} 条件进入退回路径。
Java 代码实现:
与“驳回”类似,只是多了一个选项。
public void completeManagerTask(String taskId, String outcome, String comment) {
// outcome 可以是 "approve", "reject", "return"
Map<String, Object> variables = new HashMap<>();
variables.put("outcome", outcome);
variables.put("managerComment", comment);
taskService.complete(taskId, variables);
}
方式二:调用API(不推荐,但灵活)
如果不想在图上画出复杂的连线,或者需要支持“退回到任意历史节点”这种极度灵活的功能,可以使用与“撤回”相同的 ChangeActivityState API。
Java 代码实现:
与撤回的逻辑非常相似,但操作的发起人是当前任务的审批人,并且退回的目标节点通常是固定的上一个节点。
// 代码结构与 withdrawTask 方法基本一致
// 只是在权限校验和业务逻辑上有所不同
public void returnTask(String currentTaskId, String targetActivityId) {
Task currentTask = taskService.createTaskQuery().taskId(currentTaskId).singleResult();
// ... 权限和状态校验 ...
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(currentTask.getProcessInstanceId())
.moveActivityIdTo(currentTask.getTaskDefinitionKey(), targetActivityId)
.changeState();
}
ps:只是最基础的模式跟使用,后续还会提及