flowable 流程动态设置监听器(非xml中定义)及发起时从驳回节点开始审批实现
·
一、flowable 使用代码动态修改监听器
1、配置类
@Configuration
public class FlowableGlobListenerConfig {
@Lazy
@Autowired
private TaskStartListener taskStartListener;
@Lazy
@Autowired
private TaskCompleteListener taskCompletedListener;
@Lazy
@Autowired
private ActivityCompletedListener activityCompletedListener;
@Bean
@Primary
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> globalListenerConfigurer() {
return engineConfiguration -> engineConfiguration.setTypedEventListeners(this.customFlowableListeners());
}
/**
* 监听类配置 flowable监听器级别
*
* @return java.util.Map<java.lang.String, java.util.List < org.flowable.common.engine.api.delegate.event.FlowableEventListener>>
*/
private Map<String, List<FlowableEventListener>> customFlowableListeners() {
Map<String, List<FlowableEventListener>> listenerMap = Maps.newHashMap();
listenerMap.put(FlowableEngineEventType.TASK_CREATED.name(),
new ArrayList<>(Collections.singletonList(taskStartListener)));
listenerMap.put(FlowableEngineEventType.TASK_COMPLETED.name(),
new ArrayList<>(Collections.singletonList(taskCompletedListener)));
listenerMap.put(FlowableEngineEventType.ACTIVITY_COMPLETED.name(),
new ArrayList<>(Collections.singletonList(activityCompletedListener)));
return listenerMap;
}
}
说明:通过注册关注的事件,实现对应的监听逻辑实现相应业务逻辑
2、流程节点创建时的监听逻辑
import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.landray.kmss.sys.notify.webservice.NotifyTodoSendContextVo;
import cn.togeek.geekbidder.crm.contract.entity.ContractEntity;
import cn.togeek.geekbidder.crm.contract.mapper.ContractMapper;
import cn.togeek.geekbidder.crm.contract.service.ContractNewServiceImpl;
import cn.togeek.geekbidder.crm.contract.util.TitleUtil;
import cn.togeek.maple.message.api.enums.MessageType;
import cn.togeek.maple.message.api.enums.ReadState;
import cn.togeek.maple.message.api.model.Message;
import cn.togeek.maple.message.api.model.Recipient;
import cn.togeek.maple.message.api.service.MessageService;
import cn.togeek.maple.platform.api.model.User;
import cn.togeek.maple.platform.api.service.UserService;
import cn.togeek.maple.process.TaskStartInvoker;
import cn.togeek.maple.process.api.model.ProcessMessage;
import cn.togeek.maple.process.api.util.SpringContextUtil;
import cn.togeek.oa.TaskQueryService;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.common.engine.impl.event.FlowableEntityEventImpl;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("taskStartListener")
public class TaskStartListener implements FlowableEventListener {
private static final Logger log = LoggerFactory.getLogger(TaskStartListener.class);
@Autowired
private TaskService taskService;
@Autowired
private MessageService messageService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TaskQueryService taskQueryService;
@Autowired
private HistoryService historyService;
@Value("${system.url}")
private String url;
@Autowired
private ContractMapper contractMapper;
@Autowired
private ContractNewServiceImpl contractNewService;
/**
* 流程发起时统一通知逻辑
* process-task/processVariables/33a9feee-65a6-11ef-8ac0-024204500072
*/
public TaskStartListener() {
}
public void onEvent(FlowableEvent event) {
TaskEntity taskEntity = (TaskEntity)((FlowableEntityEventImpl)event).getEntity();
String taskId = taskEntity.getId();
List<IdentityLink> idList = this.taskService.getIdentityLinksForTask(taskId);
if (!CollectionUtils.isEmpty(idList)) {
List<Recipient> recipientList = new ArrayList();
idList.stream().distinct().forEach((identityLink) -> {
Recipient recipient;
if (StringUtils.isNotBlank(identityLink.getUserId()) && "assignee".equals(identityLink.getType())) {
recipient = new Recipient();
recipient.setId(identityLink.getUserId());
recipientList.add(recipient);
}
if (StringUtils.isNotBlank(identityLink.getUserId()) && "candidate".equals(identityLink.getType())) {
recipient = new Recipient();
recipient.setId(identityLink.getUserId());
recipientList.add(recipient);
} else if (StringUtils.isNotBlank(identityLink.getGroupId()) && "candidate".equals(identityLink.getType())) {
List<User> userList = this.userService.getUserByRole(identityLink.getGroupId());
userList.forEach((obj) -> {
Recipient r = new Recipient();
r.setId(obj.getId());
recipientList.add(r);
});
}
this.sendMessage(taskEntity, recipientList);
});
/**
* 流程通知oa系统相关人逻辑
* process-task/processVariables/33a9feee-65a6-11ef-8ac0-024204500072
* //todo 需确认与oa系统对应用户的关系、及连接点击时token信息的有效性
*/
List<String> ids = recipientList.stream().map(Recipient::getId).collect(Collectors.toList());
List<String> taskUsers = this.userService.getUsersByIds(ids).stream().map(User::getName).collect(Collectors.toList());
String instId = taskEntity.getProcessInstanceId();
HistoricProcessInstance instance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(instId).singleResult();
List<HistoricVariableInstance> list =
historyService.createHistoricVariableInstanceQuery().processInstanceId(instId).list();
String definitionKey = instance.getProcessDefinitionKey();
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(definitionKey)
.active()
.singleResult();
String deploymentId = processDefinition.getDeploymentId();
Map<String, Object> map = list.stream().filter(e->Objects.nonNull(e.getValue())).collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue, (o1, o2)->o2));
String processName = processDefinition.getName();
String activeTab = processDefinition.getCategory();
String businessKey = instance.getBusinessKey();
// String activeTab = OAConstant.PROCESS_MAP.get(definitionKey).get(1);
ContractEntity contractEntity;
if((contractEntity=contractMapper.selectById(businessKey))!=null) {
contractEntity.setCurrentApprovalUser(String.join(",", taskUsers));
contractMapper.updateById(contractEntity);
}
//根据规则创建subject
CompletableFuture.runAsync(()-> {
if(CollUtil.isEmpty(map)) {
return;
}
log.info("==================BEGIN异步同步oa代办BEGIN=================");
NotifyTodoSendContextVo vo = new NotifyTodoSendContextVo();
vo.setBusinessKey(instId);
vo.setModelId(instId);
vo.setType(2);
vo.setSubject(TitleUtil.buildSubject(map, processName));
// vo.setLink(url + "/process-task/processVariables/"+ instId);
vo.setLink(buildLink(deploymentId, instId, taskId, activeTab));
taskQueryService.syncAgency(vo, taskUsers);
log.info("==================END异步同步oa代办END=================");
});
}
Map<String, TaskStartInvoker> type = SpringContextUtil.getBeansOfType(TaskStartInvoker.class);
type.values().parallelStream().forEach(
(invoker) -> {
invoker.consume(event);
}
);
}
private String buildLink(String deployId, String procInsId, String taskId, String activeTab) {
return url+ "/process/record?deployId="+deployId+"&procInsId=="+procInsId+"&taskId="+taskId+"&activeTab="+activeTab;
}
public boolean isFailOnException() {
return false;
}
public boolean isFireOnTransactionLifecycleEvent() {
return false;
}
public String getOnTransaction() {
return null;
}
public void sendMessage(TaskEntity taskEntity, List<Recipient> recipientList) {
ProcessMessage processMessage = new ProcessMessage();
ProcessDefinition pd =
this.repositoryService.createProcessDefinitionQuery().processDefinitionId(taskEntity.getProcessDefinitionId()).singleResult();
processMessage.setDeployId(pd.getDeploymentId());
processMessage.setProcInsId(taskEntity.getProcessInstanceId());
processMessage.setRouterName("审批详情路由");
processMessage.setTaskId(taskEntity.getId());
processMessage.setCheckUrl("/process-task/runing/" + taskEntity.getId());
Message message = new Message();
message.setCreateTime(LocalDateTime.now());
message.setMessage("您有新的流程待办消息,请及时处理!");
message.setRecipients(recipientList);
message.setTitle("流程待办提醒");
message.setReadState(ReadState.NOT_READ);
message.setType(MessageType.MESSAGE);
message.setMessageType("PROCESS_TODO_MESSAGE");
String transmissionText = null;
try {
transmissionText = this.objectMapper.writeValueAsString(processMessage);
} catch (JsonProcessingException var8) {
log.error(var8.getMessage(), var8);
}
message.setTransmissionText(transmissionText);
this.messageService.sendMessage(message);
}
}
3、流程节点结束时逻辑
import cn.togeek.maple.process.TaskComplateInvoker;
import cn.togeek.maple.process.api.util.SpringContextUtil;
import java.util.Map;
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component("taskCompleteListener")
public class TaskCompleteListener implements FlowableEventListener {
private static final Logger log = LoggerFactory.getLogger(TaskCompleteListener.class);
public TaskCompleteListener() {
}
public void onEvent(FlowableEvent event) {
Map<String, TaskComplateInvoker> type = SpringContextUtil.getBeansOfType(TaskComplateInvoker.class);
type.values().parallelStream().forEach(
(invoker) -> {
invoker.consume(event);
}
);
}
public boolean isFailOnException() {
return false;
}
public boolean isFireOnTransactionLifecycleEvent() {
return false;
}
public String getOnTransaction() {
return null;
}
}
4、流程任意节点结束时
@Slf4j
@Component("activityCompletedListener")
public class ActivityCompletedListener implements FlowableEventListener {
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void onEvent(FlowableEvent event) {
if(event instanceof FlowableActivityEventImpl) {
FlowableActivityEventImpl activityEvent = (FlowableActivityEventImpl) event;
String activityType = activityEvent.getActivityType();
if(!"userTask".equals(activityType)) {
return;
}
String processDefinitionId = activityEvent.getProcessDefinitionId();
String processInstanceId = activityEvent.getProcessInstanceId();
ProcessInstance processInstance =
runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);
List<HistoricActivityInstance> allProcessNodes = historyService.createHistoricActivityInstanceQuery()
.activityId(processInstance.getActivityId())
.processInstanceId(processInstanceId)
.list();
List<HistoricActivityInstance> userTaskList =
allProcessNodes.stream().filter(e -> e.getActivityType().equals("userTask")).collect(Collectors.toList());
//判断节点列表中是否大于两个节点、第一个必为填报节点、第二个必为审批节点
String businessKey = processInstance.getBusinessKey();
if(userTaskList.size() >= 2) {
ListenerEvent listenerEvent = new ListenerEvent();
listenerEvent.setApprovalResults("PROGRESS_IN");
listenerEvent.setBusinessKey(businessKey);
listenerEvent.setDefinitionId(processDefinitionId);
listenerEvent.setProcessInstanceId(processInstanceId);
listenerEvent.setDefinitionKey(processDefinition.getKey());
listenerEvent.setDefinitionName(processDefinition.getName());
this.applicationEventPublisher.publishEvent(new ListenerEventSource(listenerEvent));
}
}
}
@Override
public boolean isFailOnException() {
return false;
}
@Override
public boolean isFireOnTransactionLifecycleEvent() {
return false;
}
@Override
public String getOnTransaction() {
return null;
}
}
二、流程跳过逻辑
/**
* 对已驳回的流程发起时选择从驳回节点开始审批
* @param oldInstId
* @param newInstId
*/
public void skipPreAgreeNodes(String oldInstId, String newInstId){
HistoricProcessInstance processInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(oldInstId).singleResult();
String deployId = Optional.ofNullable(processInstance)
.map(HistoricProcessInstance::getProcessDefinitionId)
.map(item -> item.substring(item.lastIndexOf(":") + 1))
.orElse(null);
Map<String, Object> record = this.flowTaskService.flowRecord(oldInstId, deployId);
List<FlowTask> flowList = (List<FlowTask>) record.getOrDefault("flowList", Lists.newArrayList());
if(flowList.size()<2) {
return;
}
FlowTask flowTask = flowList.get(0);
String rejectTaskKey = flowTask.getTaskDefKey();
List<FlowTask> agrees = flowList.stream().skip(1).limit(flowList.size() - 2).collect(Collectors.toList());
List<String> allNodes = agrees.stream()
.map(FlowTask::getTaskDefKey)
.distinct().collect(Collectors.toList());
for(String taskDefKey : allNodes) {
List<Task> list = taskService.createTaskQuery().processInstanceId(newInstId).list();
if(list.stream().anyMatch(e->rejectTaskKey.equals(e.getTaskDefinitionKey()))) {
if(agrees.stream().noneMatch(e->rejectTaskKey.equals(e.getTaskDefKey()))) {
break;
}
}
for(Task task : list) {
//筛选办理记录中节点定义id与当前id一致的记录
List<FlowTask> allTasks = agrees.stream()
.filter(e -> e.getTaskDefKey().equals(task.getTaskDefinitionKey()))
.collect(Collectors.toList());
String assignee = task.getAssignee();
for(FlowTask allTask : allTasks) {
//多实例节点时assignee不可能为空,需根据审批记录中的审批人id 判断需不需要跳过该用户
if(assignee !=null && !assignee.equals(allTask.getAssigneeId())) {
continue;
}
FlowTaskVo flowTaskVo = new FlowTaskVo();
BeanUtils.copyProperties(task, flowTaskVo);
flowTaskVo.setInstanceId(newInstId);
flowTaskVo.setComment(allTask.getComment().getComment());
flowTaskVo.setAssignee(allTask.getAssigneeId());
flowTaskVo.setUserId(allTask.getAssigneeId());
flowTaskVo.setTaskId(task.getId());
List<Attachment> attachmentList = allTask.getAttachmentList();
if(CollUtil.isNotEmpty(attachmentList)) {
List<MultipartFile> files = attachmentList.stream().map(f -> {
InputStream attachmentContent = taskService.getAttachmentContent(f.getId());
try {
return new MockMultipartFile(f.getName(), f.getName(), f.getType(), attachmentContent);
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
flowTaskVo.setFiles(files);
}
this.flowTaskService.complete(flowTaskVo);
}
}
}
}
三、流程结束时遇到的问题(结束了但业务表状态未修改成功)
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import cn.togeek.foundation.web.api.BusinessCode;
import cn.togeek.foundation.web.exception.BusinessException;
import cn.togeek.geekbidder.crm.contract.entity.ContractEntity;
import cn.togeek.geekbidder.crm.contract.enums.ApprovalState;
import cn.togeek.geekbidder.crm.contract.mapper.ContractNewMapper;
import cn.togeek.maple.process.api.enums.FlowCommentEnum;
import cn.togeek.maple.process.api.model.ListenerEvent;
import cn.togeek.maple.process.api.model.ListenerEventSource;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@Component
@AllArgsConstructor
@Slf4j
public class ContractFlowEventListener implements ApplicationListener<ListenerEventSource> {
@Autowired
private RuntimeService runtimeService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private ContractNewMapper contractNewMapper;
@Autowired
private HistoryService historyService;
/**
* 流程结束监听
*
* @param event
*/
@Override
public void onApplicationEvent(ListenerEventSource event) {
callBackWithStateCal(event);
}
public synchronized void callBackWithStateCal(ListenerEventSource event){
log.info("------------------流程结束回调--------------------");
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(event.getListenerEvent().getProcessInstanceId())
.singleResult();
if(Objects.isNull(processInstance)) {
return;
}
String deploymentId = processInstance.getDeploymentId();
ProcessDefinition processDefinition =
repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
if(Objects.isNull(processDefinition)) {
return;
}
String defKey = processDefinition.getKey();
//只对发布的事件中关注的事件进行处理
boolean needDeal = Arrays.stream(ContractApproveGlobal.values())
.map(ContractApproveGlobal::getDefKey)
.anyMatch(e -> e.equals(defKey));
log.info("------------------needDeal{}--------------------"+needDeal);
if(needDeal) {
String id = processInstance.getBusinessKey();
ListenerEvent listenerEvent = event.getListenerEvent();
String approvalResults = listenerEvent.getApprovalResults();
log.info("------------------approvalResults{}--------------------"+approvalResults);
ContractEntity contractEntity = contractNewMapper.selectById(id);
Assert.notNull(contractEntity, () -> {
throw new BusinessException(BusinessCode.CLIENT_ERROR.getCode(), String.format(
"合同id为"+ id + "的合同不存在,可能已被删除,请检查!!!"));
});
if(FlowCommentEnum.NORMAL.getType().equals(approvalResults)) {
ContractApproveGlobal dbNode = ContractApproveGlobal.getByCode(contractEntity.getApprovalNode());
ContractApproveGlobal nextNode = dbNode.getNearNode()[1];
contractEntity.setCurrentApprovalUser("无");
//上一个流程节点结束时将全局流程节点改为下一个节点
if(nextNode!=null && Integer.parseInt(nextNode.getCode()) <=3) {
// 审批通过
contractEntity.setApprovalState("0");
contractEntity.setApprovalNode(nextNode.getCode());
} else {
contractEntity.setApprovalState(ApprovalState.CHANGE.getCode());
}
}
else if(FlowCommentEnum.REJECT.getType().equals(approvalResults)) {
// 审批驳回
Integer rejectCnt = contractEntity.getRejectCnt();
contractEntity.setCurrentApprovalUser("无");
rejectCnt = rejectCnt == null ? 0 : rejectCnt;
contractEntity.setRejectCnt(rejectCnt+1);
contractEntity.setApprovalState(ApprovalState.TERMINATION.getCode());
}
else if("PROGRESS_IN".equals(approvalResults)) {
contractEntity.setApprovalState(ApprovalState.EXECUTE.getCode());
} else if(FlowCommentEnum.REBACK.getType().equals(approvalResults)) {
// 审批退回
contractEntity.setApprovalState(ApprovalState.UN_SUBMIT.getCode());
}
contractNewMapper.updateById(contractEntity);
}
}
}
原因总结:由于流程结束时使用了 runtimeService 取流程实例已经取不到了,导致后续的操作都失败了,使用 historyService 则可以避免此问题
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)