From 0da24f4b0600877e28aa5b154e54f69351269bd8 Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Wed, 11 Jun 2025 19:36:43 +0800 Subject: [PATCH 01/15] =?UTF-8?q?enhancement=20#IBQCWB=20FlowExecutor=20?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E6=94=AF=E6=8C=81=E6=89=A7=E8=A1=8C=E8=B0=83?= =?UTF-8?q?=E7=94=A8=20EL=20=E8=A1=A8=E8=BE=BE=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yomahub/liteflow/core/FlowExecutor.java | 948 +++++++++--------- .../test/base/BaseELSpringbootTest.java | 7 + 2 files changed, 507 insertions(+), 448 deletions(-) diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java index d393d75a4..e79dd56f6 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java @@ -13,6 +13,7 @@ import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Tuple; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; +import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder; import com.yomahub.liteflow.common.ChainConstant; import com.yomahub.liteflow.enums.ChainExecuteModeEnum; import com.yomahub.liteflow.enums.ParseModeEnum; @@ -53,524 +54,575 @@ import java.util.stream.Collectors; */ public class FlowExecutor { - private static final LFLog LOG = LFLoggerManager.getLogger(FlowExecutor.class); + private static final LFLog LOG = LFLoggerManager.getLogger(FlowExecutor.class); - private static final String PREFIX_FORMAT_CONFIG_REGEX = "el_xml:|el_json:|el_yml:"; + private static final String PREFIX_FORMAT_CONFIG_REGEX = "el_xml:|el_json:|el_yml:"; - private LiteflowConfig liteflowConfig; + private LiteflowConfig liteflowConfig; - public FlowExecutor() { - // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 - FlowExecutorHolder.setHolder(this); - // 初始化DataBus - DataBus.init(); - } + public FlowExecutor() { + // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 + FlowExecutorHolder.setHolder(this); + // 初始化DataBus + DataBus.init(); + } - public FlowExecutor(LiteflowConfig liteflowConfig) { - this.liteflowConfig = liteflowConfig; - // 把liteFlowConfig设到LiteFlowGetter中去 - LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); - // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 - FlowExecutorHolder.setHolder(this); - if (!liteflowConfig.getParseMode().equals(ParseModeEnum.PARSE_ALL_ON_FIRST_EXEC)) { - this.init(true); - } - // 初始化DataBus - DataBus.init(); - } + public FlowExecutor(LiteflowConfig liteflowConfig) { + this.liteflowConfig = liteflowConfig; + // 把liteFlowConfig设到LiteFlowGetter中去 + LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); + // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 + FlowExecutorHolder.setHolder(this); + if (!liteflowConfig.getParseMode().equals(ParseModeEnum.PARSE_ALL_ON_FIRST_EXEC)) { + this.init(true); + } + // 初始化DataBus + DataBus.init(); + } - /** - * FlowExecutor的初始化化方式,主要用于parse规则文件 - * isStart表示是否是系统启动阶段,启动阶段要做额外的事情,而因为reload所调用的init就不用做 - */ - public void init(boolean isStart) { - if (ObjectUtil.isNull(liteflowConfig)) { - throw new ConfigErrorException("config error, please check liteflow config property"); - } + /** + * FlowExecutor的初始化化方式,主要用于parse规则文件 + * isStart表示是否是系统启动阶段,启动阶段要做额外的事情,而因为reload所调用的init就不用做 + */ + public void init(boolean isStart) { + if (ObjectUtil.isNull(liteflowConfig)) { + throw new ConfigErrorException("config error, please check liteflow config property"); + } - // 在相应的环境下进行节点的初始化工作 - // 在spring体系下会获得spring扫描后的节点,接入元数据 - // 在非spring体系下是一个空实现,等于不做此步骤 - ContextCmpInitHolder.loadContextCmpInit().initCmp(); + // 在相应的环境下进行节点的初始化工作 + // 在spring体系下会获得spring扫描后的节点,接入元数据 + // 在非spring体系下是一个空实现,等于不做此步骤 + ContextCmpInitHolder.loadContextCmpInit().initCmp(); - if (isStart){ - // 进行id生成器的初始化 - IdGeneratorHolder.init(); - } + if (isStart) { + // 进行id生成器的初始化 + IdGeneratorHolder.init(); + } - String ruleSource = liteflowConfig.getRuleSource(); - if (StrUtil.isBlank(ruleSource)) { - // 查看有没有Parser的SPI实现 - // 所有的Parser的SPI实现都是以custom形式放入的,且只支持xml形式 - ServiceLoader loader = ServiceLoader.load(ParserClassNameSpi.class); - Iterator it = loader.iterator(); - if (it.hasNext()) { - ParserClassNameSpi parserClassNameSpi = it.next(); - ruleSource = "el_xml:" + parserClassNameSpi.getSpiClassName(); - liteflowConfig.setRuleSource(ruleSource); - } - else { - // ruleSource为空,而且没有spi形式的扩展,那么说明真的没有ruleSource - // 这种情况有可能是基于代码动态构建的 - return; - } - } + String ruleSource = liteflowConfig.getRuleSource(); + if (StrUtil.isBlank(ruleSource)) { + // 查看有没有Parser的SPI实现 + // 所有的Parser的SPI实现都是以custom形式放入的,且只支持xml形式 + ServiceLoader loader = ServiceLoader.load(ParserClassNameSpi.class); + Iterator it = loader.iterator(); + if (it.hasNext()) { + ParserClassNameSpi parserClassNameSpi = it.next(); + ruleSource = "el_xml:" + parserClassNameSpi.getSpiClassName(); + liteflowConfig.setRuleSource(ruleSource); + } else { + // ruleSource为空,而且没有spi形式的扩展,那么说明真的没有ruleSource + // 这种情况有可能是基于代码动态构建的 + return; + } + } - // 如果有前缀的,则不需要再进行分割了,说明是一个整体 - // 如果没有前缀,说明是本地文件,可能配置多个,所以需要分割 - List sourceRulePathList; - if (ReUtil.contains(PREFIX_FORMAT_CONFIG_REGEX, ruleSource)) { - sourceRulePathList = ListUtil.toList(ruleSource); - } - else { - String afterHandleRuleSource = ruleSource.replace(StrUtil.SPACE, StrUtil.EMPTY); - sourceRulePathList = ListUtil.toList(afterHandleRuleSource.split(",|;")); - } + // 如果有前缀的,则不需要再进行分割了,说明是一个整体 + // 如果没有前缀,说明是本地文件,可能配置多个,所以需要分割 + List sourceRulePathList; + if (ReUtil.contains(PREFIX_FORMAT_CONFIG_REGEX, ruleSource)) { + sourceRulePathList = ListUtil.toList(ruleSource); + } else { + String afterHandleRuleSource = ruleSource.replace(StrUtil.SPACE, StrUtil.EMPTY); + sourceRulePathList = ListUtil.toList(afterHandleRuleSource.split(",|;")); + } - FlowParser parser = null; - Set parserNameSet = new HashSet<>(); - List rulePathList = new ArrayList<>(); - for (String path : sourceRulePathList) { - try { - // 查找对应的解析器 - parser = FlowParserProvider.lookup(path); - parserNameSet.add(parser.getClass().getName()); - // 替换掉前缀标识(如:xml:/json:),保留剩下的完整地址,并统一路径格式 - path = ReUtil.replaceAll(path, PREFIX_FORMAT_CONFIG_REGEX, "").replace("\\", "/"); - rulePathList.add(path); + FlowParser parser = null; + Set parserNameSet = new HashSet<>(); + List rulePathList = new ArrayList<>(); + for (String path : sourceRulePathList) { + try { + // 查找对应的解析器 + parser = FlowParserProvider.lookup(path); + parserNameSet.add(parser.getClass().getName()); + // 替换掉前缀标识(如:xml:/json:),保留剩下的完整地址,并统一路径格式 + path = ReUtil.replaceAll(path, PREFIX_FORMAT_CONFIG_REGEX, "").replace("\\", "/"); + rulePathList.add(path); - // 支持多类型的配置文件,分别解析 - if (BooleanUtil.isTrue(liteflowConfig.isSupportMultipleType())) { - // 解析文件 - parser.parseMain(ListUtil.toList(path)); - } - } - catch (CyclicDependencyException e) { - LOG.error(e.getMessage()); - throw e; - } - catch (Exception e) { - String errorMsg = StrUtil.format("init flow executor cause error for path {},reason:{}", path, - e.getMessage()); - LOG.error(e.getMessage(), e); - throw new FlowExecutorNotInitException(errorMsg); - } - } + // 支持多类型的配置文件,分别解析 + if (BooleanUtil.isTrue(liteflowConfig.isSupportMultipleType())) { + // 解析文件 + parser.parseMain(ListUtil.toList(path)); + } + } catch (CyclicDependencyException e) { + LOG.error(e.getMessage()); + throw e; + } catch (Exception e) { + String errorMsg = StrUtil.format("init flow executor cause error for path {},reason:{}", path, + e.getMessage()); + LOG.error(e.getMessage(), e); + throw new FlowExecutorNotInitException(errorMsg); + } + } - // 单类型的配置文件,需要一起解析 - if (BooleanUtil.isFalse(liteflowConfig.isSupportMultipleType())) { - // 检查Parser是否只有一个,因为多个不同的parser会造成子流程的混乱 - if (parserNameSet.size() > 1) { - String errorMsg = "cannot have multiple different parsers"; - LOG.error(errorMsg); - throw new MultipleParsersException(errorMsg); - } + // 单类型的配置文件,需要一起解析 + if (BooleanUtil.isFalse(liteflowConfig.isSupportMultipleType())) { + // 检查Parser是否只有一个,因为多个不同的parser会造成子流程的混乱 + if (parserNameSet.size() > 1) { + String errorMsg = "cannot have multiple different parsers"; + LOG.error(errorMsg); + throw new MultipleParsersException(errorMsg); + } - // 进行多个配置文件的一起解析 - try { - if (parser != null) { - // 解析文件 - parser.parseMain(rulePathList); - } - else { - throw new ConfigErrorException("parse error, please check liteflow config property"); - } - } - catch (CyclicDependencyException e) { - LOG.error(e.getMessage(), e); - LOG.error(e.getMessage()); - throw e; - } - catch (ChainDuplicateException e) { - LOG.error(e.getMessage(), e); - throw e; - } - catch (RouteELInvalidException e) { - LOG.error(e.getMessage(), e); - throw e; - } - catch (Exception e) { - String errorMsg = StrUtil.format("init flow executor cause error for path {},reason: {}", rulePathList, - e.getMessage()); - LOG.error(e.getMessage(), e); - throw new FlowExecutorNotInitException(errorMsg); - } - } + // 进行多个配置文件的一起解析 + try { + if (parser != null) { + // 解析文件 + parser.parseMain(rulePathList); + } else { + throw new ConfigErrorException("parse error, please check liteflow config property"); + } + } catch (CyclicDependencyException e) { + LOG.error(e.getMessage(), e); + LOG.error(e.getMessage()); + throw e; + } catch (ChainDuplicateException e) { + LOG.error(e.getMessage(), e); + throw e; + } catch (RouteELInvalidException e) { + LOG.error(e.getMessage(), e); + throw e; + } catch (Exception e) { + String errorMsg = StrUtil.format("init flow executor cause error for path {},reason: {}", rulePathList, + e.getMessage()); + LOG.error(e.getMessage(), e); + throw new FlowExecutorNotInitException(errorMsg); + } + } - // 如果是ruleSource方式的,最后判断下有没有解析出来,如果没有解析出来则报错 - if (StrUtil.isBlank(liteflowConfig.getRuleSourceExtData()) - && MapUtil.isEmpty(liteflowConfig.getRuleSourceExtDataMap())) { - if (FlowBus.getChainMap().isEmpty()) { - String errMsg = StrUtil.format("no valid rule config found in rule path [{}]", - liteflowConfig.getRuleSource()); - throw new ConfigErrorException(errMsg); - } - } + // 如果是ruleSource方式的,最后判断下有没有解析出来,如果没有解析出来则报错 + if (StrUtil.isBlank(liteflowConfig.getRuleSourceExtData()) + && MapUtil.isEmpty(liteflowConfig.getRuleSourceExtDataMap())) { + if (FlowBus.getChainMap().isEmpty()) { + String errMsg = StrUtil.format("no valid rule config found in rule path [{}]", + liteflowConfig.getRuleSource()); + throw new ConfigErrorException(errMsg); + } + } - // 执行钩子 - if (isStart) { - FlowInitHook.executeHook(); - } + // 执行钩子 + if (isStart) { + FlowInitHook.executeHook(); + } - // 文件监听 - if (isStart && liteflowConfig.getEnableMonitorFile()) { - try { - addMonitorFilePaths(rulePathList); - MonitorFile.getInstance().create(); - } - catch (Exception e) { - String errMsg = StrUtil.format("file monitor init error for path:{}", rulePathList); - throw new MonitorFileInitErrorException(errMsg); - } + // 文件监听 + if (isStart && liteflowConfig.getEnableMonitorFile()) { + try { + addMonitorFilePaths(rulePathList); + MonitorFile.getInstance().create(); + } catch (Exception e) { + String errMsg = StrUtil.format("file monitor init error for path:{}", rulePathList); + throw new MonitorFileInitErrorException(errMsg); + } - } - } + } + } - // 此方法就是从原有的配置源主动拉取新的进行刷新 - // 和FlowBus.refreshFlowMetaData的区别就是一个为主动拉取,一个为被动监听到新的内容进行刷新 - public void reloadRule() { - long start = System.currentTimeMillis(); - init(false); - LOG.info("reload rules takes {}ms", System.currentTimeMillis() - start); - } + // 此方法就是从原有的配置源主动拉取新的进行刷新 + // 和FlowBus.refreshFlowMetaData的区别就是一个为主动拉取,一个为被动监听到新的内容进行刷新 + public void reloadRule() { + long start = System.currentTimeMillis(); + init(false); + LOG.info("reload rules takes {}ms", System.currentTimeMillis() - start); + } - // 单独调用某一个node - @Deprecated - public void invoke(String nodeId, Integer slotIndex) throws Exception { - Node node = FlowBus.getNode(nodeId); - node.execute(slotIndex); - } + // 单独调用某一个node + @Deprecated + public void invoke(String nodeId, Integer slotIndex) throws Exception { + Node node = FlowBus.getNode(nodeId); + node.execute(slotIndex); + } - // 调用一个流程并返回LiteflowResponse,上下文为默认的DefaultContext,初始参数为null - public LiteflowResponse execute2Resp(String chainId) { - return this.execute2Resp(chainId, null, DefaultContext.class); - } + // 调用一个流程并返回LiteflowResponse,上下文为默认的DefaultContext,初始参数为null + public LiteflowResponse execute2Resp(String chainId) { + return this.execute2Resp(chainId, null, DefaultContext.class); + } - // 调用一个流程并返回LiteflowResponse,上下文为默认的DefaultContext - public LiteflowResponse execute2Resp(String chainId, Object param) { - return this.execute2Resp(chainId, param, DefaultContext.class); - } + // 调用一个流程并返回LiteflowResponse,上下文为默认的DefaultContext + public LiteflowResponse execute2Resp(String chainId, Object param) { + return this.execute2Resp(chainId, param, DefaultContext.class); + } - // 调用一个流程并返回LiteflowResponse,允许多上下文的传入 - public LiteflowResponse execute2Resp(String chainId, Object param, Class... contextBeanClazzArray) { - return this.execute2Resp(chainId, param, null, contextBeanClazzArray, null); - } + // 调用一个流程并返回LiteflowResponse,允许多上下文的传入 + public LiteflowResponse execute2Resp(String chainId, Object param, Class... contextBeanClazzArray) { + return this.execute2Resp(chainId, param, null, contextBeanClazzArray, null); + } - public List executeRouteChain(Object param, Class... contextBeanClazzArray){ - return this.executeWithRoute(null, param, null, contextBeanClazzArray, null); - } + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @return LiteflowResponse + */ + public LiteflowResponse execute2RespWithEL(String elStr) { + return this.execute2RespWithEL(elStr, null, null, DefaultContext.class); + } - public List executeRouteChain(String namespace, Object param, Class... contextBeanClazzArray){ - return this.executeWithRoute(namespace, param, null, contextBeanClazzArray, null); - } + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @return LiteflowResponse + */ + public LiteflowResponse execute2RespWithEL(String elStr, Object param) { + return this.execute2RespWithEL(elStr, param, null, DefaultContext.class); + } - public LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray) { - return this.execute2Resp(chainId, param, null, null, contextBeanArray); - } + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @param requestId 请求 ID + * @param contextBeanClazzArray 上下文 Class + * @return LiteflowResponse + */ + public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class... contextBeanClazzArray) { + return this.execute2RespWithEL(elStr, param, requestId, contextBeanClazzArray, null); + } - public List executeRouteChain(Object param, Object... contextBeanArray){ - return this.executeWithRoute(null, param, null, null, contextBeanArray); - } + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @param requestId 请求 ID + * @param contextBeanArray 上下文对象 + * @return LiteflowResponse + */ + public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Object... contextBeanArray) { + return this.execute2RespWithEL(elStr, param, requestId, null, contextBeanArray); + } - public List executeRouteChain(String namespace, Object param, Object... contextBeanArray){ - return this.executeWithRoute(namespace, param, null, null, contextBeanArray); - } + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @param requestId 请求 ID + * @param contextBeanClazzArray 上下文 Class 数组 + * @param contextBeanArray 上下文对象数组 + * @return LiteflowResponse + */ + private LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) { + // 调用表达式构造 chain,并且返回 UUID 作为 chainId + String chainId = IdUtil.fastSimpleUUID(); + LiteFlowChainELBuilder.createChain() + .setChainId(chainId) + .setEL(elStr) + .build(); + return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, contextBeanArray); + } - public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) { - return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, null); - } + public List executeRouteChain(Object param, Class... contextBeanClazzArray) { + return this.executeWithRoute(null, param, null, contextBeanClazzArray, null); + } - public List executeRouteChainWithRid(Object param, String requestId, Class... contextBeanClazzArray) { - return this.executeWithRoute(null, param, requestId, contextBeanClazzArray, null); - } + public List executeRouteChain(String namespace, Object param, Class... contextBeanClazzArray) { + return this.executeWithRoute(namespace, param, null, contextBeanClazzArray, null); + } - public List executeRouteChainWithRid(String namespace, Object param, String requestId, Class... contextBeanClazzArray) { - return this.executeWithRoute(namespace, param, requestId, contextBeanClazzArray, null); - } + public LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray) { + return this.execute2Resp(chainId, param, null, null, contextBeanArray); + } - public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) { - return this.execute2Resp(chainId, param, requestId, null, contextBeanArray); - } + public List executeRouteChain(Object param, Object... contextBeanArray) { + return this.executeWithRoute(null, param, null, null, contextBeanArray); + } - public List executeRouteChainWithRid(Object param, String requestId, Object... contextBeanArray) { - return this.executeWithRoute(null, param, requestId, null, contextBeanArray); - } + public List executeRouteChain(String namespace, Object param, Object... contextBeanArray) { + return this.executeWithRoute(namespace, param, null, null, contextBeanArray); + } - public List executeRouteChainWithRid(String namespace, Object param, String requestId, Object... contextBeanArray) { - return this.executeWithRoute(namespace, param, requestId, null, contextBeanArray); - } + public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) { + return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, null); + } - // 调用一个流程并返回Future,允许多上下文的传入 - public Future execute2Future(String chainId, Object param, Class... contextBeanClazzArray) { - return ExecutorHelper.loadInstance() - .buildMainExecutor(liteflowConfig.getMainExecutorClass()) - .submit(() -> FlowExecutorHolder.loadInstance().execute2Resp(chainId, param, contextBeanClazzArray)); - } + public List executeRouteChainWithRid(Object param, String requestId, Class... contextBeanClazzArray) { + return this.executeWithRoute(null, param, requestId, contextBeanClazzArray, null); + } - public Future execute2Future(String chainId, Object param, Object... contextBeanArray) { - return ExecutorHelper.loadInstance() - .buildMainExecutor(liteflowConfig.getMainExecutorClass()) - .submit(() -> FlowExecutorHolder.loadInstance().execute2Resp(chainId, param, contextBeanArray)); - } + public List executeRouteChainWithRid(String namespace, Object param, String requestId, Class... contextBeanClazzArray) { + return this.executeWithRoute(namespace, param, requestId, contextBeanClazzArray, null); + } - public Future execute2FutureWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) { - return ExecutorHelper.loadInstance() - .buildMainExecutor(liteflowConfig.getMainExecutorClass()) - .submit(() -> FlowExecutorHolder.loadInstance().execute2RespWithRid(chainId, param, requestId, contextBeanClazzArray)); - } + public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) { + return this.execute2Resp(chainId, param, requestId, null, contextBeanArray); + } - public Future execute2FutureWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) { - return ExecutorHelper.loadInstance() - .buildMainExecutor(liteflowConfig.getMainExecutorClass()) - .submit(() -> FlowExecutorHolder.loadInstance().execute2RespWithRid(chainId, param, requestId, contextBeanArray)); - } + public List executeRouteChainWithRid(Object param, String requestId, Object... contextBeanArray) { + return this.executeWithRoute(null, param, requestId, null, contextBeanArray); + } - // 调用一个流程,返回默认的上下文,适用于简单的调用 - @Deprecated - public DefaultContext execute(String chainId, Object param) throws Exception { - LiteflowResponse response = this.execute2Resp(chainId, param, DefaultContext.class); - if (!response.isSuccess()) { - throw response.getCause(); - } - else { - return response.getFirstContextBean(); - } - } + public List executeRouteChainWithRid(String namespace, Object param, String requestId, Object... contextBeanArray) { + return this.executeWithRoute(namespace, param, requestId, null, contextBeanArray); + } - private LiteflowResponse execute2Resp(String chainId, Object param, String requestId, Class[] contextBeanClazzArray, - Object[] contextBeanArray) { - Slot slot = doExecute(chainId, param, requestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.BODY); - return LiteflowResponse.newMainResponse(slot); - } + // 调用一个流程并返回Future,允许多上下文的传入 + public Future execute2Future(String chainId, Object param, Class... contextBeanClazzArray) { + return ExecutorHelper.loadInstance() + .buildMainExecutor(liteflowConfig.getMainExecutorClass()) + .submit(() -> FlowExecutorHolder.loadInstance().execute2Resp(chainId, param, contextBeanClazzArray)); + } - private List executeWithRoute(String namespace, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray){ - List slotList = doExecuteWithRoute(namespace, param, requestId, contextBeanClazzArray, contextBeanArray); - return slotList.stream().map(LiteflowResponse::newMainResponse).collect(Collectors.toList()); - } + public Future execute2Future(String chainId, Object param, Object... contextBeanArray) { + return ExecutorHelper.loadInstance() + .buildMainExecutor(liteflowConfig.getMainExecutorClass()) + .submit(() -> FlowExecutorHolder.loadInstance().execute2Resp(chainId, param, contextBeanArray)); + } - private Slot doExecute(String chainId, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray, - ChainExecuteModeEnum chainExecuteModeEnum) { - if (FlowBus.needInit()) { - init(true); - } + public Future execute2FutureWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) { + return ExecutorHelper.loadInstance() + .buildMainExecutor(liteflowConfig.getMainExecutorClass()) + .submit(() -> FlowExecutorHolder.loadInstance().execute2RespWithRid(chainId, param, requestId, contextBeanClazzArray)); + } - Integer slotIndex; - // 这里可以根据class分配,也可以根据bean去分配 - if (ArrayUtil.isNotEmpty(contextBeanClazzArray)) { - slotIndex = DataBus.offerSlotByClass(ListUtil.toList(contextBeanClazzArray)); - } - else { - slotIndex = DataBus.offerSlotByBean(ListUtil.toList(contextBeanArray)); - } + public Future execute2FutureWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) { + return ExecutorHelper.loadInstance() + .buildMainExecutor(liteflowConfig.getMainExecutorClass()) + .submit(() -> FlowExecutorHolder.loadInstance().execute2RespWithRid(chainId, param, requestId, contextBeanArray)); + } - if (slotIndex == -1) { - throw new NoAvailableSlotException("there is no available slot"); - } + // 调用一个流程,返回默认的上下文,适用于简单的调用 + @Deprecated + public DefaultContext execute(String chainId, Object param) throws Exception { + LiteflowResponse response = this.execute2Resp(chainId, param, DefaultContext.class); + if (!response.isSuccess()) { + throw response.getCause(); + } else { + return response.getFirstContextBean(); + } + } - Slot slot = DataBus.getSlot(slotIndex); - if (ObjectUtil.isNull(slot)) { - throw new NoAvailableSlotException(StrUtil.format("the slot[{}] is not exist", slotIndex)); - } + private LiteflowResponse execute2Resp(String chainId, Object param, String requestId, Class[] contextBeanClazzArray, + Object[] contextBeanArray) { + Slot slot = doExecute(chainId, param, requestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.BODY); + return LiteflowResponse.newMainResponse(slot); + } - // 如果有FlowExecute生命周期实现,则执行 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList())){ - LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList().forEach( - postProcessFlowExecuteLifeCycle -> postProcessFlowExecuteLifeCycle.postProcessBeforeFlowExecute(chainId, slot) - ); - } + private List executeWithRoute(String namespace, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) { + List slotList = doExecuteWithRoute(namespace, param, requestId, contextBeanClazzArray, contextBeanArray); + return slotList.stream().map(LiteflowResponse::newMainResponse).collect(Collectors.toList()); + } - //如果传入了用户的RequestId,则用这个请求Id,如果没传入,则进行生成 - if (StrUtil.isNotBlank(requestId)){ - slot.putRequestId(requestId); - LFLoggerManager.setRequestId(requestId); - }else if(StrUtil.isBlank(slot.getRequestId())){ - slot.generateRequestId(); - LFLoggerManager.setRequestId(slot.getRequestId()); - LOG.info("requestId has generated"); - } + private Slot doExecute(String chainId, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray, + ChainExecuteModeEnum chainExecuteModeEnum) { + if (FlowBus.needInit()) { + init(true); + } - LOG.info("slot[{}] offered", slotIndex); + Integer slotIndex; + // 这里可以根据class分配,也可以根据bean去分配 + if (ArrayUtil.isNotEmpty(contextBeanClazzArray)) { + slotIndex = DataBus.offerSlotByClass(ListUtil.toList(contextBeanClazzArray)); + } else { + slotIndex = DataBus.offerSlotByBean(ListUtil.toList(contextBeanArray)); + } - if (ObjectUtil.isNotNull(param)) { - slot.setRequestData(param); - } + if (slotIndex == -1) { + throw new NoAvailableSlotException("there is no available slot"); + } - Chain chain = null; - try { - chain = FlowBus.getChain(chainId); + Slot slot = DataBus.getSlot(slotIndex); + if (ObjectUtil.isNull(slot)) { + throw new NoAvailableSlotException(StrUtil.format("the slot[{}] is not exist", slotIndex)); + } - if (ObjectUtil.isNull(chain)) { - String errorMsg = StrUtil.format("couldn't find chain with the id[{}]", chainId); - throw new ChainNotFoundException(errorMsg); - } - // 根据chain执行模式执行chain - if (chainExecuteModeEnum.equals(ChainExecuteModeEnum.BODY)){ - chain.execute(slotIndex); - }else if(chainExecuteModeEnum.equals(ChainExecuteModeEnum.ROUTE)){ - chain.executeRoute(slotIndex); - }else{ - throw new LiteFlowException("chain execute mode error"); - } - } - catch (ChainEndException e) { - if (ObjectUtil.isNotNull(chain)) { - String warnMsg = StrUtil.format("chain[{}] execute end on slot[{}]", chain.getChainId(), slotIndex); - LOG.warn(warnMsg); - } - } - catch (Exception e) { - if (ObjectUtil.isNotNull(chain)) { - String errMsg = StrUtil.format("chain[{}] execute error on slot[{}]", chain.getChainId(), slotIndex); - LOG.error(errMsg, e); - } - else { - LOG.error(e.getMessage(), e); - } + // 如果有FlowExecute生命周期实现,则执行 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList())) { + LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList().forEach( + postProcessFlowExecuteLifeCycle -> postProcessFlowExecuteLifeCycle.postProcessBeforeFlowExecute(chainId, slot) + ); + } - slot.setException(e); - Deque executeSteps = slot.getExecuteSteps(); - try { - Iterator cmpStepIterator = executeSteps.descendingIterator(); - while(cmpStepIterator.hasNext()) { - CmpStep cmpStep = cmpStepIterator.next(); - if(cmpStep.getInstance().isRollback()) { - Rollbackable rollbackItem = cmpStep.getRefNode(); - rollbackItem.rollback(slotIndex); - } - } - } catch (Exception exception) { - LOG.error(exception.getMessage()); - } - finally { - slot.printRollbackStep(); - } - } - finally { - slot.printStep(); - DataBus.releaseSlot(slotIndex); - LFLoggerManager.removeRequestId(); + //如果传入了用户的RequestId,则用这个请求Id,如果没传入,则进行生成 + if (StrUtil.isNotBlank(requestId)) { + slot.putRequestId(requestId); + LFLoggerManager.setRequestId(requestId); + } else if (StrUtil.isBlank(slot.getRequestId())) { + slot.generateRequestId(); + LFLoggerManager.setRequestId(slot.getRequestId()); + LOG.info("requestId has generated"); + } - // 如果有FlowExecute生命周期实现,则执行 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList())){ - LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList().forEach( - postProcessFlowExecuteLifeCycle -> postProcessFlowExecuteLifeCycle.postProcessAfterFlowExecute(chainId, slot) - ); - } - } - return slot; - } + LOG.info("slot[{}] offered", slotIndex); - public LiteflowConfig getLiteflowConfig() { - return liteflowConfig; - } + if (ObjectUtil.isNotNull(param)) { + slot.setRequestData(param); + } - public void setLiteflowConfig(LiteflowConfig liteflowConfig) { - this.liteflowConfig = liteflowConfig; - // 把liteFlowConfig设到LiteFlowGetter中去 - LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); - } + Chain chain = null; + try { + chain = FlowBus.getChain(chainId); - /** - * 添加监听文件路径 - * @param pathList 文件路径 - */ - private void addMonitorFilePaths(List pathList) throws Exception { - // 添加规则文件监听 - List fileAbsolutePath = PathContentParserHolder.loadContextAware().getFileAbsolutePath(pathList); - MonitorFile.getInstance().addMonitorFilePaths(fileAbsolutePath); - } + if (ObjectUtil.isNull(chain)) { + String errorMsg = StrUtil.format("couldn't find chain with the id[{}]", chainId); + throw new ChainNotFoundException(errorMsg); + } + // 根据chain执行模式执行chain + if (chainExecuteModeEnum.equals(ChainExecuteModeEnum.BODY)) { + chain.execute(slotIndex); + } else if (chainExecuteModeEnum.equals(ChainExecuteModeEnum.ROUTE)) { + chain.executeRoute(slotIndex); + } else { + throw new LiteFlowException("chain execute mode error"); + } + } catch (ChainEndException e) { + if (ObjectUtil.isNotNull(chain)) { + String warnMsg = StrUtil.format("chain[{}] execute end on slot[{}]", chain.getChainId(), slotIndex); + LOG.warn(warnMsg); + } + } catch (Exception e) { + if (ObjectUtil.isNotNull(chain)) { + String errMsg = StrUtil.format("chain[{}] execute error on slot[{}]", chain.getChainId(), slotIndex); + LOG.error(errMsg, e); + } else { + LOG.error(e.getMessage(), e); + } - private List doExecuteWithRoute(String namespace, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray){ - if (FlowBus.needInit()) { - init(true); - } + slot.setException(e); + Deque executeSteps = slot.getExecuteSteps(); + try { + Iterator cmpStepIterator = executeSteps.descendingIterator(); + while (cmpStepIterator.hasNext()) { + CmpStep cmpStep = cmpStepIterator.next(); + if (cmpStep.getInstance().isRollback()) { + Rollbackable rollbackItem = cmpStep.getRefNode(); + rollbackItem.rollback(slotIndex); + } + } + } catch (Exception exception) { + LOG.error(exception.getMessage()); + } finally { + slot.printRollbackStep(); + } + } finally { + slot.printStep(); + DataBus.releaseSlot(slotIndex); + LFLoggerManager.removeRequestId(); - if (StrUtil.isBlank(namespace)){ - namespace = ChainConstant.DEFAULT_NAMESPACE; - } + // 如果有FlowExecute生命周期实现,则执行 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList())) { + LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList().forEach( + postProcessFlowExecuteLifeCycle -> postProcessFlowExecuteLifeCycle.postProcessAfterFlowExecute(chainId, slot) + ); + } + } + return slot; + } - String finalNamespace = namespace; - List routeChainList = FlowBus.getChainMap().values().stream() - .filter(chain -> chain.getNamespace().equals(finalNamespace)) - .filter(chain -> chain.getRouteItem() != null).collect(Collectors.toList()); + public LiteflowConfig getLiteflowConfig() { + return liteflowConfig; + } - if (CollUtil.isEmpty(routeChainList)){ - String errorMsg = StrUtil.format("no route found for namespace[{}]", finalNamespace); - throw new RouteChainNotFoundException(errorMsg); - } + public void setLiteflowConfig(LiteflowConfig liteflowConfig) { + this.liteflowConfig = liteflowConfig; + // 把liteFlowConfig设到LiteFlowGetter中去 + LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); + } - String finalRequestId; - if (StrUtil.isBlank(requestId)){ - finalRequestId = IdGeneratorHolder.getInstance().generate(); - }else{ - finalRequestId = requestId; - } + /** + * 添加监听文件路径 + * + * @param pathList 文件路径 + */ + private void addMonitorFilePaths(List pathList) throws Exception { + // 添加规则文件监听 + List fileAbsolutePath = PathContentParserHolder.loadContextAware().getFileAbsolutePath(pathList); + MonitorFile.getInstance().addMonitorFilePaths(fileAbsolutePath); + } - // 异步执行route el - List routeTupleList = new ArrayList<>(); - for (Chain routeChain : routeChainList){ - CompletableFuture f = CompletableFuture.supplyAsync( + private List doExecuteWithRoute(String namespace, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) { + if (FlowBus.needInit()) { + init(true); + } + + if (StrUtil.isBlank(namespace)) { + namespace = ChainConstant.DEFAULT_NAMESPACE; + } + + String finalNamespace = namespace; + List routeChainList = FlowBus.getChainMap().values().stream() + .filter(chain -> chain.getNamespace().equals(finalNamespace)) + .filter(chain -> chain.getRouteItem() != null).collect(Collectors.toList()); + + if (CollUtil.isEmpty(routeChainList)) { + String errorMsg = StrUtil.format("no route found for namespace[{}]", finalNamespace); + throw new RouteChainNotFoundException(errorMsg); + } + + String finalRequestId; + if (StrUtil.isBlank(requestId)) { + finalRequestId = IdGeneratorHolder.getInstance().generate(); + } else { + finalRequestId = requestId; + } + + // 异步执行route el + List routeTupleList = new ArrayList<>(); + for (Chain routeChain : routeChainList) { + CompletableFuture f = CompletableFuture.supplyAsync( () -> doExecute(routeChain.getChainId(), param, finalRequestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.ROUTE), - ExecutorHelper.loadInstance().buildWhenExecutor() - ); + ExecutorHelper.loadInstance().buildWhenExecutor() + ); - routeTupleList.add(new Tuple(routeChain, f)); - } + routeTupleList.add(new Tuple(routeChain, f)); + } - CompletableFuture resultRouteCf = CompletableFuture.allOf(routeTupleList.stream().map( - (Function>) tuple -> tuple.get(1) - ).collect(Collectors.toList()).toArray(new CompletableFuture[] {})); - try{ - resultRouteCf.get(); - }catch (Exception e){ - throw new LiteFlowException("There is An error occurred while executing the route.", e); - } + CompletableFuture resultRouteCf = CompletableFuture.allOf(routeTupleList.stream().map( + (Function>) tuple -> tuple.get(1) + ).collect(Collectors.toList()).toArray(new CompletableFuture[]{})); + try { + resultRouteCf.get(); + } catch (Exception e) { + throw new LiteFlowException("There is An error occurred while executing the route.", e); + } - // 把route执行为true都过滤出来 - List matchedRouteChainList = routeTupleList.stream().filter(tuple -> { - try{ - CompletableFuture f = tuple.get(1); - Slot slot = f.get(); - return BooleanUtil.isTrue(slot.getRouteResult()); - }catch (Exception e){ - return false; - } - }).map( - (Function) tuple -> tuple.get(0) - ).collect(Collectors.toList()); + // 把route执行为true都过滤出来 + List matchedRouteChainList = routeTupleList.stream().filter(tuple -> { + try { + CompletableFuture f = tuple.get(1); + Slot slot = f.get(); + return BooleanUtil.isTrue(slot.getRouteResult()); + } catch (Exception e) { + return false; + } + }).map( + (Function) tuple -> tuple.get(0) + ).collect(Collectors.toList()); - if (CollUtil.isEmpty(matchedRouteChainList)){ - throw new NoMatchedRouteChainException("there is no matched route chain"); - } + if (CollUtil.isEmpty(matchedRouteChainList)) { + throw new NoMatchedRouteChainException("there is no matched route chain"); + } - // 异步分别执行这些chain - List> executeChainCfList = new ArrayList<>(); - for (Chain chain : matchedRouteChainList){ - CompletableFuture cf = CompletableFuture.supplyAsync( - () -> doExecute(chain.getChainId(), param, finalRequestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.BODY), - ExecutorHelper.loadInstance().buildWhenExecutor() - ); - executeChainCfList.add(cf); - } + // 异步分别执行这些chain + List> executeChainCfList = new ArrayList<>(); + for (Chain chain : matchedRouteChainList) { + CompletableFuture cf = CompletableFuture.supplyAsync( + () -> doExecute(chain.getChainId(), param, finalRequestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.BODY), + ExecutorHelper.loadInstance().buildWhenExecutor() + ); + executeChainCfList.add(cf); + } - CompletableFuture resultChainCf = CompletableFuture.allOf(executeChainCfList.toArray(new CompletableFuture[] {})); - try{ - resultChainCf.get(); - }catch (Exception e){ - throw new LiteFlowException("There is An error occurred while executing the matched chain.", e); - } + CompletableFuture resultChainCf = CompletableFuture.allOf(executeChainCfList.toArray(new CompletableFuture[]{})); + try { + resultChainCf.get(); + } catch (Exception e) { + throw new LiteFlowException("There is An error occurred while executing the matched chain.", e); + } - List resultSlotList = executeChainCfList.stream().map(slotCompletableFuture -> { - try{ - return slotCompletableFuture.get(); - }catch (Exception e){ - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); + List resultSlotList = executeChainCfList.stream().map(slotCompletableFuture -> { + try { + return slotCompletableFuture.get(); + } catch (Exception e) { + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); - LOG.info("chain namespace:[{}], total size:[{}], matched size:[{}]", namespace, routeChainList.size(), resultSlotList.size()); + LOG.info("chain namespace:[{}], total size:[{}], matched size:[{}]", namespace, routeChainList.size(), resultSlotList.size()); - return resultSlotList; - } + return resultSlotList; + } } \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java index 09490711a..be0accbeb 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java @@ -61,4 +61,11 @@ public class BaseELSpringbootTest extends BaseTest { Assertions.assertTrue(response.isSuccess()); } + // 入参执行 EL 表达式 + @Test + public void testBase6() throws Exception { + LiteflowResponse response = flowExecutor.execute2RespWithEL("THEN(a,b,c)"); + Assertions.assertTrue(response.isSuccess()); + } + } From 735f4b170dd813e02f8da5501f9f7b20be9bae91 Mon Sep 17 00:00:00 2001 From: jay li Date: Thu, 19 Jun 2025 20:18:38 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=20#ICGGAW=20redis=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90=E6=94=AF=E6=8C=81=E9=9B=86=E7=BE=A4=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parser/redis/RedisXmlELParser.java | 9 +- .../liteflow/parser/redis/mode/RedisMode.java | 7 +- .../parser/redis/mode/RedisParserHelper.java | 40 ++++ .../mode/polling/RedisParserPollingMode.java | 43 ++-- .../subscribe/RedisParserSubscribeMode.java | 43 ++-- .../parser/redis/vo/RedisParserVO.java | 23 ++ .../redis/RedisClusterPollSpringBootTest.java | 199 +++++++++++++++++ .../RedisClusterSubscribeSpringBootTest.java | 202 ++++++++++++++++++ .../application-poll-cluster-xml.properties | 9 + .../application-sub-cluster-xml.properties | 10 + 10 files changed, 542 insertions(+), 43 deletions(-) create mode 100644 liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterPollSpringBootTest.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterSubscribeSpringBootTest.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/resources/redis/application-poll-cluster-xml.properties create mode 100644 liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/resources/redis/application-sub-cluster-xml.properties diff --git a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/RedisXmlELParser.java b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/RedisXmlELParser.java index 48a3a9c88..ce652aa03 100644 --- a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/RedisXmlELParser.java +++ b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/RedisXmlELParser.java @@ -24,6 +24,7 @@ import java.util.Objects; * Redis解析器实现,只支持EL形式的XML,不支持其他的形式 * * @author hxinyu + * @author jay li * @since 2.11.0 */ @@ -109,11 +110,17 @@ public class RedisXmlELParser extends ClassXmlFlowELParser { if (redisParserVO.getRedisMode().equals(RedisMode.SENTINEL) && CollectionUtil.isEmpty(redisParserVO.getSentinelAddress())) { throw new RedisException(StrFormatter.format(ERROR_MSG_PATTERN, "sentinel address list")); } - if (ObjectUtil.isNull(redisParserVO.getChainDataBase())) { + if (ObjectUtil.isNull(redisParserVO.getChainDataBase()) && !redisParserVO.getRedisMode().equals(RedisMode.CLUSTER)) { throw new RedisException(StrFormatter.format(ERROR_MSG_PATTERN, "chainDataBase")); } if (StrUtil.isBlank(redisParserVO.getChainKey())) { throw new RedisException(StrFormatter.format(ERROR_MSG_PATTERN, "chainKey")); } + if (redisParserVO.getRedisMode().equals(RedisMode.CLUSTER) && CollectionUtil.isEmpty(redisParserVO.getClusterNodeAddress())) { + throw new RedisException(StrFormatter.format(ERROR_MSG_PATTERN, "cluster address list")); + } + if (ObjectUtil.isNull(redisParserVO.getScriptKey()) && ObjectUtil.isNull(redisParserVO.getScriptDataBase()) && !redisParserVO.getRedisMode().equals(RedisMode.CLUSTER)) { + throw new RedisException(StrFormatter.format(ERROR_MSG_PATTERN, "scriptDataBase")); + } } } diff --git a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/RedisMode.java b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/RedisMode.java index ce5610428..d333da87d 100644 --- a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/RedisMode.java +++ b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/RedisMode.java @@ -4,16 +4,19 @@ package com.yomahub.liteflow.parser.redis.mode; * 用于定义Redis模式的枚举类 * * single单点模式, sentinel哨兵模式 - * 不支持集群模式配置 + * cluster 集群模式配置 * * @author hxinyu + * @author jay li * @since 2.11.0 */ public enum RedisMode { SINGLE("single"), - SENTINEL("sentinel"); + SENTINEL("sentinel"), + + CLUSTER("cluster"); private String mode; diff --git a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/RedisParserHelper.java b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/RedisParserHelper.java index e6d80d51f..e588ea0d9 100644 --- a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/RedisParserHelper.java +++ b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/RedisParserHelper.java @@ -3,6 +3,7 @@ package com.yomahub.liteflow.parser.redis.mode; import cn.hutool.core.lang.Pair; import cn.hutool.core.text.StrFormatter; import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.yomahub.liteflow.builder.LiteFlowNodeBuilder; import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder; @@ -13,6 +14,7 @@ import com.yomahub.liteflow.log.LFLoggerManager; import com.yomahub.liteflow.parser.helper.NodeConvertHelper; import com.yomahub.liteflow.parser.redis.vo.RedisParserVO; import com.yomahub.liteflow.util.RuleParsePluginUtil; +import org.redisson.config.ClusterServersConfig; import org.redisson.config.Config; import org.redisson.config.SentinelServersConfig; import org.redisson.config.SingleServerConfig; @@ -22,6 +24,7 @@ import org.redisson.config.SingleServerConfig; * * @author hxinyu * @author Bryan.Zhang + * @author jay li * @since 2.11.0 */ @@ -33,6 +36,8 @@ public interface RedisParserHelper { String SENTINEL_REDIS_URL_PATTERN = "redis://{}"; + String CLUSTER_REDIS_URL_PATTERN = "redis://{}"; + String CHAIN_XML_PATTERN = "{}"; String NODE_XML_PATTERN = "{}"; @@ -55,6 +60,9 @@ public interface RedisParserHelper { * @return redisson config */ default Config getSingleRedissonConfig(RedisParserVO redisParserVO, Integer dataBase) { + if (ObjectUtil.isNull(dataBase)) { + return null; + } Config config = new Config(); String redisAddress = StrFormatter.format(SINGLE_REDIS_URL_PATTERN, redisParserVO.getHost(), redisParserVO.getPort()); @@ -81,6 +89,9 @@ public interface RedisParserHelper { * @return redisson Config */ default Config getSentinelRedissonConfig(RedisParserVO redisParserVO, Integer dataBase) { + if (ObjectUtil.isNull(dataBase)) { + return null; + } Config config = new Config(); SentinelServersConfig sentinelConfig = config.useSentinelServers() .setMasterName(redisParserVO.getMasterName()) @@ -109,6 +120,35 @@ public interface RedisParserHelper { return config; } + /** + * 获取Redisson客户端的Config配置通用方法(集群模式) + * @param redisParserVO redisParserVO + * @return redisson Config + */ + default Config getCluserRedissonConfig(RedisParserVO redisParserVO) { + Config config = new Config(); + ClusterServersConfig clusterConfig = config.useClusterServers() + .setMasterConnectionPoolSize(redisParserVO.getConnectionPoolSize()) + .setSlaveConnectionPoolSize(redisParserVO.getConnectionPoolSize()) + .setMasterConnectionMinimumIdleSize(redisParserVO.getConnectionMinimumIdleSize()) + .setSlaveConnectionMinimumIdleSize(redisParserVO.getConnectionMinimumIdleSize()); + + redisParserVO.getClusterNodeAddress().forEach(address -> { + clusterConfig.addNodeAddress(StrFormatter.format(CLUSTER_REDIS_URL_PATTERN, address)); + }); + //如果配置了用户名和密码 + if(StrUtil.isNotBlank(redisParserVO.getUsername()) && StrUtil.isNotBlank(redisParserVO.getPassword())) { + clusterConfig.setUsername(redisParserVO.getUsername()) + .setPassword(redisParserVO.getPassword()); + } + //如果配置了密码 + else if(StrUtil.isNotBlank(redisParserVO.getPassword())) { + clusterConfig.setPassword(redisParserVO.getPassword()); + } + + return config; + } + /** * script节点的修改/添加 * diff --git a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/polling/RedisParserPollingMode.java b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/polling/RedisParserPollingMode.java index cafaa67fb..3338f772f 100644 --- a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/polling/RedisParserPollingMode.java +++ b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/polling/RedisParserPollingMode.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; * Redis 轮询机制实现类 * * @author hxinyu + * @author jay li * @since 2.11.0 */ @@ -81,27 +82,29 @@ public class RedisParserPollingMode implements RedisParserHelper { } if (ObjectUtil.isNull(chainClient)) { RedisMode redisMode = redisParserVO.getRedisMode(); - Config config; - //Redis单点模式 - if (redisMode.equals(RedisMode.SINGLE)){ - config = getSingleRedissonConfig(redisParserVO, redisParserVO.getChainDataBase()); - this.chainClient = new RClient(Redisson.create(config)); - //如果有脚本数据 - if (ObjectUtil.isNotNull(redisParserVO.getScriptDataBase())) { - config = getSingleRedissonConfig(redisParserVO, redisParserVO.getScriptDataBase()); - this.scriptClient = new RClient(Redisson.create(config)); - } + Config chinaConfig, scriptConfig; + + switch (redisMode) { + case SINGLE: + chinaConfig = getSingleRedissonConfig(redisParserVO, redisParserVO.getChainDataBase()); + scriptConfig = getSingleRedissonConfig(redisParserVO, redisParserVO.getScriptDataBase()); + break; + case SENTINEL: + chinaConfig = getSentinelRedissonConfig(redisParserVO, redisParserVO.getChainDataBase()); + scriptConfig = getSentinelRedissonConfig(redisParserVO, redisParserVO.getScriptDataBase()); + break; + case CLUSTER: + chinaConfig = getCluserRedissonConfig(redisParserVO); + scriptConfig = chinaConfig; + break; + default: + throw new RedisException("RedisMode is not supported"); } - //Redis哨兵模式 - else if (redisMode.equals(RedisMode.SENTINEL)) { - config = getSentinelRedissonConfig(redisParserVO, redisParserVO.getChainDataBase()); - this.chainClient = new RClient(Redisson.create(config)); - //如果有脚本数据 - if (ObjectUtil.isNotNull(redisParserVO.getScriptDataBase())) { - config = getSentinelRedissonConfig(redisParserVO, redisParserVO.getScriptDataBase()); - this.scriptClient = new RClient(Redisson.create(config)); - } + this.chainClient = new RClient(Redisson.create(chinaConfig)); + //如果有脚本数据 + if (ObjectUtil.isNotNull(redisParserVO.getScriptKey())) { + this.scriptClient = new RClient(Redisson.create(scriptConfig)); } } //创建定时任务线程池 @@ -211,7 +214,7 @@ public class RedisParserPollingMode implements RedisParserHelper { redisParserVO.getPollingInterval().longValue(), TimeUnit.SECONDS); //如果有脚本 - if (ObjectUtil.isNotNull(scriptClient) && ObjectUtil.isNotNull(redisParserVO.getScriptDataBase()) + if (ObjectUtil.isNotNull(scriptClient) && (ObjectUtil.isNotNull(redisParserVO.getScriptDataBase()) || RedisMode.CLUSTER.getMode().equals(redisParserVO.getMode().getMode())) && StrUtil.isNotBlank(redisParserVO.getScriptKey())) { //将lua脚本添加到scriptJedis脚本缓存 String keyLuaOfScript = scriptClient.scriptLoad(luaOfKey); diff --git a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/subscribe/RedisParserSubscribeMode.java b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/subscribe/RedisParserSubscribeMode.java index cb4e50c5d..4be097219 100644 --- a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/subscribe/RedisParserSubscribeMode.java +++ b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/subscribe/RedisParserSubscribeMode.java @@ -28,6 +28,7 @@ import java.util.Map; * 使用 Redisson客户端 RMapCache存储结构 * * @author hxinyu + * @author jay li * @since 2.11.0 */ @@ -50,27 +51,29 @@ public class RedisParserSubscribeMode implements RedisParserHelper { } if (ObjectUtil.isNull(chainClient)) { RedisMode redisMode = redisParserVO.getRedisMode(); - Config config; - //Redis单点模式 - if (redisMode.equals(RedisMode.SINGLE)) { - config = getSingleRedissonConfig(redisParserVO, redisParserVO.getChainDataBase()); - this.chainClient = new RClient(Redisson.create(config)); - //如果有脚本数据 - if (ObjectUtil.isNotNull(redisParserVO.getScriptDataBase())) { - config = getSingleRedissonConfig(redisParserVO, redisParserVO.getScriptDataBase()); - this.scriptClient = new RClient(Redisson.create(config)); - } + Config chinaConfig, scriptConfig; + + switch (redisMode) { + case SINGLE: + chinaConfig = getSingleRedissonConfig(redisParserVO, redisParserVO.getChainDataBase()); + scriptConfig = getSingleRedissonConfig(redisParserVO, redisParserVO.getScriptDataBase()); + break; + case SENTINEL: + chinaConfig = getSentinelRedissonConfig(redisParserVO, redisParserVO.getChainDataBase()); + scriptConfig = getSentinelRedissonConfig(redisParserVO, redisParserVO.getScriptDataBase()); + break; + case CLUSTER: + chinaConfig = getCluserRedissonConfig(redisParserVO); + scriptConfig = chinaConfig; + break; + default: + throw new RedisException("RedisMode is not supported"); } - //Redis哨兵模式 - else if (redisMode.equals(RedisMode.SENTINEL)) { - config = getSentinelRedissonConfig(redisParserVO, redisParserVO.getChainDataBase()); - this.chainClient = new RClient(Redisson.create(config)); - //如果有脚本数据 - if (ObjectUtil.isNotNull(redisParserVO.getScriptDataBase())) { - config = getSentinelRedissonConfig(redisParserVO, redisParserVO.getScriptDataBase()); - this.scriptClient = new RClient(Redisson.create(config)); - } + this.chainClient = new RClient(Redisson.create(chinaConfig)); + //如果有脚本数据 + if (ObjectUtil.isNotNull(redisParserVO.getScriptKey())) { + this.scriptClient = new RClient(Redisson.create(scriptConfig)); } } } catch (Exception e) { @@ -172,7 +175,7 @@ public class RedisParserSubscribeMode implements RedisParserHelper { }); //监听 script - if (ObjectUtil.isNotNull(scriptClient) && ObjectUtil.isNotNull(redisParserVO.getScriptDataBase())) { + if (ObjectUtil.isNotNull(scriptClient) && (ObjectUtil.isNotNull(redisParserVO.getScriptDataBase()) || RedisMode.CLUSTER.equals(redisParserVO.getRedisMode()))) { String scriptKey = redisParserVO.getScriptKey(); //添加 script diff --git a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/vo/RedisParserVO.java b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/vo/RedisParserVO.java index a3cc9293f..c4c9f3862 100644 --- a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/vo/RedisParserVO.java +++ b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/vo/RedisParserVO.java @@ -12,6 +12,7 @@ import java.util.List; * 用于解析RuleSourceExtData的vo类, 用于Redis模式中 * * @author hxinyu + * @author jay li * @since 2.11.0 */ @@ -63,6 +64,17 @@ public class RedisParserVO { /*脚本配置的键名 若没有脚本数据可不配置*/ private String scriptKey; + /*集群模式需配置 逗号分隔 集群地址 */ + private List clusterNodeAddress; + + public List getClusterNodeAddress() { + return clusterNodeAddress; + } + + public void setClusterNodeAddress(List clusterNodeAddress) { + this.clusterNodeAddress = clusterNodeAddress; + } + public void setRedisMode(String redisMode) { redisMode = redisMode.toUpperCase(); try{ @@ -120,6 +132,16 @@ public class RedisParserVO { } } + @JsonSetter("clusterAddress") + public void setClusterAddressFromString(String addresses) { + if (addresses != null && !addresses.trim().isEmpty()) { + // 按逗号分割,并去除每个地址前后的空格 + this.clusterNodeAddress = Arrays.asList(addresses.split("\\s*,\\s*")); + } else { + this.clusterNodeAddress = Collections.emptyList(); + } + } + public String getUsername() { return username; } @@ -232,6 +254,7 @@ public class RedisParserVO { ", chainKey='" + chainKey + '\'' + ", scriptDataBase=" + scriptDataBase + ", scriptKey='" + scriptKey + '\'' + + ", clusterAddress='" + clusterNodeAddress + '\'' + '}'; } } diff --git a/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterPollSpringBootTest.java b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterPollSpringBootTest.java new file mode 100644 index 000000000..b6149f86f --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterPollSpringBootTest.java @@ -0,0 +1,199 @@ +package com.yomahub.liteflow.test.redis; + +import cn.hutool.crypto.digest.DigestUtil; +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.exception.ChainNotFoundException; +import com.yomahub.liteflow.flow.FlowBus; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.log.LFLog; +import com.yomahub.liteflow.log.LFLoggerManager; +import com.yomahub.liteflow.parser.redis.mode.RClient; +import com.yomahub.liteflow.parser.redis.mode.polling.RedisParserPollingMode; + +import com.yomahub.liteflow.slot.DefaultContext; +import com.yomahub.liteflow.test.BaseTest; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import javax.annotation.Resource; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/** + * springboot环境下的redis 集群配置源poll模式功能测试 + *

