🧪示例工程(Demo)
版本支持:v2.16.1+
前面八篇快速开始是按后端拆开讲的,每一篇都只给了配置片段。如果你想直接跑起来看效果,我们准备了一个覆盖全部七个后端的示例工程:
这个工程是一个Maven聚合工程,九个子模块各自对应一种后端形态。每个模块都是一个能独立启动的Spring Boot应用,自带一个网页,页面上可以直接发布规则、发布脚本、执行链路,把发布之后各节点秒级收敛这件事亲眼看一遍。
页面上的三块正好对应Rule-DB的三个动作:publishChain发布EL、publishScript发布脚本、FlowExecutor执行。结果区会把executeStep打出来,改完规则再执行一次,执行链路的变化立刻就能看见。
# 模块一览
| 模块 | 端口 | 后端 | 变更感知 |
|---|---|---|---|
| demo-sql-mysql | 8081 | MySQL 8.0(13306) | 轮询 |
| demo-redis-single | 8082 | Redis 7 单点(16379) | 轮询 |
| demo-redis-sentinel | 8083 | Redis 7 哨兵,1主2从3哨兵(26379-26381) | 轮询 |
| demo-redis-cluster | 8084 | Redis 集群,6节点(17000-17005) | 轮询 |
| demo-etcd | 8085 | etcd 3.5(12379) | watch |
| demo-zk | 8086 | ZooKeeper 3.9(12181) | watch |
| demo-postgresql | 8087 | PostgreSQL 16(15432) | 轮询 |
| demo-mongodb | 8088 | MongoDB 7 单节点副本集(27018) | 轮询 |
| demo-nacos | 8089 | Nacos 2.5.3 standalone(18848) | Listener推送 |
端口都做了偏移,不会和你本机已有的中间件撞上。九个模块各带一份只含自己所需中间件的docker-compose.yml,互不干扰,想跑哪个起哪个。
# 跑起来
前置只要JDK 21、Maven 3.8+和Docker(Compose v2)。以demo-sql-mysql为例:
# 1. 启动中间件(在模块目录下)
cd demo-sql-mysql
docker compose up -d --wait
# 2. 构建并启动(回到根目录)
cd ..
mvn -pl demo-sql-mysql -am package -DskipTests
java -jar demo-sql-mysql/target/demo-sql-mysql-1.0.0.jar
# 3. 打开页面
open http://localhost:8081/
其余模块把模块名和端口换掉即可。用完在对应模块目录下docker compose down。
demo-redis-sentinel要先生成.env
哨兵模块的compose依赖宿主机LAN IP,首次运行前先在根目录执行bash scripts/gen-env.sh,它会把HOST_IP写进根目录和demo-redis-sentinel/两份.env。换了网络环境(LAN IP变了)要重跑一次并重建容器。其余八个模块不涉及.env。
# 一个模块只有四个文件
这是这个demo最值得看的地方。剥掉页面和docker,接入Rule-DB真正要写的东西只有四处,九个模块的结构完全一样,换后端只换其中两处。
# 1. pom:starter + 一个后端 + 脚本插件
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-rule-db-sql</artifactId>
</dependency>
<!-- 要发布脚本才需要,只发布普通chain可以不引 -->
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-javax-pro</artifactId>
</dependency>
换后端就是把liteflow-rule-db-sql换成-redis、-zk、-etcd、-postgresql、-mongodb、-nacos。同一时刻classpath上只能有一个后端模块,多了启动会直接报错。
# 2. application.yml:配置真的只有几行
spring:
application:
name: demo-sql-mysql # 这就是Rule-DB的隔离名
datasource:
url: jdbc:mysql://127.0.0.1:13306/liteflow_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root123456
liteflow:
rule-db:
enabled: true
sql:
auto-init-table: true # 开发期让插件建表
sync:
poll-seconds: 2 # demo调快到2秒,方便看收敛
reconcile-seconds: 60
DataSource没有指名,插件按类型从容器里取走唯一那个就用了,连接池沿用Spring Boot的。四张表由auto-init-table建。轮询周期默认3秒,demo调成2秒纯粹是为了页面上少等一会儿。
对照着看几个后端的配置差异,比看文档表格直观:
# redis 单点
liteflow.rule-db.redis.address: redis://127.0.0.1:16379
# redis 哨兵:地址填哨兵,外加 master-name
liteflow.rule-db.redis.address: redis://127.0.0.1:26379,redis://127.0.0.1:26380,redis://127.0.0.1:26381
liteflow.rule-db.redis.master-name: mymaster
# redis 集群:必须配 key-hash-tag
liteflow.rule-db.redis.address: redis://127.0.0.1:17000,...,redis://127.0.0.1:17005
liteflow.rule-db.redis.key-hash-tag: lf
# zk / etcd / nacos:一行连接地址
liteflow.rule-db.zk.connect-string: 127.0.0.1:12181
liteflow.rule-db.etcd.endpoints: http://127.0.0.1:12379
liteflow.rule-db.nacos.server-addr: 127.0.0.1:18848
# 3. PublisherConfig:把Publisher交给Spring管
RulePublisher是写入端,和执行端FlowExecutor是两条独立的路。demo把它声明成一个bean,destroyMethod = "close"让Spring负责关:
@Configuration
public class PublisherConfig {
@Bean(destroyMethod = "close")
public RulePublisher rulePublisher(DataSource dataSource,
@Value("${spring.application.name}") String applicationName) {
SqlPublisherConfig config = SqlPublisherConfig.builder()
.applicationName(applicationName) // 必须和执行端解析出的隔离名一致
.dataSource(dataSource) // 复用连接池,不要用 url
.build();
return RulePublisherFactory.create(config);
}
}
九个模块唯一的差别就是这里的XxxPublisherConfig:SQL/PostgreSQL传dataSource,Redis传address,Mongo传uri和database,zk传connectString,etcd传endpoints,Nacos传serverAddr。applicationName统一从spring.application.name取,这样天然就和执行端对上了——这一项配错是最常见的坑,规则发布进去了但执行端读不到。
# 4. RuleInitRunner:启动时灌一份初始规则
demo用CommandLineRunner在启动时发布两个Java脚本和一条链路,省得你打开页面还得先手动造数据:
@Component
public class RuleInitRunner implements CommandLineRunner {
private final RulePublisher rulePublisher;
@Override
public void run(String... args) {
rulePublisher.publishScript(PublishScriptRequest.builder()
.nodeId("s1").name("脚本s1").type("script").language("java")
.script(S1_SCRIPT).build());
rulePublisher.publishScript(PublishScriptRequest.builder()
.nodeId("s2").name("脚本s2").type("script").language("java")
.script(S2_SCRIPT).build());
rulePublisher.publishChain(PublishChainRequest.builder()
.chainId("demo_chain").el("THEN(s1, s2);").build());
}
}
S1_SCRIPT是一段完整的Java类源码,这是javax-pro脚本的写法,注意它是extends NodeComponent的完整类,不是表达式片段:
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
public class S1 extends NodeComponent {
@Override
public void process() {
DefaultContext context = this.getFirstContextBean();
Object requestData = this.getRequestData();
System.out.println("[s1] receive requestData: " + requestData);
context.setData("s1Result", "s1 handled: " + requestData);
}
}
s2则从上下文里把s1Result读出来再往下传,这样一条链路跑完你能从executeStep和上下文两个角度确认数据确实流过去了。
Publisher的调用是幂等的UPSERT,重启应用会让版本号继续加一,不会报冲突。想要"仅当不存在时创建"的语义,就加.expectedVersion(0L)。
# 三个实验,把Rule-DB的核心机制走一遍
页面开着,按顺序做这三件事,Rule-DB的运行模型基本就清楚了。
实验一:改EL,不重启。 把第1块的EL从THEN(s1, s2);改成THEN(s2, s1);,点发布规则,等两秒(etcd/zk/nacos不用等),到第3块点执行。executeStep的顺序跟着变了,应用一次没重启。这就是"存储是权威源"的意思——EL改了,执行端下一次执行会拿到新版本。
实验二:加一个脚本节点。 第2块默认给了一段s3的源码,点发布脚本,再把EL改成THEN(s1, s2, s3);发布一次,然后执行。控制台会打出[s3] requestData: ...。整个过程没有编译、没有打包、没有发版——新节点是运行期从存储里懒加载并编译出来的。
实验三:故意写错,看它什么时候失败。 把EL改成THEN(s1, s2, s99);发布,注意发布是成功的。失败发生在你点执行的那一刻,报的是找不到s99。这正是文档里那句"Publisher只校验请求字段和存储约束,发布时不会编译EL"的实际表现。这一点很关键:Rule-DB不替你做EL的语义校验,引用了不存在的组件、子chain或脚本节点,要到执行端首次编译这条chain时才暴露。所以管理后台该有的发布前校验,得自己加。
顺手还能验证一下懒加载:应用启动日志里不会有解析全量规则的动作,第一次执行demo_chain才回源拉EL和脚本并编译,第二次执行就命中缓存了,热路径上没有远程调用。
# 页面背后的三个接口
如果你要写自己的管理后台,直接照抄DemoController就行,它把Rule-DB写入端的全部用法压在了一百行里:
| 接口 | 请求体 | 对应API |
|---|---|---|
POST /api/publish/chain | {"chainId":"demo_chain","el":"THEN(s1, s2);"} | rulePublisher.publishChain(...) |
POST /api/publish/script | {"nodeId":"s3","name":"脚本s3","type":"script","script":"<java源码>"} | rulePublisher.publishScript(...) |
POST /api/execute | {"chainId":"demo_chain","requestData":"hello"} | flowExecutor.execute2Resp(...) |
type可选script、boolean_script、switch_script、for_script,对应脚本节点在EL里的四种用法。完整的字段约束、版本号语义和乐观锁用法见发布API与写入规范。
管理后台不需要拉起FlowExecutor
RulePublisher只依赖后端模块那个jar,不需要LiteFlow执行期。demo把发布端和执行端放在同一个进程里纯粹是为了一个页面能演示完,真实项目里管理后台和业务应用通常是分开部署的,两边靠applicationName这个隔离名对上。
# demo踩过的坑,都写在注释里
这九个模块是真连本地docker起的中间件,过程中撞到的环境问题都留在了compose文件和README里。几个和LiteFlow配置直接相关的:
- Redis集群必须配
key-hash-tag。Rule-DB的键要落在同一个slot上,不配这一项在集群模式下会报CROSSSLOT。demo里用的是lf。 - MongoDB必须以副本集运行。
MongoRuleRepository的读写走ClientSession.withTransaction,standalone不支持事务,会报Transaction numbers are only allowed on a replica set member or mongos。demo的compose用--replSet rs0起,并用一个一次性容器执行rs.initiate,等选出PRIMARY才退出。 - MongoDB模块排除了
MongoAutoConfiguration。否则Spring Boot会自动建一个连默认27017的MongoClientbean,而MongoConnectionManager按类型借用容器里的bean,从而绕过liteflow.rule-db.mongodb.uri,连到一个不存在的端口上。用Spring Data Mongo的项目要注意这个借用关系,多个同类bean时请显式指名。 - Nacos必须映射gRPC端口。nacos-client 2.x同时用HTTP(8848)和gRPC(9848/9849),compose里漏掉任一个gRPC端口,客户端会因长连接建不起来报
Client not connected, current status: STARTING,发布和监听都不可用。
其余的Redis哨兵网络announce、etcd镜像归档、macOS端口占用这些纯环境问题,README里有完整说明,不影响你理解Rule-DB本身。
版本
demo的父pom把liteflow.version定在2.16.1。如果你在自己的工程里遇到rule-db配javax-pro脚本时报ScriptLoadException: script for node[x] is not loaded,请升级到2.16.1.1,那是一个针对Rule-DB启动阶段初始化钩子的修复版本。



