Просмотр исходного кода

Merge branch 'fu-dev' of uskycloud/usky-modules into master

hanzhengyi 1 день назад
Родитель
Сommit
09f181493a
21 измененных файлов с 1110 добавлено и 374 удалено
  1. 5 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/BaseDataController.java
  2. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDeliveryLogService.java
  3. 6 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/config/DeviceFieldConfig.java
  4. 4 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/AlarmType.java
  5. 15 8
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataSyncService.java
  6. 10 10
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataTransferService.java
  7. 62 35
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java
  8. 368 221
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java
  9. 134 11
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java
  10. 177 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/BasementClimateUtil.java
  11. 173 42
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java
  12. 5 5
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataSyncService.java
  13. 44 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/FixedWaterLevelGenerator.java
  14. 10 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/SnowflakeIdGenerator.java
  15. 4 4
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessageVO.java
  16. 42 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/BaseMqttInfo.java
  17. 2 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/EngineeringBaseVO.java
  18. 5 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FacilityDeviceVO.java
  19. 2 15
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FloorPlaneVO.java
  20. 2 15
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ProtectiveUnitVO.java
  21. 39 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/WaterLevelVO.java

+ 5 - 2
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/BaseDataController.java

@@ -78,8 +78,11 @@ public class BaseDataController {
      * 批量上报智能监管物联设施信息
      * 批量上报智能监管物联设施信息
      */
      */
     @GetMapping("/sensorInfos")
     @GetMapping("/sensorInfos")
-    public String batchSendSensorInfos(@RequestParam(value = "tenantId",required = false) Integer tenantId) {
-        Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId);
+    public String batchSendSensorInfos(@RequestParam(value = "tenantId",required = false) Integer tenantId,
+                                       @RequestParam(value = "engineeringId") Long engineeringId,
+                                       @RequestParam(value = "username") String username,
+                                       @RequestParam(value = "password") String password) {
+        Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId, engineeringId, username, password);
         return String.format("上报成功 %d", map.get("success"));
         return String.format("上报成功 %d", map.get("success"));
     }
     }
 }
 }

+ 1 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDeliveryLogService.java

@@ -27,5 +27,5 @@ public interface CdiDeliveryLogService extends CrudService<CdiDeliveryLog> {
 
 
     // 存储日志
     // 存储日志
     void saveLog(String topic, String dataTypeName, Integer dataType, Integer tenantId, Long engineeringId, LocalDateTime now, long startTime, long endTime,
     void saveLog(String topic, String dataTypeName, Integer dataType, Integer tenantId, Long engineeringId, LocalDateTime now, long startTime, long endTime,
-                 int total, int success, int failure, int notSynced, int pushFlag);
+                 int total, int success, int failure, int notSynced, int pushFlag, String userName);
 }
 }

+ 6 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/config/DeviceFieldConfig.java

@@ -32,6 +32,9 @@ public class DeviceFieldConfig {
         // 电气火灾(704)
         // 电气火灾(704)
         fieldMapping.put("704", "voltage_a,voltage_b,voltage_c,current_a,current_b,current_c,temperature_a,temperature_b,temperature_c,current_residual,active_power");
         fieldMapping.put("704", "voltage_a,voltage_b,voltage_c,current_a,current_b,current_c,temperature_a,temperature_b,temperature_c,current_residual,active_power");
 
 
+        // 电能采集(705)
+        fieldMapping.put("705", "electrical_energy");
+
         // 温度传感器(707)
         // 温度传感器(707)
         fieldMapping.put("707", "wd");
         fieldMapping.put("707", "wd");
 
 
@@ -56,6 +59,9 @@ public class DeviceFieldConfig {
         // 位移传感器
         // 位移传感器
         fieldMapping.put("714", "wy");
         fieldMapping.put("714", "wy");
 
 
+        // 液位
+        fieldMapping.put("716", "sensorValue");
+
         return fieldMapping;
         return fieldMapping;
     }
     }
 }
 }

+ 4 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/AlarmType.java

