From 8f67134326b3e888e829fe639c6c72f686fe4b8a Mon Sep 17 00:00:00 2001 From: gaibu <1016771049@qq.com> Date: Thu, 21 Aug 2025 22:29:56 +0800 Subject: [PATCH 1/3] =?UTF-8?q?enhancement=20#ICU4Z3=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20nodeid=20=E4=B8=8D=E5=90=88=E6=B3=95=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=EF=BC=8C=E5=A2=9E=E5=8A=A0=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=BC=95=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builder/el/LiteFlowChainELBuilder.java | 52 +- .../exception/NodeIdUnIllegalException.java | 31 + .../com/yomahub/liteflow/flow/FlowBus.java | 698 +++++++++--------- .../yomahub/liteflow/util/QlExpressUtils.java | 94 +++ .../test/utils/QlExpressUtilsTest.java | 27 + 5 files changed, 519 insertions(+), 383 deletions(-) create mode 100644 liteflow-core/src/main/java/com/yomahub/liteflow/exception/NodeIdUnIllegalException.java create mode 100644 liteflow-core/src/main/java/com/yomahub/liteflow/util/QlExpressUtils.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/utils/QlExpressUtilsTest.java 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 7ce55ea09..4ce4ce0c8 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 @@ -31,6 +31,7 @@ import com.yomahub.liteflow.log.LFLoggerManager; import com.yomahub.liteflow.property.LiteflowConfig; import com.yomahub.liteflow.property.LiteflowConfigGetter; import com.yomahub.liteflow.util.ElRegexUtil; +import com.yomahub.liteflow.util.QlExpressUtils; import java.util.ArrayList; import java.util.Arrays; @@ -66,53 +67,10 @@ public class LiteFlowChainELBuilder { * 所以在这里做一个缓存,等conditionList全部build完毕后,再去一次性替换chain里面的conditionList */ private final List conditionList; - - /** - * EL解析引擎 - */ - public final static ExpressRunner EXPRESS_RUNNER = new ExpressRunner(); - - static { - // 初始化QLExpress的Runner - EXPRESS_RUNNER.addFunction(ChainConstant.THEN, new ThenOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.WHEN, new WhenOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.SER, new ThenOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.PAR, new WhenOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.SWITCH, new SwitchOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.PRE, new PreOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.FINALLY, new FinallyOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.IF, new IfOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.NODE.toUpperCase(), new NodeOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.NODE, new NodeOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.FOR, new ForOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.WHILE, new WhileOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.ITERATOR, new IteratorOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.CATCH, new CatchOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.AND, new AndOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.OR, new OrOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.NOT, new NotOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ELSE, Object.class, new ElseOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ELIF, Object.class, new ElifOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TO, Object.class, new ToOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TO.toLowerCase(), Object.class, new ToOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DEFAULT, Object.class, new DefaultOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TAG, Object.class, new TagOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ANY, Object.class, new AnyOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MUST, Object.class, new MustOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PERCENTAGE, Object.class, new PercentageOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ID, Object.class, new IdOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.IGNORE_ERROR, Object.class, new IgnoreErrorOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.THREAD_POOL, Object.class, new ThreadPoolOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DO, Object.class, new DoOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BREAK, Object.class, new BreakOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DATA, Object.class, new DataOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_SECONDS, Object.class, new MaxWaitSecondsOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_MILLISECONDS, Object.class, new MaxWaitMillisecondsOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PARALLEL, Object.class, new ParallelOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.RETRY, Object.class, new RetryOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BIND, Object.class, new BindOperator()); - - } + /** + * EL解析引擎 + */ + private final static ExpressRunner EXPRESS_RUNNER = QlExpressUtils.getInstance(); public static LiteFlowChainELBuilder createChain() { return new LiteFlowChainELBuilder(); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/exception/NodeIdUnIllegalException.java b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/NodeIdUnIllegalException.java new file mode 100644 index 000000000..820aa03fa --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/NodeIdUnIllegalException.java @@ -0,0 +1,31 @@ +package com.yomahub.liteflow.exception; + +/** + * node id不合法异常 + * + * @author tangkc + * @since 2.13.2 + */ +public class NodeIdUnIllegalException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 异常信息 + */ + private String message; + + public NodeIdUnIllegalException(String message) { + this.message = message; + } + + @Override + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java index 3c2ff9761..3fdf55c01 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java @@ -24,6 +24,7 @@ import com.yomahub.liteflow.enums.FlowParserTypeEnum; import com.yomahub.liteflow.enums.NodeTypeEnum; import com.yomahub.liteflow.enums.ParseModeEnum; import com.yomahub.liteflow.exception.ComponentCannotRegisterException; +import com.yomahub.liteflow.exception.NodeIdUnIllegalException; import com.yomahub.liteflow.exception.NullNodeTypeException; import com.yomahub.liteflow.flow.element.Chain; import com.yomahub.liteflow.flow.element.Node; @@ -43,6 +44,7 @@ import com.yomahub.liteflow.spi.ContextAware; import com.yomahub.liteflow.spi.holder.ContextAwareHolder; import com.yomahub.liteflow.spi.holder.DeclComponentParserHolder; import com.yomahub.liteflow.util.CopyOnWriteHashMap; +import com.yomahub.liteflow.util.QlExpressUtils; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -59,414 +61,438 @@ import java.util.stream.Collectors; */ public class FlowBus { - private static final LFLog LOG = LFLoggerManager.getLogger(FlowBus.class); + private static final LFLog LOG = LFLoggerManager.getLogger(FlowBus.class); - private static final Map chainMap; + private static final Map chainMap; - private static final Map nodeMap; + private static final Map nodeMap; - private static final Map fallbackNodeMap; + private static final Map fallbackNodeMap; - private static final Map elMd5Map; + private static final Map elMd5Map; - private static final AtomicBoolean initStat = new AtomicBoolean(false); + private static final AtomicBoolean initStat = new AtomicBoolean(false); - static { - LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); - if (liteflowConfig.getFastLoad()){ - chainMap = new HashMap<>(); - nodeMap = new HashMap<>(); - fallbackNodeMap = new HashMap<>(); - elMd5Map = new HashMap<>(); - } else { - chainMap = new CopyOnWriteHashMap<>(); - nodeMap = new CopyOnWriteHashMap<>(); - fallbackNodeMap = new CopyOnWriteHashMap<>(); - elMd5Map = new ConcurrentHashMap<>(); - } - } + static { + LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); + if (liteflowConfig.getFastLoad()) { + chainMap = new HashMap<>(); + nodeMap = new HashMap<>(); + fallbackNodeMap = new HashMap<>(); + elMd5Map = new HashMap<>(); + } else { + chainMap = new CopyOnWriteHashMap<>(); + nodeMap = new CopyOnWriteHashMap<>(); + fallbackNodeMap = new CopyOnWriteHashMap<>(); + elMd5Map = new ConcurrentHashMap<>(); + } + } - public static Chain getChain(String id) { - return chainMap.get(id); - } + public static Chain getChain(String id) { + return chainMap.get(id); + } - // 这一方法主要用于第一阶段chain的预装载 - public static void addChain(String chainId) { - if (!chainMap.containsKey(chainId)) { - chainMap.put(chainId, new Chain(chainId)); - } - } + // 这一方法主要用于第一阶段chain的预装载 + public static void addChain(String chainId) { + if (!chainMap.containsKey(chainId)) { + chainMap.put(chainId, new Chain(chainId)); + } + } - // 这个方法主要用于第二阶段的替换chain - public static void addChain(Chain chain) { - //如果有生命周期则执行相应生命周期实现 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())){ - LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( - postProcessAfterChainBuildLifeCycle -> postProcessAfterChainBuildLifeCycle.postProcessBeforeChainBuild(chain) - ); - } + // 这个方法主要用于第二阶段的替换chain + public static void addChain(Chain chain) { + //如果有生命周期则执行相应生命周期实现 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())) { + LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( + postProcessAfterChainBuildLifeCycle -> postProcessAfterChainBuildLifeCycle.postProcessBeforeChainBuild(chain) + ); + } - chainMap.put(chain.getChainId(), chain); + chainMap.put(chain.getChainId(), chain); - if (StrUtil.isNotBlank(chain.getEl())){ - elMd5Map.put(chain.getElMd5(), chain.getChainId()); - } + if (StrUtil.isNotBlank(chain.getEl())) { + elMd5Map.put(chain.getElMd5(), chain.getChainId()); + } - //如果有生命周期则执行相应生命周期实现 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())){ - LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( - postProcessAfterChainBuildLifeCycle -> postProcessAfterChainBuildLifeCycle.postProcessAfterChainBuild(chain) - ); - } - } + //如果有生命周期则执行相应生命周期实现 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())) { + LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( + postProcessAfterChainBuildLifeCycle -> postProcessAfterChainBuildLifeCycle.postProcessAfterChainBuild(chain) + ); + } + } - public static boolean containChain(String chainId) { - return chainMap.containsKey(chainId); - } + public static boolean containChain(String chainId) { + return chainMap.containsKey(chainId); + } - public static boolean needInit() { - return initStat.compareAndSet(false, true); - } + public static boolean needInit() { + return initStat.compareAndSet(false, true); + } - public static boolean containNode(String nodeId) { - return nodeMap.containsKey(nodeId); - } + public static boolean containNode(String nodeId) { + return nodeMap.containsKey(nodeId); + } - public static void addManagedNode(String nodeId) { - ContextAware contextAware = ContextAwareHolder.loadContextAware(); - if (contextAware.hasBean(nodeId)){ - addManagedNode(nodeId, contextAware.getBean(nodeId)); - } - } + public static void addManagedNode(String nodeId) { + ContextAware contextAware = ContextAwareHolder.loadContextAware(); + if (contextAware.hasBean(nodeId)) { + addManagedNode(nodeId, contextAware.getBean(nodeId)); + } + } - /** - * 添加已托管的节点(如:Spring、Solon 管理的节点) - * @param nodeId nodeId - * @param nodeComponent nodeComponent - */ - public static void addManagedNode(String nodeId, NodeComponent nodeComponent) { - // 根据class来猜测类型 - NodeTypeEnum type = NodeTypeEnum.guessType(nodeComponent.getClass()); + /** + * 添加已托管的节点(如:Spring、Solon 管理的节点) + * + * @param nodeId nodeId + * @param nodeComponent nodeComponent + */ + public static void addManagedNode(String nodeId, NodeComponent nodeComponent) { + // 根据class来猜测类型 + NodeTypeEnum type = NodeTypeEnum.guessType(nodeComponent.getClass()); - if (type == null) { - throw new NullNodeTypeException(StrUtil.format("node type is null for node[{}]", nodeId)); - } + if (type == null) { + throw new NullNodeTypeException(StrUtil.format("node type is null for node[{}]", nodeId)); + } - Node node = new Node(ComponentInitializer.loadInstance() - .initComponent(nodeComponent, type, nodeComponent.getName(), nodeId)); - put2NodeMap(nodeId, node); - addFallbackNode(node); - } + Node node = new Node(ComponentInitializer.loadInstance() + .initComponent(nodeComponent, type, nodeComponent.getName(), nodeId)); + put2NodeMap(nodeId, node); + addFallbackNode(node); + } - /** - * 添加 node - * @param nodeId 节点id - * @param name 节点名称 - * @param type 节点类型 - * @param cmpClazz 节点组件类 - */ - public static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz) { - addNode(nodeId, name, type, cmpClazz, null, null); - } + /** + * 添加 node + * + * @param nodeId 节点id + * @param name 节点名称 + * @param type 节点类型 + * @param cmpClazz 节点组件类 + */ + public static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz) { + addNode(nodeId, name, type, cmpClazz, null, null); + } - /** - * 添加 node - * @param nodeId 节点id - * @param name 节点名称 - * @param nodeType 节点类型 - * @param cmpClazzStr 节点组件类路径 - */ - public static void addNode(String nodeId, String name, NodeTypeEnum nodeType, String cmpClazzStr) { - Class cmpClazz; - try { - cmpClazz = Class.forName(cmpClazzStr); - } - catch (Exception e) { - throw new ComponentCannotRegisterException(e.getMessage()); - } - addNode(nodeId, name, nodeType, cmpClazz, null, null); - } + /** + * 添加 node + * + * @param nodeId 节点id + * @param name 节点名称 + * @param nodeType 节点类型 + * @param cmpClazzStr 节点组件类路径 + */ + public static void addNode(String nodeId, String name, NodeTypeEnum nodeType, String cmpClazzStr) { + Class cmpClazz; + try { + cmpClazz = Class.forName(cmpClazzStr); + } catch (Exception e) { + throw new ComponentCannotRegisterException(e.getMessage()); + } + addNode(nodeId, name, nodeType, cmpClazz, null, null); + } - /** - * 添加脚本 node - * @param nodeId 节点id - * @param name 节点名称 - * @param nodeType 节点类型 - * @param script 脚本 - * @param language 语言 - */ - public static void addScriptNode(String nodeId, String name, NodeTypeEnum nodeType, String script, - String language) { + /** + * 添加脚本 node + * + * @param nodeId 节点id + * @param name 节点名称 + * @param nodeType 节点类型 + * @param script 脚本 + * @param language 语言 + */ + public static void addScriptNode(String nodeId, String name, NodeTypeEnum nodeType, String script, + String language) { LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); - // 如果是PARSE_ONE_ON_FIRST_EXEC模式,则不进行脚本加载,而是直接把脚本内容放到node中 + // 如果是PARSE_ONE_ON_FIRST_EXEC模式,则不进行脚本加载,而是直接把脚本内容放到node中 if (liteflowConfig.getParseMode().equals(ParseModeEnum.PARSE_ONE_ON_FIRST_EXEC)) { - List nodes = LiteflowMetaOperator.getNodesInAllChain(nodeId); - if (CollectionUtil.isNotEmpty(nodes)) { - nodes.forEach(node -> { + List nodes = LiteflowMetaOperator.getNodesInAllChain(nodeId); + if (CollectionUtil.isNotEmpty(nodes)) { + nodes.forEach(node -> { node.setCompiled(false); node.setScript(script); }); - } + } - Node node = new Node(nodeId, name, nodeType, script, language); - put2NodeMap(nodeId, node); - } else { - addScriptNodeAndCompile(nodeId, name, nodeType, script, language); + Node node = new Node(nodeId, name, nodeType, script, language); + put2NodeMap(nodeId, node); + } else { + addScriptNodeAndCompile(nodeId, name, nodeType, script, language); } } - /** - * 添加脚本 node,并且编译脚本 - * @param nodeId nodeId - * @param name name - * @param type type - * @param script script content - * @param language language - */ - public static void addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script, String language) { - addNode(nodeId, name, type, ScriptComponent.ScriptComponentClassMap.get(type), script, language); - } + /** + * 添加脚本 node,并且编译脚本 + * + * @param nodeId nodeId + * @param name name + * @param type type + * @param script script content + * @param language language + */ + public static void addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script, String language) { + addNode(nodeId, name, type, ScriptComponent.ScriptComponentClassMap.get(type), script, language); + } - private static List getNodeComponentList(String nodeId, String name, NodeTypeEnum type, Class cmpClazz) throws Exception { - // 判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例 - // 如果不是声明式的,就用传统的方式进行判断 - List cmpInstanceList = new ArrayList<>(); - if (LiteFlowProxyUtil.isDeclareCmp(cmpClazz)) { - // 如果是spring体系,把原始的类往spring上下文中进行注册,那么会走到ComponentScanner中 - // 由于ComponentScanner中已经对原始类进行了动态代理,出来的对象已经变成了动态代理类,所以这时候的bean已经是NodeComponent的子类了 - // 所以spring体系下,无需再对这个bean做二次代理 - // 非spring体系下,从2.11.4开始不再支持声明式组件 - List declWarpBeanList = DeclComponentParserHolder.loadDeclComponentParser().parseDeclBean(cmpClazz, nodeId, name); + private static List getNodeComponentList(String nodeId, String name, NodeTypeEnum type, Class cmpClazz) throws Exception { + // 判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例 + // 如果不是声明式的,就用传统的方式进行判断 + List cmpInstanceList = new ArrayList<>(); + if (LiteFlowProxyUtil.isDeclareCmp(cmpClazz)) { + // 如果是spring体系,把原始的类往spring上下文中进行注册,那么会走到ComponentScanner中 + // 由于ComponentScanner中已经对原始类进行了动态代理,出来的对象已经变成了动态代理类,所以这时候的bean已经是NodeComponent的子类了 + // 所以spring体系下,无需再对这个bean做二次代理 + // 非spring体系下,从2.11.4开始不再支持声明式组件 + List declWarpBeanList = DeclComponentParserHolder.loadDeclComponentParser().parseDeclBean(cmpClazz, nodeId, name); - cmpInstanceList = declWarpBeanList.stream().map( - declWarpBean -> (NodeComponent)ContextAwareHolder.loadContextAware().registerDeclWrapBean(nodeId, declWarpBean) - ).collect(Collectors.toList()); - } else { - // 以node方式配置,本质上是为了适配无spring的环境,如果有spring环境,其实不用这么配置 - // 这里的逻辑是判断是否能从spring上下文中取到,如果没有spring,则就是new instance了 - // 如果是script类型的节点,因为class只有一个,所以也不能注册进spring上下文,注册的时候需要new Instance - if (!type.isScript()) { - cmpInstanceList = ListUtil - .toList((NodeComponent) ContextAwareHolder.loadContextAware().registerOrGet(nodeId, cmpClazz)); - } - // 如果为空 - if (cmpInstanceList.isEmpty()) { - NodeComponent cmpInstance = (NodeComponent) cmpClazz.newInstance(); - cmpInstanceList.add(cmpInstance); - } - } - // 进行初始化component - cmpInstanceList.forEach(cmpInstance -> ComponentInitializer.loadInstance() - .initComponent(cmpInstance, type, name, cmpInstance.getNodeId() == null ? nodeId : cmpInstance.getNodeId())); + cmpInstanceList = declWarpBeanList.stream().map( + declWarpBean -> (NodeComponent) ContextAwareHolder.loadContextAware().registerDeclWrapBean(nodeId, declWarpBean) + ).collect(Collectors.toList()); + } else { + // 以node方式配置,本质上是为了适配无spring的环境,如果有spring环境,其实不用这么配置 + // 这里的逻辑是判断是否能从spring上下文中取到,如果没有spring,则就是new instance了 + // 如果是script类型的节点,因为class只有一个,所以也不能注册进spring上下文,注册的时候需要new Instance + if (!type.isScript()) { + cmpInstanceList = ListUtil + .toList((NodeComponent) ContextAwareHolder.loadContextAware().registerOrGet(nodeId, cmpClazz)); + } + // 如果为空 + if (cmpInstanceList.isEmpty()) { + NodeComponent cmpInstance = (NodeComponent) cmpClazz.newInstance(); + cmpInstanceList.add(cmpInstance); + } + } + // 进行初始化component + cmpInstanceList.forEach(cmpInstance -> ComponentInitializer.loadInstance() + .initComponent(cmpInstance, type, name, cmpInstance.getNodeId() == null ? nodeId : cmpInstance.getNodeId())); - return cmpInstanceList; - } + return cmpInstanceList; + } - public static void compileScriptNode(Node node) { - String nodeId = node.getId(), name = node.getName(), script = node.getScript(), language = node.getLanguage(); - NodeTypeEnum type = node.getType(); + public static void compileScriptNode(Node node) { + String nodeId = node.getId(), name = node.getName(), script = node.getScript(), language = node.getLanguage(); + NodeTypeEnum type = node.getType(); try { List cmpInstanceList = getNodeComponentList(nodeId, name, type, ScriptComponent.ScriptComponentClassMap.get(type)); - NodeComponent cmpInstance = cmpInstanceList.get(0); + NodeComponent cmpInstance = cmpInstanceList.get(0); - addCompiledNode2Map(node, nodeId, script, language, type, cmpInstance); + addCompiledNode2Map(node, nodeId, script, language, type, cmpInstance); } catch (Exception e) { - String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name)); - LOG.error(e.getMessage()); - throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage())); + String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name)); + LOG.error(e.getMessage()); + throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage())); } } - private static void addCompiledNode2Map(Node node, String nodeId, String script, String language, NodeTypeEnum type, NodeComponent cmpInstance) { - // 如果是脚本节点,则还要加载script脚本 - if (type.isScript()) { - if (StrUtil.isNotBlank(script)) { - node.setScript(script); - node.setLanguage(language); - ((ScriptComponent) cmpInstance).loadScript(script, language); - node.setCompiled(true); - node.setInstance(cmpInstance); - } else { - String errorMsg = StrUtil.format("script for node[{}] is empty", nodeId); - throw new ScriptLoadException(errorMsg); - } - } - String activeNodeId = StrUtil.isEmpty(cmpInstance.getNodeId()) ? nodeId : cmpInstance.getNodeId(); - put2NodeMap(activeNodeId, node); - addFallbackNode(node); - } + private static void addCompiledNode2Map(Node node, String nodeId, String script, String language, NodeTypeEnum type, NodeComponent cmpInstance) { + // 如果是脚本节点,则还要加载script脚本 + if (type.isScript()) { + if (StrUtil.isNotBlank(script)) { + node.setScript(script); + node.setLanguage(language); + ((ScriptComponent) cmpInstance).loadScript(script, language); + node.setCompiled(true); + node.setInstance(cmpInstance); + } else { + String errorMsg = StrUtil.format("script for node[{}] is empty", nodeId); + throw new ScriptLoadException(errorMsg); + } + } + String activeNodeId = StrUtil.isEmpty(cmpInstance.getNodeId()) ? nodeId : cmpInstance.getNodeId(); + put2NodeMap(activeNodeId, node); + addFallbackNode(node); + } - // 如果是spring自动扫描的组件,在addManagedNode方法中就已经完成了组装了 - // 调用到这里,分两种情况,一是脚本组件,二是通过LiteFlowNodeBuilder代码进行组装的组件 - private static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz, String script, String language) { - try { - // 获得初始化好的NodeComponent - // 按理说一个nodeId对应一个NodeComponent,这里得到的是List的原因是,声明式组件有可能会有多个nodeId。 - // 声明式组件又分类声明和方法声明,如果对于方法声明来说,这里的nodeId其实并不是最终真正的nodeId。 - List cmpInstanceList = getNodeComponentList(nodeId, name, type, cmpClazz); + // 如果是spring自动扫描的组件,在addManagedNode方法中就已经完成了组装了 + // 调用到这里,分两种情况,一是脚本组件,二是通过LiteFlowNodeBuilder代码进行组装的组件 + private static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz, String script, String language) { - // 初始化Node,把component放到Node里去 - List nodes = cmpInstanceList.stream().map(Node::new).collect(Collectors.toList()); + try { + String nodeIdStr = StrUtil.isBlank(nodeId) ? name : nodeId; + // 检查nodeId是否合法 + boolean nodeIdFlag = QlExpressUtils.checkVariableName(nodeIdStr); + // node id 不合法 + if (!nodeIdFlag) { + throw new NodeIdUnIllegalException(nodeIdStr); + } - for (int i = 0; i < nodes.size(); i++) { - addCompiledNode2Map(nodes.get(i), nodeId, script, language, type, cmpInstanceList.get(i)); - } - } catch (Exception e) { - String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name)); - LOG.error(e.getMessage()); - throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage())); - } - } + // 获得初始化好的NodeComponent + // 按理说一个nodeId对应一个NodeComponent,这里得到的是List的原因是,声明式组件有可能会有多个nodeId。 + // 声明式组件又分类声明和方法声明,如果对于方法声明来说,这里的nodeId其实并不是最终真正的nodeId。 + List cmpInstanceList = getNodeComponentList(nodeId, name, type, cmpClazz); - public static Node getNode(String nodeId) { - return nodeMap.get(nodeId); - } + // 初始化Node,把component放到Node里去 + List nodes = cmpInstanceList.stream().map(Node::new).collect(Collectors.toList()); - public static Map getNodeMap() { - return nodeMap; - } + for (int i = 0; i < nodes.size(); i++) { + addCompiledNode2Map(nodes.get(i), nodeId, script, language, type, cmpInstanceList.get(i)); + } + } catch (NodeIdUnIllegalException e) { + String nodeIdStr = e.getMessage(); + String error = StrUtil.format( + "component[{}] register error", + StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name) + ); - public static Map getChainMap() { - return chainMap; - } + error = "Invalid node id: [" + nodeIdStr + "]. " + + "node id must follow variable naming rules: " + + "cannot start with a digit, must consist of letters, digits, underscores (_), or dollar signs ($), " + + "and must not contain hyphens (-). " + + error; - public static Node getFallBackNode(NodeTypeEnum nodeType){ - String key = StrUtil.format("FB_{}", nodeType.name()); - return fallbackNodeMap.get(key); - } + LOG.error(error, e); + throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage())); + } catch (Exception e) { + String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name)); + LOG.error(e.getMessage()); + throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage())); + } + } - public static void cleanCache() { - chainMap.clear(); - nodeMap.clear(); - fallbackNodeMap.clear(); - elMd5Map.clear(); - cleanScriptCache(); - } + public static Node getNode(String nodeId) { + return nodeMap.get(nodeId); + } - public static void cleanScriptCache() { - // 如果引入了脚本组件SPI,则还需要清理脚本的缓存 - try { - ScriptExecutorFactory.loadInstance().cleanScriptCache(); - } - catch (ScriptSpiException ignored) { - } - } + public static Map getNodeMap() { + return nodeMap; + } - public static void refreshFlowMetaData(FlowParserTypeEnum type, String content) throws Exception { - if (type.equals(FlowParserTypeEnum.TYPE_EL_XML)) { - new LocalXmlFlowELParser().parse(content); - } - else if (type.equals(FlowParserTypeEnum.TYPE_EL_JSON)) { - new LocalJsonFlowELParser().parse(content); - } - else if (type.equals(FlowParserTypeEnum.TYPE_EL_YML)) { - new LocalYmlFlowELParser().parse(content); - } - } + public static Map getChainMap() { + return chainMap; + } - public static String getChainIdByElMd5(String elMd5) { - return elMd5Map.get(elMd5); - } + public static Node getFallBackNode(NodeTypeEnum nodeType) { + String key = StrUtil.format("FB_{}", nodeType.name()); + return fallbackNodeMap.get(key); + } - public static boolean removeChain(String chainId) { - if (containChain(chainId)) { - Chain removedChain = chainMap.remove(chainId); - // 移除 elMd5 对应的 chainId - elMd5Map.remove(removedChain.getElMd5()); - return true; - } - else { - String errMsg = StrUtil.format("cannot find the chain[{}]", chainId); - LOG.error(errMsg); - return false; - } - } + public static void cleanCache() { + chainMap.clear(); + nodeMap.clear(); + fallbackNodeMap.clear(); + elMd5Map.clear(); + cleanScriptCache(); + } - public static void removeChain(String... chainIds) { - Arrays.stream(chainIds).forEach(FlowBus::removeChain); - } + public static void cleanScriptCache() { + // 如果引入了脚本组件SPI,则还需要清理脚本的缓存 + try { + ScriptExecutorFactory.loadInstance().cleanScriptCache(); + } catch (ScriptSpiException ignored) { + } + } - // 移除节点 - public static boolean removeNode(String nodeId) { - return nodeMap.remove(nodeId) != null; - } + public static void refreshFlowMetaData(FlowParserTypeEnum type, String content) throws Exception { + if (type.equals(FlowParserTypeEnum.TYPE_EL_XML)) { + new LocalXmlFlowELParser().parse(content); + } else if (type.equals(FlowParserTypeEnum.TYPE_EL_JSON)) { + new LocalJsonFlowELParser().parse(content); + } else if (type.equals(FlowParserTypeEnum.TYPE_EL_YML)) { + new LocalYmlFlowELParser().parse(content); + } + } - // 判断是否是降级组件,如果是则添加到 fallbackNodeMap - private static void addFallbackNode(Node node) { - NodeComponent nodeComponent = node.getInstance(); - FallbackCmp fallbackCmp = AnnoUtil.getAnnotation(nodeComponent.getClass(), FallbackCmp.class); - if (fallbackCmp == null) { - return; - } + public static String getChainIdByElMd5(String elMd5) { + return elMd5Map.get(elMd5); + } - NodeTypeEnum nodeType = node.getType(); - String key = StrUtil.format("FB_{}", nodeType.name()); - fallbackNodeMap.put(key, node); - } + public static boolean removeChain(String chainId) { + if (containChain(chainId)) { + Chain removedChain = chainMap.remove(chainId); + // 移除 elMd5 对应的 chainId + elMd5Map.remove(removedChain.getElMd5()); + return true; + } else { + String errMsg = StrUtil.format("cannot find the chain[{}]", chainId); + LOG.error(errMsg); + return false; + } + } + + public static void removeChain(String... chainIds) { + Arrays.stream(chainIds).forEach(FlowBus::removeChain); + } + + // 移除节点 + public static boolean removeNode(String nodeId) { + return nodeMap.remove(nodeId) != null; + } + + // 判断是否是降级组件,如果是则添加到 fallbackNodeMap + private static void addFallbackNode(Node node) { + NodeComponent nodeComponent = node.getInstance(); + FallbackCmp fallbackCmp = AnnoUtil.getAnnotation(nodeComponent.getClass(), FallbackCmp.class); + if (fallbackCmp == null) { + return; + } + + NodeTypeEnum nodeType = node.getType(); + String key = StrUtil.format("FB_{}", nodeType.name()); + fallbackNodeMap.put(key, node); + } // 重新加载脚本 - public static void reloadScript(String nodeId, String script) { - Node node = getNode(nodeId); - if (node == null || !node.getType().isScript()) { - return; - } + public static void reloadScript(String nodeId, String script) { + Node node = getNode(nodeId); + if (node == null || !node.getType().isScript()) { + return; + } // 更新元数据模版中的脚本 - node.setScript(script); + node.setScript(script); - // 更新Chain中的Node中的脚本 - LiteflowMetaOperator.getNodesInAllChain(nodeId).forEach(n -> n.setScript(script)); + // 更新Chain中的Node中的脚本 + LiteflowMetaOperator.getNodesInAllChain(nodeId).forEach(n -> n.setScript(script)); - ScriptExecutorFactory.loadInstance() - .getScriptExecutor(node.getLanguage()) - .load(nodeId, script); - } + ScriptExecutorFactory.loadInstance() + .getScriptExecutor(node.getLanguage()) + .load(nodeId, script); + } - // 卸载脚本节点 - public static boolean unloadScriptNode(String nodeId) { - Node node = getNode(nodeId); - if (node == null || !node.getType().isScript()) { - return false; - } - // 卸载脚本 - ScriptExecutorFactory.loadInstance() - .getScriptExecutor(node.getLanguage()) - .unLoad(nodeId); - // 移除脚本 - return removeNode(nodeId); - } + // 卸载脚本节点 + public static boolean unloadScriptNode(String nodeId) { + Node node = getNode(nodeId); + if (node == null || !node.getType().isScript()) { + return false; + } + // 卸载脚本 + ScriptExecutorFactory.loadInstance() + .getScriptExecutor(node.getLanguage()) + .unLoad(nodeId); + // 移除脚本 + return removeNode(nodeId); + } - // 重新加载规则 - public static void reloadChain(String chainId, String elContent) { - reloadChain(chainId, elContent, null); - } + // 重新加载规则 + public static void reloadChain(String chainId, String elContent) { + reloadChain(chainId, elContent, null); + } - public static void reloadChain(String chainId, String elContent, String routeContent) { - LiteFlowChainELBuilder.createChain().setChainId(chainId).setEL(elContent).setRoute(routeContent).build(); - } + public static void reloadChain(String chainId, String elContent, String routeContent) { + LiteFlowChainELBuilder.createChain().setChainId(chainId).setEL(elContent).setRoute(routeContent).build(); + } - public static void clearStat(){ - initStat.set(false); - } + public static void clearStat() { + initStat.set(false); + } - private static void put2NodeMap(String nodeId, Node node){ - // 如果有生命周期则执行相应生命周期实现 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessNodeBuildLifeCycleList())){ - LifeCycleHolder.getPostProcessNodeBuildLifeCycleList().forEach( - postProcessAfterNodeBuildLifeCycle -> postProcessAfterNodeBuildLifeCycle.postProcessBeforeNodeBuild(node) - ); - } + private static void put2NodeMap(String nodeId, Node node) { + // 如果有生命周期则执行相应生命周期实现 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessNodeBuildLifeCycleList())) { + LifeCycleHolder.getPostProcessNodeBuildLifeCycleList().forEach( + postProcessAfterNodeBuildLifeCycle -> postProcessAfterNodeBuildLifeCycle.postProcessBeforeNodeBuild(node) + ); + } - nodeMap.put(nodeId, node); + nodeMap.put(nodeId, node); - // 如果有生命周期则执行相应生命周期实现 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessNodeBuildLifeCycleList())){ - LifeCycleHolder.getPostProcessNodeBuildLifeCycleList().forEach( - postProcessAfterNodeBuildLifeCycle -> postProcessAfterNodeBuildLifeCycle.postProcessAfterNodeBuild(node) - ); - } - } + // 如果有生命周期则执行相应生命周期实现 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessNodeBuildLifeCycleList())) { + LifeCycleHolder.getPostProcessNodeBuildLifeCycleList().forEach( + postProcessAfterNodeBuildLifeCycle -> postProcessAfterNodeBuildLifeCycle.postProcessAfterNodeBuild(node) + ); + } + } } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/util/QlExpressUtils.java b/liteflow-core/src/main/java/com/yomahub/liteflow/util/QlExpressUtils.java new file mode 100644 index 000000000..a840e91cb --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/util/QlExpressUtils.java @@ -0,0 +1,94 @@ +package com.yomahub.liteflow.util; + +import com.ql.util.express.ExpressRunner; +import com.yomahub.liteflow.builder.el.operator.*; +import com.yomahub.liteflow.common.ChainConstant; + +/** + * EL 工具类 + * + * @author tangkc + * @since 2.13.2 + */ +public class QlExpressUtils { + + /** + * EL解析引擎 + */ + private final static ExpressRunner EXPRESS_RUNNER = new ExpressRunner(); + + static { + // 初始化QLExpress的Runner + EXPRESS_RUNNER.addFunction(ChainConstant.THEN, new ThenOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.WHEN, new WhenOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.SER, new ThenOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.PAR, new WhenOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.SWITCH, new SwitchOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.PRE, new PreOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.FINALLY, new FinallyOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.IF, new IfOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.NODE.toUpperCase(), new NodeOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.NODE, new NodeOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.FOR, new ForOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.WHILE, new WhileOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.ITERATOR, new IteratorOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.CATCH, new CatchOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.AND, new AndOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.OR, new OrOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.NOT, new NotOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ELSE, Object.class, new ElseOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ELIF, Object.class, new ElifOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TO, Object.class, new ToOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TO.toLowerCase(), Object.class, new ToOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DEFAULT, Object.class, new DefaultOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TAG, Object.class, new TagOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ANY, Object.class, new AnyOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MUST, Object.class, new MustOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PERCENTAGE, Object.class, new PercentageOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ID, Object.class, new IdOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.IGNORE_ERROR, Object.class, new IgnoreErrorOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.THREAD_POOL, Object.class, new ThreadPoolOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DO, Object.class, new DoOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BREAK, Object.class, new BreakOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DATA, Object.class, new DataOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_SECONDS, Object.class, new MaxWaitSecondsOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_MILLISECONDS, Object.class, new MaxWaitMillisecondsOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PARALLEL, Object.class, new ParallelOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.RETRY, Object.class, new RetryOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BIND, Object.class, new BindOperator()); + + } + + /** + * 获取QLExpress的实例 + */ + public static ExpressRunner getInstance() { + return EXPRESS_RUNNER; + } + + /** + * 检查变量名是否符合 变量命名规则 + * + * @param variableName 变量名 + * @return 如果符合规范返回 true,否则返回 false + */ + public static boolean checkVariableName(String variableName) { + if (variableName == null || variableName.isEmpty()) { + return false; + } + + // 首字符必须是合法的 Java 标识符起始字符 + if (!Character.isJavaIdentifierStart(variableName.charAt(0))) { + return false; + } + + // 后续字符必须是合法的 Java 标识符部分 + for (int i = 1; i < variableName.length(); i++) { + if (!Character.isJavaIdentifierPart(variableName.charAt(i))) { + return false; + } + } + + return true; + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/utils/QlExpressUtilsTest.java b/liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/utils/QlExpressUtilsTest.java new file mode 100644 index 000000000..047c1d4ec --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/utils/QlExpressUtilsTest.java @@ -0,0 +1,27 @@ +package com.yomahub.liteflow.test.utils; + +import cn.hutool.core.lang.Assert; +import com.yomahub.liteflow.util.QlExpressUtils; +import org.junit.jupiter.api.Test; + +/** + * EL 工具类测试 + * + * @author tangkc + * @since 2.13.2 + */ +public class QlExpressUtilsTest { + + @Test + public void checkVariableNameTest(){ + // 错误的 + Assert.isFalse(QlExpressUtils.checkVariableName("")); + Assert.isFalse(QlExpressUtils.checkVariableName("11a")); + Assert.isFalse(QlExpressUtils.checkVariableName("a-a")); + // 正确的 + Assert.isTrue(QlExpressUtils.checkVariableName("aa")); + Assert.isTrue(QlExpressUtils.checkVariableName("_aa")); + Assert.isTrue(QlExpressUtils.checkVariableName("$aa")); + Assert.isTrue(QlExpressUtils.checkVariableName("$a_a")); + } +} From 1d300131bb1cd15b61ee7abbc72fad9c1025f55c Mon Sep 17 00:00:00 2001 From: gaibu <1016771049@qq.com> Date: Thu, 21 Aug 2025 22:29:56 +0800 Subject: [PATCH 2/3] =?UTF-8?q?enhancement=20#ICU4Z3=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20nodeid=20=E4=B8=8D=E5=90=88=E6=B3=95=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=EF=BC=8C=E5=A2=9E=E5=8A=A0=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=BC=95=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builder/el/LiteFlowChainELBuilder.java | 52 +--------- .../exception/NodeIdUnIllegalException.java | 31 ++++++ .../yomahub/liteflow/util/QlExpressUtils.java | 94 +++++++++++++++++++ .../test/utils/QlExpressUtilsTest.java | 27 ++++++ 4 files changed, 157 insertions(+), 47 deletions(-) create mode 100644 liteflow-core/src/main/java/com/yomahub/liteflow/exception/NodeIdUnIllegalException.java create mode 100644 liteflow-core/src/main/java/com/yomahub/liteflow/util/QlExpressUtils.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/utils/QlExpressUtilsTest.java 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 7ce55ea09..4ce4ce0c8 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 @@ -31,6 +31,7 @@ import com.yomahub.liteflow.log.LFLoggerManager; import com.yomahub.liteflow.property.LiteflowConfig; import com.yomahub.liteflow.property.LiteflowConfigGetter; import com.yomahub.liteflow.util.ElRegexUtil; +import com.yomahub.liteflow.util.QlExpressUtils; import java.util.ArrayList; import java.util.Arrays; @@ -66,53 +67,10 @@ public class LiteFlowChainELBuilder { * 所以在这里做一个缓存,等conditionList全部build完毕后,再去一次性替换chain里面的conditionList */ private final List conditionList; - - /** - * EL解析引擎 - */ - public final static ExpressRunner EXPRESS_RUNNER = new ExpressRunner(); - - static { - // 初始化QLExpress的Runner - EXPRESS_RUNNER.addFunction(ChainConstant.THEN, new ThenOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.WHEN, new WhenOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.SER, new ThenOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.PAR, new WhenOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.SWITCH, new SwitchOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.PRE, new PreOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.FINALLY, new FinallyOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.IF, new IfOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.NODE.toUpperCase(), new NodeOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.NODE, new NodeOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.FOR, new ForOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.WHILE, new WhileOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.ITERATOR, new IteratorOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.CATCH, new CatchOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.AND, new AndOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.OR, new OrOperator()); - EXPRESS_RUNNER.addFunction(ChainConstant.NOT, new NotOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ELSE, Object.class, new ElseOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ELIF, Object.class, new ElifOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TO, Object.class, new ToOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TO.toLowerCase(), Object.class, new ToOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DEFAULT, Object.class, new DefaultOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TAG, Object.class, new TagOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ANY, Object.class, new AnyOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MUST, Object.class, new MustOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PERCENTAGE, Object.class, new PercentageOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ID, Object.class, new IdOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.IGNORE_ERROR, Object.class, new IgnoreErrorOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.THREAD_POOL, Object.class, new ThreadPoolOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DO, Object.class, new DoOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BREAK, Object.class, new BreakOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DATA, Object.class, new DataOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_SECONDS, Object.class, new MaxWaitSecondsOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_MILLISECONDS, Object.class, new MaxWaitMillisecondsOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PARALLEL, Object.class, new ParallelOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.RETRY, Object.class, new RetryOperator()); - EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BIND, Object.class, new BindOperator()); - - } + /** + * EL解析引擎 + */ + private final static ExpressRunner EXPRESS_RUNNER = QlExpressUtils.getInstance(); public static LiteFlowChainELBuilder createChain() { return new LiteFlowChainELBuilder(); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/exception/NodeIdUnIllegalException.java b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/NodeIdUnIllegalException.java new file mode 100644 index 000000000..820aa03fa --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/NodeIdUnIllegalException.java @@ -0,0 +1,31 @@ +package com.yomahub.liteflow.exception; + +/** + * node id不合法异常 + * + * @author tangkc + * @since 2.13.2 + */ +public class NodeIdUnIllegalException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 异常信息 + */ + private String message; + + public NodeIdUnIllegalException(String message) { + this.message = message; + } + + @Override + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/util/QlExpressUtils.java b/liteflow-core/src/main/java/com/yomahub/liteflow/util/QlExpressUtils.java new file mode 100644 index 000000000..a840e91cb --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/util/QlExpressUtils.java @@ -0,0 +1,94 @@ +package com.yomahub.liteflow.util; + +import com.ql.util.express.ExpressRunner; +import com.yomahub.liteflow.builder.el.operator.*; +import com.yomahub.liteflow.common.ChainConstant; + +/** + * EL 工具类 + * + * @author tangkc + * @since 2.13.2 + */ +public class QlExpressUtils { + + /** + * EL解析引擎 + */ + private final static ExpressRunner EXPRESS_RUNNER = new ExpressRunner(); + + static { + // 初始化QLExpress的Runner + EXPRESS_RUNNER.addFunction(ChainConstant.THEN, new ThenOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.WHEN, new WhenOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.SER, new ThenOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.PAR, new WhenOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.SWITCH, new SwitchOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.PRE, new PreOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.FINALLY, new FinallyOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.IF, new IfOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.NODE.toUpperCase(), new NodeOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.NODE, new NodeOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.FOR, new ForOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.WHILE, new WhileOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.ITERATOR, new IteratorOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.CATCH, new CatchOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.AND, new AndOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.OR, new OrOperator()); + EXPRESS_RUNNER.addFunction(ChainConstant.NOT, new NotOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ELSE, Object.class, new ElseOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ELIF, Object.class, new ElifOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TO, Object.class, new ToOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TO.toLowerCase(), Object.class, new ToOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DEFAULT, Object.class, new DefaultOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.TAG, Object.class, new TagOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ANY, Object.class, new AnyOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MUST, Object.class, new MustOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PERCENTAGE, Object.class, new PercentageOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.ID, Object.class, new IdOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.IGNORE_ERROR, Object.class, new IgnoreErrorOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.THREAD_POOL, Object.class, new ThreadPoolOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DO, Object.class, new DoOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BREAK, Object.class, new BreakOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DATA, Object.class, new DataOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_SECONDS, Object.class, new MaxWaitSecondsOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_MILLISECONDS, Object.class, new MaxWaitMillisecondsOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PARALLEL, Object.class, new ParallelOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.RETRY, Object.class, new RetryOperator()); + EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BIND, Object.class, new BindOperator()); + + } + + /** + * 获取QLExpress的实例 + */ + public static ExpressRunner getInstance() { + return EXPRESS_RUNNER; + } + + /** + * 检查变量名是否符合 变量命名规则 + * + * @param variableName 变量名 + * @return 如果符合规范返回 true,否则返回 false + */ + public static boolean checkVariableName(String variableName) { + if (variableName == null || variableName.isEmpty()) { + return false; + } + + // 首字符必须是合法的 Java 标识符起始字符 + if (!Character.isJavaIdentifierStart(variableName.charAt(0))) { + return false; + } + + // 后续字符必须是合法的 Java 标识符部分 + for (int i = 1; i < variableName.length(); i++) { + if (!Character.isJavaIdentifierPart(variableName.charAt(i))) { + return false; + } + } + + return true; + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/utils/QlExpressUtilsTest.java b/liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/utils/QlExpressUtilsTest.java new file mode 100644 index 000000000..047c1d4ec --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/utils/QlExpressUtilsTest.java @@ -0,0 +1,27 @@ +package com.yomahub.liteflow.test.utils; + +import cn.hutool.core.lang.Assert; +import com.yomahub.liteflow.util.QlExpressUtils; +import org.junit.jupiter.api.Test; + +/** + * EL 工具类测试 + * + * @author tangkc + * @since 2.13.2 + */ +public class QlExpressUtilsTest { + + @Test + public void checkVariableNameTest(){ + // 错误的 + Assert.isFalse(QlExpressUtils.checkVariableName("")); + Assert.isFalse(QlExpressUtils.checkVariableName("11a")); + Assert.isFalse(QlExpressUtils.checkVariableName("a-a")); + // 正确的 + Assert.isTrue(QlExpressUtils.checkVariableName("aa")); + Assert.isTrue(QlExpressUtils.checkVariableName("_aa")); + Assert.isTrue(QlExpressUtils.checkVariableName("$aa")); + Assert.isTrue(QlExpressUtils.checkVariableName("$a_a")); + } +} From e8e1f8e2375db9f5856b3430068ea9ed9ba6f73f Mon Sep 17 00:00:00 2001 From: gaibu <1016771049@qq.com> Date: Wed, 27 Aug 2025 21:26:56 +0800 Subject: [PATCH 3/3] =?UTF-8?q?enhancement=20#ICU4Z3=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20nodeid=20=E4=B8=8D=E5=90=88=E6=B3=95=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=EF=BC=8C=E5=A2=9E=E5=8A=A0=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=BC=95=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yomahub/liteflow/flow/FlowBus.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java index 940e90893..60f03c688 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java @@ -24,6 +24,7 @@ import com.yomahub.liteflow.enums.FlowParserTypeEnum; import com.yomahub.liteflow.enums.NodeTypeEnum; import com.yomahub.liteflow.enums.ParseModeEnum; import com.yomahub.liteflow.exception.ComponentCannotRegisterException; +import com.yomahub.liteflow.exception.NodeIdUnIllegalException; import com.yomahub.liteflow.exception.NullNodeTypeException; import com.yomahub.liteflow.flow.element.Chain; import com.yomahub.liteflow.flow.element.Node; @@ -43,6 +44,7 @@ import com.yomahub.liteflow.spi.ContextAware; import com.yomahub.liteflow.spi.holder.ContextAwareHolder; import com.yomahub.liteflow.spi.holder.DeclComponentParserHolder; import com.yomahub.liteflow.util.CopyOnWriteHashMap; +import com.yomahub.liteflow.util.QlExpressUtils; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -303,6 +305,14 @@ public class FlowBus { // 调用到这里,分两种情况,一是脚本组件,二是通过LiteFlowNodeBuilder代码进行组装的组件 private static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz, String script, String language) { try { + String nodeIdStr = StrUtil.isBlank(nodeId) ? name : nodeId; + // 检查nodeId是否合法 + boolean nodeIdFlag = QlExpressUtils.checkVariableName(nodeIdStr); + // node id 不合法 + if (!nodeIdFlag) { + throw new NodeIdUnIllegalException(nodeIdStr); + } + // 获得初始化好的NodeComponent // 按理说一个nodeId对应一个NodeComponent,这里得到的是List的原因是,声明式组件有可能会有多个nodeId。 // 声明式组件又分类声明和方法声明,如果对于方法声明来说,这里的nodeId其实并不是最终真正的nodeId。 @@ -314,7 +324,22 @@ public class FlowBus { for (int i = 0; i < nodes.size(); i++) { addCompiledNode2Map(nodes.get(i), nodeId, script, language, type, cmpInstanceList.get(i)); } - } catch (Exception e) { + } catch (NodeIdUnIllegalException e) { + String nodeIdStr = e.getMessage(); + String error = StrUtil.format( + "component[{}] register error", + StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name) + ); + + error = "Invalid node id: [" + nodeIdStr + "]. " + + "node id must follow variable naming rules: " + + "cannot start with a digit, must consist of letters, digits, underscores (_), or dollar signs ($), " + + "and must not contain hyphens (-). " + + error; + + LOG.error(error, e); + throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage())); + } catch (Exception e) { String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name)); LOG.error(e.getMessage()); throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage()));