特别声明:仅用于学习,如对LiteFlow感兴趣,可以前往官网查看更多知识点。LiteFlow
任务编排框架LiteFlow
快速开始
此示例使用的是SpringBoot,如用的是Spring或者其他,可以前往官网上查看用法。
-
新建一个 maven 项目,在pom.xml中导入相关依赖
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> <!--核心是 liteflow-spring-boot-starter --> <dependency> <groupId>com.yomahub</groupId> <artifactId>liteflow-spring-boot-starter</artifactId> <version>2.10.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> -
编写主启动类
package com.gjy.liteflow; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LiteflowApp { public static void main(String[] args) { SpringApplication.run(LiteflowApp.class, args); } } -
编写三个普通组件,参考Acmp编写Bcmp,Ccmp。@Component的value需要修改为对应的值为b,c。日志打印也要修改。
package com.gjy.liteflow.flow; import com.yomahub.liteflow.core.NodeComponent; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component(value = "a") @Slf4j public class Acmp extends NodeComponent { @Override public void process() throws Exception { log.info("process a"); } } -
编写 application.yaml
liteflow: rule-source: config/flow.el.xml -
编写规则定义文件 flow.el.xml
<?xml version="1.0" encoding="UTF-8"?> <flow> <chain name="chain"> THEN(a,b,c); </chain> </flow> -
编写测试代码
package com.gjy.liteflow.flow; import com.gjy.liteflow.LiteflowApp; import com.yomahub.liteflow.core.FlowExecutor; import com.yomahub.liteflow.flow.LiteflowResponse; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; @SpringBootTest(classes = LiteflowApp.class) public class FlowTest { @Resource private FlowExecutor flowExecutor; @Test public void test1() { LiteflowResponse resp = flowExecutor.execute2Resp("chain"); } } -
观察运行结果,会发现a,b,c依次打印
常规组件
任务的执行顺序依赖于EL的编写,任务的执行功能依赖于组件的编写。
本节不仅介绍了组件的编写,也介绍了对应的EL规则的写法。
普通组件
普通组件节点需要继承 NodeComponent ,可用于 EL 的 THEN 和 WHEN 关键字中,必须实现抽象方法 process,下面是一个简单示例:
@Component(value = "a")
public class Acmp extends NodeComponent {
@Override
public void process() throws Exception {
}
}
其中,@Component也可以换成 @LiteflowComponent,编写时最好指定value的值,以方便后续编写EL规则,value的值就是EL规则中的组件ID。
可以覆盖的方法
-
isAccess
推荐实现,表示是否进入该节点,可用于业务参数的预先判断。建议搭配 isEnd 来使用,因为 isAccess 的结果不会影响后面任务的执行。下面是一个示例,**注意:**使用的组件和EL规则还是
快速开始中的,除了Acmp和测试用例有改动。通过改变测试用例中的流程入参,查看不同的效果。
@Component(value = "a") @Slf4j public class Acmp extends NodeComponent { @Override public void process() throws Exception { log.info("process a"); } @Override public boolean isAccess() { // 判断是否进入该节点,比如判断订单号是否存在,再进行操作 String s = this.getRequestData(); // 获取流程入参 return Lists.newArrayList("1000111001", "1000111002").contains(s); } @Override public boolean isEnd() { // 搭配 isAccess,如果该节点不执行的时候,且不需要后续节点执行了,返回值为isAccess取反 return !isAccess(); } }测试用例:
@SpringBootTest(classes = LiteflowApp.class) public class FlowTest { @Resource private FlowExecutor flowExecutor; @Test public void test1() { LiteflowResponse resp = flowExecutor.execute2Resp( "chain", "1000111005" // 流程入参, 在任务中可以使用 this.getRequestData() 接受该参数,类型任意 ); } } -
isContinueOnError
表示出错是否执行下一个节点,默认为false。根据需要选择合适的返回结果。下面是一个示例,**注意:**使用的组件和EL规则还是
快速开始中的,除了Bcmp有改动。Acmp不是上面的,是快速开始里面的,这个需要注意,不然效果可能不一样。@Component(value = "b") @Slf4j public class Bcmp extends NodeComponent { @Override public void process() throws Exception { log.info("process b"); throw new RuntimeException(); } @Override public boolean isContinueOnError() { return false; } }测试用例
@SpringBootTest(classes = LiteflowApp.class) public class FlowTest { @Resource private FlowExecutor flowExecutor; @Test public void test2() { LiteflowResponse resp = flowExecutor.execute2Resp("chain"); } }在Bcmp的process中抛出了一个异常,通过改变Bcmp中isContinueOnError的返回值,查看任务执行的结果。为true时会忽略process中的错误,继续执行下面的任务节点,为false时会抛出异常,执行中止。
-
isEnd
在isAccess中已经介绍了一部分。为true时表示整个流程终止,但是返回的isSuccess为true,因为是用户主动结束的流程。
注意:
在isContinueOnError为true的情况下,给isEnd设置为true,流程依然会终止,response中的isSuccess也是true。
在isContinueOnError为true的情况下,给isEnd设置为true,但是process抛异常了,流程不会终止,response中的isSuccess也是true。
issues:gitee.com/dromara/lit…
-
beforeProcess和afterProcess
任务的前置和后置处理器,前置处理器在isAccess之后。可以在组件中重写或者继承ICmpAroundAspect 做全局处理
@Component public class LfCmpAspect implements ICmpAroundAspect { @Override public void beforeProcess(NodeComponent cmp) { System.out.println("lf before"); } @Override public void afterProcess(NodeComponent cmp) { System.out.println("lf after"); } }public void beforeProcess() { log.info("a before"); } @Override public void afterProcess() { log.info("a after"); } -
onSuccess和onError
组件的成功失败回调。
@Override public void onSuccess() throws Exception { } @Override public void onError(Exception e) throws Exception { }(1)onError执行后,afterProcess依然会执行
-
rollback
v2.11.0开始支持,本文章采用的是2.10.6,没有此项功能,建议去官网查看。
this关键字可以调用的方法
- this.getContextBean(clazz):获取创建流程时定义的上下文,clazz为类型。
@Test
public void test1() {
// 第三个参数为上下文对象,可以为对象或者为类名,支持多个。
// 需要注意的是,要么都穿bean,要么都穿class
LiteflowResponse resp = flowExecutor.execute2Resp(
"chain",
"1000111005",
new AcmpDomain("1", LocalDateTime.now())
);
}
// 任务中获取的方式:AcmpDomain bean = this.getContextBean(AcmpDomain.class);
-
getNodeId():获取组件ID
-
getName:获取组件别名
-
getChainName:获取当前流程名
-
getRequestData:获取流程参数
可以参考上文的
可以覆盖的方法-isAccess -
setIsEnd:是否立即终止路程
-
getTag:获取组件的tag标签
-
invoke2Resp:隐式调用子流程
选择组件
选择组件节点需要继承 NodeSwitchComponent,可用于 EL 的 SWITCH 关键字中,必须实现抽象方法 processSwitch。
相信经过上面的联系,已经掌握了普通组件的编写,后面就不一一列出普通组件的写法了。
根据nodeId选择
a,b是一个普通组件,下面是一个简单示例:
@Component(value = "d")
public class Dcmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
int i = ThreadLocalRandom.current().nextInt(3);
if (i == 0) {
return "a";
} else {
return "b";
}
}
}
EL规则
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain1">
SWITCH(d).TO(a,b);
</chain>
</flow>
测试代码
@Test
public void test3() {
LiteflowResponse resp = flowExecutor.execute2Resp("chain1");
System.out.println("resp.isSuccess() = " + resp.isSuccess());
}
根据表达式的ID选择
a,b,c是一个普通组件,可以通过修改EL规则中的 id和else中的返回值查看相同的效果,此两值必须一致,下面是一个简单示例:
@Component(value = "d")
@Slf4j
public class Dcmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
int i = ThreadLocalRandom.current().nextInt(3);
log.info("process d {}", i);
if (i == 0) {
return "a";
} else {
return "ww";
}
}
}
EL规则
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain3">
SWITCH(d).TO(a,WHEN(b,c).id("ww"));
</chain>
</flow>
测试代码
@Test
public void test4() {
LiteflowResponse resp = flowExecutor.execute2Resp("chain3");
System.out.println("resp.isSuccess() = " + resp.isSuccess());
}
表达式tag选择
a,c是一个普通组件,下面是一个简单示例:
@Component(value = "d")
@Slf4j
public class Dcmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
int i = ThreadLocalRandom.current().nextInt(3);
log.info("process d {}", i);
if (i == 0) {
return "tag:cat";
} else {
return "tag:dog";
}
}
}
EL规则
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain4">
SWITCH(d).TO(
a.tag("cat"),
c.tag("dog")
);
</chain>
</flow>
测试代码
@Test
public void test5() {
LiteflowResponse resp = flowExecutor.execute2Resp("chain4");
System.out.println("resp.isSuccess() = " + resp.isSuccess());
}
链路tag选择
此项就不给测试用例了,可以参考上文的 根据nodeId选择 改写代码并测试。
<chain name="chain5">
SWITCH(d).to(a, sub.tag("w1"));
</chain>
<chain name="sub">
THEN(c,d);
</chain>
条件组件
条件组件节点需要继承 NodeIfComponent,可用于 EL 的 IF ELIF ELSE 关键字中,必须实现抽象方法 processIf。
三元表达式
下面是一个简单示例:
@Component(value = "h")
@Slf4j
public class HCmp extends NodeIfComponent {
@Override
public boolean processIf() throws Exception {
int i = ThreadLocalRandom.current().nextInt(5);
log.info("process h {}", i);
return i >= 3;
}
}
EL规则
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain6">
IF(h,a,b);
</chain>
</flow>
测试代码
@Test
public void test6() {
LiteflowResponse resp = flowExecutor.execute2Resp("chain6");
System.out.println("resp.isSuccess() = " + resp.isSuccess());
}
如果条件组件返回false,执行后一个任务,返回true,执行前一个任务。
二元和ELSE
和三元表达式相比没有明显的优势,这里简单介绍下EL规则文件的编写,感兴趣可以自己做测试用例。
二元表达式
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="c8">
THEN(IF(h,a),b);
</chain>
</flow>
ELSE
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="c7">
IF(h,a).ELIF(b)
</chain>
</flow>
ELIF
ELIF和三元表达式一样重要,是条件组件中必须掌握的。
下面看一下测试用例,可以根据组件名自己编写条件组件和普通组件,这里不一一列出。
EL规则
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="i0">
IF(h,a).ELIF(i,b).ELIF(j,c).ELSE(d)
</chain>
</flow>
测试代码
@Test
public void test6() {
LiteflowResponse resp = flowExecutor.execute2Resp("chain6");
System.out.println("resp.isSuccess() = " + resp.isSuccess());
}
次数循环组件
次数组件节点需要继承 NodeForComponent,可用于 EL 的 FOR DO 关键字中,必须实现抽象方法 processFor。
下面是一个简单的测试。
@Component(value = "k")
@Slf4j
public class KCmp extends NodeForComponent {
@Override
public int processFor() throws Exception {
log.info("process k");
return 3;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="f1">
FOR(k).DO(a);
</chain>
</flow>
@Test
public void test8() {
LiteflowResponse resp = flowExecutor.execute2Resp("f1");
System.out.println("resp.isSuccess() = " + resp.isSuccess());
}
可以在组件中 this.getLoopIndex() 获取下标。**注意:**次数循环只会执行一次自身,后续都是DO中的组件。
条件循环组件
条件组件节点需要继承 NodeWhileComponent,可用于 EL 的 WHILE DO 关键字中,必须实现抽象方法 processWhile。
下面是一个简单的测试。
@LiteflowComponent(value = "l")
@Slf4j
public class LCmp extends NodeWhileComponent {
@Override
public boolean processWhile() throws Exception {
int i = ThreadLocalRandom.current().nextInt(3);
log.info("process l {}", i);
return i < 2;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="w1">
WHILE(l).DO(a);
</chain>
</flow>
@Test
public void test9() {
LiteflowResponse resp = flowExecutor.execute2Resp("w1");
System.out.println("resp.isSuccess() = " + resp.isSuccess());
}
可以在组件中 this.getLoopIndex() 获取下标。**注意:**条件循环每次执行时都会执行一下自身,这点和次数循环组件不同。
迭代循环组件
迭代循环组件节点需要继承 NodeIteratorComponent,可用于 EL 的 ITERATOR DO 关键字中,必须实现抽象方法 processIterator。
下面是一个简单的测试。
@LiteflowComponent(value = "m")
@Slf4j
public class MCmp extends NodeIteratorComponent {
@Override
public Iterator<?> processIterator() throws Exception {
Iterator<String> iterator = Lists.newArrayList("hello", "World").iterator();
log.info("process m {}", iterator);
return iterator;
}
}
@Component(value = "g")
@Slf4j
public class Gcmp extends NodeComponent {
@Override
public void process() throws Exception {
String currLoopObj = this.getCurrLoopObj();
log.info("process g {}", currLoopObj);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="m1">
ITERATOR(m).DO(g);
</chain>
</flow>
@Test
public void test10() {
LiteflowResponse resp = flowExecutor.execute2Resp("m1");
System.out.println("resp.isSuccess() = " + resp.isSuccess());
}
可以在组件中 this.getCurrLoopObj() 获取下标。
退出循环组件
迭代循环组件节点需要继承 NodeBreakComponent,可用于 EL 的 BREAK 关键字中,必须实现抽象方法 processBreak。
下面是一个简单的测试。
@Component(value = "n")
@Slf4j
public class NCmp extends NodeBreakComponent {
@Override
public boolean processBreak() throws Exception {
// 获取 迭代循环组件的当前值
Object o = this.getCurrLoopObj();
// 获取 次数循环组件 和 条件循环组件 的当前下标值
Integer loopIndex = this.getLoopIndex();
return true;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="m1">
ITERATOR(m).DO(g).BREAK(n);
</chain>
<chain name="f1">
FOR(k).DO(a).BREAK(n);
</chain>
<chain name="w1">
WHILE(l).DO(a).BREAK(n);
</chain>
</flow>
可以通过代码示例中的获取当前值或当前下标判断是否终止循环。
EL规则的写法
在常规组件中已经介绍了大量的组件编写,本章中不在编写新的组件了,只给出EL规则文件和流程图,如果确实有需要的话,可以留言,后面我补充一下。
串行编排
el1 和 el2 是等价的。
<?xml version="1.0" encoding="utf-8" ?>
<flow>
<chain name="el1">
THEN(a,b,c,d);
</chain>
<chain name="el2">
THEN(a,b,THEN(c,d));
</chain>
</flow>
并行编排
直接并行
这种情况下,执行顺序无法确认,任务执行顺序每次都可能不一样,但确定的是,每个任务节点都会执行。
<?xml version="1.0" encoding="utf-8" ?>
<flow>
<chain name="mm2">
WHEN(a,b,c);
</chain>
</flow>
和串行嵌套
默认情况下,b,c,d都执行完后才会执行e。
<?xml version="1.0" encoding="utf-8" ?>
<flow>
<chain name="el3">
THEN(a,WHEN(b,c,d),e);
</chain>
</flow>
如果想要任一节点执行完则忽略其他节点,可以参考下面的EL,在WHEN后面加上any设置为true。
<?xml version="1.0" encoding="utf-8" ?>
<flow>
<chain name="el4">
THEN(a,WHEN(b,c,d).any(true),e);
</chain>
</flow>
如果想要忽略错误,可以参考下面的EL,在WHEN后面加上ignoreError设置为true。b,c,d任一节点发生错误,e最终依然会执行。
<?xml version="1.0" encoding="utf-8" ?>
<flow>
<chain name="el5">
THEN(a,WHEN(b,c,d).ignoreError(true),e);
</chain>
</flow>
最后再看一个复杂的例子,由于确实挺复杂的,所以采用了子流程的方式,子流程也是一种EL规则的编写方式,这里不单独列出来介绍了:
<?xml version="1.0" encoding="utf-8" ?>
<flow>
<chain name="sub1">
THEN(b,c);
</chain>
<chain name="sub2">
THEN(d,e);
</chain>
<chain name="sub4">
WHEN(g,h,i);
</chain>
<chain name="el6">
THEN(a,WHEN(sub1,sub2),f,sub4,j);
</chain>
</flow>
选择编排
<chain name="el7">
THEN(a,WHEN(b,SWITCH(d).TO(e,g).DEFAULT(c)),f);
</chain>
新的关键字 DEFAULT,配合SWITCH TO使用,表示结果非e,g到c。
条件编排
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="el8">
IF(h,a).ELIF(i,b).ELIF(j,c).ELSE(d)
</chain>
</flow>
在常规组件-条件组件中介绍的非常详细了。
循环编排
-
FOR
<chain name="f1"> FOR(k).DO(a).BREAK(n); </chain> -
WHILE
<chain name="w1"> WHILE(l).DO(a).BREAK(n); </chain> -
ITERATOR
<chain name="m1"> ITERATOR(m).DO(g).BREAK(n); </chain> -
BREAK
上面三个都带有BREAK,BREAK关键字是在每次循环的末尾进行判断的。
-
异步循环
v2.11.0开始支持,高于本文章LiteFlow的版本,感兴趣可以到官网上看一下
其他编排
-
捕获异常
<chain name="chain1"> CATCH( THEN(a,b) ).DO(c) </chain> -
与或非表达式
在IF,WHILE中均可以使用。
<chain name="chain1"> IF(AND(x,y), a, b); </chain> <chain name="chain2"> IF(OR(x,y), a, b); </chain> <chain name="chain3"> IF(NOT(x), a, b); </chain>