第一次提交
This commit is contained in:
60
fastbee-gateway/fastbee-mq/pom.xml
Normal file
60
fastbee-gateway/fastbee-mq/pom.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>fastbee-gateway</artifactId>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<version>3.8.5</version>
|
||||
</parent>
|
||||
<artifactId>fastbee-mq</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>fastbee-protocol-base</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>fastbee-protocol-collect</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>sip-server</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-core</artifactId>
|
||||
<version>${liteflow.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-spring</artifactId>
|
||||
<version>${liteflow.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>fastbee-notify-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
@ -0,0 +1,36 @@
|
||||
package com.fastbee.mq.config;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.mq.redischannel.service.RedisPublishServiceImpl;
|
||||
import com.fastbee.mq.rocketmq.service.RocketMqPublishServiceImpl;
|
||||
import com.fastbee.mq.service.IMessagePublishService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* mq集群配置
|
||||
* @author gsb
|
||||
* @date 2022/10/10 8:27
|
||||
*/
|
||||
@Configuration
|
||||
//是否开启集群,默认不开启
|
||||
@ConditionalOnExpression("${cluster.enable:false}")
|
||||
public class MqConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type" ,havingValue = FastBeeConstant.MQTT.REDIS_CHANNEL,matchIfMissing = true)
|
||||
public IMessagePublishService redisChannelPublish(){
|
||||
return new RedisPublishServiceImpl();
|
||||
}
|
||||
|
||||
//@Bean
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type",havingValue = FastBeeConstant.MQTT.ROCKET_MQ)
|
||||
public IMessagePublishService rocketMqPublish() {
|
||||
return new RocketMqPublishServiceImpl();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.fastbee.mq.model;
|
||||
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 上报数据模型bo
|
||||
* @author bill
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ReportDataBo {
|
||||
|
||||
/**产品id*/
|
||||
private Long productId;
|
||||
/**设备编号*/
|
||||
private String serialNumber;
|
||||
/**上报消息*/
|
||||
private String message;
|
||||
/**上报的数据*/
|
||||
private List<ThingsModelSimpleItem> dataList;
|
||||
/**设备影子*/
|
||||
private boolean isShadow;
|
||||
/**
|
||||
* 物模型类型
|
||||
* 1=属性,2=功能,3=事件,4=设备升级,5=设备上线,6=设备下线
|
||||
*/
|
||||
private int type;
|
||||
/**是否执行规则引擎*/
|
||||
private boolean isRuleEngine;
|
||||
/**从机编号*/
|
||||
private Integer slaveId;
|
||||
/**
|
||||
* 上报原数据包
|
||||
*/
|
||||
private Object sources;
|
||||
|
||||
private Long userId;
|
||||
private String userName;
|
||||
private String deviceName;
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.fastbee.mq.redischannel.config;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.mq.redischannel.consumer.RedisChannelConsume;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
|
||||
|
||||
/**
|
||||
* redisChannel配置
|
||||
* @author gsb
|
||||
* @date 2022/10/10 8:57
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
@Slf4j
|
||||
public class RedisConsumeConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type" ,havingValue = FastBeeConstant.MQTT.REDIS_CHANNEL,matchIfMissing = true)
|
||||
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
|
||||
MessageListenerAdapter listenerAdapter) {
|
||||
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.PROP_READ));
|
||||
container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.FUNCTION_INVOKE));
|
||||
/*推送消息不需要关联ClientId,只需要处理推送数据, 即使本地服务不存在该客户端*/
|
||||
//container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.PUBLISH));
|
||||
container.addMessageListener(listenerAdapter,new PatternTopic(FastBeeConstant.CHANNEL.UPGRADE));
|
||||
//container.addMessageListener(listenerAdapter,new PatternTopic(FastBeeConstant.CHANNEL.OTHER));
|
||||
//container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.PUBLISH_ACK));
|
||||
//container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.PUB_REC));
|
||||
//container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.PUB_REL));
|
||||
//container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.PUB_COMP));
|
||||
return container;
|
||||
}
|
||||
|
||||
/**配置消息监听类 默认监听方法onMessage*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type" ,havingValue = FastBeeConstant.MQTT.REDIS_CHANNEL,matchIfMissing = true)
|
||||
MessageListenerAdapter listenerAdapter(RedisChannelConsume consume){
|
||||
return new MessageListenerAdapter(consume,"onMessage");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type" ,havingValue = FastBeeConstant.MQTT.REDIS_CHANNEL,matchIfMissing = true)
|
||||
StringRedisTemplate template(RedisConnectionFactory connectionFactory){
|
||||
return new StringRedisTemplate(connectionFactory);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.fastbee.mq.redischannel.consumer;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.mq.service.impl.DeviceOtherMsgHandler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2023/2/27 14:33
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceOtherMsgConsumer {
|
||||
|
||||
@Resource
|
||||
private DeviceOtherMsgHandler otherMsgHandler;
|
||||
|
||||
@Async(FastBeeConstant.TASK.DEVICE_OTHER_TASK)
|
||||
public void consume(DeviceReportBo bo){
|
||||
try {
|
||||
//处理emq订阅的非 property/post 属性上报的消息 ,因为其他消息量小,放在一起处理
|
||||
otherMsgHandler.messageHandler(bo);
|
||||
}catch (Exception e){
|
||||
log.error("=>设备其他消息处理出错",e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package com.fastbee.mq.redischannel.consumer;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.message.DeviceDownMessage;
|
||||
import com.fastbee.common.core.mq.message.ModbusPollMsg;
|
||||
import com.fastbee.common.core.mq.message.PropRead;
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.core.redis.RedisKeyBuilder;
|
||||
import com.fastbee.common.enums.DeviceStatus;
|
||||
import com.fastbee.common.enums.ServerType;
|
||||
import com.fastbee.common.enums.TopicType;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.common.utils.gateway.mq.TopicsUtils;
|
||||
import com.fastbee.common.utils.gateway.protocol.ByteUtils;
|
||||
import com.fastbee.common.utils.modbus.ModbusUtils;
|
||||
import com.fastbee.iot.model.DeviceStatusVO;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import com.fastbee.iot.service.IFunctionLogService;
|
||||
import com.fastbee.mq.service.impl.MessageManager;
|
||||
import com.fastbee.mqttclient.PubMqttClient;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 平台定时批量获取设备属性(或单个获取)
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DevicePropFetchConsumer {
|
||||
|
||||
|
||||
@Autowired
|
||||
private PubMqttClient pubMqttClient;
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
@Resource
|
||||
private MessageManager messageManager;
|
||||
@Resource
|
||||
private TopicsUtils topicsUtils;
|
||||
@Resource
|
||||
private IFunctionLogService functionLogService;
|
||||
//锁集合
|
||||
private final ConcurrentHashMap<String, Lock> taskLocks = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@Async(FastBeeConstant.TASK.DEVICE_FETCH_PROP_TASK)
|
||||
public void consume(ModbusPollMsg task) {
|
||||
String serialNumber = task.getSerialNumber();
|
||||
//获取一个线程锁
|
||||
Lock lock = taskLocks.computeIfAbsent(serialNumber, k -> new ReentrantLock());
|
||||
//阻塞直到获取到锁
|
||||
lock.lock();
|
||||
try {
|
||||
Long productId = task.getProductId();
|
||||
List<String> commandList = task.getCommandList();
|
||||
ServerType serverType = ServerType.explain(task.getTransport());
|
||||
String topic = topicsUtils.buildTopic(productId, serialNumber, TopicType.FUNCTION_GET);
|
||||
for (String command : commandList) {
|
||||
String cacheKey = RedisKeyBuilder.buildModbusRuntimeCacheKey(serialNumber);
|
||||
redisCache.zSetAdd(cacheKey, command, DateUtils.getTimestampSeconds());
|
||||
switch (serverType) {
|
||||
//通过mqtt内部客户端 下发指令
|
||||
case MQTT:
|
||||
publish(topic, ByteBufUtil.decodeHexDump(command));
|
||||
log.info("=>MQTT-线程=[{}],轮询指令:[{}],主题:[{}]", Thread.currentThread().getName(), command, topic);
|
||||
break;
|
||||
// 下发TCP客户端
|
||||
case TCP:
|
||||
Message msg = new Message();
|
||||
msg.setClientId(serialNumber);
|
||||
msg.setPayload(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump(command)));
|
||||
messageManager.requestR(serialNumber, msg, Message.class);
|
||||
log.info("=>TCP-线程=[{}],轮询指令:[{}]", Thread.currentThread().getName(), command);
|
||||
break;
|
||||
}
|
||||
//指令间隔时间
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("线程错误e", e);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void publish(String topic, byte[] pushMessage) {
|
||||
redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_SEND_TOTAL, -1L);
|
||||
redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_SEND_TODAY, 60 * 60 * 24);
|
||||
pubMqttClient.publish(pushMessage, topic, false, 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.fastbee.mq.redischannel.consumer;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.mq.service.IDeviceReportMessageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 设备消息回复
|
||||
*
|
||||
* @author gsb
|
||||
* @date 2022/10/10 11:12
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DeviceReplyMsgConsumer {
|
||||
|
||||
|
||||
@Resource
|
||||
private IDeviceReportMessageService deviceReportMessageService;
|
||||
|
||||
|
||||
/*设备回调消息,统一用上报model*/
|
||||
@Async(FastBeeConstant.TASK.DEVICE_REPLY_MESSAGE_TASK)
|
||||
public void consume(DeviceReportBo bo) {
|
||||
try {
|
||||
String topicName = bo.getTopicName();
|
||||
|
||||
if (topicName.endsWith(FastBeeConstant.TOPIC.MSG_REPLY)) {
|
||||
//普通设备回复消息
|
||||
deviceReportMessageService.parseReplyMsg(bo);
|
||||
} else if (topicName.endsWith(FastBeeConstant.TOPIC.UPGRADE_REPLY)) {
|
||||
//OTA升级的回复消息
|
||||
deviceReportMessageService.parseOTAUpdateReply(bo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("=>设备回复消息消费异常", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.fastbee.mq.redischannel.consumer;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.mq.service.IDeviceReportMessageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备上报消息处理
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceReportMsgConsumer {
|
||||
|
||||
|
||||
@Autowired
|
||||
private IDeviceReportMessageService reportMessageService;
|
||||
|
||||
@Async(FastBeeConstant.TASK.DEVICE_UP_MESSAGE_TASK)
|
||||
public void consume(DeviceReportBo bo) {
|
||||
try {
|
||||
//处理数据解析
|
||||
reportMessageService.parseReportMsg(bo);
|
||||
} catch (Exception e) {
|
||||
log.error("设备主动上报队列监听异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package com.fastbee.mq.redischannel.consumer;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.fastbee.base.service.ISessionStore;
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.device.DeviceAndProtocol;
|
||||
import com.fastbee.common.core.mq.DeviceStatusBo;
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.common.enums.DeviceStatus;
|
||||
import com.fastbee.common.enums.ThingsModelType;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.domain.Product;
|
||||
import com.fastbee.iot.model.ThingsModels.ThingsModelShadow;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import com.fastbee.iot.service.IProductService;
|
||||
import com.fastbee.iot.cache.IDeviceCache;
|
||||
import com.fastbee.iot.util.SnowflakeIdWorker;
|
||||
import com.fastbee.mq.model.ReportDataBo;
|
||||
import com.fastbee.mq.redischannel.producer.MessageProducer;
|
||||
import com.fastbee.mq.service.IMqttMessagePublish;
|
||||
import com.fastbee.mq.service.IRuleEngine;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备状态消息处理类
|
||||
* @author gsb
|
||||
* @date 2022/10/10 11:02
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceStatusConsumer {
|
||||
|
||||
@Autowired
|
||||
private IDeviceCache deviceCache;
|
||||
@Resource
|
||||
private IRuleEngine ruleEngine;
|
||||
@Resource
|
||||
private IDeviceService deviceService;
|
||||
@Resource
|
||||
private IProductService productService;
|
||||
@Resource
|
||||
private ISessionStore sessionStore;
|
||||
@Resource
|
||||
private IMqttMessagePublish mqttMessagePublish;
|
||||
@Value("${server.broker.enabled}")
|
||||
private Boolean enabled;
|
||||
private SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(2);
|
||||
|
||||
public synchronized void consume(DeviceStatusBo bo) {
|
||||
try {
|
||||
Device device = deviceService.selectDeviceBySerialNumber(bo.getSerialNumber());
|
||||
Product product = productService.selectProductByProductId(device.getProductId());
|
||||
if (enabled && product.getDeviceType() != 3) { //如果使用Netty版本 监控设备不走这里
|
||||
boolean containsKey = sessionStore.containsKey(bo.getSerialNumber());
|
||||
boolean isOnline = device.getStatus() == 3;
|
||||
log.info("=>session:{},数据库:{},更新状态:{}", containsKey, isOnline, bo.getStatus().getCode());
|
||||
if (containsKey && !isOnline) {
|
||||
//如果session存在,但数据库状态不在线,则以session为准
|
||||
bo.setStatus(DeviceStatus.ONLINE);
|
||||
}
|
||||
if (!containsKey && isOnline) {
|
||||
bo.setStatus(DeviceStatus.OFFLINE);
|
||||
}
|
||||
}
|
||||
/*更新设备状态*/
|
||||
deviceCache.updateDeviceStatusCache(bo, device);
|
||||
//处理影子模式值
|
||||
this.handlerShadow(device, bo.getStatus());
|
||||
//设备上下线执行规则引擎
|
||||
ReportDataBo dataBo = new ReportDataBo();
|
||||
dataBo.setRuleEngine(true);
|
||||
dataBo.setProductId(device.getProductId());
|
||||
dataBo.setType(bo.getStatus().equals(DeviceStatus.ONLINE) ? 5 : 6);
|
||||
dataBo.setSerialNumber(bo.getSerialNumber());
|
||||
ruleEngine.ruleMatch(dataBo);
|
||||
} catch (Exception e) {
|
||||
log.error("=>设备状态处理异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlerShadow(Device device,DeviceStatus status){
|
||||
//获取设备协议编码
|
||||
DeviceAndProtocol dp = deviceService.selectProtocolBySerialNumber(device.getSerialNumber());
|
||||
String protocolCode = dp.getProtocolCode();
|
||||
/* 设备上线 处理影子值*/
|
||||
if (status.equals(DeviceStatus.ONLINE) && device.getIsShadow() ==1){
|
||||
ThingsModelShadow shadow = deviceService.getDeviceShadowThingsModel(device);
|
||||
List<ThingsModelSimpleItem> properties = shadow.getProperties();
|
||||
List<ThingsModelSimpleItem> functions = shadow.getFunctions();
|
||||
//JsonArray组合发送
|
||||
if (FastBeeConstant.PROTOCOL.JsonArray.equals(protocolCode)) {
|
||||
if (!CollectionUtils.isEmpty(properties)) {
|
||||
mqttMessagePublish.publishProperty(device.getProductId(), device.getSerialNumber(), properties, 3);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(functions)) {
|
||||
mqttMessagePublish.publishFunction(device.getProductId(), device.getSerialNumber(), functions, 3);
|
||||
}
|
||||
} else { //其他协议单个发送
|
||||
functions.addAll(properties);
|
||||
if (!CollectionUtils.isEmpty(functions)) {
|
||||
for (ThingsModelSimpleItem function : functions) {
|
||||
MQSendMessageBo bo = new MQSendMessageBo();
|
||||
bo.setDp(dp);
|
||||
bo.setShadow(false);
|
||||
bo.setIdentifier(function.getId());
|
||||
bo.setSerialNumber(device.getSerialNumber());
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put(function.getId(),function.getValue());
|
||||
bo.setParams(jsonObject);
|
||||
bo.setValue(function.getValue());
|
||||
long id = snowflakeIdWorker.nextId();
|
||||
bo.setMessageId(id +"");
|
||||
//发送到MQ处理
|
||||
MessageProducer.sendFunctionInvoke(bo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.fastbee.mq.redischannel.consumer;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceTestReportBo;
|
||||
import com.fastbee.mq.service.IDeviceTestService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceTestConsumer {
|
||||
|
||||
@Resource
|
||||
private IDeviceTestService testHandler;
|
||||
|
||||
@Async(FastBeeConstant.TASK.DEVICE_TEST_TASK)
|
||||
public void consume(DeviceTestReportBo bo){
|
||||
try {
|
||||
//处理emq订阅的非 property/post 属性上报的消息 ,因为其他消息量小,放在一起处理
|
||||
testHandler.messageHandler(bo);
|
||||
}catch (Exception e){
|
||||
log.error("=>设备其他消息处理出错",e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.fastbee.mq.redischannel.consumer;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.device.DeviceAndProtocol;
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import com.fastbee.mq.service.IMqttMessagePublish;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 指令(服务)下发处理类
|
||||
*
|
||||
* @author gsb
|
||||
* @date 2022/10/11 8:17
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FunctionInvokeConsumer {
|
||||
|
||||
|
||||
@Autowired
|
||||
private IMqttMessagePublish functionSendService;
|
||||
|
||||
@Async(FastBeeConstant.TASK.FUNCTION_INVOKE_TASK)
|
||||
public void handler(MQSendMessageBo bo) {
|
||||
try {
|
||||
functionSendService.funcSend(bo);
|
||||
} catch (Exception e) {
|
||||
log.error("=>服务下发异常", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.fastbee.mq.redischannel.consumer;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceStatusBo;
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import com.fastbee.common.core.mq.message.DeviceDownMessage;
|
||||
import com.fastbee.common.core.mq.message.ModbusPollMsg;
|
||||
import com.fastbee.common.core.mq.ota.OtaUpgradeBo;
|
||||
import com.fastbee.mq.redischannel.queue.DevicePropFetchQueue;
|
||||
import com.fastbee.mq.redischannel.queue.DeviceStatusQueue;
|
||||
import com.fastbee.mq.redischannel.queue.FunctionInvokeQueue;
|
||||
import com.fastbee.mq.redischannel.queue.OtaUpgradeQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.connection.Message;
|
||||
import org.springframework.data.redis.connection.MessageListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* redisChannel消息监听
|
||||
*
|
||||
* @author gsb
|
||||
* @date 2022/10/10 9:17
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RedisChannelConsume implements MessageListener {
|
||||
|
||||
/**
|
||||
* 监听推送消息
|
||||
*/
|
||||
@Override
|
||||
public void onMessage(Message message, byte[] pattern) {
|
||||
try {
|
||||
/*获取channel*/
|
||||
String channel = new String(message.getChannel());
|
||||
/*获取消息*/
|
||||
String body = new String(message.getBody());
|
||||
switch (channel) {
|
||||
case FastBeeConstant.CHANNEL.PROP_READ:
|
||||
ModbusPollMsg downMessage = JSONObject.parseObject(body, ModbusPollMsg.class);
|
||||
DevicePropFetchQueue.offer(downMessage);
|
||||
break;
|
||||
case FastBeeConstant.CHANNEL.FUNCTION_INVOKE:
|
||||
MQSendMessageBo sendBo = JSONObject.parseObject(body, MQSendMessageBo.class);
|
||||
FunctionInvokeQueue.offer(sendBo);
|
||||
break;
|
||||
case FastBeeConstant.CHANNEL.UPGRADE:
|
||||
OtaUpgradeBo upgradeBo = JSONObject.parseObject(body, OtaUpgradeBo.class);
|
||||
OtaUpgradeQueue.offer(upgradeBo);
|
||||
break;
|
||||
|
||||
default:
|
||||
log.error("=>未知消息类型,channel:[{}]", channel);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("=>redisChannel处理消息异常,e", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.fastbee.mq.redischannel.listen;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.mq.redischannel.consumer.DeviceOtherMsgConsumer;
|
||||
import com.fastbee.mq.redischannel.queue.DeviceOtherQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2023/2/28 10:02
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceOtherListen {
|
||||
|
||||
@Resource
|
||||
private DeviceOtherMsgConsumer otherMsgConsumer;
|
||||
|
||||
@Async(FastBeeConstant.TASK.DEVICE_OTHER_TASK)
|
||||
public void listen(){
|
||||
while (true){
|
||||
try {
|
||||
DeviceReportBo reportBo = DeviceOtherQueue.take();
|
||||
otherMsgConsumer.consume(reportBo);
|
||||
}catch (Exception e){
|
||||
log.error("=>emq数据转发异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.fastbee.mq.redischannel.listen;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.message.DeviceDownMessage;
|
||||
import com.fastbee.common.core.mq.message.ModbusPollMsg;
|
||||
import com.fastbee.mq.redischannel.consumer.DevicePropFetchConsumer;
|
||||
import com.fastbee.mq.redischannel.queue.DevicePropFetchQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备属性获取(定时获取)监听
|
||||
*
|
||||
* @author gsb
|
||||
* @date 2022/10/11 8:26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DevicePropFetchListen {
|
||||
|
||||
@Autowired
|
||||
private DevicePropFetchConsumer devicePropFetchConsumer;
|
||||
|
||||
@Async(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK_FETCH)
|
||||
public void listen() {
|
||||
while (true) {
|
||||
try {
|
||||
ModbusPollMsg take = DevicePropFetchQueue.take();
|
||||
devicePropFetchConsumer.consume(take);
|
||||
} catch (Exception e) {
|
||||
log.error("=>设备属性获取异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.fastbee.mq.redischannel.listen;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.mq.redischannel.consumer.DeviceReplyMsgConsumer;
|
||||
import com.fastbee.mq.redischannel.queue.DeviceReplyQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* 设备回调消息监听
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceReplyListen {
|
||||
|
||||
@Autowired
|
||||
private DeviceReplyMsgConsumer deviceReplyMsgHandler;
|
||||
|
||||
@Async(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK_PUB)
|
||||
public void listen() {
|
||||
while (true) {
|
||||
try {
|
||||
/*读队列消息*/
|
||||
DeviceReportBo reportBo = DeviceReplyQueue.take();
|
||||
/*处理消息*/
|
||||
deviceReplyMsgHandler.consume(reportBo);
|
||||
} catch (Exception e) {
|
||||
log.error("=>设备回调消息监听异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.fastbee.mq.redischannel.listen;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.mq.redischannel.consumer.DeviceReportMsgConsumer;
|
||||
import com.fastbee.mq.redischannel.queue.DeviceReplyQueue;
|
||||
import com.fastbee.mq.redischannel.queue.DeviceReportQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* 设备主动上报消息监听
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceReportListen {
|
||||
|
||||
@Autowired
|
||||
private DeviceReportMsgConsumer reportMsgConsumer;
|
||||
|
||||
@Async(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK_PUB)
|
||||
public void listen() {
|
||||
while (true) {
|
||||
try {
|
||||
/*取出数据*/
|
||||
DeviceReportBo reportBo = DeviceReportQueue.take();
|
||||
/*处理数据*/
|
||||
reportMsgConsumer.consume(reportBo);
|
||||
} catch (Exception e) {
|
||||
log.error("=>设备上报数据监听异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.fastbee.mq.redischannel.listen;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceStatusBo;
|
||||
import com.fastbee.mq.redischannel.consumer.DeviceStatusConsumer;
|
||||
import com.fastbee.mq.redischannel.queue.DeviceStatusQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备状态监听
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DeviceStatusListen {
|
||||
|
||||
@Autowired
|
||||
private DeviceStatusConsumer deviceStatusConsumer;
|
||||
|
||||
@Async(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK)
|
||||
public void listen() {
|
||||
try {
|
||||
while (true) {
|
||||
DeviceStatusBo status = DeviceStatusQueue.take();
|
||||
deviceStatusConsumer.consume(status);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("设备状态监听错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.fastbee.mq.redischannel.listen;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceTestReportBo;
|
||||
import com.fastbee.mq.redischannel.consumer.DeviceTestConsumer;
|
||||
import com.fastbee.mq.redischannel.queue.DeviceTestQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author bill
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DeviceTestListen {
|
||||
|
||||
@Resource
|
||||
private DeviceTestConsumer deviceTestConsumer;
|
||||
|
||||
@Async(FastBeeConstant.TASK.DEVICE_TEST_TASK)
|
||||
public void listen(){
|
||||
while (true){
|
||||
try {
|
||||
DeviceTestReportBo take = DeviceTestQueue.take();
|
||||
|
||||
deviceTestConsumer.consume(take);
|
||||
}catch (Exception e){
|
||||
log.error("=>emq数据转发异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.fastbee.mq.redischannel.listen;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import com.fastbee.mq.redischannel.consumer.FunctionInvokeConsumer;
|
||||
import com.fastbee.mq.redischannel.queue.FunctionInvokeQueue;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备服务下发监听
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FunctionInvokeListen {
|
||||
|
||||
@Autowired
|
||||
private FunctionInvokeConsumer functionInvokeConsumer;
|
||||
|
||||
@Async(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK)
|
||||
public void listen() {
|
||||
while (true) {
|
||||
try {
|
||||
MQSendMessageBo sendBo = FunctionInvokeQueue.take();
|
||||
functionInvokeConsumer.handler(sendBo);
|
||||
} catch (Exception e) {
|
||||
log.error("=>下发服务消费异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.fastbee.mq.redischannel.listen;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.ota.OtaUpgradeBo;
|
||||
import com.fastbee.mq.redischannel.queue.OtaUpgradeQueue;
|
||||
import com.fastbee.mq.service.IMqttMessagePublish;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* OTA升级消息监听
|
||||
*
|
||||
* @author gsb
|
||||
* @date 2022/10/11 8:36
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class UpgradeListen {
|
||||
|
||||
@Autowired
|
||||
private IMqttMessagePublish functionSendService;
|
||||
|
||||
@Async(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK)
|
||||
public void listen() {
|
||||
while (true) {
|
||||
try {
|
||||
/*获取队列中的OTA升级消息*/
|
||||
OtaUpgradeBo upgradeBo = OtaUpgradeQueue.take();
|
||||
// OTA升级处理
|
||||
functionSendService.upGradeOTA(upgradeBo);
|
||||
} catch (Exception e) {
|
||||
log.error("->OTA消息监听异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.fastbee.mq.redischannel.producer;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.mqttclient.IEmqxMessageProducer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author bill
|
||||
*/
|
||||
@Component
|
||||
public class EmqxMessageProducer implements IEmqxMessageProducer {
|
||||
|
||||
|
||||
@Override
|
||||
public void sendEmqxMessage(String topicName, DeviceReportBo deviceReportBo) {
|
||||
if (topicName.contains("property/post")){
|
||||
MessageProducer.sendPublishMsg(deviceReportBo);
|
||||
} else {
|
||||
MessageProducer.sendOtherMsg(deviceReportBo);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.fastbee.mq.redischannel.producer;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.common.core.mq.DeviceStatusBo;
|
||||
import com.fastbee.common.core.mq.DeviceTestReportBo;
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import com.fastbee.common.core.mq.message.ModbusPollMsg;
|
||||
import com.fastbee.common.core.mq.ota.OtaUpgradeBo;
|
||||
import com.fastbee.common.core.mq.message.DeviceDownMessage;
|
||||
import com.fastbee.mq.redischannel.queue.*;
|
||||
|
||||
/**
|
||||
*设备消息生产者 ,设备的消息发送通道
|
||||
* @author bill
|
||||
*/
|
||||
public class MessageProducer {
|
||||
|
||||
/*发送设备获取属性消息到队列*/
|
||||
public static void sendPropFetch(ModbusPollMsg bo){
|
||||
DevicePropFetchQueue.offer(bo);
|
||||
}
|
||||
/*发送设备服务下发消息到队列*/
|
||||
public static void sendFunctionInvoke(MQSendMessageBo bo){
|
||||
FunctionInvokeQueue.offer(bo);
|
||||
}
|
||||
/*发送设备上报消息到队列*/
|
||||
public static void sendPublishMsg(DeviceReportBo bo){
|
||||
DeviceReportQueue.offer(bo);
|
||||
}
|
||||
public static void sendOtherMsg(DeviceReportBo bo){
|
||||
DeviceOtherQueue.offer(bo);
|
||||
}
|
||||
|
||||
public static void sendStatusMsg(DeviceStatusBo bo){
|
||||
DeviceStatusQueue.offer(bo);
|
||||
}
|
||||
/**
|
||||
* 设备调试通道
|
||||
* @param bo
|
||||
*/
|
||||
public static void sendDeviceTestMsg(DeviceTestReportBo bo){
|
||||
DeviceTestQueue.offer(bo);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fastbee.mq.redischannel.queue;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2022/10/10 10:13
|
||||
*/
|
||||
public class DeviceOtherQueue {
|
||||
|
||||
private static final LinkedBlockingQueue<DeviceReportBo> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
/*元素加入队列,最后*/
|
||||
public static void offer(DeviceReportBo dto){
|
||||
queue.offer(dto);
|
||||
}
|
||||
/*取出队列元素 先进先出*/
|
||||
@SneakyThrows
|
||||
public static DeviceReportBo take(){
|
||||
return queue.take();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.fastbee.mq.redischannel.queue;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.common.core.mq.message.DeviceDownMessage;
|
||||
import com.fastbee.common.core.mq.message.ModbusPollMsg;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* 设备属性获取存储列队
|
||||
* @author gsb
|
||||
* @date 2022/10/11 8:29
|
||||
*/
|
||||
public class DevicePropFetchQueue {
|
||||
private static final LinkedBlockingQueue<ModbusPollMsg> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
/*元素加入队列,最后*/
|
||||
public static void offer(ModbusPollMsg dto){
|
||||
queue.offer(dto);
|
||||
}
|
||||
/*取出队列元素 先进先出*/
|
||||
@SneakyThrows
|
||||
public static ModbusPollMsg take(){
|
||||
return queue.take();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fastbee.mq.redischannel.queue;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* 设备消息回调队列 {@link DeviceReportBo}
|
||||
* @author gsb
|
||||
* @date 2022/10/10 10:15
|
||||
*/
|
||||
public class DeviceReplyQueue {
|
||||
private static final LinkedBlockingQueue<DeviceReportBo> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
/*元素加入队列,最后*/
|
||||
public static void offer(DeviceReportBo dto){
|
||||
queue.offer(dto);
|
||||
}
|
||||
/*取出队列元素 先进先出*/
|
||||
@SneakyThrows
|
||||
public static DeviceReportBo take(){
|
||||
return queue.take();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fastbee.mq.redischannel.queue;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2022/10/10 10:13
|
||||
*/
|
||||
public class DeviceReportQueue {
|
||||
|
||||
private static final LinkedBlockingQueue<DeviceReportBo> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
/*元素加入队列,最后*/
|
||||
public static void offer(DeviceReportBo dto){
|
||||
queue.offer(dto);
|
||||
}
|
||||
/*取出队列元素 先进先出*/
|
||||
@SneakyThrows
|
||||
public static DeviceReportBo take(){
|
||||
return queue.take();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fastbee.mq.redischannel.queue;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceStatusBo;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* 设备消息缓存队列 添加{@link DeviceStatusBo} 消息
|
||||
* @author gsb
|
||||
* @date 2022/10/10 9:59
|
||||
*/
|
||||
public class DeviceStatusQueue {
|
||||
private static final LinkedBlockingQueue<DeviceStatusBo> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
/*元素加入队列,最后*/
|
||||
public static void offer(DeviceStatusBo dto){
|
||||
queue.offer(dto);
|
||||
}
|
||||
/*取出队列元素 先进先出*/
|
||||
@SneakyThrows
|
||||
public static DeviceStatusBo take(){
|
||||
return queue.take();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fastbee.mq.redischannel.queue;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceTestReportBo;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* @author bill
|
||||
*/
|
||||
public class DeviceTestQueue {
|
||||
|
||||
|
||||
private static final LinkedBlockingQueue<DeviceTestReportBo> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
/*元素加入队列,最后*/
|
||||
public static void offer(DeviceTestReportBo dto){
|
||||
queue.offer(dto);
|
||||
}
|
||||
/*取出队列元素 先进先出*/
|
||||
@SneakyThrows
|
||||
public static DeviceTestReportBo take(){
|
||||
return queue.take();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fastbee.mq.redischannel.queue;
|
||||
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* 服务下发队列 处理{@link MQSendMessageBo}
|
||||
* @author gsb
|
||||
* @date 2022/10/10 10:11
|
||||
*/
|
||||
public class FunctionInvokeQueue {
|
||||
private static final LinkedBlockingQueue<MQSendMessageBo> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
/*元素加入队列,最后*/
|
||||
public static void offer(MQSendMessageBo dto){
|
||||
queue.offer(dto);
|
||||
}
|
||||
/*取出队列元素 先进先出*/
|
||||
@SneakyThrows
|
||||
public static MQSendMessageBo take(){
|
||||
return queue.take();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fastbee.mq.redischannel.queue;
|
||||
|
||||
import com.fastbee.common.core.mq.ota.OtaUpgradeBo;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* OTA升级列队 {@link OtaUpgradeBo}
|
||||
* @author gsb
|
||||
* @date 2022/10/10 10:30
|
||||
*/
|
||||
public class OtaUpgradeQueue {
|
||||
private static final LinkedBlockingQueue<OtaUpgradeBo> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
/*元素加入队列,最后*/
|
||||
public static void offer(OtaUpgradeBo dto){
|
||||
queue.offer(dto);
|
||||
}
|
||||
/*取出队列元素 先进先出*/
|
||||
@SneakyThrows
|
||||
public static OtaUpgradeBo take(){
|
||||
return queue.take();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.fastbee.mq.redischannel.service;
|
||||
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.mq.service.IMessagePublishService;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* 设备消息推送至RedisChannel
|
||||
* @author bill
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class RedisPublishServiceImpl implements IMessagePublishService {
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 消息推送到redisChannel
|
||||
* @param message 设备消息
|
||||
* @param channel 推送channel
|
||||
*/
|
||||
@Override
|
||||
public void publish(Object message,String channel) {
|
||||
redisCache.publish(message,channel);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.fastbee.mq.rocketmq.consumer;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author bill
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "rocketmq.producer")
|
||||
@Data
|
||||
public class ConsumerTopicConstant {
|
||||
|
||||
/**网关默认主题*/
|
||||
private String mqTopic;
|
||||
/*设备状态topic*/
|
||||
private String deviceStatusTopic;
|
||||
/*设备主动上报topic*/
|
||||
private String deviceUpTopic;
|
||||
/*设备服务下发topic*/
|
||||
private String functionInvokeTopic;
|
||||
/*设备消息回调topic*/
|
||||
private String deviceReplyTopic;
|
||||
/*平台获取属性topic*/
|
||||
private String fetchPropTopic;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.fastbee.mq.rocketmq.listener;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceStatusBo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* RocketMQ监听设备状态消息
|
||||
* @author gsb
|
||||
* @date 2022/10/11 9:37
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RocketMQMessageListener(consumerGroup = FastBeeConstant.CHANNEL.DEVICE_STATUS_GROUP , topic = FastBeeConstant.CHANNEL.DEVICE_STATUS)
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type",havingValue = FastBeeConstant.MQTT.ROCKET_MQ)
|
||||
public class RocketDeviceStatusListener implements RocketMQListener<DeviceStatusBo> {
|
||||
|
||||
@Override
|
||||
public void onMessage(DeviceStatusBo deviceStatusBo) {
|
||||
log.debug("=>收到设备状态消息,message=[{}]",deviceStatusBo);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.fastbee.mq.rocketmq.listener;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* MQ监听服务下发消息
|
||||
* @author gsb
|
||||
* @date 2022/10/11 9:53
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RocketMQMessageListener(consumerGroup = FastBeeConstant.CHANNEL.FUNCTION_INVOKE_GROUP , topic = FastBeeConstant.CHANNEL.FUNCTION_INVOKE)
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type",havingValue = FastBeeConstant.MQTT.ROCKET_MQ)
|
||||
public class RocketFunctionInvokeListener implements RocketMQListener<MQSendMessageBo> {
|
||||
|
||||
|
||||
@Override
|
||||
public void onMessage(MQSendMessageBo bo) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fastbee.mq.rocketmq.listener;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.message.DeviceDownMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2022/10/11 16:49
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RocketMQMessageListener(consumerGroup = FastBeeConstant.CHANNEL.PROP_READ_GROUP , topic = FastBeeConstant.CHANNEL.PROP_READ)
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type",havingValue = FastBeeConstant.MQTT.ROCKET_MQ)
|
||||
public class RocketPropReadListener implements RocketMQListener<DeviceDownMessage> {
|
||||
|
||||
@Override
|
||||
public void onMessage(DeviceDownMessage deviceDownMessage) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.fastbee.mq.rocketmq.listener;
|
||||
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* MQ监听设备推送消息(上报消息和回调消息)
|
||||
* @author gsb
|
||||
* @date 2022/10/11 9:51
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RocketMQMessageListener(consumerGroup = FastBeeConstant.CHANNEL.PUBLISH_GROUP , topic = FastBeeConstant.CHANNEL.PUBLISH)
|
||||
@ConditionalOnProperty(prefix ="cluster", name = "type",havingValue = FastBeeConstant.MQTT.ROCKET_MQ)
|
||||
public class RocketPublishMsgListener implements RocketMQListener<DeviceReportBo> {
|
||||
|
||||
@Override
|
||||
public void onMessage(DeviceReportBo bo) {
|
||||
log.debug("=>收到设备推送消息,message=[{}]",bo);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.fastbee.mq.rocketmq.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 网关通用模型
|
||||
* @author bill
|
||||
*/
|
||||
@Data
|
||||
public class MQSendMessage implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**topic*/
|
||||
private String topicName;
|
||||
|
||||
/**消息-json格式*/
|
||||
private String message;
|
||||
}
|
@ -0,0 +1,261 @@
|
||||
package com.fastbee.mq.rocketmq.producer;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.client.producer.*;
|
||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||
import org.apache.rocketmq.spring.support.RocketMQHeaders;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
//@Component
|
||||
@Slf4j
|
||||
public class RocketMqProducer {
|
||||
|
||||
/**
|
||||
* rocketmq模板注入
|
||||
*/
|
||||
@Autowired
|
||||
private RocketMQTemplate rocketMQTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* 普通发送
|
||||
* @param topic 消息主题
|
||||
* @param msg 消息体
|
||||
* @param <T> 消息泛型
|
||||
*/
|
||||
public <T> void send(String topic, T msg) {
|
||||
rocketMQTemplate.convertAndSend(topic, msg);
|
||||
//rocketMQTemplate.send(topic + ":tag1", MessageBuilder.withPayload(msg).build()); // 等价于上面一行
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带tag的消息,直接在topic后面加上":tag"
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param tag 消息tag
|
||||
* @param msg 消息体
|
||||
* @param <T> 消息泛型
|
||||
* @return
|
||||
*/
|
||||
public <T> SendResult sendTagMsg(String topic, String tag, T msg) {
|
||||
topic = topic + ":" + tag;
|
||||
return rocketMQTemplate.syncSend(topic, MessageBuilder.withPayload(msg).build());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送同步消息(阻塞当前线程,等待broker响应发送结果,这样不太容易丢失消息)
|
||||
* sendResult为返回的发送结果
|
||||
*/
|
||||
public <T> SendResult sendMsg(String topic, T msg) {
|
||||
Message<T> message = MessageBuilder.withPayload(msg).build();
|
||||
SendResult sendResult = rocketMQTemplate.syncSend(topic, message);
|
||||
log.info("【sendMsg】sendResult={}", JSON.toJSONString(sendResult));
|
||||
return sendResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送异步消息
|
||||
* 发送异步消息(通过线程池执行发送到broker的消息任务,执行完后回调:在SendCallback中可处理相关成功失败时的逻辑)
|
||||
* (适合对响应时间敏感的业务场景)
|
||||
* @param topic 消息Topic
|
||||
* @param msg 消息实体
|
||||
*
|
||||
*/
|
||||
public <T> void asyncSend(String topic, T msg) {
|
||||
Message<T> message = MessageBuilder.withPayload(msg).build();
|
||||
asyncSend(topic, message, new SendCallback() {
|
||||
@Override
|
||||
public void onSuccess(SendResult sendResult) {
|
||||
log.info("topic:{}消息---发送MQ成功---", topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Throwable throwable) {
|
||||
log.error("topic:{}消息---发送MQ失败 ex:{}---", topic, throwable.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送异步消息
|
||||
* 发送异步消息(通过线程池执行发送到broker的消息任务,执行完后回调:在SendCallback中可处理相关成功失败时的逻辑)
|
||||
* (适合对响应时间敏感的业务场景)
|
||||
* @param topic 消息Topic
|
||||
* @param message 消息实体
|
||||
* @param sendCallback 回调函数
|
||||
*/
|
||||
public void asyncSend(String topic, Message<?> message, SendCallback sendCallback) {
|
||||
rocketMQTemplate.asyncSend(topic, message, sendCallback);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送异步消息
|
||||
*
|
||||
* @param topic 消息Topic
|
||||
* @param message 消息实体
|
||||
* @param sendCallback 回调函数
|
||||
* @param timeout 超时时间
|
||||
*/
|
||||
public void asyncSend(String topic, Message<?> message, SendCallback sendCallback, long timeout) {
|
||||
rocketMQTemplate.asyncSend(topic, message, sendCallback, timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 同步延迟消息
|
||||
* rocketMQ的延迟消息发送其实是已发送就已经到broker端了,然后消费端会延迟收到消息。
|
||||
* RocketMQ 目前只支持固定精度的定时消息。
|
||||
* 固定等级:1到18分别对应1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
|
||||
* 延迟的底层方法是用定时任务实现的。
|
||||
* 发送延时消息(delayLevel的值就为0,因为不延时)
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param msg 消息体
|
||||
* @param timeout 发送超时时间
|
||||
* @param delayLevel 延迟级别 1到18
|
||||
* @param <T> 消息泛型
|
||||
*/
|
||||
public <T> void sendDelay(String topic, T msg, long timeout, int delayLevel) {
|
||||
Message<T> message = MessageBuilder.withPayload(msg).build();
|
||||
rocketMQTemplate.syncSend(topic, message, timeout, delayLevel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送异步延迟消息
|
||||
*
|
||||
* @param topic 消息Topic
|
||||
* @param message 消息实体
|
||||
* @param sendCallback 回调函数
|
||||
* @param timeout 超时时间
|
||||
* @param delayLevel 延迟消息的级别
|
||||
*/
|
||||
public void asyncSendDelay(String topic, Message<?> message, SendCallback sendCallback, long timeout, int delayLevel) {
|
||||
rocketMQTemplate.asyncSend(topic, message, sendCallback, timeout, delayLevel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送异步延迟消息
|
||||
*
|
||||
* @param topic 消息Topic
|
||||
* @param message 消息实体
|
||||
* @param timeout 超时时间
|
||||
* @param delayLevel 延迟消息的级别
|
||||
*/
|
||||
public void asyncSendDelay(String topic, Message<?> message, long timeout, int delayLevel) {
|
||||
rocketMQTemplate.asyncSend(topic, message, new SendCallback() {
|
||||
@Override
|
||||
public void onSuccess(SendResult sendResult) {
|
||||
log.info("topic:{}消息---发送MQ成功---", topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Throwable throwable) {
|
||||
log.error("topic:{}消息---发送MQ失败 ex:{}---", topic, throwable.getMessage());
|
||||
}
|
||||
}, timeout, delayLevel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 单向消息
|
||||
* 特点为只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答
|
||||
* 此方式发送消息的过程耗时非常短,一般在微秒级别
|
||||
* 应用场景:适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集
|
||||
* @param topic 消息主题
|
||||
* @param msg 消息体
|
||||
* @param <T> 消息泛型
|
||||
*/
|
||||
public <T> void sendOneWayMsg(String topic, T msg) {
|
||||
Message<T> message = MessageBuilder.withPayload(msg).build();
|
||||
rocketMQTemplate.sendOneWay(topic, message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送顺序消息
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param msg 消息体
|
||||
* @param hashKey 确定消息发送到哪个队列中
|
||||
* @param <T> 消息泛型
|
||||
*/
|
||||
public <T> void syncSendOrderly(String topic, T msg, String hashKey) {
|
||||
Message<T> message = MessageBuilder.withPayload(msg).build();
|
||||
log.info("发送顺序消息,topic:{}, hashKey:{}", topic, hashKey);
|
||||
rocketMQTemplate.syncSendOrderly(topic, message, hashKey);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送顺序消息
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param msg 消息体
|
||||
* @param hashKey 确定消息发送到哪个队列中
|
||||
* @param timeout 超时时间
|
||||
*/
|
||||
public <T> void syncSendOrderly(String topic, T msg, String hashKey, long timeout) {
|
||||
Message<T> message = MessageBuilder.withPayload(msg).build();
|
||||
log.info("发送顺序消息,topic:{}, hashKey:{}, timeout:{}", topic, hashKey, timeout);
|
||||
rocketMQTemplate.syncSendOrderly(topic, message, hashKey, timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送批量消息
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param msgList 消息体集合
|
||||
* @param <T> 消息泛型
|
||||
* @return
|
||||
*/
|
||||
public <T> SendResult asyncSendBatch(String topic, List<T> msgList) {
|
||||
List<Message<T>> messageList = msgList.stream()
|
||||
.map(msg -> MessageBuilder.withPayload(msg).build()).collect(Collectors.toList());
|
||||
return rocketMQTemplate.syncSend(topic, messageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送事务消息
|
||||
*
|
||||
* @param txProducerGroup 事务消息的生产者组名称
|
||||
* @param topic 事务消息主题
|
||||
* @param tag 事务消息tag
|
||||
* @param msg 事务消息体
|
||||
* @param arg 事务消息监听器回查参数
|
||||
* @param <T> 事务消息泛型
|
||||
*/
|
||||
public <T> void sendTransaction(String txProducerGroup, String topic, String tag, T msg, T arg){
|
||||
if(StringUtils.isNotEmpty(tag)){
|
||||
topic = topic + ":" + tag;
|
||||
}
|
||||
String transactionId = UUID.randomUUID().toString();
|
||||
Message<T> message = MessageBuilder.withPayload(msg)
|
||||
//header也有大用处
|
||||
.setHeader(RocketMQHeaders.TRANSACTION_ID, transactionId)
|
||||
.setHeader("share_id", "TEST")
|
||||
.build();
|
||||
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(topic, message, arg);
|
||||
if(result.getLocalTransactionState().equals(LocalTransactionState.COMMIT_MESSAGE)
|
||||
&& result.getSendStatus().equals(SendStatus.SEND_OK)){
|
||||
log.info("事物消息发送成功");
|
||||
}
|
||||
log.info("事物消息发送结果:{}", result);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.fastbee.mq.rocketmq.service;
|
||||
|
||||
import com.fastbee.mq.rocketmq.producer.RocketMqProducer;
|
||||
import com.fastbee.mq.service.IMessagePublishService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* 设备消息推送至RocketMq
|
||||
* @author bill
|
||||
*/
|
||||
public class RocketMqPublishServiceImpl implements IMessagePublishService {
|
||||
|
||||
@Autowired
|
||||
private RocketMqProducer rocketMqProducer;
|
||||
|
||||
/**
|
||||
* rocket通用生产消息方法
|
||||
* @param message 设备消息
|
||||
* @param channel 推送topic
|
||||
*/
|
||||
@Override
|
||||
public void publish(Object message,String channel)
|
||||
{
|
||||
rocketMqProducer.send(channel,message);
|
||||
}
|
||||
}
|
@ -0,0 +1,689 @@
|
||||
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.notify.AlertPushParams;
|
||||
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.domain.*;
|
||||
import com.fastbee.iot.model.AlertSceneSendVO;
|
||||
import com.fastbee.iot.model.SceneTerminalUserVO;
|
||||
import com.fastbee.iot.model.ScriptTemplate;
|
||||
import com.fastbee.iot.model.ThingsModels.ValueItem;
|
||||
import com.fastbee.iot.service.*;
|
||||
import com.fastbee.mq.service.IFunctionInvoke;
|
||||
import com.fastbee.mq.service.IRuleEngine;
|
||||
import com.fastbee.notify.core.service.NotifySendService;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.regex.Pattern.compile;
|
||||
|
||||
@Data
|
||||
@Slf4j
|
||||
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 IAlertLogService alertLogService = SpringUtils.getBean(IAlertLogService.class);
|
||||
|
||||
private static IDeviceUserService deviceUserService = SpringUtils.getBean(IDeviceUserService.class);
|
||||
|
||||
private static ISceneService sceneService = SpringUtils.getBean(ISceneService.class);
|
||||
|
||||
private static IRuleEngine ruleService = SpringUtils.getBean(IRuleEngine.class);
|
||||
|
||||
private static IDeviceAlertUserService deviceAlertUserService = SpringUtils.getBean(IDeviceAlertUserService.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 {
|
||||
log.info("------------------[规则引擎执行...]---------------------");
|
||||
ScriptTemplate scriptTemplate = JSON.parseObject(json, ScriptTemplate.class);
|
||||
log.info("规则引擎脚本配置:{},规则引擎上下文:{}", scriptTemplate, this);
|
||||
//触发器
|
||||
if (scriptTemplate.getPurpose() == 2) {
|
||||
// 触发器,检查静默时间
|
||||
if (!checkSilent(scriptTemplate.getSilent(), scriptTemplate.getSceneId(), this.deviceNum)) {
|
||||
// 触发条件为不满足时,返回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) {
|
||||
if (scriptTemplate.getCheckdelay() != 0) {
|
||||
// 告警恢复规则 设备上线触发 不加载
|
||||
if (scriptTemplate.getSource() != 5 && type != 5) {
|
||||
loadDelayTrigger(scriptTemplate);
|
||||
}
|
||||
} else {
|
||||
// 执行动作,延迟执行,线程休眠 delay x 1000毫秒
|
||||
Thread.sleep(scriptTemplate.getDelay() * 1000);
|
||||
// 存在相同未处理告警只记录 不触发告警通知和动作
|
||||
|
||||
if (scriptTemplate.getSource() == 4) {
|
||||
//存在相同未处理告警只记录 不触发告警通知和动作
|
||||
if (checkDeviceAlerting(scriptTemplate.getSceneId(), this.deviceNum)) {
|
||||
this.alert(scriptTemplate.getSceneId(),false);
|
||||
} else {
|
||||
this.alert(scriptTemplate.getSceneId(),true);
|
||||
}
|
||||
|
||||
} else if (scriptTemplate.getSource() == 5) {
|
||||
this.alertRecover(scriptTemplate);
|
||||
} else if (scriptTemplate.getSource() == 1 || scriptTemplate.getSource() == 3) {
|
||||
// 下发指令
|
||||
this.send(scriptTemplate);
|
||||
}
|
||||
// 更新静默时间
|
||||
this.updateSilent(scriptTemplate.getSilent(), scriptTemplate.getSceneId(), this.deviceNum);
|
||||
}
|
||||
}
|
||||
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 (type == 5 && scriptTemplate.getCheckdelay() != 0) {
|
||||
String key = "CHECK_" + scriptTemplate.getSceneId() + deviceNum;
|
||||
//设备上线删除告警任务
|
||||
ruleService.removeCheckTask(key);
|
||||
log.info("设备上线清理 离线延时匹配任务:{}", key);
|
||||
}
|
||||
// 上线,下线
|
||||
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 (type == 5 && scriptTemplate.getCheckdelay() != 0) {
|
||||
String key = "CHECK_" + scriptTemplate.getSceneId() + deviceNum;
|
||||
//设备上线删除告警任务
|
||||
ruleService.removeCheckTask(key);
|
||||
log.info("设备上线清理 离线延时匹配任务:{}", key);
|
||||
}
|
||||
// 上线,下线
|
||||
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;
|
||||
}
|
||||
|
||||
private void loadDelayTrigger(ScriptTemplate scriptTemplate) {
|
||||
String key = "CHECK_" + scriptTemplate.getSceneId() + deviceNum;
|
||||
//默认上线执行告警恢复 在延迟匹配周期内不告警
|
||||
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
||||
executor.schedule(() -> {
|
||||
//设备未上线,则执行告警任务
|
||||
if (scriptTemplate.getSource() == 4) {
|
||||
// 告警
|
||||
this.alert(scriptTemplate.getSceneId(),true);
|
||||
} else if (scriptTemplate.getSource() == 1 || scriptTemplate.getSource() == 3) {
|
||||
// 下发指令
|
||||
this.send(scriptTemplate);
|
||||
}
|
||||
}, scriptTemplate.getCheckdelay(), TimeUnit.SECONDS);
|
||||
log.info("延时执行告警任务:{}", key);
|
||||
ruleService.addCheckTask(key, executor);
|
||||
}
|
||||
|
||||
private boolean checkDeviceAlerting(Long sceneId, String deviceNum) {
|
||||
AlertLog alertLog = new AlertLog();
|
||||
alertLog.setSerialNumber(deviceNum);
|
||||
alertLog.setStatus(2);
|
||||
alertLog.setCreateBy(sceneId.toString());
|
||||
Long count = alertLogService.selectAlertLogListCount(alertLog);
|
||||
// 查询设备告警对应的场景是否有未处理告警
|
||||
if (count > 0) {
|
||||
return true;
|
||||
} else {
|
||||
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.setParams(new JSONObject(reqDto.getRemoteCommand()));
|
||||
functionInvoke.invokeNoReply(reqDto);
|
||||
}
|
||||
}
|
||||
|
||||
private void alertRecover(ScriptTemplate scriptTemplate) {
|
||||
AlertLog alertLog = new AlertLog();
|
||||
alertLog.setCreateBy(scriptTemplate.getRecoverId().toString());
|
||||
alertLog.setSerialNumber(deviceNum);
|
||||
alertLog.setStatus(2);
|
||||
//自动设置告警处理状态
|
||||
alertLogService.updateAlertLogStatus(alertLog);
|
||||
//如果存在延时确认任务,则删除
|
||||
String key = "CHECK_" + scriptTemplate.getRecoverId() + deviceNum;
|
||||
//设备上线删除告警任务
|
||||
ruleService.removeCheckTask(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行动作,告警处理
|
||||
*
|
||||
* @param sceneId 场景ID
|
||||
*/
|
||||
private void alert(Long sceneId, boolean isNodify) {
|
||||
Set<Long> sceneIdSet = sceneThingsModelItems.stream().map(SceneThingsModelItem::getSceneId).collect(Collectors.toSet());
|
||||
List<SceneTerminalUserVO> sceneTerminalUserVOList = sceneService.selectTerminalUserBySceneIds(sceneIdSet);
|
||||
Map<Long, SceneTerminalUserVO> sceneTerminalUserMap = sceneTerminalUserVOList.stream().collect(Collectors.toMap(SceneTerminalUserVO::getSceneId, Function.identity()));
|
||||
List<AlertLog> alertLogList = new ArrayList<>();
|
||||
for (SceneThingsModelItem sceneThingsModelItem : sceneThingsModelItems) {
|
||||
// 查询设备信息
|
||||
Device device = deviceService.selectDeviceBySerialNumber(sceneThingsModelItem.getDeviceNumber());
|
||||
Optional.ofNullable(device).orElseThrow(() -> new ServiceException("告警推送,设备不存在" + "[{" + sceneThingsModelItem.getDeviceNumber() + "}]"));
|
||||
// 判断是否是终端用户的场景
|
||||
SceneTerminalUserVO sceneTerminalUserVO = sceneTerminalUserMap.get(sceneId);
|
||||
if (1 == sceneTerminalUserVO.getTerminalUser()) {
|
||||
AlertLog alertLog = this.getTerminalUserAlertLog(sceneTerminalUserVO, device, sceneThingsModelItem);
|
||||
alertLogList.add(alertLog);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取场景相关的告警参数,告警必须要是启动状态
|
||||
List<AlertSceneSendVO> sceneSendVOList = alertService.listByAlertIds(sceneId);
|
||||
if (CollectionUtils.isEmpty(sceneSendVOList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取告警关联模版id
|
||||
for (AlertSceneSendVO alertSceneSendVO : sceneSendVOList) {
|
||||
// 开启通知发送
|
||||
if (isNodify) {
|
||||
AlertPushParams alertPushParams = buildAlertPushParams(device, sceneThingsModelItem);
|
||||
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)) {
|
||||
alertLogService.insertAlertLogBatch(alertLogList);
|
||||
}
|
||||
}
|
||||
|
||||
private AlertPushParams buildAlertPushParams(Device device, SceneThingsModelItem sceneThingsModelItem) {
|
||||
// 获取告警推送参数
|
||||
AlertPushParams alertPushParams = new AlertPushParams();
|
||||
alertPushParams.setDeviceName(device.getDeviceName());
|
||||
alertPushParams.setSerialNumber(sceneThingsModelItem.getDeviceNumber());
|
||||
// List<DeviceUser> deviceUserList = deviceUserService.getDeviceUserAndShare(device.getDeviceId());
|
||||
// 多租户改版查询自己配置的告警用户
|
||||
DeviceAlertUser deviceAlertUser = new DeviceAlertUser();
|
||||
deviceAlertUser.setDeviceId(device.getDeviceId());
|
||||
List<DeviceAlertUser> deviceUserList = deviceAlertUserService.selectDeviceAlertUserList(deviceAlertUser);
|
||||
if (CollectionUtils.isNotEmpty(deviceUserList)) {
|
||||
alertPushParams.setUserPhoneSet(deviceUserList.stream().map(DeviceAlertUser::getPhoneNumber).filter(StringUtils::isNotEmpty).collect(Collectors.toSet()));
|
||||
alertPushParams.setUserIdSet(deviceUserList.stream().map(DeviceAlertUser::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()));
|
||||
return alertPushParams;
|
||||
}
|
||||
|
||||
private AlertLog getTerminalUserAlertLog(SceneTerminalUserVO sceneTerminalUserVO, Device device, SceneThingsModelItem sceneThingsModelItem) {
|
||||
AlertLog alertLog = new AlertLog();
|
||||
alertLog.setAlertName("设备告警");
|
||||
alertLog.setAlertLevel(1L);
|
||||
alertLog.setSerialNumber(sceneThingsModelItem.getDeviceNumber());
|
||||
alertLog.setProductId(sceneThingsModelItem.getProductId());
|
||||
alertLog.setDeviceName(device.getDeviceName());
|
||||
alertLog.setUserId(sceneTerminalUserVO.getUserId());
|
||||
alertLog.setCreateBy(sceneThingsModelItem.getSceneId().toString());
|
||||
// 统一未处理
|
||||
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());
|
||||
alertLog.setCreateBy(sceneThingsModelItem.getSceneId().toString());
|
||||
return alertLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装告警日志
|
||||
*
|
||||
* @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.setUserId(device.getTenantId());
|
||||
alertLog.setCreateBy(sceneThingsModelItem.getSceneId().toString());
|
||||
// 统一未处理
|
||||
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());
|
||||
alertLog.setCreateBy(sceneThingsModelItem.getSceneId().toString());
|
||||
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, String serialNumber) {
|
||||
if (silent == 0 || sceneId == 0) {
|
||||
return true;
|
||||
}
|
||||
// silent:scene_场景编号
|
||||
String key = "silent:" + "scene_" + sceneId + ":" + serialNumber;
|
||||
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, String serialNumber) {
|
||||
if (silent == 0 || sceneId == 0) {
|
||||
return;
|
||||
}
|
||||
// 更新场景静默时间
|
||||
String key = "silent:" + "scene_" + sceneId + ":" + serialNumber;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.fastbee.mq.service;
|
||||
|
||||
import com.fastbee.common.core.mq.InvokeReqDto;
|
||||
import com.fastbee.mq.model.ReportDataBo;
|
||||
|
||||
/**
|
||||
* 客户端上报数据处理方法集合
|
||||
* @author bill
|
||||
*/
|
||||
public interface IDataHandler {
|
||||
|
||||
/**
|
||||
* 上报属性或功能处理
|
||||
*
|
||||
* @param bo 上报数据模型
|
||||
*/
|
||||
public void reportData(ReportDataBo bo);
|
||||
|
||||
|
||||
/**
|
||||
* 上报事件
|
||||
*
|
||||
* @param bo 上报数据模型
|
||||
*/
|
||||
public void reportEvent(ReportDataBo bo);
|
||||
|
||||
/**
|
||||
* 上报设备信息
|
||||
* @param bo 上报数据模型
|
||||
*/
|
||||
public void reportDevice(ReportDataBo bo);
|
||||
|
||||
/**
|
||||
* 计算场景变量的值
|
||||
* @param id 主键id
|
||||
* @return java.lang.String
|
||||
*/
|
||||
String calculateSceneModelTagValue(Long id);
|
||||
|
||||
/**
|
||||
* 场景变量指令下发
|
||||
* @param reqDto 下发类
|
||||
* @return void
|
||||
*/
|
||||
void invokeSceneModelTagValue(InvokeReqDto reqDto, String messageId);
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.fastbee.mq.service;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReport;
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.model.DeviceStatusVO;
|
||||
import com.fastbee.protocol.base.protocol.IProtocol;
|
||||
|
||||
/**
|
||||
* 处理设备上报数据解析
|
||||
* @author gsb
|
||||
* @date 2022/10/10 13:48
|
||||
*/
|
||||
public interface IDeviceReportMessageService {
|
||||
|
||||
/**
|
||||
* 处理设备主动上报数据
|
||||
* @param bo
|
||||
*/
|
||||
public void parseReportMsg(DeviceReportBo bo);
|
||||
|
||||
/**
|
||||
* 处理设备普通消息回调
|
||||
* @param bo
|
||||
*/
|
||||
public void parseReplyMsg(DeviceReportBo bo);
|
||||
|
||||
/**
|
||||
* 处理设备OTA升级
|
||||
* @param bo
|
||||
*/
|
||||
public void parseOTAUpdateReply(DeviceReportBo bo);
|
||||
|
||||
|
||||
/**
|
||||
* 构建消息
|
||||
* @param bo
|
||||
*/
|
||||
public DeviceStatusVO buildReport(DeviceReportBo bo);
|
||||
|
||||
|
||||
/**
|
||||
* 根据产品id获取协议处理器
|
||||
*/
|
||||
IProtocol selectedProtocol(Long productId);
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.fastbee.mq.service;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceTestReportBo;
|
||||
|
||||
/**
|
||||
* @author bill
|
||||
*/
|
||||
public interface IDeviceTestService {
|
||||
|
||||
public void messageHandler(DeviceTestReportBo testReportBo);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.fastbee.mq.service;
|
||||
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.mq.InvokeReqDto;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备指令下发接口
|
||||
* @author gsb
|
||||
* @date 2022/12/5 11:03
|
||||
*/
|
||||
public interface IFunctionInvoke {
|
||||
|
||||
/**
|
||||
* 服务调用,等待设备响应
|
||||
* @param reqDto 服务下发对象
|
||||
* @return 数据结果
|
||||
*/
|
||||
public AjaxResult invokeReply(InvokeReqDto reqDto);
|
||||
|
||||
/**
|
||||
* 服务调用,设备不响应
|
||||
* @param reqDto 服务下发对象
|
||||
* @return 消息id messageId
|
||||
*/
|
||||
public AjaxResult invokeNoReply(InvokeReqDto reqDto);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.fastbee.mq.service;
|
||||
|
||||
/**
|
||||
* 设备消息推送mq
|
||||
* @author bill
|
||||
*/
|
||||
public interface IMessagePublishService {
|
||||
|
||||
|
||||
/**
|
||||
* 发布消息到mq
|
||||
* @param message 设备消息
|
||||
* @param channel 推送channel
|
||||
*/
|
||||
public void publish(Object message,String channel);
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.fastbee.mq.service;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import com.fastbee.common.core.mq.message.DeviceDownMessage;
|
||||
import com.fastbee.common.core.mq.message.FunctionCallBackBo;
|
||||
import com.fastbee.common.core.mq.ota.OtaUpgradeBo;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.common.enums.TopicType;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.mq.model.ReportDataBo;
|
||||
import org.checkerframework.checker.units.qual.C;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IMqttMessagePublish {
|
||||
|
||||
|
||||
/**
|
||||
* 服务(指令)下发
|
||||
*/
|
||||
public void funcSend(MQSendMessageBo bo);
|
||||
|
||||
/**
|
||||
* OTA升级下发
|
||||
*/
|
||||
public void upGradeOTA(OtaUpgradeBo bo);
|
||||
|
||||
/**
|
||||
* 下发数据编码
|
||||
*/
|
||||
FunctionCallBackBo buildMessage(MQSendMessageBo bo);
|
||||
|
||||
/**
|
||||
* 1.发布设备状态
|
||||
*/
|
||||
public void publishStatus(Long productId, String deviceNum, int deviceStatus, int isShadow, int rssi);
|
||||
|
||||
|
||||
/**
|
||||
* 2.发布设备信息
|
||||
*/
|
||||
public void publishInfo(Long productId, String deviceNum);
|
||||
|
||||
|
||||
/**
|
||||
* 3.发布时钟同步信息
|
||||
*
|
||||
* @param bo 数据模型
|
||||
*/
|
||||
public void publishNtp(ReportDataBo bo);
|
||||
|
||||
|
||||
/**
|
||||
* 4.发布属性
|
||||
* delay 延时,秒为单位
|
||||
*/
|
||||
public void publishProperty(Long productId, String deviceNum, List<ThingsModelSimpleItem> thingsList, int delay);
|
||||
|
||||
|
||||
/**
|
||||
* 5.发布功能
|
||||
* delay 延时,秒为单位
|
||||
*/
|
||||
public void publishFunction(Long productId, String deviceNum, List<ThingsModelSimpleItem> thingsList, int delay);
|
||||
|
||||
/**
|
||||
* 设备数据同步
|
||||
*
|
||||
* @param deviceNumber 设备编号
|
||||
* @return 设备
|
||||
*/
|
||||
public Device deviceSynchronization(String deviceNumber);
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.fastbee.mq.service;
|
||||
|
||||
import com.fastbee.mq.model.ReportDataBo;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 规则引擎处理数据方法集合
|
||||
* @author bill
|
||||
*/
|
||||
public interface IRuleEngine {
|
||||
|
||||
|
||||
/**
|
||||
* 规则匹配(告警和场景联动)
|
||||
*
|
||||
* @param bo 上报数据模型
|
||||
*/
|
||||
public void ruleMatch(ReportDataBo bo);
|
||||
public void addCheckTask(String key, ScheduledExecutorService task);
|
||||
public void removeCheckTask(String key);
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package com.fastbee.mq.service.impl;
|
||||
|
||||
import com.fastbee.common.core.mq.DeviceReportBo;
|
||||
import com.fastbee.common.enums.TopicType;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.gateway.mq.TopicsUtils;
|
||||
import com.fastbee.mq.model.ReportDataBo;
|
||||
import com.fastbee.mq.service.IDataHandler;
|
||||
import com.fastbee.mq.service.IMqttMessagePublish;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2023/2/27 14:42
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DeviceOtherMsgHandler {
|
||||
|
||||
@Resource
|
||||
private TopicsUtils topicsUtils;
|
||||
@Resource
|
||||
private IDataHandler dataHandler;
|
||||
@Resource
|
||||
private IMqttMessagePublish messagePublish;
|
||||
|
||||
/**
|
||||
* true: 使用netty搭建的mqttBroker false: 使用emq
|
||||
*/
|
||||
@Value("${server.broker.enabled}")
|
||||
private Boolean enabled;
|
||||
|
||||
|
||||
/**
|
||||
* 非属性消息消息处理入口
|
||||
*
|
||||
* @param bo
|
||||
*/
|
||||
public void messageHandler(DeviceReportBo bo) {
|
||||
String type = "";
|
||||
String name = topicsUtils.parseTopicName(bo.getTopicName());
|
||||
if (StringUtils.isEmpty(name) || name.endsWith(TopicType.FUNCTION_GET.getTopicSuffix())) return;
|
||||
ReportDataBo data = this.buildReportData(bo);
|
||||
TopicType topicType = TopicType.getType(name);
|
||||
switch (topicType) {
|
||||
case INFO_POST:
|
||||
dataHandler.reportDevice(data);
|
||||
break;
|
||||
case NTP_POST:
|
||||
messagePublish.publishNtp(data);
|
||||
break;
|
||||
case FUNCTION_POST:
|
||||
data.setShadow(false);
|
||||
data.setType(2);
|
||||
data.setRuleEngine(true);
|
||||
dataHandler.reportData(data);
|
||||
break;
|
||||
case EVENT_POST:
|
||||
data.setType(3);
|
||||
data.setRuleEngine(true);
|
||||
dataHandler.reportEvent(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装数据
|
||||
*/
|
||||
private ReportDataBo buildReportData(DeviceReportBo bo) {
|
||||
String message = new String(bo.getData());
|
||||
log.info("收到设备信息[{}]", message);
|
||||
Long productId = topicsUtils.parseProductId(bo.getTopicName());
|
||||
String serialNumber = topicsUtils.parseSerialNumber(bo.getTopicName());
|
||||
ReportDataBo dataBo = new ReportDataBo();
|
||||
dataBo.setMessage(message);
|
||||
dataBo.setProductId(productId);
|
||||
dataBo.setSerialNumber(serialNumber);
|
||||
dataBo.setRuleEngine(false);
|
||||
return dataBo;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package com.fastbee.mq.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.common.core.mq.DeviceReplyBo;
|
||||
import com.fastbee.common.core.mq.InvokeReqDto;
|
||||
import com.fastbee.common.core.mq.MQSendMessageBo;
|
||||
import com.fastbee.common.core.mq.MessageReplyBo;
|
||||
import com.fastbee.common.core.protocol.modbus.ModbusCode;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.core.redis.RedisKeyBuilder;
|
||||
import com.fastbee.common.enums.FunctionReplyStatus;
|
||||
import com.fastbee.common.enums.ResultCode;
|
||||
import com.fastbee.common.enums.ThingsModelType;
|
||||
import com.fastbee.common.enums.scenemodel.SceneModelVariableTypeEnum;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.common.utils.MessageUtils;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.bean.BeanUtils;
|
||||
import com.fastbee.iot.cache.ITSLCache;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.domain.FunctionLog;
|
||||
import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import com.fastbee.iot.service.IFunctionLogService;
|
||||
import com.fastbee.iot.service.IOrderControlService;
|
||||
import com.fastbee.iot.util.SnowflakeIdWorker;
|
||||
import com.fastbee.mq.redischannel.producer.MessageProducer;
|
||||
import com.fastbee.mq.service.IDataHandler;
|
||||
import com.fastbee.mq.service.IFunctionInvoke;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.weaver.loadtime.Aj;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2022/12/5 11:34
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FunctionInvokeImpl implements IFunctionInvoke {
|
||||
|
||||
@Resource
|
||||
private IDataHandler dataHandler;
|
||||
@Resource
|
||||
private IFunctionLogService functionLogService;
|
||||
private SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(2);
|
||||
|
||||
/**
|
||||
* 服务调用,等待设备响应
|
||||
* @param reqDto 服务下发对象
|
||||
* @return 数据结果
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult invokeReply(InvokeReqDto reqDto){
|
||||
AjaxResult ajaxResult = invokeNoReply(reqDto);
|
||||
String messageId = ajaxResult.get("msg")+"";
|
||||
int status = this.queryData(messageId);
|
||||
ajaxResult.put("code", status);
|
||||
if (status == FunctionReplyStatus.UNKNOWN.getCode()) {
|
||||
ajaxResult.put("msg", FunctionReplyStatus.UNKNOWN.getMessage());
|
||||
this.updateFunctionLog(messageId);
|
||||
}else {
|
||||
ajaxResult.put("msg",FunctionReplyStatus.SUCCESS.getMessage());
|
||||
}
|
||||
return ajaxResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务调用,设备不响应
|
||||
* @param reqDto 服务下发对象
|
||||
* @return 消息id messageId
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult invokeNoReply(InvokeReqDto reqDto){
|
||||
log.debug("=>下发指令请求:[{}]",reqDto);
|
||||
if (null != reqDto.getSceneModelId() && null != reqDto.getVariableType() && !SceneModelVariableTypeEnum.THINGS_MODEL.getType().equals(reqDto.getVariableType())) {
|
||||
String messageId = snowflakeIdWorker.nextId() + "";
|
||||
dataHandler.invokeSceneModelTagValue(reqDto, messageId);
|
||||
return AjaxResult.success(messageId);
|
||||
}
|
||||
if (StringUtils.isEmpty(reqDto.getSerialNumber())) {
|
||||
return AjaxResult.error("设备编号不能为空");
|
||||
}
|
||||
MQSendMessageBo bo = new MQSendMessageBo();
|
||||
BeanUtils.copyBeanProp(bo,reqDto);
|
||||
long id = snowflakeIdWorker.nextId();
|
||||
String messageId = id+"";
|
||||
bo.setMessageId(messageId+"");
|
||||
bo.setUserId(SecurityUtils.getUserId());
|
||||
MessageProducer.sendFunctionInvoke(bo);
|
||||
return AjaxResult.success(messageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询5S拿返回值
|
||||
* @param messageId 消息id
|
||||
* @return 回调对象
|
||||
*/
|
||||
private int queryData(String messageId) {
|
||||
FunctionLog functionLog = null;
|
||||
int status = 204;
|
||||
long startTime = DateUtils.getTimestamp();
|
||||
/**5秒轮询拿值*/
|
||||
while ((DateUtils.getTimestamp() - startTime) < 5 * 1000) {
|
||||
try {
|
||||
functionLog = functionLogService.selectLogByMessageId(messageId);
|
||||
if (null != functionLog && functionLog.getResultCode() == FunctionReplyStatus.SUCCESS.getCode()) {
|
||||
status = FunctionReplyStatus.SUCCESS.getCode();
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
log.error("=>读取设备下发指定回执异常", e);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备下发指令,当设备超时未回复情况
|
||||
* @param messageId
|
||||
*/
|
||||
private void updateFunctionLog(String messageId){
|
||||
FunctionLog functionLog = new FunctionLog();
|
||||
functionLog.setResultCode(FunctionReplyStatus.UNKNOWN.getCode());
|
||||
functionLog.setResultMsg(FunctionReplyStatus.UNKNOWN.getMessage());
|
||||
functionLog.setReplyTime(DateUtils.getNowDate());
|
||||
functionLog.setMessageId(messageId);
|
||||
functionLogService.updateByMessageId(functionLog);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.fastbee.mq.service.impl;
|
||||
|
||||
import com.fastbee.base.session.Session;
|
||||
import com.fastbee.base.session.SessionManager;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.protocol.Message;
|
||||
import com.fastbee.common.exception.ServiceException;
|
||||
import com.fastbee.modbus.model.ModbusRtu;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* @author gsb
|
||||
* @date 2022/11/22 10:30
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MessageManager {
|
||||
|
||||
private static final Mono<Void> NEVER = Mono.never();
|
||||
private static final Mono OFFLINE_EXCEPTION = Mono.error( new ServiceException("离线的客户端",4000));
|
||||
private static final Mono OFFLINE_RESULT = Mono.just(new AjaxResult(4000, "离线的客户端"));
|
||||
private static final Mono SEND_FAIL_RESULT = Mono.just(new AjaxResult(4001, "消息发送失败"));
|
||||
|
||||
private SessionManager sessionManager;
|
||||
|
||||
public MessageManager(SessionManager sessionManager){
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
public Mono<Void> notifyR(String sessionId, ModbusRtu request){
|
||||
Session session = sessionManager.getSession(sessionId);
|
||||
if (session == null){
|
||||
return OFFLINE_EXCEPTION;
|
||||
}
|
||||
return session.notify(request);
|
||||
}
|
||||
|
||||
public <T> Mono<AjaxResult> requestR(String sessionId, Message request, Class<T> responseClass){
|
||||
Session session = sessionManager.getSession(sessionId);
|
||||
if (session == null){
|
||||
return OFFLINE_RESULT;
|
||||
}
|
||||
return session.request(request,responseClass)
|
||||
.map(message -> AjaxResult.success(message))
|
||||
.onErrorResume(e ->{
|
||||
log.warn("消息发送失败:{}",e);
|
||||
return SEND_FAIL_RESULT;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 下发指令等待回复
|
||||
* @param sessionId
|
||||
* @return
|
||||
*/
|
||||
public <T> Mono<T> request(String sessionId, ModbusRtu request, Class<T> responseClass, long timeout){
|
||||
return request(sessionId,request,responseClass).timeout(Duration.ofMillis(timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下发指令,不等待回复
|
||||
* @param sessionId
|
||||
* @return
|
||||
*/
|
||||
public <T> Mono<T> request(String sessionId, ModbusRtu request, Class<T> responseClass){
|
||||
Session session = sessionManager.getSession(sessionId);
|
||||
if (session == null){
|
||||
return OFFLINE_EXCEPTION;
|
||||
}
|
||||
return session.request(request,responseClass);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package com.fastbee.mq.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.iot.domain.*;
|
||||
import com.fastbee.iot.mapper.*;
|
||||
import com.fastbee.iot.model.ScriptCondition;
|
||||
import com.fastbee.iot.service.IScriptService;
|
||||
import com.fastbee.mq.model.ReportDataBo;
|
||||
import com.fastbee.mq.service.IRuleEngine;
|
||||
import com.fastbee.mq.ruleEngine.SceneContext;
|
||||
import com.fastbee.ruleEngine.core.FlowLogExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import static java.util.regex.Pattern.compile;
|
||||
|
||||
/**
|
||||
* 规则引擎处理数据方法
|
||||
*
|
||||
* @author bill
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RuleEngineHandler implements IRuleEngine {
|
||||
|
||||
@Resource
|
||||
private SceneDeviceMapper sceneDeviceMapper;
|
||||
|
||||
@Resource
|
||||
private IScriptService scriptService;
|
||||
|
||||
@Autowired
|
||||
private FlowLogExecutor flowExecutor;
|
||||
|
||||
private static final Map<String, ScheduledExecutorService> CEHCK_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 规则匹配(告警和场景联动)
|
||||
*
|
||||
* @param bo 上报数据模型bo
|
||||
* @see ReportDataBo
|
||||
*/
|
||||
public void ruleMatch(ReportDataBo bo) {
|
||||
try {
|
||||
// 场景联动处理
|
||||
this.sceneProcess(bo);
|
||||
} catch (Exception e) {
|
||||
log.error("接收数据,解析数据时异常 message={}", e, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景规则处理
|
||||
*/
|
||||
public void sceneProcess(ReportDataBo bo) throws ExecutionException, InterruptedException {
|
||||
// 查询设备关联的场景
|
||||
SceneDevice sceneDeviceParam = new SceneDevice();
|
||||
sceneDeviceParam.setProductId(bo.getProductId());
|
||||
sceneDeviceParam.setSerialNumber(bo.getSerialNumber());
|
||||
List<Scene> sceneList = sceneDeviceMapper.selectTriggerDeviceRelateScenes(sceneDeviceParam);
|
||||
|
||||
int type = bo.getType();
|
||||
// 获取上报的物模型
|
||||
List<ThingsModelSimpleItem> thingsModelSimpleItems = bo.getDataList();
|
||||
if (CollectionUtils.isEmpty(bo.getDataList())) {
|
||||
thingsModelSimpleItems = JSON.parseArray(bo.getMessage(), ThingsModelSimpleItem.class);
|
||||
}
|
||||
// 执行场景规则,异步非阻塞
|
||||
for (Scene scene : sceneList) {
|
||||
SceneContext context = new SceneContext(bo.getSerialNumber(), bo.getProductId(), type, thingsModelSimpleItems);
|
||||
ScriptCondition scriptCondition = ScriptCondition.builder().sceneId(scene.getSceneId()).build();
|
||||
String requestId = scene.getChainName();
|
||||
List<Script> list = scriptService.selectExecRuleScriptList(scriptCondition);
|
||||
for(Script script : list) {
|
||||
requestId = requestId + "/" + script.getScriptId();
|
||||
}
|
||||
flowExecutor.execute2FutureWithRid(String.valueOf(scene.getChainName()), null, requestId, context);
|
||||
}
|
||||
}
|
||||
|
||||
public void addCheckTask(String key, ScheduledExecutorService task) {
|
||||
ScheduledExecutorService taskold = CEHCK_CACHE.get(key);
|
||||
if (taskold != null) {
|
||||
taskold.shutdown();
|
||||
}
|
||||
CEHCK_CACHE.put(key, task);
|
||||
}
|
||||
|
||||
public void removeCheckTask(String key) {
|
||||
ScheduledExecutorService taskold = CEHCK_CACHE.get(key);
|
||||
if (taskold != null) {
|
||||
taskold.shutdown();
|
||||
CEHCK_CACHE.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user