简单规则引擎
背景
在一些项目中我们需要通过用户自定义配置一些阈值逻辑进行判断是否触发阈值,例如大部分IOT项目中需要设置自动触发条件(例如:每天22:00后 且 窗帘关闭的情况下关灯)。
当然有一些成熟如规则引擎如:Drools和LiteFlow,但是这里写一款简单的java实现方案作为记录。
前端示例
以下示例实现了在贫困帮扶人员中的逻辑 (人员年龄大于80或者小于10岁,并且个人年收入小于1万或者家庭年收入少于3万的人员)
前端代码
前端使用的是elementui,这里可以根据自己的需求封装成组件。
<template>
<div class="logic-builder">
<el-card shadow="hover">
<div slot="header" >
<slot name="title"></slot>
</div>
<div style="height: 500px;overflow-y: auto" >
<div class="logic-group" v-for="(group, groupIndex) in logicGroups" :key="groupIndex">
<div class="group-header">
<span>
条件组 {{ groupIndex + 1 }}
<el-select v-model="group.logic" size="small" style="width: 100px">
<el-option label="并且" value="AND"></el-option>
<el-option label="或者" value="OR"></el-option>
</el-select>
</span>
<el-button
size="mini"
type="danger"
icon="el-icon-delete"
circle
@click="removeGroup(groupIndex)"
v-if="logicGroups.length > 1"
></el-button>
</div>
<div class="condition-item" v-for="(condition, condIndex) in group.conditions" :key="condIndex">
<el-input
v-model="condition.field"
placeholder="属性名"
size="small"
style="width: 120px"
></el-input>
<el-select v-model="condition.operator" size="small" style="width: 100px">
<el-option label="大于" value=">"></el-option>
<el-option label="小于" value="<"></el-option>
<el-option label="等于" value="=="></el-option>
<el-option label="不等于" value="!="></el-option>
<el-option label="大于等于" value=">="></el-option>
<el-option label="小于等于" value="<="></el-option>
</el-select>
<el-input-number
v-model="condition.value"
size="small"
controls-position="right"
style="width: 120px"
></el-input-number>
<el-button
size="mini"
type="danger"
icon="el-icon-delete"
circle
@click="removeCondition(groupIndex, condIndex)"
v-if="group.conditions.length > 1"
></el-button>
</div>
<el-button
size="mini"
type="primary"
plain
icon="el-icon-plus"
@click="addCondition(groupIndex)"
>添加条件</el-button>
<div class="group-relation" v-if="groupIndex < logicGroups.length - 1">
<el-select v-model="group.relationToNext" size="small" style="width: 100px">
<el-option label="并且" value="AND"></el-option>
<el-option label="或者" value="OR"></el-option>
</el-select>
</div>
</div>
</div>
<div class="actions">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="addGroup('AND')">添加【并且】组</el-button>
<el-button size="mini" type="warning" icon="el-icon-plus" @click="addGroup('OR')">添加【或者】组</el-button>
<el-button size="mini" type="success" icon="el-icon-check" @click="saveLogic">保存规则</el-button>
</div>
</el-card>
<el-card shadow="hover" class="preview-card">
<div slot="header">
<span>规则预览</span>
</div>
<pre>{{ logicPreview }}</pre>
</el-card>
</div>
</template>
<script>
export default {
name: "LogicSelect",
props: {
fields: {
type: Array,
default: ()=>{ return [];}
}
},
data() {
return {
logicGroups: [
{
logic: 'AND',
conditions: [
{
field: '',
operator: '>',
value: 100
}
],
relationToNext: 'AND'
}
]
}
},
computed: {
logicPreview() {
let previewData = this.logicGroups.map(group => {
const conditions = group.conditions.map(cond => {
return `${cond.field} ${cond.operator} ${cond.value}`
}).join(group.logic === "AND"?" && ":" || ");
return `(${conditions}) ${group.relationToNext} `;
}).join("").replaceAll("AND", "&&").replaceAll("OR", "||");
return previewData.substring(0, previewData.length - 4)
}
},
methods: {
addGroup(logic) {
this.logicGroups.push({
logic,
conditions: [
{
field: '',
operator: '>',
value: 0
}
],
relationToNext: 'AND'
});
},
removeGroup(index) {
this.logicGroups.splice(index, 1);
},
addCondition(groupIndex) {
this.logicGroups[groupIndex].conditions.push({
field: '',
operator: '>',
value: 0
});
},
removeCondition(groupIndex, condIndex) {
this.logicGroups[groupIndex].conditions.splice(condIndex, 1);
},
async saveLogic() {
try {
// 验证逻辑
const isValid = this.validateLogic();
if (!isValid) {
this.$message.error(' 请填写完整的逻辑规则');
return;
}
console.log(btoa(JSON.stringify(this.logicGroups))); return;
// 发送到后端
const response = await this.$http.post('/api/logic-rules', {
logicGroups: this.logicGroups
});
this.$message.success(' 规则保存成功');
console.log(' 保存的规则:', response.data);
} catch (error) {
console.error(' 保存规则失败:', error);
this.$message.error(' 保存失败,请重试');
}
},
validateLogic() {
for (const group of this.logicGroups) {
for (const cond of group.conditions) {
if (!cond.field || cond.value === null || cond.value === undefined) {
return false;
}
}
}
return true;
}
}
}
</script>
<style scoped>
.logic-builder {
max-width: 800px;
margin: 20px auto;
}
.logic-group {
padding: 15px;
margin-bottom: 15px;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #f5f7fa;
}
.group-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
font-weight: bold;
}
.condition-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.condition-item > * {
margin-right: 10px;
}
.group-relation {
margin: 15px 0;
text-align: center;
}
.actions {
margin-top: 20px;
text-align: center;
}
.preview-card {
margin-top: 20px;
}
.preview-card pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
.logic-builder>>>.el-card__header{
padding: 5px 20px;
}
</style>
后端示例(实体Bean)
import java.util.List;
// LogicRule.java
public class LogicRule {
private Long id;
private List<Condition> conditions;
private String relationToNext; // "AND" 或 "OR"
private String logic;
// 嵌套Condition类
public static class Condition {
private String field;
private String operator;
private Double value;
// 无参构造
public Condition() {}
// 全参构造
public Condition(String field, String operator, Double value) {
this.field = field;
this.operator = operator;
this.value = value;
}
// getter和setter
public String getField() { return field; }
public void setField(String field) { this.field = field; }
public String getOperator() { return operator; }
public void setOperator(String operator) { this.operator = operator; }
public Double getValue() { return value; }
public void setValue(Double value) { this.value = value; }
}
// getter和setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public List<Condition> getConditions() { return conditions; }
public void setConditions(List<Condition> conditions) { this.conditions = conditions; }
public String getRelationToNext() {
return relationToNext;
}
public void setRelationToNext(String relationToNext) {
this.relationToNext = relationToNext;
}
public String getLogic() {return logic;}
public void setLogic(String logic) {this.logic = logic;}
}
后端示例(Service)
这里简单的将规则解析成json格式存储。
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.warning.domain.LogicRule;
import com.ruoyi.warning.service.LogicRuleService;
import org.springframework.stereotype.Service;
import java.util.Iterator;
import java.util.Map;
@Service
public class LogicRuleServiceImpl {
/**
*
* @param warningThreshold 规则引擎JSON字符串
* @param message 当前对象信息
* @return
*/
@Override
public boolean isWarning(String warningThreshold, Map<String, Object> message){
JSONArray jsonArray = JSON.parseArray(warningThreshold);
Iterator<Object> groupIterator = jsonArray.iterator();
LogicRule logicRule = JSON.parseObject(groupIterator.next().toString(), LogicRule.class);
boolean result = evaluate(logicRule, message);
String relation = logicRule.getRelationToNext();
while (groupIterator.hasNext()) {
LogicRule rule = JSON.parseObject(groupIterator.next().toString(), LogicRule.class);
if ("AND".equals(relation)){
result = result && evaluate(rule, message);
} else if ("OR".equals(relation)){
result = result || evaluate(rule, message);
} else {
throw new IllegalArgumentException("规则不存在");
}
relation = rule.getRelationToNext();
}
return result;
}
// 评估逻辑规则
public boolean evaluate(LogicRule rule, Map<String, Object> params) {
if (rule == null) {
throw new IllegalArgumentException("规则不存在");
}
boolean result = "AND".equals(rule.getLogic());
for (LogicRule.Condition condition : rule.getConditions()) {
if (condition.getField() == null) {
throw new IllegalArgumentException("缺少参数: " + condition.getField());
}
Double value = Double.parseDouble(params.get(condition.getField()).toString());
boolean currentResult = evaluateCondition(value, condition);
if ("AND".equals(rule.getLogic())) {
result = result && currentResult;
if (!result) break; // AND短路优化
} else {
result = result || currentResult;
if (result) break; // OR短路优化
}
}
return result;
}
private boolean evaluateCondition(Double value, LogicRule.Condition condition) {
switch (condition.getOperator()) {
case ">": return value > condition.getValue();
case "<": return value < condition.getValue();
case "==": return Math.abs(value - condition.getValue()) < 0.000001;
case "!=": return Math.abs(value - condition.getValue()) >= 0.000001;
case ">=": return value >= condition.getValue();
case "<=": return value <= condition.getValue();
default: throw new IllegalArgumentException("不支持的运算符: " + condition.getOperator());
}
}
}
使用方案
数据存储,采用JSON格式存储。
前后端交互,因为涉及到特殊字符,推荐前端使用Base64进行编码后传入到后端,后端再进行解码实现对应的业务逻辑。
后端调用直接调用isWarning方法判断是否满足规则。
License:
CC BY 4.0