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 1/9] =?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 852c18ac0b6dfb843533ce69ba6a70943a26a4bf Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Mon, 30 Jun 2025 17:14:22 +0800 Subject: [PATCH 2/9] =?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 3/9] =?UTF-8?q?enhancement=20#IBQCWB=20=E5=AF=B9=E6=89=80?= =?UTF-8?q?=E6=9C=89=20EL=20=E8=BF=9B=E8=A1=8C=E8=A7=84=E8=8C=83=E5=8C=96?= =?UTF-8?q?=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 4/9] =?UTF-8?q?bug=20=E8=B0=83=E6=95=B4=20FlowExecutor=20?= =?UTF-8?q?=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 bb9ca71a80002af1a1bebf56e9499ddaa40fc44d Mon Sep 17 00:00:00 2001 From: luoyi <972849752@qq.com> Date: Wed, 9 Jul 2025 18:34:48 +0800 Subject: [PATCH 5/9] =?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 6/9] =?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 7/9] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20lite?= =?UTF-8?q?flow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlo?= =?UTF-8?q?wChainELBuilder.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 8/9] =?UTF-8?q?=E6=81=A2=E5=A4=8D=20LiteFlowChainELBuilder?= =?UTF-8?q?=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 9/9] =?UTF-8?q?=E6=81=A2=E5=A4=8D=20LiteFlowChainELBuilder?= =?UTF-8?q?=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.*; /**