通用回绝
从这章开始,就正式进入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命令,就能将流程路由到任意节点,一开始咱们也想靠这个实现任意节点路由的性能,但认真一想外面的坑十分多,遇到子流程,并行审批等简单的流程时,会产生很多矛盾点,想想也就放弃了。