Merge remote-tracking branch 'origin/feature/REQ-2924' into feature/REQ-2924

This commit is contained in:
wangli 2024-09-12 14:48:12 +08:00
commit d05c42b606
5 changed files with 198 additions and 84 deletions

View File

@ -38,6 +38,7 @@ public enum BpmnTaskRespCode implements IModuleRespCode {
PROCESS_CANT_SET_ASSIGNEE("021", "当前审批状态不允许设置审批人"),
ASSIGNER_NUMBER_EXCEEDS_NUMBER_LIMIT("022", String.format("人员数量超过限制,节点审批人限制数量为: %d!", APPROVAL_ASSIGNER_LIMIT_NUMBER)),
BACK_TARGET_ACTIVITY_NOT_EXISTS("023", "回退到指定节点【{}】失败!"),
BACK_NODE_CANNOT_REACHABLE("024", "退回节点【{}】不可达,不允许退回"),
;
private final String code;

View File

@ -0,0 +1,91 @@
package cn.axzo.workflow.core.common.utils;
import org.flowable.bpmn.model.EventSubProcess;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowElementsContainer;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.SubProcess;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class BpmnModelUtils {
/**
* 节点是否可达
* @param process
* @param sourceElement
* @param targetElement
* @return
*/
public static boolean isReachable(org.flowable.bpmn.model.Process process, FlowNode sourceElement, FlowNode targetElement) {
return isReachable(process, sourceElement, targetElement, new HashSet<>());
}
public static boolean isReachable(org.flowable.bpmn.model.Process process, FlowNode sourceElement, FlowNode targetElement, Set<String> visitedElements) {
// Special case: start events in an event subprocess might exist as an execution and are most likely be able to
// reach the target
// when the target is in the event subprocess, but should be ignored as they are not 'real' runtime executions
// (but rather waiting for trigger)
if (sourceElement instanceof StartEvent && isInEventSubprocess(sourceElement)) {
return false;
}
// No outgoing seq flow: could be the end of eg . the process or an embedded subprocess
if (sourceElement.getOutgoingFlows().isEmpty()) {
visitedElements.add(sourceElement.getId());
FlowElementsContainer parentElement = process.findParent(sourceElement);
if (parentElement instanceof SubProcess) {
sourceElement = (SubProcess) parentElement;
// 子流程的结束节点若目标节点在该子流程中说明无法到达返回false
if (((SubProcess) sourceElement).getFlowElement(targetElement.getId()) != null) {
return false;
}
} else {
return false;
}
}
if (sourceElement.getId().equals(targetElement.getId())) {
return true;
}
// To avoid infinite looping, we must capture every node we visit
// and check before going further in the graph if we have already
// visited the node.
visitedElements.add(sourceElement.getId());
// 当前节点能够到达子流程且目标节点在子流程中说明可以到达返回true
if (sourceElement instanceof SubProcess && ((SubProcess) sourceElement).getFlowElement(targetElement.getId()) != null) {
return true;
}
List<SequenceFlow> sequenceFlows = sourceElement.getOutgoingFlows();
if (sequenceFlows != null && !sequenceFlows.isEmpty()) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
String targetRef = sequenceFlow.getTargetRef();
FlowNode sequenceFlowTarget = (FlowNode) process.getFlowElement(targetRef, true);
if (sequenceFlowTarget != null && !visitedElements.contains(sequenceFlowTarget.getId())) {
boolean reachable = isReachable(process, sequenceFlowTarget, targetElement, visitedElements);
if (reachable) {
return true;
}
}
}
}
return false;
}
protected static boolean isInEventSubprocess(FlowNode flowNode) {
FlowElementsContainer flowElementsContainer = flowNode.getParentContainer();
while (flowElementsContainer != null) {
if (flowElementsContainer instanceof EventSubProcess) {
return true;
}
if (flowElementsContainer instanceof FlowElement) {
flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
} else {
flowElementsContainer = null;
}
}
return false;
}
}

View File

