diff --git a/easyflow-modules/easyflow-module-ai/pom.xml b/easyflow-modules/easyflow-module-ai/pom.xml
index 0f713e43..c866056f 100644
--- a/easyflow-modules/easyflow-module-ai/pom.xml
+++ b/easyflow-modules/easyflow-module-ai/pom.xml
@@ -53,6 +53,10 @@
com.easyagents
easy-agents-spring-boot-starter
+
+ com.google.re2j
+ re2j
+
diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java
index e085e218..a73a74ce 100644
--- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java
+++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckService.java
@@ -11,6 +11,8 @@ import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckResult;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
+import tech.easyflow.ai.node.ConditionNode;
+import tech.easyflow.ai.node.ConditionRuleSupport;
import tech.easyflow.ai.node.filegeneration.FileGenerationRules;
import tech.easyflow.ai.node.filegeneration.SourceFormat;
import tech.easyflow.ai.node.filegeneration.TargetFormat;
@@ -50,6 +52,7 @@ public class WorkflowCheckService {
private static final String TYPE_START = "startNode";
private static final String TYPE_END = "endNode";
private static final String TYPE_LOOP = "loopNode";
+ private static final String TYPE_CONDITION = "conditionNode";
private static final String TYPE_WORKFLOW = "workflow-node";
private static final String TYPE_PLUGIN = "plugin-node";
private static final String TYPE_MAKE_FILE = "make-file";
@@ -178,6 +181,7 @@ public class WorkflowCheckService {
}
}
checkLoopConfigurations(nodes, nodeMap, issues, issueKeys);
+ checkConditionConfigurations(nodes, issues, issueKeys);
List edges = new ArrayList<>();
Set edgeIds = new HashSet<>();
@@ -262,6 +266,131 @@ public class WorkflowCheckService {
}
}
+ /**
+ * 校验条件判断节点的可视化规则配置。
+ *
+ * @param nodes 节点列表
+ * @param issues 问题列表
+ * @param issueKeys 问题去重键
+ */
+ private void checkConditionConfigurations(
+ List nodes,
+ List issues,
+ Set issueKeys) {
+ for (NodeView node : nodes) {
+ if (!TYPE_CONDITION.equals(node.type) || node.data == null) {
+ continue;
+ }
+
+ Object branchesValue = node.data.get("branches");
+ if (!(branchesValue instanceof JSONArray)
+ || ((JSONArray) branchesValue).isEmpty()) {
+ addIssue(
+ issues,
+ issueKeys,
+ "CONDITION_BRANCHES_EMPTY",
+ "条件判断节点至少需要一个分支",
+ node.id,
+ null,
+ node.name);
+ continue;
+ }
+
+ JSONArray branches = (JSONArray) branchesValue;
+ for (int branchIndex = 0; branchIndex < branches.size(); branchIndex++) {
+ Object branchValue = branches.get(branchIndex);
+ if (!(branchValue instanceof JSONObject)) {
+ addIssue(
+ issues,
+ issueKeys,
+ "CONDITION_BRANCH_INVALID",
+ "第 " + (branchIndex + 1) + " 个条件分支配置无效",
+ node.id,
+ null,
+ node.name);
+ continue;
+ }
+
+ JSONObject branch = (JSONObject) branchValue;
+ if ("expression".equalsIgnoreCase(
+ trimToNull(branch.getString("mode")))) {
+ continue;
+ }
+ Object rulesValue = branch.get("rules");
+ if (rulesValue == null) {
+ continue;
+ }
+ if (!(rulesValue instanceof JSONArray)) {
+ addIssue(
+ issues,
+ issueKeys,
+ "CONDITION_RULES_INVALID",
+ conditionBranchLabel(branch, branchIndex) + "的条件规则必须是数组",
+ node.id,
+ null,
+ node.name);
+ continue;
+ }
+
+ JSONArray rules = (JSONArray) rulesValue;
+ for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) {
+ Object ruleValue = rules.get(ruleIndex);
+ ConditionNode.ConditionRule rule = toConditionRule(ruleValue);
+ String error = ConditionRuleSupport.validateRule(rule);
+ if (error == null) {
+ continue;
+ }
+ addIssue(
+ issues,
+ issueKeys,
+ "CONDITION_RULE_INVALID",
+ conditionBranchLabel(branch, branchIndex)
+ + "第 " + (ruleIndex + 1)
+ + " 条条件配置无效: " + error,
+ node.id,
+ null,
+ node.name);
+ }
+ }
+ }
+ }
+
+ /**
+ * 将 JSON 条件规则转换为运行时规则对象。
+ *
+ * @param ruleValue 条件规则 JSON 值
+ * @return 条件规则;输入无效时返回 {@code null}
+ */
+ private ConditionNode.ConditionRule toConditionRule(Object ruleValue) {
+ if (!(ruleValue instanceof JSONObject)) {
+ return null;
+ }
+ JSONObject ruleJson = (JSONObject) ruleValue;
+ ConditionNode.ConditionRule rule = new ConditionNode.ConditionRule();
+ rule.setId(ruleJson.getString("id"));
+ rule.setJoiner(ruleJson.getString("joiner"));
+ rule.setLeftRef(ruleJson.getString("leftRef"));
+ rule.setOperator(ruleJson.getString("operator"));
+ rule.setRightType(ruleJson.getString("rightType"));
+ rule.setRightValue(ruleJson.getString("rightValue"));
+ rule.setRightRef(ruleJson.getString("rightRef"));
+ return rule;
+ }
+
+ /**
+ * 获取用于校验错误展示的条件分支名称。
+ *
+ * @param branch 分支配置
+ * @param branchIndex 分支序号
+ * @return 分支名称
+ */
+ private String conditionBranchLabel(JSONObject branch, int branchIndex) {
+ String label = trimToNull(branch.getString("label"));
+ return StringUtils.hasText(label)
+ ? "条件分支[" + label + "]"
+ : "第 " + (branchIndex + 1) + " 个条件分支";
+ }
+
/**
* 校验普通节点启用循环后的总执行次数。
*
diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNode.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNode.java
index c4c5394c..34bb0fdb 100644
--- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNode.java
+++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNode.java
@@ -148,8 +148,19 @@ public class ConditionNode extends BaseNode {
}
Boolean matched = null;
- for (ConditionRule rule : rules) {
- boolean ruleMatched = checkRule(chain, rule);
+ for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) {
+ ConditionRule rule = rules.get(ruleIndex);
+ boolean ruleMatched;
+ try {
+ ruleMatched = checkRule(chain, rule);
+ } catch (IllegalArgumentException e) {
+ throw new ChainException(String.format(
+ "条件分支规则执行失败,分支[%s/%s],规则[%d]: %s",
+ branch.getId(),
+ branch.getLabel(),
+ ruleIndex + 1,
+ e.getMessage()), e);
+ }
if (matched == null) {
matched = ruleMatched;
continue;
@@ -167,8 +178,9 @@ public class ConditionNode extends BaseNode {
}
private boolean checkRule(Chain chain, ConditionRule rule) {
- if (rule == null || StringUtil.noText(rule.getOperator())) {
- return false;
+ String validationError = ConditionRuleSupport.validateRule(rule);
+ if (validationError != null) {
+ throw new IllegalArgumentException(validationError);
}
Object leftValue = resolveValue(chain, rule.getLeftRef());
@@ -201,8 +213,10 @@ public class ConditionNode extends BaseNode {
return contains(leftValue, rightValue);
case "notContains":
return !contains(leftValue, rightValue);
+ case ConditionRuleSupport.OPERATOR_REGEX_MATCH:
+ return ConditionRuleSupport.matchesRegex(leftValue, rule.getRightValue());
default:
- return false;
+ throw new IllegalArgumentException("不支持的条件操作符: " + operator);
}
}
diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNodeParser.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNodeParser.java
index 46b673cd..71fe30fe 100644
--- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNodeParser.java
+++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionNodeParser.java
@@ -13,6 +13,15 @@ import java.util.List;
*/
public class ConditionNodeParser extends BaseNodeParser {
+ /**
+ * 将 TinyFlow 条件节点配置解析为运行时节点。
+ *
+ * @param root 节点根配置
+ * @param data 节点业务配置
+ * @param tinyflow 工作流配置
+ * @return 条件判断运行时节点
+ * @throws RuntimeException 分支或规则配置无效
+ */
@Override
protected ConditionNode doParse(JSONObject root, JSONObject data, JSONObject tinyflow) {
ConditionNode node = new ConditionNode();
@@ -32,11 +41,50 @@ public class ConditionNodeParser extends BaseNodeParser {
if (StringUtil.noText(node.getDefaultBranchId())) {
throw new RuntimeException("条件判断节点必须配置默认分支");
}
+ validateBranches(branches);
return node;
}
+ /**
+ * 返回条件节点类型名称。
+ *
+ * @return 条件节点类型名称
+ */
public String getNodeName() {
return "conditionNode";
}
+
+ /**
+ * 校验可视化分支内的全部条件规则。
+ *
+ * @param branches 条件分支
+ * @throws RuntimeException 任一规则配置无效
+ */
+ private void validateBranches(List branches) {
+ for (int branchIndex = 0; branchIndex < branches.size(); branchIndex++) {
+ ConditionNode.ConditionBranch branch = branches.get(branchIndex);
+ if (branch == null
+ || "expression".equalsIgnoreCase(
+ StringUtil.getFirstWithText(branch.getMode(), "visual"))) {
+ continue;
+ }
+ List rules = branch.getRules();
+ if (rules == null || rules.isEmpty()) {
+ continue;
+ }
+ for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) {
+ String error = ConditionRuleSupport.validateRule(rules.get(ruleIndex));
+ if (error != null) {
+ String branchName = StringUtil.getFirstWithText(
+ branch.getLabel(),
+ branch.getId(),
+ "第 " + (branchIndex + 1) + " 个分支");
+ throw new RuntimeException(
+ "条件分支[" + branchName + "]第 " + (ruleIndex + 1)
+ + " 条规则配置无效: " + error);
+ }
+ }
+ }
+ }
}
diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionRuleSupport.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionRuleSupport.java
new file mode 100644
index 00000000..6020735a
--- /dev/null
+++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/ConditionRuleSupport.java
@@ -0,0 +1,155 @@
+package tech.easyflow.ai.node;
+
+import com.easyagents.flow.core.util.StringUtil;
+import com.google.re2j.Pattern;
+import com.google.re2j.PatternSyntaxException;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 条件判断节点规则校验与安全正则执行支持。
+ */
+public final class ConditionRuleSupport {
+
+ public static final String OPERATOR_REGEX_MATCH = "regexMatch";
+ public static final int MAX_REGEX_LENGTH = 512;
+
+ private static final int MAX_REGEX_CACHE_SIZE = 256;
+ private static final Set SUPPORTED_OPERATORS = Set.of(
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "isEmpty",
+ "isNotEmpty",
+ "contains",
+ "notContains",
+ OPERATOR_REGEX_MATCH);
+ private static final Map REGEX_CACHE =
+ new LinkedHashMap(32, 0.75F, true) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > MAX_REGEX_CACHE_SIZE;
+ }
+ };
+
+ /**
+ * 禁止创建工具类实例。
+ */
+ private ConditionRuleSupport() {
+ }
+
+ /**
+ * 校验一条可视化条件规则。
+ *
+ * @param rule 条件规则
+ * @return 校验错误;返回 {@code null} 表示通过
+ */
+ public static String validateRule(ConditionNode.ConditionRule rule) {
+ if (rule == null) {
+ return "条件规则不能为空";
+ }
+
+ String operator = rule.getOperator();
+ if (StringUtil.noText(operator)) {
+ return "条件操作符不能为空";
+ }
+ if (!SUPPORTED_OPERATORS.contains(operator)) {
+ return "不支持的条件操作符: " + operator;
+ }
+ if (!OPERATOR_REGEX_MATCH.equals(operator)) {
+ return null;
+ }
+ if (!"fixed".equals(rule.getRightType())) {
+ return "正则表达式必须使用固定值";
+ }
+ return validateRegex(rule.getRightValue());
+ }
+
+ /**
+ * 校验正则表达式的长度和 RE2/J 语法。
+ *
+ * @param regex 正则表达式
+ * @return 校验错误;返回 {@code null} 表示通过
+ */
+ public static String validateRegex(String regex) {
+ try {
+ requireRegex(regex);
+ return null;
+ } catch (IllegalArgumentException e) {
+ return e.getMessage();
+ }
+ }
+
+ /**
+ * 使用 RE2/J 在标量左值中查找正则匹配。
+ *
+ * @param leftValue 条件左值
+ * @param regex 正则表达式
+ * @return 任意位置存在匹配时返回 {@code true}
+ * @throws IllegalArgumentException 正则无效或左值类型不支持
+ */
+ public static boolean matchesRegex(Object leftValue, String regex) {
+ Pattern pattern = requireRegex(regex);
+ if (leftValue == null) {
+ return false;
+ }
+ if (!(leftValue instanceof CharSequence)
+ && !(leftValue instanceof Number)
+ && !(leftValue instanceof Boolean)
+ && !(leftValue instanceof Character)) {
+ throw new IllegalArgumentException(
+ "正则匹配仅支持字符串、数字或布尔值,当前类型: "
+ + leftValue.getClass().getSimpleName());
+ }
+ return pattern.matcher(String.valueOf(leftValue)).find();
+ }
+
+ /**
+ * 校验并获取正则模式。
+ *
+ * @param regex 正则表达式
+ * @return 已编译 RE2/J 模式
+ * @throws IllegalArgumentException 正则为空、过长或语法无效
+ */
+ private static Pattern requireRegex(String regex) {
+ if (StringUtil.noText(regex)) {
+ throw new IllegalArgumentException("正则表达式不能为空");
+ }
+ if (regex.length() > MAX_REGEX_LENGTH) {
+ throw new IllegalArgumentException(
+ "正则表达式不能超过 " + MAX_REGEX_LENGTH + " 个字符");
+ }
+ try {
+ return compileRegex(regex);
+ } catch (PatternSyntaxException e) {
+ throw new IllegalArgumentException(
+ "正则表达式语法错误: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 获取已编译正则,并以有界 LRU 缓存复用结果。
+ *
+ * @param regex 正则表达式
+ * @return 已编译 RE2/J 模式
+ * @throws PatternSyntaxException 正则语法无效
+ */
+ private static Pattern compileRegex(String regex) {
+ synchronized (REGEX_CACHE) {
+ Pattern cached = REGEX_CACHE.get(regex);
+ if (cached != null) {
+ return cached;
+ }
+ Pattern compiled = Pattern.compile(regex);
+ REGEX_CACHE.put(regex, compiled);
+ return compiled;
+ }
+ }
+}
diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java
index d9190cff..48dbf070 100644
--- a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java
+++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowCheckServiceTest.java
@@ -8,6 +8,7 @@ import org.junit.Test;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckResult;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
import tech.easyflow.ai.entity.Workflow;
+import tech.easyflow.ai.node.ConditionNodeParser;
import tech.easyflow.ai.node.MakeFileNodeParser;
import tech.easyflow.ai.node.SearchDatasetNodeParser;
import tech.easyflow.ai.node.WorkflowNodeParser;
@@ -21,6 +22,92 @@ import java.util.Map;
public class WorkflowCheckServiceTest {
+ /**
+ * 验证保存阶段接受合法的正则条件规则。
+ */
+ @Test
+ public void testSaveShouldPassValidRegexCondition() throws Exception {
+ WorkflowCheckService service = newService(new HashMap<>());
+ String content = workflowJson(
+ array(node(
+ "condition-1",
+ "conditionNode",
+ null,
+ conditionData("regexMatch", "fixed", "(?i)^[a-z]+-\\d+$"))),
+ new JSONArray());
+
+ WorkflowCheckResult result = service.checkContent(
+ content, WorkflowCheckStage.SAVE, null);
+
+ Assert.assertTrue(result.isPassed());
+ }
+
+ /**
+ * 验证保存阶段拒绝 RE2/J 不支持的正则语法。
+ */
+ @Test
+ public void testSaveShouldBlockUnsupportedRegexSyntax() throws Exception {
+ WorkflowCheckService service = newService(new HashMap<>());
+ String content = workflowJson(
+ array(node(
+ "condition-1",
+ "conditionNode",
+ null,
+ conditionData("regexMatch", "fixed", "(?=VIP)VIP"))),
+ new JSONArray());
+
+ WorkflowCheckResult result = service.checkContent(
+ content, WorkflowCheckStage.SAVE, null);
+
+ Assert.assertFalse(result.isPassed());
+ assertHasCode(result, "CONDITION_RULE_INVALID");
+ Assert.assertTrue(result.getIssues().stream()
+ .anyMatch(issue -> issue.getMessage().contains("VIP 分支")
+ && issue.getMessage().contains("第 1 条")));
+
+ WorkflowCheckResult preExecuteResult = service.checkContent(
+ content, WorkflowCheckStage.PRE_EXECUTE, null);
+ Assert.assertFalse(preExecuteResult.isPassed());
+ assertHasCode(preExecuteResult, "CONDITION_RULE_INVALID");
+ }
+
+ /**
+ * 验证保存阶段拒绝变量正则和未知操作符。
+ */
+ @Test
+ public void testSaveShouldBlockDynamicRegexAndUnknownOperator() throws Exception {
+ WorkflowCheckService service = newService(new HashMap<>());
+ String content = workflowJson(
+ array(
+ node(
+ "condition-ref",
+ "conditionNode",
+ null,
+ conditionData(
+ "regexMatch",
+ "ref",
+ "start.regex")),
+ node(
+ "condition-unknown",
+ "conditionNode",
+ null,
+ conditionData(
+ "unknown",
+ "fixed",
+ "VIP"))),
+ new JSONArray());
+
+ WorkflowCheckResult result = service.checkContent(
+ content, WorkflowCheckStage.SAVE, null);
+
+ Assert.assertFalse(result.isPassed());
+ Assert.assertEquals(
+ 2,
+ result.getIssues().stream()
+ .filter(issue -> "CONDITION_RULE_INVALID".equals(issue.getCode()))
+ .count());
+ }
+
/**
* 验证普通节点循环次数必须处于 1~300。
*/
@@ -579,6 +666,7 @@ public class WorkflowCheckServiceTest {
parser.addNodeParser("workflow-node", new WorkflowNodeParser());
parser.addNodeParser("search-dataset-node", new SearchDatasetNodeParser());
parser.addNodeParser("make-file", new MakeFileNodeParser());
+ parser.addNodeParser("conditionNode", new ConditionNodeParser());
setField(service, "chainParser", parser);
setField(service, "workflowService", mockWorkflowService(workflowStore));
setField(service, "workflowDatacenterContentService", new WorkflowDatacenterContentService());
@@ -680,6 +768,49 @@ public class WorkflowCheckServiceTest {
return node(id, "search-dataset-node", parentId, data);
}
+ /**
+ * 创建包含一个普通分支和 Else 分支的条件节点配置。
+ *
+ * @param operator 条件操作符
+ * @param rightType 右值类型
+ * @param rightValue 右值
+ * @return 条件节点数据
+ */
+ private static JSONObject conditionData(
+ String operator,
+ String rightType,
+ String rightValue) {
+ JSONObject rule = new JSONObject();
+ rule.put("id", "rule-1");
+ rule.put("joiner", "AND");
+ rule.put("leftRef", "start.value");
+ rule.put("operator", operator);
+ rule.put("rightType", rightType);
+ rule.put("rightValue", rightValue);
+ if ("ref".equals(rightType)) {
+ rule.put("rightRef", rightValue);
+ }
+
+ JSONObject branch = new JSONObject();
+ branch.put("id", "branch-vip");
+ branch.put("label", "VIP 分支");
+ branch.put("mode", "visual");
+ branch.put("rules", array(rule));
+
+ JSONObject defaultBranch = new JSONObject();
+ defaultBranch.put("id", "branch-else");
+ defaultBranch.put("label", "Else");
+ defaultBranch.put("mode", "visual");
+ defaultBranch.put("rules", new JSONArray());
+
+ JSONObject data = data("条件判断");
+ data.put("branchMode", "first_match");
+ data.put("branches", array(branch, defaultBranch));
+ data.put("defaultBranchId", "branch-else");
+ data.put("defaultBranchLabel", "Else");
+ return data;
+ }
+
/**
* 创建显式循环节点数据。
*
diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/ConditionNodeTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/ConditionNodeTest.java
index 6ea90aa7..3fa4891a 100644
--- a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/ConditionNodeTest.java
+++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/ConditionNodeTest.java
@@ -150,6 +150,117 @@ public class ConditionNodeTest {
Assert.assertEquals(false, result.get("matchedByDefault"));
}
+ /**
+ * 验证正则默认查找匹配,并支持锚点和内联大小写标志。
+ */
+ @Test
+ public void testRegexMatchFindAnchorsAndInlineFlag() {
+ ConditionNode node = new ConditionNode();
+ ConditionNode.ConditionBranch hit = visualBranch(
+ "branch_regex",
+ "正则分支",
+ visualRule(
+ "ctx.orderCode",
+ ConditionRuleSupport.OPERATOR_REGEX_MATCH,
+ "fixed",
+ "(?i)[a-z]+-\\d+",
+ null));
+ ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
+ node.setBranches(Arrays.asList(hit, def));
+ node.setDefaultBranchId(def.getId());
+ node.setDefaultBranchLabel(def.getLabel());
+
+ Map partialResult = node.execute(
+ createChain(Map.of("ctx", Map.of("orderCode", "订单ABC-123完成"))));
+ Assert.assertEquals("branch_regex", partialResult.get("matchedBranchId"));
+
+ hit.setRules(Collections.singletonList(
+ visualRule(
+ "ctx.orderCode",
+ ConditionRuleSupport.OPERATOR_REGEX_MATCH,
+ "fixed",
+ "(?i)^[a-z]+-\\d+$",
+ null)));
+ Map anchoredMiss = node.execute(
+ createChain(Map.of("ctx", Map.of("orderCode", "订单ABC-123完成"))));
+ Assert.assertEquals("branch_default", anchoredMiss.get("matchedBranchId"));
+
+ Map anchoredHit = node.execute(
+ createChain(Map.of("ctx", Map.of("orderCode", "ABC-123"))));
+ Assert.assertEquals("branch_regex", anchoredHit.get("matchedBranchId"));
+ }
+
+ /**
+ * 验证正则支持数字和布尔标量,并覆盖语法和长度边界。
+ */
+ @Test
+ public void testRegexMatchScalarAndValidationBoundaries() {
+ Assert.assertTrue(ConditionRuleSupport.matchesRegex(123, "^12\\d$"));
+ Assert.assertTrue(ConditionRuleSupport.matchesRegex(true, "^true$"));
+ Assert.assertFalse(ConditionRuleSupport.matchesRegex(null, ".*"));
+ Assert.assertTrue(
+ ConditionRuleSupport.validateRegex(" ")
+ .contains("不能为空"));
+ Assert.assertTrue(
+ ConditionRuleSupport.validateRegex("(a)\\1")
+ .contains("语法错误"));
+ Assert.assertTrue(
+ ConditionRuleSupport.validateRegex(
+ "a".repeat(ConditionRuleSupport.MAX_REGEX_LENGTH + 1))
+ .contains(String.valueOf(ConditionRuleSupport.MAX_REGEX_LENGTH)));
+ }
+
+ /**
+ * 验证正则拒绝集合左值并返回可定位错误。
+ */
+ @Test
+ public void testRegexMatchShouldRejectCollectionValue() {
+ ConditionNode node = new ConditionNode();
+ ConditionNode.ConditionBranch hit = visualBranch(
+ "branch_regex",
+ "正则分支",
+ visualRule(
+ "ctx.values",
+ ConditionRuleSupport.OPERATOR_REGEX_MATCH,
+ "fixed",
+ "\\d+",
+ null));
+ ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
+ node.setBranches(Arrays.asList(hit, def));
+ node.setDefaultBranchId(def.getId());
+
+ ChainException exception = Assert.assertThrows(
+ ChainException.class,
+ () -> node.execute(createChain(
+ Map.of("ctx", Map.of("values", Arrays.asList(1, 2))))));
+
+ Assert.assertTrue(exception.getMessage().contains("正则分支"));
+ Assert.assertTrue(exception.getMessage().contains("规则[1]"));
+ Assert.assertTrue(exception.getMessage().contains("仅支持字符串、数字或布尔值"));
+ }
+
+ /**
+ * 验证未知条件操作符不会静默进入默认分支。
+ */
+ @Test
+ public void testUnknownOperatorShouldFailExplicitly() {
+ ConditionNode node = new ConditionNode();
+ ConditionNode.ConditionBranch hit = visualBranch(
+ "branch_invalid",
+ "异常分支",
+ visualRule("ctx.value", "unknown", "fixed", "x", null));
+ ConditionNode.ConditionBranch def = defaultBranch("branch_default", "默认分支");
+ node.setBranches(Arrays.asList(hit, def));
+ node.setDefaultBranchId(def.getId());
+
+ ChainException exception = Assert.assertThrows(
+ ChainException.class,
+ () -> node.execute(createChain(Map.of("ctx", Map.of("value", "x")))));
+
+ Assert.assertTrue(exception.getMessage().contains("不支持的条件操作符"));
+ Assert.assertTrue(exception.getMessage().contains("异常分支"));
+ }
+
@Test
public void testManagedEdgeConditionRouting() {
ConditionNode node = new ConditionNode();
diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/ConditionNode.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/ConditionNode.svelte
index ef60a43b..ccf3cdb5 100644
--- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/ConditionNode.svelte
+++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/nodes/ConditionNode.svelte
@@ -1,6 +1,7 @@