@@ -46,7 +46,10 @@ public enum AlarmType {
     TILT("712", "34.1", Category.STRUCTURE_MONITORING, "有倾斜"),
     TILT("712", "34.1", Category.STRUCTURE_MONITORING, "有倾斜"),
 
 
     // 裂缝检测告警
     // 裂缝检测告警
-    CRACK("713", "36.1", Category.STRUCTURE_MONITORING, "有裂缝");
+    CRACK("713", "36.1", Category.STRUCTURE_MONITORING, "有裂缝"),
+
+    // 集水井水位偏高
+    SEWAGE_LEVEL("716", "11.1", Category.STRUCTURE_MONITORING, "集水井水位偏高");
 
 
     private final String originalCode;
     private final String originalCode;
     private final String mappedCode;
     private final String mappedCode;

+ 15 - 8
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataSyncService.java

@@ -12,6 +12,7 @@ import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
 import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
 import com.usky.cdi.service.enums.AlarmType;
 import com.usky.cdi.service.enums.AlarmType;
+import com.usky.common.security.utils.SecurityUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,11 +22,9 @@ import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import javax.annotation.PostConstruct;
 import javax.annotation.PostConstruct;
+import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 /**
 /**
@@ -49,6 +48,7 @@ public class AlarmDataSyncService {
     private static final String WARTIME = "wartime";
     private static final String WARTIME = "wartime";
     private static final String ALARM_DELIVERY_KEY_PREFIX = "alarm:delivery:";
     private static final String ALARM_DELIVERY_KEY_PREFIX = "alarm:delivery:";
     private final MqttConnectionTool mqttConnectionTool;
     private final MqttConnectionTool mqttConnectionTool;
+    private final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
 
     @Autowired
     @Autowired
     private StringRedisTemplate stringRedisTemplate;
     private StringRedisTemplate stringRedisTemplate;
@@ -69,8 +69,8 @@ public class AlarmDataSyncService {
     /**
     /**
      * 获取当前时间
      * 获取当前时间
      */
      */
-    private LocalDateTime getCurrentTime() {
-        return LocalDateTime.now();
+    private String getCurrentTime() {
+        return timeFormat.format(new Date());
     }
     }
 
 
     /**
     /**
@@ -117,6 +117,13 @@ public class AlarmDataSyncService {
         String topic = MqttTopics.Alarm.MESSAGE.getTopic();
         String topic = MqttTopics.Alarm.MESSAGE.getTopic();
         String desc = MqttTopics.Alarm.MESSAGE.getDesc();
         String desc = MqttTopics.Alarm.MESSAGE.getDesc();
 
 
+        String userName = "自动同步";
+        try {
+            userName = SecurityUtils.getUsername();
+        } catch (Exception e) {
+            log.error("定时任务无法获取用户名,使用默认‘自动同步’", e);
+        }
+
         try {
         try {
             // 2.创建MQTT连接
             // 2.创建MQTT连接
             mqttConnectionTool.connectOrRefresh(username, password);
             mqttConnectionTool.connectOrRefresh(username, password);
@@ -198,7 +205,7 @@ public class AlarmDataSyncService {
             endTime = System.currentTimeMillis();
             endTime = System.currentTimeMillis();
 
 
             cdiDeliveryLogService.saveLog(topic, desc, 5, tenantId, engineeringId, now, startTime, endTime, size,
             cdiDeliveryLogService.saveLog(topic, desc, 5, tenantId, engineeringId, now, startTime, endTime, size,
-                    successCount, failureCount, size - successCount - failureCount, 1);
+                    successCount, failureCount, size - successCount - failureCount, 1, userName);
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("租户{}的告警数据推送定时任务执行失败:{}", tenantId, e.getMessage(), e);
             log.error("租户{}的告警数据推送定时任务执行失败:{}", tenantId, e.getMessage(), e);
         } finally {
         } finally {
@@ -206,7 +213,7 @@ public class AlarmDataSyncService {
             log.info("结束时间:{}, 耗时:{}ms", getCurrentTime(), endTime - startTime);
             log.info("结束时间:{}, 耗时:{}ms", getCurrentTime(), endTime - startTime);
 
 
             cdiDeliveryLogService.saveLog(topic, desc, 5, tenantId, engineeringId, now, startTime, endTime, size,
             cdiDeliveryLogService.saveLog(topic, desc, 5, tenantId, engineeringId, now, startTime, endTime, size,
-                    successCount, failureCount, size - successCount - failureCount, 0);
+                    successCount, failureCount, size - successCount - failureCount, 0, userName);
         }
         }
     }
     }
 
 

+ 10 - 10
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataTransferService.java

@@ -53,7 +53,7 @@ public class AlarmDataTransferService {
      * 生成数据包ID
      * 生成数据包ID
      */
      */
     private Long generateDataPacketID() {
     private Long generateDataPacketID() {
-        return idGenerator.nextPacketId();
+        return idGenerator.nextPacketId10();
     }
     }
 
 
     /**
     /**
@@ -69,7 +69,7 @@ public class AlarmDataTransferService {
                 vo.setDataPacketID(generateDataPacketID());
                 vo.setDataPacketID(generateDataPacketID());
             }
             }
             if (vo.getPublishTime() == null) {
             if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
+                vo.setPublishTime(getCurrentTime());
             }
             }
 
 
 //            HashMap<String, Object> map = new HashMap<>();
 //            HashMap<String, Object> map = new HashMap<>();
@@ -87,7 +87,7 @@ public class AlarmDataTransferService {
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             String json = jsonObject.toJSONString();
             String json = jsonObject.toJSONString();
             System.out.println(json);
             System.out.println(json);
-            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
+            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101100017", "gjB4v1bh");
             String topic = "alarm/message";
             String topic = "alarm/message";
             IQeIRyXG.sendToMqtt(topic, json);
             IQeIRyXG.sendToMqtt(topic, json);
 
 
@@ -106,17 +106,16 @@ public class AlarmDataTransferService {
      * @return 是否发送成功
      * @return 是否发送成功
      */
      */
     public boolean sendAlarmMessage1(AlarmMessageVO vo) {
     public boolean sendAlarmMessage1(AlarmMessageVO vo) {
-        MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
 
 
         try {
         try {
             if (vo.getDataPacketID() == null) {
             if (vo.getDataPacketID() == null) {
                 vo.setDataPacketID(generateDataPacketID());
                 vo.setDataPacketID(generateDataPacketID());
             }
             }
             if (vo.getPublishTime() == null) {
             if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
+                vo.setPublishTime(getCurrentTime());
             }
             }
 
 
-            HashMap<String, Object> map = new HashMap<>();
+            // HashMap<String, Object> map = new HashMap<>();
 //            map.put("dataPacketID", vo.getDataPacketID());
 //            map.put("dataPacketID", vo.getDataPacketID());
 //            map.put("engineeringID", vo.getEngineeringID());
 //            map.put("engineeringID", vo.getEngineeringID());
 //            map.put("floor", vo.getFloor());
 //            map.put("floor", vo.getFloor());
@@ -132,6 +131,7 @@ public class AlarmDataTransferService {
             String json = jsonObject.toJSONString();
             String json = jsonObject.toJSONString();
             System.out.println(json);
             System.out.println(json);
             String topic = "alarm/message";
             String topic = "alarm/message";
+            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101100017", "gjB4v1bh");
             IQeIRyXG.sendToMqtt(topic, json);
             IQeIRyXG.sendToMqtt(topic, json);
 
 
             return true;
             return true;
@@ -147,13 +147,13 @@ public class AlarmDataTransferService {
                 vo.setDataPacketID(generateDataPacketID());
                 vo.setDataPacketID(generateDataPacketID());
             }
             }
             if (vo.getPublishTime() == null) {
             if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
+                vo.setPublishTime(getCurrentTime());
             }
             }
 
 
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             String json = jsonObject.toJSONString();
             String json = jsonObject.toJSONString();
             System.out.println(json);
             System.out.println(json);
-            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
+            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101100017", "gjB4v1bh");
             String topic = "alarm/message";
             String topic = "alarm/message";
             IQeIRyXG.sendToMqtt(topic, json);
             IQeIRyXG.sendToMqtt(topic, json);
 
 
@@ -170,10 +170,10 @@ public class AlarmDataTransferService {
                 vo.setDataPacketID(generateDataPacketID());
                 vo.setDataPacketID(generateDataPacketID());
             }
             }
             if (vo.getPublishTime() == null) {
             if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
+                vo.setPublishTime(getCurrentTime());
             }
             }
 
 
-            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
+            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101100017", "3101100017");
 
 
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             String json = jsonObject.toJSONString();
             String json = jsonObject.toJSONString();

+ 62 - 35
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java

@@ -3,13 +3,15 @@ package com.usky.cdi.service.impl;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.nacos.shaded.com.google.gson.Gson;
 import com.alibaba.nacos.shaded.com.google.gson.Gson;
+import com.alibaba.nacos.shaded.com.google.gson.GsonBuilder;
+import com.alibaba.nacos.shaded.com.google.gson.LongSerializationPolicy;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.usky.cdi.domain.BaseBuildFacility;
 import com.usky.cdi.domain.BaseBuildFacility;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.service.BaseBuildFacilityService;
 import com.usky.cdi.service.BaseBuildFacilityService;
-import com.usky.cdi.service.CdiDeliveryLogService;
 import com.usky.cdi.service.DmpDeviceInfoService;
 import com.usky.cdi.service.DmpDeviceInfoService;
 import com.usky.cdi.service.config.mqtt.MqttOutConfig;
 import com.usky.cdi.service.config.mqtt.MqttOutConfig;
+import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.info.EngineeringBaseVO;
 import com.usky.cdi.service.vo.info.EngineeringBaseVO;
 import com.usky.cdi.service.vo.info.FacilityDeviceVO;
 import com.usky.cdi.service.vo.info.FacilityDeviceVO;
@@ -22,9 +24,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
 import java.nio.file.Files;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.nio.file.Paths;
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.*;
 
 
 /**
 /**
@@ -48,8 +55,11 @@ public class BaseDataTransferService {
     @Resource
     @Resource
     private MqttOutConfig.MqttGateway mqttGateway;
     private MqttOutConfig.MqttGateway mqttGateway;
 
 
-    @Value("${config.engineeringID}")
-    private String engineeringID;
+    // @Value("${config.engineeringID}")
+    // private String engineeringID;
+
+    @Autowired
+    private MqttConnectionTool mqttConnectionTool;
 
 
     private final SnowflakeIdGenerator idGenerator;
     private final SnowflakeIdGenerator idGenerator;
     private final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
     private final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@@ -122,7 +132,8 @@ public class BaseDataTransferService {
             String topic = "base/protectiveUnit";
             String topic = "base/protectiveUnit";
 
 
             log.info("发送防护单元基础信息,Topic: {}, Data: {}", topic, json);
             log.info("发送防护单元基础信息,Topic: {}, Data: {}", topic, json);
-            mqttGateway.sendToMqtt(topic, json);
+            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(vo.getUserName(), vo.getPassword());
+            gateway.sendToMqtt(topic, json);
 
 
             return true;
             return true;
         } catch (Exception e) {
         } catch (Exception e) {
@@ -140,57 +151,67 @@ public class BaseDataTransferService {
      */
      */
     public boolean sendFloorPlane(FloorPlaneVO vo) {
     public boolean sendFloorPlane(FloorPlaneVO vo) {
         try {
         try {
+            // ========== 1. 基础参数填充 ==========
             if (vo.getDataPacketID() == null) {
             if (vo.getDataPacketID() == null) {
                 vo.setDataPacketID(generateDataPacketID());
                 vo.setDataPacketID(generateDataPacketID());
             }
             }
             if (vo.getPublishTime() == null) {
             if (vo.getPublishTime() == null) {
-                vo.setPublishTime(getCurrentTime());
+                vo.setPublishTime(timeFormat.format(new Date()));
             }
             }
 
 
-            String imagePath = "D://games/3492.jpg";
-            // 将图片文件读取为字节数组
+            // ========== 2. 读取本地图片 ==========
+            String imagePath = "C:\\Users\\f\\Downloads\\45_平面图.jpg";
             byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
             byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
 
 
-            // 检查文件大小(不超过5MB)
-            if (vo.getFloorFile() != null && imageBytes.length > 5 * 1024 * 1024) {
-                log.error("楼层平面图文件大小超过5MB限制,FileID: {}", vo.getFloorFileID());
+            // 大小校验 ≤5MB
+            if (imageBytes.length > 5 * 1024 * 1024) {
+                System.err.println("文件超过5MB");
                 return false;
                 return false;
             }
             }
 
 
-            HashMap<String, Object> map = new HashMap<>();
+            // 格式校验
+            if (!Arrays.asList("jpg", "jpeg", "png").contains(vo.getFloorFileSuffix().toLowerCase())) {
+                System.err.println("不支持的格式");
+                return false;
+            }
+
+            // 获取宽高
+            BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
+            int width = image.getWidth();
+            int height = image.getHeight();
+
+            // ========== 3. 时间格式化 ==========
+
+            // ========== 4. 构建标准JSON消息体 ==========
+            Map<String, Object> map = new HashMap<>();
             map.put("dataPacketID", vo.getDataPacketID());
             map.put("dataPacketID", vo.getDataPacketID());
             map.put("engineeringID", vo.getEngineeringID());
             map.put("engineeringID", vo.getEngineeringID());
             map.put("floor", vo.getFloor());
             map.put("floor", vo.getFloor());
             map.put("floorFileID", vo.getFloorFileID());
             map.put("floorFileID", vo.getFloorFileID());
             map.put("floorFileName", vo.getFloorFileName());
             map.put("floorFileName", vo.getFloorFileName());
             map.put("floorFileSuffix", vo.getFloorFileSuffix());
             map.put("floorFileSuffix", vo.getFloorFileSuffix());
-            map.put("filePixWidth", vo.getFilePixWidth());
-            map.put("filePixHeight", vo.getFilePixHeight());
+            map.put("filePixWidth", width);
+            map.put("filePixHeight", height);
             map.put("floorFile", imageBytes);
             map.put("floorFile", imageBytes);
             map.put("publishTime", vo.getPublishTime());
             map.put("publishTime", vo.getPublishTime());
+
+            //使用Gson:
             Gson gson = new Gson();
             Gson gson = new Gson();
-            // 将字节数组转换为Base64编码
-            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
-            vo.setFloorFile(imageBytes);
-//            jsonObject.put("floorFile", imageBytes);
-            if (vo.getFloorFile() != null) {
-                // 使用Base64编码传输二进制数据
-                String base64File = java.util.Base64.getEncoder().encodeToString(vo.getFloorFile());
-                jsonObject.put("floorFile", imageBytes);
-            }
 
 
-            String json = jsonObject.toJSONString();
-            System.out.println(gson.toJson(map));
+            // ========== 5. MQTT发送(修复版) ==========
             String topic = "base/floorPlane";
             String topic = "base/floorPlane";
+            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh("3101100021", "SIixzph1");
 
 
-            log.info("发送楼层平面图信息,Topic: {}, FileID: {}, FileSize: {} bytes",
-                    topic, vo.getFloorFileID(),
-                    vo.getFloorFile() != null ? vo.getFloorFile().length : 0);
-            mqttGateway.sendToMqtt(topic, gson.toJson(map));
+            // 发送JSON字符串
+            gateway.sendToMqtt(topic, gson.toJson(map));
 
 
+            System.out.println("✅ MQTT发送成功 TOPIC: " + topic);
             return true;
             return true;
+
         } catch (Exception e) {
         } catch (Exception e) {
-            log.error("发送楼层平面图信息失败,FileID: {}", vo.getFloorFileID(), e);
+            // 打印完整异常
+            e.printStackTrace();
+            System.err.println("❌ 发送失败:" + e.getMessage());
             return false;
             return false;
         }
         }
     }
     }
@@ -223,19 +244,21 @@ public class BaseDataTransferService {
             userIdToName.put(702, 31);
             userIdToName.put(702, 31);
             userIdToName.put(703, 33);
             userIdToName.put(703, 33);
             userIdToName.put(704, 11);
             userIdToName.put(704, 11);
+            userIdToName.put(705, 11);
             userIdToName.put(707, 19);
             userIdToName.put(707, 19);
             userIdToName.put(708, 19);
             userIdToName.put(708, 19);
             userIdToName.put(709, 15);
             userIdToName.put(709, 15);
             userIdToName.put(710, 16);
             userIdToName.put(710, 16);
             userIdToName.put(711, 2);
             userIdToName.put(711, 2);
-            //userIdToName.put(712, 34);
-            //userIdToName.put(713, 36);
+            // userIdToName.put(712, 34);
+            // userIdToName.put(713, 36);
             userIdToName.put(714, 37);
             userIdToName.put(714, 37);
+            userIdToName.put(716, 26);
 
 
             HashMap<String, Object> map = new HashMap<>();
             HashMap<String, Object> map = new HashMap<>();
             map.put("dataPacketID", generateDataPacketID());
             map.put("dataPacketID", generateDataPacketID());
-            map.put("engineeringID", Long.parseLong(engineeringID));
-            map.put("floor", "B2");
+            map.put("engineeringID", vo.getEngineeringID());
+            map.put("floor", vo.getFloor());
             map.put("floorFileID", 1);
             map.put("floorFileID", 1);
             map.put("sensorID", Integer.parseInt(vo.getDeviceId()));
             map.put("sensorID", Integer.parseInt(vo.getDeviceId()));
             map.put("sensorNo", vo.getDeviceUuid());
             map.put("sensorNo", vo.getDeviceUuid());
@@ -253,7 +276,8 @@ public class BaseDataTransferService {
             String topic = "base/sensorInfo";
             String topic = "base/sensorInfo";
             System.out.println(gson.toJson(map));
             System.out.println(gson.toJson(map));
 //            log.info("发送智能监管物联设施信息,Topic: {}, SensorID: {}", topic, vo.getSensorID());
 //            log.info("发送智能监管物联设施信息,Topic: {}, SensorID: {}", topic, vo.getSensorID());
-            mqttGateway.sendToMqtt(topic, gson.toJson(map));
+            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(vo.getUserName(), vo.getPassword());
+            gateway.sendToMqtt(topic, gson.toJson(map));
 
 
             return true;
             return true;
         } catch (Exception e) {
         } catch (Exception e) {
@@ -290,7 +314,7 @@ public class BaseDataTransferService {
      * @param tenantId 租户ID
      * @param tenantId 租户ID
      * @return 成功发送的数量
      * @return 成功发送的数量
      */
      */
-    public Map<String, Integer> batchSendSensorInfos(Integer tenantId) {
+    public Map<String, Integer> batchSendSensorInfos(Integer tenantId, Long engineeringId, String username, String password) {
         List<BaseBuildFacility> list = baseBuildFacilityService.facilityInfo(tenantId);
         List<BaseBuildFacility> list = baseBuildFacilityService.facilityInfo(tenantId);
         List<DmpDevice> list1 = dmpDeviceInfoService.deviceInfo(tenantId);
         List<DmpDevice> list1 = dmpDeviceInfoService.deviceInfo(tenantId);
         List<FacilityDeviceVO> list2 = new ArrayList<>();
         List<FacilityDeviceVO> list2 = new ArrayList<>();
@@ -309,6 +333,9 @@ public class BaseDataTransferService {
                         facilityDeviceVO.setDeviceUuid(list1.get(k).getDeviceUuid());
                         facilityDeviceVO.setDeviceUuid(list1.get(k).getDeviceUuid());
                         facilityDeviceVO.setFacilityDesc(list.get(j).getFacilityDesc());
                         facilityDeviceVO.setFacilityDesc(list.get(j).getFacilityDesc());
                         facilityDeviceVO.setDeviceType(list1.get(k).getDeviceType());
                         facilityDeviceVO.setDeviceType(list1.get(k).getDeviceType());
+                        facilityDeviceVO.setEngineeringID(engineeringId);
+                        facilityDeviceVO.setUserName(username);
+                        facilityDeviceVO.setPassword(password);
                         list2.add(facilityDeviceVO);
                         list2.add(facilityDeviceVO);
                     }
                     }
                 }
                 }

+ 368 - 221
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java

@@ -3,12 +3,13 @@ package com.usky.cdi.service.impl;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.nacos.shaded.com.google.gson.Gson;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -17,6 +18,7 @@ import com.usky.cdi.service.enums.MqttTopics;
 import com.usky.cdi.mapper.*;
 import com.usky.cdi.mapper.*;
 import com.usky.cdi.service.CdiDeliveryLogService;
 import com.usky.cdi.service.CdiDeliveryLogService;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
+import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.SyncTaskStatisticsVO;
 import com.usky.cdi.service.vo.SyncTaskStatisticsVO;
 import com.usky.cdi.service.vo.info.FloorPlaneVO;
 import com.usky.cdi.service.vo.info.FloorPlaneVO;
 import com.usky.cdi.service.vo.info.ProtectiveUnitVO;
 import com.usky.cdi.service.vo.info.ProtectiveUnitVO;
@@ -29,15 +31,20 @@ import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Value;
 
 
+import javax.annotation.PostConstruct;
 import javax.imageio.ImageIO;
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.File;
 import java.io.IOException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
 import java.math.BigDecimal;
+import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLConnection;
+import java.net.URLEncoder;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.*;
@@ -84,108 +91,60 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     @Autowired
     @Autowired
     private MqttConnectionTool mqttConnectionTool;
     private MqttConnectionTool mqttConnectionTool;
 
 
+    // 从配置文件读取Snowflake参数,默认值为1
+    @Value("${snowflake.worker-id:1}")
+    private long workerId;
+
+    @Value("${snowflake.data-center-id:1}")
+    private long dataCenterId;
+
+    private SnowflakeIdGenerator idGenerator;
+
+    @PostConstruct
+    public void init() {
+        this.idGenerator = new SnowflakeIdGenerator(workerId, dataCenterId);
+    }
+
+    /**
+     * 生成数据包ID
+     */
+    private Long generateDataPacketID() {
+        return idGenerator.nextPacketId();
+    }
+
     @Override
     @Override
     public List<SyncTaskStatisticsVO> selectById(Long id) {
     public List<SyncTaskStatisticsVO> selectById(Long id) {
-        // 1. 租户ID校验(必须非空,无租户直接返回空列表)
         Integer tenantId = SecurityUtils.getTenantId();
         Integer tenantId = SecurityUtils.getTenantId();
-        if (tenantId == null) {
+        if (tenantId == null || tenantId <= 0) {
             log.warn("未获取到当前租户ID,无法查询人防投递日志");
             log.warn("未获取到当前租户ID,无法查询人防投递日志");
             return Collections.emptyList();
             return Collections.emptyList();
         }
         }
 
 
-        // 2. 动态构建查询条件:id为null时只查租户,id不为null时租户+id精准查
-        // 【小优化】按ID倒序,后续取最新数据更直观(ID自增则大ID是最新)
         List<CdiDeliveryLog> logList = lambdaQuery()
         List<CdiDeliveryLog> logList = lambdaQuery()
                 .eq(CdiDeliveryLog::getTenantId, tenantId)
                 .eq(CdiDeliveryLog::getTenantId, tenantId)
                 .eq(id != null, CdiDeliveryLog::getId, id)
                 .eq(id != null, CdiDeliveryLog::getId, id)
-                .orderByDesc(CdiDeliveryLog::getId) // 改为倒序,优先最新数据
+                .orderByDesc(CdiDeliveryLog::getId)
                 .list();
                 .list();
 
 
         LambdaQueryWrapper<CdiDefenseProject> queryWrapper = new LambdaQueryWrapper<>();
         LambdaQueryWrapper<CdiDefenseProject> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(CdiDefenseProject::getTenantId, tenantId);
         queryWrapper.eq(CdiDefenseProject::getTenantId, tenantId);
-        // 【空指针防护】新增非空判断,避免selectOne返回null时报错
         CdiDefenseProject defenseProject = cdiDefenseProjectMapper.selectOne(queryWrapper);
         CdiDefenseProject defenseProject = cdiDefenseProjectMapper.selectOne(queryWrapper);
         boolean isEnable = defenseProject != null && defenseProject.getIsEnable() == 0;
         boolean isEnable = defenseProject != null && defenseProject.getIsEnable() == 0;
 
 
         List<SyncTaskStatisticsVO> finalResult = new ArrayList<>();
         List<SyncTaskStatisticsVO> finalResult = new ArrayList<>();
 
 
-        // 3. 日志集合判空:无数据查询设备表(原逻辑不变)
-        if (CollectionUtils.isEmpty(logList)) {
-
-            // 单元数据
-            List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
-            SyncTaskStatisticsVO vo1 = new SyncTaskStatisticsVO();
-            vo1.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
-            vo1.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
-            vo1.setDataType(1);
-            vo1.setTotal(buildUnitList.size());
-            vo1.setSuccessNumber(0);
-            vo1.setFailNumber(0);
-            vo1.setNotSynced(0);
-            vo1.setState(isEnable ? 1 : 0);
-            finalResult.add(vo1);
-
-            // 平面图
-            List<Integer> buildIdList = getBuildList(tenantId).stream().map(BaseBuild::getId).collect(Collectors.toList());
-            List<BaseBuildPlane> buildPlaneList = getBuildPlaneList(buildIdList);
-            SyncTaskStatisticsVO vo4 = new SyncTaskStatisticsVO();
-            vo4.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
-            vo4.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
-            vo4.setDataType(2);
-            vo4.setTotal(buildPlaneList.size());
-            vo4.setSuccessNumber(0);
-            vo4.setFailNumber(0);
-            vo4.setNotSynced(0);
-            vo4.setState(isEnable ? 1 : 0);
-            finalResult.add(vo4);
-
-            // 设施数据
-            List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
-            SyncTaskStatisticsVO vo2 = new SyncTaskStatisticsVO();
-            vo2.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
-            vo2.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
-            vo2.setDataType(3);
-            vo2.setTotal(buildFacilityList.size());
-            vo2.setSuccessNumber(0);
-            vo2.setFailNumber(0);
-            vo2.setNotSynced(0);
-            vo2.setState(isEnable ? 1 : 0);
-            finalResult.add(vo2);
-
-            // 监测数据
-            SyncTaskStatisticsVO vo3 = new SyncTaskStatisticsVO();
-            vo3.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
-            vo3.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
-            vo3.setDataType(4);
-            vo3.setTotal(buildFacilityList.size());
-            vo3.setSuccessNumber(0);
-            vo3.setFailNumber(0);
-            vo3.setNotSynced(0);
-            vo3.setState(isEnable ? 1 : 0);
-            finalResult.add(vo3);
-
-            SyncTaskStatisticsVO vo5 = new SyncTaskStatisticsVO();
-            vo5.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
-            vo5.setTopic(MqttTopics.Alarm.MESSAGE.getTopic());
-            vo5.setDataType(5);
-            vo5.setTotal(0);
-            vo5.setSuccessNumber(0);
-            vo5.setFailNumber(0);
-            vo5.setNotSynced(0);
-            vo5.setState(isEnable ? 1 : 0);
-            finalResult.add(vo5);
+        // 预先查询各类型的基础数据量(无日志时使用)
+        List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
+        List<BaseBuild> buildList = getBuildList(tenantId);
+        List<Integer> buildIdList = buildList.stream().map(BaseBuild::getId).collect(Collectors.toList());
+        List<BaseBuildPlane> buildPlaneList = buildIdList.isEmpty() ? Collections.emptyList() : getBuildPlaneList(buildIdList);
+        List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
 
 
+        if (CollectionUtils.isEmpty(logList)) {
+            fillEmptyStatistics(finalResult, tenantId, isEnable);
             return finalResult;
             return finalResult;
         }
         }
 
 
-        // 优先自动解析:JSON数组直接转VO列表,简洁高效
-        //     JSONArray jsonArray = JSONUtil.parseArray(jsonContent);
-        //     List<SyncTaskStatisticsVO> autoParseList = JSONUtil.toList(jsonArray, SyncTaskStatisticsVO.class);
-        //     finalResult.addAll(autoParseList);
-        //     log.info("租户ID:{} 日志ID:{} 自动解析成功,解析出{}条同步统计数据", tenantId, logId, autoParseList.size());
-
-        // ########## 核心改造:按dataType分组,取每种类型最新的一条日志 ##########
-        // 步骤1:先过滤出infoContent非空的有效日志(提前过滤,减少分组计算量)
         List<CdiDeliveryLog> validLogList = logList.stream()
         List<CdiDeliveryLog> validLogList = logList.stream()
                 .filter(logEntity -> logEntity != null
                 .filter(logEntity -> logEntity != null
                         && StrUtil.isNotBlank(logEntity.getInfoContent())
                         && StrUtil.isNotBlank(logEntity.getInfoContent())
@@ -193,130 +152,185 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 .collect(Collectors.toList());
                 .collect(Collectors.toList());
 
 
         if (CollectionUtils.isEmpty(validLogList)) {
         if (CollectionUtils.isEmpty(validLogList)) {
-            log.warn("租户ID:{} 查询到{}条日志,但所有日志的JSON内容为空或dataType为空,查询ID:{}", tenantId, logList.size(), id);
-            return Collections.emptyList();
+            log.warn("租户ID:{} 查询到{}条日志,但所有日志的JSON内容为空或dataType为空", tenantId, logList.size());
+            fillEmptyStatistics(finalResult, tenantId, isEnable);
+            return finalResult;
         }
         }
 
 
-        // 步骤2:按dataType分组,每组按ID倒序(最新)取第一条,保证1-4类型各一条
-        Map<Integer, CdiDeliveryLog> latestLogByType = validLogList.stream()
-                .collect(Collectors.groupingBy(
-                        CdiDeliveryLog::getDataType, // 分组键:dataType(1-4)
-                        Collectors.collectingAndThen(
-                                Collectors.maxBy(Comparator.comparingLong(CdiDeliveryLog::getId)), // 取组内ID最大的(最新)
-                                opt -> opt.orElse(null) // 空值处理
-                        )
-                ));
+        // ★ 改动1:按 dataType 分组,保留每种类型的全部日志
+        Map<Integer, List<CdiDeliveryLog>> allLogsByType = validLogList.stream()
+                .collect(Collectors.groupingBy(CdiDeliveryLog::getDataType));
 
 
-        // 步骤3:转换为列表,仅保留1-4类型的最新日志(过滤非目标类型)
-        List<CdiDeliveryLog> finalValidLogList = new ArrayList<>();
         for (int type = 1; type <= 5; type++) {
         for (int type = 1; type <= 5; type++) {
-            CdiDeliveryLog latestLog = latestLogByType.get(type);
-            if (latestLog != null) {
-                finalValidLogList.add(latestLog);
+            List<CdiDeliveryLog> logsForType = allLogsByType.get(type);
+
+            // 创建基础 VO,设置 dataType 和 topic
+            SyncTaskStatisticsVO vo = createEmptyVO(type, isEnable);
+
+            // ★ total 始终从数据库查询获取(单元表、平面图表等),与日志记录无关
+            switch (type) {
+                case 1:
+                    vo.setTotal(buildUnitList.size());
+                    break;
+                case 2:
+                    vo.setTotal(buildPlaneList.size());
+                    break;
+                case 3:
+                case 4:
+                    vo.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
+                    break;
+                case 5:
+                default:
+                    break;
             }
             }
-            // 若某类型无日志,无需处理:后续解析后若该类型无数据,是否补空VO可按需求调整,原逻辑是返回解析到的内容
-        }
 
 
-        if (CollectionUtils.isEmpty(finalValidLogList)) {
-            log.warn("租户ID:{} 无1-4类型的有效日志,查询ID:{}", tenantId, id);
-            return Collections.emptyList();
-        }
-        // ########## 核心改造结束 ##########
-
-        // 5. 遍历【每种类型最新的日志】,解析JSON并合并结果(单日志解析失败不影响其他)
-        for (CdiDeliveryLog deliveryLog : finalValidLogList) {
-            Long logId = deliveryLog.getId();
-            String jsonContent = deliveryLog.getInfoContent();
-            try {
-                JSONArray array = JSONUtil.parseArray(jsonContent);
-                List<SyncTaskStatisticsVO> manualParseList = new ArrayList<>(array.size());
-                for (int i = 0; i < array.size(); i++) {
-                    JSONObject obj = array.getJSONObject(i);
-                    SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
-                    // 手动映射字段:按实际入库的JSON字段名匹配
-                    vo.setId(obj.getLong("id"));
-                    vo.setTopic(obj.getStr("topic"));
-                    switch (obj.getInt("dataType")) {
-                        case 1:
-                            vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
-                            break;
-                        case 2:
-                            vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
-                            break;
-                        case 3:
-                            vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
-                            break;
-                        case 4:
-                            vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
-                            break;
-                        case 5:
-                            vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
-                            break;
+            if (logsForType != null && !logsForType.isEmpty()) {
+                // ★ 有日志时:按类型累加 success / fail / notSynced
+                int sumSuccess = 0, sumFail = 0, sumNotSynced = 0;
+
+                for (CdiDeliveryLog logEntry : logsForType) {
+                    try {
+                        // ★ infoContent 是 JSON 对象,用 parseObj 解析
+                        JSONObject o = JSONUtil.parseObj(logEntry.getInfoContent());
+                        sumSuccess += o.getInt("successNumber");
+                        sumFail += o.getInt("failNumber");
+                        sumNotSynced += o.getInt("notSynced");
+                    } catch (Exception ignored) {
+                        // 单条解析失败不影响其他日志的累加
+                    }
+                }
+
+                vo.setSuccessNumber(sumSuccess);
+                vo.setFailNumber(sumFail);
+                vo.setNotSynced(sumNotSynced);
+
+                // 取最新一条日志作为元数据来源(createTime / costTime 等)
+                CdiDeliveryLog latestLog = logsForType.stream()
+                        .max(Comparator.comparingLong(CdiDeliveryLog::getId))
+                        .orElse(null);
+
+                if (latestLog != null) {
+                    try {
+                        // ★ infoContent 是 JSON 对象,用 parseObj 解析
+                        JSONObject obj = JSONUtil.parseObj(latestLog.getInfoContent());
+                        vo.setId(obj.getLong("id"));
+                        vo.setCreateTime(obj.getStr("createTime"));
+                        vo.setCostTime(obj.getFloat("costTime"));
+                        vo.setState(isEnable ? 1 : obj.getInt("state"));
+                    } catch (Exception ex) {
+                        log.error("租户ID:{} dataType:{} 解析最新日志元数据失败", tenantId, type, ex);
                     }
                     }
-                    vo.setDataType(obj.getInt("dataType"));
-                    vo.setTotal(obj.getInt("total"));
-                    vo.setSuccessNumber(obj.getInt("successNumber"));
-                    vo.setFailNumber(obj.getInt("failNumber"));
-                    vo.setNotSynced(obj.getInt("notSynced"));
-                    vo.setCreateTime(obj.getStr("createTime"));
-                    vo.setCostTime(obj.getFloat("costTime"));
-                    vo.setState(isEnable ? 1 : obj.getInt("state"));
-                    manualParseList.add(vo);
                 }
                 }
-                finalResult.addAll(manualParseList);
-                log.info("租户ID:{} 日志ID:{} 手动解析成功,解析出{}条同步统计数据", tenantId, logId, manualParseList.size());
-            } catch (Exception ex) {
-                // 单日志解析失败,仅打印日志,继续解析其他日志
-                log.error("租户ID:{} 日志ID:{} 解析失败,跳过该日志", tenantId, logId, ex);
+
+                log.info("租户ID:{} dataType:{} 共{}条日志, 聚合结果: total={}, success={}, fail={}, notSynced={}",
+                        tenantId, type, logsForType.size(), vo.getTotal(), sumSuccess, sumFail, sumNotSynced);
+
+            } else {
+                // ★ 无日志时:success/fail/notSynced 保持为0(createEmptyVO已设置)
             }
             }
+
+            finalResult.add(vo);
         }
         }
 
 
-        // // 6. 【可选优化】若解析后部分类型缺失,补全空VO(和无日志时格式完全一致)
-        // // 提取已解析的dataType
-        // Set<Integer> parsedTypes = finalResult.stream()
-        //         .map(SyncTaskStatisticsVO::getDataType)
-        //         .filter(Objects::nonNull)
-        //         .collect(Collectors.toSet());
-        // // 补全1-4中缺失的类型,设置默认值(和无日志时一致)
-        // for (int type = 1; type <= 3; type++) {
-        //     if (!parsedTypes.contains(type)) {
-        //         SyncTaskStatisticsVO emptyVo = new SyncTaskStatisticsVO();
-        //         // 按类型设置名称、主题,和情况一保持一致
-        //         switch (type) {
-        //             case 1:
-        //                 emptyVo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
-        //                 emptyVo.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
-        //                 break;
-        //             case 2:
-        //                 emptyVo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
-        //                 emptyVo.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
-        //                 break;
-        //             case 3:
-        //                 emptyVo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
-        //                 emptyVo.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
-        //                 break;
-        //             // case 4:
-        //             //     emptyVo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
-        //             //     emptyVo.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
-        //             //     break;
-        //         }
-        //         emptyVo.setDataType(type);
-        //         emptyVo.setTotal(0);
-        //         emptyVo.setSuccessNumber(0);
-        //         emptyVo.setFailNumber(0);
-        //         emptyVo.setNotSynced(0);
-        //         emptyVo.setState(isEnable ? 1 : 0);
-        //         finalResult.add(emptyVo);
-        //     }
-        // }
-
-        // 对结果按dataType排序(1-4),和情况一返回顺序一致
         finalResult.sort(Comparator.comparingInt(SyncTaskStatisticsVO::getDataType));
         finalResult.sort(Comparator.comparingInt(SyncTaskStatisticsVO::getDataType));
-
-        // 6. 返回结果
         return finalResult;
         return finalResult;
     }
     }
 
 
+    private void fillEmptyStatistics(List<SyncTaskStatisticsVO> result, Integer tenantId, boolean isEnable) {
+        List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
+        SyncTaskStatisticsVO vo1 = createEmptyVO(1, isEnable);
+        vo1.setTotal(buildUnitList.size());
+        result.add(vo1);
+
+        List<Integer> buildIdList = getBuildList(tenantId).stream().map(BaseBuild::getId).collect(Collectors.toList());
+        List<BaseBuildPlane> buildPlaneList = buildIdList.isEmpty() ? Collections.emptyList() : getBuildPlaneList(buildIdList);
+        SyncTaskStatisticsVO vo2 = createEmptyVO(2, isEnable);
+        vo2.setTotal(buildPlaneList.size());
+        result.add(vo2);
+
+        List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
+        SyncTaskStatisticsVO vo3 = createEmptyVO(3, isEnable);
+        vo3.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
+        result.add(vo3);
+
+        SyncTaskStatisticsVO vo4 = createEmptyVO(4, isEnable);
+        vo4.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
+        result.add(vo4);
+
+        SyncTaskStatisticsVO vo5 = createEmptyVO(5, isEnable);
+        result.add(vo5);
+    }
+
+    private SyncTaskStatisticsVO createEmptyVO(int dataType, boolean isEnable) {
+        SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
+        vo.setDataType(dataType);
+        vo.setSuccessNumber(0);
+        vo.setFailNumber(0);
+        vo.setNotSynced(0);
+        vo.setState(isEnable ? 1 : 0);
+
+        switch (dataType) {
+            case 1:
+                vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
+                vo.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
+                break;
+            case 2:
+                vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
+                vo.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
+                break;
+            case 3:
+                vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
+                vo.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
+                break;
+            case 4:
+                vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
+                vo.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
+                break;
+            case 5:
+                vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
+                vo.setTopic(MqttTopics.Alarm.MESSAGE.getTopic());
+                break;
+        }
+        return vo;
+    }
+
+    private SyncTaskStatisticsVO parseSyncStatisticsVO(JSONObject obj, boolean isEnable) {
+        SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
+        vo.setId(obj.getLong("id"));
+        vo.setTopic(obj.getStr("topic"));
+
+        int dataType = obj.getInt("dataType");
+        vo.setDataType(dataType);
+
+        switch (dataType) {
+            case 1:
+                vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
+                break;
+            case 2:
+                vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
+                break;
+            case 3:
+                vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
+                break;
+            case 4:
+                vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
+                break;
+            case 5:
+                vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
+                break;
+        }
+
+        vo.setTotal(obj.getInt("total"));
+        vo.setSuccessNumber(obj.getInt("successNumber"));
+        vo.setFailNumber(obj.getInt("failNumber"));
+        vo.setNotSynced(obj.getInt("notSynced"));
+        vo.setCreateTime(obj.getStr("createTime"));
+        vo.setCostTime(obj.getFloat("costTime"));
+        vo.setState(isEnable ? 1 : obj.getInt("state"));
+
+        return vo;
+    }
+
     @Override
     @Override
     public CommonPage<CdiDeliveryLog> logList(Long id, Integer pageNum, Integer pageSize, Integer dataType, Integer
     public CommonPage<CdiDeliveryLog> logList(Long id, Integer pageNum, Integer pageSize, Integer dataType, Integer
             logType, String startTime, String endTime) {
             logType, String startTime, String endTime) {
@@ -432,6 +446,13 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         Integer tenantId = one.getTenantId();
         Integer tenantId = one.getTenantId();
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime now = LocalDateTime.now();
 
 
+        String userName = "自动同步";
+        try {
+            userName = SecurityUtils.getUsername();
+        } catch (Exception e) {
+            log.error("无法获取用户名或姓名,使用默认‘自动同步’", e);
+        }
+
         switch (vo.getDataType()) {
         switch (vo.getDataType()) {
             // 单元信息
             // 单元信息
             case 1:
             case 1:
@@ -460,7 +481,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 endTime = System.currentTimeMillis();
                 endTime = System.currentTimeMillis();
 
 
                 notSynced = total - success - failure;
                 notSynced = total - success - failure;
-                saveLog(topic, desc, 1, tenantId, engineeringId, now, startTime, endTime, total, success, failure, notSynced, failure > 0 ? 0 : 1);
+                saveLog(topic, desc, 1, tenantId, engineeringId, now, startTime, endTime, total, success, failure, notSynced, failure > 0 ? 0 : 1, userName);
 
 
                 break;
                 break;
             // 平面图信息
             // 平面图信息
@@ -472,10 +493,16 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 int total2 = 0, success2 = 0, failure2 = 0, notSynced2 = 0;
                 int total2 = 0, success2 = 0, failure2 = 0, notSynced2 = 0;
 
 
                 List<FloorPlaneVO> floorPlaneVOS = buildPlanes(tenantId, engineeringId);
                 List<FloorPlaneVO> floorPlaneVOS = buildPlanes(tenantId, engineeringId);
+                if (CollUtil.isEmpty(floorPlaneVOS)) {
+                    log.error("未找到楼层平面图信息!");
+                    break;
+                }
                 total2 = floorPlaneVOS.size();
                 total2 = floorPlaneVOS.size();
                 iotDataTransferService.createMqttConnection(username, password);
                 iotDataTransferService.createMqttConnection(username, password);
                 String topic1 = MqttTopics.Base.FLOOR_PLANE.getTopic();
                 String topic1 = MqttTopics.Base.FLOOR_PLANE.getTopic();
                 String desc1 = MqttTopics.Base.FLOOR_PLANE.getDesc();
                 String desc1 = MqttTopics.Base.FLOOR_PLANE.getDesc();
+
+
                 for (FloorPlaneVO floorPlaneVO : floorPlaneVOS) {
                 for (FloorPlaneVO floorPlaneVO : floorPlaneVOS) {
                     try {
                     try {
                         iotDataTransferService.sendMqttMessage(topic1, floorPlaneVO, desc1, username);
                         iotDataTransferService.sendMqttMessage(topic1, floorPlaneVO, desc1, username);
@@ -488,7 +515,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 endTime2 = System.currentTimeMillis();
                 endTime2 = System.currentTimeMillis();
                 notSynced2 = total2 - success2 - failure2;
                 notSynced2 = total2 - success2 - failure2;
                 saveLog(topic1, desc1, 2, tenantId, engineeringId, now, startTime2, endTime2, total2, success2,
                 saveLog(topic1, desc1, 2, tenantId, engineeringId, now, startTime2, endTime2, total2, success2,
-                        failure2, notSynced2, failure2 > 0 ? 0 : 1);
+                        failure2, notSynced2, failure2 > 0 ? 0 : 1, userName);
 
 
                 break;
                 break;
             // 推送设施信息
             // 推送设施信息
@@ -498,11 +525,11 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 long startTime3, endTime3;
                 long startTime3, endTime3;
                 startTime3 = System.currentTimeMillis();
                 startTime3 = System.currentTimeMillis();
 
 
-                Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId);
+                Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId, engineeringId, username, password);
 
 
                 endTime3 = System.currentTimeMillis();
                 endTime3 = System.currentTimeMillis();
                 saveLog(MqttTopics.Base.SENSOR_INFO.getTopic(), MqttTopics.Base.SENSOR_INFO.getDesc(), 3, tenantId, engineeringId,
                 saveLog(MqttTopics.Base.SENSOR_INFO.getTopic(), MqttTopics.Base.SENSOR_INFO.getDesc(), 3, tenantId, engineeringId,
-                        now, startTime3, endTime3, map.get("total"), map.get("success"), map.get("failure"), map.get("notSynced"), map.get("failure") > 0 ? 0 : 1);
+                        now, startTime3, endTime3, map.get("total"), map.get("success"), map.get("failure"), map.get("notSynced"), map.get("failure") > 0 ? 0 : 1, userName);
 
 
                 break;
                 break;
             // 推送监测数据
             // 推送监测数据
@@ -518,7 +545,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     @Override
     @Override
     @Async("asyncServiceExecutor")
     @Async("asyncServiceExecutor")
     public void saveLog(String topic, String dataTypeName, Integer dataType, Integer tenantId, Long engineeringId, LocalDateTime now, long startTime, long endTime,
     public void saveLog(String topic, String dataTypeName, Integer dataType, Integer tenantId, Long engineeringId, LocalDateTime now, long startTime, long endTime,
-                        int total, int success, int failure, int notSynced, int pushFlag) {
+                        int total, int success, int failure, int notSynced, int pushFlag, String userName) {
 
 
         SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
         SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
         vo.setDataType(dataType);
         vo.setDataType(dataType);
@@ -537,8 +564,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         log.setDataType(dataType);
         log.setDataType(dataType);
         log.setTopic(topic);
         log.setTopic(topic);
         log.setDataTypeName(vo.getDataTypeName());
         log.setDataTypeName(vo.getDataTypeName());
-        log.setUserName(SecurityUtils.getUsername() == null ? "自动同步" : SecurityUtils.getUsername());
-        log.setNickName(SecurityUtils.getUsername() == null ? "自动同步" : SecurityUtils.getLoginUser().getSysUser().getNickName());
+        log.setUserName(userName);
         log.setCreateTime(now);
         log.setCreateTime(now);
         log.setTenantId(tenantId);
         log.setTenantId(tenantId);
         log.setPushFlag(pushFlag);
         log.setPushFlag(pushFlag);
@@ -559,6 +585,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         List<ProtectiveUnitVO> result = new ArrayList<>(buildUnitList.size());
         List<ProtectiveUnitVO> result = new ArrayList<>(buildUnitList.size());
         for (BaseBuildUnit buildUnit : buildUnitList) {
         for (BaseBuildUnit buildUnit : buildUnitList) {
             ProtectiveUnitVO vo = new ProtectiveUnitVO();
             ProtectiveUnitVO vo = new ProtectiveUnitVO();
+            vo.setDataPacketID(generateDataPacketID());
             vo.setEngineeringID(engineeringId);
             vo.setEngineeringID(engineeringId);
             vo.setUnitName(buildUnit.getUnitName());
             vo.setUnitName(buildUnit.getUnitName());
             vo.setFloor(buildUnit.getFloor());
             vo.setFloor(buildUnit.getFloor());
@@ -586,21 +613,42 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         List<Integer> buildIds = buildList.stream().map(BaseBuild::getId).collect(Collectors.toList());
         List<Integer> buildIds = buildList.stream().map(BaseBuild::getId).collect(Collectors.toList());
         List<BaseBuildPlane> buildPlaneList = getBuildPlaneList(buildIds);
         List<BaseBuildPlane> buildPlaneList = getBuildPlaneList(buildIds);
 
 
-        String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
+        if (CollUtil.isEmpty(buildPlaneList)) {
+            return Collections.emptyList();
+        }
+
+        Map<String, BaseBuildPlane> latestPlaneByFloor = buildPlaneList.stream()
+                .collect(Collectors.groupingBy(
+                        BaseBuildPlane::getFloor,
+                        Collectors.collectingAndThen(
+                                Collectors.maxBy(Comparator.comparingInt(BaseBuildPlane::getId)),
+                                opt -> opt.orElse(null)
+                        )
+                ));
 
 
-        List<FloorPlaneVO> result = new ArrayList<>(buildPlaneList.size());
-        for (BaseBuildPlane buildPlane : buildPlaneList) {
+        List<BaseBuildPlane> filteredPlaneList = latestPlaneByFloor.values().stream()
+                .filter(Objects::nonNull)
+                .sorted(Comparator.comparing(BaseBuildPlane::getFloor))
+                .collect(Collectors.toList());
 
 
-            String planeViewUrl = buildPlane.getPlaneViewUrl();
+        log.info("楼层平面图数据过滤:原始{}条,按楼层去重后{}条", buildPlaneList.size(), filteredPlaneList.size());
 
 
+        String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
 
 
+        List<FloorPlaneVO> result = new ArrayList<>(filteredPlaneList.size());
+        for (BaseBuildPlane buildPlane : filteredPlaneList) {
+            String planeViewUrl = buildPlane.getPlaneViewUrl();
             FloorPlaneVO vo = new FloorPlaneVO();
             FloorPlaneVO vo = new FloorPlaneVO();
             checkFileSize(vo, planeViewUrl);
             checkFileSize(vo, planeViewUrl);
+            fillImageInfo(vo, planeViewUrl);
+
+            vo.setDataPacketID(generateDataPacketID());
             vo.setEngineeringID(engineeringId);
             vo.setEngineeringID(engineeringId);
             vo.setFloor(buildPlane.getFloor());
             vo.setFloor(buildPlane.getFloor());
             vo.setFloorFileID(Long.valueOf(buildPlane.getId()));
             vo.setFloorFileID(Long.valueOf(buildPlane.getId()));
-            fillImageInfo(vo, planeViewUrl);
             vo.setPublishTime(time);
             vo.setPublishTime(time);
+
+            result.add(vo);
         }
         }
 
 
         return result;
         return result;
@@ -613,42 +661,141 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     private void checkFileSize(FloorPlaneVO vo, String filePath) {
     private void checkFileSize(FloorPlaneVO vo, String filePath) {
         Assert.notBlank(filePath, "文件路径不能为空");
         Assert.notBlank(filePath, "文件路径不能为空");
 
 
-        long size = FileUtil.size(new File(filePath));
-        if (size > MAX_FILE_SIZE_BYTES) {
-            double sizeMB = size / 1024.0 / 1024.0;
-            throw new BusinessException(
-                    StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
-            );
+        byte[] fileBytes;
+
+        if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
+            fileBytes = downloadFileFromUrl(filePath);
+        } else {
+            File localFile = new File(filePath);
+            if (!localFile.exists()) {
+                log.warn("本地文件不存在: {}", filePath);
+                throw new BusinessException("楼层平面图文件不存在:" + filePath);
+            }
+
+            long size = FileUtil.size(localFile);
+            if (size > MAX_FILE_SIZE_BYTES) {
+                double sizeMB = size / 1024.0 / 1024.0;
+                throw new BusinessException(
+                        StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
+                );
+            }
+            fileBytes = FileUtil.readBytes(localFile);
+        }
+
+        vo.setFloorFile(fileBytes);
+    }
+
+    private byte[] downloadFileFromUrl(String fileUrl) {
+        try {
+            String encodedUrl = encodeUrl(fileUrl);
+            URL url = new URL(encodedUrl);
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("GET");
+            conn.setConnectTimeout(5000);
+            conn.setReadTimeout(10000);
+            conn.setRequestProperty("User-Agent", "Mozilla/5.0");
+
+            int responseCode = conn.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                log.error("下载文件失败,HTTP状态码: {}, URL: {}", responseCode, fileUrl);
+                throw new BusinessException("下载楼层平面图失败,服务器返回状态码:" + responseCode);
+            }
+
+            int contentLength = conn.getContentLength();
+            if (contentLength > MAX_FILE_SIZE_BYTES) {
+                double sizeMB = contentLength / 1024.0 / 1024.0;
+                throw new BusinessException(
+                        StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
+                );
+            }
+
+            try (InputStream inputStream = conn.getInputStream()) {
+                return IoUtil.readBytes(inputStream);
+            }
+        } catch (BusinessException e) {
+            throw e;
+        } catch (IOException e) {
+            log.error("从URL下载文件失败: {}", fileUrl, e);
+            throw new BusinessException("下载楼层平面图失败:" + e.getMessage());
         }
         }
-        vo.setFloorFile(FileUtil.readBytes(filePath));
     }
     }
 
 
-    // 获取图片信息
+    private String encodeUrl(String url) throws UnsupportedEncodingException {
+        if (StrUtil.isBlank(url)) {
+            return url;
+        }
+
+        if (!(url.startsWith("http://") || url.startsWith("https://"))) {
+            return url;
+        }
+
+        int protocolEnd = url.indexOf("://");
+        String protocol = url.substring(0, protocolEnd + 3);
+        String rest = url.substring(protocolEnd + 3);
+
+        int firstSlash = rest.indexOf("/");
+        if (firstSlash == -1) {
+            return url;
+        }
+
+        String hostAndPort = rest.substring(0, firstSlash);
+        String path = rest.substring(firstSlash);
+
+        String[] pathSegments = path.split("/");
+        StringBuilder encodedPath = new StringBuilder();
+        for (int i = 0; i < pathSegments.length; i++) {
+            if (i > 0) {
+                encodedPath.append("/");
+            }
+            encodedPath.append(URLEncoder.encode(pathSegments[i], "UTF-8"));
+        }
+
+        return protocol + hostAndPort + encodedPath.toString();
+    }
+
     private void fillImageInfo(FloorPlaneVO vo, String imageUrl) {
     private void fillImageInfo(FloorPlaneVO vo, String imageUrl) {
         if (StrUtil.isBlank(imageUrl)) {
         if (StrUtil.isBlank(imageUrl)) {
             return;
             return;
         }
         }
 
 
-        // 提取文件名信息
         String fileName = FileUtil.getName(imageUrl);
         String fileName = FileUtil.getName(imageUrl);
         vo.setFloorFileName(FileUtil.mainName(fileName));
         vo.setFloorFileName(FileUtil.mainName(fileName));
         vo.setFloorFileSuffix(FileUtil.extName(fileName));
         vo.setFloorFileSuffix(FileUtil.extName(fileName));
 
 
-        // 读取像素尺寸(带超时控制)
         try {
         try {
-            URLConnection conn = new URL(imageUrl).openConnection();
-            conn.setConnectTimeout(3000);
-            conn.setReadTimeout(5000);
-
-            try (InputStream in = conn.getInputStream()) {
-                BufferedImage image = ImageIO.read(in);
-                if (image != null) {
-                    vo.setFilePixWidth(image.getWidth());
-                    vo.setFilePixHeight(image.getHeight());
+            BufferedImage image;
+            if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
+                String encodedUrl = encodeUrl(imageUrl);
+                URL url = new URL(encodedUrl);
+                URLConnection conn = url.openConnection();
+                conn.setConnectTimeout(3000);
+                conn.setReadTimeout(5000);
+
+                try (InputStream in = conn.getInputStream()) {
+                    image = ImageIO.read(in);
                 }
                 }
+            } else {
+                File imageFile = new File(imageUrl);
+                if (imageFile.exists()) {
+                    image = ImageIO.read(imageFile);
+                } else {
+                    log.warn("图片文件不存在: {}", imageUrl);
+                    vo.setFilePixWidth(7016);
+                    vo.setFilePixHeight(9933);
+                    return;
+                }
+            }
+
+            if (image != null) {
+                vo.setFilePixWidth(image.getWidth());
+                vo.setFilePixHeight(image.getHeight());
+            } else {
+                log.warn("无法读取图片尺寸: {}", imageUrl);
+                vo.setFilePixWidth(7016);
+                vo.setFilePixHeight(9933);
             }
             }
         } catch (IOException e) {
         } catch (IOException e) {
-            log.error("获取图片尺寸失败: {}", imageUrl);
+            log.error("获取图片尺寸失败: {}", imageUrl, e);
             vo.setFilePixWidth(7016);
             vo.setFilePixWidth(7016);
             vo.setFilePixHeight(9933);
             vo.setFilePixHeight(9933);
         }
         }

+ 134 - 11
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -12,8 +12,6 @@ import com.usky.cdi.mapper.CdiDefenseProjectMapper;
 import com.usky.cdi.mapper.CdiDeliveryLogMapper;
 import com.usky.cdi.mapper.CdiDeliveryLogMapper;
 import com.usky.cdi.mapper.DmpDeviceMapper;
 import com.usky.cdi.mapper.DmpDeviceMapper;
 import com.usky.cdi.mapper.DmpProductMapper;
 import com.usky.cdi.mapper.DmpProductMapper;
-import com.usky.cdi.service.CdiDeliveryLogService;
-import com.usky.cdi.service.config.mqtt.MqttOutConfig;
 import com.usky.cdi.service.enums.MqttTopics;
 import com.usky.cdi.service.enums.MqttTopics;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.util.DeviceDataQuery;
 import com.usky.cdi.service.util.DeviceDataQuery;
@@ -50,8 +48,6 @@ import java.util.stream.Collectors;
 @Service
 @Service
 public class IotDataTransferService {
 public class IotDataTransferService {
 
 
-    private MqttOutConfig.MqttGateway mqttGateway;
-
     @Autowired
     @Autowired
     private MqttConnectionTool mqttConnectionTool;
     private MqttConnectionTool mqttConnectionTool;
 
 
@@ -576,7 +572,7 @@ public class IotDataTransferService {
     }
     }
 
 
     /**
     /**
-     * 推送温度信息(701
+     * 推送温度信息(707
      *
      *
      * @param deviceDataItem 设备数据
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
      * @param deviceId 设备ID
@@ -597,11 +593,12 @@ public class IotDataTransferService {
         tempVO.setPublishTime(getCurrentTime());
         tempVO.setPublishTime(getCurrentTime());
         tempVO.setSensorValue(value);
         tempVO.setSensorValue(value);
         tempVO.setDataEndTime(dataEndTime);
         tempVO.setDataEndTime(dataEndTime);
+        System.out.println("监测时间:" + dataEndTime);
         sendMqttMessage(MqttTopics.IotInfo.TEMP.getTopic(), tempVO, MqttTopics.IotInfo.TEMP.getDesc(), username);
         sendMqttMessage(MqttTopics.IotInfo.TEMP.getTopic(), tempVO, MqttTopics.IotInfo.TEMP.getDesc(), username);
     }
     }
 
 
     /**
     /**
-     * 推送湿度信息(702
+     * 推送湿度信息(708
      *
      *
      * @param deviceDataItem 设备数据
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
      * @param deviceId 设备ID
@@ -651,7 +648,7 @@ public class IotDataTransferService {
     }
     }
 
 
     /**
     /**
-     * 推送一氧化碳浓度信息(706
+     * 推送一氧化碳浓度信息(711
      *
      *
      * @param deviceDataItem 设备数据
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
      * @param deviceId 设备ID
@@ -676,7 +673,7 @@ public class IotDataTransferService {
     }
     }
 
 
     /**
     /**
-     * 推送二氧化碳浓度信息(707
+     * 推送二氧化碳浓度信息(710)
      *
      *
      * @param deviceDataItem 设备数据
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
      * @param deviceId 设备ID
@@ -929,6 +926,91 @@ public class IotDataTransferService {
         }
         }
     }
     }
 
 
+    /**
+     * 发送液位数据(716)
+     *
+     * @return 推送结果,包含成功数和失败数
+     **/
+    public Map<String, Integer> sendWaterLevel(IotDataTransferVO transferVO) {
+        Map<String, Integer> result = new HashMap<>();
+        result.put("successCount", 0);
+        result.put("failureCount", 0);
+
+        if (!validateMqttGateway(transferVO.getUsername())) {
+            return result;
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+        long endTime;
+
+        List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
+        Integer deviceType = transferVO.getDeviceType();
+        Integer totalDevices = transferVO.getDevices().size();
+
+        try {
+
+            log.info("开始推送集水井水位数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
+                    deviceType, totalDevices, deviceData.size());
+
+            if (deviceData.isEmpty()) {
+                log.warn("没有获取到位移数据!设备类型:{}", deviceType);
+                result.put("failureCount", totalDevices);
+                return result;
+            }
+
+            Long engineeringId = transferVO.getEngineeringId();
+            for (JSONObject deviceDataItem : deviceData) {
+                LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
+                if (dataEndTime == null) {
+                    result.put("failureCount", result.get("failureCount") + 1);
+                    continue;
+                }
+
+                Integer deviceId = deviceDataItem.getIntValue("device_id");
+                Double value = deviceDataItem.getDouble("sensorValue");
+                if (value == null) {
+                    log.warn("设备{}的水位数据为空", deviceId);
+                    result.put("failureCount", result.get("failureCount") + 1);
+                    continue;
+                }
+
+                WaterLevelVO vo = new WaterLevelVO();
+                vo.setDataPacketID(generateDataPacketID());
+                vo.setSensorID(deviceId);
+                vo.setEngineeringID(engineeringId);
+                vo.setPublishTime(getCurrentTime());
+                vo.setDataEndTime(dataEndTime);
+                vo.setSensorValue(value);
+
+                try {
+                    sendMqttMessage(MqttTopics.IotInfo.SEWAGE_LEVEL.getTopic(), vo, MqttTopics.IotInfo.SEWAGE_LEVEL.getDesc(), transferVO.getUsername());
+                    result.put("successCount", result.get("successCount") + 1);
+                } catch (Exception e) {
+                    log.warn("设备{}的水位数据推送失败:{}", deviceId, e.getMessage());
+                    result.put("failureCount", result.get("failureCount") + 1);
+                }
+            }
+
+            log.info("水位数据推送完成,设备类型:{},成功:{},失败:{}",
+                    deviceType, result.get("successCount"), result.get("failureCount"));
+
+            endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    result.get("successCount"), result.get("failureCount"),
+                    totalDevices - result.get("successCount") - result.get("failureCount"), 1, SecurityUtils.getUsername());
+            return result;
+        } catch (Exception e) {
+            log.error("水位数据推送发生异常", e);
+            result.put("failureCount", transferVO.getDevices().size());
+            endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    result.get("successCount"), result.get("failureCount"),
+                    totalDevices - result.get("successCount") - result.get("failureCount"), 0, SecurityUtils.getUsername());
+            return result;
+        }
+    }
+
     /**
     /**
      * 同步设备数据
      * 同步设备数据
      * @param tenantId 租户ID
      * @param tenantId 租户ID
@@ -1035,7 +1117,9 @@ public class IotDataTransferService {
                 case 714:
                 case 714:
                     result = sendDeviationData(transferVO);
                     result = sendDeviationData(transferVO);
                     break;
                     break;
-
+                case 716:
+                    result = sendWaterLevel(transferVO);
+                    break;
                 default:
                 default:
                     log.debug("不支持的设备类型:{}", deviceType);
                     log.debug("不支持的设备类型:{}", deviceType);
                     continue;
                     continue;
@@ -1153,8 +1237,17 @@ public class IotDataTransferService {
      * @param username 用户名
      * @param username 用户名
      */
      */
     void sendMqttMessage(String topic, Object vo, String messageType, String username) {
     void sendMqttMessage(String topic, Object vo, String messageType, String username) {
-        String json = JSON.toJSONString(vo);
-        // 不再记录每条数据的详情,只记录发送操作
+        String json;
+
+        // 针对楼层平面图特殊处理:将 byte[] 转为 Base64 字符串
+        if (vo instanceof com.usky.cdi.service.vo.info.FloorPlaneVO) {
+            json = serializeFloorPlaneVO((com.usky.cdi.service.vo.info.FloorPlaneVO) vo);
+        } else {
+            json = JSON.toJSONString(vo);
+        }
+
+        log.info("发送MQTT消息,Topic: {}, 消息类型: {}, JSON长度: {}", topic, messageType, json.length());
+
         MqttConnectionTool.MqttGateway gateway = mqttGatewayMap.get(username);
         MqttConnectionTool.MqttGateway gateway = mqttGatewayMap.get(username);
         if (gateway != null) {
         if (gateway != null) {
             gateway.sendToMqtt(topic, json);
             gateway.sendToMqtt(topic, json);
@@ -1163,6 +1256,36 @@ public class IotDataTransferService {
         }
         }
     }
     }
 
 
+    /**
+     * 序列化楼层平面图VO(将 floorFile byte[] 转为 Base64 字符串)
+     */
+    private String serializeFloorPlaneVO(com.usky.cdi.service.vo.info.FloorPlaneVO vo) {
+        com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
+
+        jsonObject.put("dataPacketID", vo.getDataPacketID());
+        jsonObject.put("engineeringID", vo.getEngineeringID());
+        jsonObject.put("floor", vo.getFloor());
+        jsonObject.put("floorFileID", vo.getFloorFileID());
+        jsonObject.put("floorFileName", vo.getFloorFileName());
+        jsonObject.put("floorFileSuffix", vo.getFloorFileSuffix());
+        jsonObject.put("filePixWidth", vo.getFilePixWidth());
+        jsonObject.put("filePixHeight", vo.getFilePixHeight());
+        jsonObject.put("publishTime", vo.getPublishTime());
+
+        // 关键:将 byte[] 转为 Base64 字符串
+        if (vo.getFloorFile() != null) {
+            String base64File = java.util.Base64.getEncoder().encodeToString(vo.getFloorFile());
+            jsonObject.put("floorFile", base64File);
+            log.info("平面图文件转换Base64成功,FileID: {}, 原始大小: {} bytes, Base64长度: {}",
+                    vo.getFloorFileID(), vo.getFloorFile().length, base64File.length());
+        } else {
+            jsonObject.put("floorFile", "");
+            log.warn("平面图文件为空,FileID: {}", vo.getFloorFileID());
+        }
+
+        return jsonObject.toJSONString();
+    }
+
     public void allData(Long engineeringId, String username, String password) {
     public void allData(Long engineeringId, String username, String password) {
         Integer tenantId = 0;
         Integer tenantId = 0;
         synchronizeDeviceData(tenantId, engineeringId, username, password);
         synchronizeDeviceData(tenantId, engineeringId, username, password);

+ 177 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/BasementClimateUtil.java

@@ -0,0 +1,177 @@
+package com.usky.cdi.service.util;
+
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.Random;
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/23
+ */
+
+/**
+ * 上海地区 地下室温湿度常量 + 四季判断工具
+ */
+public class BasementClimateUtil {
+
+    private static final Random RANDOM = new Random();
+
+    // ====================== 春季(3-5月)======================
+    public static final double SPRING_TEMP_MIN = 16.0;
+    public static final double SPRING_TEMP_MAX = 20.0;
+    public static final double SPRING_HUMI_MIN = 70.0;
+    public static final double SPRING_HUMI_MAX = 74.0;
+
+    // ====================== 夏季(6-8月)======================
+    public static final double SUMMER_TEMP_MIN = 22.0;
+    public static final double SUMMER_TEMP_MAX = 26.0;
+    public static final double SUMMER_HUMI_MIN = 80.0;
+    public static final double SUMMER_HUMI_MAX = 84.0;
+
+    // ====================== 秋季(9-11月)=====================
+    public static final double AUTUMN_TEMP_MIN = 17.0;
+    public static final double AUTUMN_TEMP_MAX = 21.0;
+    public static final double AUTUMN_HUMI_MIN = 68.0;
+    public static final double AUTUMN_HUMI_MAX = 72.0;
+
+    // ====================== 冬季(12-1-2月)===================
+    public static final double WINTER_TEMP_MIN = 12.0;
+    public static final double WINTER_TEMP_MAX = 16.0;
+    public static final double WINTER_HUMI_MIN = 60.0;
+    public static final double WINTER_HUMI_MAX = 64.0;
+
+    /**
+     * 获取当前系统时间的季节
+     * 0=春 1=夏 2=秋 3=冬
+     */
+    public static int getSeason() {
+        return getSeason(LocalDate.now());
+    }
+
+    /**
+     * 根据日期判断季节(JDK8 标准写法)
+     */
+    public static int getSeason(LocalDate date) {
+        Month month = date.getMonth();
+        int monthValue = month.getValue();
+
+        if (monthValue >= 3 && monthValue <= 5) {
+            return 0; // 春
+        } else if (monthValue >= 6 && monthValue <= 8) {
+            return 1; // 夏
+        } else if (monthValue >= 9 && monthValue <= 11) {
+            return 2; // 秋
+        } else {
+            return 3; // 冬(12、1、2月)
+        }
+    }
+
+    /**
+     * 获取当前季节的温度范围 [最小值, 最大值]
+     */
+    public static double getCurrentTempRange() {
+        return randomBetween(getTempRange(getSeason())[0], getTempRange(getSeason())[1]);
+    }
+
+    /**
+     * 获取当前季节的湿度范围 [最小值, 最大值]
+     */
+    public static double getCurrentHumiRange() {
+        return randomBetween(getHumiRange(getSeason())[0], getHumiRange(getSeason())[1]);
+    }
+
+    /**
+     * 根据季节获取温度范围
+     */
+    public static double[] getTempRange(int season) {
+        if (season == 0) {
+            return new double[]{SPRING_TEMP_MIN, SPRING_TEMP_MAX};
+        } else if (season == 1) {
+            return new double[]{SUMMER_TEMP_MIN, SUMMER_TEMP_MAX};
+        } else if (season == 2) {
+            return new double[]{AUTUMN_TEMP_MIN, AUTUMN_TEMP_MAX};
+        } else if (season == 3) {
+            return new double[]{WINTER_TEMP_MIN, WINTER_TEMP_MAX};
+        } else {
+            throw new IllegalArgumentException("无效季节:" + season);
+        }
+    }
+
+    /**
+     * 根据季节获取湿度范围
+     */
+    public static double[] getHumiRange(int season) {
+        if (season == 0) {
+            return new double[]{SPRING_HUMI_MIN, SPRING_HUMI_MAX};
+        } else if (season == 1) {
+            return new double[]{SUMMER_HUMI_MIN, SUMMER_HUMI_MAX};
+        } else if (season == 2) {
+            return new double[]{AUTUMN_HUMI_MIN, AUTUMN_HUMI_MAX};
+        } else if (season == 3) {
+            return new double[]{WINTER_HUMI_MIN, WINTER_HUMI_MAX};
+        } else {
+            throw new IllegalArgumentException("无效季节:" + season);
+        }
+    }
+
+    /**
+     * 获取地下室相对室外的温度差值
+     * 负=地下室更冷,正=地下室更暖
+     */
+    public static double getTempDiffWithOutdoor() {
+        return getTempDiffWithOutdoor(getSeason());
+    }
+
+    /**
+     * 根据季节获取温度差值
+     */
+    public static double getTempDiffWithOutdoor(int season) {
+        // 所有区间范围都 < 2.0
+        switch (season) {
+            case 0: // 春:地下室略低
+                return randomBetween(-1.0, -0.5);
+            case 1: // 夏:地下室明显低
+                return randomBetween(-6.0, -5.5);
+            case 2: // 秋:低于室外
+                return randomBetween(-3.5, -3.0);
+            case 3: // 冬:地下室更高
+                return randomBetween(4.5, 5.0);
+            default:
+                return 0.0;
+        }
+    }
+
+    /**
+     * 获取地下室相对室外的湿度差值
+     */
+    public static double getHumiDiffWithOutdoor() {
+        return getHumiDiffWithOutdoor(getSeason());
+    }
+
+    /**
+     * 根据季节获取湿度差值
+     */
+    public static double getHumiDiffWithOutdoor(int season) {
+        switch (season) {
+            case 0:
+                return randomBetween(12.0, 13.0);
+            case 1:
+                return randomBetween(19.0, 20.0);
+            case 2:
+                return randomBetween(9.0, 10.0);
+            case 3:
+                return randomBetween(7.0, 8.0);
+            default:
+                return 0.0;
+        }
+    }
+
+    /**
+     * 生成 [min, max] 之间的随机小数,保留 2 位
+     */
+    private static double randomBetween(double min, double max) {
+        double val = min + (max - min) * RANDOM.nextDouble();
+        return Math.round(val * 100) / 100.0;
+    }
+}

+ 173 - 42
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java

@@ -3,6 +3,7 @@ package com.usky.cdi.service.util;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.mapper.DmpDeviceMapper;
 import com.usky.cdi.mapper.DmpDeviceMapper;
 import com.usky.cdi.service.config.DeviceFieldConfig;
 import com.usky.cdi.service.config.DeviceFieldConfig;
@@ -40,9 +41,14 @@ public class DeviceDataQuery {
     @Autowired
     @Autowired
     private DeviceFieldConfig deviceFieldConfig;
     private DeviceFieldConfig deviceFieldConfig;
 
 
+    private static final Set<Integer> ALLOWED_DEVICE_TYPES = new HashSet<>(Arrays.asList(
+            711, 710, 709, 708, 707
+    ));
+
     // 定义各参数的格式化器(整数位,小数位)
     // 定义各参数的格式化器(整数位,小数位)
     private static final DecimalFormat FORMAT_2_2 = new DecimalFormat("00.00"); // 2位整数+2位小数
     private static final DecimalFormat FORMAT_2_2 = new DecimalFormat("00.00"); // 2位整数+2位小数
     private static final DecimalFormat FORMAT_0_3 = new DecimalFormat("0.000"); // 0位整数+3位小数
     private static final DecimalFormat FORMAT_0_3 = new DecimalFormat("0.000"); // 0位整数+3位小数
+    private static final DecimalFormat FORMAT_1_2 = new DecimalFormat("0.00");  // 1位整数+2位小数
     private static final DecimalFormat FORMAT_3_2 = new DecimalFormat("000.00"); // 3位整数+2位小数
     private static final DecimalFormat FORMAT_3_2 = new DecimalFormat("000.00"); // 3位整数+2位小数
     private static final DecimalFormat FORMAT_4_2 = new DecimalFormat("0000.00"); // 4位整数+2位小数
     private static final DecimalFormat FORMAT_4_2 = new DecimalFormat("0000.00"); // 4位整数+2位小数
 
 
@@ -79,6 +85,8 @@ public class DeviceDataQuery {
     private static final double FLOATING_RANGE_MAX = 0.5;
     private static final double FLOATING_RANGE_MAX = 0.5;
     private static final double FLOATING_RANGE_MIN1 = 0.0;
     private static final double FLOATING_RANGE_MIN1 = 0.0;
     private static final double FLOATING_RANGE_MAX1 = 1.0;
     private static final double FLOATING_RANGE_MAX1 = 1.0;
+    private static final double SEWAGE_LEVEL_MIN = 0.0;
+    private static final double SEWAGE_LEVEL_MAX = 0.1;
 
 
     private static int callCount = 0;
     private static int callCount = 0;
 
 
@@ -107,11 +115,52 @@ public class DeviceDataQuery {
             log.warn("接口返回数据:{}", response);
             log.warn("接口返回数据:{}", response);
 
 
             List<JSONObject> resultList = parseResponseData(response, transferVO.getDeviceType(), transferVO.getDevices());
             List<JSONObject> resultList = parseResponseData(response, transferVO.getDeviceType(), transferVO.getDevices());
+            boolean useXiangyuMock = false;
+            List<JSONObject> xiangyuDataList = new ArrayList<>();
+
+            // 当租户数据为空时获取桃浦象屿相同设备数据进行模拟(修复版:不修改原始设备列表)
+            if (resultList.isEmpty() && ALLOWED_DEVICE_TYPES.contains(transferVO.getDeviceType())) {
+                LambdaQueryWrapper<DmpDevice> queryWrapper = new LambdaQueryWrapper<>();
+                queryWrapper.eq(DmpDevice::getTenantId, 1205)
+                        .eq(DmpDevice::getDeviceType, transferVO.getDeviceType())
+                        .orderByAsc(DmpDevice::getId)
+                        .last("LIMIT 1");
+                DmpDevice xiangyuDevice = dmpDeviceMapper.selectOne(queryWrapper);
+
+                if (xiangyuDevice != null) {
+                    JSONObject xiangyuReq = new JSONObject();
+                    xiangyuReq.put("deviceuuid", Collections.singletonList(xiangyuDevice.getDeviceUuid()));
+                    String xiangyuResp = HttpClientUtils.doPostJson(baseUrl, xiangyuReq.toJSONString());
+
+                    JSONObject responseObj = JSON.parseObject(xiangyuResp);
+                    if (responseObj.getJSONArray("data") != null && !responseObj.getJSONArray("data").isEmpty()) {
+                        JSONArray dataArray = responseObj.getJSONArray("data");
+                        JSONObject deviceData = dataArray.getJSONObject(0);
+                        JSONObject metrics = deviceData.getJSONObject("metrics");
+                        Object realtime = metrics.get("realtime");
+                        System.out.println("接口返回数据时间戳对象:" + realtime);
+                        Long timestamp = realtime instanceof Long ? (Long) realtime : null;
+                        System.out.println("接口返回数据时间戳:" + timestamp);
+
+                        if (timestamp != null && timestamp >= (System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L)) {
+                            // 修复:不修改原始设备列表,直接生成模拟模板
+                            useXiangyuMock = true;
+                            xiangyuDataList = parseResponseData(xiangyuResp, transferVO.getDeviceType(),
+                                    Collections.singletonList(xiangyuDevice));
+                        }
+                    }
+                }
+            }
 
 
             // 若返回数据为空且开启模拟模式,则生成模拟数据
             // 若返回数据为空且开启模拟模式,则生成模拟数据
             if (resultList.isEmpty() && simulation) {
             if (resultList.isEmpty() && simulation) {
                 log.info("接口返回数据为空,生成模拟数据,设备类型:{}", transferVO.getDeviceType());
                 log.info("接口返回数据为空,生成模拟数据,设备类型:{}", transferVO.getDeviceType());
-                resultList = generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices(), null);
+                if (useXiangyuMock && !xiangyuDataList.isEmpty()) {
+                    // 使用象屿数据作为模板生成全量模拟
+                    resultList = generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices(), xiangyuDataList.get(0));
+                } else {
+                    resultList = generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices(), null);
+                }
             } else if (resultList.size() < transferVO.getDevices().size()) {
             } else if (resultList.size() < transferVO.getDevices().size()) {
                 log.warn("接口返回数据数量与请求数量不一致,设备类型:{}", transferVO.getDeviceType());
                 log.warn("接口返回数据数量与请求数量不一致,设备类型:{}", transferVO.getDeviceType());
 
 
@@ -136,22 +185,22 @@ public class DeviceDataQuery {
                 List<DmpDevice> missingDevices = missingDeviceIds.stream()
                 List<DmpDevice> missingDevices = missingDeviceIds.stream()
                         .map(requestDeviceMap::get)
                         .map(requestDeviceMap::get)
                         .collect(Collectors.toList());
                         .collect(Collectors.toList());
-                List<JSONObject> missingSimulationData = generateSimulationData(transferVO.getDeviceType(), missingDevices, resultList.get(0));
-                // 将模拟数据与返回数据结合
-                resultList.addAll(missingSimulationData);
 
 
-                // 校验结合后的数据是否与请求的device_id一一对应
-                Set<String> combinedDeviceIds = resultList.stream()
-                        .map(data -> data.getString("device_id"))
-                        .filter(Objects::nonNull)
-                        .collect(Collectors.toSet());
+                JSONObject templateData = resultList.isEmpty() ?
+                        (xiangyuDataList.isEmpty() ? null : xiangyuDataList.get(0))
+                        : resultList.get(0);
 
 
-                if (combinedDeviceIds.size() != transferVO.getDevices().size()) {
-                    log.warn("数据整合后仍存在缺失,请求设备数量:{},返回设备数量:{}",
-                            transferVO.getDevices().size(), combinedDeviceIds.size());
-                } else {
-                    log.debug("数据整合完成,设备数量与请求一致:{}", combinedDeviceIds.size());
-                }
+                List<JSONObject> missingSimulationData = generateSimulationData(transferVO.getDeviceType(), missingDevices, templateData);
+                // 修复:不再清空原有数据
+                resultList.addAll(missingSimulationData);
+            }
+
+            // 最终校验
+            if (resultList.size() != transferVO.getDevices().size()) {
+                log.warn("数据整合后仍存在缺失,请求设备数量:{},返回设备数量:{}",
+                        transferVO.getDevices().size(), resultList.size());
+            } else {
+                log.debug("数据整合完成,设备数量与请求一致:{}", resultList.size());
             }
             }
 
 
             return resultList;
             return resultList;
@@ -231,8 +280,15 @@ public class DeviceDataQuery {
                 }
                 }
 
 
                 // 添加设备标识信息
                 // 添加设备标识信息
-                targetData.put("deviceuuid", deviceUuid);
                 String deviceId = deviceUuidToIdMap.get(deviceUuid);
                 String deviceId = deviceUuidToIdMap.get(deviceUuid);
+
+                // 如果找不到,直接丢弃这条数据,不放入结果
+                if (deviceId == null) {
+                    continue;
+                }
+
+                // 能走到这里,说明一定有 deviceId
+                targetData.put("deviceuuid", deviceUuid);
                 targetData.put("device_id", deviceId);
                 targetData.put("device_id", deviceId);
 
 
                 if (hasValidData) {
                 if (hasValidData) {
@@ -287,6 +343,7 @@ public class DeviceDataQuery {
     private List<JSONObject> generateSimulationData(Integer deviceType, List<DmpDevice> devices, JSONObject standard) {
     private List<JSONObject> generateSimulationData(Integer deviceType, List<DmpDevice> devices, JSONObject standard) {
         List<JSONObject> simulationList = new ArrayList<>();
         List<JSONObject> simulationList = new ArrayList<>();
         long currentTime = System.currentTimeMillis();
         long currentTime = System.currentTimeMillis();
+        // System.out.println("深拷贝当前时间戳:" + currentTime);
 
 
         // 获取天气数据(仅在需要时)
         // 获取天气数据(仅在需要时)
         Map<String, Double> weatherData = null;
         Map<String, Double> weatherData = null;
@@ -298,18 +355,25 @@ public class DeviceDataQuery {
             JSONObject simData;
             JSONObject simData;
 
 
             if (standard != null) {
             if (standard != null) {
-                // ✅ 使用标准模板进行模拟(深拷贝)
-                simData = JSONObject.parseObject(standard.toJSONString());
+                simData = new JSONObject();
+                simData.putAll(standard);
+
+                // 【关键】先移除,确保 FastJSON 内部缓存失效
+                simData.remove("realtime");
+                simData.remove("device_id");
+                simData.remove("device_uuid");
+
+                // 再重新 put,强制类型正确
                 simData.put("realtime", currentTime);
                 simData.put("realtime", currentTime);
                 simData.put("device_id", device.getDeviceId());
                 simData.put("device_id", device.getDeviceId());
-                // 可选:替换 device_uuid(如果模板中有)
-                if (device.getDeviceUuid() != null && simData.containsKey("device_uuid")) {
+                // System.out.println("深拷贝时间戳1:" + simData.getLong("realtime"));
+
+                if (device.getDeviceUuid() != null && standard.containsKey("device_uuid")) {
                     simData.put("device_uuid", device.getDeviceUuid());
                     simData.put("device_uuid", device.getDeviceUuid());
                 }
                 }
 
 
-                // 对数值字段添加微小扰动(±2% 或固定范围)
                 addNoiseToNumericFields(simData, deviceType);
                 addNoiseToNumericFields(simData, deviceType);
-
+                // System.out.println("深拷贝时间戳2:" + simData.getLong("realtime"));
             } else {
             } else {
                 // ❌ 无标准模板,走原始随机逻辑
                 // ❌ 无标准模板,走原始随机逻辑
                 simData = new JSONObject();
                 simData = new JSONObject();
@@ -319,17 +383,24 @@ public class DeviceDataQuery {
                 switch (deviceType) {
                 switch (deviceType) {
                     case 707:
                     case 707:
                         double temp707 = 0.0;
                         double temp707 = 0.0;
-                        if (weatherData != null && !weatherData.isEmpty()) {
-                            double floating = ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN, FLOATING_RANGE_MAX);
-                            temp707 = weatherData.get("temperature") + 0.5 + floating;
+                        if (!weatherData.isEmpty()) {
+                            double floating = BasementClimateUtil.getTempDiffWithOutdoor();
+                            temp707 = weatherData.get("temperature") + floating;
                         } else {
                         } else {
-                            temp707 = ThreadLocalRandom.current().nextDouble(TEMP_RANGE_MIN, TEMP_RANGE_MAX);
+                            temp707 = BasementClimateUtil.getCurrentTempRange();
                         }
                         }
-                        simData.put("wd", formatNumber(temp707, FORMAT_2_2));
+                        simData.put("wd", temp707);
                         break;
                         break;
 
 
                     case 708:
                     case 708:
-                        simData.put("sd", formatNumber(ThreadLocalRandom.current().nextDouble(HUMIDITY_RANGE_MIN, HUMIDITY_RANGE_MAX), FORMAT_2_2));
+                        double humi708 = 0.0;
+                        if (!weatherData.isEmpty()) {
+                            double floating = BasementClimateUtil.getHumiDiffWithOutdoor();
+                            humi708 = weatherData.get("humidity") + floating;
+                        } else {
+                            humi708 = BasementClimateUtil.getCurrentHumiRange();
+                        }
+                        simData.put("sd", humi708);
                         break;
                         break;
 
 
                     case 709:
                     case 709:
@@ -355,6 +426,7 @@ public class DeviceDataQuery {
                         break;
                         break;
 
 
                     case 704:
                     case 704:
+                    case 705:
                         double aVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
                         double aVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
                         simData.put("aVoltage", formatNumber(aVoltage, FORMAT_3_2));
                         simData.put("aVoltage", formatNumber(aVoltage, FORMAT_3_2));
                         double bVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
                         double bVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
@@ -399,6 +471,12 @@ public class DeviceDataQuery {
                         simData.put("wy", 0);
                         simData.put("wy", 0);
                         break;
                         break;
 
 
+                    case 716:
+                        String deviceId1 = device.getDeviceId();
+                        double value = FixedWaterLevelGenerator.getSensorValue(deviceId1);
+                        simData.put("sensorValue", formatNumber(value, FORMAT_1_2));
+                        break;
+
                     default:
                     default:
                         log.warn("未知设备类型:{},无法生成模拟数据", deviceType);
                         log.warn("未知设备类型:{},无法生成模拟数据", deviceType);
                         continue;
                         continue;
@@ -413,33 +491,86 @@ public class DeviceDataQuery {
     }
     }
 
 
     private void addNoiseToNumericFields(JSONObject data, Integer deviceType) {
     private void addNoiseToNumericFields(JSONObject data, Integer deviceType) {
-        // 定义各字段的噪声比例或范围(可根据设备类型定制)
         Map<String, Double> noiseConfig = getNoiseConfigByDeviceType(deviceType);
         Map<String, Double> noiseConfig = getNoiseConfigByDeviceType(deviceType);
 
 
+        // 这些字段永远不加噪
+        Set<String> skipFields = new HashSet<>(Arrays.asList("realtime", "device_id", "device_uuid"));
+
         for (String key : data.keySet()) {
         for (String key : data.keySet()) {
+            if (skipFields.contains(key)) continue;
+            if (isDiscreteField(key)) continue;
+
             Object value = data.get(key);
             Object value = data.get(key);
-            if (value instanceof Number) {
-                double original = ((Number) value).doubleValue();
-                // 默认 ±2%
-                double noiseFactor = noiseConfig.getOrDefault(key, 0.02);
+            Double original = null;
 
 
-                // 避免对状态类字段(如 0/1)加噪
-                if (isDiscreteField(key)) {
-                    continue;
+            // 【关键】支持 Number 和 String 两种类型
+            if (value instanceof Number) {
+                original = ((Number) value).doubleValue();
+            } else if (value instanceof String) {
+                try {
+                    original = Double.parseDouble((String) value);
+                } catch (NumberFormatException e) {
+                    System.out.println("跳过字段(非数字): " + key + " = " + value);
+                    continue; // 不是数字字符串,跳过
                 }
                 }
+            }
 
 
-                double noise = original * noiseFactor * (ThreadLocalRandom.current().nextDouble(-1, 1));
-                double newValue = original + noise;
+            if (original == null) continue;
 
 
-                // 根据原格式保留小数位(这里简化处理,也可记录原格式)
+            // 每个设备、每个字段、每次调用都是独立的随机噪声 ✅
+            double noiseFactor = noiseConfig.getOrDefault(key, 0.02);
+            double noise = original * noiseFactor * (ThreadLocalRandom.current().nextDouble(-1, 1));
+            double newValue = original + noise;
+
+            System.out.println("加噪: " + key + " | 原值=" + original + " | 噪声=" + noise + " | 新值=" + newValue);
+
+            // 保持原类型:原来是 String 就格式化回 String,原来是 Number 就放 Double
+            if (value instanceof String) {
+                data.put(key, formatNumber(newValue, getFormatByKey(key)));
+            } else {
                 data.put(key, newValue);
                 data.put(key, newValue);
             }
             }
         }
         }
     }
     }
 
 
+    /**
+     * 根据字段名获取对应的格式化器
+     */
+    private DecimalFormat getFormatByKey(String key) {
+        switch (key) {
+            case "aVoltage":
+            case "bVoltage":
+            case "cVoltage":
+            case "aElectricity":
+            case "bElectricity":
+            case "cElectricity":
+                return FORMAT_3_2;
+            case "totalPower":
+                return FORMAT_4_2;
+            case "line1TEMP":
+            case "Line2TEMP":
+            case "Line3TEMP":
+            case "Line4TEMP":
+            case "wd":
+            case "sd":
+            case "o2":
+                return FORMAT_2_2;
+            case "co2":
+                return FORMAT_0_3;
+            case "leakageCurrent":
+                return FORMAT_4_2;
+            case "sensorValue":
+                return FORMAT_1_2;
+            default:
+                return null; // 不格式化,保持原样
+        }
+    }
+
     private boolean isDiscreteField(String field) {
     private boolean isDiscreteField(String field) {
-        // 这些字段是状态码,不应加噪
-        return field.equalsIgnoreCase("leach_status")
+        return field.equalsIgnoreCase("realtime")
+                || field.equalsIgnoreCase("device_id")
+                || field.equalsIgnoreCase("device_uuid")
+                || field.equalsIgnoreCase("leach_status")
                 || field.equalsIgnoreCase("sensorValue")
                 || field.equalsIgnoreCase("sensorValue")
                 || field.equalsIgnoreCase("qx")
                 || field.equalsIgnoreCase("qx")
                 || field.equalsIgnoreCase("cd")
                 || field.equalsIgnoreCase("cd")
@@ -452,7 +583,7 @@ public class DeviceDataQuery {
         Map<String, Double> config = new HashMap<>();
         Map<String, Double> config = new HashMap<>();
         switch (deviceType) {
         switch (deviceType) {
             case 707: // 温度
             case 707: // 温度
-                config.put("wd", 0.03); // ±3%
+                config.put("wd", 0.03); //
                 break;
                 break;
             case 708: // 湿度
             case 708: // 湿度
                 config.put("sd", 0.05);
                 config.put("sd", 0.05);

+ 5 - 5
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataSyncService.java

@@ -26,12 +26,12 @@ public class DeviceDataSyncService {
      * fixedDelay:任务执行完成后固定延迟29分钟执行下一次
      * fixedDelay:任务执行完成后固定延迟29分钟执行下一次
      * initialDelay:初始化后立即执行第一次任务
      * initialDelay:初始化后立即执行第一次任务
      */
      */
-    // @Scheduled(fixedDelay = 26 * 60 * 1000, initialDelay = 0)
+    // @Scheduled(fixedDelay = 2 * 60 * 1000, initialDelay = 0)
     // public void scheduledDeviceDataSync() {
     // public void scheduledDeviceDataSync() {
-    //     Integer tenantId = 1205;
-    //     Long engineeringId = 3101070011L;
-    //     String username = "3101070011";
-    //     String password = "5RqhJ7VG";
+    //     Integer tenantId = 1222;
+    //     Long engineeringId = 3101100021L;
+    //     String username = "3101100021";
+    //     String password = "SIixzph1";
     //     log.info("开始执行桃浦象屿人防设备数据同步定时任务,租户ID:{},工程ID:{}", tenantId, engineeringId);
     //     log.info("开始执行桃浦象屿人防设备数据同步定时任务,租户ID:{},工程ID:{}", tenantId, engineeringId);
     //
     //
     //     try {
     //     try {

+ 44 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/FixedWaterLevelGenerator.java

@@ -0,0 +1,44 @@
+package com.usky.cdi.service.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/28
+ */
+public class FixedWaterLevelGenerator {
+
+    // 关键:缓存每个设备的固定水位(只生成一次)
+    private static final Map<String, Double> deviceLevelMap = new HashMap<>();
+
+    /**
+     * 获取设备固定水位(永远不变)
+     */
+    public static double getSensorValue(String deviceId) {
+        // 如果这个设备还没生成过值 → 生成一个不重复的固定值
+        if (!deviceLevelMap.containsKey(deviceId)) {
+            double value = generateUniqueValue();
+            deviceLevelMap.put(deviceId, value);
+        }
+        // 直接返回固定值
+        return deviceLevelMap.get(deviceId);
+    }
+
+    /**
+     * 生成一个不重复、≤0.1 的水位
+     */
+    private static double generateUniqueValue() {
+        double val;
+        do {
+            // 生成 0.01 ~ 0.09 的随机数(保证不超0.1)
+            val = ThreadLocalRandom.current().nextDouble(0.02, 0.05);
+            // 保留2位小数
+            val = Math.round(val * 100) / 100.0;
+        } while (deviceLevelMap.containsValue(val)); // 确保不重复
+        return val;
+    }
+}

+ 10 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/SnowflakeIdGenerator.java

@@ -141,5 +141,15 @@ public class SnowflakeIdGenerator {
         // 取后15位,确保不超过15位
         // 取后15位,确保不超过15位
         return id % 1000000000000000L;
         return id % 1000000000000000L;
     }
     }
+
+    /**
+     * 生成10位数据包ID(取后10位)
+     * */
+
+    public long nextPacketId10() {
+        long id = nextId();
+        // 取后10位,确保不超过10位
+        return id % 10000000000L;
+    }
 }
 }
 
 

+ 4 - 4
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessageVO.java

@@ -1,6 +1,6 @@
 package com.usky.cdi.service.vo.alarm;
 package com.usky.cdi.service.vo.alarm;
 
 
-import com.fasterxml.jackson.annotation.JsonFormat;
+import com.alibaba.fastjson.annotation.JSONField;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import lombok.Data;
 import lombok.Data;
 
 
@@ -93,7 +93,7 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * 查询告警表 base_alarm 时新增告警取 alarm_type 字段,更新数据则取 handle_time 字段
      * 查询告警表 base_alarm 时新增告警取 alarm_type 字段,更新数据则取 handle_time 字段
      * 时间型(带毫秒),格式为 yyyy-MM-DD hh :mm :ss.SSS
      * 时间型(带毫秒),格式为 yyyy-MM-DD hh :mm :ss.SSS
      **/
      **/
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "GMT+8")
+    @JSONField(format = "yyyy-MM-dd HH:mm:ss.SSS")
     private String alarmUpdateTime;
     private String alarmUpdateTime;
 
 
     /**
     /**
@@ -113,8 +113,8 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * 获取当前时间
      * 获取当前时间
      * 时间型(带毫秒),格式为 yyyy-MM-DD hh :mm :ss.SSS
      * 时间型(带毫秒),格式为 yyyy-MM-DD hh :mm :ss.SSS
      **/
      **/
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "GMT+8")
-    private LocalDateTime publishTime;
+    @JSONField(format = "yyyy-MM-dd HH:mm:ss.SSS")
+    private String publishTime;
 
 
     /** 告警数据字段 必填、通用
     /** 告警数据字段 必填、通用
      * 水浸(Integer) 0:无水、1:有水
      * 水浸(Integer) 0:无水、1:有水

+ 42 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/BaseMqttInfo.java

@@ -0,0 +1,42 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/10
+ */
+@Data
+public class BaseMqttInfo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 数据包ID
+     */
+    private Long dataPacketID;
+
+    /**
+     *  工程id
+     */
+    private Long engineeringID;
+
+    /**
+     * 上报时间
+     */
+    private String publishTime;
+
+    /**
+     *  MQTT用户名
+     */
+    private String userName;
+
+    /**
+     *  MQTT密码
+     */
+    private String password;
+}

+ 2 - 2
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/EngineeringBaseVO.java

@@ -1,7 +1,7 @@
 package com.usky.cdi.service.vo.info;
 package com.usky.cdi.service.vo.info;
 
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
 import lombok.Data;
-import java.io.Serializable;
 
 
 /**
 /**
  * 人防工程基础信息VO
  * 人防工程基础信息VO
@@ -11,7 +11,7 @@ import java.io.Serializable;
  * @date 2025/03/20
  * @date 2025/03/20
  */
  */
 @Data
 @Data
-public class EngineeringBaseVO implements Serializable {
+public class EngineeringBaseVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
     /**
     /**

+ 5 - 2
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FacilityDeviceVO.java

@@ -1,18 +1,20 @@
 package com.usky.cdi.service.vo.info;
 package com.usky.cdi.service.vo.info;
 
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
 import lombok.Data;
+
 import java.io.Serializable;
 import java.io.Serializable;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 
 
 /**
 /**
  * 楼层平面图信息VO
  * 楼层平面图信息VO
  * Topic: base/floorPlane
  * Topic: base/floorPlane
- * 
+ *
  * @author han
  * @author han
  * @date 2025/03/20
  * @date 2025/03/20
  */
  */
 @Data
 @Data
-public class FacilityDeviceVO implements Serializable {
+public class FacilityDeviceVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
     private Integer id;
     private Integer id;
@@ -157,5 +159,6 @@ public class FacilityDeviceVO implements Serializable {
      * 设备UUID
      * 设备UUID
      */
      */
     private String deviceUuid;
     private String deviceUuid;
+
 }
 }
 
 

+ 2 - 15
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FloorPlaneVO.java

@@ -1,5 +1,6 @@
 package com.usky.cdi.service.vo.info;
 package com.usky.cdi.service.vo.info;
 
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
 import lombok.Data;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
@@ -12,19 +13,9 @@ import java.io.Serializable;
  * @date 2025/03/20
  * @date 2025/03/20
  */
  */
 @Data
 @Data
-public class FloorPlaneVO implements Serializable {
+public class FloorPlaneVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
-    /**
-     * 数据包ID
-     */
-    private Long dataPacketID;
-
-    /**
-     * 人防工程ID
-     */
-    private Long engineeringID;
-
     /**
     /**
      * 楼层
      * 楼层
      */
      */
@@ -60,9 +51,5 @@ public class FloorPlaneVO implements Serializable {
      */
      */
     private byte[] floorFile;
     private byte[] floorFile;
 
 
-    /**
-     * 上报时间
-     */
-    private String publishTime;
 }
 }
 
 

+ 2 - 15
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ProtectiveUnitVO.java

@@ -1,5 +1,6 @@
 package com.usky.cdi.service.vo.info;
 package com.usky.cdi.service.vo.info;
 
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
 import lombok.Data;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
@@ -13,19 +14,9 @@ import java.math.BigDecimal;
  * @date 2025/03/20
  * @date 2025/03/20
  */
  */
 @Data
 @Data
-public class ProtectiveUnitVO implements Serializable {
+public class ProtectiveUnitVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
-    /**
-     * 数据包ID
-     */
-    private Long dataPacketID;
-
-    /**
-     * 人防工程ID
-     */
-    private Long engineeringID;
-
     /**
     /**
      * 楼层
      * 楼层
      */
      */
@@ -61,9 +52,5 @@ public class ProtectiveUnitVO implements Serializable {
      */
      */
     private String unitotherexit;
     private String unitotherexit;
 
 
-    /**
-     * 上报时间
-     */
-    private String publishTime;
 }
 }
 
 

+ 39 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/WaterLevelVO.java

@@ -0,0 +1,39 @@
+package com.usky.cdi.service.vo.info;
+
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
+import lombok.Data;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/27
+ */
+@Data
+public class WaterLevelVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final Double MIN_SENSOR_VALUE = 0.0;
+    private static final Double MAX_SENSOR_VALUE = 2.0;
+
+    /**
+     * 水位高度
+     * 类型:double
+     */
+    private Double sensorValue;
+
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        // 位移值校验规则,根据实际业务需求调整
+        if (sensorValue == null || sensorValue < MIN_SENSOR_VALUE || sensorValue > MAX_SENSOR_VALUE) {
+            throw new IllegalArgumentException("水位高度(sensorValue)为必填项,且必须在" + MIN_SENSOR_VALUE + "到" + MAX_SENSOR_VALUE + "之间");
+        }
+    }
+
+}