diff --git a/src/main/java/com/ruoyi/device/domain/impl/DeviceDomainImpl.java b/src/main/java/com/ruoyi/device/domain/impl/DeviceDomainImpl.java index 98d0f44..61f3583 100644 --- a/src/main/java/com/ruoyi/device/domain/impl/DeviceDomainImpl.java +++ b/src/main/java/com/ruoyi/device/domain/impl/DeviceDomainImpl.java @@ -6,6 +6,7 @@ import com.ruoyi.device.domain.convert.DeviceConvert; import com.ruoyi.device.domain.model.Device; import com.ruoyi.device.mapper.DeviceMapper; import com.ruoyi.device.mapper.entity.DeviceEntity; +import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; @@ -19,6 +20,7 @@ import java.util.List; * @author ruoyi * @date 2026-01-16 */ +@Slf4j @Component public class DeviceDomainImpl implements IDeviceDomain { @@ -57,8 +59,11 @@ public class DeviceDomainImpl implements IDeviceDomain @Cacheable(value = DeviceCacheConfig.DEVICE_CACHE, key = "'sn:' + #deviceSn", unless = "#result == null") public Device selectDeviceByDeviceSn(String deviceSn) { + log.info("查询设备 by device_sn: {}", deviceSn); DeviceEntity entity = deviceMapper.selectDeviceByDeviceSn(deviceSn); - return DeviceConvert.from(entity); + Device result = DeviceConvert.from(entity); + log.info("查询设备结果: device_sn={}, result={}", deviceSn, result != null ? result.getDeviceId() : "null"); + return result; } @Override diff --git a/src/main/java/com/ruoyi/device/service/impl/DjiService.java b/src/main/java/com/ruoyi/device/service/impl/DjiService.java index 5300d28..8ae6594 100644 --- a/src/main/java/com/ruoyi/device/service/impl/DjiService.java +++ b/src/main/java/com/ruoyi/device/service/impl/DjiService.java @@ -95,7 +95,9 @@ public class DjiService { boolean isStateMessage = "state".equalsIgnoreCase(droneData.getMessageType()); // 更新 Dock 表的 lastActiveTime + log.info("准备查询设备: device_sn={}", droneData.getDeviceSn()); Device device = deviceDomain.selectDeviceByDeviceSn(droneData.getDeviceSn()); + log.info("查询设备成功: device_sn={}, deviceId={}", droneData.getDeviceSn(), device != null ? device.getDeviceId() : null); if(Objects.nonNull(device)) { Aircraft aircraft = aircraftDomain.selectAircraftByDeviceId(device.getDeviceId()); if(Objects.nonNull(aircraft)) { diff --git a/src/main/java/com/ruoyi/device/service/impl/SynService.java b/src/main/java/com/ruoyi/device/service/impl/SynService.java index 2d8922f..63d8369 100644 --- a/src/main/java/com/ruoyi/device/service/impl/SynService.java +++ b/src/main/java/com/ruoyi/device/service/impl/SynService.java @@ -16,12 +16,14 @@ 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.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; @Service @@ -67,6 +69,9 @@ public class SynService { @Autowired private IDockGroupDomain dockGroupDomain; + @Autowired(required = false) + private StringRedisTemplate redisTemplate; + public SynService(IThingsBoardDomain iThingsBoardDomain) { this.iThingsBoardDomain = iThingsBoardDomain; } @@ -127,7 +132,49 @@ public class SynService { return false; } + /** + * 获取分布式锁 + * + * @param lockKey 锁的key + * @param lockValue 锁的value(用于释放锁时验证) + * @param expireTime 锁的过期时间(秒) + * @return true表示获取锁成功,false表示获取锁失败 + */ + private boolean tryLock(String lockKey, String lockValue, long expireTime) { + if (redisTemplate == null) { + log.warn("Redis未配置,跳过分布式锁,使用本地同步"); + return true; + } + try { + Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.SECONDS); + return Boolean.TRUE.equals(result); + } catch (Exception e) { + log.error("获取分布式锁失败: lockKey={}, error={}", lockKey, e.getMessage(), e); + return false; + } + } + + /** + * 释放分布式锁 + * + * @param lockKey 锁的key + * @param lockValue 锁的value(用于验证是否是自己持有的锁) + */ + private void releaseLock(String lockKey, String lockValue) { + if (redisTemplate == null) { + return; + } + + try { + String currentValue = redisTemplate.opsForValue().get(lockKey); + if (lockValue.equals(currentValue)) { + redisTemplate.delete(lockKey); + } + } catch (Exception e) { + log.error("释放分布式锁失败: lockKey={}, error={}", lockKey, e.getMessage(), e); + } + } /** * 定时任务:同步基础表数据 @@ -335,31 +382,77 @@ public class SynService { log.info("开始同步设备: iotDeviceId={}, deviceName={}, deviceType={}, gatewayId={}", iotDeviceId, deviceName, deviceType, gatewayId); - // 查询设备是否已存在 + // 查询设备是否已存在(通过 iotDeviceId) Device existingDevice = deviceDomain.selectDeviceByIotDeviceId(iotDeviceId); + // 如果通过 iotDeviceId 未找到,再检查 device_sn 是否已存在 if (existingDevice == null) { - // 设备不存在,插入新设备 - Device newDevice = new Device(); - newDevice.setDeviceName(deviceName); - newDevice.setIotDeviceId(iotDeviceId); - newDevice.setDeviceType(deviceType.getCode()); - newDevice.setDeviceSn(deviceSn); - newDevice.setGateway(gatewayId); - newDevice.setCreateBy("system"); + Device existingDeviceBySn = deviceDomain.selectDeviceByDeviceSn(deviceSn); + if (existingDeviceBySn != null) { + log.warn("设备SN已存在但iotDeviceId不同: deviceSn={}, existingIotDeviceId={}, newIotDeviceId={}", + deviceSn, existingDeviceBySn.getIotDeviceId(), iotDeviceId); + // 返回已存在的设备ID,避免重复插入 + return existingDeviceBySn.getDeviceId(); + } + } - // 根据设备名称判断厂商 - String manufacturer = deviceName.startsWith("TH") ? "tuoheng" : "dajiang"; - newDevice.setDeviceManufacturer(manufacturer); + if (existingDevice == null) { + // 设备不存在,使用分布式锁防止并发插入 + String lockKey = "device:sync:lock:" + deviceSn; + String lockValue = UUID.randomUUID().toString(); - log.info("准备插入新设备: deviceName={}, deviceType={}, deviceSn={}, manufacturer={}", - deviceName, deviceType, deviceSn, manufacturer); - deviceDomain.insertDevice(newDevice); + // 尝试获取锁,最多等待3秒 + boolean locked = tryLock(lockKey, lockValue, 10); + if (!locked) { + log.warn("获取设备同步锁失败,可能有其他线程正在同步该设备: deviceSn={}", deviceSn); + // 等待一小段时间后重新查询,可能已经被其他线程插入 + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + Device retryDevice = deviceDomain.selectDeviceByDeviceSn(deviceSn); + if (retryDevice != null) { + log.info("重新查询到设备: deviceSn={}, deviceId={}", deviceSn, retryDevice.getDeviceId()); + return retryDevice.getDeviceId(); + } + log.error("获取锁失败且重新查询也未找到设备: deviceSn={}", deviceSn); + return null; + } - Long deviceId = newDevice.getDeviceId(); - log.info("插入新设备成功: iotDeviceId={}, deviceName={}, deviceType={}, 返回deviceId={}", - iotDeviceId, deviceName, deviceType, deviceId); - return deviceId; + try { + // 双重检查:获取锁后再次查询,防止重复插入 + Device doubleCheckDevice = deviceDomain.selectDeviceByDeviceSn(deviceSn); + if (doubleCheckDevice != null) { + log.info("双重检查发现设备已存在: deviceSn={}, deviceId={}", deviceSn, doubleCheckDevice.getDeviceId()); + return doubleCheckDevice.getDeviceId(); + } + + // 插入新设备 + Device newDevice = new Device(); + newDevice.setDeviceName(deviceName); + newDevice.setIotDeviceId(iotDeviceId); + newDevice.setDeviceType(deviceType.getCode()); + newDevice.setDeviceSn(deviceSn); + newDevice.setGateway(gatewayId); + newDevice.setCreateBy("system"); + + // 根据设备名称判断厂商 + String manufacturer = deviceName.startsWith("TH") ? "tuoheng" : "dajiang"; + newDevice.setDeviceManufacturer(manufacturer); + + log.info("准备插入新设备: deviceName={}, deviceType={}, deviceSn={}, manufacturer={}", + deviceName, deviceType, deviceSn, manufacturer); + deviceDomain.insertDevice(newDevice); + + Long deviceId = newDevice.getDeviceId(); + log.info("插入新设备成功: iotDeviceId={}, deviceName={}, deviceType={}, 返回deviceId={}", + iotDeviceId, deviceName, deviceType, deviceId); + return deviceId; + } finally { + // 释放锁 + releaseLock(lockKey, lockValue); + } } else { // 设备已存在,检查是否需要更新 boolean needUpdate = false; @@ -776,25 +869,71 @@ public class SynService { List existingDevices = deviceDomain.selectDeviceList(queryDevice); Device existingDevice = (existingDevices != null && !existingDevices.isEmpty()) ? existingDevices.get(0) : null; + // 如果通过 iotDeviceId + deviceType 未找到,再检查 device_sn 是否已存在 if (existingDevice == null) { - // 设备不存在,插入新设备 - Device newDevice = new Device(); - newDevice.setDeviceName(deviceName); - newDevice.setIotDeviceId(iotDeviceId); - newDevice.setDeviceType(deviceType.getCode()); - newDevice.setDeviceSn(deviceSn); - newDevice.setGateway(gatewayId); - newDevice.setDeviceManufacturer("tuoheng"); - newDevice.setCreateBy("system"); + Device existingDeviceBySn = deviceDomain.selectDeviceByDeviceSn(deviceSn); + if (existingDeviceBySn != null) { + log.warn("拓恒设备SN已存在但iotDeviceId或deviceType不同: deviceSn={}, existingIotDeviceId={}, existingDeviceType={}, newIotDeviceId={}, newDeviceType={}", + deviceSn, existingDeviceBySn.getIotDeviceId(), existingDeviceBySn.getDeviceType(), iotDeviceId, deviceType.getCode()); + // 返回已存在的设备ID,避免重复插入 + return existingDeviceBySn.getDeviceId(); + } + } - log.info("准备插入新拓恒设备: deviceName={}, deviceType={}, deviceSn={}, manufacturer=tuoheng", - deviceName, deviceType, deviceSn); - deviceDomain.insertDevice(newDevice); + if (existingDevice == null) { + // 设备不存在,使用分布式锁防止并发插入 + String lockKey = "device:sync:lock:" + deviceSn; + String lockValue = UUID.randomUUID().toString(); - Long deviceId = newDevice.getDeviceId(); - log.info("插入新拓恒设备成功: iotDeviceId={}, deviceName={}, deviceType={}, deviceSn={}, 返回deviceId={}", - iotDeviceId, deviceName, deviceType, deviceSn, deviceId); - return deviceId; + // 尝试获取锁 + boolean locked = tryLock(lockKey, lockValue, 10); + if (!locked) { + log.warn("获取拓恒设备同步锁失败,可能有其他线程正在同步该设备: deviceSn={}", deviceSn); + // 等待后重新查询 + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + Device retryDevice = deviceDomain.selectDeviceByDeviceSn(deviceSn); + if (retryDevice != null) { + log.info("重新查询到拓恒设备: deviceSn={}, deviceId={}", deviceSn, retryDevice.getDeviceId()); + return retryDevice.getDeviceId(); + } + log.error("获取锁失败且重新查询也未找到拓恒设备: deviceSn={}", deviceSn); + return null; + } + + try { + // 双重检查 + Device doubleCheckDevice = deviceDomain.selectDeviceByDeviceSn(deviceSn); + if (doubleCheckDevice != null) { + log.info("双重检查发现拓恒设备已存在: deviceSn={}, deviceId={}", deviceSn, doubleCheckDevice.getDeviceId()); + return doubleCheckDevice.getDeviceId(); + } + + // 插入新设备 + Device newDevice = new Device(); + newDevice.setDeviceName(deviceName); + newDevice.setIotDeviceId(iotDeviceId); + newDevice.setDeviceType(deviceType.getCode()); + newDevice.setDeviceSn(deviceSn); + newDevice.setGateway(gatewayId); + newDevice.setDeviceManufacturer("tuoheng"); + newDevice.setCreateBy("system"); + + log.info("准备插入新拓恒设备: deviceName={}, deviceType={}, deviceSn={}, manufacturer=tuoheng", + deviceName, deviceType, deviceSn); + deviceDomain.insertDevice(newDevice); + + Long deviceId = newDevice.getDeviceId(); + log.info("插入新拓恒设备成功: iotDeviceId={}, deviceName={}, deviceType={}, deviceSn={}, 返回deviceId={}", + iotDeviceId, deviceName, deviceType, deviceSn, deviceId); + return deviceId; + } finally { + // 释放锁 + releaseLock(lockKey, lockValue); + } } else { // 设备已存在,检查是否需要更新 boolean needUpdate = false; diff --git a/src/main/java/com/ruoyi/device/service/impl/TuohengBufferDeviceImpl.java b/src/main/java/com/ruoyi/device/service/impl/TuohengBufferDeviceImpl.java index 6ad65f0..f65a0ec 100644 --- a/src/main/java/com/ruoyi/device/service/impl/TuohengBufferDeviceImpl.java +++ b/src/main/java/com/ruoyi/device/service/impl/TuohengBufferDeviceImpl.java @@ -194,47 +194,83 @@ public class TuohengBufferDeviceImpl implements IBufferDeviceService { */ private void fillTuohengDockDetail(DockDetailDTO dto, String iotDeviceId) { try { + log.info("========== 开始填充拓恒机场详情 =========="); + log.info("iotDeviceId: {}", iotDeviceId); + // 获取拓恒设备属性 AttributeMap attributes = thingsBoardDomain.getPredefinedTuohengDeviceAttributes(iotDeviceId); + log.info("拓恒设备属性数据: {}", attributes); + // 获取拓恒设备遥测数据 TelemetryMap telemetry = thingsBoardDomain.getPredefinedTuohengDeviceTelemetry(iotDeviceId); + log.info("拓恒设备遥测数据: {}", telemetry); // 设置在线状态 Boolean isActive = attributes.get(TuohengDeviceAttributes.ACTIVE).orElse(false); + log.info("设备在线状态 ACTIVE: {}", isActive); if (isActive) { telemetry.get(TuohengDeviceTelemetry.STATUS).ifPresent(statusValue -> { String status = statusValue.getValue(); - dto.setDockStatus("online".equals(status) ? "IDLE" : "OFFLINE"); + log.info("STATUS 遥测值: {}", status); + String dockStatus = "online".equals(status) ? "IDLE" : "OFFLINE"; + dto.setDockStatus(dockStatus); + log.info("设置机场状态: {}", dockStatus); }); } else { dto.setDockStatus("OFFLINE"); + log.info("设备离线,设置机场状态为 OFFLINE"); } // 设置舱内温度和湿度 + log.info("---------- 解析舱内环境数据 ----------"); telemetry.get(TuohengDeviceTelemetry.NEST_INNER_TEMP) - .ifPresent(value -> dto.setCabinTemperature(value.getValue())); + .ifPresent(value -> { + log.info("NEST_INNER_TEMP 舱内温度: {}", value.getValue()); + dto.setCabinTemperature(value.getValue()); + }); telemetry.get(TuohengDeviceTelemetry.NEST_INNER_HUM) - .ifPresent(value -> dto.setCabinHumidity(value.getValue())); + .ifPresent(value -> { + log.info("NEST_INNER_HUM 舱内湿度: {}", value.getValue()); + dto.setCabinHumidity(value.getValue()); + }); // 设置环境数据 + log.info("---------- 解析气象数据 ----------"); telemetry.get(TuohengDeviceTelemetry.NEST_INNER_TEMP) - .ifPresent(value -> dto.setEnvironmentTemperature(value.getValue())); + .ifPresent(value -> { + log.info("环境温度(使用舱内温度): {}", value.getValue()); + dto.setEnvironmentTemperature(value.getValue()); + }); telemetry.get(TuohengDeviceTelemetry.WEATHER_WIND_SPEED) - .ifPresent(value -> dto.setWindSpeed(value.getValue())); + .ifPresent(value -> { + log.info("WEATHER_WIND_SPEED 风速: {}", value.getValue()); + dto.setWindSpeed(value.getValue()); + }); telemetry.get(TuohengDeviceTelemetry.WEATHER_RAINFALL) - .ifPresent(value -> dto.setRainfall(value.getValue())); + .ifPresent(value -> { + log.info("WEATHER_RAINFALL 降雨量: {}", value.getValue()); + dto.setRainfall(value.getValue()); + }); // 设置电池信息 + log.info("---------- 解析电池数据 ----------"); telemetry.get(TuohengDeviceTelemetry.BATTERY_LEVEL) - .ifPresent(value -> dto.setCapacity_percent(value.getValue())); + .ifPresent(value -> { + log.info("BATTERY_LEVEL 电池电量: {}", value.getValue()); + dto.setCapacity_percent(value.getValue()); + }); // 设置充电状态 telemetry.get(TuohengDeviceTelemetry.BATTERY_B_CHARGING) .ifPresent(value -> { - dto.setChargingStatus(value.getValue() == 1 ? "CHARGING" : "FREE"); + log.info("BATTERY_B_CHARGING 充电状态原始值: {}", value.getValue()); + String chargingStatus = value.getValue() == 1 ? "CHARGING" : "FREE"; + dto.setChargingStatus(chargingStatus); + log.info("设置充电状态: {}", chargingStatus); }); - log.debug("拓恒机场详情填充完成: iotDeviceId={}", iotDeviceId); + log.info("拓恒机场详情填充完成: iotDeviceId={}, dockStatus={}", iotDeviceId, dto.getDockStatus()); + log.info("========== 拓恒机场详情填充结束 =========="); } catch (Exception e) { log.error("填充拓恒机场详情失败: iotDeviceId={}, error={}", iotDeviceId, e.getMessage(), e); } @@ -248,23 +284,33 @@ public class TuohengBufferDeviceImpl implements IBufferDeviceService { */ private void fillTuohengAircraftStatus(DockDetailDTO dto, String aircraftIotDeviceId) { try { + log.info("========== 开始填充拓恒无人机状态(机场详情中) =========="); + log.info("aircraftIotDeviceId: {}", aircraftIotDeviceId); + // 获取拓恒无人机遥测数据 TelemetryMap telemetry = thingsBoardDomain.getPredefinedTuohengDeviceTelemetry(aircraftIotDeviceId); + log.info("拓恒无人机遥测数据: {}", telemetry); // 设置无人机状态 - 根据 armed 状态判断 telemetry.get(TuohengDeviceTelemetry.ARMED).ifPresent(armedValue -> { String armed = armedValue.getValue(); + log.info("ARMED 解锁状态原始值: {}", armed); if ("true".equals(armed) || "1".equals(armed)) { dto.setAircraftStatus("IN_MISSION"); // 解锁状态表示在任务中 + log.info("设置无人机状态: IN_MISSION (任务中)"); } else { dto.setAircraftStatus("POWER_ON_IN_CABIN"); // 未解锁表示在舱内待机 + log.info("设置无人机状态: POWER_ON_IN_CABIN (舱内待机)"); } }); // 设置作业架次 - 暂时设置为0,拓恒设备可能没有这个数据 dto.setMissionCount(0); + log.info("设置作业架次: 0 (拓恒设备暂无此数据)"); - log.debug("拓恒无人机状态填充完成: aircraftIotDeviceId={}", aircraftIotDeviceId); + log.info("拓恒无人机状态填充完成: aircraftIotDeviceId={}, aircraftStatus={}", + aircraftIotDeviceId, dto.getAircraftStatus()); + log.info("========== 拓恒无人机状态填充结束 =========="); } catch (Exception e) { log.error("填充拓恒无人机状态失败: aircraftIotDeviceId={}, error={}", aircraftIotDeviceId, e.getMessage(), e); @@ -279,45 +325,71 @@ public class TuohengBufferDeviceImpl implements IBufferDeviceService { */ private void fillTuohengAircraftDetail(AircraftDetailDTO dto, String iotDeviceId) { try { + log.info("========== 开始填充拓恒无人机详情 =========="); + log.info("iotDeviceId: {}", iotDeviceId); + // 获取拓恒设备属性 AttributeMap attributes = thingsBoardDomain.getPredefinedTuohengDeviceAttributes(iotDeviceId); + log.info("拓恒无人机属性数据: {}", attributes); + // 获取拓恒设备遥测数据 TelemetryMap telemetry = thingsBoardDomain.getPredefinedTuohengDeviceTelemetry(iotDeviceId); + log.info("拓恒无人机遥测数据: {}", telemetry); // 设置无人机状态 + log.info("---------- 解析无人机状态 ----------"); telemetry.get(TuohengDeviceTelemetry.ARMED).ifPresent(armedValue -> { String armed = armedValue.getValue(); + log.info("ARMED 解锁状态: {}", armed); if ("true".equals(armed) || "1".equals(armed)) { dto.setAircraftStatus("IN_MISSION"); + log.info("设置无人机状态: IN_MISSION"); } else { dto.setAircraftStatus("POWER_ON_IN_CABIN"); + log.info("设置无人机状态: POWER_ON_IN_CABIN"); } }); // 设置作业架次 - 暂时设置为0 dto.setMissionCount(0); + log.info("设置作业架次: 0"); // 设置GPS信号 + log.info("---------- 解析GPS数据 ----------"); telemetry.get(TuohengDeviceTelemetry.SAT_COUNT) - .ifPresent(value -> dto.setGpsSignal(value.getValue())); + .ifPresent(value -> { + log.info("SAT_COUNT 卫星数量: {}", value.getValue()); + dto.setGpsSignal(value.getValue()); + }); // 设置电池信息 + log.info("---------- 解析电池数据 ----------"); telemetry.get(TuohengDeviceTelemetry.BATTERY_REMAIN) - .ifPresent(value -> dto.setBatteryLevel(value.getValue())); + .ifPresent(value -> { + log.info("BATTERY_REMAIN 剩余电量: {}", value.getValue()); + dto.setBatteryLevel(value.getValue()); + }); telemetry.get(TuohengDeviceTelemetry.VOLTAGE) .ifPresent(value -> { + log.info("VOLTAGE 电压原始值: {}", value.getValue()); Double voltage = value.getValue(); if (voltage != null) { dto.setVoltage(voltage.intValue()); + log.info("VOLTAGE 电压转换后: {}", voltage.intValue()); } }); // 设置飞行时长(秒) + log.info("---------- 解析飞行数据 ----------"); telemetry.get(TuohengDeviceTelemetry.FLIGHT_TIME) - .ifPresent(value -> dto.setFlightDuration(value.getValue())); + .ifPresent(value -> { + log.info("FLIGHT_TIME 飞行时长(秒): {}", value.getValue()); + dto.setFlightDuration(value.getValue()); + }); - log.debug("拓恒无人机详情填充完成: iotDeviceId={}", iotDeviceId); + log.info("拓恒无人机详情填充完成: iotDeviceId={}, aircraftStatus={}", iotDeviceId, dto.getAircraftStatus()); + log.info("========== 拓恒无人机详情填充结束 =========="); } catch (Exception e) { log.error("填充拓恒无人机详情失败: iotDeviceId={}, error={}", iotDeviceId, e.getMessage(), e); } diff --git a/src/main/resources/mapper/device/DeviceMapper.xml b/src/main/resources/mapper/device/DeviceMapper.xml index d9bac45..2d3d639 100644 --- a/src/main/resources/mapper/device/DeviceMapper.xml +++ b/src/main/resources/mapper/device/DeviceMapper.xml @@ -40,6 +40,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"