+ * 测试用例会在1号database中添加测试数据 chainKey:testChainKey; scriptKey:testScriptKey + * 测试完成后清除测试数据 + * + * @author jay li + * @since 2.13.3 + */ +@ExtendWith(SpringExtension.class) +@TestPropertySource(value = "classpath:/redis/application-poll-cluster-xml.properties") +@SpringBootTest(classes = RedisClusterPollSpringBootTest.class) +@EnableAutoConfiguration +@ComponentScan({"com.yomahub.liteflow.test.redis.cmp"}) +public class RedisClusterPollSpringBootTest extends BaseTest { + + @MockBean(name = "chainClient") + private static RClient chainClient; + + @MockBean(name = "scriptClient") + private static RClient scriptClient; + + @Resource + private FlowExecutor flowExecutor; + + //计算hash中field数量的lua脚本 + private final String luaOfKey = "local keys = redis.call(\"hkeys\", KEYS[1]);\n" + + "return #keys;\n"; + + //计算hash中value的SHA值的lua脚本 + private final String luaOfValue = "local key = KEYS[1];\n" + + "local field = KEYS[2];\n" + + "local value, err = redis.call(\"hget\", key, field);\n" + + "if value == false or value == nil then\n" + + " return \"nil\";\n" + + "end\n" + + "local sha1 = redis.sha1hex(value);\n" + + "return sha1;"; + + static LFLog LOG = LFLoggerManager.getLogger(RedisClusterPollSpringBootTest.class); + + + @AfterAll + public static void after() { + //关闭poll模式的轮询线程池 + try{ + Field pollExecutor = RedisParserPollingMode.class.getDeclaredField("pollExecutor"); + pollExecutor.setAccessible(true); + ScheduledThreadPoolExecutor threadPoolExecutor = (ScheduledThreadPoolExecutor) pollExecutor.get(null); + threadPoolExecutor.shutdownNow(); + } catch (Exception ignored) { + LOG.error("[Polling thread pool not closed]", ignored); + } + } + + /** + * 统一测试chain和script + * + * 测试数据流程: + * 1、执行chain1值:"THEN(a, b, c);" + * 2、修改chain1值为:"THEN(s11, s22, s33, a, b);", 执行新chain 验证chain的轮询拉取功能 + * 3、修改chain1其中的script11值 执行chain 验证script的轮询拉取功能 + */ + @Test + public void testPollWithXml() throws InterruptedException { + Set chainNameSet = new HashSet<>(); + chainNameSet.add("chain11"); + String chainValue = "THEN(a, b, c);"; + String chainSHA = DigestUtil.sha1Hex(chainValue); + + //修改chain并更新SHA值 + String changeChainValue = "THEN(s11, s22, s33, a, b);"; + String changeChainSHA = DigestUtil.sha1Hex(changeChainValue); + + when(chainClient.hkeys("pollChainKey")).thenReturn(chainNameSet); + when(chainClient.hget("pollChainKey", "chain11")).thenReturn(chainValue).thenReturn(changeChainValue); + when(chainClient.scriptLoad(luaOfKey)).thenReturn("keysha"); + when(chainClient.scriptLoad(luaOfValue)).thenReturn("valuesha"); + when(chainClient.evalSha(eq("keysha"), anyString())).thenReturn("1"); + when(chainClient.evalSha(eq("valuesha"), anyString(), anyString())).thenReturn(chainSHA).thenReturn(changeChainSHA); + + //添加script + Set scriptFieldSet = new HashSet<>(); + scriptFieldSet.add("s11:script:脚本s11:groovy"); + scriptFieldSet.add("s22:script:脚本s22:js"); + scriptFieldSet.add("s33:script:脚本s33"); + String s11 = "defaultContext.setData(\"test11\",\"hello s11\");"; + String s22 = "defaultContext.setData(\"test22\",\"hello s22\");"; + String s33 = "defaultContext.setData(\"test33\",\"hello s33\");"; + //SHA值用于测试修改script的轮询刷新功能 + String s11SHA = DigestUtil.sha1Hex(s11); + String s22SHA = DigestUtil.sha1Hex(s22); + String s33SHA = DigestUtil.sha1Hex(s33); + //修改script值并更新SHA值 + String changeS11 = "defaultContext.setData(\"test11\",\"hello world\");"; + String changeS11SHA = DigestUtil.sha1Hex(changeS11); + + when(scriptClient.hkeys("pollScriptKey")).thenReturn(scriptFieldSet); + //这里休眠一段时间是为了防止在未修改脚本的chain还没有执行前 轮询线程就拉取了新script值 + when(scriptClient.hget("pollScriptKey", "s11:script:脚本s11:groovy")).thenReturn(s11).thenAnswer(invocation -> { + Thread.sleep(2000); + return changeS11; + }).thenReturn(changeS11); + when(scriptClient.hget("pollScriptKey", "s22:script:脚本s22:js")).thenReturn(s22); + when(scriptClient.hget("pollScriptKey", "s33:script:脚本s33")).thenReturn(s33); + + //分别模拟三个script的evalsha指纹值计算的返回值, 其中s11脚本修改 指纹值变化 + when(scriptClient.scriptLoad(luaOfKey)).thenReturn("keysha"); + when(scriptClient.scriptLoad(luaOfValue)).thenReturn("valuesha"); + when(scriptClient.evalSha(eq("keysha"), anyString())).thenReturn("3"); + when(scriptClient.evalSha("valuesha", "pollScriptKey", "s11:script:脚本s11:groovy")).thenReturn(s11SHA).thenAnswer(invocation -> { + Thread.sleep(2000); + return changeS11SHA; + }).thenReturn(changeS11SHA); + when(scriptClient.evalSha("valuesha", "pollScriptKey", "s22:script:脚本s22:js")).thenReturn(s22SHA); + when(scriptClient.evalSha("valuesha", "pollScriptKey", "s33:script:脚本s33")).thenReturn(s33SHA); + + //测试修改前的chain + LiteflowResponse response = flowExecutor.execute2Resp("chain11", "arg"); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("a==>b==>c", response.getExecuteStepStr()); + + Thread.sleep(4000); + + //测试加了script的chain + response = flowExecutor.execute2Resp("chain11", "arg"); + DefaultContext context = response.getFirstContextBean(); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("hello s11", context.getData("test11")); + Assertions.assertEquals("hello s22", context.getData("test22")); + Assertions.assertEquals("s11[脚本s11]==>s22[脚本s22]==>s33[脚本s33]==>a==>b", response.getExecuteStepStrWithoutTime()); + + Thread.sleep(4000); + + //测试修改script后的chain + response = flowExecutor.execute2Resp("chain11", "arg"); + context = response.getFirstContextBean(); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("hello world", context.getData("test11")); + } + + @Test + public void testDisablePollWithXml() throws InterruptedException { + Set chainNameSet = new HashSet<>(); + chainNameSet.add("chain1122:false"); + String chainValue = "THEN(a, b, c);"; + + when(chainClient.hkeys("pollChainKey")).thenReturn(chainNameSet); + when(chainClient.hget("pollChainKey", "chain1122:true")).thenReturn(chainValue); + + Set scriptFieldSet = new HashSet<>(); + scriptFieldSet.add("s4:script:脚本s3:groovy:false"); + when(scriptClient.hkeys("pollScriptKey")).thenReturn(scriptFieldSet); + when(scriptClient.hget("pollScriptKey", "s4:script:脚本s3:groovy:true")).thenReturn("defaultContext.setData(\"test\",\"hello\");"); + + // 测试 chain 停用 + Assertions.assertThrows(ChainNotFoundException.class, () -> { + throw flowExecutor.execute2Resp("chain1122", "arg").getCause(); + }); + + // 测试 script 停用 + Assertions.assertTrue(!FlowBus.getNodeMap().containsKey("s4")); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterSubscribeSpringBootTest.java b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterSubscribeSpringBootTest.java new file mode 100644 index 000000000..f90f70985 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterSubscribeSpringBootTest.java @@ -0,0 +1,202 @@ +package com.yomahub.liteflow.test.redis; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.FlowBus; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.parser.helper.NodeConvertHelper; +import com.yomahub.liteflow.parser.redis.mode.RClient; +import com.yomahub.liteflow.parser.redis.mode.RedisParserHelper; + +import com.yomahub.liteflow.slot.DefaultContext; +import com.yomahub.liteflow.test.BaseTest; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.redisson.api.RMapCache; +import org.redisson.api.RedissonClient; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import javax.annotation.Resource; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * springboot环境下的redis 集群配置源订阅模式功能测试 + *

+ * 测试用例会在1号database中添加测试数据 chainKey:testChainKey; scriptKey:testScriptKey + * 测试完成后清除测试数据 + * + * @author jay li + * @since 2.13.3 + */ +@ExtendWith(SpringExtension.class) +@TestPropertySource(value = "classpath:/redis/application-sub-cluster-xml.properties") +@SpringBootTest(classes = RedisClusterSubscribeSpringBootTest.class) +@EnableAutoConfiguration +@ComponentScan({"com.yomahub.liteflow.test.redis.cmp"}) +public class RedisClusterSubscribeSpringBootTest extends BaseTest { + @Mock + private RedissonClient redissonClient; + + @Resource + private FlowExecutor flowExecutor; + + @MockBean(name = "chainClient") + private RClient chainClient; + + @MockBean(name = "scriptClient") + private RClient scriptClient; + + @Mock + private RMapCache chainKey; + + @Mock + private RMapCache scriptKey; + + @BeforeEach + public void setUpBeforeClass() { + + when(redissonClient.getMapCache("testChainKey")).thenReturn(chainKey); + when(redissonClient.getMapCache("testScriptKey")).thenReturn(scriptKey); + + when(scriptKey.get("s1:script:脚本s1:groovy")).thenReturn("defaultContext.setData(\"test1\",\"hello s1\");"); + when(scriptKey.get("s2:script:脚本s2:js")).thenReturn("defaultContext.setData(\"test2\",\"hello s2\");"); + when(scriptKey.get("s3:script:脚本s3")).thenReturn("defaultContext.setData(\"test3\",\"hello s3\");"); + + Set> mockEntrySet = new HashSet<>(); + mockEntrySet.add(createMockEntry("s1:script:脚本s1:groovy", "defaultContext.setData(\"test1\",\"hello s1\");")); + mockEntrySet.add(createMockEntry("s2:script:脚本s2:js", "defaultContext.setData(\"test2\",\"hello s2\");")); + mockEntrySet.add(createMockEntry("s3:script:脚本s3", "defaultContext.setData(\"test3\",\"hello s3\");")); + when(scriptKey.entrySet()).thenReturn(mockEntrySet); + + when(chainKey.get("chain1")).thenReturn("THEN(a, b, c);"); + when(chainKey.get("chain2")).thenReturn("THEN(a, b, c, s3);"); + when(chainKey.get("chain3")).thenReturn("THEN(a, b, c, s1, s2);"); + + mockEntrySet = new HashSet<>(); + mockEntrySet.add(createMockEntry("chain1", "THEN(a, b, c);")); + mockEntrySet.add(createMockEntry("chain2", "THEN(a, b, c, s3);")); + mockEntrySet.add(createMockEntry("chain3", "THEN(a, b, c, s1, s2);")); + + Set> mockEntrySet1 = new HashSet<>(mockEntrySet); + + mockEntrySet1.add(createMockEntry("chain1", "THEN(a, c, b);")); + when(chainKey.entrySet()).thenReturn(mockEntrySet).thenReturn(mockEntrySet1); + + when(chainClient.getMap(anyString())).thenReturn(chainKey); + when(scriptClient.getMap(anyString())).thenReturn(scriptKey); + } + + private Map.Entry createMockEntry(Object key, Object value) { + Map.Entry entry = mock(Map.Entry.class); + when(entry.getKey()).thenReturn(key); + when(entry.getValue()).thenReturn(value); + return entry; + } + + /** + * 测试chain + */ + @Test + public void testSubWithXml() throws InterruptedException { + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("a==>b==>c", response.getExecuteStepStr()); + + //修改redis中规则 + changeXMLData(); + //重新加载规则 + Thread.sleep(100); + + Assertions.assertEquals("a==>c==>b", flowExecutor.execute2Resp("chain1", "arg").getExecuteStepStr()); + + //删除redis中规则 + deleteXMLData(); + //重新加载规则 + Thread.sleep(100); + //由于chain1已被删除 这里会报ChainNotFoundException异常 + response = flowExecutor.execute2Resp("chain1", "arg"); + Assertions.assertTrue(!response.isSuccess()); + + //添加redis中规则 + addXMLData(); + //重新加载规则 + Thread.sleep(100); + Assertions.assertEquals("b==>c", flowExecutor.execute2Resp("chain4", "arg").getExecuteStepStr()); + } + + /** + * 测试script + */ + @Test + public void testSubWithScriptXml() throws InterruptedException { + LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg"); + DefaultContext context = response.getFirstContextBean(); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("hello s1", context.getData("test1")); + Assertions.assertEquals("a==>b==>c==>s1[脚本s1]==>s2[脚本s2]", response.getExecuteStepStrWithoutTime()); + + //添加和删除脚本 + addAndDeleteScriptData(); + //修改redis脚本 + changeScriptData(); + Thread.sleep(100); + context = flowExecutor.execute2Resp("chain3", "arg").getFirstContextBean(); + Assertions.assertEquals("hello s1 version2", context.getData("test1")); + context = flowExecutor.execute2Resp("chain2", "arg").getFirstContextBean(); + Assertions.assertEquals("hello s3 version2", context.getData("test2")); + } + + /** + * 修改redisson中的chain + */ + public void changeXMLData() { + RedisParserHelper.changeChain("chain1", "THEN(a, c, b);"); + } + + /** + * 删除redisson中的chain + */ + public void deleteXMLData() { + FlowBus.removeChain("chain1"); + FlowBus.removeChain("chain4"); + } + + /** + * 新增redisson中的chain + */ + public void addXMLData() { + RedisParserHelper.changeChain("chain4", "THEN(b, c);"); + } + + /** + * 修改redisson中的脚本 + */ + public void changeScriptData() { + RedisParserHelper.changeScriptNode("s1:script:脚本s1:groovy", "defaultContext.setData(\"test1\",\"hello s1 version2\");"); + RedisParserHelper.changeScriptNode("s3:script:脚本s3", "defaultContext.setData(\"test2\",\"hello s3 version2\");"); + } + + /** + * 新增和删除redisson中的chain + */ + public void addAndDeleteScriptData() { + NodeConvertHelper.NodeSimpleVO nodeSimpleVO = NodeConvertHelper.convert("s3:script:脚本s3"); + FlowBus.unloadScriptNode(nodeSimpleVO.getNodeId()); + + RedisParserHelper.changeScriptNode("s5:script:脚本s5:groovy", "defaultContext.setData(\"test1\",\"hello s5\");"); + } + + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/resources/redis/application-poll-cluster-xml.properties b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/resources/redis/application-poll-cluster-xml.properties new file mode 100644 index 000000000..6153f5d55 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/resources/redis/application-poll-cluster-xml.properties @@ -0,0 +1,9 @@ +liteflow.rule-source-ext-data={\ + "redisMode":"cluster",\ + "clusterAddress":"127.0.0.1:26389,127.0.0.1:26379",\ + "pollingInterval":1,\ + "pollingStartTime":2,\ + "chainKey":"pollChainKey",\ + "scriptKey":"pollScriptKey"\ + } +liteflow.parse-mode=PARSE_ALL_ON_FIRST_EXEC \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/resources/redis/application-sub-cluster-xml.properties b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/resources/redis/application-sub-cluster-xml.properties new file mode 100644 index 000000000..826ac8948 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/resources/redis/application-sub-cluster-xml.properties @@ -0,0 +1,10 @@ +liteflow.rule-source-ext-data={\ + "redisMode":"cluster",\ + "clusterAddress":"127.0.0.1:26389,127.0.0.1:26379",\ + "mode":"sub",\ + "chainDataBase":1,\ + "chainKey":"testChainKey",\ + "scriptDataBase":1,\ + "scriptKey":"testScriptKey"\ + } +liteflow.parse-mode=PARSE_ALL_ON_FIRST_EXEC \ No newline at end of file From e659a4a2b0c50247ceec00356688cdbaa49e8f5c Mon Sep 17 00:00:00 2001 From: jay li Date: Tue, 24 Jun 2025 11:37:12 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=20#ICGGAW=20redis=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90=E6=94=AF=E6=8C=81=E9=9B=86=E7=BE=A4=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?,=E4=BF=AE=E6=94=B9=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yomahub/liteflow/parser/redis/RedisXmlELParser.java | 2 +- .../parser/redis/mode/polling/RedisParserPollingMode.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/RedisXmlELParser.java b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/RedisXmlELParser.java index ce652aa03..92196a6ac 100644 --- a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/RedisXmlELParser.java +++ b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/RedisXmlELParser.java @@ -119,7 +119,7 @@ public class RedisXmlELParser extends ClassXmlFlowELParser { if (redisParserVO.getRedisMode().equals(RedisMode.CLUSTER) && CollectionUtil.isEmpty(redisParserVO.getClusterNodeAddress())) { throw new RedisException(StrFormatter.format(ERROR_MSG_PATTERN, "cluster address list")); } - if (ObjectUtil.isNull(redisParserVO.getScriptKey()) && ObjectUtil.isNull(redisParserVO.getScriptDataBase()) && !redisParserVO.getRedisMode().equals(RedisMode.CLUSTER)) { + if (ObjectUtil.isNotNull(redisParserVO.getScriptKey()) && ObjectUtil.isNull(redisParserVO.getScriptDataBase()) && !redisParserVO.getRedisMode().equals(RedisMode.CLUSTER)) { throw new RedisException(StrFormatter.format(ERROR_MSG_PATTERN, "scriptDataBase")); } } diff --git a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/polling/RedisParserPollingMode.java b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/polling/RedisParserPollingMode.java index 3338f772f..4331a5f02 100644 --- a/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/polling/RedisParserPollingMode.java +++ b/liteflow-rule-plugin/liteflow-rule-redis/src/main/java/com/yomahub/liteflow/parser/redis/mode/polling/RedisParserPollingMode.java @@ -214,7 +214,7 @@ public class RedisParserPollingMode implements RedisParserHelper { redisParserVO.getPollingInterval().longValue(), TimeUnit.SECONDS); //如果有脚本 - if (ObjectUtil.isNotNull(scriptClient) && (ObjectUtil.isNotNull(redisParserVO.getScriptDataBase()) || RedisMode.CLUSTER.getMode().equals(redisParserVO.getMode().getMode())) + if (ObjectUtil.isNotNull(scriptClient) && (ObjectUtil.isNotNull(redisParserVO.getScriptDataBase()) || RedisMode.CLUSTER.equals(redisParserVO.getRedisMode())) && StrUtil.isNotBlank(redisParserVO.getScriptKey())) { //将lua脚本添加到scriptJedis脚本缓存 String keyLuaOfScript = scriptClient.scriptLoad(luaOfKey); From 23c540ae53d9ced9b7569222a2a784fc54499eb0 Mon Sep 17 00:00:00 2001 From: jay li Date: Tue, 24 Jun 2025 14:36:17 +0800 Subject: [PATCH 04/15] =?UTF-8?q?=20#ICGGAW=20=E9=98=B2=E6=AD=A2=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=B1=BB=E5=85=B3=E9=97=AD=E7=BA=BF=E7=A8=8B=E6=B1=A0?= =?UTF-8?q?=E4=BA=92=E7=9B=B8=E5=BD=B1=E5=93=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../redis/RedisClusterPollSpringBootTest.java | 21 +++++++++------- .../RedisWithXmlELPollSpringbootTest.java | 24 +++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterPollSpringBootTest.java b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterPollSpringBootTest.java index b6149f86f..14f697796 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterPollSpringBootTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisClusterPollSpringBootTest.java @@ -27,6 +27,7 @@ import javax.annotation.Resource; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import static org.mockito.ArgumentMatchers.anyString; @@ -74,17 +75,21 @@ public class RedisClusterPollSpringBootTest extends BaseTest { static LFLog LOG = LFLoggerManager.getLogger(RedisClusterPollSpringBootTest.class); - - @AfterAll - public static void after() { - //关闭poll模式的轮询线程池 - try{ + @AfterEach + void afterEach() { + try { Field pollExecutor = RedisParserPollingMode.class.getDeclaredField("pollExecutor"); pollExecutor.setAccessible(true); - ScheduledThreadPoolExecutor threadPoolExecutor = (ScheduledThreadPoolExecutor) pollExecutor.get(null); - threadPoolExecutor.shutdownNow(); + // 关闭旧线程池 + ScheduledThreadPoolExecutor oldPool = (ScheduledThreadPoolExecutor) pollExecutor.get(null); + if (oldPool != null) { + oldPool.shutdownNow(); + } + // 创建新线程池并设置回去 + ScheduledThreadPoolExecutor newPool = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1); + pollExecutor.set(null, newPool); } catch (Exception ignored) { - LOG.error("[Polling thread pool not closed]", ignored); + LOG.error("[Polling thread pool reset failed]", ignored); } } diff --git a/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisWithXmlELPollSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisWithXmlELPollSpringbootTest.java index 2de4189a9..2c0a7397a 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisWithXmlELPollSpringbootTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-redis-springboot/src/test/java/com/yomahub/liteflow/test/redis/RedisWithXmlELPollSpringbootTest.java @@ -11,9 +11,7 @@ import com.yomahub.liteflow.parser.redis.mode.RClient; import com.yomahub.liteflow.parser.redis.mode.polling.RedisParserPollingMode; import com.yomahub.liteflow.slot.DefaultContext; import com.yomahub.liteflow.test.BaseTest; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; @@ -26,6 +24,7 @@ import javax.annotation.Resource; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import static org.mockito.ArgumentMatchers.anyString; @@ -71,16 +70,21 @@ public class RedisWithXmlELPollSpringbootTest extends BaseTest { static LFLog LOG = LFLoggerManager.getLogger(RedisWithXmlELPollSpringbootTest.class); - @AfterAll - public static void after() { - //关闭poll模式的轮询线程池 - try{ + @AfterEach + void afterEach() { + try { Field pollExecutor = RedisParserPollingMode.class.getDeclaredField("pollExecutor"); pollExecutor.setAccessible(true); - ScheduledThreadPoolExecutor threadPoolExecutor = (ScheduledThreadPoolExecutor) pollExecutor.get(null); - threadPoolExecutor.shutdownNow(); + // 关闭旧线程池 + ScheduledThreadPoolExecutor oldPool = (ScheduledThreadPoolExecutor) pollExecutor.get(null); + if (oldPool != null) { + oldPool.shutdownNow(); + } + // 创建新线程池并设置回去 + ScheduledThreadPoolExecutor newPool = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1); + pollExecutor.set(null, newPool); } catch (Exception ignored) { - LOG.error("[Polling thread pool not closed]", ignored); + LOG.error("[Polling thread pool reset failed]", ignored); } } From 852c18ac0b6dfb843533ce69ba6a70943a26a4bf Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Mon, 30 Jun 2025 17:14:22 +0800 Subject: [PATCH 05/15] =?UTF-8?q?enhancement=20#IBQCWB=20FlowExecutor=20?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E6=94=AF=E6=8C=81=E6=89=A7=E8=A1=8C=E8=B0=83?= =?UTF-8?q?=E7=94=A8=20EL=20=E8=A1=A8=E8=BE=BE=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builder/el/LiteFlowChainELBuilder.java | 16 +- .../yomahub/liteflow/core/FlowExecutor.java | 51 +- .../com/yomahub/liteflow/flow/FlowBus.java | 647 +++++++++--------- .../yomahub/liteflow/flow/element/Chain.java | 334 ++++----- .../BaseNodeInstanceIdManageSpi.java | 6 +- .../yomahub/liteflow/util/ElRegexUtil.java | 13 +- .../test/base/BaseELSpringbootTest.java | 23 +- 7 files changed, 589 insertions(+), 501 deletions(-) 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 0a4f426d7..f19286306 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,7 +1,10 @@ package com.yomahub.liteflow.builder.el; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.*; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.ql.util.express.DefaultContext; @@ -27,7 +30,10 @@ import com.yomahub.liteflow.log.LFLoggerManager; import com.yomahub.liteflow.property.LiteflowConfig; import com.yomahub.liteflow.property.LiteflowConfigGetter; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; /** @@ -36,6 +42,7 @@ import java.util.*; * @author Bryan.Zhang * @author Jay li * @author jason + * @author luo yi * @since 2.8.0 */ public class LiteFlowChainELBuilder { @@ -268,6 +275,11 @@ public class LiteFlowChainELBuilder { } } + public LiteFlowChainELBuilder setElMd5(String md5) { + this.chain.setElMd5(md5); + return this; + } + public void build() { this.chain.setRouteItem(this.route); this.chain.setConditionList(this.conditionList); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java index e79dd56f6..37d9e4925 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java @@ -13,8 +13,10 @@ import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Tuple; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; +import cn.hutool.crypto.digest.MD5; import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder; import com.yomahub.liteflow.common.ChainConstant; +import com.yomahub.liteflow.common.entity.ValidationResp; import com.yomahub.liteflow.enums.ChainExecuteModeEnum; import com.yomahub.liteflow.enums.ParseModeEnum; import com.yomahub.liteflow.exception.*; @@ -40,6 +42,7 @@ import com.yomahub.liteflow.slot.Slot; import com.yomahub.liteflow.spi.holder.ContextCmpInitHolder; import com.yomahub.liteflow.spi.holder.PathContentParserHolder; import com.yomahub.liteflow.thread.ExecutorHelper; +import com.yomahub.liteflow.util.ElRegexUtil; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -51,6 +54,7 @@ import java.util.stream.Collectors; * 流程规则主要执行器类 * * @author Bryan.Zhang + * @author luo yi */ public class FlowExecutor { @@ -252,8 +256,9 @@ public class FlowExecutor { * * @param elStr EL 表达式 * @return LiteflowResponse + * @throws ELParseException */ - public LiteflowResponse execute2RespWithEL(String elStr) { + public LiteflowResponse execute2RespWithEL(String elStr) throws Exception { return this.execute2RespWithEL(elStr, null, null, DefaultContext.class); } @@ -263,8 +268,9 @@ public class FlowExecutor { * @param elStr EL 表达式 * @param param 入参 * @return LiteflowResponse + * @throws ELParseException */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param) { + public LiteflowResponse execute2RespWithEL(String elStr, Object param) throws Exception { return this.execute2RespWithEL(elStr, param, null, DefaultContext.class); } @@ -276,8 +282,9 @@ public class FlowExecutor { * @param requestId 请求 ID * @param contextBeanClazzArray 上下文 Class * @return LiteflowResponse + * @throws ELParseException */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class... contextBeanClazzArray) { + public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class... contextBeanClazzArray) throws Exception { return this.execute2RespWithEL(elStr, param, requestId, contextBeanClazzArray, null); } @@ -289,8 +296,9 @@ public class FlowExecutor { * @param requestId 请求 ID * @param contextBeanArray 上下文对象 * @return LiteflowResponse + * @throws ELParseException */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Object... contextBeanArray) { + public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Object... contextBeanArray) throws Exception { return this.execute2RespWithEL(elStr, param, requestId, null, contextBeanArray); } @@ -303,14 +311,35 @@ public class FlowExecutor { * @param contextBeanClazzArray 上下文 Class 数组 * @param contextBeanArray 上下文对象数组 * @return LiteflowResponse + * @throws ELParseException */ - private LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) { - // 调用表达式构造 chain,并且返回 UUID 作为 chainId - String chainId = IdUtil.fastSimpleUUID(); - LiteFlowChainELBuilder.createChain() - .setChainId(chainId) - .setEL(elStr) - .build(); + private LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) throws Exception { + // 规范化 el 表达式 + String normalizedEl = ElRegexUtil.normalize(elStr); + + // 校验 EL 是否正常 + ValidationResp validationResp = LiteFlowChainELBuilder.validateWithEx(normalizedEl); + + if (!validationResp.isSuccess()) { + // 实际封装的是 ELParseException 类型 + throw validationResp.getCause(); + } + + // 计算 EL MD5 值,并检查对应的 chain 是否已加载到内存中 + String elMd5 = MD5.create().digestHex(normalizedEl); + + String chainId; + + if (StrUtil.isEmpty(chainId = FlowBus.getChainIdByElMd5(elMd5))) { + // 调用表达式构造 chain,并且返回 UUID 作为 chainId + chainId = IdUtil.fastSimpleUUID(); + LiteFlowChainELBuilder.createChain() + .setChainId(chainId) + .setEL(normalizedEl) + .setElMd5(elMd5) + .build(); + } + return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, contextBeanArray); } 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 0e16b183a..4eea59284 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 @@ -19,13 +19,13 @@ import com.yomahub.liteflow.core.ComponentInitializer; import com.yomahub.liteflow.core.NodeComponent; import com.yomahub.liteflow.core.ScriptComponent; import com.yomahub.liteflow.core.proxy.DeclWarpBean; +import com.yomahub.liteflow.core.proxy.LiteFlowProxyUtil; 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.NullNodeTypeException; 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.lifecycle.LifeCycleHolder; import com.yomahub.liteflow.log.LFLog; @@ -43,14 +43,10 @@ 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.core.proxy.LiteFlowProxyUtil; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * 流程元数据类 @@ -58,384 +54,395 @@ import java.util.stream.Stream; * @author Bryan.Zhang * @author DaleLee * @author Jay li + * @author luo yi */ 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 AtomicBoolean initStat = new AtomicBoolean(false); + private static final Map elMd5Map; - static { - LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); - if (liteflowConfig.getFastLoad()){ - chainMap = new HashMap<>(); - nodeMap = new HashMap<>(); - fallbackNodeMap = new HashMap<>(); - }else{ - chainMap = new CopyOnWriteHashMap<>(); - nodeMap = new CopyOnWriteHashMap<>(); - fallbackNodeMap = new CopyOnWriteHashMap<>(); - } - } + private static final AtomicBoolean initStat = new AtomicBoolean(false); - public static Chain getChain(String id) { - return chainMap.get(id); - } + 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 CopyOnWriteHashMap<>(); + } + } - // 这一方法主要用于第一阶段chain的预装载 - public static void addChain(String chainName) { - if (!chainMap.containsKey(chainName)) { - chainMap.put(chainName, new Chain(chainName)); - } - } + public static Chain getChain(String id) { + return chainMap.get(id); + } - // 这个方法主要用于第二阶段的替换chain - public static void addChain(Chain chain) { - //如果有生命周期则执行相应生命周期实现 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())){ - LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( - postProcessAfterChainBuildLifeCycle -> postProcessAfterChainBuildLifeCycle.postProcessBeforeChainBuild(chain) - ); - } + // 这一方法主要用于第一阶段chain的预装载 + public static void addChain(String chainName) { + if (!chainMap.containsKey(chainName)) { + chainMap.put(chainName, new Chain(chainName)); + } + } - chainMap.put(chain.getChainId(), chain); + // 这个方法主要用于第二阶段的替换chain + public static void addChain(Chain chain) { + //如果有生命周期则执行相应生命周期实现 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())) { + LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( + postProcessAfterChainBuildLifeCycle -> postProcessAfterChainBuildLifeCycle.postProcessBeforeChainBuild(chain) + ); + } - //如果有生命周期则执行相应生命周期实现 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())){ - LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( - postProcessAfterChainBuildLifeCycle -> postProcessAfterChainBuildLifeCycle.postProcessAfterChainBuild(chain) - ); - } - } + chainMap.put(chain.getChainId(), chain); - public static boolean containChain(String chainId) { - return chainMap.containsKey(chainId); - } + elMd5Map.put(chain.getElMd5(), chain.getChainId()); - public static boolean needInit() { - return initStat.compareAndSet(false, true); - } + //如果有生命周期则执行相应生命周期实现 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())) { + LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( + postProcessAfterChainBuildLifeCycle -> postProcessAfterChainBuildLifeCycle.postProcessAfterChainBuild(chain) + ); + } + } - public static boolean containNode(String nodeId) { - return nodeMap.containsKey(nodeId); - } + public static boolean containChain(String chainId) { + return chainMap.containsKey(chainId); + } - public static void addManagedNode(String nodeId) { - ContextAware contextAware = ContextAwareHolder.loadContextAware(); - if (contextAware.hasBean(nodeId)){ - addManagedNode(nodeId, contextAware.getBean(nodeId)); - } - } + public static boolean needInit() { + return initStat.compareAndSet(false, true); + } - /** - * 添加已托管的节点(如:Spring、Solon 管理的节点) - * @param nodeId nodeId - * @param nodeComponent nodeComponent - */ - public static void addManagedNode(String nodeId, NodeComponent nodeComponent) { - // 根据class来猜测类型 - NodeTypeEnum type = NodeTypeEnum.guessType(nodeComponent.getClass()); + public static boolean containNode(String nodeId) { + return nodeMap.containsKey(nodeId); + } - if (type == null) { - throw new NullNodeTypeException(StrUtil.format("node type is null for node[{}]", nodeId)); - } + public static void addManagedNode(String nodeId) { + ContextAware contextAware = ContextAwareHolder.loadContextAware(); + if (contextAware.hasBean(nodeId)) { + addManagedNode(nodeId, contextAware.getBean(nodeId)); + } + } - Node node = new Node(ComponentInitializer.loadInstance() - .initComponent(nodeComponent, type, nodeComponent.getName(), nodeId)); - put2NodeMap(nodeId, node); - addFallbackNode(node); - } + /** + * 添加已托管的节点(如:Spring、Solon 管理的节点) + * + * @param nodeId nodeId + * @param nodeComponent nodeComponent + */ + public static void addManagedNode(String nodeId, NodeComponent nodeComponent) { + // 根据class来猜测类型 + NodeTypeEnum type = NodeTypeEnum.guessType(nodeComponent.getClass()); - /** - * 添加 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); - } + if (type == null) { + throw new NullNodeTypeException(StrUtil.format("node type is null for node[{}]", nodeId)); + } - /** - * 添加 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 node = new Node(ComponentInitializer.loadInstance() + .initComponent(nodeComponent, type, nodeComponent.getName(), nodeId)); + put2NodeMap(nodeId, node); + addFallbackNode(node); + } - /** - * 添加脚本 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 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 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); - nodeMap.put(nodeId, node); - } else { - addScriptNodeAndCompile(nodeId, name, nodeType, script, language); + Node node = new Node(nodeId, name, nodeType, script, language); + nodeMap.put(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 - * @return NodeComponent instance - */ - public static NodeComponent addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script, - String language) { - addNode(nodeId, name, type, ScriptComponent.ScriptComponentClassMap.get(type), script, language); - return nodeMap.get(nodeId).getInstance(); - } + /** + * 添加脚本 node,并且编译脚本 + * + * @param nodeId nodeId + * @param name name + * @param type type + * @param script script content + * @param language language + * @return NodeComponent instance + */ + public static NodeComponent addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script, + String language) { + addNode(nodeId, name, type, ScriptComponent.ScriptComponentClassMap.get(type), script, language); + return nodeMap.get(nodeId).getInstance(); + } - private static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz, String script, - String language) { - try { - // 判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例 - // 如果不是声明式的,就用传统的方式进行判断 - 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 void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz, String script, + String language) { + try { + // 判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例 + // 如果不是声明式的,就用传统的方式进行判断 + 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 = cmpInstanceList.stream() - .map(cmpInstance -> ComponentInitializer.loadInstance() - .initComponent(cmpInstance, type, name, - cmpInstance.getNodeId() == null ? nodeId : cmpInstance.getNodeId())) - .collect(Collectors.toList()); + 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 = cmpInstanceList.stream() + .map(cmpInstance -> ComponentInitializer.loadInstance() + .initComponent(cmpInstance, type, name, + cmpInstance.getNodeId() == null ? nodeId : cmpInstance.getNodeId())) + .collect(Collectors.toList()); - // 初始化Node,把component放到Node里去 - List nodes = cmpInstanceList.stream().map(Node::new).collect(Collectors.toList()); + // 初始化Node,把component放到Node里去 + List nodes = cmpInstanceList.stream().map(Node::new).collect(Collectors.toList()); - for (int i = 0; i < nodes.size(); i++) { - Node node = nodes.get(i); - NodeComponent cmpInstance = cmpInstanceList.get(i); - // 如果是脚本节点,则还要加载script脚本 - if (type.isScript()) { - if (StrUtil.isNotBlank(script)) { - node.setScript(script); - node.setLanguage(language); - ((ScriptComponent) cmpInstance).loadScript(script, language); - } - 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); - } - } - 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())); - } - } + for (int i = 0; i < nodes.size(); i++) { + Node node = nodes.get(i); + NodeComponent cmpInstance = cmpInstanceList.get(i); + // 如果是脚本节点,则还要加载script脚本 + if (type.isScript()) { + if (StrUtil.isNotBlank(script)) { + node.setScript(script); + node.setLanguage(language); + ((ScriptComponent) cmpInstance).loadScript(script, language); + } 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); + } + } 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 Node getNode(String nodeId) { - return nodeMap.get(nodeId); - } + public static Node getNode(String nodeId) { + return nodeMap.get(nodeId); + } - public static Map getNodeMap() { - return nodeMap; - } + public static Map getNodeMap() { + return nodeMap; + } - public static Map getChainMap() { - return chainMap; - } + public static Map getChainMap() { + return chainMap; + } - public static Node getFallBackNode(NodeTypeEnum nodeType){ - String key = StrUtil.format("FB_{}", nodeType.name()); - return fallbackNodeMap.get(key); - } + public static Node getFallBackNode(NodeTypeEnum nodeType) { + String key = StrUtil.format("FB_{}", nodeType.name()); + return fallbackNodeMap.get(key); + } - public static void cleanCache() { - chainMap.clear(); - nodeMap.clear(); - fallbackNodeMap.clear(); - cleanScriptCache(); - } + public static void cleanCache() { + chainMap.clear(); + nodeMap.clear(); + fallbackNodeMap.clear(); + elMd5Map.clear(); + cleanScriptCache(); + } - public static void cleanScriptCache() { - // 如果引入了脚本组件SPI,则还需要清理脚本的缓存 - try { - ScriptExecutorFactory.loadInstance().cleanScriptCache(); - } - catch (ScriptSpiException ignored) { - } - } + public static void cleanScriptCache() { + // 如果引入了脚本组件SPI,则还需要清理脚本的缓存 + try { + ScriptExecutorFactory.loadInstance().cleanScriptCache(); + } catch (ScriptSpiException ignored) { + } + } - 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 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 boolean removeChain(String chainId) { - if (containChain(chainId)) { - chainMap.remove(chainId); - return true; - } - else { - String errMsg = StrUtil.format("cannot find the chain[{}]", chainId); - LOG.error(errMsg); - return false; - } - } + public static String getChainIdByElMd5(String elMd5) { + return elMd5Map.get(elMd5); + } - public static void removeChain(String... chainIds) { - Arrays.stream(chainIds).forEach(FlowBus::removeChain); - } + 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 boolean removeNode(String nodeId) { - return nodeMap.remove(nodeId) != null; - } + public static void removeChain(String... chainIds) { + Arrays.stream(chainIds).forEach(FlowBus::removeChain); + } - // 判断是否是降级组件,如果是则添加到 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 boolean removeNode(String nodeId) { + return nodeMap.remove(nodeId) != null; + } - NodeTypeEnum nodeType = node.getType(); - String key = StrUtil.format("FB_{}", nodeType.name()); - fallbackNodeMap.put(key, node); - } + // 判断是否是降级组件,如果是则添加到 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/flow/element/Chain.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java index 489749f39..62f6e1abb 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java @@ -10,6 +10,7 @@ package com.yomahub.liteflow.flow.element; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.BooleanUtil; +import cn.hutool.crypto.digest.MD5; import com.alibaba.ttl.TransmittableThreadLocal; import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder; import com.yomahub.liteflow.common.ChainConstant; @@ -30,207 +31,206 @@ import java.util.List; * * @author Bryan.Zhang * @author jason + * @author luo yi */ -public class Chain implements Executable{ +public class Chain implements Executable { - private static final LFLog LOG = LFLoggerManager.getLogger(Chain.class); + private static final LFLog LOG = LFLoggerManager.getLogger(Chain.class); - private String chainId; + private String chainId; - private Executable routeItem; + private Executable routeItem; - private List conditionList = new ArrayList<>(); + private List conditionList = new ArrayList<>(); - private String el; + private String el; - private boolean isCompiled = true; + private boolean isCompiled = true; - private String namespace = ChainConstant.DEFAULT_NAMESPACE; + private String namespace = ChainConstant.DEFAULT_NAMESPACE; + + private String elMd5; private String threadPoolExecutorClass; - private final TransmittableThreadLocal runtimeIdTL = new TransmittableThreadLocal<>(); + private final TransmittableThreadLocal runtimeIdTL = new TransmittableThreadLocal<>(); - public Chain(String chainName) { - this.chainId = chainName; - } + public Chain(String chainName) { + this.chainId = chainName; + } - public Chain() { - } + public Chain() { + } - public Chain(String chainName, List conditionList) { - this.chainId = chainName; - this.conditionList = conditionList; - } + public Chain(String chainName, List conditionList) { + this.chainId = chainName; + this.conditionList = conditionList; + } - public List getConditionList() { - return conditionList; - } + public List getConditionList() { + return conditionList; + } - public void setConditionList(List conditionList) { - this.conditionList = conditionList; - } + public void setConditionList(List conditionList) { + this.conditionList = conditionList; + } - /** - * @deprecated 请使用{@link #getChainId()} - * @return chainId - */ - @Deprecated - public String getChainName() { - return chainId; - } + /** + * @return chainId + * @deprecated 请使用{@link #getChainId()} + */ + @Deprecated + public String getChainName() { + return chainId; + } - /** - * @param chainName chainId - * @deprecated 请使用 {@link #setChainId(String)} - */ - @Deprecated - public void setChainName(String chainName) { - this.chainId = chainName; - } + /** + * @param chainName chainId + * @deprecated 请使用 {@link #setChainId(String)} + */ + @Deprecated + public void setChainName(String chainName) { + this.chainId = chainName; + } - public String getChainId() { - return chainId; - } + public String getChainId() { + return chainId; + } - public void setChainId(String chainId) { - this.chainId = chainId; - } + public void setChainId(String chainId) { + this.chainId = chainId; + } - // 执行chain的主方法 - @Override - public void execute(Integer slotIndex) throws Exception { - //生成runtimeId - this.runtimeIdTL.set(System.nanoTime()); + // 执行chain的主方法 + @Override + public void execute(Integer slotIndex) throws Exception { + //生成runtimeId + this.runtimeIdTL.set(System.nanoTime()); - //如果EL还未编译,则进行编译 - if (BooleanUtil.isFalse(isCompiled)) { - LiteFlowChainELBuilder.buildUnCompileChain(this); - } + //如果EL还未编译,则进行编译 + if (BooleanUtil.isFalse(isCompiled)) { + LiteFlowChainELBuilder.buildUnCompileChain(this); + } - if (CollUtil.isEmpty(conditionList)) { - throw new FlowSystemException("no conditionList in this chain[" + chainId + "]"); - } - Slot slot = DataBus.getSlot(slotIndex); - try { - //如果有生命周期则执行相应生命周期实现 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainExecuteLifeCycleList())){ - LifeCycleHolder.getPostProcessChainExecuteLifeCycleList().forEach( - postProcessChainExecuteLifeCycle -> postProcessChainExecuteLifeCycle.postProcessBeforeChainExecute(chainId, slot) - ); - } + if (CollUtil.isEmpty(conditionList)) { + throw new FlowSystemException("no conditionList in this chain[" + chainId + "]"); + } + Slot slot = DataBus.getSlot(slotIndex); + try { + //如果有生命周期则执行相应生命周期实现 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainExecuteLifeCycleList())) { + LifeCycleHolder.getPostProcessChainExecuteLifeCycleList().forEach( + postProcessChainExecuteLifeCycle -> postProcessChainExecuteLifeCycle.postProcessBeforeChainExecute(chainId, slot) + ); + } - // 设置主ChainId - slot.setChainId(chainId); - slot.addChainInstance(this); - // 执行主体Condition - for (Condition condition : conditionList) { - condition.setCurrChainId(chainId); - condition.execute(slotIndex); - } - } - catch (ChainEndException e) { - // 这里单独catch ChainEndException是因为ChainEndException是用户自己setIsEnd抛出的异常 - // 是属于正常逻辑,所以会在FlowExecutor中判断。这里不作为异常处理 - throw e; - } - catch (Exception e) { - // 这里事先取到exception set到slot里,为了方便finally取到exception - slot.setException(e); - throw e; - }finally { - //如果有生命周期则执行相应生命周期实现 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainExecuteLifeCycleList())){ - LifeCycleHolder.getPostProcessChainExecuteLifeCycleList().forEach( - postProcessChainExecuteLifeCycle -> postProcessChainExecuteLifeCycle.postProcessAfterChainExecute(chainId, slot) - ); - } - runtimeIdTL.remove(); - } - } + // 设置主ChainId + slot.setChainId(chainId); + slot.addChainInstance(this); + // 执行主体Condition + for (Condition condition : conditionList) { + condition.setCurrChainId(chainId); + condition.execute(slotIndex); + } + } catch (ChainEndException e) { + // 这里单独catch ChainEndException是因为ChainEndException是用户自己setIsEnd抛出的异常 + // 是属于正常逻辑,所以会在FlowExecutor中判断。这里不作为异常处理 + throw e; + } catch (Exception e) { + // 这里事先取到exception set到slot里,为了方便finally取到exception + slot.setException(e); + throw e; + } finally { + //如果有生命周期则执行相应生命周期实现 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainExecuteLifeCycleList())) { + LifeCycleHolder.getPostProcessChainExecuteLifeCycleList().forEach( + postProcessChainExecuteLifeCycle -> postProcessChainExecuteLifeCycle.postProcessAfterChainExecute(chainId, slot) + ); + } + runtimeIdTL.remove(); + } + } - public void executeRoute(Integer slotIndex) throws Exception { - if (routeItem == null) { - throw new FlowSystemException("no route condition or node in this chain[" + chainId + "]"); - } - Slot slot = DataBus.getSlot(slotIndex); - try { - // 设置主ChainName - slot.setChainId(chainId); + public void executeRoute(Integer slotIndex) throws Exception { + if (routeItem == null) { + throw new FlowSystemException("no route condition or node in this chain[" + chainId + "]"); + } + Slot slot = DataBus.getSlot(slotIndex); + try { + // 设置主ChainName + slot.setChainId(chainId); - // 执行决策路由 - routeItem.setCurrChainId(chainId); - routeItem.execute(slotIndex); + // 执行决策路由 + routeItem.setCurrChainId(chainId); + routeItem.execute(slotIndex); - boolean routeResult = routeItem.getItemResultMetaValue(slotIndex); + boolean routeResult = routeItem.getItemResultMetaValue(slotIndex); - slot.setRouteResult(routeResult); - } - catch (ChainEndException e) { - throw e; - } - catch (Exception e) { - slot.setException(e); - throw e; - } - } + slot.setRouteResult(routeResult); + } catch (ChainEndException e) { + throw e; + } catch (Exception e) { + slot.setException(e); + throw e; + } + } - @Override - public ExecuteableTypeEnum getExecuteType() { - return ExecuteableTypeEnum.CHAIN; - } + @Override + public ExecuteableTypeEnum getExecuteType() { + return ExecuteableTypeEnum.CHAIN; + } - @Override - public void setId(String id) { - this.chainId = id; - } + @Override + public void setId(String id) { + this.chainId = id; + } - @Override - public String getId() { - return chainId; - } + @Override + public String getId() { + return chainId; + } - @Override - public void setTag(String tag) { - //do nothing - } + @Override + public void setTag(String tag) { + //do nothing + } - @Override - public String getTag() { - return null; - } + @Override + public String getTag() { + return null; + } - public Executable getRouteItem() { - return routeItem; - } + public Executable getRouteItem() { + return routeItem; + } - public void setRouteItem(Executable routeItem) { - this.routeItem = routeItem; - } + public void setRouteItem(Executable routeItem) { + this.routeItem = routeItem; + } - public String getEl() { - return el; - } + public String getEl() { + return el; + } - public void setEl(String el) { - this.el = el; - } + public void setEl(String el) { + this.el = el; + } - public boolean isCompiled() { - return isCompiled; - } + public boolean isCompiled() { + return isCompiled; + } - public void setCompiled(boolean compiled) { - isCompiled = compiled; - } + public void setCompiled(boolean compiled) { + isCompiled = compiled; + } - public String getNamespace() { - return namespace; - } + public String getNamespace() { + return namespace; + } - public void setNamespace(String namespace) { - this.namespace = namespace; - } + public void setNamespace(String namespace) { + this.namespace = namespace; + } public String getThreadPoolExecutorClass() { return threadPoolExecutorClass; @@ -240,7 +240,15 @@ public class Chain implements Executable{ this.threadPoolExecutorClass = threadPoolExecutorClass; } - public Long getRuntimeId(){ - return runtimeIdTL.get(); - } + public Long getRuntimeId() { + return runtimeIdTL.get(); + } + + public String getElMd5() { + return elMd5 == null ? MD5.create().digestHex(el) : elMd5; + } + + public void setElMd5(String elMd5) { + this.elMd5 = elMd5; + } } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/BaseNodeInstanceIdManageSpi.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/BaseNodeInstanceIdManageSpi.java index b88183623..b18ef6467 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/BaseNodeInstanceIdManageSpi.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/instanceId/BaseNodeInstanceIdManageSpi.java @@ -1,7 +1,6 @@ package com.yomahub.liteflow.flow.instanceId; import cn.hutool.core.collection.CollUtil; -import cn.hutool.crypto.digest.MD5; import com.yomahub.liteflow.flow.FlowBus; import com.yomahub.liteflow.flow.element.Chain; import com.yomahub.liteflow.flow.element.Condition; @@ -9,11 +8,14 @@ import com.yomahub.liteflow.flow.element.Node; import com.yomahub.liteflow.flow.entity.InstanceInfoDto; import com.yomahub.liteflow.util.JsonUtil; import org.apache.commons.lang.StringUtils; + import java.util.*; + import static com.yomahub.liteflow.util.SerialsUtil.generateShortUUID; /** * @author lhh + * @author luo yi * @since 2.13.0 */ public abstract class BaseNodeInstanceIdManageSpi implements NodeInstanceIdManageSpi { @@ -156,7 +158,7 @@ public abstract class BaseNodeInstanceIdManageSpi implements NodeInstanceIdManag public void setNodesInstanceId(Condition condition, Chain chain) { NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi(); - String elMd5 = MD5.create().digestHex(chain.getEl()); + String elMd5 = chain.getElMd5(); String chainId = chain.getChainId(); List instanceIdFile = nodeInstanceIdManageSpi.readInstanceIdFile(chainId); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/util/ElRegexUtil.java b/liteflow-core/src/main/java/com/yomahub/liteflow/util/ElRegexUtil.java index 8598f0531..aa758b544 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/util/ElRegexUtil.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/util/ElRegexUtil.java @@ -1,7 +1,5 @@ package com.yomahub.liteflow.util; -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.util.StrUtil; import com.yomahub.liteflow.exception.ParseException; import java.util.regex.Matcher; @@ -56,4 +54,15 @@ public class ElRegexUtil { public static boolean isAbstractChain(String elStr) { return Pattern.compile(REGEX_ABSTRACT_HOLDER).matcher(elStr).find(); } + + /** + * 规范化 EL + * @param elStr + * @return String + */ + public static String normalize(String elStr) { + // 替换 EL 中多余空格,并在末尾保留分号 + return elStr.replaceAll("\\s", "").replaceFirst(";*$", ";"); + } + } diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java index be0accbeb..d0f7d56e5 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java @@ -1,6 +1,7 @@ package com.yomahub.liteflow.test.base; import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.FlowBus; import com.yomahub.liteflow.flow.LiteflowResponse; import com.yomahub.liteflow.test.BaseTest; import org.junit.jupiter.api.Assertions; @@ -16,6 +17,7 @@ import javax.annotation.Resource; * springboot环境EL常规的例子测试 * * @author Bryan.Zhang + * @author luo yi */ @TestPropertySource(value = "classpath:/base/application.properties") @SpringBootTest(classes = BaseELSpringbootTest.class) @@ -64,8 +66,27 @@ public class BaseELSpringbootTest extends BaseTest { // 入参执行 EL 表达式 @Test public void testBase6() throws Exception { - LiteflowResponse response = flowExecutor.execute2RespWithEL("THEN(a,b,c)"); + LiteflowResponse response = flowExecutor.execute2RespWithEL("THEN(a, b,c);;"); Assertions.assertTrue(response.isSuccess()); + + LiteflowResponse response1 = flowExecutor.execute2RespWithEL("THEN(\na, \tb,c);"); + Assertions.assertTrue(response1.isSuccess()); + + Assertions.assertEquals(response.getChainId(), response1.getChainId()); + } + + // 入参执行 EL 表达式,测试移除 chain + @Test + public void testBase7() throws Exception { + LiteflowResponse response = flowExecutor.execute2RespWithEL("THEN(a,b, \nc);;"); + Assertions.assertTrue(response.isSuccess()); + + FlowBus.removeChain(response.getChainId()); + + LiteflowResponse response1 = flowExecutor.execute2RespWithEL("THEN(a,b, c);"); + Assertions.assertTrue(response1.isSuccess()); + + Assertions.assertNotEquals(response.getChainId(), response1.getChainId()); } } From 670a110bc2c3f3698498665445b6eace6945ab24 Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Mon, 30 Jun 2025 19:39:53 +0800 Subject: [PATCH 06/15] =?UTF-8?q?enhancement=20#IBQCWB=20=E5=AF=B9?= =?UTF-8?q?=E6=89=80=E6=9C=89=20EL=20=E8=BF=9B=E8=A1=8C=E8=A7=84=E8=8C=83?= =?UTF-8?q?=E5=8C=96=E5=90=8E=E9=87=8D=E6=96=B0=E8=AE=A1=E7=AE=97=20MD5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yomahub/liteflow/flow/element/Chain.java | 7 ++++++- .../yomahub/liteflow/test/base/BaseELSpringbootTest.java | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java index 62f6e1abb..f5eef78e6 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java @@ -22,6 +22,7 @@ import com.yomahub.liteflow.log.LFLog; import com.yomahub.liteflow.log.LFLoggerManager; import com.yomahub.liteflow.slot.DataBus; import com.yomahub.liteflow.slot.Slot; +import com.yomahub.liteflow.util.ElRegexUtil; import java.util.ArrayList; import java.util.List; @@ -245,7 +246,11 @@ public class Chain implements Executable { } public String getElMd5() { - return elMd5 == null ? MD5.create().digestHex(el) : elMd5; + // 若为 null 时,先规范化 EL,再计算 MD5 + if (elMd5 == null) { + elMd5 = MD5.create().digestHex(ElRegexUtil.normalize(el)); + } + return elMd5; } public void setElMd5(String elMd5) { diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java index d0f7d56e5..a4ceede08 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java @@ -89,4 +89,13 @@ public class BaseELSpringbootTest extends BaseTest { Assertions.assertNotEquals(response.getChainId(), response1.getChainId()); } + // 运行文件里同样的 chain + @Test + public void testBase8() throws Exception { + LiteflowResponse response = flowExecutor.execute2RespWithEL("THEN(a,b,SWITCH(e).to(d,f));"); + Assertions.assertTrue(response.isSuccess()); + // 应返回 chain2 + Assertions.assertEquals("chain2", response.getChainId()); + } + } From e56960cb8e55d2e19aafc552efa3bdd739cb7ca1 Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Fri, 4 Jul 2025 16:55:27 +0800 Subject: [PATCH 07/15] =?UTF-8?q?bug=20=E8=B0=83=E6=95=B4=20FlowExecutor?= =?UTF-8?q?=20=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yomahub/liteflow/core/FlowExecutor.java | 1202 ++++++++--------- 1 file changed, 601 insertions(+), 601 deletions(-) diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java index 3afa01ffc..1648fa379 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java @@ -58,616 +58,616 @@ import java.util.stream.Collectors; */ public class FlowExecutor { - private static final LFLog LOG = LFLoggerManager.getLogger(FlowExecutor.class); - - private static final String PREFIX_FORMAT_CONFIG_REGEX = "el_xml:|el_json:|el_yml:"; - - private LiteflowConfig liteflowConfig; - - public FlowExecutor() { - // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 - FlowExecutorHolder.setHolder(this); - // 初始化DataBus - DataBus.init(); - } - - public FlowExecutor(LiteflowConfig liteflowConfig) { - this.liteflowConfig = liteflowConfig; - // 把liteFlowConfig设到LiteFlowGetter中去 - LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); - // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 - FlowExecutorHolder.setHolder(this); - if (!liteflowConfig.getParseMode().equals(ParseModeEnum.PARSE_ALL_ON_FIRST_EXEC)) { - this.init(true); - } - // 初始化DataBus - DataBus.init(); - } - - /** - * FlowExecutor的初始化化方式,主要用于parse规则文件 - * isStart表示是否是系统启动阶段,启动阶段要做额外的事情,而因为reload所调用的init就不用做 - */ - public void init(boolean isStart) { - if (ObjectUtil.isNull(liteflowConfig)) { - throw new ConfigErrorException("config error, please check liteflow config property"); - } - - // 在相应的环境下进行节点的初始化工作 - // 在spring体系下会获得spring扫描后的节点,接入元数据 - // 在非spring体系下是一个空实现,等于不做此步骤 - ContextCmpInitHolder.loadContextCmpInit().initCmp(); - - if (isStart){ - // 进行id生成器的初始化 - IdGeneratorHolder.init(); - } - - String ruleSource = liteflowConfig.getRuleSource(); - if (StrUtil.isBlank(ruleSource)) { - // 查看有没有Parser的SPI实现 - // 所有的Parser的SPI实现都是以custom形式放入的,且只支持xml形式 - ServiceLoader loader = ServiceLoader.load(ParserClassNameSpi.class); - Iterator it = loader.iterator(); - if (it.hasNext()) { - ParserClassNameSpi parserClassNameSpi = it.next(); - ruleSource = "el_xml:" + parserClassNameSpi.getSpiClassName(); - liteflowConfig.setRuleSource(ruleSource); - } - else { - // ruleSource为空,而且没有spi形式的扩展,那么说明真的没有ruleSource - // 这种情况有可能是基于代码动态构建的 - return; - } - } - - // 如果有前缀的,则不需要再进行分割了,说明是一个整体 - // 如果没有前缀,说明是本地文件,可能配置多个,所以需要分割 - List sourceRulePathList; - if (ReUtil.contains(PREFIX_FORMAT_CONFIG_REGEX, ruleSource)) { - sourceRulePathList = ListUtil.toList(ruleSource); - } - else { - String afterHandleRuleSource = ruleSource.replace(StrUtil.SPACE, StrUtil.EMPTY); - sourceRulePathList = ListUtil.toList(afterHandleRuleSource.split(",|;")); - } - - FlowParser parser = null; - Set parserNameSet = new HashSet<>(); - List rulePathList = new ArrayList<>(); - for (String path : sourceRulePathList) { - try { - // 查找对应的解析器 - parser = FlowParserProvider.lookup(path); - parserNameSet.add(parser.getClass().getName()); - // 替换掉前缀标识(如:xml:/json:),保留剩下的完整地址,并统一路径格式 - path = ReUtil.replaceAll(path, PREFIX_FORMAT_CONFIG_REGEX, "").replace("\\", "/"); - rulePathList.add(path); - - // 支持多类型的配置文件,分别解析 - if (BooleanUtil.isTrue(liteflowConfig.isSupportMultipleType())) { - // 解析文件 - parser.parseMain(ListUtil.toList(path)); - } - } - catch (CyclicDependencyException e) { - LOG.error(e.getMessage()); - throw e; - } - catch (Exception e) { - String errorMsg = StrUtil.format("init flow executor cause error for path {},reason:{}", path, - e.getMessage()); - LOG.error(e.getMessage(), e); - throw new FlowExecutorNotInitException(errorMsg); - } - } - - // 单类型的配置文件,需要一起解析 - if (BooleanUtil.isFalse(liteflowConfig.isSupportMultipleType())) { - // 检查Parser是否只有一个,因为多个不同的parser会造成子流程的混乱 - if (parserNameSet.size() > 1) { - String errorMsg = "cannot have multiple different parsers"; - LOG.error(errorMsg); - throw new MultipleParsersException(errorMsg); - } - - // 进行多个配置文件的一起解析 - try { - if (parser != null) { - // 解析文件 - parser.parseMain(rulePathList); - } - else { - throw new ConfigErrorException("parse error, please check liteflow config property"); - } - } - catch (CyclicDependencyException e) { - LOG.error(e.getMessage(), e); - LOG.error(e.getMessage()); - throw e; - } - catch (ChainDuplicateException e) { - LOG.error(e.getMessage(), e); - throw e; - } - catch (RouteELInvalidException e) { - LOG.error(e.getMessage(), e); - throw e; - } - catch (Exception e) { - String errorMsg = StrUtil.format("init flow executor cause error for path {},reason: {}", rulePathList, - e.getMessage()); - LOG.error(e.getMessage(), e); - throw new FlowExecutorNotInitException(errorMsg); - } - } - - // 如果是ruleSource方式的,最后判断下有没有解析出来,如果没有解析出来则报错 - if (StrUtil.isBlank(liteflowConfig.getRuleSourceExtData()) - && MapUtil.isEmpty(liteflowConfig.getRuleSourceExtDataMap())) { - if (FlowBus.getChainMap().isEmpty()) { - String errMsg = StrUtil.format("no valid rule config found in rule path [{}]", - liteflowConfig.getRuleSource()); - throw new ConfigErrorException(errMsg); - } - } - - // 执行钩子 - if (isStart) { - FlowInitHook.executeHook(); - } - - // 文件监听 - if (isStart && liteflowConfig.getEnableMonitorFile()) { - try { - addMonitorFilePaths(rulePathList); - MonitorFile.getInstance().create(); - } - catch (Exception e) { - String errMsg = StrUtil.format("file monitor init error for path:{}", rulePathList); - throw new MonitorFileInitErrorException(errMsg); - } - - } - } - - // 此方法就是从原有的配置源主动拉取新的进行刷新 - // 和FlowBus.refreshFlowMetaData的区别就是一个为主动拉取,一个为被动监听到新的内容进行刷新 - public void reloadRule() { - long start = System.currentTimeMillis(); - init(false); - LOG.info("reload rules takes {}ms", System.currentTimeMillis() - start); - } - - // 单独调用某一个node - @Deprecated - public void invoke(String nodeId, Integer slotIndex) throws Exception { - Node node = FlowBus.getNode(nodeId); - node.execute(slotIndex); - } - - // 调用一个流程并返回LiteflowResponse,上下文为默认的DefaultContext,初始参数为null - public LiteflowResponse execute2Resp(String chainId) { - return this.execute2Resp(chainId, null, DefaultContext.class); - } - - // 调用一个流程并返回LiteflowResponse,上下文为默认的DefaultContext - public LiteflowResponse execute2Resp(String chainId, Object param) { - return this.execute2Resp(chainId, param, DefaultContext.class); - } - - // 调用一个流程并返回LiteflowResponse,允许多上下文的传入 - public LiteflowResponse execute2Resp(String chainId, Object param, Class... contextBeanClazzArray) { - return this.execute2Resp(chainId, param, null, contextBeanClazzArray, null); - } - - /** - * 直接执行 EL 表达式 - * - * @param elStr EL 表达式 - * @return LiteflowResponse - * @throws ELParseException - */ - public LiteflowResponse execute2RespWithEL(String elStr) throws Exception { - return this.execute2RespWithEL(elStr, null, null, DefaultContext.class); - } - - /** - * 直接执行 EL 表达式 - * - * @param elStr EL 表达式 - * @param param 入参 - * @return LiteflowResponse - * @throws ELParseException - */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param) throws Exception { - return this.execute2RespWithEL(elStr, param, null, DefaultContext.class); - } - - /** - * 直接执行 EL 表达式 - * - * @param elStr EL 表达式 - * @param param 入参 - * @param requestId 请求 ID - * @param contextBeanClazzArray 上下文 Class - * @return LiteflowResponse - * @throws ELParseException - */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class... contextBeanClazzArray) throws Exception { - return this.execute2RespWithEL(elStr, param, requestId, contextBeanClazzArray, null); - } - - /** - * 直接执行 EL 表达式 - * - * @param elStr EL 表达式 - * @param param 入参 - * @param requestId 请求 ID - * @param contextBeanArray 上下文对象 - * @return LiteflowResponse - * @throws ELParseException - */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Object... contextBeanArray) throws Exception { - return this.execute2RespWithEL(elStr, param, requestId, null, contextBeanArray); - } - - /** - * 直接执行 EL 表达式 - * - * @param elStr EL 表达式 - * @param param 入参 - * @param requestId 请求 ID - * @param contextBeanClazzArray 上下文 Class 数组 - * @param contextBeanArray 上下文对象数组 - * @return LiteflowResponse - * @throws ELParseException - */ - private LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) throws Exception { - // 规范化 el 表达式 - String normalizedEl = ElRegexUtil.normalize(elStr); - - // 校验 EL 是否正常 - ValidationResp validationResp = LiteFlowChainELBuilder.validateWithEx(normalizedEl); - - if (!validationResp.isSuccess()) { - // 实际封装的是 ELParseException 类型 - throw validationResp.getCause(); - } - - // 计算 EL MD5 值,并检查对应的 chain 是否已加载到内存中 - String elMd5 = MD5.create().digestHex(normalizedEl); - - String chainId; - - if (StrUtil.isEmpty(chainId = FlowBus.getChainIdByElMd5(elMd5))) { - // 调用表达式构造 chain,并且返回 UUID 作为 chainId - chainId = IdUtil.fastSimpleUUID(); - LiteFlowChainELBuilder.createChain() - .setChainId(chainId) - .setEL(normalizedEl) - .setElMd5(elMd5) - .build(); - } - - return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, contextBeanArray); - } - - public List executeRouteChain(Object param, Class... contextBeanClazzArray){ - return this.executeWithRoute(null, param, null, contextBeanClazzArray, null); - } - - public List executeRouteChain(String namespace, Object param, Class... contextBeanClazzArray){ - return this.executeWithRoute(namespace, param, null, contextBeanClazzArray, null); - } - - public LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray) { - return this.execute2Resp(chainId, param, null, null, contextBeanArray); - } - - public List executeRouteChain(Object param, Object... contextBeanArray){ - return this.executeWithRoute(null, param, null, null, contextBeanArray); - } - - public List executeRouteChain(String namespace, Object param, Object... contextBeanArray){ - return this.executeWithRoute(namespace, param, null, null, contextBeanArray); - } - - public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) { - return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, null); - } - - public List executeRouteChainWithRid(Object param, String requestId, Class... contextBeanClazzArray) { - return this.executeWithRoute(null, param, requestId, contextBeanClazzArray, null); - } - - public List executeRouteChainWithRid(String namespace, Object param, String requestId, Class... contextBeanClazzArray) { - return this.executeWithRoute(namespace, param, requestId, contextBeanClazzArray, null); - } - - public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) { - return this.execute2Resp(chainId, param, requestId, null, contextBeanArray); - } - - public List executeRouteChainWithRid(Object param, String requestId, Object... contextBeanArray) { - return this.executeWithRoute(null, param, requestId, null, contextBeanArray); - } - - public List executeRouteChainWithRid(String namespace, Object param, String requestId, Object... contextBeanArray) { - return this.executeWithRoute(namespace, param, requestId, null, contextBeanArray); - } - - // 调用一个流程并返回Future,允许多上下文的传入 - public Future execute2Future(String chainId, Object param, Class... contextBeanClazzArray) { - return ExecutorHelper.loadInstance() - .buildMainExecutor(liteflowConfig.getMainExecutorClass()) - .submit(() -> FlowExecutorHolder.loadInstance().execute2Resp(chainId, param, contextBeanClazzArray)); - } - - public Future execute2Future(String chainId, Object param, Object... contextBeanArray) { - return ExecutorHelper.loadInstance() - .buildMainExecutor(liteflowConfig.getMainExecutorClass()) - .submit(() -> FlowExecutorHolder.loadInstance().execute2Resp(chainId, param, contextBeanArray)); - } - - public Future execute2FutureWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) { - return ExecutorHelper.loadInstance() - .buildMainExecutor(liteflowConfig.getMainExecutorClass()) - .submit(() -> FlowExecutorHolder.loadInstance().execute2RespWithRid(chainId, param, requestId, contextBeanClazzArray)); - } - - public Future execute2FutureWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) { - return ExecutorHelper.loadInstance() - .buildMainExecutor(liteflowConfig.getMainExecutorClass()) - .submit(() -> FlowExecutorHolder.loadInstance().execute2RespWithRid(chainId, param, requestId, contextBeanArray)); - } - - // 调用一个流程,返回默认的上下文,适用于简单的调用 - @Deprecated - public DefaultContext execute(String chainId, Object param) throws Exception { - LiteflowResponse response = this.execute2Resp(chainId, param, DefaultContext.class); - if (!response.isSuccess()) { - throw response.getCause(); - } - else { - return response.getFirstContextBean(); - } - } - - private LiteflowResponse execute2Resp(String chainId, Object param, String requestId, Class[] contextBeanClazzArray, - Object[] contextBeanArray) { - Slot slot = doExecute(chainId, param, requestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.BODY); - return LiteflowResponse.newMainResponse(slot); - } - - private List executeWithRoute(String namespace, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray){ - List slotList = doExecuteWithRoute(namespace, param, requestId, contextBeanClazzArray, contextBeanArray); - return slotList.stream().map(LiteflowResponse::newMainResponse).collect(Collectors.toList()); - } - - private Slot doExecute(String chainId, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray, - ChainExecuteModeEnum chainExecuteModeEnum) { - if (FlowBus.needInit()) { - init(true); - } - - Integer slotIndex; - // 这里可以根据class分配,也可以根据bean去分配 - if (ArrayUtil.isNotEmpty(contextBeanClazzArray)) { - slotIndex = DataBus.offerSlotByClass(ListUtil.toList(contextBeanClazzArray)); - } - else { - slotIndex = DataBus.offerSlotByBean(ListUtil.toList(contextBeanArray)); - } - - if (slotIndex == -1) { - throw new NoAvailableSlotException("there is no available slot"); - } - - Slot slot = DataBus.getSlot(slotIndex); - if (ObjectUtil.isNull(slot)) { - throw new NoAvailableSlotException(StrUtil.format("the slot[{}] is not exist", slotIndex)); - } - - // 如果有FlowExecute生命周期实现,则执行 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList())){ - LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList().forEach( - postProcessFlowExecuteLifeCycle -> postProcessFlowExecuteLifeCycle.postProcessBeforeFlowExecute(chainId, slot) - ); - } - - //如果传入了用户的RequestId,则用这个请求Id,如果没传入,则进行生成 - if (StrUtil.isNotBlank(requestId)){ - slot.putRequestId(requestId); - LFLoggerManager.setRequestId(requestId); - }else if(StrUtil.isBlank(slot.getRequestId())){ - slot.generateRequestId(); - LFLoggerManager.setRequestId(slot.getRequestId()); - LOG.info("requestId has generated"); - } - - LOG.info("slot[{}] offered", slotIndex); - - if (ObjectUtil.isNotNull(param)) { - slot.setRequestData(param); - } - - Chain chain = null; - try { - chain = FlowBus.getChain(chainId); - - if (ObjectUtil.isNull(chain)) { - String errorMsg = StrUtil.format("couldn't find chain with the id[{}]", chainId); - throw new ChainNotFoundException(errorMsg); - } - // 根据chain执行模式执行chain - if (chainExecuteModeEnum.equals(ChainExecuteModeEnum.BODY)){ - chain.execute(slotIndex); - }else if(chainExecuteModeEnum.equals(ChainExecuteModeEnum.ROUTE)){ - chain.executeRoute(slotIndex); - }else{ - throw new LiteFlowException("chain execute mode error"); - } - } - catch (ChainEndException e) { - if (ObjectUtil.isNotNull(chain)) { - String warnMsg = StrUtil.format("chain[{}] execute end on slot[{}]", chain.getChainId(), slotIndex); - LOG.warn(warnMsg); - } - } - catch (Exception e) { - if (ObjectUtil.isNotNull(chain)) { - String errMsg = StrUtil.format("chain[{}] execute error on slot[{}]", chain.getChainId(), slotIndex); - LOG.error(errMsg, e); - } - else { - LOG.error(e.getMessage(), e); - } - - slot.setException(e); - Deque executeSteps = slot.getExecuteSteps(); - try { - Iterator cmpStepIterator = executeSteps.descendingIterator(); - while(cmpStepIterator.hasNext()) { - CmpStep cmpStep = cmpStepIterator.next(); - if(cmpStep.getInstance().isRollback()) { - Rollbackable rollbackItem = cmpStep.getRefNode(); - rollbackItem.rollback(slotIndex); - } - } - } catch (Exception exception) { - LOG.error(exception.getMessage()); - } - finally { - slot.printRollbackStep(); - } - } - finally { - slot.printStep(); - DataBus.releaseSlot(slotIndex); - LFLoggerManager.removeRequestId(); - - // 如果有FlowExecute生命周期实现,则执行 - if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList())){ - LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList().forEach( - postProcessFlowExecuteLifeCycle -> postProcessFlowExecuteLifeCycle.postProcessAfterFlowExecute(chainId, slot) - ); - } - } - return slot; - } - - public LiteflowConfig getLiteflowConfig() { - return liteflowConfig; - } - - public void setLiteflowConfig(LiteflowConfig liteflowConfig) { - this.liteflowConfig = liteflowConfig; - // 把liteFlowConfig设到LiteFlowGetter中去 - LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); - } - - /** - * 添加监听文件路径 - * @param pathList 文件路径 - */ - private void addMonitorFilePaths(List pathList) throws Exception { - // 添加规则文件监听 - List fileAbsolutePath = PathContentParserHolder.loadContextAware().getFileAbsolutePath(pathList); - MonitorFile.getInstance().addMonitorFilePaths(fileAbsolutePath); - } - - private List doExecuteWithRoute(String namespace, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray){ - if (FlowBus.needInit()) { - init(true); - } - - if (StrUtil.isBlank(namespace)){ - namespace = ChainConstant.DEFAULT_NAMESPACE; - } - - String finalNamespace = namespace; - List routeChainList = FlowBus.getChainMap().values().stream() - .filter(chain -> chain.getNamespace().equals(finalNamespace)) - .filter(chain -> chain.getRouteItem() != null).collect(Collectors.toList()); - - if (CollUtil.isEmpty(routeChainList)){ - String errorMsg = StrUtil.format("no route found for namespace[{}]", finalNamespace); - throw new RouteChainNotFoundException(errorMsg); - } - - String finalRequestId; - if (StrUtil.isBlank(requestId)){ - finalRequestId = IdGeneratorHolder.getInstance().generate(); - }else{ - finalRequestId = requestId; - } - - // 异步执行route el - List routeTupleList = new ArrayList<>(); - for (Chain routeChain : routeChainList){ - CompletableFuture f = CompletableFuture.supplyAsync( + private static final LFLog LOG = LFLoggerManager.getLogger(FlowExecutor.class); + + private static final String PREFIX_FORMAT_CONFIG_REGEX = "el_xml:|el_json:|el_yml:"; + + private LiteflowConfig liteflowConfig; + + public FlowExecutor() { + // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 + FlowExecutorHolder.setHolder(this); + // 初始化DataBus + DataBus.init(); + } + + public FlowExecutor(LiteflowConfig liteflowConfig) { + this.liteflowConfig = liteflowConfig; + // 把liteFlowConfig设到LiteFlowGetter中去 + LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); + // 设置FlowExecutor的Holder,虽然大部分地方都可以通过Spring上下文获取到,但放入Holder,还是为了某些地方能方便的取到 + FlowExecutorHolder.setHolder(this); + if (!liteflowConfig.getParseMode().equals(ParseModeEnum.PARSE_ALL_ON_FIRST_EXEC)) { + this.init(true); + } + // 初始化DataBus + DataBus.init(); + } + + /** + * FlowExecutor的初始化化方式,主要用于parse规则文件 + * isStart表示是否是系统启动阶段,启动阶段要做额外的事情,而因为reload所调用的init就不用做 + */ + public void init(boolean isStart) { + if (ObjectUtil.isNull(liteflowConfig)) { + throw new ConfigErrorException("config error, please check liteflow config property"); + } + + // 在相应的环境下进行节点的初始化工作 + // 在spring体系下会获得spring扫描后的节点,接入元数据 + // 在非spring体系下是一个空实现,等于不做此步骤 + ContextCmpInitHolder.loadContextCmpInit().initCmp(); + + if (isStart){ + // 进行id生成器的初始化 + IdGeneratorHolder.init(); + } + + String ruleSource = liteflowConfig.getRuleSource(); + if (StrUtil.isBlank(ruleSource)) { + // 查看有没有Parser的SPI实现 + // 所有的Parser的SPI实现都是以custom形式放入的,且只支持xml形式 + ServiceLoader loader = ServiceLoader.load(ParserClassNameSpi.class); + Iterator it = loader.iterator(); + if (it.hasNext()) { + ParserClassNameSpi parserClassNameSpi = it.next(); + ruleSource = "el_xml:" + parserClassNameSpi.getSpiClassName(); + liteflowConfig.setRuleSource(ruleSource); + } + else { + // ruleSource为空,而且没有spi形式的扩展,那么说明真的没有ruleSource + // 这种情况有可能是基于代码动态构建的 + return; + } + } + + // 如果有前缀的,则不需要再进行分割了,说明是一个整体 + // 如果没有前缀,说明是本地文件,可能配置多个,所以需要分割 + List sourceRulePathList; + if (ReUtil.contains(PREFIX_FORMAT_CONFIG_REGEX, ruleSource)) { + sourceRulePathList = ListUtil.toList(ruleSource); + } + else { + String afterHandleRuleSource = ruleSource.replace(StrUtil.SPACE, StrUtil.EMPTY); + sourceRulePathList = ListUtil.toList(afterHandleRuleSource.split(",|;")); + } + + FlowParser parser = null; + Set parserNameSet = new HashSet<>(); + List rulePathList = new ArrayList<>(); + for (String path : sourceRulePathList) { + try { + // 查找对应的解析器 + parser = FlowParserProvider.lookup(path); + parserNameSet.add(parser.getClass().getName()); + // 替换掉前缀标识(如:xml:/json:),保留剩下的完整地址,并统一路径格式 + path = ReUtil.replaceAll(path, PREFIX_FORMAT_CONFIG_REGEX, "").replace("\\", "/"); + rulePathList.add(path); + + // 支持多类型的配置文件,分别解析 + if (BooleanUtil.isTrue(liteflowConfig.isSupportMultipleType())) { + // 解析文件 + parser.parseMain(ListUtil.toList(path)); + } + } + catch (CyclicDependencyException e) { + LOG.error(e.getMessage()); + throw e; + } + catch (Exception e) { + String errorMsg = StrUtil.format("init flow executor cause error for path {},reason:{}", path, + e.getMessage()); + LOG.error(e.getMessage(), e); + throw new FlowExecutorNotInitException(errorMsg); + } + } + + // 单类型的配置文件,需要一起解析 + if (BooleanUtil.isFalse(liteflowConfig.isSupportMultipleType())) { + // 检查Parser是否只有一个,因为多个不同的parser会造成子流程的混乱 + if (parserNameSet.size() > 1) { + String errorMsg = "cannot have multiple different parsers"; + LOG.error(errorMsg); + throw new MultipleParsersException(errorMsg); + } + + // 进行多个配置文件的一起解析 + try { + if (parser != null) { + // 解析文件 + parser.parseMain(rulePathList); + } + else { + throw new ConfigErrorException("parse error, please check liteflow config property"); + } + } + catch (CyclicDependencyException e) { + LOG.error(e.getMessage(), e); + LOG.error(e.getMessage()); + throw e; + } + catch (ChainDuplicateException e) { + LOG.error(e.getMessage(), e); + throw e; + } + catch (RouteELInvalidException e) { + LOG.error(e.getMessage(), e); + throw e; + } + catch (Exception e) { + String errorMsg = StrUtil.format("init flow executor cause error for path {},reason: {}", rulePathList, + e.getMessage()); + LOG.error(e.getMessage(), e); + throw new FlowExecutorNotInitException(errorMsg); + } + } + + // 如果是ruleSource方式的,最后判断下有没有解析出来,如果没有解析出来则报错 + if (StrUtil.isBlank(liteflowConfig.getRuleSourceExtData()) + && MapUtil.isEmpty(liteflowConfig.getRuleSourceExtDataMap())) { + if (FlowBus.getChainMap().isEmpty()) { + String errMsg = StrUtil.format("no valid rule config found in rule path [{}]", + liteflowConfig.getRuleSource()); + throw new ConfigErrorException(errMsg); + } + } + + // 执行钩子 + if (isStart) { + FlowInitHook.executeHook(); + } + + // 文件监听 + if (isStart && liteflowConfig.getEnableMonitorFile()) { + try { + addMonitorFilePaths(rulePathList); + MonitorFile.getInstance().create(); + } + catch (Exception e) { + String errMsg = StrUtil.format("file monitor init error for path:{}", rulePathList); + throw new MonitorFileInitErrorException(errMsg); + } + + } + } + + // 此方法就是从原有的配置源主动拉取新的进行刷新 + // 和FlowBus.refreshFlowMetaData的区别就是一个为主动拉取,一个为被动监听到新的内容进行刷新 + public void reloadRule() { + long start = System.currentTimeMillis(); + init(false); + LOG.info("reload rules takes {}ms", System.currentTimeMillis() - start); + } + + // 单独调用某一个node + @Deprecated + public void invoke(String nodeId, Integer slotIndex) throws Exception { + Node node = FlowBus.getNode(nodeId); + node.execute(slotIndex); + } + + // 调用一个流程并返回LiteflowResponse,上下文为默认的DefaultContext,初始参数为null + public LiteflowResponse execute2Resp(String chainId) { + return this.execute2Resp(chainId, null, DefaultContext.class); + } + + // 调用一个流程并返回LiteflowResponse,上下文为默认的DefaultContext + public LiteflowResponse execute2Resp(String chainId, Object param) { + return this.execute2Resp(chainId, param, DefaultContext.class); + } + + // 调用一个流程并返回LiteflowResponse,允许多上下文的传入 + public LiteflowResponse execute2Resp(String chainId, Object param, Class... contextBeanClazzArray) { + return this.execute2Resp(chainId, param, null, contextBeanClazzArray, null); + } + + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @return LiteflowResponse + * @throws ELParseException + */ + public LiteflowResponse execute2RespWithEL(String elStr) throws Exception { + return this.execute2RespWithEL(elStr, null, null, DefaultContext.class); + } + + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @return LiteflowResponse + * @throws ELParseException + */ + public LiteflowResponse execute2RespWithEL(String elStr, Object param) throws Exception { + return this.execute2RespWithEL(elStr, param, null, DefaultContext.class); + } + + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @param requestId 请求 ID + * @param contextBeanClazzArray 上下文 Class + * @return LiteflowResponse + * @throws ELParseException + */ + public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class... contextBeanClazzArray) throws Exception { + return this.execute2RespWithEL(elStr, param, requestId, contextBeanClazzArray, null); + } + + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @param requestId 请求 ID + * @param contextBeanArray 上下文对象 + * @return LiteflowResponse + * @throws ELParseException + */ + public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Object... contextBeanArray) throws Exception { + return this.execute2RespWithEL(elStr, param, requestId, null, contextBeanArray); + } + + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @param requestId 请求 ID + * @param contextBeanClazzArray 上下文 Class 数组 + * @param contextBeanArray 上下文对象数组 + * @return LiteflowResponse + * @throws ELParseException + */ + private LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) throws Exception { + // 规范化 el 表达式 + String normalizedEl = ElRegexUtil.normalize(elStr); + + // 校验 EL 是否正常 + ValidationResp validationResp = LiteFlowChainELBuilder.validateWithEx(normalizedEl); + + if (!validationResp.isSuccess()) { + // 实际封装的是 ELParseException 类型 + throw validationResp.getCause(); + } + + // 计算 EL MD5 值,并检查对应的 chain 是否已加载到内存中 + String elMd5 = MD5.create().digestHex(normalizedEl); + + String chainId; + + if (StrUtil.isEmpty(chainId = FlowBus.getChainIdByElMd5(elMd5))) { + // 调用表达式构造 chain,并且返回 UUID 作为 chainId + chainId = IdUtil.fastSimpleUUID(); + LiteFlowChainELBuilder.createChain() + .setChainId(chainId) + .setEL(normalizedEl) + .setElMd5(elMd5) + .build(); + } + + return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, contextBeanArray); + } + + public List executeRouteChain(Object param, Class... contextBeanClazzArray){ + return this.executeWithRoute(null, param, null, contextBeanClazzArray, null); + } + + public List executeRouteChain(String namespace, Object param, Class... contextBeanClazzArray){ + return this.executeWithRoute(namespace, param, null, contextBeanClazzArray, null); + } + + public LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray) { + return this.execute2Resp(chainId, param, null, null, contextBeanArray); + } + + public List executeRouteChain(Object param, Object... contextBeanArray){ + return this.executeWithRoute(null, param, null, null, contextBeanArray); + } + + public List executeRouteChain(String namespace, Object param, Object... contextBeanArray){ + return this.executeWithRoute(namespace, param, null, null, contextBeanArray); + } + + public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) { + return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, null); + } + + public List executeRouteChainWithRid(Object param, String requestId, Class... contextBeanClazzArray) { + return this.executeWithRoute(null, param, requestId, contextBeanClazzArray, null); + } + + public List executeRouteChainWithRid(String namespace, Object param, String requestId, Class... contextBeanClazzArray) { + return this.executeWithRoute(namespace, param, requestId, contextBeanClazzArray, null); + } + + public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) { + return this.execute2Resp(chainId, param, requestId, null, contextBeanArray); + } + + public List executeRouteChainWithRid(Object param, String requestId, Object... contextBeanArray) { + return this.executeWithRoute(null, param, requestId, null, contextBeanArray); + } + + public List executeRouteChainWithRid(String namespace, Object param, String requestId, Object... contextBeanArray) { + return this.executeWithRoute(namespace, param, requestId, null, contextBeanArray); + } + + // 调用一个流程并返回Future,允许多上下文的传入 + public Future execute2Future(String chainId, Object param, Class... contextBeanClazzArray) { + return ExecutorHelper.loadInstance() + .buildMainExecutor(liteflowConfig.getMainExecutorClass()) + .submit(() -> FlowExecutorHolder.loadInstance().execute2Resp(chainId, param, contextBeanClazzArray)); + } + + public Future execute2Future(String chainId, Object param, Object... contextBeanArray) { + return ExecutorHelper.loadInstance() + .buildMainExecutor(liteflowConfig.getMainExecutorClass()) + .submit(() -> FlowExecutorHolder.loadInstance().execute2Resp(chainId, param, contextBeanArray)); + } + + public Future execute2FutureWithRid(String chainId, Object param, String requestId, Class... contextBeanClazzArray) { + return ExecutorHelper.loadInstance() + .buildMainExecutor(liteflowConfig.getMainExecutorClass()) + .submit(() -> FlowExecutorHolder.loadInstance().execute2RespWithRid(chainId, param, requestId, contextBeanClazzArray)); + } + + public Future execute2FutureWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) { + return ExecutorHelper.loadInstance() + .buildMainExecutor(liteflowConfig.getMainExecutorClass()) + .submit(() -> FlowExecutorHolder.loadInstance().execute2RespWithRid(chainId, param, requestId, contextBeanArray)); + } + + // 调用一个流程,返回默认的上下文,适用于简单的调用 + @Deprecated + public DefaultContext execute(String chainId, Object param) throws Exception { + LiteflowResponse response = this.execute2Resp(chainId, param, DefaultContext.class); + if (!response.isSuccess()) { + throw response.getCause(); + } + else { + return response.getFirstContextBean(); + } + } + + private LiteflowResponse execute2Resp(String chainId, Object param, String requestId, Class[] contextBeanClazzArray, + Object[] contextBeanArray) { + Slot slot = doExecute(chainId, param, requestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.BODY); + return LiteflowResponse.newMainResponse(slot); + } + + private List executeWithRoute(String namespace, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray){ + List slotList = doExecuteWithRoute(namespace, param, requestId, contextBeanClazzArray, contextBeanArray); + return slotList.stream().map(LiteflowResponse::newMainResponse).collect(Collectors.toList()); + } + + private Slot doExecute(String chainId, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray, + ChainExecuteModeEnum chainExecuteModeEnum) { + if (FlowBus.needInit()) { + init(true); + } + + Integer slotIndex; + // 这里可以根据class分配,也可以根据bean去分配 + if (ArrayUtil.isNotEmpty(contextBeanClazzArray)) { + slotIndex = DataBus.offerSlotByClass(ListUtil.toList(contextBeanClazzArray)); + } + else { + slotIndex = DataBus.offerSlotByBean(ListUtil.toList(contextBeanArray)); + } + + if (slotIndex == -1) { + throw new NoAvailableSlotException("there is no available slot"); + } + + Slot slot = DataBus.getSlot(slotIndex); + if (ObjectUtil.isNull(slot)) { + throw new NoAvailableSlotException(StrUtil.format("the slot[{}] is not exist", slotIndex)); + } + + // 如果有FlowExecute生命周期实现,则执行 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList())){ + LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList().forEach( + postProcessFlowExecuteLifeCycle -> postProcessFlowExecuteLifeCycle.postProcessBeforeFlowExecute(chainId, slot) + ); + } + + //如果传入了用户的RequestId,则用这个请求Id,如果没传入,则进行生成 + if (StrUtil.isNotBlank(requestId)){ + slot.putRequestId(requestId); + LFLoggerManager.setRequestId(requestId); + }else if(StrUtil.isBlank(slot.getRequestId())){ + slot.generateRequestId(); + LFLoggerManager.setRequestId(slot.getRequestId()); + LOG.info("requestId has generated"); + } + + LOG.info("slot[{}] offered", slotIndex); + + if (ObjectUtil.isNotNull(param)) { + slot.setRequestData(param); + } + + Chain chain = null; + try { + chain = FlowBus.getChain(chainId); + + if (ObjectUtil.isNull(chain)) { + String errorMsg = StrUtil.format("couldn't find chain with the id[{}]", chainId); + throw new ChainNotFoundException(errorMsg); + } + // 根据chain执行模式执行chain + if (chainExecuteModeEnum.equals(ChainExecuteModeEnum.BODY)){ + chain.execute(slotIndex); + }else if(chainExecuteModeEnum.equals(ChainExecuteModeEnum.ROUTE)){ + chain.executeRoute(slotIndex); + }else{ + throw new LiteFlowException("chain execute mode error"); + } + } + catch (ChainEndException e) { + if (ObjectUtil.isNotNull(chain)) { + String warnMsg = StrUtil.format("chain[{}] execute end on slot[{}]", chain.getChainId(), slotIndex); + LOG.warn(warnMsg); + } + } + catch (Exception e) { + if (ObjectUtil.isNotNull(chain)) { + String errMsg = StrUtil.format("chain[{}] execute error on slot[{}]", chain.getChainId(), slotIndex); + LOG.error(errMsg, e); + } + else { + LOG.error(e.getMessage(), e); + } + + slot.setException(e); + Deque executeSteps = slot.getExecuteSteps(); + try { + Iterator cmpStepIterator = executeSteps.descendingIterator(); + while(cmpStepIterator.hasNext()) { + CmpStep cmpStep = cmpStepIterator.next(); + if(cmpStep.getInstance().isRollback()) { + Rollbackable rollbackItem = cmpStep.getRefNode(); + rollbackItem.rollback(slotIndex); + } + } + } catch (Exception exception) { + LOG.error(exception.getMessage()); + } + finally { + slot.printRollbackStep(); + } + } + finally { + slot.printStep(); + DataBus.releaseSlot(slotIndex); + LFLoggerManager.removeRequestId(); + + // 如果有FlowExecute生命周期实现,则执行 + if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList())){ + LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList().forEach( + postProcessFlowExecuteLifeCycle -> postProcessFlowExecuteLifeCycle.postProcessAfterFlowExecute(chainId, slot) + ); + } + } + return slot; + } + + public LiteflowConfig getLiteflowConfig() { + return liteflowConfig; + } + + public void setLiteflowConfig(LiteflowConfig liteflowConfig) { + this.liteflowConfig = liteflowConfig; + // 把liteFlowConfig设到LiteFlowGetter中去 + LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); + } + + /** + * 添加监听文件路径 + * @param pathList 文件路径 + */ + private void addMonitorFilePaths(List pathList) throws Exception { + // 添加规则文件监听 + List fileAbsolutePath = PathContentParserHolder.loadContextAware().getFileAbsolutePath(pathList); + MonitorFile.getInstance().addMonitorFilePaths(fileAbsolutePath); + } + + private List doExecuteWithRoute(String namespace, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray){ + if (FlowBus.needInit()) { + init(true); + } + + if (StrUtil.isBlank(namespace)){ + namespace = ChainConstant.DEFAULT_NAMESPACE; + } + + String finalNamespace = namespace; + List routeChainList = FlowBus.getChainMap().values().stream() + .filter(chain -> chain.getNamespace().equals(finalNamespace)) + .filter(chain -> chain.getRouteItem() != null).collect(Collectors.toList()); + + if (CollUtil.isEmpty(routeChainList)){ + String errorMsg = StrUtil.format("no route found for namespace[{}]", finalNamespace); + throw new RouteChainNotFoundException(errorMsg); + } + + String finalRequestId; + if (StrUtil.isBlank(requestId)){ + finalRequestId = IdGeneratorHolder.getInstance().generate(); + }else{ + finalRequestId = requestId; + } + + // 异步执行route el + List routeTupleList = new ArrayList<>(); + for (Chain routeChain : routeChainList){ + CompletableFuture f = CompletableFuture.supplyAsync( () -> doExecute(routeChain.getChainId(), param, finalRequestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.ROUTE), - ExecutorHelper.loadInstance().buildWhenExecutor() - ); + ExecutorHelper.loadInstance().buildWhenExecutor() + ); - routeTupleList.add(new Tuple(routeChain, f)); - } + routeTupleList.add(new Tuple(routeChain, f)); + } - CompletableFuture resultRouteCf = CompletableFuture.allOf(routeTupleList.stream().map( - (Function>) tuple -> tuple.get(1) - ).collect(Collectors.toList()).toArray(new CompletableFuture[] {})); - try{ - resultRouteCf.get(); - }catch (Exception e){ - throw new LiteFlowException("There is An error occurred while executing the route.", e); - } + CompletableFuture resultRouteCf = CompletableFuture.allOf(routeTupleList.stream().map( + (Function>) tuple -> tuple.get(1) + ).collect(Collectors.toList()).toArray(new CompletableFuture[] {})); + try{ + resultRouteCf.get(); + }catch (Exception e){ + throw new LiteFlowException("There is An error occurred while executing the route.", e); + } - // 把route执行为true都过滤出来 - List matchedRouteChainList = routeTupleList.stream().filter(tuple -> { - try{ - CompletableFuture f = tuple.get(1); - Slot slot = f.get(); - return BooleanUtil.isTrue(slot.getRouteResult()); - }catch (Exception e){ - return false; - } - }).map( - (Function) tuple -> tuple.get(0) - ).collect(Collectors.toList()); + // 把route执行为true都过滤出来 + List matchedRouteChainList = routeTupleList.stream().filter(tuple -> { + try{ + CompletableFuture f = tuple.get(1); + Slot slot = f.get(); + return BooleanUtil.isTrue(slot.getRouteResult()); + }catch (Exception e){ + return false; + } + }).map( + (Function) tuple -> tuple.get(0) + ).collect(Collectors.toList()); - if (CollUtil.isEmpty(matchedRouteChainList)){ - throw new NoMatchedRouteChainException("there is no matched route chain"); - } + if (CollUtil.isEmpty(matchedRouteChainList)){ + throw new NoMatchedRouteChainException("there is no matched route chain"); + } - // 异步分别执行这些chain - List> executeChainCfList = new ArrayList<>(); - for (Chain chain : matchedRouteChainList){ - CompletableFuture cf = CompletableFuture.supplyAsync( - () -> doExecute(chain.getChainId(), param, finalRequestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.BODY), - ExecutorHelper.loadInstance().buildWhenExecutor() - ); - executeChainCfList.add(cf); - } + // 异步分别执行这些chain + List> executeChainCfList = new ArrayList<>(); + for (Chain chain : matchedRouteChainList){ + CompletableFuture cf = CompletableFuture.supplyAsync( + () -> doExecute(chain.getChainId(), param, finalRequestId, contextBeanClazzArray, contextBeanArray, ChainExecuteModeEnum.BODY), + ExecutorHelper.loadInstance().buildWhenExecutor() + ); + executeChainCfList.add(cf); + } - CompletableFuture resultChainCf = CompletableFuture.allOf(executeChainCfList.toArray(new CompletableFuture[] {})); - try{ - resultChainCf.get(); - }catch (Exception e){ - throw new LiteFlowException("There is An error occurred while executing the matched chain.", e); - } + CompletableFuture resultChainCf = CompletableFuture.allOf(executeChainCfList.toArray(new CompletableFuture[] {})); + try{ + resultChainCf.get(); + }catch (Exception e){ + throw new LiteFlowException("There is An error occurred while executing the matched chain.", e); + } - List resultSlotList = executeChainCfList.stream().map(slotCompletableFuture -> { - try{ - return slotCompletableFuture.get(); - }catch (Exception e){ - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); + List resultSlotList = executeChainCfList.stream().map(slotCompletableFuture -> { + try{ + return slotCompletableFuture.get(); + }catch (Exception e){ + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); - LOG.info("chain namespace:[{}], total size:[{}], matched size:[{}]", namespace, routeChainList.size(), resultSlotList.size()); + LOG.info("chain namespace:[{}], total size:[{}], matched size:[{}]", namespace, routeChainList.size(), resultSlotList.size()); - return resultSlotList; - } + return resultSlotList; + } } \ No newline at end of file From 375e916e3442a52965d84da59726a199fe013a83 Mon Sep 17 00:00:00 2001 From: "everywhere.z" Date: Mon, 7 Jul 2025 16:39:40 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yomahub/liteflow/flow/FlowBus.java | 13 +++++--- .../yomahub/liteflow/flow/element/Node.java | 2 +- .../ScriptJavaxProParseOneModeTest.java | 33 +++++++++++++++++++ .../parseOneMode/application.properties | 2 ++ .../src/test/resources/parseOneMode/flow.xml | 32 ++++++++++++++++++ 5 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/java/com/yomahub/liteflow/test/script/javapro/parseOneMode/ScriptJavaxProParseOneModeTest.java create mode 100644 liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/resources/parseOneMode/application.properties create mode 100644 liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/resources/parseOneMode/flow.xml 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 b870ae257..b8bcf5686 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 @@ -201,7 +201,7 @@ public class FlowBus { } Node node = new Node(nodeId, name, nodeType, script, language); - nodeMap.put(nodeId, node); + put2NodeMap(nodeId, node); } else { addScriptNodeAndCompile(nodeId, name, nodeType, script, language); } @@ -215,11 +215,9 @@ public class FlowBus { * @param type type * @param script script content * @param language language - * @return NodeComponent instance */ - public static NodeComponent addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script, String 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); - return nodeMap.get(nodeId).getInstance(); } private static List getNodeComponentList(String nodeId, String name, NodeTypeEnum type, Class cmpClazz) throws Exception { @@ -257,7 +255,7 @@ public class FlowBus { return cmpInstanceList; } - public static void compileNode(Node node) { + public static void compileScriptNode(Node node) { String nodeId = node.getId(), name = node.getName(), script = node.getScript(), language = node.getLanguage(); NodeTypeEnum type = node.getType(); try { @@ -292,8 +290,13 @@ public class FlowBus { 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); // 初始化Node,把component放到Node里去 diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Node.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Node.java index 90f7800e4..11d082ecc 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Node.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Node.java @@ -173,7 +173,7 @@ public class Node implements Executable, Cloneable, Rollbackable{ if (!this.isCompiled()) { synchronized (this) { if (!this.isCompiled()) { - FlowBus.compileNode(this); + FlowBus.compileScriptNode(this); } } } diff --git a/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/java/com/yomahub/liteflow/test/script/javapro/parseOneMode/ScriptJavaxProParseOneModeTest.java b/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/java/com/yomahub/liteflow/test/script/javapro/parseOneMode/ScriptJavaxProParseOneModeTest.java new file mode 100644 index 000000000..734a0ff1d --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/java/com/yomahub/liteflow/test/script/javapro/parseOneMode/ScriptJavaxProParseOneModeTest.java @@ -0,0 +1,33 @@ +package com.yomahub.liteflow.test.script.javapro.parseOneMode; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.slot.DefaultContext; +import com.yomahub.liteflow.test.BaseTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import javax.annotation.Resource; + +@ExtendWith(SpringExtension.class) +@TestPropertySource(value = "classpath:/parseOneMode/application.properties") +@SpringBootTest(classes = ScriptJavaxProParseOneModeTest.class) +@EnableAutoConfiguration +public class ScriptJavaxProParseOneModeTest extends BaseTest { + + @Resource + private FlowExecutor flowExecutor; + + @Test + public void testParseOneMode() { + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); + Assertions.assertTrue(response.isSuccess()); + } + +} \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/resources/parseOneMode/application.properties b/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/resources/parseOneMode/application.properties new file mode 100644 index 000000000..5775aca1c --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/resources/parseOneMode/application.properties @@ -0,0 +1,2 @@ +liteflow.rule-source=parseOneMode/flow.xml +liteflow.parse-mode=PARSE_ONE_ON_FIRST_EXEC \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/resources/parseOneMode/flow.xml b/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/resources/parseOneMode/flow.xml new file mode 100644 index 000000000..3da4f17fd --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-script-javaxpro-springboot/src/test/resources/parseOneMode/flow.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + THEN(s1); + + + \ No newline at end of file From ae9aa3ffa5863f0b4491bed7f008992656cf0f9e Mon Sep 17 00:00:00 2001 From: "everywhere.z" Date: Mon, 7 Jul 2025 22:39:18 +0800 Subject: [PATCH 09/15] =?UTF-8?q?chainName=E6=94=B9=E6=88=90chainId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/yomahub/liteflow/flow/FlowBus.java | 6 +++--- .../com/yomahub/liteflow/parser/helper/ParserHelper.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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 b8bcf5686..dce9ef3bf 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 @@ -86,9 +86,9 @@ public class FlowBus { } // 这一方法主要用于第一阶段chain的预装载 - public static void addChain(String chainName) { - if (!chainMap.containsKey(chainName)) { - chainMap.put(chainName, new Chain(chainName)); + public static void addChain(String chainId) { + if (!chainMap.containsKey(chainId)) { + chainMap.put(chainId, new Chain(chainId)); } } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java index 7b13563c6..9d1b6832c 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java @@ -183,8 +183,8 @@ public class ParserHelper { //首先需要对继承自抽象Chain的chain进行字符串替换 parseImplChain(abstratChainMap, implChainSet, chain); //如果一个chain不为抽象chain,则进行解析 - String chainName = Optional.ofNullable(chain.attributeValue(ID)).orElse(chain.attributeValue(NAME)); - if(!abstratChainMap.containsKey(chainName)){ + String chainId = Optional.ofNullable(chain.attributeValue(ID)).orElse(chain.attributeValue(NAME)); + if(!abstratChainMap.containsKey(chainId)){ parseOneChainConsumer.accept(chain); } } From bb9ca71a80002af1a1bebf56e9499ddaa40fc44d Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Wed, 9 Jul 2025 18:34:48 +0800 Subject: [PATCH 10/15] =?UTF-8?q?bug=20FlowExecutor.execute2RespWithEL=20?= =?UTF-8?q?=E5=B0=81=E8=A3=85=E5=BC=82=E5=B8=B8=E7=BB=93=E6=9E=9C=EF=BC=8C?= =?UTF-8?q?=E5=B0=BD=E9=87=8F=E9=81=BF=E5=85=8D=E5=BC=82=E5=B8=B8=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E6=8A=9B=E5=87=BA=EF=BC=9B=E7=A7=BB=E9=99=A4=20LiteFl?= =?UTF-8?q?owChainELBuilder.setElMd5=20=E6=96=B9=E6=B3=95=EF=BC=9B?= =?UTF-8?q?=E8=B0=83=E6=95=B4=20elMd5Map=20=E5=AD=98=E5=82=A8=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builder/el/LiteFlowChainELBuilder.java | 5 --- .../yomahub/liteflow/core/FlowExecutor.java | 18 ++++------ .../com/yomahub/liteflow/flow/FlowBus.java | 3 +- .../liteflow/flow/LiteflowResponse.java | 35 ++++++++++++------- 4 files changed, 30 insertions(+), 31 deletions(-) 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 f19286306..cad9d1161 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 @@ -275,11 +275,6 @@ public class LiteFlowChainELBuilder { } } - public LiteFlowChainELBuilder setElMd5(String md5) { - this.chain.setElMd5(md5); - return this; - } - public void build() { this.chain.setRouteItem(this.route); this.chain.setConditionList(this.conditionList); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java index 1648fa379..628d7f70b 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java @@ -266,9 +266,8 @@ public class FlowExecutor { * * @param elStr EL 表达式 * @return LiteflowResponse - * @throws ELParseException */ - public LiteflowResponse execute2RespWithEL(String elStr) throws Exception { + public LiteflowResponse execute2RespWithEL(String elStr) { return this.execute2RespWithEL(elStr, null, null, DefaultContext.class); } @@ -278,9 +277,8 @@ public class FlowExecutor { * @param elStr EL 表达式 * @param param 入参 * @return LiteflowResponse - * @throws ELParseException */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param) throws Exception { + public LiteflowResponse execute2RespWithEL(String elStr, Object param) { return this.execute2RespWithEL(elStr, param, null, DefaultContext.class); } @@ -292,9 +290,8 @@ public class FlowExecutor { * @param requestId 请求 ID * @param contextBeanClazzArray 上下文 Class * @return LiteflowResponse - * @throws ELParseException */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class... contextBeanClazzArray) throws Exception { + public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class... contextBeanClazzArray) { return this.execute2RespWithEL(elStr, param, requestId, contextBeanClazzArray, null); } @@ -306,9 +303,8 @@ public class FlowExecutor { * @param requestId 请求 ID * @param contextBeanArray 上下文对象 * @return LiteflowResponse - * @throws ELParseException */ - public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Object... contextBeanArray) throws Exception { + public LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Object... contextBeanArray) { return this.execute2RespWithEL(elStr, param, requestId, null, contextBeanArray); } @@ -321,9 +317,8 @@ public class FlowExecutor { * @param contextBeanClazzArray 上下文 Class 数组 * @param contextBeanArray 上下文对象数组 * @return LiteflowResponse - * @throws ELParseException */ - private LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) throws Exception { + private LiteflowResponse execute2RespWithEL(String elStr, Object param, String requestId, Class[] contextBeanClazzArray, Object[] contextBeanArray) { // 规范化 el 表达式 String normalizedEl = ElRegexUtil.normalize(elStr); @@ -332,7 +327,7 @@ public class FlowExecutor { if (!validationResp.isSuccess()) { // 实际封装的是 ELParseException 类型 - throw validationResp.getCause(); + return LiteflowResponse.newMainResponse(validationResp.getCause()); } // 计算 EL MD5 值,并检查对应的 chain 是否已加载到内存中 @@ -346,7 +341,6 @@ public class FlowExecutor { LiteFlowChainELBuilder.createChain() .setChainId(chainId) .setEL(normalizedEl) - .setElMd5(elMd5) .build(); } 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 9448ceb69..52ba5cc46 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 @@ -45,6 +45,7 @@ import com.yomahub.liteflow.spi.holder.DeclComponentParserHolder; import com.yomahub.liteflow.util.CopyOnWriteHashMap; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -81,7 +82,7 @@ public class FlowBus { chainMap = new CopyOnWriteHashMap<>(); nodeMap = new CopyOnWriteHashMap<>(); fallbackNodeMap = new CopyOnWriteHashMap<>(); - elMd5Map = new CopyOnWriteHashMap<>(); + elMd5Map = new ConcurrentHashMap<>(); } } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/LiteflowResponse.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/LiteflowResponse.java index 4c24df652..d5eec0e8e 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/LiteflowResponse.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/LiteflowResponse.java @@ -5,9 +5,10 @@ import com.yomahub.liteflow.exception.LiteFlowException; import com.yomahub.liteflow.flow.entity.CmpStep; import com.yomahub.liteflow.slot.Slot; -import java.io.Serializable; -import java.util.*; -import java.util.function.Consumer; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; /** * 执行结果封装类 @@ -35,6 +36,12 @@ public class LiteflowResponse { return newResponse(slot, slot.getException()); } + public static LiteflowResponse newMainResponse(Exception exception) { + LiteflowResponse response = new LiteflowResponse(); + response.setExceptionParams(exception); + return response; + } + public static LiteflowResponse newInnerResponse(String chainId, Slot slot) { return newResponse(slot, slot.getSubException(chainId)); } @@ -42,20 +49,22 @@ public class LiteflowResponse { private static LiteflowResponse newResponse(Slot slot, Exception e) { LiteflowResponse response = new LiteflowResponse(); response.setChainId(slot.getChainId()); - if (e != null) { - response.setSuccess(false); - response.setCause(e); - response.setMessage(response.getCause().getMessage()); - response.setCode(response.getCause() instanceof LiteFlowException - ? ((LiteFlowException) response.getCause()).getCode() : null); - } - else { - response.setSuccess(true); - } + response.setExceptionParams(e); response.setSlot(slot); return response; } + private void setExceptionParams(Exception exception) { + if (exception != null) { + this.setSuccess(false); + this.setCause(exception); + this.setMessage(exception.getMessage()); + this.setCode(exception instanceof LiteFlowException ? ((LiteFlowException) exception).getCode() : null); + } else { + this.setSuccess(true); + } + } + public boolean isSuccess() { return success; } From 1450cda7aeeb57c123f8d3218366d0a2b322e06c Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Wed, 9 Jul 2025 18:56:48 +0800 Subject: [PATCH 11/15] =?UTF-8?q?enhancement=20=E8=A1=A5=E5=85=85=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B=EF=BC=8C=E8=B0=83=E6=95=B4=20ElRege?= =?UTF-8?q?xUtil.normalize=20=E6=96=B9=E6=B3=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/yomahub/liteflow/util/ElRegexUtil.java | 5 +++-- .../yomahub/liteflow/test/base/BaseELSpringbootTest.java | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/util/ElRegexUtil.java b/liteflow-core/src/main/java/com/yomahub/liteflow/util/ElRegexUtil.java index aa758b544..1cd752002 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/util/ElRegexUtil.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/util/ElRegexUtil.java @@ -57,12 +57,13 @@ public class ElRegexUtil { /** * 规范化 EL + * * @param elStr * @return String */ public static String normalize(String elStr) { - // 替换 EL 中多余空格,并在末尾保留分号 - return elStr.replaceAll("\\s", "").replaceFirst(";*$", ";"); + // 剔除 EL 中多余空格,且将单引号变为双引号,并在末尾保留一个分号 + return elStr.replace("'", "\"").replaceAll("\\s", "").replaceFirst(";*$", ";"); } } diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java index a4ceede08..4e600df2a 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/base/BaseELSpringbootTest.java @@ -96,6 +96,12 @@ public class BaseELSpringbootTest extends BaseTest { Assertions.assertTrue(response.isSuccess()); // 应返回 chain2 Assertions.assertEquals("chain2", response.getChainId()); + + LiteflowResponse response1 = flowExecutor.execute2RespWithEL("t1=THEN(c, WHEN(j,k));w1 = WHEN(q, THEN(p, r)).id('w01');t2 = THEN(h, i);\n" + + "THEN(a,b,WHEN(t1, d, t2 ),SWITCH(x).to(m, n, w1),z);"); + Assertions.assertTrue(response1.isSuccess()); + // 应返回 chain5 + Assertions.assertEquals("chain5", response1.getChainId()); } } From 3fb0bfc5a0db56382c11e6d2fd493a331226dfcb Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Wed, 9 Jul 2025 11:15:20 +0000 Subject: [PATCH 12/15] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20li?= =?UTF-8?q?teflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteF?= =?UTF-8?q?lowChainELBuilder.java?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builder/el/LiteFlowChainELBuilder.java | 473 ------------------ 1 file changed, 473 deletions(-) delete mode 100644 liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.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 deleted file mode 100644 index cad9d1161..000000000 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java +++ /dev/null @@ -1,473 +0,0 @@ -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.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -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.common.ChainConstant; -import com.yomahub.liteflow.common.entity.ValidationResp; -import com.yomahub.liteflow.enums.ParseModeEnum; -import com.yomahub.liteflow.exception.*; -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.Executable; -import com.yomahub.liteflow.flow.element.Node; -import com.yomahub.liteflow.flow.element.condition.AndOrCondition; -import com.yomahub.liteflow.flow.element.condition.NotCondition; -import com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpi; -import com.yomahub.liteflow.flow.instanceId.NodeInstanceIdManageSpiHolder; -import com.yomahub.liteflow.log.LFLog; -import com.yomahub.liteflow.log.LFLoggerManager; -import com.yomahub.liteflow.property.LiteflowConfig; -import com.yomahub.liteflow.property.LiteflowConfigGetter; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - - -/** - * Chain基于代码形式的组装器 EL表达式规则专属组装器 - * - * @author Bryan.Zhang - * @author Jay li - * @author jason - * @author luo yi - * @since 2.8.0 - */ -public class LiteFlowChainELBuilder { - - private static final LFLog LOG = LFLoggerManager.getLogger(LiteFlowChainELBuilder.class); - - private static ObjectMapper objectMapper = new ObjectMapper(); - - private Chain chain; - - /** - * 这是route EL的文本 - */ - private Executable route; - - /** - * 这是主体的Condition //声明这个变量,而不是用chain.getConditionList的目的,是为了辅助平滑加载 - * 虽然FlowBus里面的map都是CopyOnWrite类型的,但是在buildCondition的时候,为了平滑加载,所以不能事先把chain.getConditionList给设为空List - * 所以在这里做一个缓存,等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.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()); - - } - - public static LiteFlowChainELBuilder createChain() { - return new LiteFlowChainELBuilder(); - } - - public LiteFlowChainELBuilder() { - chain = new Chain(); - conditionList = new ArrayList<>(); - } - - // 在parser中chain的build是2段式的,因为涉及到依赖问题,以前是递归parser - // 2.6.8之后取消了递归的模式,两段式组装,先把带有chainName的chain对象放进去,第二段再组装chain里面的condition - // 所以这里setChainName的时候需要判断下 - - /** - * @return LiteFlowChainELBuilder - * @deprecated 请使用 {@link #setChainId(String)} - */ - @Deprecated - public LiteFlowChainELBuilder setChainName(String chainName) { - if (FlowBus.containChain(chainName)) { - this.chain = FlowBus.getChain(chainName); - } else { - this.chain.setChainName(chainName); - } - return this; - } - - public LiteFlowChainELBuilder setChainId(String chainId) { - if (FlowBus.containChain(chainId)) { - this.chain = FlowBus.getChain(chainId); - } else { - this.chain.setChainId(chainId); - } - return this; - } - - public LiteFlowChainELBuilder setRoute(String routeEl){ - if (StrUtil.isBlank(routeEl)) { - return this; - } - List errorList = new ArrayList<>(); - try { - Executable routeExecutable = compile(routeEl, errorList, false); - - // 判断routeEL是不是符合规范 - if (!(routeExecutable instanceof AndOrCondition || routeExecutable instanceof NotCondition || routeExecutable instanceof Node)){ - throw new RouteELInvalidException("the route EL can only be a boolean node, or an AND or OR expression."); - } - - // 把主要的condition加入 - this.route = routeExecutable; - return this; - } catch (QLException e) { - // EL 底层会包装异常,这里是曲线处理 - if (ObjectUtil.isNotNull(e.getCause()) && Objects.equals(e.getCause().getMessage(), DataNotFoundException.MSG)) { - // 构建错误信息 - String msg = buildDataNotFoundExceptionMsg(routeEl); - throw new ELParseException(msg); - }else if (ObjectUtil.isNotNull(e.getCause())){ - throw new ELParseException(e.getCause().getMessage()); - }else{ - throw new ELParseException(e.getMessage()); - } - }catch (RouteELInvalidException e){ - throw e; - }catch (Exception e) { - String errMsg = StrUtil.format("parse el fail in this chain[{}];\r\n", chain.getChainId()); - throw new ELParseException(errMsg + e.getMessage()); - } - } - - public LiteFlowChainELBuilder setEL(String elStr) { - if (StrUtil.isBlank(elStr)) { - String errMsg = StrUtil.format("no el in this chain[{}]", chain.getChainId()); - throw new FlowSystemException(errMsg); - } - - this.chain.setEl(elStr); - LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); - // 如果设置了不检查Node是否存在,那么这里是不解析的 - if (liteflowConfig.getParseMode().equals(ParseModeEnum.PARSE_ONE_ON_FIRST_EXEC)){ - this.chain.setCompiled(false); - return this; - } - - List errorList = new ArrayList<>(); - try { - Condition condition = compile(elStr, errorList, true); - - if (Objects.isNull(condition)){ - throw new QLException(StrUtil.format("parse el fail,el:[{}]", elStr)); - } - - if (liteflowConfig.getEnableNodeInstanceId()) { - setNodesInstanceId(condition); - } - - // 把主要的condition加入 - this.conditionList.add(condition); - return this; - } catch (QLException e) { - // EL 底层会包装异常,这里是曲线处理 - if (ObjectUtil.isNotNull(e.getCause()) && Objects.equals(e.getCause().getMessage(), DataNotFoundException.MSG)) { - // 构建错误信息 - String msg = buildDataNotFoundExceptionMsg(elStr); - throw new ELParseException(msg); - }else if (ObjectUtil.isNotNull(e.getCause())){ - throw new ELParseException(e.getCause().getMessage()); - }else{ - throw new ELParseException(e.getMessage()); - } - } catch (Exception e) { - String errMsg = StrUtil.format("parse el fail in this chain[{}];\r\n", chain.getChainId()); - throw new ELParseException(errMsg + e.getMessage()); - } - } - - // 往condition里设置instanceId - private void setNodesInstanceId(Condition condition) { - NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi(); - - nodeInstanceIdManageSpi.setNodesInstanceId(condition, chain); - } - - - public LiteFlowChainELBuilder setNamespace(String nameSpace){ - if (StrUtil.isBlank(nameSpace)) { - nameSpace = ChainConstant.DEFAULT_NAMESPACE; - } - this.chain.setNamespace(nameSpace); - return this; - } - - public LiteFlowChainELBuilder setThreadPoolExecutorClass(String threadPoolExecutorClass) { - this.chain.setThreadPoolExecutorClass(threadPoolExecutorClass); - return this; - } - - /** - * EL表达式校验,此方法已经过时,请使用 {@link LiteFlowChainELBuilder#validateWithEx(String)} - * - * @param elStr EL表达式 - * @return true 校验成功 false 校验失败 - */ - public static boolean validate(String elStr) { - return validateWithEx(elStr).isSuccess(); - } - - /** - * 校验 - * - * @param elStr EL表达式 - * @return ValidationResp - */ - public static ValidationResp validateWithEx(String elStr) { - try { - LiteFlowChainELBuilder.createChain().compile(elStr, null, true); - return ValidationResp.success(); - } catch (Exception e) { - String msg = buildDataNotFoundExceptionMsg(elStr); - return ValidationResp.fail(new ELParseException(msg)); - } - } - - public void build() { - this.chain.setRouteItem(this.route); - this.chain.setConditionList(this.conditionList); - - //暂且去掉循环依赖检测,因为有发现循环依赖检测在对大的EL进行检测的时候,会导致CPU飙升,也或许是jackson低版本的问题 - //checkBuild(); - - FlowBus.addChain(this.chain); - } - - // #region private method - - /** - * build 前简单校验 - */ - private void checkBuild() { - List errorList = new ArrayList<>(); - if (StrUtil.isBlank(this.chain.getChainId())) { - errorList.add("name is blank"); - } - if (CollUtil.isNotEmpty(errorList)) { - throw new RuntimeException(CollUtil.join(errorList, ",", "[", "]")); - } - // 对每一个 chain 进行循环引用检测 - try { - objectMapper.writeValueAsString(this.chain); - } catch (Exception e) { - if (e instanceof JsonMappingException) { - throw new CyclicDependencyException(StrUtil.format("There is a circular dependency in the chain[{}], please check carefully.", chain.getChainId(), e)); - } else { - throw new ParseException(e.getMessage()); - } - } - } - - /** - * 解析 EL 表达式,查找未定义的 id 并构建错误信息 - * @param elStr el 表达式 - */ - private static 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 - return 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 - return 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 - return msg + sourceEl + "\n" + StrUtil.fill("^", CharUtil.SPACE, commaLeftIndex + 12, true); - } - } - } - } catch (Exception ex) { - // ignore - } - return msg; - } - - public static void buildUnCompileChain(Chain chain){ - if (StrUtil.isBlank(chain.getEl())){ - throw new FlowSystemException(StrUtil.format("no el content in this unCompile chain[{}]", chain.getChainId())); - } - LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); - - // 如果chain已经有Condition了,那说明已经解析过了,这里只对未解析的chain进行解析 - if (CollUtil.isNotEmpty(chain.getConditionList())){ - return; - } - - List errorList = new ArrayList<>(); - try { - DefaultContext context = new DefaultContext<>(); - - // 这里一定要先放chain,再放node,因为node优先于chain,所以当重名时,node会覆盖掉chain - // 往上下文里放入所有的chain,是的el表达式可以直接引用到chain - FlowBus.getChainMap().values().forEach(chainItem -> context.put(chainItem.getChainId(), chainItem)); - - // 往上下文里放入所有的node,使得el表达式可以直接引用到nodeId - FlowBus.getNodeMap().keySet().forEach(nodeId -> context.put(nodeId, FlowBus.getNode(nodeId))); - - // 放入当前主chain的ID - context.put(ChainConstant.CURR_CHAIN_ID, chain.getChainId()); - - - // 只有当PARSE_ONE_ON_FIRST_EXEC时才会执行这个方法 - // 那么会有一种级联的情况:这个EL中含有其他的chain,如果这时候不先解析其他chain,就到导致诸如循环场景无法设置index或者obj的情况 - // 所以这里要判断表达式里有没有其他的chain,如果有,进行先行解析 - - String[] itemArray = EXPRESS_RUNNER.getOutVarNames(chain.getEl()); - Arrays.stream(itemArray).forEach(item -> { - if (FlowBus.containChain(item)){ - Chain itemChain = FlowBus.getChain(item); - if (!itemChain.isCompiled()){ - buildUnCompileChain(FlowBus.getChain(item)); - } - } - }); - - // 解析el成为一个Condition - // 为什么这里只是一个Condition,而不是一个List呢 - // 这里无论多复杂的,外面必定有一个最外层的Condition,所以这里只有一个,内部可以嵌套很多层,这点和以前的不太一样 - Condition condition = (Condition) EXPRESS_RUNNER.execute(chain.getEl(), context, errorList, true, true); - - if (Objects.isNull(condition)){ - throw new QLException(StrUtil.format("parse el fail,el:[{}]", chain.getEl())); - } - - // 设置实例id - if (liteflowConfig.getEnableNodeInstanceId()) { - NodeInstanceIdManageSpi nodeInstanceIdManageSpi = NodeInstanceIdManageSpiHolder.getInstance().getNodeInstanceIdManageSpi(); - nodeInstanceIdManageSpi.setNodesInstanceId(condition, chain); - } - - // 把主要的condition加入 - chain.setConditionList(CollUtil.toList(condition)); - - // 把chain的isCompiled设置为true - chain.setCompiled(true); - - FlowBus.addChain(chain); - } catch (QLException e) { - // EL 底层会包装异常,这里是曲线处理 - if (ObjectUtil.isNotNull(e.getCause()) && Objects.equals(e.getCause().getMessage(), DataNotFoundException.MSG)) { - // 构建错误信息 - String msg = buildDataNotFoundExceptionMsg(chain.getEl()); - throw new ELParseException(msg); - }else if (ObjectUtil.isNotNull(e.getCause())){ - throw new ELParseException(e.getCause().getMessage()); - }else{ - throw new ELParseException(e.getMessage()); - } - } catch (Exception e) { - String errMsg = StrUtil.format("parse el fail in this chain[{}];\r\n", chain.getChainId()); - throw new ELParseException(errMsg + e.getMessage()); - } - } - - @SuppressWarnings("unchecked") - private T compile(String elStr, List errorList, boolean putChain2Context) throws Exception{ - DefaultContext context = new DefaultContext<>(); - - if (putChain2Context){ - // 这里一定要先放chain,再放node,因为node优先于chain,所以当重名时,node会覆盖掉chain - // 往上下文里放入所有的chain,是的el表达式可以直接引用到chain - FlowBus.getChainMap().values().forEach(chain -> context.put(chain.getChainId(), chain)); - } - - // 往上下文里放入所有的node,使得el表达式可以直接引用到nodeId - FlowBus.getNodeMap().keySet().forEach(nodeId -> context.put(nodeId, FlowBus.getNode(nodeId))); - - // 放入当前主chain的ID - if (this.chain != null){ - context.put(ChainConstant.CURR_CHAIN_ID, this.chain.getChainId()); - } - - // 解析el成为一个Condition - // 为什么这里只是一个Condition,而不是一个List呢 - // 这里无论多复杂的,外面必定有一个最外层的Condition,所以这里只有一个,内部可以嵌套很多层,这点和以前的不太一样 - - return (T) EXPRESS_RUNNER.execute(elStr, context, errorList, true, true); - } - -} From 420fad3bd3c9c9e48d9ba3efd3d0e9d4e21b067a Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Wed, 9 Jul 2025 20:48:16 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E6=81=A2=E5=A4=8D=20LiteFlowChainELBuild?= =?UTF-8?q?er=20=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java | 1 - 1 file changed, 1 deletion(-) 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 cad9d1161..5269d0d44 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 @@ -42,7 +42,6 @@ import java.util.Objects; * @author Bryan.Zhang * @author Jay li * @author jason - * @author luo yi * @since 2.8.0 */ public class LiteFlowChainELBuilder { From 6dc41f4d6cb545722f629be0a8a69df4dfb47b10 Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Wed, 9 Jul 2025 21:34:51 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E6=81=A2=E5=A4=8D=20LiteFlowChainELBuild?= =?UTF-8?q?er=20=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../liteflow/builder/el/LiteFlowChainELBuilder.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) 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 5269d0d44..0a4f426d7 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,10 +1,7 @@ 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.ObjectUtil; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.*; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.ql.util.express.DefaultContext; @@ -30,10 +27,7 @@ import com.yomahub.liteflow.log.LFLoggerManager; import com.yomahub.liteflow.property.LiteflowConfig; import com.yomahub.liteflow.property.LiteflowConfigGetter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; /** From 2dd7d54418fa8f6a2125974940fb3540dacf6250 Mon Sep 17 00:00:00 2001 From: "everywhere.z" Date: Tue, 15 Jul 2025 11:39:24 +0800 Subject: [PATCH 15/15] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20pr332=20=E5=87=BA?= =?UTF-8?q?=E7=8E=B0=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java | 6 ++++++ .../src/main/java/com/yomahub/liteflow/flow/FlowBus.java | 4 +++- .../main/java/com/yomahub/liteflow/flow/element/Chain.java | 4 ---- 3 files changed, 9 insertions(+), 5 deletions(-) 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 0a4f426d7..8dd55c9d3 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 @@ -2,6 +2,7 @@ package com.yomahub.liteflow.builder.el; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.*; +import cn.hutool.crypto.digest.MD5; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.ql.util.express.DefaultContext; @@ -26,6 +27,7 @@ import com.yomahub.liteflow.log.LFLog; 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 java.util.*; @@ -182,6 +184,10 @@ public class LiteFlowChainELBuilder { } this.chain.setEl(elStr); + + String elMd5 = MD5.create().digestHex(ElRegexUtil.normalize(elStr)); + this.chain.setElMd5(elMd5); + LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); // 如果设置了不检查Node是否存在,那么这里是不解析的 if (liteflowConfig.getParseMode().equals(ParseModeEnum.PARSE_ONE_ON_FIRST_EXEC)){ 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 719d0235e..3c2ff9761 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 @@ -108,7 +108,9 @@ public class FlowBus { chainMap.put(chain.getChainId(), chain); - elMd5Map.put(chain.getElMd5(), chain.getChainId()); + if (StrUtil.isNotBlank(chain.getEl())){ + elMd5Map.put(chain.getElMd5(), chain.getChainId()); + } //如果有生命周期则执行相应生命周期实现 if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())){ diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java index 69ae92e3b..9a17ca252 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java @@ -254,10 +254,6 @@ public class Chain implements Executable { } public String getElMd5() { - // 若为 null 时,先规范化 EL,再计算 MD5 - if (elMd5 == null) { - elMd5 = MD5.create().digestHex(ElRegexUtil.normalize(el)); - } return elMd5; }