4、规则语言入门:Drools/CLIPS规则语法基础、条件(when)与动作(then)结构
在FMS报警规则引擎中,规则语言是定义“何时触发报警”与“触发后执行什么联动”的核心工具。本章聚焦于业界最成熟的两种规则语言——Drools(Java生态)与CLIPS(C语言经典实现),深入解析其语法基础、条件(when)与动作(then)结构,并结合厂务监控场景给出可直接参考的规则模板。
学习目标:
- 掌握Drools规则文件(.drl)的基本结构与关键字
- 理解CLIPS规则的定义方式与事实匹配机制
- 熟练编写条件(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 |
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 规则编写最佳实践
规则编写核心原则:
- 单一职责:每条规则只处理一个明确的报警场景,避免“万能规则”
- 条件精确:使用具体阈值、时间窗口、状态组合,减少误报
- 动作可控:动作中避免复杂循环或长时间阻塞操作,使用异步调用
- 防重复触发:利用
no-loop、时间窗口、状态标记防止规则无限循环 - 优先级明确:使用
salience控制规则执行顺序,紧急报警优先 - 可测试性:规则应可独立测试,输入固定事实应得到预期动作
| 场景 | 推荐做法 | 避免做法 |
|---|---|---|
| 阈值判断 | 使用 >= 或 > 明确边界 |
使用模糊比较或硬编码魔法数字 |
| 时间窗口 | 在条件中比较时间戳差值 | 在动作中 sleep 或延迟 |
| 多条件组合 | 使用 && 或 and 明确组合 |
在动作中 if-else 判断 |
| 事实修改 | 使用 modify 或 update 并配合 no-loop |
直接修改对象属性而不通知引擎 |
| 外部调用 | 通过 global 或注入服务异步调用 |
在规则中直接 new 对象或发起网络请求 |
⚠️ 常见陷阱:
- 规则无限循环:动作中 insert/update 了与条件匹配的事实,且未设置 no-loop
- 事实泄漏:未及时 retract 过期事实,导致工作内存膨胀
- 优先级冲突:多条规则 salience 相同且条件重叠,执行顺序不确定
- 全局变量滥用:global 变量在多线程环境下存在并发问题