4、规则语言入门:Drools/CLIPS规则语法基础、条件(when)与动作(then)结构

在FMS报警规则引擎中,规则语言是定义“何时触发报警”与“触发后执行什么联动”的核心工具。本章聚焦于业界最成熟的两种规则语言——Drools(Java生态)与CLIPS(C语言经典实现),深入解析其语法基础、条件(when)与动作(then)结构,并结合厂务监控场景给出可直接参考的规则模板。

学习目标:
  • 掌握Drools规则文件(.drl)的基本结构与关键字
  • 理解CLIPS规则的定义方式与事实匹配机制
  • 熟练编写条件(when)中的多条件组合与模式绑定
  • 掌握动作(then)中的报警触发、设备控制与数据持久化写法
规则引擎核心结构:条件(when)→ 动作(then) 事实(Fact) 传感器数据 / 设备状态 规则引擎(Inference Engine) 模式匹配 · 冲突消解 · 规则执行 Rete / Treat 算法 动作(Action) 报警 / 联动 / 日志 规则库(Rule Base) .drl 文件 / CLIPS 规则集 💡 核心流程: 事实(Fact)→ 规则引擎匹配规则库中的条件(when)→ 满足时执行动作(then)→ 产生新事实或控制指令

4.1 Drools 规则语法基础

Drools 使用 .drl(Decision Rule Language)文件定义规则,是 Java 生态中最广泛使用的规则引擎之一。其语法结构清晰,天然支持对象化的事实模型。

4.1.1 规则文件基本结构

package com.fms.alarm.rules;          // 包声明(可选,用于组织规则)

import com.fms.model.SensorData;      // 导入事实类
import com.fms.model.AlarmRecord;     // 导入动作相关类

global AlarmService alarmService;     // 全局服务(注入外部服务)

rule "HighTemperatureAlarm"           // 规则名称(必须唯一)
    salience 10                       // 优先级(数值越大越优先)
    no-loop true                      // 防止无限循环
    when
        // 条件部分:匹配满足条件的事实
        $s : SensorData( temperature > 85.0, sensorType == "TEMP" )
    then
        // 动作部分:执行报警逻辑
        alarmService.raiseAlarm(
            new AlarmRecord($s.getSensorId(), "HIGH_TEMP", $s.getTemperature())
        );
        System.out.println("报警触发:传感器 " + $s.getSensorId() + " 温度过高");
end
关键字说明:
  • package:规则包路径,用于命名空间隔离
  • import:导入 Java 类,用于事实类型声明
  • global:全局变量,通常用于注入外部服务(如报警服务、数据库连接)
  • rule ... end:规则体,包含属性、when 和 then 三部分
  • salience:优先级,数值越大越先执行
  • no-loop:防止规则修改事实后再次触发自身

4.1.2 条件(when)部分详解

when 部分使用 模式匹配 语法,对传入的事实对象进行条件筛选。支持多条件组合、绑定变量、逻辑运算符。

语法模式 示例 说明
简单属性约束 SensorData( temperature > 85.0 ) 直接比较事实对象的属性值
变量绑定 $s : SensorData( temperature > 85.0 ) 将匹配的事实绑定到变量 $s,在 then 中引用
多条件组合(&&) SensorData( temperature > 85.0 && humidity < 30 ) 同时满足多个条件
多条件组合(||) SensorData( temperature > 85.0 || pressure > 10.0 ) 满足任一条件即可
嵌套属性访问 SensorData( location.floor == 3 ) 访问对象内部的嵌套属性
集合成员判断 SensorData( sensorId memberOf criticalSensorList ) 判断属性是否属于某个集合
not 存在判断 not ( AlarmRecord( severity == "CRITICAL" ) ) 当不存在匹配事实时条件成立
exists 存在判断 exists ( SensorData( status == "OFFLINE" ) ) 当至少存在一个匹配事实时条件成立

4.1.3 动作(then)部分详解

then 部分使用标准 Java 语法,可以调用任何 Java 方法、修改事实、插入新事实、调用全局服务。

rule "TemperatureAndHumidityAlarm"
    when
        $s : SensorData( temperature > 80.0, humidity > 70.0 )
        // 还可以引用其他事实
        $area : Area( areaId == $s.getAreaId(), level == "CLEANROOM" )
    then
        // 1. 创建报警记录
        AlarmRecord alarm = new AlarmRecord();
        alarm.setSensorId($s.getSensorId());
        alarm.setAlarmType("ENV_ABNORMAL");
        alarm.setSeverity("HIGH");
        alarm.setTimestamp(new Date());

        // 2. 插入新事实(触发其他规则)
        insert(alarm);

        // 3. 调用全局服务发送通知
        alarmService.sendNotification(alarm, "紧急:洁净室环境异常");

        // 4. 修改事实属性(需配合 no-loop 使用)
        $s.setAlarmFlag(true);
        update($s);

        // 5. 调用外部联动逻辑
        hvacControlService.activateEmergencyCooling($area.getAreaId());
