diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java b/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java index b86949f9e..6d1740dfb 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java @@ -1,18 +1,42 @@ package com.yomahub.liteflow.builder.el; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; import com.ql.util.express.DefaultContext; import com.ql.util.express.ExpressRunner; +import com.ql.util.express.InstructionSet; import com.ql.util.express.exception.QLException; -import com.yomahub.liteflow.builder.el.operator.*; +import com.yomahub.liteflow.builder.el.operator.AnyOperator; +import com.yomahub.liteflow.builder.el.operator.BreakOperator; +import com.yomahub.liteflow.builder.el.operator.DataOperator; +import com.yomahub.liteflow.builder.el.operator.DefaultOperator; +import com.yomahub.liteflow.builder.el.operator.DoOperator; +import com.yomahub.liteflow.builder.el.operator.ElifOperator; +import com.yomahub.liteflow.builder.el.operator.ElseOperator; +import com.yomahub.liteflow.builder.el.operator.FinallyOperator; +import com.yomahub.liteflow.builder.el.operator.ForOperator; +import com.yomahub.liteflow.builder.el.operator.IdOperator; +import com.yomahub.liteflow.builder.el.operator.IfOperator; +import com.yomahub.liteflow.builder.el.operator.IgnoreErrorOperator; +import com.yomahub.liteflow.builder.el.operator.NodeOperator; +import com.yomahub.liteflow.builder.el.operator.PreOperator; +import com.yomahub.liteflow.builder.el.operator.SwitchOperator; +import com.yomahub.liteflow.builder.el.operator.TagOperator; +import com.yomahub.liteflow.builder.el.operator.ThenOperator; +import com.yomahub.liteflow.builder.el.operator.ThreadPoolOperator; +import com.yomahub.liteflow.builder.el.operator.ToOperator; +import com.yomahub.liteflow.builder.el.operator.WhenOperator; +import com.yomahub.liteflow.builder.el.operator.WhileOperator; import com.yomahub.liteflow.common.ChainConstant; -import com.yomahub.liteflow.exception.DataNofFoundException; +import com.yomahub.liteflow.exception.DataNotFoundException; import com.yomahub.liteflow.exception.ELParseException; import com.yomahub.liteflow.exception.FlowSystemException; import com.yomahub.liteflow.flow.FlowBus; import com.yomahub.liteflow.flow.element.Chain; -import com.yomahub.liteflow.flow.element.condition.*; +import com.yomahub.liteflow.flow.element.Node; +import com.yomahub.liteflow.flow.element.condition.Condition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,8 +176,10 @@ public class LiteFlowChainELBuilder { return this; } catch (QLException e) { // EL 底层会包装异常,这里是曲线处理 - if (Objects.equals(e.getCause().getMessage(), DataNofFoundException.MSG)) { - throw new ELParseException(String.format("[node/chain is not exist or node/chain not register]elStr=%s", elStr)); + if (Objects.equals(e.getCause().getMessage(), DataNotFoundException.MSG)) { + // 构建错误信息 + String msg = buildDataNotFoundExceptionMsg(elStr); + throw new ELParseException(msg); } throw new ELParseException(e.getCause().getMessage()); } catch (Exception e) { @@ -163,17 +189,18 @@ public class LiteFlowChainELBuilder { /** * EL表达式校验 + * * @param elStr EL表达式 * @return true 校验成功 false 校验失败 */ public static boolean validate(String elStr) { - try { - LiteFlowChainELBuilder.createChain().setEL(elStr); - return Boolean.TRUE; - } catch (ELParseException e) { - LOG.error(e.getMessage()); - } - return Boolean.FALSE; + try { + LiteFlowChainELBuilder.createChain().setEL(elStr); + return Boolean.TRUE; + } catch (ELParseException e) { + LOG.error(e.getMessage()); + } + return Boolean.FALSE; } public void build() { @@ -184,6 +211,8 @@ public class LiteFlowChainELBuilder { FlowBus.addChain(this.chain); } + //#region private method + /** * build 前简单校验 */ @@ -196,4 +225,58 @@ public class LiteFlowChainELBuilder { throw new RuntimeException(CollUtil.join(errorList, ",", "[", "]")); } } + + /** + * 解析 EL 表达式,查找未定义的 id 并构建错误信息 + * + * @param elStr el 表达式 + */ + private String buildDataNotFoundExceptionMsg(String elStr) { + String msg = String.format("[node/chain is not exist or node/chain not register]\n EL: %s", StrUtil.trim(elStr)); + try { + InstructionSet parseResult = EXPRESS_RUNNER.getInstructionSetFromLocalCache(elStr); + if (parseResult == null) { + return msg; + } + + String[] outAttrNames = parseResult.getOutAttrNames(); + if (ArrayUtil.isEmpty(outAttrNames)) { + return msg; + } + + List chainIds = CollUtil.map(FlowBus.getChainMap().values(), Chain::getChainId, true); + List nodeIds = CollUtil.map(FlowBus.getNodeMap().values(), Node::getId, true); + for (String attrName : outAttrNames) { + if (!chainIds.contains(attrName) && !nodeIds.contains(attrName)) { + msg = String.format("[%s] is not exist or [%s] is not registered, you need to define a node or chain with id [%s] and register it \n EL: ", attrName, attrName, attrName); + + // 去除 EL 表达式中的空格和换行符 + String sourceEl = StrUtil.removeAll(elStr, CharUtil.SPACE, CharUtil.LF, CharUtil.CR); + // 这里需要注意的是,nodeId 和 chainId 可能是关键字的一部分,如果直接 indexOf(attrName) 会出现误判 + // 所以需要判断 attrName 前后是否有 "," + int commaRightIndex = sourceEl.indexOf(attrName + StrUtil.COMMA); + if (commaRightIndex != -1) { + // 需要加上 "EL: " 的长度 4,再加上 "^" 的长度 1,indexOf 从 0 开始,所以还需要加 1 + msg = msg + sourceEl + "\n" + StrUtil.fill("^", CharUtil.SPACE, commaRightIndex + 6, true); + } + int commaLeftIndex = sourceEl.indexOf(StrUtil.COMMA + attrName); + if (commaLeftIndex != -1) { + // 需要加上 "EL: " 的长度 4,再加上 "^" 的长度 1,再加上 "," 的长度 1,indexOf 从 0 开始,所以还需要加 1 + msg = msg + sourceEl + "\n" + StrUtil.fill("^", CharUtil.SPACE, commaLeftIndex + 7, true); + } + // 还有一种特殊情况,就是 EL 表达式中的节点使用 node("a") + int nodeIndex = sourceEl.indexOf(String.format("node(\"%s\")", attrName)); + if (nodeIndex != -1) { + // 需要加上 "EL: " 的长度 4,再加上 “node("” 长度 6,再加上 "^" 的长度 1,indexOf 从 0 开始,所以还需要加 1 + msg = msg + sourceEl + "\n" + StrUtil.fill("^", CharUtil.SPACE, commaLeftIndex + 12, true); + } + break; + } + } + } catch (Exception ex) { + // ignore + } + return msg; + } + //#endregion } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/base/OperatorHelper.java b/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/base/OperatorHelper.java index 401254036..88f60764b 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/base/OperatorHelper.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/base/OperatorHelper.java @@ -2,7 +2,7 @@ package com.yomahub.liteflow.builder.el.operator.base; import cn.hutool.core.util.StrUtil; import com.ql.util.express.exception.QLException; -import com.yomahub.liteflow.exception.DataNofFoundException; +import com.yomahub.liteflow.exception.DataNotFoundException; import com.yomahub.liteflow.flow.element.Node; import java.util.Objects; @@ -156,7 +156,7 @@ public class OperatorHelper { public static void checkNodeAndChainExist(Object[] objects) throws QLException { for (Object object : objects) { if (Objects.isNull(object)) { - throw new QLException(DataNofFoundException.MSG); + throw new QLException(DataNotFoundException.MSG); } } } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/exception/DataNofFoundException.java b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/DataNotFoundException.java similarity index 67% rename from liteflow-core/src/main/java/com/yomahub/liteflow/exception/DataNofFoundException.java rename to liteflow-core/src/main/java/com/yomahub/liteflow/exception/DataNotFoundException.java index c31111517..cb2f65ad6 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/exception/DataNofFoundException.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/DataNotFoundException.java @@ -4,8 +4,8 @@ package com.yomahub.liteflow.exception; * 未找到数据异常 * @author tangkc */ -public class DataNofFoundException extends RuntimeException { - public static final String MSG = "DataNofFoundException"; +public class DataNotFoundException extends RuntimeException { + public static final String MSG = "DataNotFoundException"; private static final long serialVersionUID = 1L; @@ -14,11 +14,11 @@ public class DataNofFoundException extends RuntimeException { */ private String message; - public DataNofFoundException() { + public DataNotFoundException() { this.message = MSG; } - public DataNofFoundException(String message) { + public DataNotFoundException(String message) { this.message = message; }