package com.ycl.service.common; import com.alibaba.fastjson2.JSONObject; import com.ycl.common.constant.ProcessConstants; import com.ycl.common.core.domain.entity.SysDictData; import com.ycl.common.core.domain.entity.SysUser; import com.ycl.common.enums.FlowComment; import com.ycl.common.enums.business.TaskStatusEnum; import com.ycl.common.utils.SecurityUtils; import com.ycl.domain.entity.SysForm; import com.ycl.domain.vo.FormDetailVO; import com.ycl.flow.FindNextNodeUtil; import com.ycl.service.ISysFormService; import com.ycl.system.service.ISysDictDataService; import com.ycl.system.service.ISysDictTypeService; import com.ycl.system.service.ISysUserService; import lombok.RequiredArgsConstructor; import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.Process; import org.flowable.engine.HistoryService; import org.flowable.engine.RepositoryService; import org.flowable.engine.RuntimeService; 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.identitylink.api.IdentityLinkType; import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.*; import java.util.stream.Collectors; /** * @author:xp * @date:2024/12/4 23:22 */ @Service @RequiredArgsConstructor public class TaskCommonService { private final RuntimeService runtimeService; private final RepositoryService repositoryService; private final TaskService taskService; private final HistoryService historyService; private final ISysUserService sysUserService; private final ISysDictTypeService sysDictDService; /** * 通过当前节点定义key,获取其上一个节点的信息,如果前面是并行的会返回多个(包含当前节点) * * @param processDefId 流程定义id * @param currentNodeDefId 当前节点定义id * @param sysFormService 表单服务层 * @param needInitCurrentForm 是否需要初始化当前节点的表单数据,一般查询已完成的任务详情需要 * @return */ public List getBeforeNodeDefInfo(String processDefId, String currentNodeDefId, ISysFormService sysFormService, Boolean needInitCurrentForm) { // 获取流程定义 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionId(processDefId) .singleResult(); // 获取流程模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId); if (bpmnModel == null) { throw new RuntimeException("BpmnModel not found for processDefinitionId: " + processDefId); } // 获取流程对象 Process process = bpmnModel.getProcessById(processDefinition.getKey()); if (process == null) { throw new RuntimeException("Process not found for processDefinitionId: " + processDefId); } // 遍历流程元素,找到对应的任务节点 Collection flowElements = process.getFlowElements(); UserTask currentElement = null; for (FlowElement flowElement : flowElements) { if (flowElement instanceof UserTask && flowElement.getId().equals(currentNodeDefId)) { currentElement = (UserTask) flowElement; break; } } if (Objects.isNull(currentElement)) { throw new RuntimeException("未找到该任务的流程定义节点"); } // 获取当前节点的信息 List defKeys = new ArrayList<>(2); FormDetailVO formDetailVO = new FormDetailVO(); formDetailVO.setCurrent(Boolean.TRUE); formDetailVO.setBeforeNodeDefId(currentElement.getId()); formDetailVO.setBeforeNodeName(currentElement.getName()); if (StringUtils.hasText(currentElement.getFormKey())) { if (needInitCurrentForm) { SysForm sysForm = sysFormService.selectSysFormById(Long.parseLong(currentElement.getFormKey())); if (Objects.isNull(sysForm)) { throw new RuntimeException("该流程绑定的表单不存在或已被删除"); } Map data = new HashMap<>(1); data.put(ProcessConstants.TASK_FORM_KEY, JSONObject.parseObject(sysForm.getFormContent())); formDetailVO.setFormJsonObj(data); } } formDetailVO.setCanJump(this.checkHasExeProperty(currentElement.getExtensionElements().get("properties"), ProcessConstants.EXTENSION_PROPERTY_CAN_JUMP_TEXT)); formDetailVO.setCanWait(this.checkHasExeProperty(currentElement.getExtensionElements().get("properties"), ProcessConstants.EXTENSION_PROPERTY_CAN_WAIT_TEXT)); formDetailVO.setCanHangup(this.checkHasExeProperty(currentElement.getExtensionElements().get("properties"), ProcessConstants.EXTENSION_PROPERTY_CAN_HANGUP_TEXT)); defKeys.add(formDetailVO); this.beforeNodeInfo(currentElement, defKeys); return defKeys; } /** * 获取当前节点的前置节点,不包含当前节点 * * @param processDefId * @param currentNodeDefId * @return */ public List getBeforeNodeList(String processDefId, String currentNodeDefId) { // 获取流程定义 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionId(processDefId) .singleResult(); // 获取流程模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId); if (bpmnModel == null) { throw new RuntimeException("BpmnModel not found for processDefinitionId: " + processDefId); } // 获取流程对象 Process process = bpmnModel.getProcessById(processDefinition.getKey()); if (process == null) { throw new RuntimeException("Process not found for processDefinitionId: " + processDefId); } // 遍历流程元素,找到对应的任务节点 Collection flowElements = process.getFlowElements(); UserTask currentElement = null; for (FlowElement flowElement : flowElements) { if (flowElement instanceof UserTask && flowElement.getId().equals(currentNodeDefId)) { currentElement = (UserTask) flowElement; break; } } if (Objects.isNull(currentElement)) { throw new RuntimeException("未找到该任务的流程定义节点"); } List defKeys = new ArrayList<>(2); this.beforeNodeInfo(currentElement, defKeys); return defKeys; } /** * 递归获取当前节点的前一个任务节点信息 * * @param currentElement * @param defKeys */ private void beforeNodeInfo(FlowElement currentElement, List defKeys) { if (currentElement instanceof UserTask) { UserTask userTask = (UserTask) currentElement; if (!CollectionUtils.isEmpty(userTask.getIncomingFlows())) { for (SequenceFlow incomingFlow : userTask.getIncomingFlows()) { if (incomingFlow.getSourceFlowElement() instanceof UserTask) { FormDetailVO formDetailVO = new FormDetailVO(); formDetailVO.setBeforeNodeDefId(incomingFlow.getSourceFlowElement().getId()); formDetailVO.setBeforeNodeName(incomingFlow.getSourceFlowElement().getName()); formDetailVO.setCanJump(this.checkHasExeProperty(currentElement.getExtensionElements().get("properties"), ProcessConstants.EXTENSION_PROPERTY_CAN_JUMP_TEXT)); formDetailVO.setCanWait(this.checkHasExeProperty(currentElement.getExtensionElements().get("properties"), ProcessConstants.EXTENSION_PROPERTY_CAN_WAIT_TEXT)); defKeys.add(formDetailVO); } else { beforeNodeInfo(incomingFlow.getSourceFlowElement(), defKeys); } } } } else if (currentElement instanceof Gateway) { Gateway gateway = (Gateway) currentElement; if (!CollectionUtils.isEmpty(gateway.getIncomingFlows())) { for (SequenceFlow incomingFlow : gateway.getIncomingFlows()) { if (incomingFlow.getSourceFlowElement() instanceof UserTask) { FormDetailVO formDetailVO = new FormDetailVO(); formDetailVO.setBeforeNodeDefId(incomingFlow.getSourceFlowElement().getId()); formDetailVO.setBeforeNodeName(incomingFlow.getSourceFlowElement().getName()); formDetailVO.setCanJump(this.checkHasExeProperty(currentElement.getExtensionElements().get("properties"), ProcessConstants.EXTENSION_PROPERTY_CAN_JUMP_TEXT)); formDetailVO.setCanWait(this.checkHasExeProperty(currentElement.getExtensionElements().get("properties"), ProcessConstants.EXTENSION_PROPERTY_CAN_WAIT_TEXT)); defKeys.add(formDetailVO); } else { beforeNodeInfo(incomingFlow.getSourceFlowElement(), defKeys); } } } } } /** * 获取当前节点的上一节点id,不反悔当前节点信息,如果前面是并行,那么会返回多个 * * @param processDefId 流程定义id * @param currentNodeDefId 当前节点定义id * @return */ public List getBeforeNodeInfo(String processDefId, String currentNodeDefId) { // 获取流程定义 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionId(processDefId) .singleResult(); // 获取流程模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId); if (bpmnModel == null) { throw new RuntimeException("BpmnModel not found for processDefinitionId: " + processDefId); } // 获取流程对象 Process process = bpmnModel.getProcessById(processDefinition.getKey()); if (process == null) { throw new RuntimeException("Process not found for processDefinitionId: " + processDefId); } // 遍历流程元素,找到对应的任务节点 Collection flowElements = process.getFlowElements(); UserTask currentElement = null; for (FlowElement flowElement : flowElements) { if (flowElement instanceof UserTask && flowElement.getId().equals(currentNodeDefId)) { currentElement = (UserTask) flowElement; break; } } if (Objects.isNull(currentElement)) { throw new RuntimeException("未找到该任务的流程定义节点"); } // 获取当前节点的输入 List defKeys = new ArrayList<>(2); this.beforeNodeInfo(currentElement, defKeys); return defKeys.stream().map(FormDetailVO::getBeforeNodeDefId).collect(Collectors.toList()); } /** * 检查任务节点是否配置了某个的扩展属性 * * @param extensionElements 扩展列表 * @return */ public Boolean checkHasExeProperty(List extensionElements, String exePropertyName) { if (CollectionUtils.isEmpty(extensionElements)) { return Boolean.FALSE; } return extensionElements.stream().anyMatch(extensionElement -> { if (CollectionUtils.isEmpty(extensionElement.getAttributes())) { // 如果本身没有属性,则递归child return checkHasExeProperty(extensionElement.getChildElements().get("property"), exePropertyName); } else { // 否则先查本身的属性有不有,没有也是递归child if (extensionElement.getAttributes().get("name").stream().anyMatch(attribute -> exePropertyName.equals(attribute.getValue())) && extensionElement.getAttributes().get("value").stream().anyMatch(attribute -> ProcessConstants.EXTENSION_PROPERTY_VALUE.equals(attribute.getValue())) ) { return Boolean.TRUE; } else { return checkHasExeProperty(extensionElement.getChildElements().get("property"), exePropertyName); } } }); } /** * 驳回任务 * * @param rejectedTaskDefKey 被驳回的任务key * @param rejectTaskDefKey 执行驳回操作所在的任务key * @param processInsId 流程实例id * @param taskId 当前任务id * @param msg 审核意见 */ @Deprecated public void reject(String rejectedTaskDefKey, String rejectTaskDefKey, String processInsId, String taskId, String msg) { // 驳回的核心api:runtimeService.createChangeActivityStateBuilder().moveXXX 的api,可以设置从当前节点移动到目标节点 // 驳回的核心:需要找到当前节点、以及要流转到的目标节点。其中比较麻烦的是处理并行等比较复杂的情况 /** * 驳回的情况分为以下三种: * * 1. 如果执行驳回操作的任务是在并行结束的后一个节点,那么被驳回的任务属于并行中的某个分支的结束节点 * 2. 如果执行驳回操作的节点是并行开始后的某一分支的开始节点,那么该节点和被驳回节点实际上属于串行, 只不过需要同时把其它并行分支也驳回了(这里并不需要手动处理) * 3. 如果 被驳回节点和驳回节点属于串行,则直接驳回无需考虑其它 */ // 所以重要的是判断两个任务之间是否存在特殊节点,目前只先考虑并行网关 // 设置两条评论 List rejectedTaskList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInsId).taskDefinitionKey(rejectedTaskDefKey).orderByHistoricTaskInstanceStartTime().desc().list(); String msg1 = "驳回了:【" + rejectedTaskList.get(0).getName() + "】,驳回原因:"; taskService.addComment(taskId, processInsId, FlowComment.REJECT.getType(), msg1 + msg); // TODO 直接使用这个api好像有问题 runtimeService.createChangeActivityStateBuilder().processInstanceId(processInsId).moveActivityIdTo(rejectTaskDefKey, rejectedTaskDefKey).changeState(); runtimeService.createChangeActivityStateBuilder().processInstanceId(processInsId).moveExecutionToActivityId(rejectTaskDefKey, rejectedTaskDefKey).changeState(); } /** * 获取当前用户的组 * * @return */ public List getCurrentUserGroups() { String deptId = "dept:" + SecurityUtils.getLoginUser().getDeptId(); List roleIds; if (CollectionUtils.isEmpty(SecurityUtils.getLoginUser().getUser().getRoles())) { roleIds = new ArrayList<>(1); } else { roleIds = SecurityUtils.getLoginUser().getUser().getRoles().stream().map(role -> role.getRoleId() + "").collect(Collectors.toList()); } roleIds.add(deptId); return roleIds; } /** * 处理流程中的变量 * * @param variables * @param taskDefKey * @return */ public Map handleVar(Map variables, String taskDefKey) { Map processVariables = new HashMap<>(); //查出字典中需要注入的字段信息 List dictList = sysDictDService.selectDictDataByType("flow_variables").stream().map(SysDictData::getDictValue).collect(Collectors.toList()); Map newV = new HashMap<>(2); if (!org.springframework.util.CollectionUtils.isEmpty(variables)) { for (String key : variables.keySet()) { newV.put(taskDefKey + "&" + key, variables.get(key)); //字典里有就放入流程变量中 if (!org.apache.commons.collections4.CollectionUtils.isEmpty(dictList) && dictList.contains(key)) { processVariables.put(key,variables.get(key)); } } } return processVariables; } }