end
⚠️ 动作执行注意事项:
  • insert():插入新事实会重新触发规则匹配,需配合 no-loop 或 lock-on-active 防止死循环
  • update():更新已有事实同样会触发重新匹配,慎用
  • retract():从工作内存中移除事实
  • modify():批量修改事实属性并通知引擎(Drools 6+ 推荐)

4.2 CLIPS 规则语法基础

CLIPS(C Language Integrated Production System)是经典的规则引擎语言,广泛应用于嵌入式系统和工业控制领域。其语法更接近 Lisp,使用前缀表达式。

4.2.1 规则定义结构

;; CLIPS 规则示例:高温报警
(defrule HIGH-TEMPERATURE-ALARM
    "当传感器温度超过85度时触发报警"
    ;; 条件部分(when)
    (sensor-data (sensor-id ?id) (temperature ?temp&:(> ?temp 85.0)) (type "TEMP"))
    ;; 动作部分(then)
    =>
    (bind ?alarm-id (gensym*))
    (assert (alarm-record (alarm-id ?alarm-id)
                          (sensor-id ?id)
                          (alarm-type "HIGH_TEMP")
                          (severity "HIGH")
                          (timestamp (time))))
    (printout t "报警触发:传感器 " ?id " 温度过高,当前值 " ?temp crlf)
)

4.2.2 事实(Fact)与模板(Template)

CLIPS 使用 模板(deftemplate) 定义事实结构,类似于 Java 中的类定义。

(deftemplate sensor-data
    "传感器数据事实模板"
    (slot sensor-id (type STRING))
    (slot temperature (type FLOAT))
    (slot humidity (type FLOAT))
    (slot type (type STRING))
    (slot status (type SYMBOL) (default OK))
)

(deftemplate alarm-record
    "报警记录事实模板"
    (slot alarm-id (type STRING))
    (slot sensor-id (type STRING))
    (slot alarm-type (type STRING))
    (slot severity (type SYMBOL))
    (slot timestamp (type INTEGER))
)

4.2.3 条件模式与变量绑定

CLIPS 语法 示例 说明
单字段变量 ?id 绑定单个字段值
多字段变量 $?list 绑定剩余所有字段(类似可变参数)
谓词约束 ?temp&:(> ?temp 85.0) 使用 &: 连接任意 Lisp 谓词表达式
逻辑与(and) (and (条件1) (条件2)) 所有条件同时满足
逻辑或(or) (or (条件1) (条件2)) 任一条件满足即可
逻辑非(not) (not (alarm-record (severity CRITICAL))) 当不存在匹配事实时成立
测试条件(test) (test (> ?temp1 ?temp2)) 比较多个绑定变量的值

4.2.4 动作部分常用函数

(defrule MULTI-ACTION-EXAMPLE
    (sensor-data (sensor-id ?id) (temperature ?temp&:(> ?temp 80.0))
                 (humidity ?hum&:(> ?hum 70.0)))
    (area (area-id ?aid) (level CLEANROOM))
    =>
    ;; 1. 断言新事实(assert)
    (assert (alarm-record (alarm-id (gensym*))
                          (sensor-id ?id)
                          (alarm-type "ENV_ABNORMAL")
                          (severity HIGH)))

    ;; 2. 修改事实(modify)
    (modify ?fact-index (status ALARMED))

    ;; 3. 撤销事实(retract)
    (retract ?old-alarm)

    ;; 4. 打印输出
    (printout t "洁净室环境异常,传感器 " ?id " 温度=" ?temp " 湿度=" ?hum crlf)

    ;; 5. 调用外部函数(需提前注册)
    (send-alert ?id "ENV_ABNORMAL")
)
Drools vs CLIPS 核心差异:
对比维度 Drools CLIPS
语法风格 类 Java 语法,易读性强 Lisp 前缀表达式,学习曲线陡
事实模型 Java POJO 对象 deftemplate 模板定义
集成方式 原生 Java 集成,Spring 支持好 C API / Java JNI 桥接
适用场景 企业级 FMS、IT 系统 嵌入式、实时控制系统
规则文件后缀 .drl .clp
Drools 与 CLIPS 规则结构对比 Drools 规则结构 rule "RuleName" salience 10 no-loop true when // 条件部分 $s : SensorData( temp > 85 ) then // 动作部分 alarmService.raiseAlarm(...) end CLIPS 规则结构 (defrule RULE-NAME "规则注释说明" ;; 条件部分 (sensor-data (temp ?t&:(> ?t 85))) => ;; 动作部分 (assert (alarm-record ...)) ) 🔑 结构对应关系: rule ... end ↔ defrule ... ) | when ↔ 条件模式 | then ↔ => | 属性(salience) ↔ 规则属性声明

