开源版新增规则脚本

This commit is contained in:
kerwincui
2024-04-23 16:34:20 +08:00
parent de7e2529a3
commit 054e414b48
50 changed files with 7233 additions and 13 deletions

View File

@@ -4,8 +4,11 @@ import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.DeviceReportBo;
import com.fastbee.common.enums.ServerType;
import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.gateway.mq.TopicsPost;
import com.fastbee.common.utils.gateway.mq.TopicsUtils;
import com.fastbee.iot.ruleEngine.MsgContext;
import com.fastbee.iot.ruleEngine.RuleProcess;
import com.fastbee.mq.redischannel.producer.MessageProducer;
import com.fastbee.mq.service.IDeviceReportMessageService;
import com.fastbee.mq.service.IMessagePublishService;
@@ -17,6 +20,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Objects;
@Component
@@ -28,6 +32,9 @@ public class MqttService {
@Resource
private IDeviceReportMessageService deviceReportMessageService;
@Resource
private RuleProcess ruleProcess;
public void subscribe(MqttAsyncClient client) throws MqttException {
@@ -48,6 +55,18 @@ public class MqttService {
log.info("接收消息主题 : " + topic);
log.info("接收消息Qos : " + mqttMessage.getQos());
log.info("接收消息内容 : " + message);
//这里默认设备编号长度超过9位
String[] split = topic.split("/");
String clientId = Arrays.stream(split).filter(imei -> imei.length() > 9).findFirst().get();
// 规则引擎脚本处理,完成后返回结果
MsgContext context = ruleProcess.processRuleScript(clientId, 1, topic, message);
if (!Objects.isNull(context) && StringUtils.isNotEmpty(context.getPayload())
&& StringUtils.isNotEmpty(context.getTopic())) {
topic = context.getTopic();
message = context.getPayload();
}
String serialNumber = topicsUtils.parseSerialNumber(topic);
Long productId = topicsUtils.parseProductId(topic);
String name = topicsUtils.parseTopicName(topic);

View File

@@ -0,0 +1,553 @@
package com.fastbee.mq.ruleEngine;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.core.mq.InvokeReqDto;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.core.redis.RedisKeyBuilder;
import com.fastbee.common.core.thingsModel.SceneThingsModelItem;
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.spring.SpringUtils;
import com.fastbee.iot.model.ScriptTemplate;
import com.fastbee.iot.model.ThingsModels.ValueItem;
import com.fastbee.iot.service.IDeviceService;
import com.fastbee.iot.service.IDeviceUserService;
import com.fastbee.mq.service.IFunctionInvoke;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.regex.Pattern.compile;
@Data
public class SceneContext {
/**
* 上报信息的设备编号
*/
private String deviceNum;
/**
* 上报信息的设备所属产品ID
*/
private Long productId;
/**
* 上报信息的设备信息类型 1=属性, 2=功能3=事件4=设备升级5=设备上线6=设备下线
*/
private int type;
/**
* 上报的物模型集合
*/
private List<ThingsModelSimpleItem> thingsModelSimpleItems;
/**
* 触发成功的物模型集合,保留给告警记录
*/
private List<SceneThingsModelItem> sceneThingsModelItems;
private static IFunctionInvoke functionInvoke = SpringUtils.getBean(IFunctionInvoke.class);
private static IDeviceService deviceService = SpringUtils.getBean(IDeviceService.class);
// private static IAlertService alertService = SpringUtils.getBean(IAlertService.class);
// private static NotifySendService notifySendService = SpringUtils.getBean(NotifySendService.class);
private static RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
// private static AlertLogMapper alertLogMapper = SpringUtils.getBean(AlertLogMapper.class);
private static IDeviceUserService deviceUserService = SpringUtils.getBean(IDeviceUserService.class);
public SceneContext(String deviceNum, Long productId, int type, List<ThingsModelSimpleItem> thingsModelSimpleItems) {
this.deviceNum = deviceNum;
this.productId = productId;
this.type = type;
this.thingsModelSimpleItems = thingsModelSimpleItems;
}
/**
* 处理规则脚本
*
* @return
*/
private boolean process(String json) throws InterruptedException {
System.out.println("------------------[规则引擎执行...]---------------------");
ScriptTemplate scriptTemplate = JSON.parseObject(json, ScriptTemplate.class);
if (scriptTemplate.getPurpose() == 2) {
// 触发器,检查静默时间
if (!checkSilent(scriptTemplate.getSilent(), scriptTemplate.getSceneId())) {
// 触发条件为不满足时返回true
if (scriptTemplate.getCond() == 3) {
return true;
}
return false;
}
// 触发器
if (scriptTemplate.getSource() == 1) {
// 设备触发
return deviceTrigger(scriptTemplate);
} else if (scriptTemplate.getSource() == 3) {
// 产品触发
return productTrigger(scriptTemplate);
}
} /*else if (scriptTemplate.getPurpose() == 3) {
// 执行动作,延迟执行,线程休眠 delay x 1000毫秒
Thread.sleep(scriptTemplate.getDelay() * 1000);
if (scriptTemplate.getSource() == 4) {
// 告警
this.alert(scriptTemplate.getDelay(), scriptTemplate.getSceneId());
} else if (scriptTemplate.getSource() == 1 || scriptTemplate.getSource() == 3) {
// 下发指令
this.send(scriptTemplate);
}
// 更新静默时间
this.updateSilent(scriptTemplate.getSilent(), scriptTemplate.getSceneId());
}*/
return false;
}
/***
* 设备触发脚本处理
* @param scriptTemplate 解析后的Json脚本数据
* @return
*/
private boolean deviceTrigger(ScriptTemplate scriptTemplate) {
// 判断定制触发(执行一次)或设备上报
boolean isDeviceReport = StringUtils.isEmpty(deviceNum) ? false : true;
if (isDeviceReport) {
// 1. 匹配设备编号
boolean matchDeviceNum = Arrays.asList(scriptTemplate.getDeviceNums().split(",")).contains(deviceNum);
if (scriptTemplate.getType() < 4) {
// 2.匹配物模型标识符
ThingsModelSimpleItem matchItem = null;
if (thingsModelSimpleItems != null) {
for (ThingsModelSimpleItem item : thingsModelSimpleItems) {
if (item.getId().equals(scriptTemplate.getId())) {
matchItem = item;
break;
}
}
}
if (matchDeviceNum && matchItem != null) {
// 记录结果
if (sceneThingsModelItems == null) {
sceneThingsModelItems = new ArrayList<>();
}
SceneThingsModelItem sceneItem = new SceneThingsModelItem(scriptTemplate.getId(), matchItem.getValue(), type,
scriptTemplate.getScriptId(), scriptTemplate.getSceneId(), scriptTemplate.getProductId(), deviceNum);
sceneThingsModelItems.add(sceneItem);
// 3.设备上报值匹配
boolean isMatch = matchValue(scriptTemplate.getOperator(), scriptTemplate.getValue(), matchItem.getValue());
if (isMatch) {
return true;
}
}
} else {
// 上线,下线
if (matchDeviceNum && scriptTemplate.getType() == type) {
// 记录结果
if (sceneThingsModelItems == null) {
sceneThingsModelItems = new ArrayList<>();
}
SceneThingsModelItem sceneItem = new SceneThingsModelItem(type == 5 ? "online" : "offline", type == 5 ? "1" : "0", type,
scriptTemplate.getScriptId(), scriptTemplate.getSceneId(), scriptTemplate.getProductId(), deviceNum);
sceneThingsModelItems.add(sceneItem);
// 记录结果
return true;
}
}
} else {
// 定时触发/执行一次
int resultCount = 0;
// 3.查询设备最新上报值去匹配
for (String num : Arrays.asList(scriptTemplate.getDeviceNums().split(","))) {
// 数组类型key去除前缀值从逗号分隔的字符串获取
String id = "";
String value = "";
int index = 0;
if (scriptTemplate.getId().startsWith("array_")) {
id = scriptTemplate.getId().substring(9);
index = Integer.parseInt(scriptTemplate.getId().substring(6, 8));
} else {
id = scriptTemplate.getId();
}
String key = RedisKeyBuilder.buildTSLVCacheKey(scriptTemplate.getProductId(), num);
String cacheValue = redisCache.getCacheMapValue(key, id);
if (StringUtils.isEmpty(cacheValue)) {
continue;
}
ValueItem valueItem = JSON.parseObject(cacheValue, ValueItem.class);
if (scriptTemplate.getId().startsWith("array_")) {
String[] values = valueItem.getValue().split(",");
value = values[index];
} else {
value = valueItem.getValue();
}
boolean isMatch = matchValue(scriptTemplate.getOperator(), scriptTemplate.getValue(), value);
if (isMatch) {
// 记录结果
if (sceneThingsModelItems == null) {
sceneThingsModelItems = new ArrayList<>();
}
SceneThingsModelItem sceneItem = new SceneThingsModelItem(scriptTemplate.getId(), value, type,
scriptTemplate.getScriptId(), scriptTemplate.getSceneId(), scriptTemplate.getProductId(), num);
sceneThingsModelItems.add(sceneItem);
resultCount++;
}
}
// 任意设备匹配成功返回true
return resultCount > 0 ? true : false;
}
return false;
}
/***
* 产品触发脚本处理
* @param scriptTemplate
* @return
*/
private boolean productTrigger(ScriptTemplate scriptTemplate) {
// 判断定制触发(执行一次)或设备上报
boolean isDeviceReport = StringUtils.isEmpty(deviceNum) ? false : true;
if (isDeviceReport) {
// 匹配产品编号
boolean matchProductId = scriptTemplate.getProductId().equals(productId);
if (scriptTemplate.getType() < 4) {
// 匹配物模型标识符
ThingsModelSimpleItem matchItem = null;
if (thingsModelSimpleItems != null) {
for (ThingsModelSimpleItem item : thingsModelSimpleItems) {
if (item.getId().equals(scriptTemplate.getId())) {
matchItem = item;
break;
}
}
}
if (matchProductId && matchItem != null) {
// 记录结果
if (sceneThingsModelItems == null) {
sceneThingsModelItems = new ArrayList<>();
}
SceneThingsModelItem sceneItem = new SceneThingsModelItem(scriptTemplate.getId(), matchItem.getValue(), type,
scriptTemplate.getScriptId(), scriptTemplate.getSceneId(), scriptTemplate.getProductId(), deviceNum);
sceneThingsModelItems.add(sceneItem);
// 设备上报值匹配
boolean isMatch = matchValue(scriptTemplate.getOperator(), scriptTemplate.getValue(), matchItem.getValue());
if (isMatch) {
return true;
}
}
} else {
// 上线,下线
if (matchProductId && scriptTemplate.getType() == type) {
// 记录结果
if (sceneThingsModelItems == null) {
sceneThingsModelItems = new ArrayList<>();
}
SceneThingsModelItem sceneItem = new SceneThingsModelItem(type == 5 ? "online" : "offline", type == 5 ? "1" : "0", type,
scriptTemplate.getScriptId(), scriptTemplate.getSceneId(), scriptTemplate.getProductId(), deviceNum);
sceneThingsModelItems.add(sceneItem);
// 记录结果
return true;
}
}
} else {
// 定时触发/执行一次
int resultCount = 0;
// 查询设备最新上报值去匹配
String[] deviceNums = deviceService.getDeviceNumsByProductId(scriptTemplate.getProductId());
for (String num : deviceNums) {
// 数组类型key去除前缀值从逗号分隔的字符串获取
String id = "";
String value = "";
int index = 0;
if (scriptTemplate.getId().startsWith("array_")) {
id = scriptTemplate.getId().substring(9);
index = Integer.parseInt(scriptTemplate.getId().substring(6, 8));
} else {
id = scriptTemplate.getId();
}
String key = RedisKeyBuilder.buildTSLVCacheKey(scriptTemplate.getProductId(), num);
String cacheValue = redisCache.getCacheMapValue(key, id);
if (StringUtils.isEmpty(cacheValue)) {
continue;
}
ValueItem valueItem = JSON.parseObject(cacheValue, ValueItem.class);
if (scriptTemplate.getId().startsWith("array_")) {
String[] values = valueItem.getValue().split(",");
value = values[index];
} else {
value = valueItem.getValue();
}
boolean isMatch = matchValue(scriptTemplate.getOperator(), scriptTemplate.getValue(), value);
if (isMatch) {
// 记录结果
if (sceneThingsModelItems == null) {
sceneThingsModelItems = new ArrayList<>();
}
SceneThingsModelItem sceneItem = new SceneThingsModelItem(scriptTemplate.getId(), value, type,
scriptTemplate.getScriptId(), scriptTemplate.getSceneId(), scriptTemplate.getProductId(), num);
sceneThingsModelItems.add(sceneItem);
resultCount++;
}
}
// 任意设备匹配成功返回true
return resultCount > 0 ? true : false;
}
return false;
}
/**
* 执行动作,下发指令
*
* @param scriptTemplate
*/
private void send(ScriptTemplate scriptTemplate) {
String[] deviceNumbers = null;
if (scriptTemplate.getSource() == 1) {
// 下发给指定设备
deviceNumbers = scriptTemplate.getDeviceNums().split(",");
} else if (scriptTemplate.getSource() == 3) {
// 下发给产品下所有设备
deviceNumbers = deviceService.getDeviceNumsByProductId(scriptTemplate.getProductId());
}
for (String deviceNum : deviceNumbers) {
InvokeReqDto reqDto = new InvokeReqDto();
reqDto.setProductId(scriptTemplate.getProductId());
reqDto.setSerialNumber(deviceNum);
reqDto.setModelName("");
reqDto.setType(1);
reqDto.setIdentifier(scriptTemplate.getId());
Map<String, Object> params = new HashMap<>();
params.put(scriptTemplate.getId(), scriptTemplate.getValue());
reqDto.setRemoteCommand(params);
reqDto.setValue(new JSONObject(reqDto.getRemoteCommand()));
functionInvoke.invokeNoReply(reqDto);
}
}
/**
* 执行动作,告警处理
*
* @param sceneId 场景ID
* @param delay 延时(单位秒,90秒内
*/
/*private void alert(int delay, Long sceneId) {
for (SceneThingsModelItem sceneThingsModelItem : sceneThingsModelItems) {
// 查询设备信息
Device device = deviceService.selectDeviceBySerialNumber(sceneThingsModelItem.getDeviceNumber());
Optional.ofNullable(device).orElseThrow(() -> new ServiceException("告警推送,设备不存在" + "[{" + sceneThingsModelItem.getDeviceNumber() + "}]"));
// 获取场景相关的告警参数,告警必须要是启动状态
List<AlertSceneSendVO> sceneSendVOList = alertService.listByAlertIds(sceneId);
if (CollectionUtils.isEmpty(sceneSendVOList)) {
return;
}
// 获取告警推送参数
AlertPushParams alertPushParams = new AlertPushParams();
alertPushParams.setDeviceName(device.getDeviceName());
alertPushParams.setSerialNumber(sceneThingsModelItem.getDeviceNumber());
// 获取设备所属及分享用户信息
List<DeviceUser> deviceUserList = deviceUserService.selectDeviceUserByDeviceId(device.getDeviceId());
if (CollectionUtils.isNotEmpty(deviceUserList)) {
alertPushParams.setUserPhoneSet(deviceUserList.stream().map(DeviceUser::getPhonenumber).filter(StringUtils::isNotEmpty).collect(Collectors.toSet()));
alertPushParams.setUserIdSet(deviceUserList.stream().map(DeviceUser::getUserId).collect(Collectors.toSet()));
}
String address;
if (StringUtils.isNotEmpty(device.getNetworkAddress())) {
address = device.getNetworkAddress();
} else if (StringUtils.isNotEmpty(device.getNetworkIp())) {
address = device.getNetworkIp();
} else if (Objects.nonNull(device.getLongitude()) && Objects.nonNull(device.getLatitude())) {
address = device.getLongitude() + "," + device.getLatitude();
} else {
address = "未知地点";
}
alertPushParams.setAddress(address);
alertPushParams.setAlertTime(DateUtils.parseDateToStr(DateUtils.YY_MM_DD_HH_MM_SS, new Date()));
List<AlertLog> alertLogList = new ArrayList<>();
// 获取告警关联模版id
for (AlertSceneSendVO alertSceneSendVO : sceneSendVOList) {
List<AlertNotifyTemplate> alertNotifyTemplateList = alertService.listAlertNotifyTemplate(alertSceneSendVO.getAlertId());
alertPushParams.setAlertName(alertSceneSendVO.getAlertName());
for (AlertNotifyTemplate alertNotifyTemplate : alertNotifyTemplateList) {
alertPushParams.setNotifyTemplateId(alertNotifyTemplate.getNotifyTemplateId());
notifySendService.alertSend(alertPushParams);
}
AlertLog alertLog = getAlertLog(alertSceneSendVO, device, sceneThingsModelItem);
alertLogList.add(alertLog);
}
// 保存告警日志
if (CollectionUtils.isNotEmpty(alertLogList)) {
alertLogMapper.insertAlertLogBatch(alertLogList);
}
}
}
*//**
* 组装告警日志
*
* @param alertSceneSendVO
* @return com.fastbee.iot.domain.AlertLog
* @param: device
*//*
private AlertLog getAlertLog(AlertSceneSendVO alertSceneSendVO, Device device, SceneThingsModelItem sceneThingsModelItem) {
AlertLog alertLog = new AlertLog();
alertLog.setAlertName(alertSceneSendVO.getAlertName());
alertLog.setAlertLevel(alertSceneSendVO.getAlertLevel());
alertLog.setSerialNumber(sceneThingsModelItem.getDeviceNumber());
alertLog.setProductId(sceneThingsModelItem.getProductId());
alertLog.setDeviceName(device.getDeviceName());
// 统一未处理
alertLog.setStatus(2);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", sceneThingsModelItem.getId());
jsonObject.put("value", sceneThingsModelItem.getValue());
jsonObject.put("remark", "");
alertLog.setDetail(jsonObject.toJSONString());
alertLog.setCreateTime(new Date());
return alertLog;
}*/
/**
* 检查静默周期物模型值是否匹配
*
* @param operator 操作符
* @param triggerValue 触发值
* @param value 上报的值
* @return
*/
private boolean matchValue(String operator, String triggerValue, String value) {
boolean result = false;
// 操作符比较
switch (operator) {
case "=":
result = value.equals(triggerValue);
break;
case "!=":
result = !value.equals(triggerValue);
break;
case ">":
if (isNumeric(value) && isNumeric(triggerValue)) {
result = Double.parseDouble(value) > Double.parseDouble(triggerValue);
}
break;
case "<":
if (isNumeric(value) && isNumeric(triggerValue)) {
result = Double.parseDouble(value) < Double.parseDouble(triggerValue);
}
break;
case ">=":
if (isNumeric(value) && isNumeric(triggerValue)) {
result = Double.parseDouble(value) >= Double.parseDouble(triggerValue);
}
break;
case "<=":
if (isNumeric(value) && isNumeric(triggerValue)) {
result = Double.parseDouble(value) <= Double.parseDouble(triggerValue);
}
break;
case "between":
// 比较值用英文中划线分割 -
String[] triggerValues = triggerValue.split("-");
if (isNumeric(value) && isNumeric(triggerValues[0]) && isNumeric(triggerValues[1])) {
result = Double.parseDouble(value) >= Double.parseDouble(triggerValues[0]) && Double.parseDouble(value) <= Double.parseDouble(triggerValues[1]);
}
break;
case "notBetween":
// 比较值用英文中划线分割 -
String[] trigValues = triggerValue.split("-");
if (isNumeric(value) && isNumeric(trigValues[0]) && isNumeric(trigValues[1])) {
result = Double.parseDouble(value) <= Double.parseDouble(trigValues[0]) || Double.parseDouble(value) >= Double.parseDouble(trigValues[1]);
}
break;
case "contain":
result = value.contains(triggerValue);
break;
case "notContain":
result = !value.contains(triggerValue);
break;
default:
break;
}
return result;
}
/**
* 检查静默时间
*
* @param silent
* @param sceneId
* @return
*/
private boolean checkSilent(int silent, Long sceneId) {
if (silent == 0 || sceneId == 0) {
return true;
}
// silent:scene_场景编号
String key = "silent:" + "scene_" + sceneId;
Calendar calendar = Calendar.getInstance();
// 查询静默截止时间
Long expireTime = redisCache.getCacheObject(key);
if (expireTime == null) {
// 添加场景静默时间
calendar.add(Calendar.MINUTE, silent);
redisCache.setCacheObject(key, calendar.getTimeInMillis());
return true;
} else {
Long NowTimestamp = Calendar.getInstance().getTimeInMillis();
if (NowTimestamp > expireTime) {
return true;
}
return false;
}
}
/**
* 更新静默时间
*
* @param sceneId
* @param silent
*/
private void updateSilent(int silent, Long sceneId) {
if (silent == 0 || sceneId == 0) {
return;
}
// 更新场景静默时间
String key = "silent:" + "scene_" + sceneId;
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, silent);
redisCache.setCacheObject(key, calendar.getTimeInMillis());
}
/**
* 判断字符串是否为整数或小数
*/
private boolean isNumeric(String str) {
Pattern pattern = compile("[0-9]*\\.?[0-9]+");
Matcher isNum = pattern.matcher(str);
if (!isNum.matches()) {
return false;
}
return true;
}
}