尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

轻量级C语言规则流引擎ruflo:面向物联网边缘设备的本地化决策方案

轻量级C语言规则流引擎ruflo:面向物联网边缘设备的本地化决策方案 1. 项目背景我做了一个名叫 ruflo 的轻量规则流引擎先介绍一下 ruflo 是什么。它的定位很明确给资源受限的物联网边缘设备提供一个不依赖云端的本地规则流处理框架。名字拆开来看ru取自 ruleflo取自 flow合在一起就是“规则流”。我最初在做一个智能家居网关项目时发现传统规则引擎在嵌入式设备上跑不动——要么依赖 Java/Python 环境要么动辄几百 KB 的内存占用更麻烦的是规则建模太重边缘设备根本承载不了数据实时上云的带宽压力。于是动手写了一个适合底层单片机与轻量级 Linux 设备运行的 C 语言规则流引擎也就是现在的 ruflo。核心解决的问题很简单当设备本地产生一条传感器数据时ruflo 能在毫秒级时间内完成事件匹配、条件判断和多动作联动全程不依赖网络。比如温湿度传感器上报温度超过 28℃引擎自动触发风扇继电器打开同时把一条状态记录写入本地 Flash——这些事情完全在设备内闭环处理不需要经过云端中转。对于智能家居、农业大棚、工业现场的传感器节点这类场景这个需求非常现实。用 ruflo 有两个门槛上的优势。第一移植成本低整个核心代码只有两个源文件加一个头文件不依赖第三方库标准 C99 编译器就能编译常见的 Keil、IAR、GCC 都能跑针对 ARM Cortex-M 系列单片机和 Linux 平台都做了适配。第二规则描述方式直观采用三段式结构“事件 条件 动作”配置人员不需要写代码用纯文本规则文件就能完成逻辑编排。这篇文章我会把 ruflo 从协议设计、核心实现到实际部署的完整过程拆开讲包括我在项目中踩过的一些坑和问题排查思路希望对做边缘计算、嵌入式开发、物联网网关的朋友有实际帮助。2. 整体架构设计与核心思路拆解2.1 为什么不做云端规则引擎而选择本地化方案在设计 ruflo 之前我先盘了一下现有的方案。主流的开源规则引擎如 Drools、Node-RED本身都是好东西但它们的设计目标跑在服务器或者树莓派级别的主机上依赖完整的 JVM 运行环境或者 Node.js 运行时。放到实际工业现场一个采用 STM32F103 芯片的采集终端只有 64KB RAM一个采用 ESP32 的传感器节点要同时承担 WiFi 协议栈和业务逻辑这些环境根本装不下重型规则引擎。另外很多项目强调“离线自治”能力。我在做的一个农业大棚项目现场部署了 20 多个传感器节点网络信号差的时候数据经常上传失败。如果规则判断依赖云端断网期间所有自动化逻辑全部失效这在实际使用中是很麻烦的。解决的办法就是在设备端放置一个“看门狗”式的自治引擎保证本地闭环。ruflo 的顶层设计思路简单直接规则引擎只负责“匹配和触发”不负责“数据存储和传输”数据链路以插件方式扩展。这样既保证了核心逻辑的轻量也保证了后续扩展的灵活性。还有一个考量是调试成本。云端规则引擎改一条规则要经历“改配置—发布—等待生效—观察日志”的循环在边缘场景下效率很低。ruflo 把规则放在本地文件系统或 Flash 分区中修改后执行一次 reload 命令即可生效整个过程不到 100ms这个体验对现场调试人员非常友好。2.2 规则建模用“事件-条件-动作”三段式覆盖绝大多数场景我第一次设计规则模型时想得太复杂参考了复杂事件处理引擎里的事件流窗口、时间衰减、模式匹配等概念结果代码膨胀快资源开销大而且对使用者来说理解门槛很高。做了几个版本后我把模型狠心砍到最精简的三段式WHEN 某个事件发生IF 某些条件满足THEN 执行一系列动作。以实际的智能家居场景为例事件Eventtemp_high表示温度传感器上报超过阈值的警示事件。条件Conditiondevice.zone bedroom并且time_window 5min表示该事件发生在卧室区域并且在 5 分钟窗口内没有重复触发过。动作Actionrelay.open(channel_1) log.write(temp alarm)即打开继电器通道 1 并记录日志。这种三段式模型的好处是容易映射到工程实现。事件是引擎的输入中断源条件是一个可嵌套的布尔表达式求值器动作则是一个函数注册表每种动作类型对应一个 C 函数指针。新增一种动作类型只需要实现一个注册函数然后告诉引擎这个动作的处理函数地址即可。可能有读者会问这种模型会不会太简单处理不了复杂联动我承认它有表达边界但实际部署下来覆盖了项目中 80% 的联动需求。真正复杂的场景比如多事件时序依赖、窗口聚合统计这一类ruflo 的定位是交给上层业务模块处理而不是无限堆叠引擎复杂度。小体积、可控、易用是边缘设备的优先诉求这一点必须在设计之初就坚持。2.3 基于发布-订阅的事件总线让模块间彻底解耦ruflo 的核心组件是一个轻量级的事件总线。所有模块——传感器驱动、继电器控制、日志系统——都不直接调用对方接口而是向总线“发布”事件或“订阅”感兴趣的规则。事件总线的实现基于一个定长环形缓冲区和哈希索引表。传统嵌入式开发中模块间最常见的是函数直接调用比如temp_sensor_read()然后if (temp 28) relay_open()。这种写法在逻辑简单的场景下没问题但一旦联动关系复杂比如一个温度值既需要触发风扇、又要控制湿度调节器、还要上送报警就会形成网状耦合任何一处的修改都可能连锁引发其他模块问题。事件总线把网状耦合变成了星形拓扑生产者和消费者之间只认识事件类型不认识彼此。事件总线的核心数据结构是typedef struct { uint16_t event_id; uint32_t timestamp; uint8_t data[MAX_EVENT_DATA_LEN]; } ruflo_event_t;订阅者注册时声明自己关心的事件 ID总线维护一张哈希表事件到达时根据 ID 做常数时间查找找到对应的订阅者链表后逐个分发。整个分发过程不分配动态内存完全基于静态数组完成这样在长时间运行的嵌入式设备上不会产生内存碎片。3. 核心实现细节与关键模块拆解3.1 事件消息格式设计紧凑、定长、可校验在实际物联网设备上温度、湿度、开关状态这些数据都是结构化的不适合用什么复杂序列化格式。最初我尝试过 JSON但解析 JSON 在单片机上开销太大一个cJSON库就要占 8KB 以上 ROM而且动态分配内存的特性在资源受限环境下容易出问题。后来切换到自描述的二进制格式整条事件消息固定占用 48 字节在任何平台上都不会产生内存抖动。事件消息的格式如下#define RU FLO_MAX_EVENT_DATA_LEN 32 typedef struct { uint16_t magic; // 起始标记,固定为0xAC01 uint8_t version; // 协议版本号 uint8_t event_type; // 事件类型ID uint16_t payload_len; // payload长度 uint8_t payload[RU FLO_MAX_EVENT_DATA_LEN]; // 数据负载 uint16_t crc16; // 校验字段 } ruflo_message_t;magic用于快速判断消息是否被截断或错位crc16用查表法计算确保时间戳、数值等负载信息在总线传输或 Flash 存储后没有被破坏payload_len字段用于支持变长数据但在本协议设计中最大长度固定为 32 字节这样缓冲区可以静态分配不需要动态申请内存。这样的设计也为将来扩展预留了空间。比如需要通过串口对接外部传感器只需要在上层做一个协议转换适配器把外部数据解析后填充进ruflo_message_t结构体就能接入事件总线。我这里在 struct 里写的是RU FLO实际项目代码中是连写的这个是早期版本命名上的失误大家写代码的时候不要学我随手敲出空格容易导致宏定义编译不过。3.2 事件分发机制哈希索引加链地址法不碰动态内存事件总线的最核心操作是事件分发。如果只有一个订阅者线性遍历就够了但实际项目中有温度、湿度、光照、烟雾、人体红外等多种事件源每种事件可能有多个订阅者所以需要更快的索引方式。我实现了一个基于哈希表的订阅管理器表项数量固定在 64每个表项挂一条订阅者链表。#define SUBSCRIBER_HASH_SIZE 64 typedef struct subscriber_node { uint16_t event_id; ruflo_event_callback_t cb; void *user_ctx; struct subscriber_node *next; } subscriber_node_t; static subscriber_node_t subscriber_pool[MAX_SUBSCRIBERS]; static subscriber_node_t *subscriber_hash[SUBSCRIBER_HASH_SIZE];哈希函数直接用事件 ID 对表项数取模hash_index event_id % SUBSCRIBER_HASH_SIZE。因为项目的事件 ID 数量一般不会超过 64 个冲突概率很低。所有节点都从静态数组池subscriber_pool中分配通过空闲链表管理。这样完美避开了malloc/free在长时间运行中造成的内存碎片问题。我在一个连续运行 72 小时的实测中统计了内存变化发现堆内存占用完全稳定这也是静态池设计带来的直接收益。事件分发的大致逻辑是当一个驱动模块调用ruflo_event_publish()时总线先根据事件 ID 计算哈希位置然后遍历该位置的链表逐个匹配事件 ID匹配成功后进一步把消息中的 payload 传给订阅者的回调函数。每个订阅者的回调函数返回值有RU FLO_OK和RU FLO_ABORT两种ABORT可以用来停止后续分发在某些安全场景下比较有用。3.3 条件求值器小型表达式计算支持比较和逻辑运算如果说事件总线是 ruflo 的“血管”那么条件求值器就是它的“大脑”。一条规则是否执行动作完全由条件表达式的结果决定。为了保持简单条件表达式被设计为前缀表达式支持以下操作符操作符含义举例GT大于(GT temp 28.5)LT小于(LT humidity 60)EQ等于(EQ zone 1)AND逻辑与(AND cond1 cond2)OR逻辑或(OR cond1 cond2)表达式以逆波兰形式存储在规则的“条件区”中求值器用一个固定大小的栈来执行。比如规则“温度大于 28 且湿度小于 60”表达式生成器会把它编译成如下字节码序列PUSH_VAR temp_index PUSH_CONST 28.5 GT PUSH_VAR humidity_index PUSH_CONST 60 LT AND求值器顺序读取字节码遇到变量就压栈遇到操作符就从栈顶弹出两个操作数计算再把结果压回。所有数值统一用 IEEE 754 单精度浮点数表示在嵌入式场景中足够用。这个栈大小只有 16因为条件嵌套深度一般不会超过 5 层所以不会溢出。这里分享一个实际经验温度数据经过传感器读取后往往有较大的噪声波动如果直接拿原始值与阈值比较经常会出现继电器频繁开关的现象。解决的办法是在条件变量进入求值器之前先做一级滑动平均滤波。我在 ruflo 中内置了一个 4 点滑动平均滤波器用户通过配置filter_win 4即可启用这个参数在实际大棚温控中非常管用能显著减少继电器抖动磨损。3.4 动作执行器函数注册表加参数绑定条件满足后引擎需要触发动作。ruflo 的动作是基于注册函数指针实现的每种动作类型对应一个处理函数。比如控制 GPIO 的动作类型是ACT_GPIO_WRITE写日志的动作类型是ACT_LOG。typedef void (*ruflo_action_fn)(const ruflo_action_param_t *param, void *ctx); typedef struct { uint16_t action_type; ruflo_action_fn handler; } ruflo_action_reg_t;规则文件中的动作描述与动作处理器之间需要有一层参数绑定机制。这个机制的实现方式是动作参数保存在一个参数表中规则文件通过param_id引用参数表中的参数。比如要控制 3 号继电器规则是DO relay_set(param_id5)参数表中 5 号参数的值是{channel: 3, state: ON}。这样好处很明显修改继电器编号时只需要改参数表不需要修改规则文件。在现场调试中这个设计帮我省去了反复修改配置的麻烦一条规则可以复用多组参数灵活度提升不少。动作执行器的另一个关键点是异常处理。GPIO 写入失败、Flash 写入失败等错误不能简单地吞掉需要在日志中留下痕迹。所以在动作处理函数中我强制要求返回一个ruflo_ret_t状态码执行器统一收集并上报给日志模块。这样规则执行失败后运维人员可以通过日志快速定位是哪一步出了问题。4. 从零到一完整部署 MySQL 风格配置到边缘设备全过程4.1 环境准备需要用到的交叉编译工具链部署 ruflo 之前需要准备交叉编译环境。我假设目标平台是常见的 ARM Cortex-M4 系列单片机采用 arm-none-eabi 工具链同时也支持 x86 Linux 环境便于在开发机上做单元测试。以 Linux 开发机为例安装工具链sudo apt-get update sudo apt-get install gcc-arm-none-eabi cmake make如果目标平台是 ESP32可以参考 ESP-IDF 环境的设置如果是 STM32也可以用 STM32CubeMX 生成的工程直接加入 ruflo 源码。ruflo 的源码结构非常清晰只有四个文件ruflo.h对外头文件包含所有接口声明和数据结构定义。ruflo_core.c事件总线与规则匹配核心逻辑。ruflo_actions.c内置动作执行器实现。ruflo_cli.c规则加载与重载的命令行接口。把这个四个文件加入工程后还需要在编译配置中指定RU FLO_MAX_SUBSCRIBERS、RU FLO_MAX_RULES等编译期常量。这部分我在下方流程中给出具体推荐值。4.2 配置模板一份可直接抄写的规则文件ruflo 的规则文件采用纯文本格式每行一条指令支持#注释。下面是我在温室大棚项目中实际使用的一份配置模板覆盖了温度联动、湿度联动和断电保护三个场景。# 温室环境联动规则 v1.3 # 事件类型: 0x01温度上报 0x02湿度上报 0x03断电通知 # 规则1: 温度高于28度且持续5分钟,打开风扇并记录告警 RULE temp_high_alert WHEN event_id 0x01 IF (GT temp_value 28.0) AND (GT uptime_min 5) DO action_id 1, param_id 5 DO action_id 2, param_id 1 END # 规则2: 湿度低于40%时,打开加湿器,同时发送本地提示 RULE humidity_low_alert WHEN event_id 0x02 IF (LT humidity_value 40.0) DO action_id 1, param_id 6 DO action_id 3, param_id 2 END # 规则3: 检测到断电事件(事件ID 0x03),立刻保存关键状态 RULE power_loss_save WHEN event_id 0x03 IF (EQ emergency_flag 1) DO action_id 4, param_id 3 END动作 ID 对应关系1GPIO 写操作2写日志3本地提示4关键状态保存。参数表中 5 号参数对应“风扇继电器”6 号参数对应“加湿器继电器”。规则文件通过串口命令行加载在设备启动时执行ruflo load /flash/rules.conf命令。4.3 编译与移植从 PC 端模拟到单片机的完整流程在开发初期我通常先在 PC 的 Linux 环境下编译调试因为能直接用 gdb 查看内存排错效率高。下面是一段简单的编译命令gcc -o ruflo_sim ruflo_core.c ruflo_actions.c ruflo_cli.c sim_main.c -I. -Wall -stdc99sim_main.c是我写的一个模拟入口里面会初始化引擎、加载规则文件然后通过ruflo_event_publish()模拟传感器事件。在 PC 上验证规则逻辑没问题后再用交叉编译工具链生成单片机固件。交叉编译时需要注意几个点。因为单片机没有文件系统规则文件不能直接加载需要把规则文本转换为 C 语言字符串数组或者使用文件系统组件比如 LittleFS 或 SPIFFS预先烧写规则文件。我一般采取“编译时嵌入 运行时重载”的双重机制出厂固件内嵌一份默认规则现场调试时通过命令行接口重新加载规则这样既保证开箱即用的可靠性也保留现场调整的灵活性。编译 STM32 版本时建议做如下裁剪配置配置宏推荐值说明RU FLO_MAX_RULES64最大规则条数RU FLO_MAX_SUBSCRIBERS32最大订阅者数量RU FLO_MAX_ACTIONS16动作注册上限RU FLO_MAX_EVENT_DATA_LEN32事件负载最大长度RU FLO_STACK_SIZE16条件求值栈深度这样配置后ruflo 核心代码的静态内存占用约为 12KB RAMROM 占用约为 6KB。在资源适中的 STM32F103 上完全可以轻松运行即使在资源更紧的 8 位单片机上也能勉强跑起来不过没有硬件乘法指令的计算性能会稍弱一些。4.4 联调验证用模拟事件检验规则触发的正确性规则加载完之后需要做完整的联调验证。我会写一个自动化测试脚本模拟各种传感器事件并检查动作执行情况。下面是对应的模拟代码核心片段#include ruflo.h void app_main(void) { ruflo_init(); ruflo_load_rules(flash:/rules.conf); // 构建一条温度上报事件:数据区前两个字节是温度值(单位0.1度) ruflo_message_t msg; msg.event_type 0x01; msg.payload_len 2; msg.payload[0] 30; // 数值300,对应30.0度 msg.payload[1] 0; ruflo_event_publish(msg); // 检查继电器动作是否触发 int relay_state gpio_read(RELAY_CHANNEL_1); assert(relay_state 1); }联调时最重要的是验证“条件不满足时不执行动作”。很多新手只测试了正向来例忽略了反向测试导致误触发。我一般会在测试用例中同时覆盖四类情况条件满足且应触发、条件不满足且不应触发、事件 ID 不存在、重复事件触发去重。把这个测试用例集跑完规则的可靠性才有保障。5. 常见问题与排查技巧实录5.1 事件发布了但规则不触发可能问题出在哪这是新人接触 ruflo 时问得最多的一个情况。根据我自己的排错经验最常见的三个原因依次是事件 ID 不匹配、条件表达式解析失败、订阅者单元格耗尽。事件 ID 不匹配比较常见于多人协作项目。传感器驱动写的publish事件 ID 是 0x01但规则文件里WHEN event_id 1用的是十进制 1在 C 语言中 0x01 和 1 确实是相等的但有些同学会把配置写成event_id 1加上引号变成字符串类型不匹配就直接报错了。所以排查的第一步是打印事件总线上实际发布的事件 ID再和规则里的WHEN比较。条件表达式解析失败通常来自规则文件格式错误。比如少了括号、操作符拼写错误等。ruflo 的命令行接口中有一个ruflo dump rules命令可以打印解析后的规则 AST 结构能直观看到哪个环节解析异常。曾经有一次我漏写了一个右括号规则加载命令居然不报错而是静默忽略了该条规则后来我在加载函数中增加了严格的括号配对检查并输出具体的行列号这个问题才彻底解决。订阅者单元格耗尽也遇到过。如果你用静态池限制订阅者数量为 32但规则表中有 40 条规则就有部分规则永远无法订阅到事件表现就是“部分规则生效部分规则不生效”排查时很容易误认为是逻辑问题。解决办法是把RU FLO_MAX_SUBSCRIBERS调大并增加一个系统告警当订阅者数量达到上限的 80% 时在日志中输出警告。5.2 条件值总是差一位浮点和整型的精度坑在条件求值中我遇到过不少与精度相关的坑。比如温度传感器返回的数据是整数类型表示“温度的 10 倍”即 283 表示 28.3 度。如果在求值器中把 283 转换为浮点数再和 28.5 比较完全没有问题但如果忘记转换直接用整数 283 和浮点数 28.5 比较结果是 283 28.5 成立这完全错了。我的处理方法是在消息进入事件总线之后、条件求值之前统一做一次单位转换。在规则模型中加入一个“变量修饰符”字段比如temp_value可以声明scale 0.1这样在求值时引擎会先把原始整数乘以 0.1 得到标准单位浮点值再进行逻辑比较。这个修饰符在规则文件中的写法是IF (GT temp_value*0.1 28.5)这里也建议读者在写规则时固定好数据的单位约定比如温度统一用摄氏度、湿度统一用百分比不要一边用原始 ADC 值一边用物理量否则排查成本会非常高。5.3 动作执行了两次是不是引擎有 bug这个问题绝大多数时候不是引擎的问题而是过程调用链反复触发导致的。最常见的场景规则 A 在触发后执行“打开风扇”的动作风扇状态的改变又发布了一条“风扇状态变化”事件如果还有一条规则订阅了这个事件并执行相同的 GPIO 写操作那么动作就重复了。解决的办法有两个层面。第一动作执行器中加入去重机制对同一个参数在 500ms 内不重复执行同一动作。这个机制在 ruflo 中默认开启可以通过RU FLO_ACTION_DEDUP_TIME_MS调整。第二在规则设计上避免“状态变化事件”和“状态命令动作”互相触发通常在事件类型设计时区分为“外部传感器上报”与“内部状态更新”内部状态更新不上事件总线。经过这个调整后循环触发问题基本绝迹。5.4 长时间运行后规则突然失效内存和 CRC16 异常排查嵌入式设备长时间运行后出现规则失效通常需要重点排查内存覆盖和存储数据损坏。ruflo 采用静态内存池方案理论上不会产生内存碎片但如果你在回调函数中使用了自己的动态内存分配且没有正确释放就会破坏堆空间可能导致订阅者链表指针被意外改写。排查这类问题时我强烈建议在调试期打开地址消毒器AddressSanitizer。Linux 模拟环境下编译时加上-fsanitizeaddress在 PC 端跑压力测试就能直接定位内存越界。在单片机上我会定期输出订阅者链表的完整性校验值并和上一次的值比较一旦不一致就主动触发异常复位恢复机制。CRC16 异常通常源于存储介质位翻转。外部 SPI Flash 在温度变化大或供电不稳时偶尔会发生比特位翻转这会导致规则文件加载后校验失败。ruflo 在做规则加载时会用 CRC16 整体校验规则文件失败则回退到出厂默认规则。这个机制的代码逻辑很简单但带来的稳定性提升非常明显现场维护人员不需要远程处理规则文件损坏的问题设备能自动恢复可用状态。6. 实战优化技巧与性能调优方向6.1 规则匹配的热路径优化避免线性扫描如果规则数量较多每次事件到达都线性扫描全部规则性能会随规则数线性下降。我在 ruflo 中做了一个简单优化在加载规则时为每个事件 ID 建立一条规则链事件发布时只需要遍历该事件 ID 对应的规则链而不需要扫描全部规则。这个优化在规则数量不超过 20 条时效果不明显但到了 60 条以上时差异就很显著。实测数据在线性扫描方案中60 条规则时一次事件匹配需要约 32 微秒采用事件 ID 索引后相同规则量下降到 5 微秒左右这对高频传感器事件上报场景非常重要。如果你的设备每秒要处理 200 次传感器事件这个差异直接决定了 CPU 是否有余量处理其他业务逻辑。6.2 定时器与时间窗口让规则支持“持续 5 分钟”语义很多联动规则包含“持续 N 分钟后触发”的时间窗口语义。比如前面写的“温度高于 28 度且持续 5 分钟”这里的“持续”不是瞬时判断而是一个时间段内的连续条件满足。ruflo 通过内置的软件定时器实现了这个能力。具体实现是当事件匹配条件后先不立即执行动作而是启动一个定时器定时时间到期后再次检查条件是否仍然满足满足才执行动作如果中间条件被打破定时器取消。这个机制在继电器防抖和告警确认场景中非常实用。6.3 规则动态重载热更新策略的最小实现规则文件可能随业务需求变化ruflo 支持运行时重载。这个功能的核心是将“旧规则集”和“新规则集”双缓冲管理。加载新规则前先解析和校验成功后一次性切换指针切换过程不打断正在执行的事件分发。我在实际部署中验证过热重载期间最多只丢失一个正在处理的事件而不会出现规则一半新一半旧的情况。这个特性对 24 小时连续运转的现场设备来说是一个很关键的升级路径。7. 撰写项目落地方案的一些个人体会回到项目本身ruflo 并不是一个“闭门造车”的玩具代码它已经在两个实际场景中跑过完整闭环一个是家庭智能网关负责本地设备联动另一个是温室环境监测节点负责温湿度超限告警和设备控制。两套系统都有 7x24 小时连续运行的记录稳定性表现符合预期。个人最大的心得是“轻量优先”的设计哲学带来的回报。最初也想过加入多级事件流窗口、复杂模式识别和可视化管理界面但这些功能每增加一项核心代码体积就会膨胀调试周期也会拉长。边缘设备的开发中稳定性和可控性永远是第一位的如果你不能确定某个特性在几百个节点上会不会成为隐藏炸弹不如先不放进去。另外命名这件事值得多说一句。早期版本结构体里我写了RU FLO这样的带空格宏编译时直接报错检查半天才发现是手滑。编译不过能立刻发现但如果是命名不规范导致代码风格混乱在嵌入式多文件联编时排查起来会非常痛苦。后来我对整个代码库做了一次命名规范清理全部改用ruflo_前缀接口文件、内部宏、函数名统一风格可读性提升非常明显。你如果也想在自己的物联网项目里用上类似的本地规则流方案建议从最简单的“事件-条件-动作”开始先把核心链路打通再逐步增加复杂特性。ruflo 以不到 20KB 的资源占用覆盖了大多数边缘场景的需求这个取舍已经被实际使用验证过了。如果以后再开源我会把单元测试和文档体系一起发布让更多人能快速上手。
返回列表