@ -1,34 +1,39 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.core.engine.job.AsyncApproveTaskJobHandler;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnModelUtils;
import cn.axzo.workflow.core.engine.job.AsyncBackTaskJobHandler;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.job.service.JobService;
import org.flowable.job.service.impl.persistence.entity.JobEntity;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.Objects;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_TARGET_ACTIVITY_NOT_EXISTS;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask;
@Slf4j
public class CustomBackTaskAsyncCmd extends AbstractCommand<String> implements Serializable {
private static final long serialVersionUID = 1773108485033787095L;
private static final Logger log = LoggerFactory.getLogger(CustomBackTaskAsyncCmd.class);
private final BpmnTaskBackAuditDTO dto;
public CustomBackTaskAsyncCmd(BpmnTaskBackAuditDTO dto) {
@ -41,17 +46,25 @@ public class CustomBackTaskAsyncCmd extends AbstractCommand<String> implements S
CommandContextUtil.getProcessEngineConfiguration(commandContext);
HistoricTaskInstanceQuery taskQuery =
processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery();
TaskService taskService = processEngineConfiguration.getTaskService();
TaskService taskService = processEngineConfiguration.getTaskServiceConfiguration().getTaskService();
HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult();
Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult();
if (Objects.isNull(task)) {
log.info("任务不存在: {}", dto.getTaskId());
}
validTask(historicTaskInstance, (TaskEntity) task, dto.getApprover(), null);
TaskEntity taskEntity = taskService.getTask(dto.getTaskId());
validTask(historicTaskInstance, taskEntity, dto.getApprover(), dto.getNodeTypes());
return startAsync(processEngineConfiguration, task);
Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId());
FlowElement targetFlowElement = process.getFlowElement(dto.getToActivityId(), true);
if (Objects.isNull(targetFlowElement)) {
throw new WorkflowEngineException(BACK_TARGET_ACTIVITY_NOT_EXISTS, dto.getToActivityId());
}
FlowElement sourceFlowElement = process.getFlowElement(taskEntity.getTaskDefinitionKey(), true);
// 退回节点到当前节点不可达到不允许退回
if (!BpmnModelUtils.isReachable(process, (FlowNode) sourceFlowElement, (FlowNode) targetFlowElement)) {
throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId());
}
return startAsync(processEngineConfiguration, taskEntity);
}
private String startAsync(ProcessEngineConfigurationImpl processEngineConfiguration, Task task) {

View File

@ -2,18 +2,21 @@ package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnModelUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import java.io.Serializable;
@ -26,6 +29,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.BACKED;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_TARGET_ACTIVITY_NOT_EXISTS;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment;
@ -40,14 +44,14 @@ public class CustomBackTaskCmd extends AbstractCommand<Void> implements Serializ
private final BpmnTaskBackAuditDTO dto;
private static String operationDesc = "回退至";
private static final String OPERATION_DESC = "回退至";
@Override
public String paramToJsonString() {
Map<String, Object> params = new HashMap<>();
params.put("taskId", dto.getTaskId());
params.put("advice", dto.getAdvice());
params.put("operationDesc", operationDesc);
params.put("operationDesc", OPERATION_DESC);
params.put("attachmentList", JSON.toJSONString(dto.getAttachmentList()));
params.put("approver", JSON.toJSONString(dto.getApprover()));
params.put("nodeTypes", JSON.toJSONString(dto.getNodeTypes()));
@ -64,24 +68,28 @@ public class CustomBackTaskCmd extends AbstractCommand<Void> implements Serializ
CommandContextUtil.getProcessEngineConfiguration(commandContext);
HistoricTaskInstanceQuery taskQuery =
processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery();
TaskService taskService = processEngineConfiguration.getTaskService();
TaskService taskService = processEngineConfiguration.getTaskServiceConfiguration().getTaskService();
HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult();
TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult();
TaskEntity task = taskService.getTask(dto.getTaskId());
validTask(historicTaskInstance, task, dto.getApprover(), dto.getNodeTypes());
BpmnModel bpmnModel = processEngineConfiguration.getRepositoryService().getBpmnModel(historicTaskInstance.getProcessDefinitionId());
FlowElement flowElement = bpmnModel.getFlowElement(dto.getToActivityId());
if (Objects.isNull(flowElement)) {
Process process = ProcessDefinitionUtil.getProcess(task.getProcessDefinitionId());
FlowElement targetFlowElement = process.getFlowElement(dto.getToActivityId(), true);
if (Objects.isNull(targetFlowElement)) {
throw new WorkflowEngineException(BACK_TARGET_ACTIVITY_NOT_EXISTS, dto.getToActivityId());
}
FlowElement sourceFlowElement = process.getFlowElement(task.getTaskDefinitionKey(), true);
// 退回节点到当前节点不可达到不允许退回
if (!BpmnModelUtils.isReachable(process, (FlowNode) sourceFlowElement, (FlowNode) targetFlowElement)) {
throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId());
}
batchAddAttachment(commandContext, task.getProcessInstanceId(), dto.getTaskId(), dto.getAttachmentList(), dto.getApprover());
Authentication.setAuthenticatedUserId(dto.getApprover().buildAssigneeId());
addComment(commandContext, task, COMMENT_TYPE_ADVICE, dto.getAdvice());
addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, operationDesc + flowElement.getName());
addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, OPERATION_DESC + targetFlowElement.getName());
Authentication.setAuthenticatedUserId(null);
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();

View File

@ -63,6 +63,7 @@ import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ASSIGNER_NUMBER
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_TYPE_MISMATCH;
import static org.flowable.task.api.Task.DEFAULT_PRIORITY;
/**
@ -154,7 +155,7 @@ public class CustomTaskHelper {
//不包含对应的任务
if (!nodeTypes.contains(nodeType)) {
// log.warn(TASK_TYPE_MISMATCH.getMessage(), nodeType.getDesc(), nodeTypes.stream().map(BpmnFlowNodeType::getDesc).collect(Collectors.joining(",")));
throw new WorkflowEngineException(ASSIGNER_NUMBER_EXCEEDS_NUMBER_LIMIT, nodeType.getDesc(), nodeTypes.stream().map(BpmnFlowNodeType::getDesc).collect(Collectors.joining(",")));
throw new WorkflowEngineException(TASK_TYPE_MISMATCH, nodeType.getDesc(), nodeTypes.stream().map(BpmnFlowNodeType::getDesc).collect(Collectors.joining(",")));
}
}
}