Build a Complex Approval Workflow with Spring Boot & Activiti Using Exclusive Gateway
This tutorial walks through creating a leave‑approval process in Spring Boot 2.3.12 with Activiti 7.1, illustrating how an Exclusive Gateway routes requests based on leave days, how flags control loop‑back or completion, and provides full BPMN, service, and controller code examples.
Environment: Spring Boot 2.3.12.RELEASE + Activiti 7.1.0.M6.
This article demonstrates a complex approval workflow that uses an Exclusive Gateway to route leave requests: if the number of leave days is less than or equal to three, the request is approved by the department manager; otherwise it proceeds to the general manager after department‑manager approval. The decision is captured by a flag variable— flag==1 ends the process, while flag==0 returns the flow to the employee request node.
The BPMN XML definition of the process is shown below, including start event, user tasks, exclusive gateway, conditional sequence flows, and end event.
<code><?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.xg.com">
<process id="holiday3" name="holiday3" isExecutable="true">
<startEvent id="startevent1" name="Start"/>
<userTask id="usertask1" name="员工请假" activiti:assignee="${userId}"/>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"/>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"/>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="exclusivegateway1"/>
<userTask id="usertask2" name="部门经理审批" activiti:assignee="${d_mgr}"/>
<sequenceFlow id="flow3" name="${days <=3}" sourceRef="exclusivegateway1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days <=3}]]></conditionExpression>
</sequenceFlow>
<userTask id="usertask3" name="部门经理审批" activiti:assignee="${d_mgr}"/>
<userTask id="usertask4" name="总经理审批" activiti:assignee="${g_mgr}"/>
<sequenceFlow id="flow4" name="${days > 3}" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days > 3}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" sourceRef="usertask3" targetRef="usertask4"/>
<endEvent id="endevent1" name="End"/>
<sequenceFlow id="flow6" name="${flag==1}" sourceRef="usertask2" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${flag==1}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" name="${flag==1}" sourceRef="usertask4" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${flag==1}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow8" name="${flag==0}" sourceRef="usertask2" targetRef="usertask1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${flag==0}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow9" name="${flag==0}" sourceRef="usertask4" targetRef="usertask1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${flag==0}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_holiday3">
<bpmndi:BPMNPlane bpmnElement="holiday3" id="BPMNPlane_holiday3">
<!-- Diagram shapes omitted for brevity -->
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
</code>The service layer provides methods to start a process instance with dynamic assignee variables and to complete tasks while setting process variables.
<code>@Service
public class HolidayService {
private static final Logger logger = LoggerFactory.getLogger(HolidayService2.class);
@Resource
private RuntimeService runtimeService;
@Resource
private TaskService taskService;
public ProcessInstance startProcessInstanceAssignVariables(String processDefinitionId, Map<String, Object> variables) {
Authentication.setAuthenticatedUserId((String)variables.get("assignee"));
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, variables);
logger.info("流程定义ID: {}", processInstance.getProcessDefinitionId());
logger.info("流程实例ID: {}", processInstance.getId());
logger.info("BussinessKey: {}", processInstance.getBusinessKey());
return processInstance;
}
public void executionTask(Map<String, Object> variables, String instanceId) {
Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult();
if (task == null) {
logger.error("任务【{}】不存在", instanceId);
throw new RuntimeException("任务【" + instanceId + "】不存在");
}
taskService.setVariables(task.getId(), variables);
taskService.complete(task.getId(), variables);
}
}
</code>The controller exposes two REST endpoints: one to start the leave process and another to submit approval data.
<code>@RestController
@RequestMapping("/holidays2")
public class HolidayController2 {
@Resource
private HolidayService holidayService;
@GetMapping("/start")
public R startProcess(String userId, String processDefinitionId) {
Map<String, Object> variables = new HashMap<>();
variables.put("userId", userId);
ProcessInstance instance = holidayService.startProcessInstanceAssignVariables(processDefinitionId, variables);
ProcessInstanceDTO dto = new ProcessInstanceDTO();
dto.setInstanceId(instance.getId());
return R.success(dto);
}
@GetMapping("/apply")
public R fillApply(@RequestParam Map<String, Object> variables) {
String instanceId = (String) variables.remove("instanceId");
if (StringUtils.isEmpty(instanceId)) {
return R.failure("未知任务");
}
holidayService.executionTask(variables, instanceId);
return R.success();
}
}
</code>Testing screenshots show the process being started, the employee submitting a request, the department manager handling the task, and the loop back when the manager rejects (flag=0). When the manager approves (flag=1), the process proceeds to completion.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.