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..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 @@ -13,7 +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.*; @@ -39,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; @@ -50,6 +54,7 @@ import java.util.stream.Collectors; * 流程规则主要执行器类 * * @author Bryan.Zhang + * @author luo yi */ public class FlowExecutor { @@ -256,6 +261,92 @@ public class FlowExecutor { return this.execute2Resp(chainId, param, null, contextBeanClazzArray, null); } + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @return LiteflowResponse + */ + public LiteflowResponse execute2RespWithEL(String elStr) { + return this.execute2RespWithEL(elStr, null, null, DefaultContext.class); + } + + /** + * 直接执行 EL 表达式 + * + * @param elStr EL 表达式 + * @param param 入参 + * @return LiteflowResponse + */ + public LiteflowResponse execute2RespWithEL(String elStr, Object param) { + return this.execute2RespWithEL(elStr, param, null, DefaultContext.class); + } + + /** + * 直接执行 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); + } + + /** + * 直接执行 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); + } + + /** + * 直接执行 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) { + // 规范化 el 表达式 + String normalizedEl = ElRegexUtil.normalize(elStr); + + // 校验 EL 是否正常 + ValidationResp validationResp = LiteFlowChainELBuilder.validateWithEx(normalizedEl); + + if (!validationResp.isSuccess()) { + // 实际封装的是 ELParseException 类型 + return LiteflowResponse.newMainResponse(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) + .build(); + } + + return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, contextBeanArray); + } + public List executeRouteChain(Object param, Class... contextBeanClazzArray){ return this.executeWithRoute(null, param, null, contextBeanClazzArray, null); } 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 dce9ef3bf..719d0235e 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; @@ -66,6 +67,8 @@ public class FlowBus { private static final Map fallbackNodeMap; + private static final Map elMd5Map; + private static final AtomicBoolean initStat = new AtomicBoolean(false); static { @@ -74,10 +77,12 @@ public class FlowBus { chainMap = new HashMap<>(); nodeMap = new HashMap<>(); fallbackNodeMap = new HashMap<>(); - }else{ + elMd5Map = new HashMap<>(); + } else { chainMap = new CopyOnWriteHashMap<>(); nodeMap = new CopyOnWriteHashMap<>(); fallbackNodeMap = new CopyOnWriteHashMap<>(); + elMd5Map = new ConcurrentHashMap<>(); } } @@ -103,6 +108,8 @@ public class FlowBus { chainMap.put(chain.getChainId(), chain); + elMd5Map.put(chain.getElMd5(), chain.getChainId()); + //如果有生命周期则执行相应生命周期实现 if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())){ LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach( @@ -333,6 +340,7 @@ public class FlowBus { chainMap.clear(); nodeMap.clear(); fallbackNodeMap.clear(); + elMd5Map.clear(); cleanScriptCache(); } @@ -357,9 +365,15 @@ public class FlowBus { } } + public static String getChainIdByElMd5(String elMd5) { + return elMd5Map.get(elMd5); + } + public static boolean removeChain(String chainId) { if (containChain(chainId)) { - chainMap.remove(chainId); + Chain removedChain = chainMap.remove(chainId); + // 移除 elMd5 对应的 chainId + elMd5Map.remove(removedChain.getElMd5()); return true; } else { 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; } 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 04ba347b4..69ae92e3b 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; @@ -21,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; @@ -32,7 +34,7 @@ import java.util.List; * @author jason * @author luo yi */ -public class Chain implements Executable{ +public class Chain implements Executable { private static final LFLog LOG = LFLoggerManager.getLogger(Chain.class); @@ -48,6 +50,8 @@ public class Chain implements Executable{ private String namespace = ChainConstant.DEFAULT_NAMESPACE; + private String elMd5; + private String threadPoolExecutorClass; private final TransmittableThreadLocal runtimeIdTL = new TransmittableThreadLocal<>(); @@ -245,7 +249,19 @@ public class Chain implements Executable{ this.threadPoolExecutorClass = threadPoolExecutorClass; } - public Long getRuntimeId(){ - return runtimeIdTL.get(); - } + public Long getRuntimeId() { + return runtimeIdTL.get(); + } + + public String getElMd5() { + // 若为 null 时,先规范化 EL,再计算 MD5 + if (elMd5 == null) { + elMd5 = MD5.create().digestHex(ElRegexUtil.normalize(el)); + } + return 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..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 @@ -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,16 @@ 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.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 09490711a..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 @@ -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) @@ -61,4 +63,45 @@ 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()); + + 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()); + } + + // 运行文件里同样的 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()); + + 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()); + } + }