This commit is contained in:
2026-08-05 13:01:59 +08:00
parent 36fd688d17
commit e5623fefb9
26 changed files with 1940 additions and 109 deletions

View File

@@ -1,7 +1,7 @@
package com.maibu.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.maibu.core.business.DeviceModel;
import com.maibu.mybatis.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper;
/**
@@ -11,7 +11,7 @@ import org.apache.ibatis.annotations.Mapper;
* @Entity com.maibu.core.business.DeviceModel;
*/
@Mapper
public interface SysDeviceModelMapper extends BaseMapper<DeviceModel> {
public interface SysDeviceModelMapper extends BaseMapperX<DeviceModel> {
}

View File

@@ -1 +1 @@
package com.maibu.mapper;
package com.maibu.mapper;

View File

@@ -2,25 +2,37 @@ package com.maibu.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.maibu.constant.Constant;
import com.maibu.core.business.DeviceModelPropertyMapping;
import com.maibu.core.business.DevicePlanTask;
import com.maibu.core.business.IoTCommonDevice;
import com.maibu.core.business.Product;
import com.maibu.core.business.device.NettyDevice;
import com.maibu.core.business.dto.DeviceConfigDTO;
import com.maibu.core.enums.ConnectType;
import com.maibu.mapper.IotDeviceCommonMapper;
import com.maibu.mapper.SysDeviceModelPropertyMappingMapper;
import com.maibu.memory.GlobalMemory;
import com.maibu.memory.SiteMemory;
import com.maibu.mqtt.*;
import com.maibu.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -38,6 +50,17 @@ public class IotDeviceCommonService {
@Autowired
private IotDeviceCommonMapper iotDeviceCommonMapper;
@Autowired
private SysDeviceModelPropertyMappingMapper sysDeviceModelPropertyMappingMapper;
public void save(IoTCommonDevice ioTCommonDevice) throws Exception {
iotDeviceCommonMapper.saveOrUpdate(ioTCommonDevice);
SiteMemory siteMemory = GlobalMemory.getSiteMemory(ioTCommonDevice.getOrgId(), ioTCommonDevice.getSiteId());
if (siteMemory != null) {
siteMemory.saveCommonDevice(ioTCommonDevice);
}
}
public void addDeviceToUnRegisterCache(DeviceConfigDTO deviceDTO) throws Exception {
// 1. 检查 SN 是否已存在(缓存 + 数据库)
if (GlobalMemory.unRegisterCommonDeviceMap.containsKey(deviceDTO.getSn())) {
@@ -68,8 +91,7 @@ public class IotDeviceCommonService {
device.setDelFlag(false);
device.setCreateTime(LocalDateTime.now());
// 存入未注册缓存(key 用 SN)
GlobalMemory.unRegisterCommonDeviceMap.put(deviceDTO.getSn(), device);
log.info("设备 {} 已加入未注册缓存", deviceDTO.getSn());
}
@@ -109,10 +131,16 @@ public class IotDeviceCommonService {
}
@Transactional(rollbackFor = Exception.class)
public void registerDevice(Long deviceId, Long orgId, Long siteId) {
// TODO: 从未注册缓存取出设备,绑定组织/场站,落库
// 根据具体业务实现
public void registerDevice(IoTCommonDevice device) {
device.setHasRegister(true);
device.setUpdateTime(LocalDateTime.now());
device.setUpdateBy(SecurityUtils.getUsername());
iotDeviceCommonMapper.saveOrUpdate(device);
SiteMemory siteMemory = GlobalMemory.getSiteMemory(device.getOrgId(), device.getSiteId());
if (siteMemory != null) {
siteMemory.saveCommonDevice(device);
}
GlobalMemory.unRegisterCommonDeviceMap.remove(device.getSn());
}
@@ -237,23 +265,77 @@ public class IotDeviceCommonService {
}
// 3. 按 productId 分组,缓存映射配置避免重复查询
public static Map<Long, List<DeviceModelPropertyMapping>> mappingCache = new ConcurrentHashMap<>();
public void commonDeviceMonitor() {
//prepare 进执行execute的逻辑
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(() -> {
try {
//先初始化 外部设备连接
GlobalMemory.getAllSiteMemory().forEach(x -> {
List<IoTCommonDevice> list = x.getAllCommonDevice();
if (!CollectionUtils.isEmpty(list)) {
list.forEach(y -> {
if (ConnectType.MQTT.equals(y.getConnectType())) {
String ip = y.getNetworkIp();
Integer port = y.getPort();
}
});
GlobalMemory.getAllSiteMemory().forEach(siteMemory -> {
CustomMqttDeviceMonitor monitor = siteMemory.getCustomMqttDeviceMonitor();
if (monitor == null) {
// 1. 创建 MQTT 监控器和消息处理器
monitor = new CustomMqttDeviceMonitor();
monitor.registerHandler(new GenericPropertyMessageHandler());
monitor.registerHandler(new ActionMessageHandler());
siteMemory.setCustomMqttDeviceMonitor(monitor);
}
// 2. 查询所有外部设备
List<IoTCommonDevice> deviceList = siteMemory.getAllCommonDevice();
if (CollectionUtils.isEmpty(deviceList)) {
return;
}
for (IoTCommonDevice device : deviceList) {
if (!ConnectType.MQTT.equals(device.getConnectType())) {
continue;
}
Long productId = device.getProductId();
// 查询产品属性映射(缓存避免同一产品多次查询)
List<DeviceModelPropertyMapping> mappings = mappingCache.computeIfAbsent(productId, pid -> {
LambdaQueryWrapper<DeviceModelPropertyMapping> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DeviceModelPropertyMapping::getProductId, pid);
return sysDeviceModelPropertyMappingMapper.selectList(wrapper);
});
// 构建映射配置
PropertyMappingConfig mappingConfig = buildMappingConfig(mappings);
// 注册到处理器,用 productId 区分不同产品的映射
String handlerType = "property-" + productId;
GenericPropertyMessageHandler handler = (GenericPropertyMessageHandler) monitor.getMessageHandler(handlerType);
handler.registerMappingConfig(handlerType, mappingConfig);
// 获取产品信息
Product product = siteMemory.getProduct(productId);
if (product == null || !StringUtils.hasText(product.getSubscribeTopic())) {
log.warn("设备 {} 的产品 {} 未配置订阅主题,跳过", device.getSn(), productId);
continue;
}
// 注册设备到 MQTT 监控器
CustomMqttDeviceConfig deviceConfig = new CustomMqttDeviceConfig();
deviceConfig.setDeviceKey(device.getSn());
deviceConfig.setBrokerUrl(device.getNetworkIp());
deviceConfig.setClientId(Constant.mqttClientPrefix + device.getSn());
deviceConfig.setUsername(device.getUsername());
deviceConfig.setPassword(device.getPassword());
deviceConfig.setSubscribeTopics(Arrays.asList(product.getSubscribeTopic()));
deviceConfig.setHandlerType(handlerType);
monitor.registerDevice(deviceConfig);
}
siteMemory.setCustomMqttDeviceMonitor(monitor);
monitor.start();
});
} catch (Exception e) {
log.error("执行任务execute线程错误", e);
@@ -262,4 +344,63 @@ public class IotDeviceCommonService {
}
/**
* 根据数据库映射列表构建 PropertyMappingConfig
*
* <p>将 {@link DeviceModelPropertyMapping} 转换为 {@link PropertyMappingConfig.FieldMapping}:</p>
* <ul>
* <li>sourceKey → FieldMapping.source(外部 JSON 中的字段名)</li>
* <li>standardKey → FieldMapping.target(统一后的内部属性名)</li>
* <li>dataType → FieldMapping.valueType(自动类型转换)</li>
* </ul>
*/
private PropertyMappingConfig buildMappingConfig(List<DeviceModelPropertyMapping> mappings) {
PropertyMappingConfig config = new PropertyMappingConfig();
config.setParserType(PropertyMappingConfig.ParserType.FLAT);
if (CollectionUtils.isEmpty(mappings)) {
return config;
}
List<PropertyMappingConfig.FieldMapping> fieldMappings = new ArrayList<>();
for (DeviceModelPropertyMapping mapping : mappings) {
if (!StringUtils.hasText(mapping.getSourceKey()) || !StringUtils.hasText(mapping.getStandardKey())) {
continue;
}
PropertyMappingConfig.FieldMapping fieldMapping = PropertyMappingConfig.FieldMapping
.of(mapping.getSourceKey(), mapping.getStandardKey(), convertValueType(mapping.getDataType()));
fieldMappings.add(fieldMapping);
}
config.setMappings(fieldMappings);
return config;
}
/**
* 将数据库 dataType 转换为 ValueType
*/
private PropertyMappingConfig.ValueType convertValueType(String dataType) {
if (!StringUtils.hasText(dataType)) {
return PropertyMappingConfig.ValueType.AUTO;
}
switch (dataType.toLowerCase()) {
case "string":
case "varchar":
case "text":
return PropertyMappingConfig.ValueType.STRING;
case "int":
case "integer":
case "long":
case "float":
case "double":
case "number":
case "decimal":
return PropertyMappingConfig.ValueType.NUMBER;
case "boolean":
case "bool":
return PropertyMappingConfig.ValueType.BOOLEAN;
default:
return PropertyMappingConfig.ValueType.AUTO;
}
}
}