通用回绝
从这章开始,就正式进入 activiti 的实战开发,应用 activiti 实现各种审批动作,包含一些中国式流程操作,比方回退,咨询等,这些操作 activiti 的规范性能是没有的,但因为 activiti 不算简单,也比拟灵便,因而能够通过一些技巧或者变通的办法实现,这章就探讨 通用回绝
的实现。为什么叫通用回绝,因为在 activiti 里,失常的回绝都是通过连接线加条件判断实现,你能够定义一个变量如 outcome,回绝的时候给这个变量赋值REJECT
,在连接线上设置条件表达式从而实现回绝操作。如图:
项目经理回绝到发起人的表达式为${outcome=='REJECT'}
,在流程里设置好变量就能实现回绝操作:
taskService.setVariable(taskId, "outcome", "approve");
taskService.complete(taskId);
这种回绝实现形式长处是简略,规范反对,灵活性强,可能从任意节点回绝回任意节点,但毛病也是显著的
- 个别流程每个节点都有可能回绝,那就意味着每个节点都须要设置判断条件,如果都要回绝回发起人,那么都要跟发起人节点进行连贯,如果节点多的话会大大增加流程图的复杂度,让流程图变成一张“蜘蛛网”。
因而咱们须要一个通用回绝的性能,需要是,在任意节点回绝后主动回到发起人节点,发起人从新提交后流程从新开始。
那么面临的两个问题是
- 流程图中没有发起人节点,怎么造出这个发起人节点
- 流程曾经在流转中了,如何从新流转
咱们顺次解决以上两个问题
发起人节点解决
activiti 提供动静批改流程模型的 api,但批改流程模型后全局失效,所有的流程都会受影响,因而就算咱们能通过代码“造出”发起人几点,也是不可取的。其实认真想想,咱们是须要一个发起人节点,还是须要一个审批人是发起人的节点,显然,咱们的需要是前面那个,明确了这个情理后,问题就变得简略了,如何让以后节点的审批人变成发起人,计划能够是这样的:
- 删除以后节点所有的待办,只保留一个待办
- 将保留下来的那个待办审批人设置为发起人
通过以上两个步骤咱们能够实现回绝后将待办转移到发起人那里,当然为了在流程里可能获取到发起人,你应该在流程发动的时候将发起人信息存储到变量中。
那么问题又来了,咱们这是将以后节点伪造成了发起人节点,但假的毕竟是假的,等发起人一审批,就露馅了,因为流程会持续往下走,那么为了达到以假乱真的境地,咱们要持续实现以下两件事
- 审批接口须要晓得以后节点的审批是否是“伪造”的发起人节点
- 如果审批接口晓得了以后节点的审批是发起人发动的,那么就须要将流程从新拨回到第一个节点
第一个需要能够通过设置一个变量进行标识,第二个需要是咱们的下一个议题。
回绝实现代码参考:
public TaskResponse reject(TaskResponse task, String user) {
// 删除所有以后 task,保留一个,并且将该 task 的审批人设为发起人
// 设置 reject 标记
Task t = taskService.createTaskQuery()
.taskId(task.getTaskId())
.singleResult();
String instanceId = t.getProcessInstanceId();
List<Task> tasks = taskService.createTaskQuery()
.processInstanceId(instanceId)
.list();
Task luckyTask = tasks.get(0);
managementService.executeCommand(new ExecutionVariableDeleteCmd(t.getExecutionId()));
for (int i = 1; i < tasks.size(); ++i) {managementService.executeCommand(new TaskDeleteCmd(tasks.get(i).getId()));
managementService.executeCommand(new ExecutionVariableDeleteCmd(tasks.get(i).getExecutionId()));
}
// 将发起人设置为以后审批人
taskService.setAssignee(luckyTask.getId(), (String) taskService.getVariable(luckyTask.getId(), "submitter"));
// 设置变量标识以后状态是已回绝状态
taskService.setVariable(luckyTask.getId(), "status", "reject");
return this.taskResponse(t, instanceId);
}
审批的代码参考如下:
String status = (String) taskService.getVariable(taskId, "status");
if ("reject".equals(status)) {
// 发起人从新发动
this.rollbackFirstask(task, user);
} else {
// 失常审批
taskService.complete(taskId, taskParams);
}
流程从新发动
剩下最初一个问题,就算下面的代码中 rollbackFirstask
如何实现,该办法将流程拨回到第一个节点从新开始,在探讨实现之前,咱们须要理解下命令模式,也是设计模式中的一种,其实并不生疏,咱们能够在日常的开发中就用到了,但并不知道原来这个还有一个专门的名字。简略说就像 Linux 下的 shell 脚本,调用一个个命令一样,将每个独立的操作封装成一个命令(Command),由命令调用者(Command Executor)进行调用,每个命令只负责本人的业务逻辑,不与其余命令交互,上下文信息(Command Context)由命令调用者提供。activiti 就是采纳命令模式对流程资源进行操作,比方删除一个工作,会有一个 DeleteTaskCmd 的命令类。activiti 命令申明如下:
public interface Command<T> {T execute(CommandContext commandContext);
}
命令调用者执行命令的 execute 办法,命令能够通过 commandContext 获取上下文,commandContext 里蕴含了对所有资源的治理类。理解了命令模式后,咱们就能够开始执行咱们的回滚计划了,具体计划步骤:
-
- 革除现场,革除所有两头过程的变量
-
- 找到开始节点,调用 api 将流程拨回到开始节点
实现代码如下:
/**
* 流程回退到第一个节点
*
* @param context
* @param request
* @param user
* @return
*/
public TaskResponse rollbackFirstask(String taskId, String user) {
// 移除标记 REJECT 的 status
taskService.removeVariable(taskId, "status");
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 删除工作
managementService.executeCommand(new TaskDeleteCmd(request.getTaskId()));
// 删除变量
managementService.executeCommand(new ExecutionVariableDeleteCmd(task.getExecutionId()));
// 将流程回滚到第一个节点
managementService.executeCommand(new FlowToFirstCmd(task));
return this.taskResponse(task.getProcessInstanceId());
}
几个命令的实现如下:
TaskDeleteCmd
import org.activiti.engine.impl.cmd.NeedsActiveTaskCmd;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.*;
import java.util.List;
/**
* @Copyright: Shanghai Definesys Company.All rights reserved.
* @Description:
* @author: jianfeng.zheng
* @since: 2019/9/24 6:09 PM
* @history: 1.2019/9/24 created by jianfeng.zheng
*/
public class TaskDeleteCmd extends NeedsActiveTaskCmd<String> {public TaskDeleteCmd(String taskId) {super(taskId);
}
@Override
public String execute(CommandContext commandContext, TaskEntity currentTask) {TaskEntityManagerImpl taskEntityManager = (TaskEntityManagerImpl) commandContext.getTaskEntityManager();
ExecutionEntity executionEntity = currentTask.getExecution();
taskEntityManager.deleteTask(currentTask, "reject", false, false);
return executionEntity.getId();}
}
ExecutionVariableDeleteCmd
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.VariableInstanceEntity;
import org.activiti.engine.impl.persistence.entity.VariableInstanceEntityManager;
import java.util.List;
/**
* @Copyright: Shanghai Definesys Company.All rights reserved.
* @Description:
* @author: jianfeng.zheng
* @since: 2019/9/24 6:10 PM
* @history: 1.2019/9/24 created by jianfeng.zheng
*/
public class ExecutionVariableDeleteCmd implements Command<String> {
private String executionId;
public ExecutionVariableDeleteCmd(String executionId) {this.executionId = executionId;}
@Override
public String execute(CommandContext commandContext) {VariableInstanceEntityManager vm = commandContext.getVariableInstanceEntityManager();
List<VariableInstanceEntity> vs = vm.findVariableInstancesByExecutionId(this.executionId);
for (VariableInstanceEntity v : vs) {vm.delete(v);
}
return executionId;
}
}
FlowToFirstCmd
import com.definesys.mpaas.common.exception.MpaasBusinessException;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.task.Task;
import java.util.List;
/**
* @Copyright: Shanghai Definesys Company.All rights reserved.
* @Description:
* @author: jianfeng.zheng
* @since: 2019/9/25 12:36 AM
* @history: 1.2019/9/25 created by jianfeng.zheng
*/
public class FlowToFirstCmd implements Command<String> {
private Task task;
public FlowToFirstCmd(Task task) {this.task = task;}
@Override
public String execute(CommandContext context) {FlowElement startNode = this.getFirstNode(this.task, context);
ExecutionEntity executionEntity = context.getExecutionEntityManager().findById(task.getExecutionId());
executionEntity.setCurrentFlowElement(startNode);
context.getAgenda().planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
return executionEntity.getId();}
private FlowElement getFirstNode(Task task, CommandContext context) {HistoryService historyService = context.getProcessEngineConfiguration().getHistoryService();
HistoricActivityInstance startNode = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.activityType("startEvent")
.singleResult();
if (startNode == null) {throw new MpaasBusinessException("未找到开始节点");
}
RepositoryService repositoryService = context.getProcessEngineConfiguration().getRepositoryService();
org.activiti.bpmn.model.Process process = repositoryService.getBpmnModel(task.getProcessDefinitionId()).getMainProcess();
FlowElement node = process.getFlowElement(startNode.getActivityId());
return node;
}
}
总结
其实,略微革新下 FlowToFirstCmd 命令,就能将流程路由到任意节点,一开始咱们也想靠这个实现任意节点路由的性能,但认真一想外面的坑十分多,遇到子流程,并行审批等简单的流程时,会产生很多矛盾点,想想也就放弃了。