bug #ID7OTO bind对象为chain时,chain的定义顺序影响了bind数据的获取

重写了整个底层的parser逻辑
This commit is contained in:
everywhere.z
2026-01-21 15:52:21 +08:00
parent 04008d12b3
commit f289045046
7 changed files with 172 additions and 16 deletions

View File

@@ -1,15 +1,14 @@
package com.yomahub.liteflow.builder.el.operator;
import cn.hutool.core.util.BooleanUtil;
import com.yomahub.liteflow.builder.el.operator.base.BaseOperator;
import com.yomahub.liteflow.builder.el.operator.base.OperatorHelper;
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.ChainBindWrapperCondition;
import com.yomahub.liteflow.meta.LiteflowMetaOperator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* EL规则中的bind的操作符
*
@@ -27,20 +26,53 @@ public class BindOperator extends BaseOperator<Executable> {
String value = OperatorHelper.convert(objects[2], String.class);
AtomicBoolean override = new AtomicBoolean(false);
// 获取 override 参数(第四个参数,默认为 false
boolean override = false;
if (objects.length > 3) {
override.set(OperatorHelper.convert(objects[3], Boolean.class));
}
if (bindItem instanceof Node){
override.set(true);
override = OperatorHelper.convert(objects[3], Boolean.class);
}
LiteflowMetaOperator.getNodes(bindItem).forEach(node -> {
if (BooleanUtil.isFalse(node.hasBindData(key)) || override.get()){
node.putBindData(key, value);
// 场景1对 Node bind保持现有逻辑bind 数据存在 Node 上)
if (bindItem instanceof Node) {
Node node = (Node) bindItem;
node.putBindData(key, value);
return node;
}
// 场景2对 Condition bind如 THEN(...).bind(...)bind 数据存在 Condition 上
if (bindItem instanceof Condition) {
Condition condition = (Condition) bindItem;
condition.putBindData(key, value);
// 如果 override=true需要清除该 Condition 下所有 Node 上相同 key 的 bind 数据
// 这样可以确保 Condition 级别的 bind 能够覆盖 Node 级别的 bind
if (override) {
clearNodeBindData(condition, key);
}
});
return condition;
}
// 场景3对 Chain bind新逻辑包装成 ChainBindWrapperCondition
// 这样不会修改 Chain 本身,而是创建一个包装 Condition 来持有 bind 数据
// 从而避免多个 chain 引用同一个子 chain 时的 bind 数据污染问题
if (bindItem instanceof Chain) {
Chain chain = (Chain) bindItem;
ChainBindWrapperCondition wrapper = new ChainBindWrapperCondition(chain);
wrapper.putBindData(key, value);
return wrapper;
}
return bindItem;
}
/**
* 清除 Condition 下所有 Node 上指定 key 的 bind 数据
* 用于 override=true 时,确保 Condition 级别的 bind 能够覆盖 Node 级别的 bind
*/
private void clearNodeBindData(Condition condition, String key) {
LiteflowMetaOperator.getNodes(condition).forEach(node -> {
if (node.hasBindData(key)) {
node.removeBindData(key);
}
});
}
}

View File

@@ -19,6 +19,7 @@ import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.exception.ObjectConvertException;
import com.yomahub.liteflow.flow.FlowBus;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.flow.element.Condition;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.flow.entity.CmpStep;
import com.yomahub.liteflow.flow.executor.DefaultNodeExecutor;
@@ -442,7 +443,23 @@ public abstract class NodeComponent{
}
public <T> T getBindData(String key, Class<T> clazz) {
String bindData = getRefNode().getBindData(key);
String bindData = null;
// 第一步:先从 Node 级别查找node.bind(...) 场景,优先级最高)
bindData = getRefNode().getBindData(key);
// 第二步:如果 Node 级别没有找到,从 Condition 栈中查找(从栈顶向下遍历)
// 这样可以正确处理 chain.bind(...) 和 THEN(...).bind(...) 的场景
if (StrUtil.isBlank(bindData)) {
Slot slot = this.getSlot();
for (Condition condition : slot.getConditionStack()) {
if (condition.hasBindData(key)) {
bindData = condition.getBindData(key);
break;
}
}
}
if (StrUtil.isBlank(bindData)) {
return null;
}
@@ -474,7 +491,22 @@ public abstract class NodeComponent{
}
public <T> List<T> getBindDataList(String key, Class<T> clazz) {
String bindData = getRefNode().getBindData(key);
String bindData = null;
// 第一步:先从 Node 级别查找node.bind(...) 场景,优先级最高)
bindData = getRefNode().getBindData(key);
// 第二步:如果 Node 级别没有找到,从 Condition 栈中查找(从栈顶向下遍历)
if (StrUtil.isBlank(bindData)) {
Slot slot = this.getSlot();
for (Condition condition : slot.getConditionStack()) {
if (condition.hasBindData(key)) {
bindData = condition.getBindData(key);
break;
}
}
}
if (StrUtil.isBlank(bindData)) {
return null;
}

View File

@@ -29,7 +29,9 @@ public enum ConditionTypeEnum {
TYPE_NOT_OPT("not_opt", "not_opt"),
TYPE_ABSTRACT("abstract", "abstract");
TYPE_ABSTRACT("abstract", "abstract"),
TYPE_CHAIN_BIND_WRAPPER("chain_bind_wrapper", "chain_bind_wrapper");
private String type;

View File

@@ -49,6 +49,12 @@ public abstract class Condition implements Executable{
*/
private String currChainId;
/**
* bind 数据存储,用于存储在该 Condition 上的 bind 数据
* 运行时 node 会通过 Condition 栈向上查找 bind 数据
*/
private Map<String, String> bindDataMap = new HashMap<>();
@Override
public void execute(Integer slotIndex) throws Exception {
Slot slot = DataBus.getSlot(slotIndex);
@@ -191,4 +197,31 @@ public abstract class Condition implements Executable{
public Map<String, List<Executable>> getExecutableGroup() {
return executableGroup;
}
/**
* 设置 bind 数据
* @param key bind 数据的 key
* @param value bind 数据的 value
*/
public void putBindData(String key, String value) {
this.bindDataMap.put(key, value);
}
/**
* 获取 bind 数据
* @param key bind 数据的 key
* @return bind 数据的 value
*/
public String getBindData(String key) {
return this.bindDataMap.get(key);
}
/**
* 判断是否存在指定 key 的 bind 数据
* @param key bind 数据的 key
* @return 是否存在
*/
public boolean hasBindData(String key) {
return this.bindDataMap.containsKey(key);
}
}

View File

@@ -522,6 +522,10 @@ public class Node implements Executable, Cloneable, Rollbackable{
return this.bindDataMap.get(key);
}
public void removeBindData(String key) {
this.bindDataMap.remove(key);
}
public Object getStepData(){
return this.stepDataTL.get();
}

View File

@@ -0,0 +1,44 @@
package com.yomahub.liteflow.flow.element.condition;
import com.yomahub.liteflow.enums.ConditionTypeEnum;
import com.yomahub.liteflow.flow.element.Chain;
import com.yomahub.liteflow.flow.element.Condition;
/**
* Chain bind 包装 Condition
* 用于在对 Chain 进行 bind 操作时,创建一个包装 Condition 来持有 bind 数据,
* 而不是直接修改 Chain 内部的 Node从而避免多个 chain 引用同一个子 chain 时的 bind 数据污染问题。
*
* @author Bryan.Zhang
* @since 2.15.3
*/
public class ChainBindWrapperCondition extends Condition {
private final Chain wrappedChain;
public ChainBindWrapperCondition(Chain chain) {
this.wrappedChain = chain;
}
@Override
public void executeCondition(Integer slotIndex) throws Exception {
// 设置当前 chainId
wrappedChain.setCurrChainId(this.getCurrChainId());
// 执行被包装的 chain
wrappedChain.execute(slotIndex);
}
@Override
public ConditionTypeEnum getConditionType() {
return ConditionTypeEnum.TYPE_CHAIN_BIND_WRAPPER;
}
public Chain getWrappedChain() {
return wrappedChain;
}
@Override
public String getId() {
return "chain_bind_wrapper_" + wrappedChain.getChainId();
}
}

View File

@@ -316,6 +316,15 @@ public class Slot {
conditionStack.get().pop();
}
/**
* 获取 Condition 调用栈
* 用于在运行时向上查找 bind 数据
* @return Condition 栈
*/
public Deque<Condition> getConditionStack() {
return conditionStack.get();
}
/**
* @deprecated 请使用 {@link #setChainId(String)}
*/