4.3 厂务监控场景规则示例

4.3.1 温度超限联动排风机(Drools 版本)

package com.fms.alarm.hvac

import com.fms.model.SensorData
import com.fms.model.EquipmentStatus
import com.fms.service.HvacControlService

global HvacControlService hvacService

rule "TempHigh_ActivateExhaustFan"
    salience 20
    no-loop true
    when
        // 温度超过 40°C 且持续超过 5 分钟(通过时间戳判断)
        $s : SensorData( temperature > 40.0, sensorType == "TEMP" )
        // 排风机当前未运行
        $fan : EquipmentStatus( equipmentId == "EXHAUST_FAN_01", running == false )
        // 5 分钟内没有触发过相同报警(防止重复)
        not ( AlarmRecord( sensorId == $s.sensorId, alarmType == "HIGH_TEMP",
                           timestamp > ($s.getTimestamp() - 300000) ) )
    then
        // 启动排风机
        hvacService.startEquipment("EXHAUST_FAN_01");

        // 记录报警
        AlarmRecord alarm = new AlarmRecord($s.getSensorId(), "HIGH_TEMP",
                                            "温度超限,启动排风机", "WARNING");
        insert(alarm);

        // 更新设备状态
        modify($fan) { setRunning(true); }
end

4.3.2 多传感器交叉验证报警(CLIPS 版本)

(defrule CROSS-VALIDATION-ALARM
    "当两个独立温度传感器同时报高温时触发确认报警"
    ;; 条件:两个不同传感器同时报高温
    (sensor-data (sensor-id ?id1) (temperature ?t1&:(> ?t1 85.0))
                 (type "TEMP") (location ?loc))
    (sensor-data (sensor-id ?id2&:(neq ?id2 ?id1))
                 (temperature ?t2&:(> ?t2 85.0))
                 (type "TEMP") (location ?loc))
    ;; 确保两个传感器在同一区域
    (test (eq ?loc ?loc))
    ;; 5分钟内没有触发过确认报警
    (not (confirmed-alarm (location ?loc) (alarm-type "CONFIRMED_HIGH_TEMP")
                          (timestamp ?ts&:(> (- (time) ?ts) 300))))
    =>
    ;; 动作:生成确认报警
    (assert (confirmed-alarm (alarm-id (gensym*))
                             (location ?loc)
                             (alarm-type "CONFIRMED_HIGH_TEMP")
                             (severity CRITICAL)
                             (timestamp (time))))
    (printout t "确认报警:区域 " ?loc " 温度异常,传感器 " ?id1 "=" ?t1
              " 传感器 " ?id2 "=" ?t2 crlf)
    ;; 调用外部联动
    (send-command "ACTIVATE_SPRINKLER" ?loc)
)

4.4 规则编写最佳实践

规则编写核心原则:
  1. 单一职责:每条规则只处理一个明确的报警场景,避免“万能规则”
  2. 条件精确:使用具体阈值、时间窗口、状态组合,减少误报
  3. 动作可控:动作中避免复杂循环或长时间阻塞操作,使用异步调用
  4. 防重复触发:利用 no-loop、时间窗口、状态标记防止规则无限循环
  5. 优先级明确:使用 salience 控制规则执行顺序,紧急报警优先
  6. 可测试性:规则应可独立测试,输入固定事实应得到预期动作
场景 推荐做法 避免做法
阈值判断 使用 >= 或 > 明确边界 使用模糊比较或硬编码魔法数字
时间窗口 在条件中比较时间戳差值 在动作中 sleep 或延迟
多条件组合 使用 && 或 and 明确组合 在动作中 if-else 判断
事实修改 使用 modify 或 update 并配合 no-loop 直接修改对象属性而不通知引擎
外部调用 通过 global 或注入服务异步调用 在规则中直接 new 对象或发起网络请求
⚠️ 常见陷阱:
  • 规则无限循环:动作中 insert/update 了与条件匹配的事实,且未设置 no-loop
  • 事实泄漏:未及时 retract 过期事实,导致工作内存膨胀
  • 优先级冲突:多条规则 salience 相同且条件重叠,执行顺序不确定
  • 全局变量滥用:global 变量在多线程环境下存在并发问题