系列文章目录(springboot整合activiti5)
事件网关是基于事件的网关,比如有如下的需求,在申请人发起了申请之后,一分钟之内没有收到任务触发事件,则该任务自动进入到部门领导审批,如果有事件触发,则进入到财务部门审批。看起来挺像排他网关的,但是这里是基于事件的,可以采用事件网关。事件网关的XML代码描述如下
<eventBasedGateway id="eventgateway1" name="Event Gateway"></eventBasedGateway>
对应的流程图如下
在事件网关之后,只能使用intermediateCatchEvent
类型,不能使用其他类型。在intermediateCatchEvent
内部可以定义timerEventDefinition
(定时事件定义),比如一分钟触发
<intermediateCatchEvent id="timerintermediatecatchevent1" name="TimerCatchEvent">
<timerEventDefinition>
<timeDuration>PT1M</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
也可以定义signalEventDefinition
(信号事件触发)
<intermediateCatchEvent id="signalintermediatecatchevent1" name="SignalCatchEvent">
<signalEventDefinition signalRef="alertSignal"></signalEventDefinition>
</intermediateCatchEvent>
完整的流程定义文件如下
<?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.zioer.com/reimbursement-10">
<signal id="alertSignal" name="alert"></signal>
<process id="reimbursement-10" name="费用报销-10" isExecutable="true">
<startEvent id="startevent1" name="Start" activiti:initiator="startUserId" activiti:formKey="start.form"></startEvent>
<userTask id="usertask1" name="部门领导审批" activiti:candidateGroups="leadergroup" activiti:formKey="conform1.form"></userTask>
<userTask id="usertask3" name="申请人确认" activiti:assignee="${startUserId}" activiti:formKey="conform3.form"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow7" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
<eventBasedGateway id="eventgateway1" name="Event Gateway"></eventBasedGateway>
<sequenceFlow id="flow8" sourceRef="startevent1" targetRef="eventgateway1"></sequenceFlow>
<intermediateCatchEvent id="timerintermediatecatchevent1" name="TimerCatchEvent">
<timerEventDefinition>
<timeDuration>PT1M</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
<sequenceFlow id="flow9" sourceRef="eventgateway1" targetRef="timerintermediatecatchevent1"></sequenceFlow>
<sequenceFlow id="flow10" sourceRef="timerintermediatecatchevent1" targetRef="usertask1"></sequenceFlow>
<intermediateCatchEvent id="signalintermediatecatchevent1" name="SignalCatchEvent">
<signalEventDefinition signalRef="alertSignal"></signalEventDefinition>
</intermediateCatchEvent>
<userTask id="usertask4" name="财务部门审批" activiti:candidateGroups="feegroup" activiti:formKey="conform2.form"></userTask>
<sequenceFlow id="flow11" sourceRef="eventgateway1" targetRef="signalintermediatecatchevent1"></sequenceFlow>
<sequenceFlow id="flow12" sourceRef="signalintermediatecatchevent1" targetRef="usertask4"></sequenceFlow>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow13" sourceRef="usertask1" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow14" sourceRef="usertask4" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow15" sourceRef="exclusivegateway1" targetRef="usertask3"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_reimbursement-10">
<bpmndi:BPMNPlane bpmnElement="reimbursement-10" id="BPMNPlane_reimbursement-10">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="60.0" y="147.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="280.0" y="90.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="520.0" y="137.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="680.0" y="147.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="eventgateway1" id="BPMNShape_eventgateway1">
<omgdc:Bounds height="40.0" width="40.0" x="141.0" y="144.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="timerintermediatecatchevent1" id="BPMNShape_timerintermediatecatchevent1">
<omgdc:Bounds height="35.0" width="35.0" x="210.0" y="100.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="signalintermediatecatchevent1" id="BPMNShape_signalintermediatecatchevent1">
<omgdc:Bounds height="35.0" width="35.0" x="210.0" y="210.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask4" id="BPMNShape_usertask4">
<omgdc:Bounds height="55.0" width="105.0" x="280.0" y="200.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
<omgdc:Bounds height="40.0" width="40.0" x="430.0" y="151.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="625.0" y="164.0"></omgdi:waypoint>
<omgdi:waypoint x="680.0" y="164.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
<omgdi:waypoint x="95.0" y="164.0"></omgdi:waypoint>
<omgdi:waypoint x="141.0" y="164.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
<omgdi:waypoint x="161.0" y="144.0"></omgdi:waypoint>
<omgdi:waypoint x="161.0" y="117.0"></omgdi:waypoint>
<omgdi:waypoint x="210.0" y="117.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow10" id="BPMNEdge_flow10">
<omgdi:waypoint x="245.0" y="117.0"></omgdi:waypoint>
<omgdi:waypoint x="280.0" y="117.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow11" id="BPMNEdge_flow11">
<omgdi:waypoint x="161.0" y="184.0"></omgdi:waypoint>
<omgdi:waypoint x="161.0" y="227.0"></omgdi:waypoint>
<omgdi:waypoint x="210.0" y="227.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow12" id="BPMNEdge_flow12">
<omgdi:waypoint x="245.0" y="227.0"></omgdi:waypoint>
<omgdi:waypoint x="280.0" y="227.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow13" id="BPMNEdge_flow13">
<omgdi:waypoint x="385.0" y="117.0"></omgdi:waypoint>
<omgdi:waypoint x="450.0" y="117.0"></omgdi:waypoint>
<omgdi:waypoint x="450.0" y="151.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow14" id="BPMNEdge_flow14">
<omgdi:waypoint x="385.0" y="227.0"></omgdi:waypoint>
<omgdi:waypoint x="450.0" y="227.0"></omgdi:waypoint>
<omgdi:waypoint x="450.0" y="191.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow15" id="BPMNEdge_flow15">
<omgdi:waypoint x="470.0" y="171.0"></omgdi:waypoint>
<omgdi:waypoint x="520.0" y="164.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
对应的控制层代码TimeController如下
package com.xquant.platform.test.activiti.controller;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.activiti.engine.FormService;
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.engine.history.HistoricDetail;
import org.activiti.engine.impl.persistence.entity.HistoricProcessInstanceEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ExecutionQuery;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 事件网关
*/
@Controller
@RequestMapping(value = "/time")
public class TimeController {
@Autowired
private RepositoryService repositoryService;
@Autowired
private FormService formService;
@Autowired
private TaskService taskService;
@Autowired
private IdentityService identityService;
@Autowired
private HistoryService historyService;
@Autowired
private RuntimeService runtimeService;
@RequestMapping(value = "/add")
public String add(Model model,HttpSession session) {
if (session.getAttribute("userId") == null){
return "redirect:/login/";
}
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("reimbursement-10")
.latestVersion().singleResult();
Object startForm = formService.getRenderedStartForm(processDefinition.getId());
model.addAttribute("formData", startForm);
return "reimbursement-5_start";
}
/**
* 提交启动流程
*/
@RequestMapping(value = "/start/save")
public String saveStartForm(Model model,HttpServletRequest request,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
Map formProperties = PageData(request);
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey("reimbursement-10")
.latestVersion().singleResult();
String processDefinitionId = processDefinition.getId();
try {
identityService.setAuthenticatedUserId(userId);
formService.submitStartFormData(processDefinitionId, formProperties);
} finally {
identityService.setAuthenticatedUserId(null);
}
return "redirect:/time/list";
}
@RequestMapping(value = "/list")
public String list(Model model,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
List<Task> tasks = new ArrayList<Task>();
//获得当前用户的任务
tasks = taskService.createTaskQuery().processDefinitionKey("reimbursement-10")
.taskCandidateOrAssigned(userId)
.active()
.orderByTaskId().desc().list();
model.addAttribute("list", tasks);
return "reimbursement-5_list";
}
/**
* 接收信号
* @param model
* @param session
* @return
*/
@RequestMapping(value = "/receive")
public String receive(Model model,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
ExecutionQuery executionQuery = runtimeService.createExecutionQuery();
List<Execution> executions = executionQuery.signalEventSubscriptionName("alert").list();
for (int j=0;j<executions.size();j++){
Execution execution = executions.get(j);
if (execution != null){
// 接收信号处理
runtimeService.signalEventReceived("alert", execution.getId());
}
}
List<Task> tasks = new ArrayList<Task>();
//获得当前用户的任务
tasks = taskService.createTaskQuery().processDefinitionKey("reimbursement-10")
.taskCandidateOrAssigned(userId)
.active()
.orderByTaskId().desc().list();
model.addAttribute("list", tasks);
return "reimbursement-5_list";
}
/**
* 任务签收
*/
@RequestMapping(value = "/claim/{taskId}")
public String claim(@PathVariable("taskId") String taskId, HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
taskService.claim(taskId, userId);
return "redirect:/time/list";
}
/**
* 初始化启动流程,读取启动流程的表单字段来渲染start form
*/
@RequestMapping(value = "/startform/{taskId}")
public String StartTaskForm(@PathVariable("taskId") String taskId,Model model,HttpSession session) throws Exception {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
Object taskForm = formService.getRenderedTaskForm(taskId);
String startUserId = (String) taskService.getVariable(taskId, "startUserId");
model.addAttribute("formData", taskForm);
model.addAttribute("taskId", taskId);
model.addAttribute("startUserId", startUserId);
return "reimbursement-5_edit";
}
/**
* 提交启动流程
*/
@RequestMapping(value = "/startform/save/{taskId}")
public String saveTaskForm(@PathVariable("taskId") String taskId,HttpSession session,HttpServletRequest request) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
Map formProperties = PageData(request);
try {
identityService.setAuthenticatedUserId(userId);
formService.submitTaskFormData(taskId, formProperties);
} finally {
identityService.setAuthenticatedUserId(null);
}
return "redirect:/time/list";
}
@RequestMapping(value = "/hlist")
public String historylist(Model model,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
List<Map> hlist = new ArrayList<Map>();
List historylist = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey("reimbursement-10")
.startedBy(userId).list();
for (int i=0;i<historylist.size();i++){
Map<String, Object> map = new HashMap<String, Object>();
HistoricProcessInstanceEntity hpe = (HistoricProcessInstanceEntity) historylist.get(i);
map.put("id", hpe.getId());
map.put("startUserId", hpe.getStartUserId());
map.put("processInstanceId", hpe.getProcessInstanceId());
map.put("endTime", hpe.getEndTime());
map.put("startTime", hpe.getStartTime());
if (hpe.getEndTime() == null){
List<Task> taskList = taskService.createTaskQuery().processInstanceId(hpe.getProcessInstanceId()).active().list();
String taskName = "";
for (int j=0;j<taskList.size();j++){
if (taskList.get(j) != null){
taskName = taskName == "" ? taskList.get(j).getName() : taskName + "," + taskList.get(j).getName();
}
}
if (taskName != ""){
map.put("name", taskName);
}
}else{
map.put("name", "已完成");
}
hlist.add(map);
}
//获得当前用户的任务
model.addAttribute("list", hlist);
return "reimbursement_hlist";
}
@RequestMapping(value = "/hview/{pId}")
public String historyView(@PathVariable("pId") String pId,Model model,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
List<HistoricDetail> details = historyService
.createHistoricDetailQuery()
.processInstanceId(pId)
.orderByTime().asc()
.list();
model.addAttribute("list", details);
return "reimbursement_hview";
}
public Map PageData(HttpServletRequest request){
Map properties = request.getParameterMap();
Map returnMap = new HashMap();
Iterator entries = properties.entrySet().iterator();
Map.Entry entry;
String name = "";
String value = "";
while (entries.hasNext()) {
entry = (Map.Entry) entries.next();
name = (String) entry.getKey();
Object valueObj = entry.getValue();
if(null == valueObj){
value = "";
}else if(valueObj instanceof String[]){
String[] values = (String[])valueObj;
for(int i=0;i<values.length;i++){
value = values[i] + ",";
}
value = value.substring(0, value.length()-1);
}else{
value = valueObj.toString();
}
returnMap.put(name, value);
}
return returnMap;
}
}
其中主要添加了一个接收事件触发的action,用户通过浏览器发起触发动作
for (int j=0;j<executions.size();j++){
Execution execution = executions.get(j);
if (execution != null){
// 接收信号处理
runtimeService.signalEventReceived("alert", execution.getId());
}
}
另外还需要注意一个地方,就是打开异步任务开关,否则一分钟之后是不会触发定时任务的。
logging.level.org.activiti.engine=trace
spring.activiti.async-executor-enabled=true
spring.activiti.async-executor-activate=true
通过http://localhost:8080/time/add
创建事件
保存之后在列表页面,此时是看不到任何任务的
等待一分钟之后,刷新页面,就可以看到任务了
此时的当前节点为部门领导审批。
再次http://localhost:8080/time/add
创建一个任务,创建完成之后立即调用http://localhost:8080/time/receive
请求。可以看到此时的任务很快就出现了,当前节点为财务部门审批
。
从以上的示例中我们可以看出事件网关的触发都是基于事件的,如果事件尚未发生,那么对应的流程不会继续的。