通用回绝

从这章开始,就正式进入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里蕴含了对所有资源的治理类。理解了命令模式后,咱们就能够开始执行咱们的回滚计划了,具体计划步骤:

    1. 革除现场,革除所有两头过程的变量
    1. 找到开始节点,调用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命令,就能将流程路由到任意节点,一开始咱们也想靠这个实现任意节点路由的性能,但认真一想外面的坑十分多,遇到子流程,并行审批等简单的流程时,会产生很多矛盾点,想想也就放弃了。