Feat: Multi-Switch 初步实现

This commit is contained in:
LuanY77
2025-08-28 17:05:30 +08:00
parent e3289e264f
commit 7720894361
7 changed files with 205 additions and 5 deletions

View File

@@ -1,5 +1,7 @@
package com.yomahub.liteflow.flow.element.condition;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.enums.ConditionTypeEnum;
@@ -11,7 +13,8 @@ import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.slot.DataBus;
import com.yomahub.liteflow.slot.Slot;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
* 选择Condition
@@ -25,6 +28,8 @@ public class SwitchCondition extends Condition {
private final String TAG_FLAG = ":";
private static final String MULTI_TARGET_SPLITTER = ",";
@Override
public void executeCondition(Integer slotIndex) throws Exception {
// 获取switch node
@@ -46,6 +51,18 @@ public class SwitchCondition extends Condition {
// 拿到switch节点的结果
String targetId = switchNode.getItemResultMetaValue(slotIndex);
String[] split = targetId.split(MULTI_TARGET_SPLITTER);
if (split.length == 1) {
// 只有一个目标节点
this.findSwitchTargetAndExecute(slotIndex, targetId, targetList);
} else {
// 多个目标节点,执行多路选择
List<String> targetIds = Arrays.stream(split).map(String::trim).collect(Collectors.toList());
this.findMultiSwitchTargetsAndExecute(slotIndex, targetIds, targetList);
}
}
private void findSwitchTargetAndExecute(Integer slotIndex, String targetId, List<Executable> targetList) throws Exception {
Slot slot = DataBus.getSlot(slotIndex);
Executable targetExecutor = null;
@@ -61,9 +78,9 @@ public class SwitchCondition extends Condition {
}
else {
targetExecutor = targetList.stream()
.filter(executable -> ObjectUtil.equal(executable.getId(),targetId) )
.findFirst()
.orElse(null);
.filter(executable -> ObjectUtil.equal(executable.getId(),targetId) )
.findFirst()
.orElse(null);
}
}
@@ -90,7 +107,87 @@ public class SwitchCondition extends Condition {
}
}
@Override
private void findMultiSwitchTargetsAndExecute(Integer slotIndex, List<String> targetIds, List<Executable> targetList) throws Exception {
Slot slot = DataBus.getSlot(slotIndex);
List<Executable> matchedExecutors = null;
if (CollectionUtil.isNotEmpty(targetIds)) {
// 存储最终目标执行器的 set 集合
Set<Executable> resultSet = new HashSet<>();
// 普通 id
Set<String> normalIds = new HashSet<>();
// tag 模式的目标id & tag
List<Pair<String, String>> tagTargets = new ArrayList<>();
// 1. 分离 targetIds 中的普通 ID 和 Tag 模式 ID
for (String targetId : targetIds) {
if (StrUtil.isNotBlank(targetId)) {
if (targetId.contains(TAG_FLAG)) {
String[] target = targetId.split(TAG_FLAG, 2);
String _targetId = target[0];
String _targetTag = target[1];
tagTargets.add(Pair.of(_targetId, _targetTag));
} else {
normalIds.add(targetId);
}
}
}
// 2. 根据普通 ID 筛选目标
if (!normalIds.isEmpty()) {
targetList.stream()
.filter(executable -> normalIds.contains(executable.getId()))
.forEach(resultSet::add);
}
// 3. 根据 Tag 模式筛选目标
if (!tagTargets.isEmpty()) {
for (Pair<String, String> target : tagTargets) {
String _targetId = target.getKey();
String _targetTag = target.getValue();
targetList.stream()
.filter(executable ->
(StrUtil.startWith(_targetId, TAG_PREFIX) && ObjectUtil.equal(_targetTag, executable.getTag()))
|| ((StrUtil.isEmpty(_targetId) || _targetId.equals(executable.getId()))
&& (StrUtil.isEmpty(_targetTag) || _targetTag.equals(executable.getTag()))))
.forEach(resultSet::add);
}
}
matchedExecutors = resultSet.stream()
.sorted(Comparator.comparing(Executable::getId))
.collect(Collectors.toList());
}
if (CollectionUtil.isEmpty(matchedExecutors)) {
// 未匹配到,则走默认节点
Executable defaultExecutor = this.getDefaultExecutor();
matchedExecutors = Optional.ofNullable(defaultExecutor)
.map(CollectionUtil::newArrayList)
.orElse(null);
}
if (CollectionUtil.isNotEmpty(matchedExecutors)) {
// TODO 实现并行
for (Executable targetExecutor : matchedExecutors) {
// switch的目标不能是Pre节点或者Finally节点
if (targetExecutor instanceof PreCondition || targetExecutor instanceof FinallyCondition) {
String errorInfo = StrUtil.format(
"[{}]:switch component[{}] error, switch target node cannot be pre or finally",
slot.getRequestId(), this.getSwitchNode().getInstance().getDisplayName());
throw new SwitchTargetCannotBePreOrFinallyException(errorInfo);
}
targetExecutor.setCurrChainId(this.getCurrChainId());
targetExecutor.execute(slotIndex);
}
} else {
String errorInfo = StrUtil.format("[{}]:no target node find for the component[{}],targetIds are {}",
slot.getRequestId(), this.getSwitchNode().getInstance().getDisplayName(), targetIds);
throw new NoSwitchTargetNodeException(errorInfo);
}
}
@Override
public ConditionTypeEnum getConditionType() {
return ConditionTypeEnum.TYPE_SWITCH;
}

View File

@@ -55,4 +55,31 @@ public class SwitchTest extends BaseTest {
Assertions.assertEquals("a==>h==>b", response.getExecuteStepStr());
}
@Test
public void testMultiSwitch1() throws Exception {
LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
Assertions.assertTrue(response.isSuccess());
Assertions.assertEquals("a==>i==>a==>b==>c", response.getExecuteStepStr());
}
@Test
public void testMultiSwitch2() throws Exception {
LiteflowResponse response = flowExecutor.execute2Resp("chain7", "arg");
Assertions.assertTrue(response.isSuccess());
Assertions.assertEquals("a==>j==>a==>b==>d", response.getExecuteStepStr());
}
@Test
public void testMultiSwitch3() throws Exception {
LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
Assertions.assertTrue(response.isSuccess());
Assertions.assertEquals("a==>k==>a", response.getExecuteStepStr());
}
@Test
public void testMultiSwitch4() throws Exception {
LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
Assertions.assertTrue(response.isSuccess());
Assertions.assertEquals("a==>l==>a==>b==>d", response.getExecuteStepStr());
}
}

