diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java index fbebf1c38..d57fd2c3f 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/ActModelServiceImpl.java @@ -1,5 +1,6 @@ package org.dromara.workflow.service.impl; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -12,7 +13,6 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.dromara.common.core.constant.Constants; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -27,6 +27,7 @@ import org.flowable.engine.RepositoryService; import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.Model; import org.flowable.engine.repository.ModelQuery; +import org.flowable.validation.ValidationError; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.MultiValueMap; @@ -36,6 +37,7 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -239,12 +241,20 @@ public class ActModelServiceImpl implements IActModelService { if (ArrayUtil.isEmpty(xmlBytes)) { throw new ServiceException("模型数据为空,请先设计流程定义模型,再进行部署!"); } - if (JSONUtil.isTypeJSON(IOUtils.toString(xmlBytes, Constants.UTF8))) { + if (JSONUtil.isTypeJSON(IOUtils.toString(xmlBytes, StandardCharsets.UTF_8.toString()))) { byte[] bytes = WorkflowUtils.bpmnJsonToXmlBytes(xmlBytes); if (ArrayUtil.isEmpty(bytes)) { throw new ServiceException("模型不能为空,请至少设计一条主线流程!"); } } + BpmnModel bpmnModel = WorkflowUtils.xmlToBpmnModel(xmlBytes); + // 校验模型 + WorkflowUtils.checkBpmnModel(bpmnModel); + List validationErrors = repositoryService.validateProcess(bpmnModel); + if (CollUtil.isNotEmpty(validationErrors)) { + String errorMsg = validationErrors.stream().map(ValidationError::getProblem).distinct().collect(Collectors.joining(",")); + throw new ServiceException(errorMsg); + } // 查询模型的基本信息 Model model = repositoryService.getModel(id); // xml资源的名称 ,对应act_ge_bytearray表中的name_字段 @@ -291,7 +301,7 @@ public class ActModelServiceImpl implements IActModelService { byte[] xmlBytes = repositoryService.getModelEditorSource(modelId); if (ObjectUtil.isNotNull(model)) { byte[] bytes = WorkflowUtils.bpmnJsonToXmlBytes(xmlBytes); - if (JSONUtil.isTypeJSON(IOUtils.toString(xmlBytes, Constants.UTF8)) && ArrayUtil.isEmpty(bytes)) { + if (JSONUtil.isTypeJSON(IOUtils.toString(xmlBytes, StandardCharsets.UTF_8.toString())) && ArrayUtil.isEmpty(bytes)) { zipName = "模型不能为空,请至少设计一条主线流程!"; zos.putNextEntry(new ZipEntry(zipName + ".txt")); zos.write(zipName.getBytes(StandardCharsets.UTF_8)); diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java index 8b95b23f0..ac52c8b00 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java @@ -1,12 +1,12 @@ package org.dromara.workflow.utils; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.dromara.common.json.utils.JsonUtils; import org.flowable.bpmn.converter.BpmnXMLConverter; -import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.*; import org.flowable.editor.language.json.converter.BpmnJsonConverter; import javax.xml.stream.XMLInputFactory; @@ -14,8 +14,10 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.rmi.ServerException; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; /** * 工作流工具 @@ -59,4 +61,61 @@ public class WorkflowUtils { XMLStreamReader xtr = xif.createXMLStreamReader(byteArrayInputStream); return new BpmnXMLConverter().convertToBpmnModel(xtr); } + + /** + * 校验模型 + * + * @param bpmnModel bpmn模型 + */ + public static void checkBpmnModel(BpmnModel bpmnModel) throws ServerException { + Collection flowElements = bpmnModel.getMainProcess().getFlowElements(); + + checkBpmnNode(flowElements, false); + + List subProcessList = flowElements.stream().filter(SubProcess.class::isInstance).map(SubProcess.class::cast).collect(Collectors.toList()); + if (!CollUtil.isEmpty(subProcessList)) { + for (SubProcess subProcess : subProcessList) { + Collection subProcessFlowElements = subProcess.getFlowElements(); + checkBpmnNode(subProcessFlowElements, true); + } + } + } + + /** + * 校验bpmn节点是否合法 + * + * @param flowElements 节点集合 + * @param subtask 是否子流程 + */ + private static void checkBpmnNode(Collection flowElements, boolean subtask) throws ServerException { + + if (CollUtil.isEmpty(flowElements)) { + throw new ServerException(subtask ? "子流程必须存在节点" : "" + "必须存在节点!"); + } + + List startEventList = flowElements.stream().filter(StartEvent.class::isInstance).map(StartEvent.class::cast).collect(Collectors.toList()); + if (CollUtil.isEmpty(startEventList)) { + throw new ServerException(subtask ? "子流程必须存在开始节点" : "" + "必须存在开始节点!"); + } + + if (startEventList.size() > 1) { + throw new ServerException(subtask ? "子流程只能存在一个开始节点" : "" + "只能存在一个开始节点!"); + } + + StartEvent startEvent = startEventList.get(0); + List outgoingFlows = startEvent.getOutgoingFlows(); + if (CollUtil.isEmpty(outgoingFlows)) { + throw new ServerException(subtask ? "子流程流程节点为空,请至少设计一条主线流程!" : "" + "流程节点为空,请至少设计一条主线流程!"); + } + + FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement(); + if (!(targetFlowElement instanceof UserTask)) { + throw new ServerException(subtask ? "子流程开始节点后第一个节点必须是用户任务!" : "" + "开始节点后第一个节点必须是用户任务!"); + } + + List endEventList = flowElements.stream().filter(EndEvent.class::isInstance).map(EndEvent.class::cast).collect(Collectors.toList()); + if (CollUtil.isEmpty(endEventList)) { + throw new ServerException(subtask ? "子流程必须存在结束节点!" : "" + "必须存在结束节点!"); + } + } }