【代码新增】IoT:增加 IotRuleSceneMessageHandler 处理规则场景,尝试基于 Spring El 表达式实现初步计算(部分场景) trigger 条件匹配
This commit is contained in:
parent
06749a18fc
commit
a4be3bb84d
|
@ -0,0 +1,51 @@
|
|||
package cn.iocoder.yudao.module.iot.enums.rule;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Iot 场景触发条件参数的操作符枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotRuleSceneTriggerConditionParameterOperatorEnum implements ArrayValuable<String> {
|
||||
|
||||
EQUALS("=", "%s == %s"),
|
||||
NOT_EQUALS("!=", "%s != %s"),
|
||||
|
||||
GREATER_THAN(">", "%s > %s"),
|
||||
GREATER_THAN_OR_EQUALS(">=", "%s >= %s"),
|
||||
|
||||
LESS_THAN("<", "%s < %s"),
|
||||
LESS_THAN_OR_EQUALS("<=", "%s <= %s"),
|
||||
|
||||
IN("in", "%s in { %s }"),
|
||||
NOT_IN("not in", "%s not in { %s }"),
|
||||
|
||||
BETWEEN("between", "(%s >= %s) && (%s <= %s)"),
|
||||
NOT_BETWEEN("not between", "!(%s between %s and %s)"),
|
||||
|
||||
LIKE("like", "%s like %s"), // 字符串匹配
|
||||
NOT_NULL("not null", ""); // 非空
|
||||
|
||||
private final String operator;
|
||||
private final String springExpression;
|
||||
|
||||
public static final String[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerConditionParameterOperatorEnum::getOperator).toArray(String[]::new);
|
||||
|
||||
public static IotRuleSceneTriggerConditionParameterOperatorEnum operatorOf(String operator) {
|
||||
return ArrayUtil.firstMatch(item -> item.getOperator().equals(operator), values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.yudao.module.iot.enums.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Iot 场景流转的触发类型枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
DEVICE(1), // 设备触发
|
||||
TIMER(2); // 定时触发
|
||||
|
||||
private final Integer type;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
|||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerConditionParameterOperatorEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
|
@ -17,7 +18,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 场景联动 DO
|
||||
* IoT 规则场景(场景联动) DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -72,7 +73,7 @@ public class IotRuleSceneDO extends BaseDO {
|
|||
/**
|
||||
* 触发类型
|
||||
*
|
||||
* TODO @芋艿:device、job
|
||||
* 枚举 {@link cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
|
@ -147,12 +148,15 @@ public class IotRuleSceneDO extends BaseDO {
|
|||
/**
|
||||
* 操作符
|
||||
*
|
||||
* TODO 芋艿:枚举
|
||||
* 枚举 {@link IotRuleSceneTriggerConditionParameterOperatorEnum}
|
||||
*/
|
||||
private String operator;
|
||||
|
||||
/**
|
||||
* 值
|
||||
* 比较值
|
||||
*
|
||||
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
|
||||
* 例如说,{@link IotRuleSceneTriggerConditionParameterOperatorEnum#IN}、{@link IotRuleSceneTriggerConditionParameterOperatorEnum#BETWEEN}
|
||||
*/
|
||||
private String value;
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package cn.iocoder.yudao.module.iot.dal.mysql.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface IotRuleSceneMapper extends BaseMapperX<IotRuleSceneDO> {
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.yudao.module.iot.mq.consumer.rule;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.IotRuleSceneService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 针对 {@link IotDeviceMessage} 的消费者,处理规则场景
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotRuleSceneMessageHandler {
|
||||
|
||||
@Resource
|
||||
private IotRuleSceneService ruleSceneService;
|
||||
|
||||
@EventListener
|
||||
@Async
|
||||
public void onMessage(IotDeviceMessage message) {
|
||||
log.info("[onMessage][消息内容({})]", message);
|
||||
ruleSceneService.executeRuleScene(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package cn.iocoder.yudao.module.iot.service.rule;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
|
||||
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IoT 规则场景 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotRuleSceneService {
|
||||
|
||||
/**
|
||||
* 【缓存】获得指定设备的场景列表
|
||||
*
|
||||
* @param productKey 产品 Key
|
||||
* @param deviceName 设备名称
|
||||
* @return 场景列表
|
||||
*/
|
||||
List<IotRuleSceneDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName);
|
||||
|
||||
/**
|
||||
* 执行规则场景
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
void executeRuleScene(IotDeviceMessage message);
|
||||
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package cn.iocoder.yudao.module.iot.service.rule;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.text.CharPool;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerConditionParameterOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
|
||||
/**
|
||||
* IoT 规则场景 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotRuleSceneServiceImpl implements IotRuleSceneService {
|
||||
|
||||
@Resource
|
||||
private IotRuleSceneMapper ruleSceneMapper;
|
||||
|
||||
// TODO 芋艿,缓存待实现
|
||||
@Override
|
||||
@TenantIgnore // 忽略租户隔离:因为 IotRuleSceneMessageHandler 调用时,一般未传递租户,所以需要忽略
|
||||
public List<IotRuleSceneDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
|
||||
if (true) {
|
||||
IotRuleSceneDO ruleScene01 = new IotRuleSceneDO();
|
||||
ruleScene01.setTriggers(CollUtil.newArrayList());
|
||||
IotRuleSceneDO.Trigger trigger01 = new IotRuleSceneDO.Trigger();
|
||||
trigger01.setType(IotRuleSceneTriggerTypeEnum.DEVICE.getType());
|
||||
trigger01.setConditions(CollUtil.newArrayList());
|
||||
IotRuleSceneDO.TriggerCondition condition01 = new IotRuleSceneDO.TriggerCondition();
|
||||
condition01.setType(IotDeviceMessageTypeEnum.PROPERTY.getType());
|
||||
condition01.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_REPORT.getIdentifier());
|
||||
condition01.setParameters(CollUtil.newArrayList());
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter011 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter011.setIdentifier("width");
|
||||
parameter011.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.getOperator());
|
||||
parameter011.setValue("1");
|
||||
condition01.getParameters().add(parameter011);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter012 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter012.setIdentifier("width");
|
||||
parameter012.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.getOperator());
|
||||
parameter012.setValue("2");
|
||||
condition01.getParameters().add(parameter012);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter013 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter013.setIdentifier("width");
|
||||
parameter013.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN.getOperator());
|
||||
parameter013.setValue("0");
|
||||
condition01.getParameters().add(parameter013);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter014 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter014.setIdentifier("width");
|
||||
parameter014.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator());
|
||||
parameter014.setValue("0");
|
||||
condition01.getParameters().add(parameter014);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter015 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter015.setIdentifier("width");
|
||||
parameter015.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN.getOperator());
|
||||
parameter015.setValue("2");
|
||||
condition01.getParameters().add(parameter015);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter016 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter016.setIdentifier("width");
|
||||
parameter016.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS.getOperator());
|
||||
parameter016.setValue("2");
|
||||
condition01.getParameters().add(parameter016);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter017 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter017.setIdentifier("width");
|
||||
parameter017.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.IN.getOperator());
|
||||
parameter017.setValue("1,2,3");
|
||||
condition01.getParameters().add(parameter017);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter018 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter018.setIdentifier("width");
|
||||
parameter018.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN.getOperator());
|
||||
parameter018.setValue("0,2,3");
|
||||
condition01.getParameters().add(parameter018);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter019 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter019.setIdentifier("width");
|
||||
parameter019.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.getOperator());
|
||||
parameter019.setValue("1,3");
|
||||
condition01.getParameters().add(parameter019);
|
||||
IotRuleSceneDO.TriggerConditionParameter parameter020 = new IotRuleSceneDO.TriggerConditionParameter();
|
||||
parameter020.setIdentifier("width");
|
||||
parameter020.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN.getOperator());
|
||||
parameter020.setValue("2,3");
|
||||
condition01.getParameters().add(parameter020);
|
||||
trigger01.getConditions().add(condition01);
|
||||
ruleScene01.getTriggers().add(trigger01);
|
||||
|
||||
return ListUtil.toList(ruleScene01);
|
||||
}
|
||||
|
||||
List<IotRuleSceneDO> list = ruleSceneMapper.selectList();
|
||||
// TODO @芋艿:需要考虑开启状态
|
||||
return filterList(list, ruleScene -> {
|
||||
for (IotRuleSceneDO.Trigger trigger : ruleScene.getTriggers()) {
|
||||
if (ObjUtil.notEqual(trigger.getProductKey(), productKey)) {
|
||||
continue;
|
||||
}
|
||||
if (CollUtil.isEmpty(trigger.getDeviceNames()) // 无设备名称限制
|
||||
|| trigger.getDeviceNames().contains(deviceName)) { // 包含设备名称
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeRuleScene(IotDeviceMessage message) {
|
||||
// 1. 获得设备匹配的规则场景
|
||||
List<IotRuleSceneDO> ruleScenes = getMatchedRuleSceneList(message);
|
||||
if (CollUtil.isEmpty(ruleScenes)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得匹配的规则场景列表
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @return 规则场景列表
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<IotRuleSceneDO> getMatchedRuleSceneList(IotDeviceMessage message) {
|
||||
// 1. 匹配设备
|
||||
// TODO @芋艿:可能需要 getSelf(); 缓存
|
||||
List<IotRuleSceneDO> ruleScenes = getRuleSceneListByProductKeyAndDeviceNameFromCache(
|
||||
message.getProductKey(), message.getDeviceName());
|
||||
if (CollUtil.isEmpty(ruleScenes)) {
|
||||
return ruleScenes;
|
||||
}
|
||||
|
||||
// 2. 匹配 trigger 触发器的条件
|
||||
return filterList(ruleScenes, ruleScene -> {
|
||||
for (IotRuleSceneDO.Trigger trigger : ruleScene.getTriggers()) {
|
||||
// 非设备触发,不匹配
|
||||
if (ObjUtil.notEqual(trigger.getType(), IotRuleSceneTriggerTypeEnum.DEVICE.getType())) {
|
||||
return false;
|
||||
}
|
||||
// TODO 芋艿:产品、设备的匹配,要不要这里在做一次???貌似和 1. 部分重复了
|
||||
// 条件为空,说明没有匹配的条件,因此不匹配
|
||||
if (CollUtil.isEmpty(trigger.getConditions())) {
|
||||
return false;
|
||||
}
|
||||
IotRuleSceneDO.TriggerCondition found = CollUtil.findOne(trigger.getConditions(), condition -> {
|
||||
if (ObjUtil.notEqual(message.getType(), condition.getType())
|
||||
|| ObjUtil.notEqual(message.getIdentifier(), condition.getIdentifier())) {
|
||||
return false;
|
||||
}
|
||||
// TODO @芋艿:设备上线,需要测试下。
|
||||
for (IotRuleSceneDO.TriggerConditionParameter parameter : condition.getParameters()) {
|
||||
// 计算是否匹配
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum operator =
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum.operatorOf(parameter.getOperator());
|
||||
if (operator == null) {
|
||||
log.error("[getMatchedRuleSceneList][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]",
|
||||
ruleScene.getId(), trigger, parameter.getOperator());
|
||||
return false;
|
||||
}
|
||||
Object messageValue = ((Map<String, Object>) message.getData()).get(parameter.getIdentifier());
|
||||
if (messageValue == null) {
|
||||
return false;
|
||||
}
|
||||
String springExpression;
|
||||
if (ObjectUtils.equalsAny(operator, IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN,
|
||||
IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN)) {
|
||||
String[] parameterValues = StrUtil.splitToArray(parameter.getValue(), CharPool.COMMA);
|
||||
springExpression = String.format(operator.getSpringExpression(), messageValue, parameterValues[0],
|
||||
messageValue, parameterValues[1]);
|
||||
} else {
|
||||
springExpression = String.format(operator.getSpringExpression(), messageValue, parameter.getValue());
|
||||
}
|
||||
// TODO @芋艿:【需优化】需要考虑 struct、时间等参数的比较
|
||||
try {
|
||||
System.out.println(SpringExpressionUtils.parseExpression(springExpression));
|
||||
} catch (Exception e) {
|
||||
log.error("[getMatchedRuleSceneList][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}) 计算异常]",
|
||||
message, ruleScene.getId(), trigger, springExpression, e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (found == null) {
|
||||
return false;
|
||||
}
|
||||
log.info("[getMatchedRuleSceneList][消息({}) 匹配到规则场景编号({}) 的触发器({})]", message, ruleScene.getId(), trigger);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue