bug #IJX8AE 表达式级 bind 的数据,在流程执行结束后无法通过节点事后读取

This commit is contained in:
everywhere.z 2026-06-27 13:51:22 +08:00
parent 0892cc6620
commit 68dc35ee92
3 changed files with 84 additions and 0 deletions

View File

@ -48,6 +48,12 @@ public class BindOperator extends BaseOperator<Executable> {
if (override) {
clearNodeBindData(condition, key);
}
// bind 数据下放到该 Condition 直属不跨子 chain 引用的节点上
// 使流程执行结束后仍能通过 Node#getBindData CmpStep.getRefNode().getBindData(...)读取到
// 2.15.1 的表现保持一致
// 注意这里只下放到属于当前 chain 的节点克隆不会递归进被引用的子 chain
// 从而避免污染被多个 chain 共享的子 chain #ID7OTO / #IDCBQ2
putBindDataToLocalNodes(condition, key, value, override);
return condition;
}
@ -75,4 +81,34 @@ public class BindOperator extends BaseOperator<Executable> {
}
});
}
/**
* bind 数据下放到该 Condition 直属的节点上递归进嵌套 Condition但不跨子 chain 引用
* <p>
* 这些节点是当前 chain 构建时 clone 出来的专属于本 chain 的实例每个 clone 持有独立的
* bindDataMap因此写入 bind 数据不会污染被多个 chain 共享的子 chain
* 下放之后流程执行结束仍可通过 {@code CmpStep.getRefNode().getBindData(key)} 读取到表达式级 bind 的值
* <p>
* 对于条件中以 chainId 形式引用的子 chain{@link Chain} 类型对象在 FlowBus 中被多个 chain 共享
* 这里不会写入其内部节点 bind 数据仍由运行时通过 Condition 调用栈解析从而避免数据污染 #ID7OTO / #IDCBQ2
*
* @param condition 目标 Condition
* @param key bind 数据的 key
* @param value bind 数据的 value
* @param override true 时强制覆盖节点级已有的同 key 数据否则保留节点级 bind节点级优先
*/
private void putBindDataToLocalNodes(Condition condition, String key, String value, boolean override) {
condition.getExecutableGroup().values().forEach(executableList -> executableList.forEach(executable -> {
if (executable instanceof Node) {
Node node = (Node) executable;
// 节点级 bind 优先 override 情况下节点已有同 key 数据时不覆盖
if (override || !node.hasBindData(key)) {
node.putBindData(key, value);
}
} else if (executable instanceof Condition) {
// 递归进嵌套 Condition同属当前 chainChain 类型不进入此分支避免污染共享子 chain
putBindDataToLocalNodes((Condition) executable, key, value, override);
}
}));
}
}

View File

@ -2,6 +2,7 @@ package com.yomahub.liteflow.test.bindData;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.flow.entity.CmpStep;
import com.yomahub.liteflow.slot.DefaultContext;
import com.yomahub.liteflow.test.BaseTest;
import com.yomahub.liteflow.util.JsonUtil;
@ -13,6 +14,8 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.TestPropertySource;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
@ -105,4 +108,36 @@ public class BindDataSpringbootTest1 extends BaseTest {
Assertions.assertTrue(response.isSuccess());
}
// 复现 issue #IJT2YA表达式级 bind 的数据执行结束后应能通过 Node#getBindData 读取到
// 且节点级 bind 不会被表达式级 bind 覆盖a=v2, b=v1, c=v2
@Test
public void testBind8() throws Exception {
String el = "THEN(a, b.bind(\"k\", \"v1\"), c).bind(\"k\", \"v2\");";
LiteflowResponse response = flowExecutor.execute2RespWithEL(el);
Assertions.assertTrue(response.isSuccess());
Map<String, String> resultMap = new HashMap<>();
for (CmpStep cmpStep : response.getExecuteStepQueue()) {
resultMap.put(cmpStep.getNodeId(), cmpStep.getRefNode().getBindData("k"));
}
Assertions.assertEquals("v2", resultMap.get("a"));
Assertions.assertEquals("v1", resultMap.get("b"));
Assertions.assertEquals("v2", resultMap.get("c"));
}
// 回归保护表达式级 bind 作用于含子 chain 引用的条件时绝不能污染被共享的子 chain#ID7OTO
@Test
public void testBind9() throws Exception {
// bind chain运行时子 chainpsub内的节点 a 应解析到 outer
LiteflowResponse responseC = flowExecutor.execute2Resp("pchainC", "arg");
DefaultContext ctxC = responseC.getFirstContextBean();
Assertions.assertTrue(responseC.isSuccess());
Assertions.assertEquals("outer", ctxC.getData("a"));
// bind chain 单独引用同一子 chain绝不能被上面的 bind 污染
LiteflowResponse responseB = flowExecutor.execute2Resp("pchainB", "arg");
DefaultContext ctxB = responseB.getFirstContextBean();
Assertions.assertTrue(responseB.isSuccess());
Assertions.assertNull(ctxB.getData("a"));
}
}

View File

@ -33,4 +33,17 @@
<chain id="chain7">
THEN(a, b.bind("k1", "test_b"), c).bind("k1", "test", true);
</chain>
<!-- 以下链路用于:表达式级 bind 作用于含子 chain 引用的条件时,绝不能污染被共享的子 chain#ID7OTO 回归保护) -->
<chain id="psub">
THEN(a);
</chain>
<chain id="pchainB">
THEN(psub);
</chain>
<chain id="pchainC">
THEN(d, psub).bind("k1", "outer");
</chain>
</flow>