View File

@@ -0,0 +1,11 @@
package com.yomahub.liteflow.test.switchcase.cmp;
import com.yomahub.liteflow.core.NodeSwitchComponent;
public class IMultiSwitchCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
return "a, b, c";
}
}

View File

@@ -0,0 +1,11 @@
package com.yomahub.liteflow.test.switchcase.cmp;
import com.yomahub.liteflow.core.NodeSwitchComponent;
public class JMultiSwitchCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
return "tag:tag1, tag:tag2, d";
}
}

View File

@@ -0,0 +1,11 @@
package com.yomahub.liteflow.test.switchcase.cmp;
import com.yomahub.liteflow.core.NodeSwitchComponent;
public class KMultiSwitchCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
return "e, f, g, h";
}
}

View File

@@ -0,0 +1,11 @@
package com.yomahub.liteflow.test.switchcase.cmp;
import com.yomahub.liteflow.core.NodeSwitchComponent;
public class LMultiSwitchCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
return ":td, a";
}
}

View File

@@ -9,6 +9,10 @@
<node id="f" class="com.yomahub.liteflow.test.switchcase.cmp.FSwitchCmp"/>
<node id="g" class="com.yomahub.liteflow.test.switchcase.cmp.GSwitchCmp"/>
<node id="h" class="com.yomahub.liteflow.test.switchcase.cmp.HSwitchCmp"/>
<node id="i" class="com.yomahub.liteflow.test.switchcase.cmp.IMultiSwitchCmp"/>
<node id="j" class="com.yomahub.liteflow.test.switchcase.cmp.JMultiSwitchCmp"/>
<node id="k" class="com.yomahub.liteflow.test.switchcase.cmp.KMultiSwitchCmp"/>
<node id="l" class="com.yomahub.liteflow.test.switchcase.cmp.LMultiSwitchCmp"/>
</nodes>
<chain name="chain1">
@@ -46,4 +50,32 @@
SWITCH(h).to(b.tag("td"), d.tag("td"))
);
</chain>
<chain name="chain6">
THEN(
a,
SWITCH(i).to(a, b, c, d)
);
</chain>
<chain name="chain7">
THEN(
a,
SWITCH(j).to(a.tag("tag1"), b.tag("tag2"), c, d)
);
</chain>
<chain name="chain8">
THEN(
a,
SWITCH(k).to(a, b, c, d).DEFAULT(a)
)
</chain>
<chain name="chain9">
THEN(
a,
SWITCH(l).to(b.tag("td"), d.tag("td"), a, c)
);
</chain>
</flow>