Merge branch 'dev' of https://gitee.com/dromara/liteFlow into issues/ICM6TX

This commit is contained in:
luoyi
2025-07-15 13:10:53 +08:00
24 changed files with 849 additions and 85 deletions

View File

@@ -5,6 +5,7 @@ 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.crypto.digest.MD5;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ql.util.express.DefaultContext;
@@ -29,6 +30,7 @@ import com.yomahub.liteflow.log.LFLog;
import com.yomahub.liteflow.log.LFLoggerManager;
import com.yomahub.liteflow.property.LiteflowConfig;
import com.yomahub.liteflow.property.LiteflowConfigGetter;
import com.yomahub.liteflow.util.ElRegexUtil;
import java.util.ArrayList;
import java.util.Arrays;
@@ -190,6 +192,10 @@ public class LiteFlowChainELBuilder {
}
this.chain.setEl(elStr);
String elMd5 = MD5.create().digestHex(ElRegexUtil.normalize(elStr));
this.chain.setElMd5(elMd5);
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
// 如果设置了不检查Node是否存在那么这里是不解析的
if (liteflowConfig.getParseMode().equals(ParseModeEnum.PARSE_ONE_ON_FIRST_EXEC)){

View File

@@ -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<LiteflowResponse> executeRouteChain(Object param, Class<?>... contextBeanClazzArray){
return this.executeWithRoute(null, param, null, contextBeanClazzArray, null);
}

View File

@@ -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<String, Node> fallbackNodeMap;
private static final Map<String/* elMd5 */, String/* chainId */> 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<>();
}
}
@@ -86,9 +91,9 @@ public class FlowBus {
}
// 这一方法主要用于第一阶段chain的预装载
public static void addChain(String chainName) {
if (!chainMap.containsKey(chainName)) {
chainMap.put(chainName, new Chain(chainName));
public static void addChain(String chainId) {
if (!chainMap.containsKey(chainId)) {
chainMap.put(chainId, new Chain(chainId));
}
}
@@ -103,6 +108,10 @@ public class FlowBus {
chainMap.put(chain.getChainId(), chain);
if (StrUtil.isNotBlank(chain.getEl())){
elMd5Map.put(chain.getElMd5(), chain.getChainId());
}
//如果有生命周期则执行相应生命周期实现
if (CollUtil.isNotEmpty(LifeCycleHolder.getPostProcessChainBuildLifeCycleList())){
LifeCycleHolder.getPostProcessChainBuildLifeCycleList().forEach(
@@ -201,7 +210,7 @@ public class FlowBus {
}
Node node = new Node(nodeId, name, nodeType, script, language);
nodeMap.put(nodeId, node);
put2NodeMap(nodeId, node);
} else {
addScriptNodeAndCompile(nodeId, name, nodeType, script, language);
}
@@ -215,11 +224,9 @@ public class FlowBus {
* @param type type
* @param script script content
* @param language language
* @return NodeComponent instance
*/
public static NodeComponent addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script, String language) {
public static void addScriptNodeAndCompile(String nodeId, String name, NodeTypeEnum type, String script, String language) {
addNode(nodeId, name, type, ScriptComponent.ScriptComponentClassMap.get(type), script, language);
return nodeMap.get(nodeId).getInstance();
}
private static List<NodeComponent> getNodeComponentList(String nodeId, String name, NodeTypeEnum type, Class<?> cmpClazz) throws Exception {
@@ -257,7 +264,7 @@ public class FlowBus {
return cmpInstanceList;
}
public static void compileNode(Node node) {
public static void compileScriptNode(Node node) {
String nodeId = node.getId(), name = node.getName(), script = node.getScript(), language = node.getLanguage();
NodeTypeEnum type = node.getType();
try {
@@ -292,8 +299,13 @@ public class FlowBus {
addFallbackNode(node);
}
// 如果是spring自动扫描的组件在addManagedNode方法中就已经完成了组装了
// 调用到这里分两种情况一是脚本组件二是通过LiteFlowNodeBuilder代码进行组装的组件
private static void addNode(String nodeId, String name, NodeTypeEnum type, Class<?> cmpClazz, String script, String language) {
try {
// 获得初始化好的NodeComponent
// 按理说一个nodeId对应一个NodeComponent这里得到的是List<NodeComponent>的原因是声明式组件有可能会有多个nodeId。
// 声明式组件又分类声明和方法声明如果对于方法声明来说这里的nodeId其实并不是最终真正的nodeId。
List<NodeComponent> cmpInstanceList = getNodeComponentList(nodeId, name, type, cmpClazz);
// 初始化Node把component放到Node里去
@@ -330,6 +342,7 @@ public class FlowBus {
chainMap.clear();
nodeMap.clear();
fallbackNodeMap.clear();
elMd5Map.clear();
cleanScriptCache();
}
@@ -354,9 +367,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 {

View File

@@ -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;
}

View File

@@ -32,7 +32,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 +48,8 @@ public class Chain implements Executable{
private String namespace = ChainConstant.DEFAULT_NAMESPACE;
private String elMd5;
private String threadPoolExecutorClass;
private final TransmittableThreadLocal<Long> runtimeIdTL = new TransmittableThreadLocal<>();
@@ -245,7 +247,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;
}
public void setElMd5(String elMd5) {
this.elMd5 = elMd5;
}
}

View File

@@ -173,7 +173,7 @@ public class Node implements Executable, Cloneable, Rollbackable{
if (!this.isCompiled()) {
synchronized (this) {
if (!this.isCompiled()) {
FlowBus.compileNode(this);
FlowBus.compileScriptNode(this);
}
}
}

View File

@@ -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<String> instanceIdFile = nodeInstanceIdManageSpi.readInstanceIdFile(chainId);

View File

@@ -183,8 +183,8 @@ public class ParserHelper {
//首先需要对继承自抽象Chain的chain进行字符串替换
parseImplChain(abstratChainMap, implChainSet, chain);
//如果一个chain不为抽象chain则进行解析
String chainName = Optional.ofNullable(chain.attributeValue(ID)).orElse(chain.attributeValue(NAME));
if(!abstratChainMap.containsKey(chainName)){
String chainId = Optional.ofNullable(chain.attributeValue(ID)).orElse(chain.attributeValue(NAME));
if(!abstratChainMap.containsKey(chainId)){
parseOneChainConsumer.accept(chain);
}
}

View File

@@ -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(";*$", ";");
}
}