Browse Source

Merge branch 'dev' of gitee.com:dromara/liteFlow into dev_for_redis_v3

Signed-off-by: houxinyu <hxinyu2000@163.com>
houxinyu 1 năm trước cách đây
mục cha
commit
f937ed4b09
100 tập tin đã thay đổi với 3452 bổ sung227 xóa
  1. 4 0
      liteflow-core/src/main/java/com/yomahub/liteflow/aop/ICmpAroundAspect.java
  2. 2 0
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java
  3. 104 0
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/MaxWaitSecondsOperator.java
  4. 25 0
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/ParallelOperator.java
  5. 3 0
      liteflow-core/src/main/java/com/yomahub/liteflow/common/ChainConstant.java
  6. 19 1
      liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java
  7. 63 0
      liteflow-core/src/main/java/com/yomahub/liteflow/core/NodeComponent.java
  8. 1 1
      liteflow-core/src/main/java/com/yomahub/liteflow/core/proxy/ComponentProxy.java
  9. 3 1
      liteflow-core/src/main/java/com/yomahub/liteflow/enums/LiteFlowMethodEnum.java
  10. 2 1
      liteflow-core/src/main/java/com/yomahub/liteflow/enums/ScriptTypeEnum.java
  11. 28 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/LiteflowResponse.java
  12. 23 1
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Node.java
  13. 13 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Rollbackable.java
  14. 6 1
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/CatchCondition.java
  15. 85 53
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/ForCondition.java
  16. 106 70
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/IteratorCondition.java
  17. 130 67
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/LoopCondition.java
  18. 2 2
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/SwitchCondition.java
  19. 58 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/TimeoutCondition.java
  20. 36 10
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/WhenCondition.java
  21. 46 13
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/WhileCondition.java
  22. 47 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/entity/CmpStep.java
  23. 56 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/parallel/LoopFutureObj.java
  24. 45 0
      liteflow-core/src/main/java/com/yomahub/liteflow/property/LiteflowConfig.java
  25. 45 0
      liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java
  26. 4 0
      liteflow-core/src/main/java/com/yomahub/liteflow/spi/CmpAroundAspect.java
  27. 10 0
      liteflow-core/src/main/java/com/yomahub/liteflow/spi/local/LocalCmpAroundAspect.java
  28. 6 0
      liteflow-core/src/main/java/com/yomahub/liteflow/thread/ExecutorHelper.java
  29. 1 1
      liteflow-core/src/main/java/com/yomahub/liteflow/thread/LiteFlowDefaultMainExecutorBuilder.java
  30. 27 0
      liteflow-core/src/main/java/com/yomahub/liteflow/thread/LiteFlowDefaultParallelLoopExecutorBuilder.java
  31. 1 1
      liteflow-core/src/main/java/com/yomahub/liteflow/thread/LiteFlowDefaultWhenExecutorBuilder.java
  32. 2 2
      liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/util/JDBCHelper.java
  33. 27 0
      liteflow-script-plugin/liteflow-script-java/pom.xml
  34. 38 0
      liteflow-script-plugin/liteflow-script-java/src/main/java/com/yomahub/liteflow/script/java/JavaExecutor.java
  35. 1 0
      liteflow-script-plugin/pom.xml
  36. 3 0
      liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowAutoConfiguration.java
  37. 32 0
      liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowProperty.java
  38. 14 0
      liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/spi/solon/SolonCmpAroundAspect.java
  39. 32 0
      liteflow-spring-boot-starter/src/main/java/com/yomahub/liteflow/springboot/LiteflowProperty.java
  40. 3 0
      liteflow-spring-boot-starter/src/main/java/com/yomahub/liteflow/springboot/config/LiteflowPropertyAutoConfiguration.java
  41. 14 0
      liteflow-spring/src/main/java/com/yomahub/liteflow/spi/spring/SpringCmpAroundAspect.java
  42. 1 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/aop/GlobalAOPELDeclMultiSpringbootTest.java
  43. 11 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/aop/aspect/CmpAspect.java
  44. 1 1
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/customWhenThreadPool/CustomWhenThreadPoolELDeclMultiSpringbootTest.java
  45. 179 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/MaxWaitSecondsELDeclMultiSpringbootTest.java
  46. 98 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/CmpConfig.java
  47. 14 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/CustomStatefulException.java
  48. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/CustomThreadExecutor.java
  49. 119 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/ParallelLoopELDeclMultiSpringbootTest.java
  50. 120 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/CmpConfig.java
  51. 93 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/rollback/RollbackELDeclMultiSpringbootTest.java
  52. 136 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/CmpConfig.java
  53. 1 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/maxWaitSeconds/application.properties
  54. 110 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/maxWaitSeconds/flow.el.xml
  55. 4 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/parallelLoop/application.properties
  56. 46 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/parallelLoop/flow.xml
  57. 1 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/rollback/application.properties
  58. 34 0
      liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/rollback/flow.el.xml
  59. 1 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/aop/GlobalAOPELDeclSpringbootTest.java
  60. 11 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/aop/aspect/CmpAspect.java
  61. 1 1
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/customWhenThreadPool/CustomWhenThreadPoolELDeclSpringbootTest.java
  62. 202 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/MaxWaitSecondsELDeclSpringbootTest.java
  63. 21 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/ACmp.java
  64. 21 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/BCmp.java
  65. 21 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/CCmp.java
  66. 26 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/DCmp.java
  67. 18 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/FCmp.java
  68. 18 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/SCmp.java
  69. 31 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/WCmp.java
  70. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/XCmp.java
  71. 14 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/CustomStatefulException.java
  72. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/CustomThreadExecutor.java
  73. 118 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/ParallelLoopELDeclSpringbootTest.java
  74. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/ACmp.java
  75. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/BCmp.java
  76. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/CCmp.java
  77. 32 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/DCmp.java
  78. 35 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/ECmp.java
  79. 22 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/FCmp.java
  80. 20 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/GCmp.java
  81. 21 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/HCmp.java
  82. 24 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/ITCmp.java
  83. 17 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/XCmp.java
  84. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/YCmp.java
  85. 26 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/ZCmp.java
  86. 95 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/RollbackELDeclSpringbootTest.java
  87. 25 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/ACmp.java
  88. 31 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/BCmp.java
  89. 21 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/CCmp.java
  90. 26 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/DCmp.java
  91. 26 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/ECmp.java
  92. 19 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/FCmp.java
  93. 19 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/GCmp.java
  94. 19 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/HCmp.java
  95. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/ICmp.java
  96. 19 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/WCmp.java
  97. 18 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/XCmp.java
  98. 1 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/maxWaitSeconds/application.properties
  99. 110 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/maxWaitSeconds/flow.el.xml
  100. 4 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/parallelLoop/application.properties

+ 4 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/aop/ICmpAroundAspect.java

@@ -22,4 +22,8 @@ public interface ICmpAroundAspect {
 
 	void afterProcess(NodeComponent cmp);
 
+	void onSuccess(NodeComponent cmp);
+
+	void onError(NodeComponent cmp, Exception e);
+
 }

+ 2 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java

@@ -78,6 +78,8 @@ public class LiteFlowChainELBuilder {
 		EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DO, Object.class, new DoOperator());
 		EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BREAK, Object.class, new BreakOperator());
 		EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DATA, Object.class, new DataOperator());
