消息撤回:
用户开启审批流程后,想撤回该条流程,可以在想撤回的节点添加消息边界事件,画图如下:
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:activiti="http://activiti.org/bpmn" id="sample-diagram" targetNamespace="http://activiti.org/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="verifyNotifyV1" name="文档审阅" isExecutable="true">
<bpmn2:startEvent id="StartEvent_1" name="开始">
<bpmn2:outgoing>Flow_125gy0d</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:userTask id="Activity_06hj36k" name="文档审阅" activiti:formKey="Activity_06hj36k" activiti:assignee="${verify}">
<bpmn2:extensionElements>
<activiti:formProperty id="FormProperty_3v9ukk1--__!!radio--__!!审批意见--__!!i--__!!同意--__--不同意" />
<activiti:executionListener class="cn.piesat.document.listener.VerifyTaskListener" event="end">
<activiti:field name="state">
<activiti:string>0</activiti:string>
</activiti:field>
</activiti:executionListener>
<activiti:taskListener class="cn.piesat.document.listener.NotifyListener" event="create" />
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_125gy0d</bpmn2:incoming>
<bpmn2:outgoing>Flow_03pvffh</bpmn2:outgoing>
<bpmn2:multiInstanceLoopCharacteristics activiti:collection="${verifies}" activiti:elementVariable="verify">
<bpmn2:loopCardinality xsi:type="bpmn2:tFormalExpression">${num}</bpmn2:loopCardinality>
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">${nrOfCompletedInstances/nrOfInstances==1}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
<bpmn2:endEvent id="Event_11jqygc" name="结束">
<bpmn2:incoming>Flow_03pvffh</bpmn2:incoming>
<bpmn2:incoming>Flow_14edxvs</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_03pvffh" name="监听" sourceRef="Activity_06hj36k" targetRef="Event_11jqygc">
<bpmn2:extensionElements>
<activiti:executionListener class="cn.piesat.document.listener.VerifyStateListener" event="take" />
</bpmn2:extensionElements>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="Flow_14edxvs" sourceRef="Event_1km4ti7" targetRef="Event_11jqygc" />
<bpmn2:boundaryEvent id="Event_1km4ti7" name="消息撤回" attachedToRef="Activity_06hj36k">
<bpmn2:outgoing>Flow_14edxvs</bpmn2:outgoing>
<bpmn2:messageEventDefinition id="MessageEventDefinition_0lylqlx" messageRef="Message_06usbn7" />
</bpmn2:boundaryEvent>
<bpmn2:sequenceFlow id="Flow_125gy0d" name="存储消息撤回任务节点" sourceRef="StartEvent_1" targetRef="Activity_06hj36k">
<bpmn2:extensionElements>
<activiti:executionListener class="cn.piesat.document.listener.FileWithDrawExecutionListener" event="take">
<activiti:field name="withdrawTask">
<activiti:string>Activity_06hj36k:Message_2vb9r75</activiti:string>
</activiti:field>
<activiti:field name="countersigned">
<activiti:string>0</activiti:string>
</activiti:field>
</activiti:executionListener>
</bpmn2:extensionElements>
</bpmn2:sequenceFlow>
</bpmn2:process>
<bpmn2:message id="Message_0o9mw30" name="Message_2fg586j" />
<bpmn2:message id="Message_1vz3ayq" name="Message_3e0avui" />
<bpmn2:message id="Message_06usbn7" name="Message_2vb9r75" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="verifyNotify">
<bpmndi:BPMNEdge id="Flow_03pvffh_di" bpmnElement="Flow_03pvffh">
<di:waypoint x="570" y="258" />
<di:waypoint x="722" y="258" />
<bpmndi:BPMNLabel>
<dc:Bounds x="629" y="243" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_14edxvs_di" bpmnElement="Flow_14edxvs">
<di:waypoint x="550" y="316" />
<di:waypoint x="550" y="340" />
<di:waypoint x="740" y="340" />
<di:waypoint x="740" y="276" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_125gy0d_di" bpmnElement="Flow_125gy0d">
<di:waypoint x="378" y="258" />
<di:waypoint x="470" y="258" />
<bpmndi:BPMNLabel>
<dc:Bounds x="387" y="226" width="77" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="342" y="240" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="349" y="283" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_11jqygc_di" bpmnElement="Event_11jqygc">
<dc:Bounds x="722" y="240" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="729" y="216" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_06hj36k_di" bpmnElement="Activity_06hj36k">
<dc:Bounds x="470" y="218" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1j1exxy_di" bpmnElement="Event_1km4ti7">
<dc:Bounds x="532" y="280" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="568" y="303" width="44" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
在流程图中我配置了监听器,绘制BPMN图时,将能撤回的任务节点编号(任务节点的key)和消息名称通过注入字段存储至数据库(也可以存储到redis等,方便获取就行),从执行监听器中可以获取。此处是会签多实例任务,在任务节点配置了代理人、完成条件等参数信息,同时配置了任务监听和执行监听,任务监听实现消息通知,当到达该节点时,触发事件类型为create,也就是任务创建的时候触发,通过监听器获取到当前任务执行人,这里在监听器里通过websocket给执行人推送消息通知(也可以换成别的方式比如发送短信等)这里没有贴消息推送代码,有点多,需要的话自行百度一下哈,执行监听器在这里注入执行人的审批状态信息,触发事件为任务完成时,通过监听器字段注入获取状态,修改审批人的状态信息。
如果测试可改为普通任务节点,不需要加完成条件,代理人可暂时写审批人的用户名,若不需要消息通知,可将任务监听器去掉。
执行监听器代码如下:
package cn.piesat.document.listener;
import cn.piesat.common.constant.ProcessVariableConstant;
import cn.piesat.common.constant.UserConstants;
import cn.piesat.common.utils.spring.SpringUtils;
import cn.piesat.document.domain.FileBaseInfo;
import cn.piesat.document.service.IFileBaseInfoService;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.Expression;
/**
* 文档字段注入撤回监听器
*/
public class FileWithDrawExecutionListener implements ExecutionListener {
//撤回节点注入字段信息
private Expression withdrawTask;
//注入会签任务key
private Expression countersigned;
@Override
public void notify(DelegateExecution execution) {
//存储撤回消息名称
Object withdrawTaskValue = withdrawTask.getValue(execution);
Object countersignedValue = countersigned.getValue(execution);
execution.setVariable(ProcessVariableConstant.ACT_COUNTERSIGNED, countersignedValue.toString());
execution.setVariable(ProcessVariableConstant.ACT_WITHDRAW_TASK, withdrawTaskValue.toString());
FileBaseInfo baseInfo = FileBaseInfo.builder()
//设置消息撤回状态 为撤回
.remark(withdrawTaskValue.toString() + ":" + UserConstants.NORMAL)
.id(Long.parseLong(execution.getProcessInstanceBusinessKey()))
.build();
SpringUtils.getBean(IFileBaseInfoService.class).updateFileBaseInfo(baseInfo);
}
}
package cn.piesat.document.listener;
import cn.piesat.common.constant.Constants;
import cn.piesat.common.constant.ProcessConstant;
import cn.piesat.common.core.domain.model.LoginUser;
import cn.piesat.common.core.redis.RedisCache;
import cn.piesat.common.utils.DateUtils;
import cn.piesat.common.utils.SequenceBuilder;
import cn.piesat.common.utils.spring.SpringUtils;
import cn.piesat.system.domain.SysNotice;
import cn.piesat.system.service.ISysNoticeService;
import cn.piesat.websocket.controller.MessageStompController;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import java.util.Arrays;
import java.util.Collection;
/**
* 文件审批状态监听
*/
public class NotifyListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
//获取执行人
String assignee = delegateTask.getAssignee();
SysNotice notice = new SysNotice();
notice.setNoticeId(SequenceBuilder.sequenceId());
notice.setNoticeOperator(assignee);
notice.setNoticeTitle("您有一条待批任务," + assignee);
notice.setStatus(ProcessConstant.UNREAD);//未读
notice.setCreateTime(DateUtils.getNowDate());
notice.setNoticeType(ProcessConstant.NOTICE);//通知
System.out.println("消息通知:" + assignee);
//获取执行人入库,判断是否在线,在线发送通知,不在线入库
SpringUtils.getBean(ISysNoticeService.class).insertNotice(notice);
RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
Collection<String> keys = redisCache.keys(Constants.LOGIN_TOKEN_KEY + "*");
MessageStompController stompController = SpringUtils.getBean(MessageStompController.class);
for (String key : keys) {
LoginUser user = redisCache.getCacheObject(key);
if (user.getUsername().equals(assignee)) {
//在线发送通知
stompController.sendToUserChatNotice(assignee, Arrays.asList(notice));
break;
}
}
}
}
在开启流程时存入任务节点和消息名称,通过监听器存入是否能撤回的状态存入数据库,如Activity_0c0cb45:Message_05se1fv:0,这样 返回任务列表时,前端判断状态显示撤回按钮,当任务审批后,在监听器修改该数据状态为1,页面不显示撤回按钮,撤回消息时,前端将Activity_0c0cb45:Message_05se1fv:0 和实例Id作为参数传到后台,通过这两个参数查询当前任务执行节点,调用runtimeService.messageEventReceived()撤回接口,撤回消息,同时修改数据状态。代码如下:
/**
* 撤回文档审批
*/
@GetMapping(value = "msgBack")
public AjaxResult msgBack(String instanceId, String remark, Long id) {
if (remark == null) {
return AjaxResult.error("消息名称为空!");
}
String[] split = remark.split(":");
//通过流程id和消息名称,判断当前任务执行节点是否可以消息撤回。
Execution exec = runtimeService.createExecutionQuery()
//消息名称
.messageEventSubscriptionName(split[1])
.processInstanceId(instanceId)
.singleResult();
if (exec != null) {
runtimeService.messageEventReceived(split[1], exec.getId());
FileBaseInfo build = FileBaseInfo.builder()
.id(id)
.approvalStatus(ProcessConstant.WITHDRAW_APPROVAL)
.remark(split[0] + ":" + split[1] + ":" + UserConstants.EXCEPTION).build();
fileBaseInfoService.updateFileBaseInfo(build);
return AjaxResult.success("撤回成功");
} else {
return AjaxResult.error("撤回失败!");
}
}