package com.ruoyi.device.service.impl; import com.ruoyi.device.domain.api.IAircraftDomain; import com.ruoyi.device.domain.api.IDeviceDomain; import com.ruoyi.device.domain.api.IDockAircraftDomain; import com.ruoyi.device.domain.api.IDockDomain; import com.ruoyi.device.domain.api.IThingsBoardDomain; import com.ruoyi.device.domain.model.Aircraft; import com.ruoyi.device.domain.model.Device; import com.ruoyi.device.domain.model.Dock; import com.ruoyi.device.domain.model.DockAircraft; import com.ruoyi.device.domain.model.thingsboard.AttributeMap; import com.ruoyi.device.domain.model.thingsboard.DeviceInfo; import com.ruoyi.device.domain.model.thingsboard.constants.DeviceAttributes; import com.ruoyi.device.service.enums.DeviceType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; @Service public class SynService { private static final Logger log = LoggerFactory.getLogger(SynService.class); private final IThingsBoardDomain iThingsBoardDomain; /** * 设备名称过滤正则表达式列表(从配置文件读取) * 匹配这些正则表达式的设备将被跳过,不进行同步 */ @Value("${device.sync.exclude-patterns:}") private String excludePatterns; /** * 编译后的正则表达式模式列表(延迟初始化) */ private List excludePatternList; @Autowired private IDeviceDomain deviceDomain; @Autowired private IDockDomain dockDomain; @Autowired private IAircraftDomain aircraftDomain; @Autowired private IDockAircraftDomain dockAircraftDomain; public SynService(IThingsBoardDomain iThingsBoardDomain) { this.iThingsBoardDomain = iThingsBoardDomain; } /** * 初始化设备名称过滤正则表达式列表 * 延迟初始化,在第一次使用时编译正则表达式 */ private void initExcludePatterns() { if (excludePatternList == null) { excludePatternList = new ArrayList<>(); if (StringUtils.hasText(excludePatterns)) { String[] patterns = excludePatterns.split(","); for (String pattern : patterns) { String trimmedPattern = pattern.trim(); if (StringUtils.hasText(trimmedPattern)) { try { excludePatternList.add(Pattern.compile(trimmedPattern)); log.info("加载设备名称过滤规则: {}", trimmedPattern); } catch (Exception e) { log.error("无效的正则表达式: {}, error={}", trimmedPattern, e.getMessage()); } } } } if (excludePatternList.isEmpty()) { log.info("未配置设备名称过滤规则"); } } } /** * 判断设备名称是否应该被过滤(跳过同步) * * @param deviceName 设备名称 * @return true 表示应该被过滤,false 表示不过滤 */ private boolean shouldExcludeDevice(String deviceName) { // 延迟初始化 initExcludePatterns(); // 如果没有配置过滤规则,不过滤任何设备 if (excludePatternList.isEmpty()) { return false; } // 检查设备名称是否匹配任何过滤规则 for (Pattern pattern : excludePatternList) { if (pattern.matcher(deviceName).matches()) { log.debug("设备 {} 匹配过滤规则 {},跳过同步", deviceName, pattern.pattern()); return true; } } return false; } /** * 定时任务:同步基础表数据 * 执行时间:启动后1分钟开始,每2分钟执行一次(可通过配置文件修改) * 配置项:device.schedule.print-devices.initial-delay 初始延迟时间(毫秒) * device.schedule.print-devices.fixed-delay 执行间隔时间(毫秒) */ @Scheduled(initialDelayString = "${device.schedule.update-devices.initial-delay:60000}", fixedDelayString = "${device.schedule.update-devices.fixed-delay:120000}") public void updateDevicesScheduled() { try { log.info("========== 开始执行定时任务:同步基础表数据 =========="); Iterable> allDevices = iThingsBoardDomain.getAllDevices(); int totalCount = 0; for (List deviceBatch : allDevices) { for (DeviceInfo deviceInfo : deviceBatch) { // 通过设备名字,过滤出不需要同步的一些设备,比如 // 设备名字是 THJSQ03A2302FAWQZBJ1 和 THJSQ03A23045BLPGYC5 // 支持配置简单的正则表达式;该正则表达式需要维护在配置文件中 // 检查设备名称是否应该被过滤 if (shouldExcludeDevice(deviceInfo.getName())) { log.info("设备 {} 匹配过滤规则,跳过同步", deviceInfo.getName()); continue; } try { // 获取设备属性 AttributeMap attributes = iThingsBoardDomain.getDeviceAttributes(deviceInfo.getId()); // 判断设备类型 DeviceType deviceType = determineDeviceType(deviceInfo, attributes); // 同步设备表 Long deviceId = syncDevice(deviceInfo, attributes, deviceType); // 根据设备类型进行不同的处理 if (deviceType == DeviceType.DOCK) { // 机场:同步机场表 syncDock(deviceId, deviceInfo.getName()); // 获取机场挂载的无人机SN号 Optional subDeviceSnOpt = attributes.get(DeviceAttributes.SUB_DEVICE_SN); if (subDeviceSnOpt.isPresent() && StringUtils.hasText(subDeviceSnOpt.get())) { String aircraftSn = subDeviceSnOpt.get(); // 通过SN号查找无人机设备 Device aircraftDevice = findDeviceBySn(aircraftSn); if (aircraftDevice != null) { // 同步机场无人机关联 syncDockAircraft(deviceId, aircraftDevice.getDeviceId()); } } } else if (deviceType == DeviceType.AIRCRAFT) { // 无人机:同步无人机表 syncAircraft(deviceId, deviceInfo.getName()); } // 网关类型不需要额外处理 totalCount++; } catch (Exception e) { log.error("同步设备失败: deviceId={}, deviceName={}, error={}", deviceInfo.getId(), deviceInfo.getName(), e.getMessage(), e); } } } log.info("========== 数据同步任务完成,共同步 {} 个设备 ==========", totalCount); } catch (Exception e) { log.error("数据同步任务执行失败: {}", e.getMessage(), e); } } /** * 判断设备类型 * 优化后的判断逻辑: * 1. 优先使用 ThingsBoard 标准的 additionalInfo.gateway 字段判断网关 * 2. 对于非网关设备,通过 dock_sn 属性区分机场和无人机 * * @param deviceInfo ThingsBoard设备信息 * @param attributes 设备属性 * @return 设备类型 */ private DeviceType determineDeviceType(DeviceInfo deviceInfo, AttributeMap attributes) { String deviceName = deviceInfo.getName(); // 1. 使用 ThingsBoard 标准方式判断网关:检查 additionalInfo 中的 gateway 字段 if (deviceInfo.isGateway()) { return DeviceType.GATEWAY; } // 2. 非网关设备:通过 dock_sn 属性区分机场和无人机 Optional dockSnOpt = attributes.get(DeviceAttributes.DOCK_SN); if (dockSnOpt.isPresent() && StringUtils.hasText(dockSnOpt.get())) { String dockSn = dockSnOpt.get(); // dock_sn 等于设备名称 -> 机场 // dock_sn 不等于设备名称 -> 无人机(挂载在该机场下) return deviceName.equals(dockSn) ? DeviceType.DOCK : DeviceType.AIRCRAFT; } // dock_sn 属性不存在或为空,无法判断设备类型 throw new IllegalStateException("无法确定设备类型:设备 " + deviceName + " 缺少 dock_sn 属性"); } /** * 同步设备数据 * * @param deviceInfo ThingsBoard设备信息 * @param attributes 设备属性 * @param deviceType 设备类型 * @return 设备主键ID */ private Long syncDevice(DeviceInfo deviceInfo, AttributeMap attributes, DeviceType deviceType) { String iotDeviceId = deviceInfo.getId(); String deviceName = deviceInfo.getName(); // 使用 ThingsBoard 标准方式获取网关设备ID(通过 EntityRelation) String gateway = iThingsBoardDomain.getDeviceGatewayId(iotDeviceId); String deviceSn = deviceName; // 使用设备名称作为SN号,网关其实是没有SN号的 // 查询设备是否已存在 Device existingDevice = deviceDomain.selectDeviceByIotDeviceId(iotDeviceId); if (existingDevice == null) { // 设备不存在,插入新设备 Device newDevice = new Device(); newDevice.setDeviceName(deviceName); newDevice.setIotDeviceId(iotDeviceId); newDevice.setDeviceType(deviceType.getCode()); newDevice.setDeviceSn(deviceSn); newDevice.setGateway(gateway); newDevice.setCreateBy("system"); deviceDomain.insertDevice(newDevice); log.info("插入新设备: iotDeviceId={}, deviceName={}, deviceType={}", iotDeviceId, deviceName, deviceType); return newDevice.getDeviceId(); } else { // 设备已存在,检查是否需要更新 boolean needUpdate = false; if (!Objects.equals(existingDevice.getDeviceName(), deviceName)) { existingDevice.setDeviceName(deviceName); needUpdate = true; } if (!Objects.equals(existingDevice.getDeviceType(), deviceType.getCode())) { existingDevice.setDeviceType(deviceType.getCode()); needUpdate = true; } if (!Objects.equals(existingDevice.getDeviceSn(), deviceSn)) { existingDevice.setDeviceSn(deviceSn); needUpdate = true; } if (!Objects.equals(existingDevice.getGateway(), gateway)) { existingDevice.setGateway(gateway); needUpdate = true; } if (needUpdate) { existingDevice.setUpdateBy("system"); deviceDomain.updateDevice(existingDevice); log.info("更新设备: iotDeviceId={}, deviceName={}, deviceType={}", iotDeviceId, deviceName, deviceType); } return existingDevice.getDeviceId(); } } /** * 同步机场数据 * * @param deviceId 设备主键ID * @param deviceName 设备名称 */ private void syncDock(Long deviceId, String deviceName) { // 查询机场是否已存在 Dock existingDock = dockDomain.selectDockByDeviceId(deviceId); if (existingDock == null) { // 机场不存在,插入新机场 Dock newDock = new Dock(); newDock.setDockName(deviceName); newDock.setDeviceId(deviceId); newDock.setCreateBy("system"); dockDomain.insertDock(newDock); log.info("插入新机场: deviceId={}, dockName={}", deviceId, deviceName); } // 机场已存在,无需更新 } /** * 同步无人机数据 * * @param deviceId 设备主键ID * @param deviceName 设备名称 * @return 无人机主键ID */ private Long syncAircraft(Long deviceId, String deviceName) { // 查询无人机是否已存在 Aircraft existingAircraft = aircraftDomain.selectAircraftByDeviceId(deviceId); if (existingAircraft == null) { // 无人机不存在,插入新无人机 Aircraft newAircraft = new Aircraft(); newAircraft.setAircraftName(deviceName); newAircraft.setDeviceId(deviceId); newAircraft.setCreateBy("system"); aircraftDomain.insertAircraft(newAircraft); log.info("插入新无人机: deviceId={}, aircraftName={}", deviceId, deviceName); return newAircraft.getAircraftId(); } // 无人机已存在,无需更新 return existingAircraft.getAircraftId(); } /** * 根据设备SN号查找设备 * * @param deviceSn 设备SN号 * @return 设备信息 */ private Device findDeviceBySn(String deviceSn) { Device queryDevice = new Device(); queryDevice.setDeviceSn(deviceSn); List devices = deviceDomain.selectDeviceList(queryDevice); return (devices != null && !devices.isEmpty()) ? devices.get(0) : null; } /** * 同步机场无人机关联数据 * 按照一个机场只会挂载一台无人机的逻辑进行处理 * * @param dockDeviceId 机场设备主键ID * @param aircraftDeviceId 无人机设备主键ID */ private void syncDockAircraft(Long dockDeviceId, Long aircraftDeviceId) { // 获取机场主键 Dock dock = dockDomain.selectDockByDeviceId(dockDeviceId); if (dock == null) { log.warn("机场不存在,无法同步机场无人机关联: dockDeviceId={}", dockDeviceId); return; } // 获取无人机主键 Aircraft aircraft = aircraftDomain.selectAircraftByDeviceId(aircraftDeviceId); if (aircraft == null) { log.warn("无人机不存在,无法同步机场无人机关联: aircraftDeviceId={}", aircraftDeviceId); return; } Long dockId = dock.getDockId(); Long aircraftId = aircraft.getAircraftId(); // 查询该机场是否已有关联 List existingRelations = dockAircraftDomain.selectDockAircraftByDockId(dockId); if (existingRelations == null || existingRelations.isEmpty()) { // 机场没有关联,插入新关联 DockAircraft newRelation = new DockAircraft(); newRelation.setDockId(dockId); newRelation.setAircraftId(aircraftId); newRelation.setCreateBy("system"); dockAircraftDomain.insertDockAircraft(newRelation); log.info("插入机场无人机关联: dockId={}, aircraftId={}", dockId, aircraftId); } else { // 机场已有关联,检查是否需要更新 DockAircraft existingRelation = existingRelations.get(0); if (!Objects.equals(existingRelation.getAircraftId(), aircraftId)) { // 无人机发生变化,更新关联 existingRelation.setAircraftId(aircraftId); existingRelation.setUpdateBy("system"); dockAircraftDomain.updateDockAircraft(existingRelation); log.info("更新机场无人机关联: dockId={}, aircraftId={}", dockId, aircraftId); } } } }