+		EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.MAX_WAIT_SECONDS, Object.class, new MaxWaitSecondsOperator());
+		EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.PARALLEL, Object.class, new ParallelOperator());
 	}
 
 	public static LiteFlowChainELBuilder createChain() {

+ 104 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/MaxWaitSecondsOperator.java

@@ -0,0 +1,104 @@
+package com.yomahub.liteflow.builder.el.operator;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.text.StrFormatter;
+import com.ql.util.express.exception.QLException;
+import com.yomahub.liteflow.builder.el.operator.base.BaseOperator;
+import com.yomahub.liteflow.builder.el.operator.base.OperatorHelper;
+import com.yomahub.liteflow.flow.element.Condition;
+import com.yomahub.liteflow.flow.element.Executable;
+import com.yomahub.liteflow.flow.element.condition.*;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * EL 规则中的 maxWaitSeconds 的操作符
+ *
+ * @author DaleLee
+ * @since 2.11.0
+ */
+public class MaxWaitSecondsOperator extends BaseOperator<Condition> {
+    @Override
+    public Condition build(Object[] objects) throws Exception {
+        OperatorHelper.checkObjectSizeEqTwo(objects);
+        Executable executable = OperatorHelper.convert(objects[0], Executable.class);
+        // 获取传入的时间参数
+        Integer maxWaitSeconds = OperatorHelper.convert(objects[1], Integer.class);
+        if (executable instanceof WhenCondition) {
+            // WhenCondition,直接设置等待时间
+            WhenCondition whenCondition = OperatorHelper.convert(executable, WhenCondition.class);
+            whenCondition.setMaxWaitTime(maxWaitSeconds);
+            whenCondition.setMaxWaitTimeUnit(TimeUnit.SECONDS);
+            return whenCondition;
+        } else if (executable instanceof FinallyCondition) {
+            // FINALLY,报错
+            String errorMsg = StrFormatter.format("The caller [{}] cannot use the keyword \"maxWaitSeconds'\"", executable.toString());
+            throw new QLException(errorMsg);
+        } else if (containsFinally(executable)) {
+            // 处理 THEN 中的 FINALLY
+            ThenCondition thenCondition = OperatorHelper.convert(executable, ThenCondition.class);
+            return handleFinally(thenCondition, maxWaitSeconds);
+        } else {
+            // 其他情况,被 WHEN 包装
+            return wrappedByTimeout(executable, maxWaitSeconds);
+        }
+    }
+
+    /**
+     * 将一个 Executable 包装为带有单独超时控制的 TimeoutCondition
+     *
+     * @param executable     待包装对象
+     * @param maxWaitSeconds 最大等待秒数
+     * @return 包装后的 TimeoutCondition
+     */
+    private TimeoutCondition wrappedByTimeout(Executable executable, Integer maxWaitSeconds) {
+        TimeoutCondition timeoutCondition = new TimeoutCondition();
+        timeoutCondition.addExecutable(executable);
+        timeoutCondition.setMaxWaitTime(maxWaitSeconds);
+        timeoutCondition.setMaxWaitTimeUnit(TimeUnit.SECONDS);
+        return timeoutCondition;
+    }
+
+    /**
+     * 判断 THEN 中是否含有 FINALLY 组件
+     *
+     * @param executable 判断对象
+     * @return 含有 FINALLY 组件返回 true,否则返回 false
+     */
+    private boolean containsFinally(Executable executable) {
+        return executable instanceof ThenCondition
+                && CollUtil.isNotEmpty(((ThenCondition) executable).getFinallyConditionList());
+    }
+
+    /**
+     * 将 FINALLY 排除在超时控制之外
+     *
+     * @param thenCondition  待处理的 ThenCondition
+     * @param maxWaitSeconds 最大等待秒数
+     * @return 处理后的 ThenCondition
+     */
+    private ThenCondition handleFinally(ThenCondition thenCondition, Integer maxWaitSeconds) {
+        // 进行如下转换
+        // THEN(PRE(a),b,FINALLY(c))
+        // => THEN(
+        //      WHEN(THEN(PRE(a),b)),
+        //      FINALLY(c))
+
+        // 定义外层 THEN
+        ThenCondition outerThenCondition = new ThenCondition();
+
+        // 把 FINALLY 转移到外层 THEN
+        List<Executable> finallyList = thenCondition.getExecutableList(ConditionKey.FINALLY_KEY);
+        finallyList.forEach(executable
+                -> outerThenCondition
+                .addFinallyCondition((FinallyCondition) executable));
+        finallyList.clear();
+
+        // 包装内部 THEN
+        WhenCondition whenCondition = wrappedByTimeout(thenCondition, maxWaitSeconds);
+        outerThenCondition.addExecutable(whenCondition);
+
+        return outerThenCondition;
+    }
+}

+ 25 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/ParallelOperator.java

@@ -0,0 +1,25 @@
+package com.yomahub.liteflow.builder.el.operator;
+
+import com.yomahub.liteflow.builder.el.operator.base.BaseOperator;
+import com.yomahub.liteflow.builder.el.operator.base.OperatorHelper;
+import com.yomahub.liteflow.flow.element.condition.LoopCondition;
+
+/**
+ * EL规则中的parallel的操作符
+ *
+ * @author zhhhhy
+ * @since 2.11.0
+ */
+
+public class ParallelOperator extends BaseOperator<LoopCondition> {
+    @Override
+    public LoopCondition build(Object[] objects) throws Exception {
+        OperatorHelper.checkObjectSizeEqTwo(objects);
+
+        LoopCondition loopCondition = OperatorHelper.convert(objects[0], LoopCondition.class);
+
+        Boolean parallel = OperatorHelper.convert(objects[1], Boolean.class);
+        loopCondition.setParallel(parallel);
+        return loopCondition;
+    }
+}

+ 3 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/common/ChainConstant.java

@@ -6,6 +6,7 @@ package com.yomahub.liteflow.common;
  * @author tangkc
  */
 public interface ChainConstant {
+	String PARALLEL = "parallel";
 
 	String CHAIN = "chain";
 
@@ -81,4 +82,6 @@ public interface ChainConstant {
 
 	String NOT = "NOT";
 
+	String MAX_WAIT_SECONDS = "maxWaitSeconds";
+
 }

+ 19 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java

@@ -17,6 +17,8 @@ import com.yomahub.liteflow.flow.FlowBus;
 import com.yomahub.liteflow.flow.LiteflowResponse;
 import com.yomahub.liteflow.flow.element.Chain;
 import com.yomahub.liteflow.flow.element.Node;
+import com.yomahub.liteflow.flow.element.Rollbackable;
+import com.yomahub.liteflow.flow.entity.CmpStep;
 import com.yomahub.liteflow.flow.id.IdGeneratorHolder;
 import com.yomahub.liteflow.log.LFLog;
 import com.yomahub.liteflow.log.LFLoggerManager;
@@ -199,7 +201,7 @@ public class FlowExecutor {
 		}
 
 		// 文件监听
-		if (liteflowConfig.getEnableMonitorFile()) {
+		if (isStart && liteflowConfig.getEnableMonitorFile()) {
 			try {
 				addMonitorFilePaths(rulePathList);
 				MonitorFile.getInstance().create();
@@ -433,6 +435,22 @@ public class FlowExecutor {
 			else {
 				slot.setSubException(chainId, e);
 			}
+			Deque<CmpStep> executeSteps = slot.getExecuteSteps();
+			try {
+				Iterator<CmpStep> cmpStepIterator = executeSteps.descendingIterator();
+				while(cmpStepIterator.hasNext()) {
+					CmpStep cmpStep = cmpStepIterator.next();
+					if(cmpStep.getInstance().isRollback()) {
+						Rollbackable rollbackItem = new Node(cmpStep.getInstance());
+						rollbackItem.rollback(slotIndex);
+					}
+				}
+			} catch (Exception exception) {
+				LOG.error(exception.getMessage());
+			}
+			finally {
+				slot.printRollbackStep();
+			}
 		}
 		finally {
 			if (innerChainType.equals(InnerChainTypeEnum.NONE)) {

+ 63 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/core/NodeComponent.java

@@ -11,6 +11,7 @@ import cn.hutool.core.date.StopWatch;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.ttl.TransmittableThreadLocal;
+import com.yomahub.liteflow.exception.ChainEndException;
 import com.yomahub.liteflow.flow.LiteflowResponse;
 import com.yomahub.liteflow.flow.element.Node;
 import com.yomahub.liteflow.flow.executor.NodeExecutor;
@@ -30,6 +31,7 @@ import com.yomahub.liteflow.flow.element.Executable;
 import com.yomahub.liteflow.monitor.CompStatistics;
 import com.yomahub.liteflow.monitor.MonitorBus;
 
+import java.lang.reflect.Method;
 import java.util.Map;
 
 /**
@@ -56,6 +58,9 @@ public abstract class NodeComponent {
 	// 重试次数
 	private int retryCount = 0;
 
+	// 是否重写了rollback方法
+	private boolean isRollback = false;
+
 	// 在目标异常抛出时才重试
 	private Class<? extends Exception>[] retryForExceptions = new Class[] { Exception.class };
 
@@ -78,6 +83,14 @@ public abstract class NodeComponent {
 	private final TransmittableThreadLocal<Boolean> isEndTL = new TransmittableThreadLocal<>();
 
 	public NodeComponent() {
+		// 反射判断是否重写了rollback方法
+		Class<?> clazz = this.getClass();
+		try {
+			Method method = clazz.getDeclaredMethod("rollback");
+			if(ObjectUtil.isNotNull(method))
+				this.setRollback(true);
+		} catch (Exception e) {
+		}
 	}
 
 	public void execute() throws Exception {
@@ -86,6 +99,7 @@ public abstract class NodeComponent {
 		// 在元数据里加入step信息
 		CmpStep cmpStep = new CmpStep(nodeId, name, CmpStepTypeEnum.SINGLE);
 		cmpStep.setTag(this.getTag());
+		cmpStep.setInstance(this);
 		slot.addStep(cmpStep);
 
 		StopWatch stopWatch = new StopWatch();
@@ -139,6 +153,37 @@ public abstract class NodeComponent {
 		}
 	}
 
+	public void doRollback() throws Exception {
+		Slot slot = this.getSlot();
+
+		CmpStep cmpStep = new CmpStep(nodeId, name, CmpStepTypeEnum.SINGLE);
+		cmpStep.setTag(this.getTag());
+		slot.addRollbackStep(cmpStep);
+
+		StopWatch stopWatch = new StopWatch();
+		stopWatch.start();
+
+		try {
+			self.rollback();
+		}
+		catch (Exception e) {
+			throw e;
+		}
+		finally {
+			stopWatch.stop();
+			final long timeSpent = stopWatch.getTotalTimeMillis();
+			LOG.info("component[{}] rollback in {} milliseconds", this.getDisplayName(), timeSpent);
+
+			// 往CmpStep中放入时间消耗信息
+			cmpStep.setRollbackTimeSpent(timeSpent);
+			// 性能统计
+			if (ObjectUtil.isNotNull(monitorBus)) {
+				CompStatistics statistics = new CompStatistics(this.getClass().getSimpleName(), timeSpent);
+				monitorBus.addStatistics(statistics);
+			}
+		}
+	}
+
 	public void beforeProcess() {
 		// 全局切面只在spring体系下生效,这里用了spi机制取到相应环境下的实现类
 		// 非spring环境下,全局切面为空实现
@@ -147,12 +192,22 @@ public abstract class NodeComponent {
 
 	public abstract void process() throws Exception;
 
+	public void rollback() throws Exception{
+		// 如果需要失败后回滚某个方法,请覆盖这个方法
+	};
+
 	public void onSuccess() throws Exception {
 		// 如果需要在成功后回调某一个方法,请覆盖这个方法
+		// 全局切面只在spring体系下生效,这里用了spi机制取到相应环境下的实现类
+		// 非spring环境下,全局切面为空实现
+		CmpAroundAspectHolder.loadCmpAroundAspect().onSuccess(this.self);
 	}
 
 	public void onError(Exception e) throws Exception {
 		// 如果需要在抛错后回调某一段逻辑,请覆盖这个方法
+		// 全局切面只在spring体系下生效,这里用了spi机制取到相应环境下的实现类
+		// 非spring环境下,全局切面为空实现
+		CmpAroundAspectHolder.loadCmpAroundAspect().onError(this.self, e);
 	}
 
 	public void afterProcess() {
@@ -302,6 +357,14 @@ public abstract class NodeComponent {
 		return getSlot().getChainReqDataFromQueue(this.getCurrChainId());
 	}
 
+	public boolean isRollback() {
+		return isRollback;
+	}
+
+	public void setRollback(boolean rollback) {
+		isRollback = rollback;
+	}
+
 	/**
 	 * @deprecated 请使用 {@link #getChainId()}
 	 * @return String

+ 1 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/core/proxy/ComponentProxy.java

@@ -227,7 +227,7 @@ public class ComponentProxy {
 			}
 
 			try {
-				if (args.length > 0){
+				if (args != null && args.length > 0){
 					Object[] wrapArgs = ArrayUtil.insert(args, 0, proxy);
 					return liteFlowMethodBean.getMethod().invoke(bean, wrapArgs);
 				}else{

+ 3 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/enums/LiteFlowMethodEnum.java

@@ -26,7 +26,9 @@ public enum LiteFlowMethodEnum {
 
 	AFTER_PROCESS("afterProcess", false),
 
-	GET_DISPLAY_NAME("getDisplayName", false)
+	GET_DISPLAY_NAME("getDisplayName", false),
+
+	ROLLBACK("rollback", false)
 	;
 
 	private String methodName;

+ 2 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/enums/ScriptTypeEnum.java

@@ -7,7 +7,8 @@ public enum ScriptTypeEnum {
 	JS("javascript", "js"),
 	PYTHON("python", "python"),
 	LUA("luaj", "lua"),
-	AVIATOR("AviatorScript", "aviator");
+	AVIATOR("AviatorScript", "aviator"),
+	JAVA("java", "java");
 
 	private String engineName;
 

+ 28 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/LiteflowResponse.java

@@ -113,6 +113,34 @@ public class LiteflowResponse {
 		return map;
 	}
 
+	public Queue<CmpStep> getRollbackStepQueue() {
+		return this.getSlot().getRollbackSteps();
+	}
+
+	public String getRollbackStepStr() {
+		return getRollbackStepStrWithoutTime();
+	}
+
+	public String getRollbackStepStrWithTime() {
+		return this.getSlot().getRollbackStepStr(true);
+	}
+
+	public String getRollbackStepStrWithoutTime() {
+		return this.getSlot().getRollbackStepStr(false);
+	}
+
+	public Map<String, List<CmpStep>> getRollbackSteps() {
+		Map<String, List<CmpStep>> map = new LinkedHashMap<>();
+		this.getSlot().getRollbackSteps().forEach(cmpStep -> {
+			if (map.containsKey(cmpStep.getNodeId())){
+				map.get(cmpStep.getNodeId()).add(cmpStep);
+			}else{
+				map.put(cmpStep.getNodeId(), ListUtil.toList(cmpStep));
+			}
+		});
+		return map;
+	}
+
 	public Queue<CmpStep> getExecuteStepQueue() {
 		return this.getSlot().getExecuteSteps();
 	}

+ 23 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Node.java

@@ -31,7 +31,7 @@ import com.yomahub.liteflow.exception.FlowSystemException;
  *
  * @author Bryan.Zhang
  */
-public class Node implements Executable, Cloneable{
+public class Node implements Executable, Cloneable, Rollbackable{
 
 	private static final LFLog LOG = LFLoggerManager.getLogger(Node.class);
 
@@ -174,6 +174,28 @@ public class Node implements Executable, Cloneable{
 		}
 	}
 
+	// 回滚的主要逻辑
+	@Override
+	public void rollback(Integer slotIndex) throws Exception {
+
+		Slot slot = DataBus.getSlot(slotIndex);
+		try {
+			// 把线程属性赋值给组件对象
+			instance.setSlotIndex(slotIndex);
+			instance.setRefNode(this);
+			instance.doRollback();
+		}
+		catch (Exception e) {
+			String errorMsg = StrUtil.format("component[{}] rollback error,error:{}", id, e.getMessage());
+			LOG.error(errorMsg);
+		}
+		finally {
+			// 移除threadLocal里的信息
+			instance.removeSlotIndex();
+			instance.removeRefNode();
+		}
+	}
+
 	// 在同步场景并不会单独执行这方法,同步场景会在execute里面去判断isAccess。
 	// 但是在异步场景的any=true情况下,如果isAccess返回了false,那么异步的any有可能会认为这个组件先执行完。就会导致不正常
 	// 增加这个方法是为了在异步的时候,先去过滤掉isAccess为false的异步组件。然后再异步执行。

+ 13 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Rollbackable.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.flow.element;
+
+
+/**
+ * 回滚接口 目前实现这个接口的只有Node
+ *
+ * @author RainZs
+ */
+public interface Rollbackable {
+
+    void rollback(Integer slotIndex) throws Exception;
+
+}

+ 6 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/CatchCondition.java

@@ -7,6 +7,8 @@ import com.yomahub.liteflow.exception.CatchErrorException;
 import com.yomahub.liteflow.exception.ChainEndException;
 import com.yomahub.liteflow.flow.element.Condition;
 import com.yomahub.liteflow.flow.element.Executable;
+import com.yomahub.liteflow.log.LFLog;
+import com.yomahub.liteflow.log.LFLoggerManager;
 import com.yomahub.liteflow.slot.DataBus;
 import com.yomahub.liteflow.slot.Slot;
 
@@ -18,13 +20,15 @@ import com.yomahub.liteflow.slot.Slot;
  */
 public class CatchCondition extends Condition {
 
+	private final LFLog LOG = LFLoggerManager.getLogger(this.getClass());
+
 	@Override
 	public void executeCondition(Integer slotIndex) throws Exception {
 		Slot slot = DataBus.getSlot(slotIndex);
 		try {
 			Executable catchExecutable = this.getCatchItem();
 			if (ObjectUtil.isNull(catchExecutable)) {
-				String errorInfo = StrUtil.format("[{}]:no catch item find", slot.getRequestId());
+				String errorInfo = "no catch item find";
 				throw new CatchErrorException(errorInfo);
 			}
 			catchExecutable.setCurrChainId(this.getCurrChainId());
@@ -33,6 +37,7 @@ public class CatchCondition extends Condition {
 			//ChainEndException属于用户主动结束流程,不应该进入Catch的流程
 			throw e;
 		}catch (Exception e) {
+			LOG.error("catch exception:" + e.getMessage(), e);
 			Executable doExecutable = this.getDoItem();
 			if (ObjectUtil.isNotNull(doExecutable)) {
 				doExecutable.setCurrChainId(this.getCurrChainId());

+ 85 - 53
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/ForCondition.java

@@ -6,10 +6,17 @@ import com.yomahub.liteflow.enums.ConditionTypeEnum;
 import com.yomahub.liteflow.exception.NoForNodeException;
 import com.yomahub.liteflow.flow.element.Executable;
 import com.yomahub.liteflow.flow.element.Node;
+import com.yomahub.liteflow.flow.parallel.LoopFutureObj;
 import com.yomahub.liteflow.slot.DataBus;
 import com.yomahub.liteflow.slot.Slot;
+import com.yomahub.liteflow.thread.ExecutorHelper;
 import com.yomahub.liteflow.util.LiteFlowProxyUtil;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+
 /**
  * 循环次数Condition
  *
@@ -18,67 +25,92 @@ import com.yomahub.liteflow.util.LiteFlowProxyUtil;
  */
 public class ForCondition extends LoopCondition {
 
-	@Override
-	public void executeCondition(Integer slotIndex) throws Exception {
-		Slot slot = DataBus.getSlot(slotIndex);
-		Node forNode = this.getForNode();
-		if (ObjectUtil.isNull(forNode)) {
-			String errorInfo = StrUtil.format("[{}]:no for-node found", slot.getRequestId());
-			throw new NoForNodeException(errorInfo);
-		}
+    @Override
+    public void executeCondition(Integer slotIndex) throws Exception {
+        Slot slot = DataBus.getSlot(slotIndex);
+        Node forNode = this.getForNode();
+        if (ObjectUtil.isNull(forNode)) {
+            String errorInfo = StrUtil.format("[{}]:no for-node found", slot.getRequestId());
+            throw new NoForNodeException(errorInfo);
+        }
 
-		// 先去判断isAccess方法,如果isAccess方法都返回false,整个FOR表达式不执行
-		if (!this.getForNode().isAccess(slotIndex)) {
-			return;
-		}
+        // 先去判断isAccess方法,如果isAccess方法都返回false,整个FOR表达式不执行
+        if (!this.getForNode().isAccess(slotIndex)) {
+            return;
+        }
 
-		// 执行forCount组件
-		forNode.setCurrChainId(this.getCurrChainId());
-		forNode.execute(slotIndex);
+        // 执行forCount组件
+        forNode.setCurrChainId(this.getCurrChainId());
+        forNode.execute(slotIndex);
 
-		// 获得循环次数
-		int forCount = forNode.getItemResultMetaValue(slotIndex);
+        // 获得循环次数
+        int forCount = forNode.getItemResultMetaValue(slotIndex);
 
-		// 获得要循环的可执行对象
-		Executable executableItem = this.getDoExecutor();
+        // 获得要循环的可执行对象
+        Executable executableItem = this.getDoExecutor();
 
-		// 获取Break节点
-		Executable breakItem = this.getBreakItem();
+        // 获取Break节点
+        Executable breakItem = this.getBreakItem();
 
-		try{
-			// 循环执行
-			for (int i = 0; i < forCount; i++) {
-				executableItem.setCurrChainId(this.getCurrChainId());
-				// 设置循环index
-				setLoopIndex(executableItem, i);
-				executableItem.execute(slotIndex);
-				// 如果break组件不为空,则去执行
-				if (ObjectUtil.isNotNull(breakItem)) {
-					breakItem.setCurrChainId(this.getCurrChainId());
-					setLoopIndex(breakItem, i);
-					breakItem.execute(slotIndex);
-					boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
-					if (isBreak) {
-						break;
-					}
-				}
-			}
-		}finally {
-			removeLoopIndex(executableItem);
-		}
-	}
+        try {
+            if (!isParallel()) {
+                //串行循环执行
+                for (int i = 0; i < forCount; i++) {
+                    executableItem.setCurrChainId(this.getCurrChainId());
+                    // 设置循环index
+                    setLoopIndex(executableItem, i);
+                    executableItem.execute(slotIndex);
+                    // 如果break组件不为空,则去执行
+                    if (ObjectUtil.isNotNull(breakItem)) {
+                        breakItem.setCurrChainId(this.getCurrChainId());
+                        setLoopIndex(breakItem, i);
+                        breakItem.execute(slotIndex);
+                        boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
+                        if (isBreak) {
+                            break;
+                        }
+                    }
+                }
+            }else{
+                //并行循环执行
+                //存储所有的并行执行子项的CompletableFuture
+                List<CompletableFuture<LoopFutureObj>> futureList = new ArrayList<>();
+                //获取并行循环的线程池
+                ExecutorService parallelExecutor = ExecutorHelper.loadInstance().buildLoopParallelExecutor();
+                for (int i = 0; i < forCount; i++){
+                    //提交异步任务
+                    CompletableFuture<LoopFutureObj> future =
+                            CompletableFuture.supplyAsync(new LoopParallelSupplier(executableItem, this.getCurrChainId(), slotIndex, i), parallelExecutor);
+                    futureList.add(future);
+                    if (ObjectUtil.isNotNull(breakItem)) {
+                        breakItem.setCurrChainId(this.getCurrChainId());
+                        setLoopIndex(breakItem, i);
+                        breakItem.execute(slotIndex);
+                        boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
+                        if (isBreak) {
+                            break;
+                        }
+                    }
+                }
+                //等待所有的异步执行完毕
+                handleFutureList(futureList);
+            }
+        } finally {
+            removeLoopIndex(executableItem);
+        }
+    }
 
-	@Override
-	public ConditionTypeEnum getConditionType() {
-		return ConditionTypeEnum.TYPE_FOR;
-	}
+    @Override
+    public ConditionTypeEnum getConditionType() {
+        return ConditionTypeEnum.TYPE_FOR;
+    }
 
-	public Node getForNode() {
-		return (Node) this.getExecutableOne(ConditionKey.FOR_KEY);
-	}
+    public Node getForNode() {
+        return (Node) this.getExecutableOne(ConditionKey.FOR_KEY);
+    }
 
-	public void setForNode(Node forNode) {
-		this.addExecutable(ConditionKey.FOR_KEY, forNode);
-	}
+    public void setForNode(Node forNode) {
+        this.addExecutable(ConditionKey.FOR_KEY, forNode);
+    }
 
 }

+ 106 - 70
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/IteratorCondition.java

@@ -6,83 +6,119 @@ import com.yomahub.liteflow.enums.ConditionTypeEnum;
 import com.yomahub.liteflow.exception.NoIteratorNodeException;
 import com.yomahub.liteflow.flow.element.Executable;
 import com.yomahub.liteflow.flow.element.Node;
+import com.yomahub.liteflow.flow.parallel.LoopFutureObj;
 import com.yomahub.liteflow.slot.DataBus;
 import com.yomahub.liteflow.slot.Slot;
+import com.yomahub.liteflow.thread.ExecutorHelper;
 import com.yomahub.liteflow.util.LiteFlowProxyUtil;
 
+import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
 
 public class IteratorCondition extends LoopCondition {
 
-	@Override
-	public void executeCondition(Integer slotIndex) throws Exception {
-		Slot slot = DataBus.getSlot(slotIndex);
-		Node iteratorNode = this.getIteratorNode();
-
-		if (ObjectUtil.isNull(iteratorNode)) {
-			String errorInfo = StrUtil.format("[{}]:no iterator-node found", slot.getRequestId());
-			throw new NoIteratorNodeException(errorInfo);
-		}
-
-		// 先去判断isAccess方法,如果isAccess方法都返回false,整个ITERATOR表达式不执行
-		if (!iteratorNode.isAccess(slotIndex)) {
-			return;
-		}
-
-		// 执行Iterator组件
-		iteratorNode.setCurrChainId(this.getCurrChainId());
-		iteratorNode.execute(slotIndex);
-
-		Iterator<?> it = iteratorNode.getItemResultMetaValue(slotIndex);
-
-		// 获得要循环的可执行对象
-		Executable executableItem = this.getDoExecutor();
-
-		// 获取Break节点
-		Executable breakItem = this.getBreakItem();
-
-		try{
-			int index = 0;
-			while (it.hasNext()) {
-				Object itObj = it.next();
-
-				executableItem.setCurrChainId(this.getCurrChainId());
-				// 设置循环index
-				setLoopIndex(executableItem, index);
-				// 设置循环迭代器对象
-				setCurrLoopObject(executableItem, itObj);
-				// 执行可执行对象
-				executableItem.execute(slotIndex);
-				// 如果break组件不为空,则去执行
-				if (ObjectUtil.isNotNull(breakItem)) {
-					breakItem.setCurrChainId(this.getCurrChainId());
-					setLoopIndex(breakItem, index);
-					setCurrLoopObject(breakItem, itObj);
-					breakItem.execute(slotIndex);
-					boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
-					if (isBreak) {
-						break;
-					}
-				}
-				index++;
-			}
-		}finally{
-			removeLoopIndex(executableItem);
-			removeCurrLoopObject(executableItem);
-		}
-	}
-
-	@Override
-	public ConditionTypeEnum getConditionType() {
-		return ConditionTypeEnum.TYPE_ITERATOR;
-	}
-
-	public Node getIteratorNode() {
-		return (Node) this.getExecutableOne(ConditionKey.ITERATOR_KEY);
-	}
-
-	public void setIteratorNode(Node iteratorNode) {
-		this.addExecutable(ConditionKey.ITERATOR_KEY, iteratorNode);
-	}
+    @Override
+    public void executeCondition(Integer slotIndex) throws Exception {
+        Slot slot = DataBus.getSlot(slotIndex);
+        Node iteratorNode = this.getIteratorNode();
+
+        if (ObjectUtil.isNull(iteratorNode)) {
+            String errorInfo = StrUtil.format("[{}]:no iterator-node found", slot.getRequestId());
+            throw new NoIteratorNodeException(errorInfo);
+        }
+
+        // 先去判断isAccess方法,如果isAccess方法都返回false,整个ITERATOR表达式不执行
+        if (!iteratorNode.isAccess(slotIndex)) {
+            return;
+        }
+
+        // 执行Iterator组件
+        iteratorNode.setCurrChainId(this.getCurrChainId());
+        iteratorNode.execute(slotIndex);
+
+        Iterator<?> it = iteratorNode.getItemResultMetaValue(slotIndex);
+
+        // 获得要循环的可执行对象
+        Executable executableItem = this.getDoExecutor();
+
+        // 获取Break节点
+        Executable breakItem = this.getBreakItem();
+
+        try {
+            int index = 0;
+            if (!this.isParallel()) {
+                //原本的串行循环执行
+                while (it.hasNext()) {
+                    Object itObj = it.next();
+
+                    executableItem.setCurrChainId(this.getCurrChainId());
+                    // 设置循环index
+                    setLoopIndex(executableItem, index);
+                    // 设置循环迭代器对象
+                    setCurrLoopObject(executableItem, itObj);
+                    // 执行可执行对象
+                    executableItem.execute(slotIndex);
+                    // 如果break组件不为空,则去执行
+                    if (ObjectUtil.isNotNull(breakItem)) {
+                        breakItem.setCurrChainId(this.getCurrChainId());
+                        setLoopIndex(breakItem, index);
+                        setCurrLoopObject(breakItem, itObj);
+                        breakItem.execute(slotIndex);
+                        boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
+                        if (isBreak) {
+                            break;
+                        }
+                    }
+                    index++;
+                }
+            } else {
+                //并行循环执行
+                //存储所有的并行执行子项的CompletableFuture
+                List<CompletableFuture<LoopFutureObj>> futureList = new ArrayList<>();
+                //获取并行循环的线程池
+                ExecutorService parallelExecutor = ExecutorHelper.loadInstance().buildLoopParallelExecutor();
+                while (it.hasNext()) {
+                    Object itObj = it.next();
+                    //提交异步任务
+                    CompletableFuture<LoopFutureObj> future =
+                            CompletableFuture.supplyAsync(new LoopParallelSupplier(executableItem, this.getCurrChainId(), slotIndex, index, itObj), parallelExecutor);
+                    futureList.add(future);
+                    //break判断
+                    if (ObjectUtil.isNotNull(breakItem)) {
+                        breakItem.setCurrChainId(this.getCurrChainId());
+                        setLoopIndex(breakItem, index);
+                        setCurrLoopObject(breakItem, itObj);
+                        breakItem.execute(slotIndex);
+                        boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
+                        if (isBreak) {
+                            break;
+                        }
+                    }
+                    index++;
+                }
+                //等待所有的异步执行完毕
+                handleFutureList(futureList);
+            }
+        } finally {
+            removeLoopIndex(executableItem);
+            removeCurrLoopObject(executableItem);
+        }
+    }
+
+    @Override
+    public ConditionTypeEnum getConditionType() {
+        return ConditionTypeEnum.TYPE_ITERATOR;
+    }
+
+    public Node getIteratorNode() {
+        return (Node) this.getExecutableOne(ConditionKey.ITERATOR_KEY);
+    }
+
+    public void setIteratorNode(Node iteratorNode) {
+        this.addExecutable(ConditionKey.ITERATOR_KEY, iteratorNode);
+    }
 
 }

+ 130 - 67
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/LoopCondition.java

@@ -4,6 +4,11 @@ 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.parallel.LoopFutureObj;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
 
 /**
  * 循环Condition的抽象类 主要继承对象有ForCondition和WhileCondition
@@ -12,73 +17,131 @@ import com.yomahub.liteflow.flow.element.Node;
  * @since 2.9.0
  */
 public abstract class LoopCondition extends Condition {
+    //判断循环是否并行执行,默认为false
+    private boolean parallel = false;
+
+    protected Executable getBreakItem() {
+        return this.getExecutableOne(ConditionKey.BREAK_KEY);
+    }
+
+    public void setBreakItem(Executable breakNode) {
+        this.addExecutable(ConditionKey.BREAK_KEY, breakNode);
+    }
+
+    protected Executable getDoExecutor() {
+        return this.getExecutableOne(ConditionKey.DO_KEY);
+    }
+
+    public void setDoExecutor(Executable executable) {
+        this.addExecutable(ConditionKey.DO_KEY, executable);
+    }
+
+    protected void setLoopIndex(Executable executableItem, int index) {
+        if (executableItem instanceof Chain) {
+            ((Chain) executableItem).getConditionList().forEach(condition -> setLoopIndex(condition, index));
+        } else if (executableItem instanceof Condition) {
+            ((Condition) executableItem).getExecutableGroup()
+                    .forEach((key, value) -> value.forEach(executable -> setLoopIndex(executable, index)));
+        } else if (executableItem instanceof Node) {
+            ((Node) executableItem).setLoopIndex(index);
+        }
+    }
+
+    protected void setCurrLoopObject(Executable executableItem, Object obj) {
+        if (executableItem instanceof Chain) {
+            ((Chain) executableItem).getConditionList().forEach(condition -> setCurrLoopObject(condition, obj));
+        } else if (executableItem instanceof Condition) {
+            ((Condition) executableItem).getExecutableGroup()
+                    .forEach((key, value) -> value.forEach(executable -> setCurrLoopObject(executable, obj)));
+        } else if (executableItem instanceof Node) {
+            ((Node) executableItem).setCurrLoopObject(obj);
+        }
+    }
+
+    protected void removeLoopIndex(Executable executableItem) {
+        if (executableItem instanceof Chain) {
+            ((Chain) executableItem).getConditionList().forEach(this::removeLoopIndex);
+        } else if (executableItem instanceof Condition) {
+            ((Condition) executableItem).getExecutableGroup()
+                    .forEach((key, value) -> value.forEach(this::removeLoopIndex));
+        } else if (executableItem instanceof Node) {
+            ((Node) executableItem).removeLoopIndex();
+        }
+    }
+
+    protected void removeCurrLoopObject(Executable executableItem) {
+        if (executableItem instanceof Chain) {
+            ((Chain) executableItem).getConditionList().forEach(this::removeCurrLoopObject);
+        } else if (executableItem instanceof Condition) {
+            ((Condition) executableItem).getExecutableGroup()
+                    .forEach((key, value) -> value.forEach(this::removeCurrLoopObject));
+        } else if (executableItem instanceof Node) {
+            ((Node) executableItem).removeCurrLoopObject();
+        }
+    }
+
+    public boolean isParallel() {
+        return parallel;
+    }
+
+    public void setParallel(boolean parallel) {
+        this.parallel = parallel;
+    }
+
+    //循环并行执行的futureList处理
+    protected void handleFutureList(List<CompletableFuture<LoopFutureObj>> futureList)throws Exception{
+        CompletableFuture<?> resultCompletableFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{}));
+        resultCompletableFuture.get();
+        //获取所有的执行结果,如果有失败的,那么需要抛出异常
+        for (CompletableFuture<LoopFutureObj> future : futureList) {
+            LoopFutureObj loopFutureObj = future.get();
+            if (!loopFutureObj.isSuccess()) {
+                throw loopFutureObj.getEx();
+            }
+        }
+    }
+
+    // 循环并行执行的Supplier封装
+    public class LoopParallelSupplier implements Supplier<LoopFutureObj> {
+        private final Executable executableItem;
+        private final String currChainId;
+        private final Integer slotIndex;
+        private final Integer loopIndex;
+        private final Object itObj;
+
+        public LoopParallelSupplier(Executable executableItem, String currChainId, Integer slotIndex, Integer loopIndex) {
+            this.executableItem = executableItem;
+            this.currChainId = currChainId;
+            this.slotIndex = slotIndex;
+            this.loopIndex = loopIndex;
+            this.itObj = null;
+        }
+
+        public LoopParallelSupplier(Executable executableItem, String currChainId, Integer slotIndex, Integer loopIndex, Object itObj) {
+            this.executableItem = executableItem;
+            this.currChainId = currChainId;
+            this.slotIndex = slotIndex;
+            this.loopIndex = loopIndex;
+            this.itObj = itObj;
+        }
+
 
-	protected Executable getBreakItem() {
-		return this.getExecutableOne(ConditionKey.BREAK_KEY);
-	}
-
-	public void setBreakItem(Executable breakNode) {
-		this.addExecutable(ConditionKey.BREAK_KEY, breakNode);
-	}
-
-	protected Executable getDoExecutor() {
-		return this.getExecutableOne(ConditionKey.DO_KEY);
-	}
-
-	public void setDoExecutor(Executable executable) {
-		this.addExecutable(ConditionKey.DO_KEY, executable);
-	}
-
-	protected void setLoopIndex(Executable executableItem, int index) {
-		if (executableItem instanceof Chain) {
-			((Chain) executableItem).getConditionList().forEach(condition -> setLoopIndex(condition, index));
-		}
-		else if (executableItem instanceof Condition) {
-			((Condition) executableItem).getExecutableGroup()
-				.forEach((key, value) -> value.forEach(executable -> setLoopIndex(executable, index)));
-		}
-		else if (executableItem instanceof Node) {
-			((Node) executableItem).setLoopIndex(index);
-		}
-	}
-
-	protected void setCurrLoopObject(Executable executableItem, Object obj) {
-		if (executableItem instanceof Chain) {
-			((Chain) executableItem).getConditionList().forEach(condition -> setCurrLoopObject(condition, obj));
-		}
-		else if (executableItem instanceof Condition) {
-			((Condition) executableItem).getExecutableGroup()
-				.forEach((key, value) -> value.forEach(executable -> setCurrLoopObject(executable, obj)));
-		}
-		else if (executableItem instanceof Node) {
-			((Node) executableItem).setCurrLoopObject(obj);
-		}
-	}
-
-	protected void removeLoopIndex(Executable executableItem){
-		if (executableItem instanceof Chain) {
-			((Chain) executableItem).getConditionList().forEach(this::removeLoopIndex);
-		}
-		else if (executableItem instanceof Condition) {
-			((Condition) executableItem).getExecutableGroup()
-					.forEach((key, value) -> value.forEach(this::removeLoopIndex));
-		}
-		else if (executableItem instanceof Node) {
-			((Node) executableItem).removeLoopIndex();
-		}
-	}
-
-	protected void removeCurrLoopObject(Executable executableItem){
-		if (executableItem instanceof Chain) {
-			((Chain) executableItem).getConditionList().forEach(this::removeCurrLoopObject);
-		}
-		else if (executableItem instanceof Condition) {
-			((Condition) executableItem).getExecutableGroup()
-					.forEach((key, value) -> value.forEach(this::removeCurrLoopObject));
-		}
-		else if (executableItem instanceof Node) {
-			((Node) executableItem).removeCurrLoopObject();
-		}
-	}
+        @Override
+        public LoopFutureObj get() {
+            try {
+                executableItem.setCurrChainId(this.currChainId);
+                // 设置循环index
+                setLoopIndex(executableItem, loopIndex);
+                //IteratorCondition的情况下,需要设置当前循环对象
+                if(itObj != null){
+                    setCurrLoopObject(executableItem, itObj);
+                }
+                executableItem.execute(slotIndex);
+                return LoopFutureObj.success(executableItem.getId());
+            } catch (Exception e) {
+                return LoopFutureObj.fail(executableItem.getId(), e);
+            }
+        }
+    }
 
 }

+ 2 - 2
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/SwitchCondition.java

@@ -57,14 +57,14 @@ public class SwitchCondition extends Condition {
 				String _targetId = target[0];
 				String _targetTag = target[1];
 				targetExecutor = targetList.stream().filter(executable -> {
-					return (StrUtil.startWith(_targetId, TAG_PREFIX) && _targetTag.equals(executable.getTag()))
+					return (StrUtil.startWith(_targetId, TAG_PREFIX) && ObjectUtil.equal(_targetTag,executable.getTag()))
 							|| ((StrUtil.isEmpty(_targetId) || _targetId.equals(executable.getId()))
 							&& (StrUtil.isEmpty(_targetTag) || _targetTag.equals(executable.getTag())));
 				}).findFirst().orElse(null);
 			}
 			else {
 				targetExecutor = targetList.stream()
-					.filter(executable -> executable.getId().equals(targetId))
+					.filter(executable -> ObjectUtil.equal(executable.getId(),targetId) )
 					.findFirst()
 					.orElse(null);
 			}

+ 58 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/TimeoutCondition.java

@@ -0,0 +1,58 @@
+package com.yomahub.liteflow.flow.element.condition;
+
+import cn.hutool.core.text.StrFormatter;
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.exception.WhenTimeoutException;
+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 java.util.concurrent.TimeoutException;
+
+/**
+ * 超时控制 Condition
+ *
+ * @author DaleLee
+ * @since 2.11.0
+ */
+public class TimeoutCondition extends WhenCondition {
+
+    @Override
+    public void executeCondition(Integer slotIndex) throws Exception {
+        try {
+            super.executeCondition(slotIndex);
+        } catch (WhenTimeoutException ex) {
+            // 将 WhenTimeoutException 转换为 TimeoutException
+            String errMsg = StrFormatter.format("Timed out when executing the chain [{}] because [{}] exceeded {} {}.",
+                    this.getCurrChainId(), this.getCurrentExecutableId(), this.getMaxWaitTime(), this.getMaxWaitTimeUnit().toString().toLowerCase());
+            throw new TimeoutException(errMsg);
+        }
+    }
+
+    /**
+     * 获取当前组件的 id
+     *
+     * @return
+     */
+    private String getCurrentExecutableId() {
+        // TimeoutCondition 只有一个 Executable
+        Executable executable = this.getExecutableList().get(0);
+        if (ObjectUtil.isNotNull(executable.getId())) {
+            // 已经有 id 了
+            return executable.getId();
+        }
+        // 定义 id
+        switch (executable.getExecuteType()) {
+            // chain 和 node 一般都有 id
+            case CHAIN:
+                return ((Chain) executable).getChainId();
+            case CONDITION:
+                return "condition-" + ((Condition) executable).getConditionType().getName();
+            case NODE:
+                return "node-" + ((Node) executable).getType().getCode();
+            default:
+                return "unknown-executable";
+        }
+    }
+}

+ 36 - 10
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/WhenCondition.java

@@ -52,6 +52,12 @@ public class WhenCondition extends Condition {
 	// when单独的线程池名称
 	private String threadExecutorClass;
 
+	// 异步线程最⻓的等待时间
+	private Integer maxWaitTime;
+
+	// 等待时间单位
+	private TimeUnit maxWaitTimeUnit;
+
 	@Override
 	public void executeCondition(Integer slotIndex) throws Exception {
 		executeAsyncCondition(slotIndex);
@@ -86,15 +92,20 @@ public class WhenCondition extends Condition {
 		// 3.根据condition.getNodeList()的集合进行流处理,用map进行把executable对象转换成List<CompletableFuture<WhenFutureObj>>
 		// 4.在转的过程中,套入CompletableFutureTimeout方法进行超时判断,如果超时则用WhenFutureObj.timeOut返回超时的对象
 		// 5.第2个参数是主要的本体CompletableFuture,传入了ParallelSupplier和线程池对象
-		Integer whenMaxWaitTime;
-		TimeUnit whenMaxWaitTimeUnit;
-
-		if (ObjectUtil.isNotNull(liteflowConfig.getWhenMaxWaitSeconds())){
-			whenMaxWaitTime = liteflowConfig.getWhenMaxWaitSeconds();
-			whenMaxWaitTimeUnit = TimeUnit.SECONDS;
-		}else{
-			whenMaxWaitTime = liteflowConfig.getWhenMaxWaitTime();
-			whenMaxWaitTimeUnit = liteflowConfig.getWhenMaxWaitTimeUnit();
+		if (ObjectUtil.isNull(this.getMaxWaitTime())) {
+			if (ObjectUtil.isNotNull(liteflowConfig.getWhenMaxWaitSeconds())) {
+				// 获取全局异步线程最长等待秒数
+				this.setMaxWaitTime(liteflowConfig.getWhenMaxWaitSeconds());
+				this.setMaxWaitTimeUnit(TimeUnit.SECONDS);
+			} else {
+				// 获取全局异步线程最⻓的等待时间
+				this.setMaxWaitTime(liteflowConfig.getWhenMaxWaitTime());
+			}
+		}
+
+		if (ObjectUtil.isNull(this.getMaxWaitTimeUnit())) {
+			// 获取全局异步线程最⻓的等待时间单位
+			this.setMaxWaitTimeUnit(liteflowConfig.getWhenMaxWaitTimeUnit());
 		}
 
 		List<CompletableFuture<WhenFutureObj>> completableFutureList = this.getExecutableList()
@@ -112,7 +123,7 @@ public class WhenCondition extends Condition {
 						WhenFutureObj.timeOut(executable.getId()),
 						CompletableFuture.supplyAsync(new ParallelSupplier(executable, currChainName, slotIndex),
 								parallelExecutor),
-						whenMaxWaitTime, whenMaxWaitTimeUnit))
+						this.getMaxWaitTime(), this.getMaxWaitTimeUnit()))
 				.collect(Collectors.toList());
 
 		CompletableFuture<?> resultCompletableFuture;
@@ -221,4 +232,19 @@ public class WhenCondition extends Condition {
 		this.threadExecutorClass = threadExecutorClass;
 	}
 
+	public Integer getMaxWaitTime() {
+		return maxWaitTime;
+	}
+
+	public void setMaxWaitTime(Integer maxWaitTime) {
+		this.maxWaitTime = maxWaitTime;
+	}
+
+	public TimeUnit getMaxWaitTimeUnit() {
+		return maxWaitTimeUnit;
+	}
+
+	public void setMaxWaitTimeUnit(TimeUnit maxWaitTimeUnit) {
+		this.maxWaitTimeUnit = maxWaitTimeUnit;
+	}
 }

+ 46 - 13
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/WhileCondition.java

@@ -4,6 +4,13 @@ import cn.hutool.core.util.ObjectUtil;
 import com.yomahub.liteflow.enums.ConditionTypeEnum;
 import com.yomahub.liteflow.flow.element.Executable;
 import com.yomahub.liteflow.flow.element.Node;
+import com.yomahub.liteflow.flow.parallel.LoopFutureObj;
+import com.yomahub.liteflow.thread.ExecutorHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
 
 /**
  * 循环条件Condition
@@ -30,21 +37,47 @@ public class WhileCondition extends LoopCondition {
 
 		// 循环执行
 		int index = 0;
-		while (getWhileResult(slotIndex)) {
-			executableItem.setCurrChainId(this.getCurrChainId());
-			setLoopIndex(executableItem, index);
-			executableItem.execute(slotIndex);
-			// 如果break组件不为空,则去执行
-			if (ObjectUtil.isNotNull(breakItem)) {
-				breakItem.setCurrChainId(this.getCurrChainId());
-				setLoopIndex(breakItem, index);
-				breakItem.execute(slotIndex);
-				boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
-				if (isBreak) {
-					break;
+		if(!this.isParallel()){
+			//串行循环
+			while (getWhileResult(slotIndex)) {
+				executableItem.setCurrChainId(this.getCurrChainId());
+				setLoopIndex(executableItem, index);
+				executableItem.execute(slotIndex);
+				// 如果break组件不为空,则去执行
+				if (ObjectUtil.isNotNull(breakItem)) {
+					breakItem.setCurrChainId(this.getCurrChainId());
+					setLoopIndex(breakItem, index);
+					breakItem.execute(slotIndex);
+					boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
+					if (isBreak) {
+						break;
+					}
+				}
+				index++;
+			}
+		}else{
+			//并行循环逻辑
+			List<CompletableFuture<LoopFutureObj>> futureList = new ArrayList<>();
+			//获取并行循环的线程池
+			ExecutorService parallelExecutor = ExecutorHelper.loadInstance().buildLoopParallelExecutor();
+			while (getWhileResult(slotIndex)){
+				CompletableFuture<LoopFutureObj> future =
+						CompletableFuture.supplyAsync(new LoopParallelSupplier(executableItem, this.getCurrChainId(), slotIndex, index), parallelExecutor);
+				futureList.add(future);
+				//break判断
+				if (ObjectUtil.isNotNull(breakItem)) {
+					breakItem.setCurrChainId(this.getCurrChainId());
+					setLoopIndex(breakItem, index);
+					breakItem.execute(slotIndex);
+					boolean isBreak = breakItem.getItemResultMetaValue(slotIndex);
+					if (isBreak) {
+						break;
+					}
 				}
+				index++;
 			}
-			index++;
+			//等待所有的异步执行完毕
+			handleFutureList(futureList);
 		}
 	}
 

+ 47 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/entity/CmpStep.java

@@ -10,6 +10,7 @@ package com.yomahub.liteflow.flow.entity;
 
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import com.yomahub.liteflow.core.NodeComponent;
 import com.yomahub.liteflow.enums.CmpStepTypeEnum;
 
 /**
@@ -37,6 +38,11 @@ public class CmpStep {
 	// 但是success为false,不一定有exception,因为有可能没执行到,或者没执行结束(any)
 	private Exception exception;
 
+	private NodeComponent instance;
+
+	// 回滚消耗的时间
+	private Long rollbackTimeSpent;
+
 	public CmpStep(String nodeId, String nodeName, CmpStepTypeEnum stepType) {
 		this.nodeId = nodeId;
 		this.nodeName = nodeName;
@@ -91,6 +97,22 @@ public class CmpStep {
 		this.exception = exception;
 	}
 
+	public NodeComponent getInstance() {
+		return instance;
+	}
+
+	public void setInstance(NodeComponent instance) {
+		this.instance = instance;
+	}
+
+	public Long getRollbackTimeSpent() {
+		return rollbackTimeSpent;
+	}
+
+	public void setRollbackTimeSpent(Long rollbackTimeSpent) {
+		this.rollbackTimeSpent = rollbackTimeSpent;
+	}
+
 	public String buildString() {
 		if (stepType.equals(CmpStepTypeEnum.SINGLE)) {
 			if (StrUtil.isBlank(nodeName)) {
@@ -131,6 +153,31 @@ public class CmpStep {
 		}
 	}
 
+	public String buildRollbackStringWithTime() {
+		if (stepType.equals(CmpStepTypeEnum.SINGLE)) {
+			if (StrUtil.isBlank(nodeName)) {
+				if (rollbackTimeSpent != null) {
+					return StrUtil.format("{}<{}>", nodeId, rollbackTimeSpent);
+				}
+				else {
+					return StrUtil.format("{}", nodeId);
+				}
+			}
+			else {
+				if (rollbackTimeSpent != null) {
+					return StrUtil.format("{}[{}]<{}>", nodeId, nodeName, rollbackTimeSpent);
+				}
+				else {
+					return StrUtil.format("{}[{}]", nodeId, nodeName);
+				}
+			}
+		}
+		else {
+			// 目前没有其他的类型
+			return null;
+		}
+	}
+
 	@Override
 	public boolean equals(Object obj) {
 		if (ObjectUtil.isNull(obj)) {

+ 56 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/parallel/LoopFutureObj.java

@@ -0,0 +1,56 @@
+package com.yomahub.liteflow.flow.parallel;
+
+/**
+ * 并行循环各个并行子项的执行结果对象
+ * 如果该子项执行成功,则success为true,ex为null
+ * 否则success为false,ex为程序抛出异常
+ *
+ * @author zhhhhy
+ * @since 2.11.0
+ */
+
+public class LoopFutureObj {
+    private String executorName;
+    private boolean success;
+    private Exception ex;
+
+
+    public static LoopFutureObj success(String executorName) {
+        LoopFutureObj result = new LoopFutureObj();
+        result.setSuccess(true);
+        result.setExecutorName(executorName);
+        return result;
+    }
+
+    public static LoopFutureObj fail(String executorName, Exception ex) {
+        LoopFutureObj result = new LoopFutureObj();
+        result.setSuccess(false);
+        result.setExecutorName(executorName);
+        result.setEx(ex);
+        return result;
+    }
+
+    public Exception getEx() {
+        return ex;
+    }
+
+    public String getExecutorName() {
+        return executorName;
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setEx(Exception ex) {
+        this.ex = ex;
+    }
+
+    public void setExecutorName(String executorName) {
+        this.executorName = executorName;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+}

+ 45 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/property/LiteflowConfig.java

@@ -103,6 +103,15 @@ public class LiteflowConfig {
 	// 规则文件/脚本文件变更监听
 	private Boolean enableMonitorFile = Boolean.FALSE;
 
+	//并行循环线程池所用class路径
+	private String parallelLoopExecutorClass;
+
+	//使用默认并行循环线程池时,最大线程数
+	private Integer parallelMaxWorkers;
+
+	//使用默认并行循环线程池时,最大队列数
+	private Integer parallelQueueLimit;
+
 	public Boolean getEnableMonitorFile() {
 		return enableMonitorFile;
 	}
@@ -409,4 +418,40 @@ public class LiteflowConfig {
 	public void setWhenMaxWaitTimeUnit(TimeUnit whenMaxWaitTimeUnit) {
 		this.whenMaxWaitTimeUnit = whenMaxWaitTimeUnit;
 	}
+
+	public Integer getParallelMaxWorkers() {
+		if(ObjectUtil.isNull(parallelMaxWorkers)){
+			return 16;
+		}else{
+			return parallelMaxWorkers;
+		}
+	}
+
+	public void setParallelMaxWorkers(Integer parallelMaxWorkers) {
+		this.parallelMaxWorkers = parallelMaxWorkers;
+	}
+
+	public Integer getParallelQueueLimit() {
+		if(ObjectUtil.isNull(parallelQueueLimit)){
+			return 512;
+		}else{
+			return parallelQueueLimit;
+		}
+	}
+
+	public void setParallelQueueLimit(Integer parallelQueueLimit) {
+		this.parallelQueueLimit = parallelQueueLimit;
+	}
+
+	public String getParallelLoopExecutorClass() {
+		if (StrUtil.isBlank(parallelLoopExecutorClass)) {
+			return "com.yomahub.liteflow.thread.LiteFlowDefaultParallelLoopExecutorBuilder";
+		}
+		else {
+			return parallelLoopExecutorClass;
+		}
+	}
+	public void setParallelLoopExecutorClass(String parallelLoopExecutorClass) {
+		this.parallelLoopExecutorClass = parallelLoopExecutorClass;
+	}
 }

+ 45 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java

@@ -81,6 +81,10 @@ public class Slot {
 
 	private String executeStepsStr;
 
+	private final Deque<CmpStep> rollbackSteps = new ConcurrentLinkedDeque<>();
+
+	private String rollbackStepsStr;
+
 	protected ConcurrentHashMap<String, Object> metaDataMap = new ConcurrentHashMap<>();
 
 	private List<Object> contextBeanList;
@@ -346,6 +350,43 @@ public class Slot {
 		}
 	}
 
+	public void addRollbackStep(CmpStep step) {
+		this.rollbackSteps.add(step);
+	}
+
+	public String getRollbackStepStr(boolean withRollbackTimeSpent) {
+		StringBuilder str = new StringBuilder();
+		CmpStep cmpStep;
+		for (Iterator<CmpStep> it = rollbackSteps.iterator(); it.hasNext();) {
+			cmpStep = it.next();
+			if (withRollbackTimeSpent) {
+				str.append(cmpStep.buildRollbackStringWithTime());
+			}
+			else {
+				str.append(cmpStep.buildString());
+			}
+			if (it.hasNext()) {
+				str.append("==>");
+			}
+		}
+		this.rollbackStepsStr = str.toString();
+		return this.rollbackStepsStr;
+	}
+
+	public String getRollbackStepStr() {
+		return getRollbackStepStr(false);
+	}
+
+	public void printRollbackStep() {
+		if (ObjectUtil.isNull(this.rollbackStepsStr)) {
+			this.rollbackStepsStr = getRollbackStepStr(true);
+		}
+		if (LiteflowConfigGetter.get().getPrintExecutionLog()) {
+			LOG.info("ROLLBACK_CHAIN_NAME[{}]\n{}", this.getChainName(), this.rollbackStepsStr);
+		}
+	}
+
+
 	public void generateRequestId() {
 		metaDataMap.put(REQUEST_ID, IdGeneratorHolder.getInstance().generate());
 	}
@@ -362,6 +403,10 @@ public class Slot {
 		return executeSteps;
 	}
 
+	public Deque<CmpStep> getRollbackSteps() {
+		return rollbackSteps;
+	}
+
 	public Exception getException() {
 		return (Exception) this.metaDataMap.get(EXCEPTION);
 	}

+ 4 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/spi/CmpAroundAspect.java

@@ -15,4 +15,8 @@ public interface CmpAroundAspect extends SpiPriority {
 
 	void afterProcess(NodeComponent cmp);
 
+	void onSuccess(NodeComponent cmp);
+
+	void onError(NodeComponent cmp, Exception e);
+
 }

+ 10 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/spi/local/LocalCmpAroundAspect.java

@@ -22,6 +22,16 @@ public class LocalCmpAroundAspect implements CmpAroundAspect {
 		// 无spring环境下为空实现
 	}
 
+	@Override
+	public void onSuccess(NodeComponent cmp) {
+		// 无spring环境下为空实现
+	}
+
+	@Override
+	public void onError(NodeComponent cmp, Exception e) {
+		// 无spring环境下为空实现
+	}
+
 	@Override
 	public int priority() {
 		return 2;

+ 6 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/thread/ExecutorHelper.java

@@ -113,6 +113,12 @@ public class ExecutorHelper {
 		return getExecutorService(clazz);
 	}
 
+	//构造并行循环的线程池
+	public ExecutorService buildLoopParallelExecutor(){
+		LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
+		return getExecutorService(liteflowConfig.getParallelLoopExecutorClass());
+	}
+
 	/**
 	 * 根据线程执行构建者Class类名获取ExecutorService实例
 	 */

+ 1 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/thread/LiteFlowDefaultMainExecutorBuilder.java

@@ -21,7 +21,7 @@ public class LiteFlowDefaultMainExecutorBuilder implements ExecutorBuilder {
 			liteflowConfig = new LiteflowConfig();
 		}
 		return buildDefaultExecutor(liteflowConfig.getMainExecutorWorks(), liteflowConfig.getMainExecutorWorks(), 200,
-				"lf-main-thead-");
+				"main-thread-");
 	}
 
 }

+ 27 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/thread/LiteFlowDefaultParallelLoopExecutorBuilder.java

@@ -0,0 +1,27 @@
+package com.yomahub.liteflow.thread;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.property.LiteflowConfig;
+import com.yomahub.liteflow.property.LiteflowConfigGetter;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * LiteFlow默认的并行循环执行器实现
+ *
+ * @author zhhhhy
+ * @since 2.11.0
+ */
+
+public class LiteFlowDefaultParallelLoopExecutorBuilder implements ExecutorBuilder {
+    @Override
+    public ExecutorService buildExecutor() {
+        LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
+        // 只有在非spring的场景下liteflowConfig才会为null
+        if (ObjectUtil.isNull(liteflowConfig)) {
+            liteflowConfig = new LiteflowConfig();
+        }
+        return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
+                liteflowConfig.getParallelQueueLimit(), "loop-thread-");
+    }
+}

+ 1 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/thread/LiteFlowDefaultWhenExecutorBuilder.java

@@ -22,7 +22,7 @@ public class LiteFlowDefaultWhenExecutorBuilder implements ExecutorBuilder {
 			liteflowConfig = new LiteflowConfig();
 		}
 		return buildDefaultExecutor(liteflowConfig.getWhenMaxWorkers(), liteflowConfig.getWhenMaxWorkers(),
-				liteflowConfig.getWhenQueueLimit(), "lf-when-thead-");
+				liteflowConfig.getWhenQueueLimit(), "when-thread-");
 	}
 
 }

+ 2 - 2
liteflow-rule-plugin/liteflow-rule-sql/src/main/java/com/yomahub/liteflow/parser/sql/util/JDBCHelper.java

@@ -32,7 +32,7 @@ public class JDBCHelper {
 
     private static final String SCRIPT_WITH_LANGUAG_SQL_PATTERN = "SELECT {},{},{},{},{} FROM {} WHERE {}=?";
 
-    private static final String CHAIN_XML_PATTERN = "<chain name=\"{}\">{}</chain>";
+    private static final String CHAIN_XML_PATTERN = "<chain name=\"{}\"><![CDATA[{}]]></chain>";
 
     private static final String NODE_XML_PATTERN = "<nodes>{}</nodes>";
 
@@ -243,7 +243,7 @@ public class JDBCHelper {
                 }
 
                 if (!ScriptTypeEnum.checkScriptType(language)) {
-                    throw new ELSQLException(StrUtil.format("The language value[{}] is error", language));
+                    throw new ELSQLException(StrUtil.format("The language value[{}] is invalid", language));
                 }
 
                 result.add(StrUtil.format(NODE_ITEM_WITH_LANGUAGE_XML_PATTERN, XmlUtil.escape(id), XmlUtil.escape(name),

+ 27 - 0
liteflow-script-plugin/liteflow-script-java/pom.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.yomahub</groupId>
+        <artifactId>liteflow-script-plugin</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>liteflow-script-java</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.yomahub</groupId>
+            <artifactId>liteflow-core</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.janino</groupId>
+            <artifactId>janino</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 38 - 0
liteflow-script-plugin/liteflow-script-java/src/main/java/com/yomahub/liteflow/script/java/JavaExecutor.java

@@ -0,0 +1,38 @@
+package com.yomahub.liteflow.script.java;
+
+import com.yomahub.liteflow.enums.ScriptTypeEnum;
+import com.yomahub.liteflow.script.ScriptExecuteWrap;
+import com.yomahub.liteflow.script.ScriptExecutor;
+import org.codehaus.commons.compiler.CompilerFactoryFactory;
+import org.codehaus.commons.compiler.IScriptEvaluator;
+
+public class JavaExecutor extends ScriptExecutor {
+    @Override
+    public void load(String nodeId, String script) {
+        // 创建Janino脚本Evaluator
+        /*IScriptEvaluator se = CompilerFactoryFactory.getDefaultCompilerFactory().newScriptEvaluator();
+        // 返回值类型指定为Object以支持不同脚本
+        se.setReturnType(Object.class);
+        // 指定Janino脚本里的变量名及类型,为通用起见,只设置一个Object类型的变量
+        se.setParameters(new String[] { JANINO_SCRIPT_PARAMETER_NAME }, new Class[] { Object.class });
+        // 编译
+        se.cook(script);
+        // 缓存编译过的Evaluator
+        compiledScriptMap.put(nodeId, se);*/
+    }
+
+    @Override
+    public Object executeScript(ScriptExecuteWrap wrap) throws Exception {
+        return null;
+    }
+
+    @Override
+    public void cleanCache() {
+
+    }
+
+    @Override
+    public ScriptTypeEnum scriptType() {
+        return null;
+    }
+}

+ 1 - 0
liteflow-script-plugin/pom.xml

@@ -22,6 +22,7 @@
         <module>liteflow-script-python</module>
         <module>liteflow-script-lua</module>
         <module>liteflow-script-aviator</module>
+        <module>liteflow-script-java</module>
     </modules>
 
 </project>

+ 3 - 0
liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowAutoConfiguration.java

@@ -47,6 +47,9 @@ public class LiteflowAutoConfiguration {
 		liteflowConfig.setMainExecutorClass(property.getMainExecutorClass());
 		liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog());
 		liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass());
+		liteflowConfig.setParallelMaxWorkers(property.getParallelMaxWorkers());
+		liteflowConfig.setParallelQueueLimit(property.getParallelQueueLimit());
+		liteflowConfig.setParallelLoopExecutorClass(property.getParallelLoopExecutorClass());
 		return liteflowConfig;
 	}
 

+ 32 - 0
liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowProperty.java

@@ -70,6 +70,15 @@ public class LiteflowProperty {
 	// 替补组件的class路径
 	private String substituteCmpClass;
 
+	//并行循环线程池类路径
+	private String parallelLoopExecutorClass;
+
+	//使用默认并行循环线程池时,最大线程数
+	private Integer parallelMaxWorkers;
+
+	//使用默认并行循环线程池时,最大队列数
+	private Integer parallelQueueLimit;
+
 	public boolean isEnable() {
 		return enable;
 	}
@@ -219,4 +228,27 @@ public class LiteflowProperty {
 		this.ruleSourceExtData = ruleSourceExtData;
 	}
 
+	public String getParallelLoopExecutorClass() {
+		return parallelLoopExecutorClass;
+	}
+
+	public void setParallelLoopExecutorClass(String parallelLoopExecutorClass) {
+		this.parallelLoopExecutorClass = parallelLoopExecutorClass;
+	}
+
+	public Integer getParallelMaxWorkers() {
+		return parallelMaxWorkers;
+	}
+
+	public void setParallelMaxWorkers(Integer parallelMaxWorkers) {
+		this.parallelMaxWorkers = parallelMaxWorkers;
+	}
+
+	public Integer getParallelQueueLimit() {
+		return parallelQueueLimit;
+	}
+
+	public void setParallelQueueLimit(Integer parallelQueueLimit) {
+		this.parallelQueueLimit = parallelQueueLimit;
+	}
 }

+ 14 - 0
liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/spi/solon/SolonCmpAroundAspect.java

@@ -37,6 +37,20 @@ public class SolonCmpAroundAspect implements CmpAroundAspect {
 		}
 	}
 
+	@Override
+	public void onSuccess(NodeComponent cmp) {
+		if (ObjectUtil.isNotNull(cmpAroundAspect)) {
+			cmpAroundAspect.onSuccess(cmp);
+		}
+	}
+
+	@Override
+	public void onError(NodeComponent cmp, Exception e) {
+		if (ObjectUtil.isNotNull(cmpAroundAspect)) {
+			cmpAroundAspect.onError(cmp, e);
+		}
+	}
+
 	@Override
 	public int priority() {
 		return 1;

+ 32 - 0
liteflow-spring-boot-starter/src/main/java/com/yomahub/liteflow/springboot/LiteflowProperty.java

@@ -80,6 +80,14 @@ public class LiteflowProperty {
 	// 规则文件/脚本文件变更监听
 	private Boolean enableMonitorFile;
 
+	private String parallelLoopExecutorClass;
+
+	//使用默认并行循环线程池时,最大线程数
+	private Integer parallelMaxWorkers;
+
+	//使用默认并行循环线程池时,最大队列数
+	private Integer parallelQueueLimit;
+
 	public Boolean getEnableMonitorFile() {
 		return enableMonitorFile;
 	}
@@ -257,4 +265,28 @@ public class LiteflowProperty {
 	public void setWhenMaxWaitTimeUnit(TimeUnit whenMaxWaitTimeUnit) {
 		this.whenMaxWaitTimeUnit = whenMaxWaitTimeUnit;
 	}
+
+	public String getParallelLoopExecutorClass() {
+		return parallelLoopExecutorClass;
+	}
+
+	public void setParallelLoopExecutorClass(String parallelLoopExecutorClass) {
+		this.parallelLoopExecutorClass = parallelLoopExecutorClass;
+	}
+
+	public Integer getParallelMaxWorkers() {
+		return parallelMaxWorkers;
+	}
+
+	public void setParallelMaxWorkers(Integer parallelMaxWorkers) {
+		this.parallelMaxWorkers = parallelMaxWorkers;
+	}
+
+	public Integer getParallelQueueLimit() {
+		return parallelQueueLimit;
+	}
+
+	public void setParallelQueueLimit(Integer parallelQueueLimit) {
+		this.parallelQueueLimit = parallelQueueLimit;
+	}
 }

+ 3 - 0
liteflow-spring-boot-starter/src/main/java/com/yomahub/liteflow/springboot/config/LiteflowPropertyAutoConfiguration.java

@@ -48,6 +48,9 @@ public class LiteflowPropertyAutoConfiguration {
 		liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog());
 		liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass());
 		liteflowConfig.setEnableMonitorFile(property.getEnableMonitorFile());
+		liteflowConfig.setParallelMaxWorkers(property.getParallelMaxWorkers());
+		liteflowConfig.setParallelQueueLimit(property.getParallelQueueLimit());
+		liteflowConfig.setParallelLoopExecutorClass(property.getParallelLoopExecutorClass());
 		return liteflowConfig;
 	}
 

+ 14 - 0
liteflow-spring/src/main/java/com/yomahub/liteflow/spi/spring/SpringCmpAroundAspect.java

@@ -28,6 +28,20 @@ public class SpringCmpAroundAspect implements CmpAroundAspect {
 		}
 	}
 
+	@Override
+	public void onSuccess(NodeComponent cmp) {
+		if (ObjectUtil.isNotNull(ComponentScanner.cmpAroundAspect)) {
+			ComponentScanner.cmpAroundAspect.onSuccess(cmp);
+		}
+	}
+
+	@Override
+	public void onError(NodeComponent cmp, Exception e) {
+		if (ObjectUtil.isNotNull(ComponentScanner.cmpAroundAspect)) {
+			ComponentScanner.cmpAroundAspect.onError(cmp, e);
+		}
+	}
+
 	@Override
 	public int priority() {
 		return 1;

+ 1 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/aop/GlobalAOPELDeclMultiSpringbootTest.java

@@ -72,6 +72,7 @@ public class GlobalAOPELDeclMultiSpringbootTest extends BaseTest {
 		Assertions.assertEquals("before_after", context.getData("b"));
 		Assertions.assertEquals("before_after", context.getData("c"));
 		Assertions.assertEquals("before_after", context.getData("f"));
+		Assertions.assertEquals("test error", context.getData("f_error"));
 	}
 
 	@AfterAll

+ 11 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/aop/aspect/CmpAspect.java

@@ -20,4 +20,15 @@ public class CmpAspect implements ICmpAroundAspect {
 		context.setData(cmp.getNodeId(), StrUtil.format("{}_{}", context.getData(cmp.getNodeId()), "after"));
 	}
 
+	@Override
+	public void onSuccess(NodeComponent cmp) {
+
+	}
+
+	@Override
+	public void onError(NodeComponent cmp, Exception e) {
+		DefaultContext context = cmp.getFirstContextBean();
+		context.setData(cmp.getNodeId()+"_error", e.getMessage());
+	}
+
 }

+ 1 - 1
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/customWhenThreadPool/CustomWhenThreadPoolELDeclMultiSpringbootTest.java

@@ -44,7 +44,7 @@ public class CustomWhenThreadPoolELDeclMultiSpringbootTest extends BaseTest {
 		LiteflowResponse response = flowExecutor.execute2Resp("chain", "arg");
 		DefaultContext context = response.getFirstContextBean();
 		Assertions.assertTrue(response.isSuccess());
-		Assertions.assertTrue(context.getData("threadName").toString().startsWith("lf-when-thead"));
+		Assertions.assertTrue(context.getData("threadName").toString().startsWith("when-thread-1"));
 	}
 
 	/**

+ 179 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/MaxWaitSecondsELDeclMultiSpringbootTest.java

@@ -0,0 +1,179 @@
+package com.yomahub.liteflow.test.maxWaitSeconds;
+
+import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder;
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.exception.WhenTimeoutException;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.slot.DefaultContext;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeoutException;
+
+import static com.yomahub.liteflow.test.maxWaitSeconds.cmp.CmpConfig.CONTENT_KEY;
+
+/**
+ * Spring Boot 环境下超时控制测试
+ *
+ * @author DaleLee
+ * @since 2.11.0
+ */
+@ExtendWith(SpringExtension.class)
+@TestPropertySource(value = "classpath:/maxWaitSeconds/application.properties")
+@SpringBootTest(classes = MaxWaitSecondsELDeclMultiSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.maxWaitSeconds.cmp"})
+public class MaxWaitSecondsELDeclMultiSpringbootTest extends BaseTest {
+    @Resource
+    private FlowExecutor flowExecutor;
+
+    // 测试 THEN 的超时情况
+    @Test
+    public void testThen1() {
+        assertTimeout("then1");
+    }
+
+    // 测试 THEN 的非超时情况
+    @Test
+    public void testThen2() {
+        assertNotTimeout("then2");
+    }
+
+    // 测试 When 的超时情况
+    @Test
+    public void testWhen1() {
+        assertWhenTimeout("when1");
+    }
+
+    // 测试 WHEN 的非超时情况
+    @Test
+    public void testWhen2() {
+        assertNotTimeout("when2");
+    }
+
+    // 测试 FOR 的超时情况
+    @Test
+    public void testFor1() {
+        assertTimeout("for1");
+    }
+
+    // 测试 FOR 的非超时情况
+    @Test
+    public void testFor2() {
+        assertNotTimeout("for2");
+    }
+
+    // 测试 WHILE 的超时情况
+    @Test
+    public void testWhile1() {
+        assertTimeout("while1");
+    }
+
+    // 测试 WHILE 的非超时情况
+    @Test
+    public void testWhile2() {
+        assertNotTimeout("while2");
+    }
+
+    // 测试 ITERATOR 的超时情况
+    @Test
+    public void testIterator1() {
+        assertTimeout("iterator1");
+    }
+
+    // 测试 ITERATOR 的非超时情况
+    @Test
+    public void testIterator2() {
+        assertNotTimeout("iterator2");
+    }
+
+    // 测试 SWITCH 的超时情况
+    @Test
+    public void testSwitch1() {
+        assertTimeout("switch1");
+    }
+
+    // 测试 SWITCH 的非超时情况
+    @Test
+    public void testSwitch2() {
+        assertNotTimeout("switch2");
+    }
+
+    // 测试 IF 的超时情况
+    @Test
+    public void testIf1() {
+        assertTimeout("if1");
+    }
+
+    // 测试 SWITCH 的非超时情况
+    @Test
+    public void testIf2() {
+        assertNotTimeout("if2");
+    }
+
+    // 测试单个组件的超时情况
+    @Test
+    public void testComponent1() {
+        assertTimeout("component1");
+    }
+
+    // 测试单个组件的非超时情况
+    @Test
+    public void testComponent2() {
+        assertNotTimeout("component2");
+    }
+
+    // 测试 FINALLY,虽然超时,但 FINALLY 仍会执行
+    @Test
+    public void testFinally1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("finally", "arg");
+        Assertions.assertFalse(response.isSuccess());
+        Assertions.assertEquals(TimeoutException.class, response.getCause().getClass());
+        // FINALLY 执行时在默认数据上下文中放入了 CONTENT_KEY
+        DefaultContext contextBean = response.getFirstContextBean();
+        Assertions.assertTrue(contextBean.hasData(CONTENT_KEY));
+    }
+
+    // 测试 maxWaitSeconds 关键字不能作用于 Finally
+    @Test
+    public void testFinally2() {
+        Assertions.assertFalse(LiteFlowChainELBuilder.validate("THEN(a, b, FINALLY(c).maxWaitSeconds(10))"));
+    }
+
+    // 测试 chain 的超时情况
+    @Test
+    public void testChain1() {
+        assertTimeout("chain1");
+    }
+
+    // 测试 chain 的非超时情况
+    @Test
+    public void testChain2() {
+        assertNotTimeout("chain2");
+    }
+
+    private void assertTimeout(String chainId) {
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        Assertions.assertFalse(response.isSuccess());
+        Assertions.assertEquals(TimeoutException.class, response.getCause().getClass());
+    }
+
+    private void assertWhenTimeout(String chainId) {
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        Assertions.assertFalse(response.isSuccess());
+        Assertions.assertEquals(WhenTimeoutException.class, response.getCause().getClass());
+    }
+
+    private void assertNotTimeout(String chainId) {
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        Assertions.assertTrue(response.isSuccess());
+    }
+}

+ 98 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/CmpConfig.java

@@ -0,0 +1,98 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import cn.hutool.core.collection.ListUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+import com.yomahub.liteflow.slot.DefaultContext;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+@LiteflowComponent
+public class CmpConfig {
+
+    public static final String CONTENT_KEY = "testKey";
+
+    private int count = 0;
+
+    // 执行过的 chain
+    Set<String> executedChain = new HashSet<>();
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "a")
+    public void processA(NodeComponent bindCmp) {
+        try {
+            Thread.sleep(1000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("ACmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "b")
+    public void processB(NodeComponent bindCmp) {
+        try {
+            Thread.sleep(2000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("BCmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "c")
+    public void process(NodeComponent bindCmp) {
+        try {
+            Thread.sleep(5000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("CCmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "d")
+    public void processD(NodeComponent bindCmp) {
+        try {
+            Thread.sleep(500);
+            DefaultContext contextBean = bindCmp.getFirstContextBean();
+            contextBean.setData(CONTENT_KEY, "value");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("DCmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_IF, nodeId = "f", nodeType = NodeTypeEnum.IF)
+    public boolean processIf(NodeComponent bindCmp) throws Exception {
+        return true;
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_SWITCH, nodeId = "s", nodeType = NodeTypeEnum.SWITCH)
+    public String processSwitch(NodeComponent bindCmp) throws Exception {
+        return "b";
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE, nodeId = "w", nodeType = NodeTypeEnum.WHILE)
+    public boolean processWhile(NodeComponent bindCmp) throws Exception {
+        // 判断是否切换了 chain
+        if (!executedChain.contains(bindCmp.getCurrChainId())) {
+            count = 0;
+            executedChain.add(bindCmp.getCurrChainId());
+        }
+        count++;
+        return count <= 2;
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_ITERATOR, nodeId = "x", nodeType = NodeTypeEnum.ITERATOR)
+    public Iterator<?> processIterator(NodeComponent bindCmp) throws Exception {
+        List<String> list = ListUtil.toList("one", "two");
+        return list.iterator();
+    }
+}

+ 14 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/CustomStatefulException.java

@@ -0,0 +1,14 @@
+package com.yomahub.liteflow.test.parallelLoop;
+
+import com.yomahub.liteflow.exception.LiteFlowException;
+
+/**
+ * 用户自定义带状态码的异常
+ */
+public class CustomStatefulException extends LiteFlowException {
+
+	public CustomStatefulException(String code, String message) {
+		super(code, message);
+	}
+
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/CustomThreadExecutor.java

@@ -0,0 +1,23 @@
+package com.yomahub.liteflow.test.parallelLoop;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.property.LiteflowConfig;
+import com.yomahub.liteflow.property.LiteflowConfigGetter;
+import com.yomahub.liteflow.thread.ExecutorBuilder;
+
+import java.util.concurrent.ExecutorService;
+
+public class CustomThreadExecutor implements ExecutorBuilder {
+
+	@Override
+	public ExecutorService buildExecutor() {
+		LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
+		// 只有在非spring的场景下liteflowConfig才会为null
+		if (ObjectUtil.isNull(liteflowConfig)) {
+			liteflowConfig = new LiteflowConfig();
+		}
+		return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
+				liteflowConfig.getParallelQueueLimit(), "customer-loop-thead-");
+	}
+
+}

+ 119 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/ParallelLoopELDeclMultiSpringbootTest.java

@@ -0,0 +1,119 @@
+package com.yomahub.liteflow.test.parallelLoop;
+
+import cn.hutool.core.collection.ListUtil;
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.exception.LiteFlowException;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.slot.DefaultContext;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * springboot环境EL异步循环测试
+ *
+ * @author zhhhhy
+ * @since 2.11.0
+ */
+@ExtendWith(SpringExtension.class)
+@TestPropertySource(value = "classpath:/parallelLoop/application.properties")
+@SpringBootTest(classes = ParallelLoopELDeclMultiSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({ "com.yomahub.liteflow.test.parallelLoop.cmp" })
+public class ParallelLoopELDeclMultiSpringbootTest extends BaseTest {
+
+	@Resource
+	private FlowExecutor flowExecutor;
+
+	//测试并行FOR循环,循环次数直接在el中定义
+	@Test
+	public void testParallelLoop1() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//测试并行FOR循环,循环次数由For组件定义
+	@Test
+	public void testParallelLoop2() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//测试并行FOR循环中的BREAK组件能够正常发挥作用
+	@Test
+	public void testParallelLoop3() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//测试并行FOR循环中,主线程是否会正常等待所有并行子项完成后再继续执行
+	@Test
+	public void testParallelLoop4() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	@Test
+	//测试并行FOR循环中,某个并行子项抛出异常
+	public void testParallelLoop5() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("300", response.getCode());
+		Assertions.assertNotNull(response.getCause());
+		Assertions.assertTrue(response.getCause() instanceof LiteFlowException);
+		Assertions.assertNotNull(response.getSlot());
+	}
+
+	//并行的条件循环
+	@Test
+	public void testParallelLoop6() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//并行的迭代循环
+	@Test
+	public void testParallelLoop7() throws Exception {
+		List<String> list = ListUtil.toList("1", "2", "3", "4", "5");
+		LiteflowResponse response = flowExecutor.execute2Resp("chain7", list);
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//测试并行FOR循环中的index
+	@Test
+	public void testParallelLoop8() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
+		DefaultContext context = response.getFirstContextBean();
+		Assertions.assertTrue(response.isSuccess());
+		String regex = "(?!.*(.).*\\1)[0-4]{5}";   //匹配不包含重复数字的0-4的5位数字
+		Pattern pattern = Pattern.compile(regex);
+		//e1,e2,e3分别并行执行5次,因此单个循环的顺序可以是任意的
+		Assertions.assertTrue(pattern.matcher(context.getData("loop_e1")).matches());
+		Assertions.assertTrue(pattern.matcher(context.getData("loop_e2")).matches());
+		Assertions.assertTrue(pattern.matcher(context.getData("loop_e3")).matches());
+	}
+
+
+	//测试自定义线程池配置是否生效
+	@Test
+	public void testParallelLoop9() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
+		DefaultContext context = response.getFirstContextBean();
+		Assertions.assertTrue(response.isSuccess());
+		Assertions.assertTrue(context.getData("threadName").toString().startsWith("customer-loop-thead"));
+	}
+
+
+
+}

+ 120 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/CmpConfig.java

@@ -0,0 +1,120 @@
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import cn.hutool.core.util.StrUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+import com.yomahub.liteflow.slot.DefaultContext;
+import com.yomahub.liteflow.test.exception.CustomStatefulException;
+
+import java.util.Iterator;
+import java.util.List;
+
+@LiteflowComponent
+public class CmpConfig {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "a")
+    public void processA(NodeComponent bindCmp) {
+        System.out.println("ACmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "b")
+    public void processB(NodeComponent bindCmp) {
+
+        System.out.println("BCmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "c")
+    public void processC(NodeComponent bindCmp) {
+        System.out.println("CCmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "d")
+    public void processD(NodeComponent bindCmp) {
+        DefaultContext context = bindCmp.getFirstContextBean();
+        String key = "test";
+        if (context.hasData(key)) {
+            int count = context.getData(key);
+            context.setData(key, ++count);
+        } else {
+            context.setData(key, 1);
+        }
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "e")
+    public void processE(NodeComponent bindCmp) {
+        synchronized (this){
+            DefaultContext context = bindCmp.getFirstContextBean();
+            String key = StrUtil.format("{}_{}", "loop", bindCmp.getTag());
+            if (context.hasData(key)) {
+                String loopStr = context.getData(key);
+                String loopStrReturn = StrUtil.format("{}{}", loopStr, bindCmp.getLoopIndex());
+                context.setData(key, loopStrReturn);
+            } else {
+                context.setData(key, bindCmp.getLoopIndex().toString());
+            }
+        }
+    }
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "f")
+    public void processF(NodeComponent bindCmp){
+        try {
+            System.out.println("FCmp start to sleep 5s");
+            Thread.sleep(5000);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println("FCmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "g")
+    public void processG(NodeComponent bindCmp){
+        if(bindCmp.getLoopIndex()==1){
+            throw new CustomStatefulException("300", "chain execute custom stateful execption");
+        }
+        System.out.println("GCmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "h")
+    public void processH(NodeComponent bindCmp){
+        DefaultContext context = bindCmp.getFirstContextBean();
+        context.setData("threadName", Thread.currentThread().getName());
+        System.out.println("HCmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_ITERATOR, nodeId = "it", nodeType = NodeTypeEnum.ITERATOR)
+    public Iterator<?> processIT(NodeComponent bindCmp) {
+        List<String> list = bindCmp.getRequestData();
+        return list.iterator();
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_FOR, nodeId = "x", nodeType = NodeTypeEnum.FOR)
+    public int processX(NodeComponent bindCmp) {
+        return 3;
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_BREAK, nodeId = "y", nodeType = NodeTypeEnum.BREAK)
+    public boolean processY(NodeComponent bindCmp) {
+        DefaultContext context = bindCmp.getFirstContextBean();
+        int count = 0;
+        if(context.hasData("test")) {
+            count = context.getData("test");
+        }
+        return count > 3;
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE, nodeId = "z", nodeType = NodeTypeEnum.WHILE)
+    public boolean processZ(NodeComponent bindCmp) {
+        DefaultContext context = bindCmp.getFirstContextBean();
+        String key = "test";
+        if (context.hasData(key)) {
+            int count = context.getData("test");
+            return count < 5;
+        }
+        else {
+            return true;
+        }
+    }
+
+}

+ 93 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/rollback/RollbackELDeclMultiSpringbootTest.java

@@ -0,0 +1,93 @@
+package com.yomahub.liteflow.test.rollback;
+
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.annotation.Resource;
+
+@ExtendWith(SpringExtension.class)
+@TestPropertySource(value = "classpath:/rollback/application.properties")
+@SpringBootTest(classes = RollbackELDeclMultiSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({ "com.yomahub.liteflow.test.rollback.cmp" })
+public class RollbackELDeclMultiSpringbootTest extends BaseTest {
+
+	@Resource
+	private FlowExecutor flowExecutor;
+
+	// 在流程正常执行结束情况下的测试
+	@Test
+	public void testRollback() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
+		Assertions.assertTrue(response.isSuccess());
+		Assertions.assertNull(response.getCause());
+		Assertions.assertEquals("", response.getRollbackStepStr());
+	}
+
+	// 对串行编排与并行编排语法的测试
+	@Test
+	public void testWhenAndThen() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>b==>a", response.getRollbackStepStr());
+	}
+
+	// 对条件编排语法的测试
+	@Test
+	public void testIf() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>x", response.getRollbackStepStr());
+	}
+
+	// 对选择编排语法的测试
+	@Test
+	public void testSwitch() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>f", response.getRollbackStepStr());
+	}
+
+	// 对FOR循环编排语法的测试
+	@Test
+	public void testFor() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("h==>b==>g", response.getRollbackStepStr());
+	}
+
+	// 对WHILE循环编排语法的测试
+	@Test
+	public void testWhile() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>b==>a==>w", response.getRollbackStepStr());
+	}
+
+	// 对ITERATOR迭代循环编排语法的测试
+	@Test
+	public void testIterator() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain7", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>b==>a==>i", response.getRollbackStepStr());
+	}
+
+	@Test
+	// 对捕获异常表达式的测试
+	public void testCatch() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
+		Assertions.assertTrue(response.isSuccess());
+		Assertions.assertNull(response.getCause());
+		Assertions.assertEquals("", response.getRollbackStepStr());
+	}
+
+}

+ 136 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/CmpConfig.java

@@ -0,0 +1,136 @@
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import cn.hutool.core.collection.ListUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+import java.util.Iterator;
+import java.util.List;
+
+@LiteflowComponent
+public class CmpConfig {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "a")
+    public void processA(NodeComponent bindCmp) {
+        System.out.println("ACmp executed!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "a")
+    public void rollbackA(NodeComponent bindCmp) throws Exception {
+        System.out.println("ACmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "b")
+    public void processB(NodeComponent bindCmp) {
+        System.out.println("BCmp executed!");
+        throw new RuntimeException();
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.IS_CONTINUE_ON_ERROR, nodeId = "b")
+    public boolean isContinueOnErrorB(NodeComponent bindCmp) {
+        return true;
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "b")
+    public void rollbackB(NodeComponent bindCmp) throws Exception {
+        System.out.println("BCmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "c")
+    public void processC(NodeComponent bindCmp) {
+        System.out.println("CCmp executed!");
+    }
+
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "d")
+    public void processD(NodeComponent bindCmp) {
+        System.out.println("DCmp executed!");
+        throw new RuntimeException();
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "d")
+    public void rollbackD(NodeComponent bindCmp) throws Exception {
+        System.out.println("DCmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "e")
+    public void processE(NodeComponent bindCmp) {
+        System.out.println("ECmp executed!");
+        throw new RuntimeException();
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "e")
+    public void rollbackE() throws Exception {
+        System.out.println("ECmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_SWITCH, nodeId = "f", nodeType = NodeTypeEnum.SWITCH)
+    public String processF(NodeComponent bindCmp) {
+        System.out.println("FCmp executed!");
+        return "abc";
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "f", nodeType = NodeTypeEnum.SWITCH)
+    public void rollbackF() throws Exception {
+        System.out.println("FCmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_FOR, nodeId = "g", nodeType = NodeTypeEnum.FOR)
+    public int processG(NodeComponent bindCmp) {
+        System.out.println("GCmp executed!");
+        return 3;
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "g", nodeType = NodeTypeEnum.FOR)
+    public void rollbackG() throws Exception {
+        System.out.println("GCmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_BREAK, nodeId = "h", nodeType = NodeTypeEnum.BREAK)
+    public int processH(NodeComponent bindCmp) {
+        System.out.println("HCmp executed!");
+        throw new RuntimeException();
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "h", nodeType = NodeTypeEnum.BREAK)
+    public void rollbackH() throws Exception {
+        System.out.println("HCmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_ITERATOR, nodeId = "i", nodeType = NodeTypeEnum.ITERATOR)
+    public Iterator<?> processI(NodeComponent bindCmp) {
+        List<String> list = ListUtil.toList("jack", "mary", "tom");
+        return list.iterator();
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "i", nodeType = NodeTypeEnum.ITERATOR)
+    public void rollbackI() throws Exception {
+        System.out.println("ICmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE, nodeId = "w", nodeType = NodeTypeEnum.WHILE)
+    public boolean processW(NodeComponent bindCmp) {
+        System.out.println("WCmp executed!");
+        return true;
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "w", nodeType = NodeTypeEnum.WHILE)
+    public void rollbackW() throws Exception {
+        System.out.println("WCmp rollback!");
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_IF, nodeId = "x", nodeType = NodeTypeEnum.IF)
+    public boolean processX(NodeComponent bindCmp) {
+        System.out.println("XCmp executed!");
+        return true;
+    }
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.ROLLBACK, nodeId = "x", nodeType = NodeTypeEnum.IF)
+    public void rollbackX() throws Exception {
+        System.out.println("XCmp rollback!");
+    }
+
+}

+ 1 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/maxWaitSeconds/application.properties

@@ -0,0 +1 @@
+liteflow.rule-source=maxWaitSeconds/flow.el.xml

+ 110 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/maxWaitSeconds/flow.el.xml

@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <!--
+        a=>1s  b=>2s  c=>5s
+    -->
+    <!-- 串行编排测试 -->
+    <chain name="then1">
+        <!-- 超时 -->
+        THEN(a,b).maxWaitSeconds(2);
+    </chain>
+    <chain name="then2">
+        <!-- 不超时 -->
+        THEN(a,b).maxWaitSeconds(5);
+    </chain>
+
+    <!-- 并行编排测试 -->
+    <chain name="when1">
+        <!-- 超时 -->
+        WHEN(a,c).maxWaitSeconds(3);
+    </chain>
+    <chain name="when2">
+        <!-- 不超时 -->
+        WHEN(a,b).maxWaitSeconds(3);
+    </chain>
+
+    <!-- 循环编排测试 -->
+    <chain name="for1">
+        <!-- 超时 -->
+        FOR(2).DO(a).maxWaitSeconds(1);
+    </chain>
+    <chain name="for2">
+        <!-- 不超时 -->
+        FOR(2).DO(a).maxWaitSeconds(3);
+    </chain>
+    <!-- w 循环两次 -->
+    <chain name="while1">
+        <!-- 超时 -->
+        WHILE(w).DO(a).maxWaitSeconds(1);
+    </chain>
+    <chain name="while2">
+        <!-- 不超时 -->
+        WHILE(w).DO(a).maxWaitSeconds(3);
+    </chain>
+    <!-- x 迭代两次 -->
+    <chain name="iterator1">
+        <!-- 超时 -->
+        ITERATOR(x).DO(a).maxWaitSeconds(1);
+    </chain>
+    <chain name="iterator2">
+        <!-- 不超时 -->
+        ITERATOR(x).DO(a).maxWaitSeconds(3);
+    </chain>
+
+    <!-- 选择编排测试 -->
+    <!-- s 选择 b 组件 -->
+    <chain name="switch1">
+        <!-- 超时 -->
+        SWITCH(s).TO(a, b).maxWaitSeconds(1);
+    </chain>
+    <chain name="switch2">
+        <!-- 不超时 -->
+        SWITCH(s).TO(a, b).maxWaitSeconds(3);
+    </chain>
+
+    <!-- 条件编排测试 -->
+    <!-- f 返回 true -->
+    <chain name="if1">
+        <!-- 超时 -->
+        IF(f, b, c).maxWaitSeconds(1);
+    </chain>
+    <chain name="if2">
+        <!-- 不超时 -->
+        IF(f, b, c).maxWaitSeconds(3);
+    </chain>
+
+    <!-- 测试单个组件 -->
+    <chain name="component1">
+        <!-- 超时 -->
+        WHEN(
+            a.maxWaitSeconds(2),
+            c.maxWaitSeconds(3)
+        );
+    </chain>
+    <chain name="component2">
+        <!-- 不超时 -->
+        WHEN(
+            a.maxWaitSeconds(2),
+            b.maxWaitSeconds(3)
+        );
+    </chain>
+
+    <!-- 测试 FINALLY -->
+    <chain name="finally">
+        <!-- 超时,但 FINALLY 执行 -->
+        THEN(PRE(a), b, FINALLY(d)).maxWaitSeconds(2);
+    </chain>
+
+    <!-- 测试 chain -->
+    <chain name="testChain">
+        THEN(b)
+    </chain>
+    <chain name="chain1">
+        <!-- 超时 -->
+        testChain.maxWaitSeconds(1);
+    </chain>
+    <chain name="chain2">
+        <!-- 不超时 -->
+        testChain.maxWaitSeconds(3);
+    </chain>
+</flow>

+ 4 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/parallelLoop/application.properties

@@ -0,0 +1,4 @@
+liteflow.rule-source=parallelLoop/flow.xml
+liteflow.parallel-max-workers = 10
+liteflow.parallel-queue-limit = 1024
+liteflow.parallel-loop-executor-class =com.yomahub.liteflow.test.parallelLoop.CustomThreadExecutor

+ 46 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/parallelLoop/flow.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <chain name="chain1">
+        FOR(2).DO(THEN(a,b,c));
+    </chain>
+
+    <chain name="chain2">
+        FOR(x).parallel(true).DO(THEN(a,b,c));
+    </chain>
+
+    <chain name="chain3">
+        FOR(100).parallel(true).DO(THEN(a,b,d)).BREAK(y);
+    </chain>
+
+    <chain name="chain4">
+        FOR(x).parallel(true).DO(THEN(a,b,f));
+    </chain>
+
+    <chain name="chain5">
+        FOR(x).parallel(true).DO(THEN(a,b,g));
+    </chain>
+
+    <chain name="chain6">
+        WHILE(z).parallel(true).DO(THEN(a,d));
+    </chain>
+
+    <chain name="chain7">
+        ITERATOR(it).parallel(true).DO(THEN(a,b));
+    </chain>
+
+    <chain name="chain8">
+        FOR(5).parallel(true).DO(
+        WHEN(
+        THEN(a,e.tag("e1")),
+        THEN(c,e.tag("e2")),
+        THEN(b,e.tag("e3"))
+        )
+        );
+    </chain>
+
+
+    <chain name="chain9">
+        FOR(x).parallel(true).DO(THEN(a,b,h));
+    </chain>
+
+</flow>

+ 1 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/rollback/application.properties

@@ -0,0 +1 @@
+liteflow.rule-source=rollback/flow.el.xml

+ 34 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-multi-springboot/src/test/resources/rollback/flow.el.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <chain name="chain1">
+        THEN( a, b, WHEN(c, d).ignoreError(true), CATCH(e) );
+    </chain>
+
+    <chain name="chain2">
+        THEN( a, b, WHEN(c, d) );
+    </chain>
+
+    <chain name="chain3">
+        THEN( IF(x, d, a), CATCH(IF(x, d, a)) );
+    </chain>
+
+    <chain name="chain4">
+        SWITCH(f).TO(a, b).DEFAULT(d);
+    </chain>
+
+    <chain name="chain5">
+        FOR(g).DO(THEN(b, c)).BREAK(h);
+    </chain>
+
+    <chain name="chain6">
+        WHILE(w).DO(THEN(a, b, d));
+    </chain>
+
+    <chain name="chain7">
+        ITERATOR(i).DO(THEN(a, b, d));
+    </chain>
+
+    <chain name="chain8">
+        CATCH( THEN(b, c, d) ).DO(a);
+    </chain>
+</flow>

+ 1 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/aop/GlobalAOPELDeclSpringbootTest.java

@@ -71,6 +71,7 @@ public class GlobalAOPELDeclSpringbootTest extends BaseTest {
 		Assertions.assertEquals("before_after", context.getData("b"));
 		Assertions.assertEquals("before_after", context.getData("c"));
 		Assertions.assertEquals("before_after", context.getData("f"));
+		Assertions.assertEquals("test error", context.getData("f_error"));
 	}
 
 	@AfterAll

+ 11 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/aop/aspect/CmpAspect.java

@@ -20,4 +20,15 @@ public class CmpAspect implements ICmpAroundAspect {
 		context.setData(cmp.getNodeId(), StrUtil.format("{}_{}", context.getData(cmp.getNodeId()), "after"));
 	}
 
+	@Override
+	public void onSuccess(NodeComponent cmp) {
+
+	}
+
+	@Override
+	public void onError(NodeComponent cmp, Exception e) {
+		DefaultContext context = cmp.getFirstContextBean();
+		context.setData(cmp.getNodeId()+"_error", e.getMessage());
+	}
+
 }

+ 1 - 1
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/customWhenThreadPool/CustomWhenThreadPoolELDeclSpringbootTest.java

@@ -44,7 +44,7 @@ public class CustomWhenThreadPoolELDeclSpringbootTest extends BaseTest {
 		LiteflowResponse response = flowExecutor.execute2Resp("chain", "arg");
 		DefaultContext context = response.getFirstContextBean();
 		Assertions.assertTrue(response.isSuccess());
-		Assertions.assertTrue(context.getData("threadName").toString().startsWith("lf-when-thead"));
+		Assertions.assertTrue(context.getData("threadName").toString().startsWith("when-thread-1"));
 	}
 
 	/**

+ 202 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/MaxWaitSecondsELDeclSpringbootTest.java

@@ -0,0 +1,202 @@
+package com.yomahub.liteflow.test.maxWaitSeconds;
+
+import com.yomahub.liteflow.builder.LiteFlowNodeBuilder;
+import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder;
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+import com.yomahub.liteflow.exception.WhenTimeoutException;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.slot.DefaultContext;
+import com.yomahub.liteflow.test.BaseTest;
+import com.yomahub.liteflow.test.maxWaitSeconds.cmp.ACmp;
+import com.yomahub.liteflow.test.maxWaitSeconds.cmp.BCmp;
+import com.yomahub.liteflow.test.maxWaitSeconds.cmp.CCmp;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeoutException;
+
+import static com.yomahub.liteflow.test.maxWaitSeconds.cmp.DCmp.CONTENT_KEY;
+
+/**
+ * Spring Boot 环境下超时控制测试
+ *
+ * @author DaleLee
+ * @since 2.11.0
+ */
+@ExtendWith(SpringExtension.class)
+@TestPropertySource(value = "classpath:/maxWaitSeconds/application.properties")
+@SpringBootTest(classes = MaxWaitSecondsELDeclSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({ "com.yomahub.liteflow.test.maxWaitSeconds.cmp" })
+public class MaxWaitSecondsELDeclSpringbootTest extends BaseTest {
+    @Resource
+    private FlowExecutor flowExecutor;
+
+    // 测试 THEN 的超时情况
+    @Test
+    public void testThen1() {
+        assertTimeout("then1");
+    }
+
+    // 测试 THEN 的非超时情况
+    @Test
+    public void testThen2() {
+        assertNotTimeout("then2");
+    }
+
+    // 测试 When 的超时情况
+    @Test
+    public void testWhen1() {
+        assertWhenTimeout("when1");
+    }
+
+    // 测试 WHEN 的非超时情况
+    @Test
+    public void testWhen2() {
+        assertNotTimeout("when2");
+    }
+
+    // 测试 FOR 的超时情况
+    @Test
+    public void testFor1() {
+        assertTimeout("for1");
+    }
+
+    // 测试 FOR 的非超时情况
+    @Test
+    public void testFor2() {
+        assertNotTimeout("for2");
+    }
+
+    // 测试 WHILE 的超时情况
+    @Test
+    public void testWhile1() {
+        assertTimeout("while1");
+    }
+
+    // 测试 WHILE 的非超时情况
+    @Test
+    public void testWhile2() {
+        assertNotTimeout("while2");
+    }
+
+    // 测试 ITERATOR 的超时情况
+    @Test
+    public void testIterator1() {
+        assertTimeout("iterator1");
+    }
+
+    // 测试 ITERATOR 的非超时情况
+    @Test
+    public void testIterator2() {
+        assertNotTimeout("iterator2");
+    }
+
+    // 测试 SWITCH 的超时情况
+    @Test
+    public void testSwitch1() {
+        assertTimeout("switch1");
+    }
+
+    // 测试 SWITCH 的非超时情况
+    @Test
+    public void testSwitch2() {
+        assertNotTimeout("switch2");
+    }
+
+    // 测试 IF 的超时情况
+    @Test
+    public void testIf1() {
+        assertTimeout("if1");
+    }
+
+    // 测试 SWITCH 的非超时情况
+    @Test
+    public void testIf2() {
+        assertNotTimeout("if2");
+    }
+
+    // 测试单个组件的超时情况
+    @Test
+    public void testComponent1() {
+        assertTimeout("component1");
+    }
+
+    // 测试单个组件的非超时情况
+    @Test
+    public void testComponent2() {
+        assertNotTimeout("component2");
+    }
+
+    // 测试 FINALLY,虽然超时,但 FINALLY 仍会执行
+    @Test
+    public void testFinally1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("finally", "arg");
+        Assertions.assertFalse(response.isSuccess());
+        Assertions.assertEquals(TimeoutException.class, response.getCause().getClass());
+        // FINALLY 执行时在默认数据上下文中放入了 CONTENT_KEY
+        DefaultContext contextBean = response.getFirstContextBean();
+        Assertions.assertTrue(contextBean.hasData(CONTENT_KEY));
+    }
+
+    // 测试 maxWaitSeconds 关键字不能作用于 Finally
+    @Test
+    public void testFinally2() {
+        LiteFlowNodeBuilder.createNode()
+                .setId("a")
+                .setName("组件A")
+                .setType(NodeTypeEnum.COMMON)
+                .setClazz(ACmp.class)
+                .build();
+        LiteFlowNodeBuilder.createNode()
+                .setId("b")
+                .setName("组件B")
+                .setType(NodeTypeEnum.COMMON)
+                .setClazz(BCmp.class)
+                .build();
+        LiteFlowNodeBuilder.createNode()
+                .setId("c")
+                .setName("组件C")
+                .setType(NodeTypeEnum.COMMON)
+                .setClazz(CCmp.class)
+                .build();
+        Assertions.assertFalse(LiteFlowChainELBuilder.validate("THEN(a, b, FINALLY(c).maxWaitSeconds(10))"));
+    }
+
+    // 测试 chain 的超时情况
+    @Test
+    public void testChain1() {
+        assertTimeout("chain1");
+    }
+
+    // 测试 chain 的非超时情况
+    @Test
+    public void testChain2() {
+        assertNotTimeout("chain2");
+    }
+
+    private void assertTimeout(String chainId) {
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        Assertions.assertFalse(response.isSuccess());
+        Assertions.assertEquals(TimeoutException.class, response.getCause().getClass());
+    }
+
+    private void assertWhenTimeout(String chainId) {
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        Assertions.assertFalse(response.isSuccess());
+        Assertions.assertEquals(WhenTimeoutException.class, response.getCause().getClass());
+    }
+
+    private void assertNotTimeout(String chainId) {
+        LiteflowResponse response = flowExecutor.execute2Resp(chainId, "arg");
+        Assertions.assertTrue(response.isSuccess());
+    }
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/ACmp.java

@@ -0,0 +1,21 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+
+@LiteflowComponent("a")
+public class ACmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        try {
+            Thread.sleep(1000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("ACmp executed!");
+    }
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/BCmp.java

@@ -0,0 +1,21 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+
+@LiteflowComponent("b")
+public class BCmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        try {
+            Thread.sleep(2000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("BCmp executed!");
+    }
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/CCmp.java

@@ -0,0 +1,21 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+
+@LiteflowComponent("c")
+public class CCmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        try {
+            Thread.sleep(5000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("CCmp executed!");
+    }
+}

+ 26 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/DCmp.java

@@ -0,0 +1,26 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.slot.DefaultContext;
+
+@LiteflowComponent("d")
+public class DCmp {
+
+    public static final String CONTENT_KEY = "testKey";
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        try {
+            Thread.sleep(500);
+            DefaultContext contextBean = bindCmp.getFirstContextBean();
+            contextBean.setData(CONTENT_KEY, "value");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.out.println("DCmp executed!");
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/FCmp.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("f")
+@LiteflowCmpDefine(NodeTypeEnum.IF)
+public class FCmp {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_IF, nodeType = NodeTypeEnum.IF)
+    public boolean processIf(NodeComponent bindCmp) throws Exception {
+        return true;
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/SCmp.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("s")
+@LiteflowCmpDefine(NodeTypeEnum.SWITCH)
+public class SCmp {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_SWITCH, nodeType = NodeTypeEnum.SWITCH)
+    public String processSwitch(NodeComponent bindCmp) throws Exception {
+        return "b";
+    }
+}

+ 31 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/WCmp.java

@@ -0,0 +1,31 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@LiteflowComponent("w")
+@LiteflowCmpDefine(NodeTypeEnum.WHILE)
+public class WCmp {
+    private int count = 0;
+
+    // 执行过的 chain
+    Set<String> executedChain = new HashSet<>();
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE, nodeType = NodeTypeEnum.WHILE)
+    public boolean processWhile(NodeComponent bindCmp) throws Exception {
+        // 判断是否切换了 chain
+        if (!executedChain.contains(bindCmp.getCurrChainId())) {
+            count = 0;
+            executedChain.add(bindCmp.getCurrChainId());
+        }
+        count++;
+        return count <= 2;
+    }
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/maxWaitSeconds/cmp/XCmp.java

@@ -0,0 +1,23 @@
+package com.yomahub.liteflow.test.maxWaitSeconds.cmp;
+
+import cn.hutool.core.collection.ListUtil;
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+import java.util.Iterator;
+import java.util.List;
+
+@LiteflowComponent("x")
+@LiteflowCmpDefine(NodeTypeEnum.ITERATOR)
+public class XCmp {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_ITERATOR, nodeType = NodeTypeEnum.ITERATOR)
+    public Iterator<?> processIterator(NodeComponent bindCmp) throws Exception {
+        List<String> list = ListUtil.toList("one", "two");
+        return list.iterator();
+    }
+}

+ 14 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/CustomStatefulException.java

@@ -0,0 +1,14 @@
+package com.yomahub.liteflow.test.parallelLoop;
+
+import com.yomahub.liteflow.exception.LiteFlowException;
+
+/**
+ * 用户自定义带状态码的异常
+ */
+public class CustomStatefulException extends LiteFlowException {
+
+	public CustomStatefulException(String code, String message) {
+		super(code, message);
+	}
+
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/CustomThreadExecutor.java

@@ -0,0 +1,23 @@
+package com.yomahub.liteflow.test.parallelLoop;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.property.LiteflowConfig;
+import com.yomahub.liteflow.property.LiteflowConfigGetter;
+import com.yomahub.liteflow.thread.ExecutorBuilder;
+
+import java.util.concurrent.ExecutorService;
+
+public class CustomThreadExecutor implements ExecutorBuilder {
+
+	@Override
+	public ExecutorService buildExecutor() {
+		LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
+		// 只有在非spring的场景下liteflowConfig才会为null
+		if (ObjectUtil.isNull(liteflowConfig)) {
+			liteflowConfig = new LiteflowConfig();
+		}
+		return buildDefaultExecutor(liteflowConfig.getParallelMaxWorkers(), liteflowConfig.getParallelMaxWorkers(),
+				liteflowConfig.getParallelQueueLimit(), "customer-loop-thead-");
+	}
+
+}

+ 118 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/ParallelLoopELDeclSpringbootTest.java

@@ -0,0 +1,118 @@
+package com.yomahub.liteflow.test.parallelLoop;
+
+import cn.hutool.core.collection.ListUtil;
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.exception.LiteFlowException;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.slot.DefaultContext;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * springboot环境EL异步循环测试
+ *
+ * @author zhhhhy
+ * @since 2.11.0
+ */
+@ExtendWith(SpringExtension.class)
+@TestPropertySource(value = "classpath:/parallelLoop/application.properties")
+@SpringBootTest(classes = ParallelLoopELDeclSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({ "com.yomahub.liteflow.test.parallelLoop.cmp" })
+public class ParallelLoopELDeclSpringbootTest extends BaseTest {
+
+	@Resource
+	private FlowExecutor flowExecutor;
+
+	//测试并行FOR循环,循环次数直接在el中定义
+	@Test
+	public void testParallelLoop1() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//测试并行FOR循环,循环次数由For组件定义
+	@Test
+	public void testParallelLoop2() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//测试并行FOR循环中的BREAK组件能够正常发挥作用
+	@Test
+	public void testParallelLoop3() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//测试并行FOR循环中,主线程是否会正常等待所有并行子项完成后再继续执行
+	@Test
+	public void testParallelLoop4() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	@Test
+	//测试并行FOR循环中,某个并行子项抛出异常
+	public void testParallelLoop5() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("300", response.getCode());
+		Assertions.assertNotNull(response.getCause());
+		Assertions.assertTrue(response.getCause() instanceof LiteFlowException);
+		Assertions.assertNotNull(response.getSlot());
+	}
+
+	//并行的条件循环
+	@Test
+	public void testParallelLoop6() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//并行的迭代循环
+	@Test
+	public void testParallelLoop7() throws Exception {
+		List<String> list = ListUtil.toList("1", "2", "3", "4", "5");
+		LiteflowResponse response = flowExecutor.execute2Resp("chain7", list);
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	//测试并行FOR循环中的index
+	@Test
+	public void testParallelLoop8() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
+		DefaultContext context = response.getFirstContextBean();
+		Assertions.assertTrue(response.isSuccess());
+		String regex = "(?!.*(.).*\\1)[0-4]{5}";   //匹配不包含重复数字的0-4的5位数字
+		Pattern pattern = Pattern.compile(regex);
+		//e1,e2,e3分别并行执行5次,因此单个循环的顺序可以是任意的
+		Assertions.assertTrue(pattern.matcher(context.getData("loop_e1")).matches());
+		Assertions.assertTrue(pattern.matcher(context.getData("loop_e2")).matches());
+		Assertions.assertTrue(pattern.matcher(context.getData("loop_e3")).matches());
+	}
+
+
+	//测试自定义线程池配置是否生效
+	@Test
+	public void testParallelLoop9() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain9", "arg");
+		DefaultContext context = response.getFirstContextBean();
+		Assertions.assertTrue(response.isSuccess());
+		Assertions.assertTrue(context.getData("threadName").toString().startsWith("customer-loop-thead"));
+	}
+
+
+
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/ACmp.java

@@ -0,0 +1,23 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import org.springframework.stereotype.Component;
+
+@Component("a")
+public class ACmp {
+
+	@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+	public void process(NodeComponent bindCmp) {
+		System.out.println("ACmp executed!");
+	}
+
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/BCmp.java

@@ -0,0 +1,23 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import org.springframework.stereotype.Component;
+
+@Component("b")
+public class BCmp {
+
+	@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+	public void process(NodeComponent bindCmp) {
+		System.out.println("BCmp executed!");
+	}
+
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/CCmp.java

@@ -0,0 +1,23 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import org.springframework.stereotype.Component;
+
+@Component("c")
+public class CCmp {
+
+	@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+	public void process(NodeComponent bindCmp) {
+		System.out.println("CCmp executed!");
+	}
+
+}

+ 32 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/DCmp.java

@@ -0,0 +1,32 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.slot.DefaultContext;
+import org.springframework.stereotype.Component;
+
+@Component("d")
+public class DCmp {
+
+	@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+	public void process(NodeComponent bindCmp) {
+		DefaultContext context = bindCmp.getFirstContextBean();
+		String key = "test";
+		if (context.hasData(key)) {
+			int count = context.getData(key);
+			context.setData(key, ++count);
+		}
+		else {
+			context.setData(key, 1);
+		}
+	}
+
+}

+ 35 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/ECmp.java

@@ -0,0 +1,35 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import cn.hutool.core.util.StrUtil;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.slot.DefaultContext;
+import org.springframework.stereotype.Component;
+
+@Component("e")
+public class ECmp {
+
+	//注意与串行的ECmp相比,并行的ECmp的process方法必须保证线程安全
+	@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+	public synchronized void process(NodeComponent bindCmp) {
+		DefaultContext context = bindCmp.getFirstContextBean();
+		String key = StrUtil.format("{}_{}", "loop", bindCmp.getTag());
+		if (context.hasData(key)) {
+			String loopStr = context.getData(key);
+			String loopStrReturn = StrUtil.format("{}{}", loopStr, bindCmp.getLoopIndex());
+			context.setData(key, loopStrReturn);
+		}
+		else {
+			context.setData(key, bindCmp.getLoopIndex().toString());
+		}
+	}
+
+}

+ 22 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/FCmp.java

@@ -0,0 +1,22 @@
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import org.springframework.stereotype.Component;
+
+@Component("f")
+public class FCmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        try {
+            System.out.println("FCmp start to sleep 5s");
+            Thread.sleep(5000);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println("FCmp executed!");
+    }
+
+}

+ 20 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/GCmp.java

@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.test.exception.CustomStatefulException;
+import org.springframework.stereotype.Component;
+
+@Component("g")
+public class GCmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        if(bindCmp.getLoopIndex()==1){
+            throw new CustomStatefulException("300", "chain execute custom stateful execption");
+        }
+        System.out.println("GCmp executed!");
+    }
+
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/HCmp.java

@@ -0,0 +1,21 @@
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.slot.DefaultContext;
+import com.yomahub.liteflow.test.exception.CustomStatefulException;
+import org.springframework.stereotype.Component;
+
+
+@Component("h")
+public class HCmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        DefaultContext context = bindCmp.getFirstContextBean();
+        context.setData("threadName", Thread.currentThread().getName());
+        System.out.println("HCmp executed!");
+    }
+
+}

+ 24 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/ITCmp.java

@@ -0,0 +1,24 @@
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.core.NodeIteratorComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+import org.springframework.stereotype.Component;
+
+import java.util.Iterator;
+import java.util.List;
+
+@Component("it")
+@LiteflowCmpDefine(NodeTypeEnum.ITERATOR)
+public class ITCmp {
+
+	@LiteflowMethod(LiteFlowMethodEnum.PROCESS_ITERATOR)
+	public Iterator<?> processIterator(NodeComponent bindCmp) throws Exception {
+		List<String> list = bindCmp.getRequestData();
+		return list.iterator();
+	}
+
+}

+ 17 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/XCmp.java

@@ -0,0 +1,17 @@
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("x")
+public class XCmp {
+
+	@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_FOR, nodeType = NodeTypeEnum.FOR)
+	public int processFor(NodeComponent bindCmp) throws Exception {
+		return 3;
+	}
+
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/YCmp.java

@@ -0,0 +1,23 @@
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+import com.yomahub.liteflow.slot.DefaultContext;
+
+@LiteflowComponent("y")
+public class YCmp {
+
+	@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_BREAK, nodeType = NodeTypeEnum.BREAK)
+	public boolean processBreak(NodeComponent bindCmp) throws Exception {
+		DefaultContext context = bindCmp.getFirstContextBean();
+		int count = 0;
+		if(context.hasData("test")) {
+			count = context.getData("test");
+		}
+		return count > 3;
+	}
+
+}

+ 26 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/parallelLoop/cmp/ZCmp.java

@@ -0,0 +1,26 @@
+package com.yomahub.liteflow.test.parallelLoop.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+import com.yomahub.liteflow.slot.DefaultContext;
+
+@LiteflowComponent("z")
+public class ZCmp {
+
+	@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE, nodeType = NodeTypeEnum.WHILE)
+	public boolean processWhile(NodeComponent bindCmp) throws Exception {
+		DefaultContext context = bindCmp.getFirstContextBean();
+		String key = "test";
+		if (context.hasData(key)) {
+			int count = context.getData("test");
+			return count < 5;
+		}
+		else {
+			return true;
+		}
+	}
+
+}

+ 95 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/RollbackELDeclSpringbootTest.java

@@ -0,0 +1,95 @@
+package com.yomahub.liteflow.test.rollback;
+
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.test.BaseTest;
+import com.yomahub.liteflow.test.whenTimeOut.WhenTimeOutELDeclSpringbootTest1;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.annotation.Resource;
+
+
+@ExtendWith(SpringExtension.class)
+@TestPropertySource(value = "classpath:/rollback/application.properties")
+@SpringBootTest(classes = RollbackELDeclSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({ "com.yomahub.liteflow.test.rollback.cmp" })
+public class RollbackELDeclSpringbootTest extends BaseTest {
+
+	@Resource
+	private FlowExecutor flowExecutor;
+
+	// 在流程正常执行结束情况下的测试
+	@Test
+	public void testRollback() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
+		Assertions.assertTrue(response.isSuccess());
+		Assertions.assertNull(response.getCause());
+		Assertions.assertEquals("", response.getRollbackStepStr());
+	}
+
+	// 对串行编排与并行编排语法的测试
+	@Test
+	public void testWhenAndThen() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>b==>a", response.getRollbackStepStr());
+	}
+
+	// 对条件编排语法的测试
+	@Test
+	public void testIf() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>x", response.getRollbackStepStr());
+	}
+
+	// 对选择编排语法的测试
+	@Test
+	public void testSwitch() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain4", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>f", response.getRollbackStepStr());
+	}
+
+	// 对FOR循环编排语法的测试
+	@Test
+	public void testFor() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain5", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("h==>b==>g", response.getRollbackStepStr());
+	}
+
+	// 对WHILE循环编排语法的测试
+	@Test
+	public void testWhile() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>b==>a==>w", response.getRollbackStepStr());
+	}
+
+	// 对ITERATOR迭代循环编排语法的测试
+	@Test
+	public void testIterator() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain7", "arg");
+		Assertions.assertFalse(response.isSuccess());
+		Assertions.assertEquals("d==>b==>a==>i", response.getRollbackStepStr());
+	}
+
+	@Test
+	// 对捕获异常表达式的测试
+	public void testCatch() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
+		Assertions.assertTrue(response.isSuccess());
+		Assertions.assertNull(response.getCause());
+		Assertions.assertEquals("", response.getRollbackStepStr());
+	}
+
+}

+ 25 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/ACmp.java

@@ -0,0 +1,25 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("a")
+public class ACmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("ACmp executed!");
+	}
+
+	@Override
+	public void rollback() throws Exception {
+		System.out.println("ACmp rollback!");
+	}
+}

+ 31 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/BCmp.java

@@ -0,0 +1,31 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("b")
+public class BCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("BCmp executed!");
+		throw new RuntimeException();
+	}
+
+	@Override
+	public void rollback() throws Exception {
+		System.out.println("BCmp rollback!");
+	}
+
+	@Override
+	public boolean isContinueOnError() {
+		return true;
+	}
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/CCmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("c")
+public class CCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("CCmp executed!");
+	}
+
+}

+ 26 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/DCmp.java

@@ -0,0 +1,26 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("d")
+public class DCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("DCmp executed!");
+		throw new RuntimeException();
+	}
+
+	@Override
+	public void rollback() throws Exception {
+		System.out.println("DCmp rollback!");
+	}
+}

+ 26 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/ECmp.java

@@ -0,0 +1,26 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("e")
+public class ECmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("ECmp executed!");
+		throw new RuntimeException();
+	}
+
+	@Override
+	public void rollback() throws Exception {
+		System.out.println("ECmp rollback!");
+	}
+}

+ 19 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/FCmp.java

@@ -0,0 +1,19 @@
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+import org.springframework.stereotype.Component;
+
+@Component("f")
+public class FCmp extends NodeSwitchComponent {
+
+    @Override
+    public String processSwitch() {
+        System.out.println("FCmp executed!");
+        return "abc";
+    }
+
+    @Override
+    public void rollback() throws Exception {
+        System.out.println("FCmp rollback!");
+    }
+}

+ 19 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/GCmp.java

@@ -0,0 +1,19 @@
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeForComponent;
+import org.springframework.stereotype.Component;
+
+@Component("g")
+public class GCmp extends NodeForComponent {
+
+    @Override
+    public int processFor() throws Exception {
+        System.out.println("GCmp executed!");
+        return 3;
+    }
+
+    @Override
+    public void rollback() throws Exception {
+        System.out.println("GCmp rollback!");
+    }
+}

+ 19 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/HCmp.java

@@ -0,0 +1,19 @@
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeBreakComponent;
+import org.springframework.stereotype.Component;
+
+@Component("h")
+public class HCmp extends NodeBreakComponent {
+
+    @Override
+    public boolean processBreak() throws Exception {
+        System.out.println("HCmp executed!");
+        throw new RuntimeException();
+    }
+
+    @Override
+    public void rollback() throws Exception {
+        System.out.println("HCmp rollback!");
+    }
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/ICmp.java

@@ -0,0 +1,23 @@
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import cn.hutool.core.collection.ListUtil;
+import com.yomahub.liteflow.core.NodeIteratorComponent;
+import org.springframework.stereotype.Component;
+
+import java.util.Iterator;
+import java.util.List;
+
+@Component("i")
+public class ICmp extends NodeIteratorComponent {
+
+    @Override
+    public Iterator<?> processIterator() throws Exception {
+        List<String> list = ListUtil.toList("jack", "mary", "tom");
+        return list.iterator();
+    }
+
+    @Override
+    public void rollback() throws Exception {
+        System.out.println("ICmp rollback!");
+    }
+}

+ 19 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/WCmp.java

@@ -0,0 +1,19 @@
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeWhileComponent;
+import org.springframework.stereotype.Component;
+
+@Component("w")
+public class WCmp extends NodeWhileComponent {
+
+    @Override
+    public boolean processWhile() throws Exception {
+        System.out.println("WCmp executed!");
+        return true;
+    }
+
+    @Override
+    public void rollback() throws Exception {
+        System.out.println("WCmp rollback!");
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/rollback/cmp/XCmp.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.rollback.cmp;
+
+import com.yomahub.liteflow.core.NodeIfComponent;
+import org.springframework.stereotype.Component;
+
+@Component("x")
+public class XCmp extends NodeIfComponent {
+    @Override
+    public boolean processIf() throws Exception {
+        System.out.println("XCmp executed!");
+        return true;
+    }
+
+    @Override
+    public void rollback() throws Exception {
+        System.out.println("XCmp rollback!");
+    }
+}

+ 1 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/maxWaitSeconds/application.properties

@@ -0,0 +1 @@
+liteflow.rule-source=maxWaitSeconds/flow.el.xml

+ 110 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/maxWaitSeconds/flow.el.xml

@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <!--
+        a=>1s  b=>2s  c=>5s
+    -->
+    <!-- 串行编排测试 -->
+    <chain name="then1">
+        <!-- 超时 -->
+        THEN(a,b).maxWaitSeconds(2);
+    </chain>
+    <chain name="then2">
+        <!-- 不超时 -->
+        THEN(a,b).maxWaitSeconds(5);
+    </chain>
+
+    <!-- 并行编排测试 -->
+    <chain name="when1">
+        <!-- 超时 -->
+        WHEN(a,c).maxWaitSeconds(3);
+    </chain>
+    <chain name="when2">
+        <!-- 不超时 -->
+        WHEN(a,b).maxWaitSeconds(3);
+    </chain>
+
+    <!-- 循环编排测试 -->
+    <chain name="for1">
+        <!-- 超时 -->
+        FOR(2).DO(a).maxWaitSeconds(1);
+    </chain>
+    <chain name="for2">
+        <!-- 不超时 -->
+        FOR(2).DO(a).maxWaitSeconds(3);
+    </chain>
+    <!-- w 循环两次 -->
+    <chain name="while1">
+        <!-- 超时 -->
+        WHILE(w).DO(a).maxWaitSeconds(1);
+    </chain>
+    <chain name="while2">
+        <!-- 不超时 -->
+        WHILE(w).DO(a).maxWaitSeconds(3);
+    </chain>
+    <!-- x 迭代两次 -->
+    <chain name="iterator1">
+        <!-- 超时 -->
+        ITERATOR(x).DO(a).maxWaitSeconds(1);
+    </chain>
+    <chain name="iterator2">
+        <!-- 不超时 -->
+        ITERATOR(x).DO(a).maxWaitSeconds(3);
+    </chain>
+
+    <!-- 选择编排测试 -->
+    <!-- s 选择 b 组件 -->
+    <chain name="switch1">
+        <!-- 超时 -->
+        SWITCH(s).TO(a, b).maxWaitSeconds(1);
+    </chain>
+    <chain name="switch2">
+        <!-- 不超时 -->
+        SWITCH(s).TO(a, b).maxWaitSeconds(3);
+    </chain>
+
+    <!-- 条件编排测试 -->
+    <!-- f 返回 true -->
+    <chain name="if1">
+        <!-- 超时 -->
+        IF(f, b, c).maxWaitSeconds(1);
+    </chain>
+    <chain name="if2">
+        <!-- 不超时 -->
+        IF(f, b, c).maxWaitSeconds(3);
+    </chain>
+
+    <!-- 测试单个组件 -->
+    <chain name="component1">
+        <!-- 超时 -->
+        WHEN(
+            a.maxWaitSeconds(2),
+            c.maxWaitSeconds(3)
+        );
+    </chain>
+    <chain name="component2">
+        <!-- 不超时 -->
+        WHEN(
+            a.maxWaitSeconds(2),
+            b.maxWaitSeconds(3)
+        );
+    </chain>
+
+    <!-- 测试 FINALLY -->
+    <chain name="finally">
+        <!-- 超时,但 FINALLY 执行 -->
+        THEN(PRE(a), b, FINALLY(d)).maxWaitSeconds(2);
+    </chain>
+
+    <!-- 测试 chain -->
+    <chain name="testChain">
+        THEN(b)
+    </chain>
+    <chain name="chain1">
+        <!-- 超时 -->
+        testChain.maxWaitSeconds(1);
+    </chain>
+    <chain name="chain2">
+        <!-- 不超时 -->
+        testChain.maxWaitSeconds(3);
+    </chain>
+</flow>

+ 4 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/parallelLoop/application.properties

@@ -0,0 +1,4 @@
+liteflow.rule-source=parallelLoop/flow.xml
+liteflow.parallel-max-workers = 10
+liteflow.parallel-queue-limit = 1024
+liteflow.parallel-loop-executor-class =com.yomahub.liteflow.test.parallelLoop.CustomThreadExecutor

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác