首先上一下流程图:
其次上一下流程设计xml源码:
<?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: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="smartProcess" name="智能会签流程"> <startEvent id="startevent1" name="Start" activiti:initiator="initiator"></startEvent> <endEvent id="endevent1" name="End"></endEvent> <userTask id="submitApp" name="提交申请" activiti:assignee="${initiator}"></userTask> <userTask id="countersign" name="部门会签" activiti:candidateUsers="${countersignCandidateUser}"> <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="countersignCandidateUsers" activiti:elementVariable="countersignCandidateUser"></multiInstanceLoopCharacteristics> </userTask> <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway> <userTask id="countersignFail" name="会签不通过" activiti:candidateUsers="20000"></userTask> <userTask id="countersignSuccess" name="全票通过" activiti:candidateUsers="10000"></userTask> <sequenceFlow id="flow1" name="" sourceRef="startevent1" targetRef="submitApp"></sequenceFlow> <sequenceFlow id="flow2" name="" sourceRef="submitApp" targetRef="countersign"></sequenceFlow> <sequenceFlow id="flow5" name="" sourceRef="countersign" targetRef="exclusivegateway1"></sequenceFlow> <sequenceFlow id="flow6" name="" sourceRef="exclusivegateway1" targetRef="countersignFail"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${countersignYes!=countersignSize}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow7" name="" sourceRef="exclusivegateway1" targetRef="countersignSuccess"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${countersignYes==countersignSize}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow8" name="" sourceRef="countersignSuccess" targetRef="endevent1"></sequenceFlow> <sequenceFlow id="flow9" name="" sourceRef="countersignFail" targetRef="endevent1"></sequenceFlow> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_smartProcess"> <bpmndi:BPMNPlane bpmnElement="smartProcess" id="BPMNPlane_smartProcess"> <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1"> <omgdc:Bounds height="35" width="35" x="50" y="50"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1"> <omgdc:Bounds height="35" width="35" x="670" y="50"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="submitApp" id="BPMNShape_submitApp"> <omgdc:Bounds height="55" width="105" x="110" y="40"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="countersign" id="BPMNShape_countersign"> <omgdc:Bounds height="55" width="105" x="260" y="40"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1"> <omgdc:Bounds height="40" width="40" x="400" y="47"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="countersignFail" id="BPMNShape_countersignFail"> <omgdc:Bounds height="55" width="105" x="490" y="110"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="countersignSuccess" id="BPMNShape_countersignSuccess"> <omgdc:Bounds height="55" width="105" x="490" y="-7"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1"> <omgdi:waypoint x="85" y="67"></omgdi:waypoint> <omgdi:waypoint x="110" y="67"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2"> <omgdi:waypoint x="215" y="67"></omgdi:waypoint> <omgdi:waypoint x="260" y="67"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5"> <omgdi:waypoint x="365" y="67"></omgdi:waypoint> <omgdi:waypoint x="400" y="67"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6"> <omgdi:waypoint x="420" y="87"></omgdi:waypoint> <omgdi:waypoint x="542" y="110"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7"> <omgdi:waypoint x="440" y="67"></omgdi:waypoint> <omgdi:waypoint x="542" y="48"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8"> <omgdi:waypoint x="595" y="20"></omgdi:waypoint> <omgdi:waypoint x="687" y="50"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9"> <omgdi:waypoint x="542" y="110"></omgdi:waypoint> <omgdi:waypoint x="687" y="85"></omgdi:waypoint> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </definitions>
其次,再上一下我的测试类代码:
import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.activiti.engine.HistoryService; import org.activiti.engine.IdentityService; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.spring.impl.test.SpringActivitiTestCase; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.xyzq.gipms.domain.bpm.ProcessBusinessParam; import com.xyzq.gipms.services.bpm.ProcessBusinessService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext-dataSource_h2.xml","classpath:applicationContext-common.xml"}) //@ContextConfiguration(locations={"classpath:applicationContext-dataSource_mysql.xml","classpath:applicationContext-common.xml"}) //@ContextConfiguration(locations={"classpath:applicationContext-dataSource_oracle.xml","classpath:applicationContext-common.xml"}) @SuppressWarnings("unused") public class SmartProcessTest extends SpringActivitiTestCase { @Autowired private RepositoryService repositoryService; @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Autowired private HistoryService historyService; @Autowired private IdentityService identityService; @Autowired private ProcessBusinessService processBusinessService; @Test public void testNativeProcess(){ List<String> nextStepCandidateUsers = new ArrayList<String>(); nextStepCandidateUsers.add("3130"); nextStepCandidateUsers.add("3131"); nextStepCandidateUsers.add("3132"); Map<String,Object> variables = new HashMap<String,Object>(); variables.put("countersignSize", nextStepCandidateUsers.size()); //设置全局流程变量 variables.put("countersignCandidateUsers", nextStepCandidateUsers);//设置会签环节任务指派 //启动流程 String currentSessionUserId = "721"; identityService.setAuthenticatedUserId(currentSessionUserId); String processInstanceId = runtimeService.startProcessInstanceByKey("smartProcess", variables).getId(); System.out.println("当前执行流程实例Id:"+processInstanceId ); //提交申请 String taskId = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(currentSessionUserId).singleResult().getId(); taskService.complete(taskId); //会签1 currentSessionUserId = "3130"; taskId = taskService.createTaskQuery().processInstanceId(processInstanceId).taskCandidateUser(currentSessionUserId).singleResult().getId(); setVariable(processInstanceId, true); taskService.complete(taskId); //会签2 currentSessionUserId = "3131"; taskId = taskService.createTaskQuery().processInstanceId(processInstanceId).taskCandidateUser(currentSessionUserId).singleResult().getId(); setVariable(processInstanceId, false); taskService.complete(taskId); //会签3 currentSessionUserId = "3132"; taskId = taskService.createTaskQuery().processInstanceId(processInstanceId).taskCandidateUser(currentSessionUserId).singleResult().getId(); setVariable(processInstanceId, true); taskService.complete(taskId); //会签结束判断流程走向 long count1 = taskService.createTaskQuery().processInstanceId(processInstanceId).taskCandidateUser("10000").count(); //[通过] 判断是否有待办 long count2 = taskService.createTaskQuery().processInstanceId(processInstanceId).taskCandidateUser("20000").count(); //[未通过] 判断是否有待办 System.out.println("通过节点待办数量: "+count1+", 未通过节点待办数量: "+count2); System.out.println("按照全票通过的策略来判定会签是否通过:"+(count1>count2)); } private void setVariable(String executionId, boolean isPass){ final String YES_KEY = "countersignYes"; final String NO_KEY = "countersignNo"; //取出流程实例中存储的自定义变量值 Object countersignYes = runtimeService.getVariable(executionId, YES_KEY); Object countersignNo = runtimeService.getVariable(executionId, NO_KEY); int _countersignYes = countersignYes==null?0:Integer.parseInt(countersignYes.toString()); int _countersignNo = countersignNo==null?0:Integer.parseInt(countersignNo.toString()); //设置新值 _countersignYes = isPass?(_countersignYes+1) : _countersignYes; _countersignNo = isPass?(_countersignNo) : (_countersignNo+1); System.out.println("_countersignYes:"+(_countersignYes) +", _countersignNo:"+(_countersignNo)); runtimeService.setVariable(executionId, YES_KEY, _countersignYes); runtimeService.setVariable(executionId, NO_KEY, _countersignNo); } }
最后简要简述一下实现过程:
会签活动节点默认包含三个流程变量
nrOfInstances 实例总数
nrOfActiveInstances 当前还没有完成的实例
nrOfCompletedInstances 已经完成的实例个数
要完成包含业务意义的同意/不同意,通过/不通过等组合的会签结果判定走哪一条后续路径,这个就需要自己定义变量。我一共定义了三个变量:
countersignSize 本次会签的参与人数
countersignYes 本次会签给与通过的人数
countersignNo 本次会签给与不通过的人数
最终可以通过这几个变量组合形成多种会签结果判定策略。最后提一点这个跟会签(多实例里边的)completionCondition配置有所区分,两者可以组合使用。
<completionCondition>${nrOfCompletedInstances/nrOfInstances >=
0.6
}</completionCondition>
当然上述仅仅是一个简单的测试示例,真正能形成强大的业务支撑平台还需要多一些考虑,本文仅当抛砖引玉。