概述
加签本质上就是让多实例任务增加一个处理任务和新增处理人。在业务流程中常有一种情况是,如果A审批,那么直接通过,如果是B审批,则还需要C审批才能通过。后一类情况就特别适合使用加签。另外在转他人处理的问题上,普通任务节点可以通过自由跳转到本节点实现,但多实例任务中这样处理会导致该任务节点所有处理人重新开始处理任务,所以多实例任务的转他人处理不适合用自由跳转,更适合通过加签去实现。
实现
串行多实例任务加签
直接上源码
import org.activiti.engine.impl.context.Context;
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.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
public class SequenceContersignCmd implements Command {
private String taskId;
private Map<String, Object> variables;
public SequenceContersignCmd(String taskId, Map<String, Object> variables) {
this.taskId = taskId;
this.variables = variables;
}
public Object execute(CommandContext commandContext) {
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findTaskById(taskId);
ExecutionEntity executionEntity = taskEntity.getExecution();
//多实例任务总数加一
Integer nrOfInstances = (Integer)executionEntity.getVariable("nrOfInstances");
executionEntity.setVariable("nrOfInstances", nrOfInstances + 1);
// 设置流程变量
executionEntity.setVariables(variables);
return null;
}
}
串行多实例的加签非常简单,只需要调整任务总数变量nrOfInstances加一即可。因为串行多实例任务每个时刻最多只会有一个活动实例,所以当前活动实例数恒定是一,nrOfActiveInstances不用变。执行这个命令类,即可
并行多实例加签
import java.util.Date;
import java.util.Map;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
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.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
public class ParallelContersign implements Command {
private String taskId;
private Map<String, Object> variables;
public ParallelContersign(String taskId, Map<String, Object> variables) {
this.taskId = taskId;
this.variables = variables;
}
public Object execute(CommandContext commandContext) {
ProcessEngineConfigurationImpl pec = commandContext.getProcessEngineConfiguration();
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findTaskById(taskId);
ExecutionEntity executionEntity = taskEntity.getExecution();
// 设置流程变量
executionEntity.setVariables(variables);
ExecutionEntity parentExecutionEntity = executionEntity.getParent();
ExecutionEntity newExecutionEntity = parentExecutionEntity.createExecution();
newExecutionEntity.setActive(true);
newExecutionEntity.setConcurrent(true);
newExecutionEntity.setScope(false);
TaskEntity newTaskEntity = new TaskEntity();
newTaskEntity.setCreateTime(new Date());
newTaskEntity.setTaskDefinition(taskEntity.getTaskDefinition());
newTaskEntity.setProcessDefinitionId(taskEntity.getProcessDefinitionId());
newTaskEntity.setTaskDefinitionKey(taskEntity.getTaskDefinitionKey());
newTaskEntity.setProcessInstanceId(taskEntity.getProcessInstanceId());
newTaskEntity.setExecutionId(newExecutionEntity.getId());
newTaskEntity.setName(taskEntity.getName());
newTaskEntity.setId(pec.getIdGenerator().getNextId());
newTaskEntity.setExecution(newExecutionEntity);
pec.getTaskService().saveTask(newTaskEntity);
Integer nrOfInstances = (Integer)executionEntity.getVariable("nrOfInstances");
Integer nrOfActiveInstances = (Integer)executionEntity.getVariable("nrOfActiveInstances");
executionEntity.setVariable("nrOfInstances", nrOfInstances + 1);
executionEntity.setVariable("nrOfActiveInstances", nrOfActiveInstances + 1);
newExecutionEntity.setVariableLocal("loopCounter", nrOfInstances);
return null;
}
}
并行多实例的加签复杂一些,主要是并行多实例任务需要我们手动新建execution和对应的task。24-25行获取当前taskId对应的execution,30行再获取其父execution。31-34行通过父execution创建子execution并为其设置属性。35-45行新建task并设置其属性,最后进行保存。49行增加任务总数变量nrOfInstances,50行增加活动实例数,51行设置execution的局部变量loopCounter,loopCounter变量其实就是类似一个index,作为多实例任务的序列号。
转他人处理
文章开头提到了转他人处理的问题。这里准备以实例进行说明如何通过加签实现转他人处理。
首先创建命令类TransferToOtherCmd.java
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.context.Context;
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.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
public class TransferToOtherCmd implements Command {
private String taskId;
private Map<String, Object> variables;
private String assignee;
public TransferToOtherCmd(String taskId, Map<String, Object> variables, String assignee) {
this.taskId = taskId;
this.variables = variables;
this.assignee = assignee;
}
public Object execute(CommandContext commandContext) {
ProcessEngineConfigurationImpl pec = commandContext.getProcessEngineConfiguration();
TaskService taskService = pec.getTaskService();
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findTaskById(taskId);
ExecutionEntity executionEntity = taskEntity.getExecution();
String processDefinitionId = executionEntity.getProcessDefinitionId();
ProcessDefinitionEntity processDefinitionEntity = Context.getProcessEngineConfiguration().getDeploymentManager()
.findDeployedProcessDefinitionById(processDefinitionId);
ActivityImpl curActivityImpl = processDefinitionEntity.findActivity(taskEntity.getName());
List<String> assigneeList = (List<String>) executionEntity.getVariable("assigneeList");
if (curActivityImpl.getActivityBehavior() instanceof SequentialMultiInstanceBehavior) {
Integer nrOfInstances = (Integer) executionEntity.getVariable("nrOfInstances");
Integer loopCounter = (Integer) executionEntity.getVariable("loopCounter");
executionEntity.setVariableLocal("nrOfInstances", nrOfInstances + 1);
List<String> newAssigneeList = new ArrayList<String>();
int i=0;
for(; i<loopCounter + 1; i++) {
newAssigneeList.add(assigneeList.get(i));
}
newAssigneeList.add(assignee);
for(; i<assigneeList.size(); i++) {
newAssigneeList.add(assigneeList.get(i));
}
newAssigneeList.addAll(assigneeList);
executionEntity.setVariable("assigneeList", newAssigneeList);
} else {
ExecutionEntity parentExecutionEntity = executionEntity.getParent();
ExecutionEntity newExecutionEntity = parentExecutionEntity.createExecution();
newExecutionEntity.setActive(true);
newExecutionEntity.setConcurrent(true);
newExecutionEntity.setScope(false);
TaskEntity newTaskEntity = new TaskEntity();
newTaskEntity.setCreateTime(new Date());
newTaskEntity.setTaskDefinition(taskEntity.getTaskDefinition());
newTaskEntity.setProcessDefinitionId(taskEntity.getProcessDefinitionId());
newTaskEntity.setTaskDefinitionKey(taskEntity.getTaskDefinitionKey());
newTaskEntity.setProcessInstanceId(taskEntity.getProcessInstanceId());
newTaskEntity.setExecutionId(newExecutionEntity.getId());
newTaskEntity.setName(taskEntity.getName());
newTaskEntity.setId(pec.getIdGenerator().getNextId());
newTaskEntity.setExecution(newExecutionEntity);
newTaskEntity.setAssignee(assignee);
pec.getTaskService().saveTask(newTaskEntity);
Integer nrOfInstances = (Integer) executionEntity.getVariable("nrOfInstances");
Integer nrOfActiveInstances = (Integer) executionEntity.getVariable("nrOfActiveInstances");
executionEntity.setVariable("nrOfInstances", nrOfInstances + 1);
executionEntity.setVariable("nrOfActiveInstances", nrOfActiveInstances + 1);
newExecutionEntity.setVariableLocal("loopCounter", nrOfInstances);
}
// 设置流程变量
executionEntity.setVariables(variables);
taskService.complete(taskId);
return null;
}
}
类的属性assignee表示转给谁处理。这个转他人处理类基本融合了串行多实例和并行多实例加签的代码。这里假设多实例任务的collection属性为assigneeList,那么流程变量assigneeList的列表里面放置的是多实例任务的处理人。49-58行为串行多实例任务添加处理人。如果把转发目标处理人加到assigneeList尾部,那么任务提交后,下一个处理人不是目标处理人。例如我们原来指定张三、李四、王五作为三个处理人,张三处理的时候转给赵六去处理,如果把赵六加到assigneeList尾部,那么张三提交转他人处理后,下一个是李四处理,赵六放到最后了!而我们的预期应该是张三转赵六处理,张三提交后下一个处理人应该是赵六。所以要把赵六插到assigneeList中张三后面才对。如果张三是第一个人,那赵六就应该插在第一个元素后面。并行多实例则不需要这样做,只需要在75行对任务设置对应的处理人即可。最后在87行提交当前任务。
接下来测试一个串行多实例任务的实例,流程图如下:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="串行多实例任务" name="串行多实例任务" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="员工互评" name="员工互评" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true" activiti:collection="assigneeList" activiti:elementVariable="assignee">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="员工互评"></sequenceFlow>
<userTask id="领导审核" name="领导审核" activiti:assignee="丁总"></userTask>
<sequenceFlow id="flow2" sourceRef="员工互评" targetRef="领导审核"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow3" sourceRef="领导审核" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_串行多实例任务">
<bpmndi:BPMNPlane bpmnElement="串行多实例任务" id="BPMNPlane_串行多实例任务">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="150.0" y="320.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="员工互评" id="BPMNShape_员工互评">
<omgdc:Bounds height="55.0" width="105.0" x="230.0" y="310.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="领导审核" id="BPMNShape_领导审核">
<omgdc:Bounds height="55.0" width="105.0" x="380.0" y="310.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="530.0" y="320.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="185.0" y="337.0"></omgdi:waypoint>
<omgdi:waypoint x="230.0" y="337.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="335.0" y="337.0"></omgdi:waypoint>
<omgdi:waypoint x="380.0" y="337.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="485.0" y="337.0"></omgdi:waypoint>
<omgdi:waypoint x="530.0" y="337.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
我们定义一个简单的串行多实例任务的流程,设定多实例任务数为3,处理人的列表读取流程变量assigneeList。然后我们初始化流程引擎并部署该流程。然后启动流程的时候设置流程变量:
public void startProcessByIdWithVar() {
RuntimeService runtimeService = pe.getRuntimeService();
List<String> assigneeList = new ArrayList<String>();
assigneeList.add("张三");
assigneeList.add("李四");
assigneeList.add("王五");
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("assigneeList", assigneeList);
ProcessInstance pi = runtimeService.startProcessInstanceById("串行多实例任务:1:4", vars);
System.out.println("流程定义ID:" + pi.getProcessInstanceId());
System.out.println("流程实例ID:" + pi.getId());
}
之后观察数据库表
act_ru_execution表:
act_ru_task表:
act_ru_variable表:
通过act_ru_task表我们看到当前assignee是张三,接下来我们调用写好的TransferToOtherCmd命令:
public void TransferToOther() {
String taskId = "2513";
Map<String, Object> variables = new HashMap<String, Object>();
TransferToOtherCmd TransferToOtherCmd = new TransferToOtherCmd(taskId, variables, "赵六");
ServiceImpl service = (ServiceImpl)pe.getRepositoryService();
CommandExecutor commandExecutor = service.getCommandExecutor();
commandExecutor.execute(TransferToOtherCmd);
}
这个函数根据taskId,调用TransferToOtherCmd命令,把张三的任务提交给赵六。执行后,可以查看act_ru_task表:
此时任务处理人已经变味赵六。
至于并行多实例任务的情况,读者可以自行测试结果。
小结:
加签是业务流程中常见的情况,而是否需要用加签来作为转他人处理的手段,读者可以自行斟酌,本文只是抛砖引玉。除了加签之外,有时候可能还会有“减签”的业务情况。减签其实和加签原理差不多,对于串行多实例任务,无非就是减少任务总数变量nrOfInstances,对于并行多实例任务则除了修改任务总数变量nrOfInstances,还要修改活动实例数nrOfActiveInstances,并且被删除任务的loopCount变量以及为被删除任务loopCount大于被删除任务的都要减一,同时还要删除相关execution和task。不过本人暂时没遇到过业务上需要减签的情况。