Explorar o código

优化人防单元等基础数据推送代码

fuyuchuan hai 1 semana
pai
achega
06f6b60fef

+ 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);
 }
 }

+ 10 - 2
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;
@@ -117,6 +118,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 +206,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 +214,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);
         }
         }
     }
     }
 
 

+ 33 - 21
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java

@@ -7,9 +7,9 @@ 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;
@@ -48,8 +48,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 +125,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) {
@@ -147,9 +151,10 @@ public class BaseDataTransferService {
                 vo.setPublishTime(getCurrentTime());
                 vo.setPublishTime(getCurrentTime());
             }
             }
 
 
-            String imagePath = "D://games/3492.jpg";
+            String imagePath = "D://02H1-01.jpg";
             // 将图片文件读取为字节数组
             // 将图片文件读取为字节数组
             byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
             byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
+            vo.setFloorFile(imageBytes);
 
 
             // 检查文件大小(不超过5MB)
             // 检查文件大小(不超过5MB)
             if (vo.getFloorFile() != null && imageBytes.length > 5 * 1024 * 1024) {
             if (vo.getFloorFile() != null && imageBytes.length > 5 * 1024 * 1024) {
@@ -157,6 +162,8 @@ public class BaseDataTransferService {
                 return false;
                 return false;
             }
             }
 
 
+            String base64File = java.util.Base64.getEncoder().encodeToString(vo.getFloorFile());
+
             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());
@@ -166,27 +173,28 @@ public class BaseDataTransferService {
             map.put("floorFileSuffix", vo.getFloorFileSuffix());
             map.put("floorFileSuffix", vo.getFloorFileSuffix());
             map.put("filePixWidth", vo.getFilePixWidth());
             map.put("filePixWidth", vo.getFilePixWidth());
             map.put("filePixHeight", vo.getFilePixHeight());
             map.put("filePixHeight", vo.getFilePixHeight());
-            map.put("floorFile", imageBytes);
+            map.put("floorFile", base64File);
             map.put("publishTime", vo.getPublishTime());
             map.put("publishTime", vo.getPublishTime());
             Gson gson = new Gson();
             Gson gson = new Gson();
             // 将字节数组转换为Base64编码
             // 将字节数组转换为Base64编码
-            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
-            vo.setFloorFile(imageBytes);
+            // JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
+            // vo.setFloorFile(imageBytes);
 //            jsonObject.put("floorFile", imageBytes);
 //            jsonObject.put("floorFile", imageBytes);
-            if (vo.getFloorFile() != null) {
-                // 使用Base64编码传输二进制数据
-                String base64File = java.util.Base64.getEncoder().encodeToString(vo.getFloorFile());
-                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();
+            // String json = jsonObject.toJSONString();
             System.out.println(gson.toJson(map));
             System.out.println(gson.toJson(map));
             String topic = "base/floorPlane";
             String topic = "base/floorPlane";
 
 
             log.info("发送楼层平面图信息,Topic: {}, FileID: {}, FileSize: {} bytes",
             log.info("发送楼层平面图信息,Topic: {}, FileID: {}, FileSize: {} bytes",
                     topic, vo.getFloorFileID(),
                     topic, vo.getFloorFileID(),
                     vo.getFloorFile() != null ? vo.getFloorFile().length : 0);
                     vo.getFloorFile() != null ? vo.getFloorFile().length : 0);
-            mqttGateway.sendToMqtt(topic, gson.toJson(map));
+            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh("3101100017", "gjB4v1bh");
+            gateway.sendToMqtt(topic, gson.toJson(map));
 
 
             return true;
             return true;
         } catch (Exception e) {
         } catch (Exception e) {
@@ -228,14 +236,14 @@ public class BaseDataTransferService {
             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);
 
 
             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 +261,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 +299,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 +318,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);
         }
         }

+ 41 - 5
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -12,7 +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.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;
@@ -50,8 +49,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;
 
 
@@ -1153,8 +1150,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 +1169,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);

+ 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;
 }
 }