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

Merge branch 'master' of http://47.111.81.118:3000/uskycloud/usky-modules into fanghuisheng

fanghuisheng 2 недель назад
Родитель
Сommit
d466f0d50f
100 измененных файлов с 3495 добавлено и 924 удалено
  1. 2 1
      pom.xml
  2. 5 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/BaseDataController.java
  3. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDeliveryLogService.java
  4. 6 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/config/DeviceFieldConfig.java
  5. 4 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/AlarmType.java
  6. 15 8
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataSyncService.java
  7. 10 10
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataTransferService.java
  8. 62 35
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java
  9. 368 221
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java
  10. 187 19
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java
  11. 177 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/BasementClimateUtil.java
  12. 202 44
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java
  13. 5 5
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataSyncService.java
  14. 49 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/FixedWaterLevelGenerator.java
  15. 32 4
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/HttpClientUtils.java
  16. 10 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/SnowflakeIdGenerator.java
  17. 4 4
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessageVO.java
  18. 42 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/BaseMqttInfo.java
  19. 2 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/EngineeringBaseVO.java
  20. 5 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FacilityDeviceVO.java
  21. 2 15
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FloorPlaneVO.java
  22. 2 15
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ProtectiveUnitVO.java
  23. 39 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/WaterLevelVO.java
  24. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/MybatisGenerator.java
  25. 30 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/BaseSpaceController.java
  26. 76 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/DmpGatewayController.java
  27. 67 15
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java
  28. 0 233
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsApiV1Controller.java
  29. 0 37
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAuthController.java
  30. 36 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeController.java
  31. 21 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterController.java
  32. 21 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterValueController.java
  33. 32 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsDeviceEventController.java
  34. 210 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java
  35. 134 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java
  36. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java
  37. 20 14
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java
  38. 54 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsSystemDictController.java
  39. 8 8
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseArea.java
  40. 97 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseBuild.java
  41. 8 8
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpace.java
  42. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceArea.java
  43. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceBuild.java
  44. 34 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceGateway.java
  45. 27 19
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpGateway.java
  46. 92 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpProduct.java
  47. 1 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannel.java
  48. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelType.java
  49. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameter.java
  50. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameterValue.java
  51. 47 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDataUploadShanghaiProduct.java
  52. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceEvent.java
  53. 61 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyConsumptionFormula.java
  54. 6 3
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProject.java
  55. 39 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectDeviceSystem.java
  56. 0 67
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpaceBuilding.java
  57. 6 14
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictCode.java
  58. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictRegion.java
  59. 43 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictValue.java
  60. 82 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/TemporalTypeEnum.java
  61. 55 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/ValueTypeEnum.java
  62. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseAreaMapper.java
  63. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseBuildMapper.java
  64. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceAreaMapper.java
  65. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceBuildMapper.java
  66. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceGatewayMapper.java
  67. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceMapper.java
  68. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpGatewayMapper.java
  69. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpProductMapper.java
  70. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeMapper.java
  71. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterMapper.java
  72. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterValueMapper.java
  73. 12 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDataUploadShanghaiProductMapper.java
  74. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceEventMapper.java
  75. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsEnergyConsumptionFormulaMapper.java
  76. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsGatewayMapper.java
  77. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectDeviceSystemMapper.java
  78. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectMapper.java
  79. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceAreaMapper.java
  80. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceBuildingMapper.java
  81. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceFloorMapper.java
  82. 0 10
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceMapper.java
  83. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictCodeMapper.java
  84. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictRegionMapper.java
  85. 10 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictValueMapper.java
  86. 41 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/BaseSpaceService.java
  87. 28 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpGatewayService.java
  88. 23 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpProductService.java
  89. 2 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAnalysisService.java
  90. 0 14
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAuthService.java
  91. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeParameterService.java
  92. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeParameterValueService.java
  93. 23 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeService.java
  94. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceEventService.java
  95. 25 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceReportService.java
  96. 19 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsEnergyItemCodeService.java
  97. 0 22
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsGatewayQueryService.java
  98. 64 28
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsModelService.java
  99. 45 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java
  100. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectService.java

+ 2 - 1
pom.xml

@@ -64,7 +64,6 @@
 
 
         <module>service-agbox</module>
         <module>service-agbox</module>
 
 
-
         <module>service-job</module>
         <module>service-job</module>
 
 
 
 
@@ -88,6 +87,8 @@
 
 
         <module>service-tsdb</module>
         <module>service-tsdb</module>
 
 
+        <module>service-rule</module>
+
         <!--    <module>service-data</module>-->
         <!--    <module>service-data</module>-->
 
 
         <module>service-sas</module>
         <module>service-sas</module>

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

+ 187 - 19
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;
 
 
@@ -242,6 +238,7 @@ public class IotDataTransferService {
 
 
             Long engineeringId = transferVO.getEngineeringId();
             Long engineeringId = transferVO.getEngineeringId();
 
 
+            int skippedNullField = 0;
             for (JSONObject deviceDataItem : deviceData) {
             for (JSONObject deviceDataItem : deviceData) {
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
@@ -251,6 +248,32 @@ public class IotDataTransferService {
                     continue;
                     continue;
                 }
                 }
 
 
+                // 检查业务字段是否为空,为空则计入失败(修复:原逻辑静默跳过并误计为成功)
+                boolean fieldMissing = false;
+                switch (deviceType) {
+                    case 707:
+                        if (deviceDataItem.getFloat("wd") == null) fieldMissing = true;
+                        break;
+                    case 708:
+                        if (deviceDataItem.getFloat("sd") == null) fieldMissing = true;
+                        break;
+                    case 709:
+                        if (deviceDataItem.getFloat("o2") == null) fieldMissing = true;
+                        break;
+                    case 710:
+                        if (deviceDataItem.getFloat("co2") == null) fieldMissing = true;
+                        break;
+                    case 711:
+                        if (deviceDataItem.getFloat("co") == null) fieldMissing = true;
+                        break;
+                }
+                if (fieldMissing) {
+                    log.warn("设备{}[类型{}]的业务字段数据为空,跳过推送,计入失败", deviceId, deviceType);
+                    result.put("failureCount", result.get("failureCount") + 1);
+                    skippedNullField++;
+                    continue;
+                }
+
                 boolean deviceSuccess = true;
                 boolean deviceSuccess = true;
                 try {
                 try {
                     switch (deviceType) {
                     switch (deviceType) {
@@ -271,7 +294,7 @@ public class IotDataTransferService {
                             break;
                             break;
                     }
                     }
                 } catch (Exception e) {
                 } catch (Exception e) {
-                    log.warn("设备{}推送失败:{}", deviceId, e.getMessage());
+                    log.warn("设备{}[类型{}]推送异常:{}", deviceId, deviceType, e.getMessage(), e);
                     deviceSuccess = false;
                     deviceSuccess = false;
                 }
                 }
 
 
@@ -282,6 +305,10 @@ public class IotDataTransferService {
                 }
                 }
             }
             }
 
 
+            if (skippedNullField > 0) {
+                log.warn("[类型{}] 因业务字段为空而跳过的设备数量:{}", deviceType, skippedNullField);
+            }
+
             int success = result.get("successCount");
             int success = result.get("successCount");
             int failure = result.get("failureCount");
             int failure = result.get("failureCount");
             int notSynced = totalDevices - success - failure;
             int notSynced = totalDevices - success - failure;
@@ -576,7 +603,7 @@ public class IotDataTransferService {
     }
     }
 
 
     /**
     /**
-     * 推送温度信息(701
+     * 推送温度信息(707
      *
      *
      * @param deviceDataItem 设备数据
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
      * @param deviceId 设备ID
@@ -597,11 +624,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 +679,7 @@ public class IotDataTransferService {
     }
     }
 
 
     /**
     /**
-     * 推送一氧化碳浓度信息(706
+     * 推送一氧化碳浓度信息(711
      *
      *
      * @param deviceDataItem 设备数据
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
      * @param deviceId 设备ID
@@ -676,7 +704,7 @@ public class IotDataTransferService {
     }
     }
 
 
     /**
     /**
-     * 推送二氧化碳浓度信息(707
+     * 推送二氧化碳浓度信息(710)
      *
      *
      * @param deviceDataItem 设备数据
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
      * @param deviceId 设备ID
@@ -929,6 +957,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,15 +1148,25 @@ 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;
             }
             }
 
 
             // 累加成功数和失败数
             // 累加成功数和失败数
-            totalSuccessCount += result.getOrDefault("successCount", 0);
-            totalFailureCount += result.getOrDefault("failureCount", 0);
+            int typeSuccess = result.getOrDefault("successCount", 0);
+            int typeFailure = result.getOrDefault("failureCount", 0);
+            totalSuccessCount += typeSuccess;
+            totalFailureCount += typeFailure;
+
+            // 分类型诊断日志:精确定位每种类型的成功/失败/未同步
+            int typeTotal = transferVO.getDevices() != null ? transferVO.getDevices().size() : 0;
+            int typeNotSynced = typeTotal - typeSuccess - typeFailure;
+            log.info("[类型分诊] deviceType={} | 总设备={} | 成功={} | 失败={} | 未同步={}",
+                    deviceType, typeTotal, typeSuccess, typeFailure, typeNotSynced);
         }
         }
 
 
         // 任务完成总结
         // 任务完成总结
@@ -1136,12 +1259,18 @@ public class IotDataTransferService {
      * @return 解析后的时间,如果解析失败返回null
      * @return 解析后的时间,如果解析失败返回null
      */
      */
     private LocalDateTime parseDataTime(JSONObject deviceDataItem) {
     private LocalDateTime parseDataTime(JSONObject deviceDataItem) {
-        // log.warn("解析的json{}", deviceDataItem.toString());
-        Long dataTime = deviceDataItem.getLong("realtime");
-        if (dataTime == null) {
-            log.warn("设备{}的time为空", deviceDataItem.getString("device_id"));
-            return null;
+        Object raw = deviceDataItem.get("realtime");
+        Long dataTime = null;
+        if (raw instanceof Long) {
+            dataTime = (Long) raw;
+        } else if (raw instanceof Integer) {
+            dataTime = ((Integer) raw).longValue();
+        } else if (raw instanceof Number) {
+            dataTime = ((Number) raw).longValue();
+        } else if (raw instanceof String) {
+            try { dataTime = Long.parseLong((String) raw); } catch (Exception ignored) {}
         }
         }
+        if (dataTime == null) return null;
         return LocalDateTime.ofInstant(Instant.ofEpochMilli(dataTime), ZoneId.systemDefault());
         return LocalDateTime.ofInstant(Instant.ofEpochMilli(dataTime), ZoneId.systemDefault());
     }
     }
 
 
@@ -1153,8 +1282,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 +1301,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;
+    }
+}

+ 202 - 44
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;
 
 
@@ -104,14 +112,56 @@ public class DeviceDataQuery {
 
 
             log.debug("请求设备数据接口,设备数量:{}", deviceUuids.size());
             log.debug("请求设备数据接口,设备数量:{}", deviceUuids.size());
             String response = HttpClientUtils.doPostJson(baseUrl, requestBody.toJSONString());
             String response = HttpClientUtils.doPostJson(baseUrl, requestBody.toJSONString());
-            log.warn("接口返回数据:{}", response);
+            log.info("接口返回数据:{}", 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);
+                    // 修复:象屿接口可能返回null/空串/非JSON,导致responseObj为null引发NPE
+                    if (responseObj != null && 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 +186,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 +281,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,10 +344,15 @@ 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);
 
 
         // 获取天气数据(仅在需要时)
         // 获取天气数据(仅在需要时)
+        // 期望:只有“象屿模板为空/缺字段”时,才用天气数据对温湿度做修正;有象屿模板就不要再相加
+        boolean needWeatherForTemp = (deviceType != null && deviceType == 707) && (standard == null || standard.get("wd") == null);
+        boolean needWeatherForHumi = (deviceType != null && deviceType == 708) && (standard == null || standard.get("sd") == null);
+        boolean needWeatherForElectric = (deviceType != null && deviceType == 704) && (standard == null);
         Map<String, Double> weatherData = null;
         Map<String, Double> weatherData = null;
-        if (standard == null && (deviceType == 707 || deviceType == 704)) {
+        if (needWeatherForTemp || needWeatherForHumi || needWeatherForElectric) {
             weatherData = WeatherFetcher.fetchWeather();
             weatherData = WeatherFetcher.fetchWeather();
         }
         }
 
 
@@ -298,18 +360,46 @@ 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("deviceuuid");
+
+                // 再重新 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")) {
-                    simData.put("device_uuid", device.getDeviceUuid());
+                // System.out.println("深拷贝时间戳1:" + simData.getLong("realtime"));
+
+                if (device.getDeviceUuid() != null && standard.containsKey("deviceuuid")) {
+                    simData.put("deviceuuid", device.getDeviceUuid());
                 }
                 }
 
 
-                // 对数值字段添加微小扰动(±2% 或固定范围)
-                addNoiseToNumericFields(simData, deviceType);
+                // 若象屿模板里缺少温湿度字段,则再回退到天气+差值逻辑补齐(避免“所有情况都相加”)
+                if (deviceType != null && deviceType == 707 && simData.get("wd") == null) {
+                    double temp707;
+                    if (weatherData != null && !weatherData.isEmpty()) {
+                        double floating = BasementClimateUtil.getTempDiffWithOutdoor();
+                        temp707 = weatherData.get("temperature") + floating;
+                    } else {
+                        temp707 = BasementClimateUtil.getCurrentTempRange();
+                    }
+                    simData.put("wd", temp707);
+                } else if (deviceType != null && deviceType == 708 && simData.get("sd") == null) {
+                    double humi708;
+                    if (weatherData != null && !weatherData.isEmpty()) {
+                        double floating = BasementClimateUtil.getHumiDiffWithOutdoor();
+                        humi708 = weatherData.get("humidity") + floating;
+                    } else {
+                        humi708 = BasementClimateUtil.getCurrentHumiRange();
+                    }
+                    simData.put("sd", humi708);
+                }
 
 
+                addNoiseToNumericFields(simData, deviceType);
+                // System.out.println("深拷贝时间戳2:" + simData.getLong("realtime"));
             } else {
             } else {
                 // ❌ 无标准模板,走原始随机逻辑
                 // ❌ 无标准模板,走原始随机逻辑
                 simData = new JSONObject();
                 simData = new JSONObject();
@@ -320,16 +410,23 @@ public class DeviceDataQuery {
                     case 707:
                     case 707:
                         double temp707 = 0.0;
                         double temp707 = 0.0;
                         if (weatherData != null && !weatherData.isEmpty()) {
                         if (weatherData != null && !weatherData.isEmpty()) {
-                            double floating = ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN, FLOATING_RANGE_MAX);
-                            temp707 = weatherData.get("temperature") + 0.5 + floating;
+                            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 != null && !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 +452,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 +497,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 +517,87 @@ 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", "deviceuuid"));
+
         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);
+            log.info("加噪: {} | 原值={} | 噪声={} | 新值={}", 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("deviceuuid")
+                || field.equalsIgnoreCase("leach_status")
                 || field.equalsIgnoreCase("sensorValue")
                 || field.equalsIgnoreCase("sensorValue")
                 || field.equalsIgnoreCase("qx")
                 || field.equalsIgnoreCase("qx")
                 || field.equalsIgnoreCase("cd")
                 || field.equalsIgnoreCase("cd")
@@ -452,7 +610,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 = 10 * 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 {

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

@@ -0,0 +1,49 @@
+package com.usky.cdi.service.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Random;
+
+/**
+ * 固定水位生成器 - 为液位传感器(716)生成模拟数据
+ * <p>
+ * 修复说明:
+ * 1. HashMap 改为 ConcurrentHashMap,解决并发安全问题
+ * 2. 移除 containsValue 死循环风险,改用基于设备ID哈希的确定性算法,
+ *    保证每个设备获得唯一且稳定的水位值,不受设备数量限制
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/28
+ */
+
+/**
+ * 固定水位值生成器:
+ * 1. 每台设备首次调用时基于设备ID生成一个随机水位值
+ * 2. 同一设备后续调用直接取缓存,保持一致
+ * 3. 不同设备之间数值随机分散
+ */
+@Slf4j
+public class FixedWaterLevelGenerator {
+
+    private static final Map<String, Double> DEVICE_LEVEL_MAP = new ConcurrentHashMap<>();
+
+    private static final double MIN_VALUE = 0.02;
+    private static final double MAX_VALUE = 0.05;
+
+    public static double getSensorValue(String deviceId) {
+        return DEVICE_LEVEL_MAP.computeIfAbsent(deviceId, FixedWaterLevelGenerator::computeFixedValue);
+    }
+
+    private static double computeFixedValue(String deviceId) {
+        // 乘以一个大的质数,打乱原本连续ID的哈希值分布
+        long seed = (deviceId.hashCode() * 2654435761L) & 0xFFFFFFFFL;
+        Random random = new Random(seed);
+
+        double value = MIN_VALUE + random.nextDouble() * (MAX_VALUE - MIN_VALUE);
+        log.info("生成设备[{}]的固定水位值:{}", deviceId, value);
+        return Math.round(value * 1000) / 1000.0;
+    }
+}

+ 32 - 4
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/HttpClientUtils.java

@@ -20,6 +20,7 @@ import java.net.URI;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.UUID;
 
 
 @Slf4j
 @Slf4j
 public class HttpClientUtils {
 public class HttpClientUtils {
@@ -111,7 +112,9 @@ public class HttpClientUtils {
     }
     }
 
 
     public static String doPostJson(String url, String json) {
     public static String doPostJson(String url, String json) {
-        log.info(">>> 请求URL={}, 请求体={}", url, json);
+        final String requestId = UUID.randomUUID().toString();
+        final long start = System.currentTimeMillis();
+        log.info("[{}] >>> POST JSON 请求开始 URL={}, body={}", requestId, url, json);
 
 
         CloseableHttpResponse response = null;
         CloseableHttpResponse response = null;
         String resultString = "";
         String resultString = "";
@@ -128,19 +131,44 @@ public class HttpClientUtils {
             httpPost.setEntity(entity);
             httpPost.setEntity(entity);
             // 执行http请求
             // 执行http请求
             response = HTTP_CLIENT.execute(httpPost);
             response = HTTP_CLIENT.execute(httpPost);
-            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
+            int statusCode = response.getStatusLine() != null ? response.getStatusLine().getStatusCode() : -1;
+            resultString = response.getEntity() == null ? "" : EntityUtils.toString(response.getEntity(), "utf-8");
+
+            long costMs = System.currentTimeMillis() - start;
+            String preview = resultString == null ? "null" : (resultString.length() > 500 ? resultString.substring(0, 500) + "...(truncated)" : resultString);
+
+            if (statusCode != 200) {
+                log.warn("[{}] <<< POST JSON 响应异常 status={} costMs={} preview={}", requestId, statusCode, costMs, preview);
+            } else if (resultString == null || resultString.trim().isEmpty()) {
+                log.warn("[{}] <<< POST JSON 响应为空 status=200 costMs={}", requestId, costMs);
+            } else {
+                log.info("[{}] <<< POST JSON 响应成功 status=200 costMs={} preview={}", requestId, costMs, preview);
+            }
         } catch (Exception e) {
         } catch (Exception e) {
-            e.printStackTrace();
+            long costMs = System.currentTimeMillis() - start;
+            String root = buildRootCause(e);
+            log.error("[{}] !!! POST JSON 请求异常 costMs={} URL={} body={} exType={} exMsg={} rootCause={}",
+                    requestId, costMs, url, json, e.getClass().getName(), e.getMessage(), root, e);
         } finally {
         } finally {
             try {
             try {
                 if (response != null) {
                 if (response != null) {
                     response.close();
                     response.close();
                 }
                 }
             } catch (IOException e) {
             } catch (IOException e) {
-                e.printStackTrace();
+                log.warn("[{}] 关闭HTTP响应失败: {}", requestId, e.getMessage(), e);
             }
             }
         }
         }
 
 
         return resultString;
         return resultString;
     }
     }
+
+    private static String buildRootCause(Throwable t) {
+        if (t == null) return "null";
+        Throwable cur = t;
+        while (cur.getCause() != null && cur.getCause() != cur) {
+            cur = cur.getCause();
+        }
+        String msg = cur.getMessage();
+        return cur.getClass().getName() + (msg == null ? "" : (": " + msg));
+    }
 }
 }

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

+ 1 - 1
service-ems/service-ems-biz/src/main/java/com/usky/ems/MybatisGenerator.java

@@ -70,7 +70,7 @@ public class MybatisGenerator {
         // strategy.setTablePrefix("t_"); // 表名前缀
         // strategy.setTablePrefix("t_"); // 表名前缀
         strategy.setEntityLombokModel(true); //使用lombok
         strategy.setEntityLombokModel(true); //使用lombok
         //修改自己想要生成的表
         //修改自己想要生成的表
-        strategy.setInclude("ems_cons_platform_config");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+        strategy.setInclude(new String[]{"ems_channel_type", "ems_channel_type_parameter", "ems_channel_type_parameter_value"});  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
         mpg.setStrategy(strategy);
         mpg.setStrategy(strategy);
 
 
         // 关闭默认 xml 生成,调整生成 至 根目录
         // 关闭默认 xml 生成,调整生成 至 根目录

+ 30 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/BaseSpaceController.java

@@ -0,0 +1,30 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.BaseSpaceService;
+import com.usky.ems.service.vo.BaseSpaceForestNodeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 空间树接口(base_space)
+ *
+ * GET /base-space/tree  获取空间树
+ */
+@RestController
+@RequestMapping("/space")
+public class BaseSpaceController {
+
+    @Autowired
+    private BaseSpaceService baseSpaceService;
+
+    /**
+     * 获取空间树(根据 base_space 构建)
+     */
+    @GetMapping("/tree")
+    public ApiResult<BaseSpaceForestNodeVO> tree() {
+        return ApiResult.success(baseSpaceService.tree());
+    }
+}

+ 76 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/DmpGatewayController.java

@@ -0,0 +1,76 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.DmpGateway;
+import com.usky.ems.service.DmpGatewayService;
+import com.usky.ems.service.vo.DmpGatewayDevicePageRequest;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
+import com.usky.ems.service.vo.DmpGatewayListItem;
+import com.usky.ems.service.vo.DmpGatewayPageRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * dmp_gateway 网关维护 API(REST 风格对齐 IoT 模块 DmpDeviceInfoController 的增删改)
+ * 基础路径:/dmp/gateway(网关前缀如 /prod-api/service-ems 依部署而定)
+ */
+@RestController
+@RequestMapping("/gateway")
+public class DmpGatewayController {
+
+    @Autowired
+    private DmpGatewayService dmpGatewayService;
+
+    /**
+     * 分页列表
+     */
+    @PostMapping("/list")
+    public ApiResult<CommonPage<DmpGatewayListItem>> gatewayList(@RequestBody DmpGatewayPageRequest request) {
+        return ApiResult.success(dmpGatewayService.pageGateways(request));
+    }
+
+    /**
+     * 按网关 UUID 分页查询 dmp_device 列表
+     */
+    @PostMapping("/device/list")
+    public ApiResult<CommonPage<DmpDevice>> gatewayDeviceList(@RequestBody DmpGatewayDevicePageRequest request) {
+        return ApiResult.success(dmpGatewayService.pageGatewayDevices(request));
+    }
+
+    /**
+     * 详情
+     */
+    @GetMapping("/{id}")
+    public ApiResult<DmpGatewayDetailResponse> getGateway(@PathVariable Integer id) {
+        return ApiResult.success(dmpGatewayService.getGatewayDetail(id));
+    }
+
+    /**
+     * 新增
+     */
+    @PostMapping
+    public ApiResult<Void> add(@RequestBody DmpGateway entity) {
+        dmpGatewayService.add(entity);
+        return ApiResult.success();
+    }
+
+    /**
+     * 修改
+     */
+    @PutMapping
+    public ApiResult<Void> edit(@RequestBody DmpGateway entity) {
+        dmpGatewayService.update(entity);
+        return ApiResult.success();
+    }
+
+    /**
+     * 删除
+     */
+    @DeleteMapping("/{id}")
+    public ApiResult<Void> remove(@PathVariable("id") Integer id) {
+        dmpGatewayService.remove(id);
+        return ApiResult.success();
+    }
+}

+ 67 - 15
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java

@@ -9,53 +9,105 @@ import org.springframework.web.bind.annotation.*;
 /**
 /**
  * 能耗分析接口
  * 能耗分析接口
  * 趋势、指标、分类占比、区域分析、对比分析
  * 趋势、指标、分类占比、区域分析、对比分析
+ * 区域与面积相关统计在 {@link com.usky.ems.service.impl.EmsAnalysisServiceImpl} 中基于
+ * base_space、base_area、base_space_area、base_space_build 等表查询。
+ * 基础路径前缀:/analysis(网关再加 /prod-api/service-ems)
  */
  */
 @RestController
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/analysis")
 public class EmsAnalysisController {
 public class EmsAnalysisController {
 
 
     @Autowired
     @Autowired
     private EmsAnalysisService emsAnalysisService;
     private EmsAnalysisService emsAnalysisService;
+    
+    /**
+     * 获取当前日期的默认值
+     */
+    private String getCurrentDefaultTimeDimension() {
+        return "D"; // 按日统计
+    }
+    
+    /**
+     * 获取当前日期的默认值(根据时间维度返回对应格式)
+     */
+    private String getCurrentDefaultTimeValue(String timeDimension) {
+        java.time.LocalDate now = java.time.LocalDate.now();
+        int year = now.getYear();
+        int month = now.getMonthValue();
+        int day = now.getDayOfMonth();
+        if ("D".equals(timeDimension)) {
+            // 按日:返回年月日格式,如 2026-03-09
+            return year + "-" + (month < 10 ? "0" : "") + month + "-" + (day < 10 ? "0" : "") + day;
+        } else if ("M".equals(timeDimension)) {
+            // 按月:返回年月格式,如 2026-03
+            return year + "-" + (month < 10 ? "0" : "") + month;
+        } else {
+            // 按年:返回年格式,如 2026
+            return String.valueOf(year);
+        }
+    }
 
 
-    @GetMapping("/analysis/trend")
+    @GetMapping("/trend")
     public ApiResult<EmsTrendResponse> getTrend(
     public ApiResult<EmsTrendResponse> getTrend(
             @RequestParam(required = false) Long projectId,
             @RequestParam(required = false) Long projectId,
-            @RequestParam String timeDimension,
-            @RequestParam String timeValue,
+            @RequestParam(required = false) String timeDimension,
+            @RequestParam(required = false) String timeValue,
             @RequestParam(required = false) Long energyTypeId) {
             @RequestParam(required = false) Long energyTypeId) {
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
         return ApiResult.success(emsAnalysisService.getTrend(projectId, timeDimension, timeValue, energyTypeId));
         return ApiResult.success(emsAnalysisService.getTrend(projectId, timeDimension, timeValue, energyTypeId));
     }
     }
 
 
-    @GetMapping("/analysis/trend/indicators")
+    @GetMapping("/trend/indicators")
     public ApiResult<EmsTrendIndicatorsResponse> getTrendIndicators(
     public ApiResult<EmsTrendIndicatorsResponse> getTrendIndicators(
             @RequestParam(required = false) Long projectId,
             @RequestParam(required = false) Long projectId,
-            @RequestParam String timeDimension,
-            @RequestParam String timeValue,
+            @RequestParam(required = false) String timeDimension,
+            @RequestParam(required = false) String timeValue,
             @RequestParam(required = false) Long energyTypeId) {
             @RequestParam(required = false) Long energyTypeId) {
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
         return ApiResult.success(emsAnalysisService.getTrendIndicators(projectId, timeDimension, timeValue, energyTypeId));
         return ApiResult.success(emsAnalysisService.getTrendIndicators(projectId, timeDimension, timeValue, energyTypeId));
     }
     }
 
 
-    @GetMapping("/analysis/trend/category")
+    @GetMapping("/trend/category")
     public ApiResult<EmsTrendCategoryResponse> getTrendCategory(
     public ApiResult<EmsTrendCategoryResponse> getTrendCategory(
             @RequestParam(required = false) Long projectId,
             @RequestParam(required = false) Long projectId,
-            @RequestParam String timeDimension,
-            @RequestParam String timeValue,
+            @RequestParam(required = false) String timeDimension,
+            @RequestParam(required = false) String timeValue,
             @RequestParam(required = false) Long energyTypeId) {
             @RequestParam(required = false) Long energyTypeId) {
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
         return ApiResult.success(emsAnalysisService.getTrendCategory(projectId, timeDimension, timeValue, energyTypeId));
         return ApiResult.success(emsAnalysisService.getTrendCategory(projectId, timeDimension, timeValue, energyTypeId));
     }
     }
 
 
-    @GetMapping("/analysis/region")
+    @GetMapping("/region")
     public ApiResult<EmsRegionAnalysisResponse> getRegionAnalysis(
     public ApiResult<EmsRegionAnalysisResponse> getRegionAnalysis(
             @RequestParam(required = false) Long projectId,
             @RequestParam(required = false) Long projectId,
             @RequestParam(required = false) String regionIds,
             @RequestParam(required = false) String regionIds,
-            @RequestParam String timeDimension,
-            @RequestParam String timeValue,
+            @RequestParam(required = false) String timeDimension,
+            @RequestParam(required = false) String timeValue,
             @RequestParam(required = false) Long energyTypeId) {
             @RequestParam(required = false) Long energyTypeId) {
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
         return ApiResult.success(emsAnalysisService.getRegionAnalysis(projectId, regionIds, timeDimension, timeValue, energyTypeId));
         return ApiResult.success(emsAnalysisService.getRegionAnalysis(projectId, regionIds, timeDimension, timeValue, energyTypeId));
     }
     }
 
 
-    @PostMapping("/analysis/compare")
+    @PostMapping("/compare")
     public ApiResult<EmsCompareResponse> getCompare(@RequestBody EmsCompareRequest request) {
     public ApiResult<EmsCompareResponse> getCompare(@RequestBody EmsCompareRequest request) {
         return ApiResult.success(emsAnalysisService.getCompare(request));
         return ApiResult.success(emsAnalysisService.getCompare(request));
     }
     }
-}
+
+    @GetMapping("/average")
+    public ApiResult<EmsAverageResponse> getAverage(
+            @RequestParam(required = false) Long projectId,
+            @RequestParam(required = false) String timeDimension,
+            @RequestParam(required = false) String timeValue,
+            @RequestParam(required = false) Long energyTypeId) {
+        // 设置默认值:如果没有提供参数,使用默认的项目和能源类型
+        if (projectId == null) projectId = 1L;  // 默认项目ID
+        if (energyTypeId == null) energyTypeId = 1L;  // 默认电能源类型
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
+        return ApiResult.success(emsAnalysisService.getAverage(projectId, timeDimension, timeValue, energyTypeId));
+    }
+}

+ 0 - 233
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsApiV1Controller.java

@@ -1,233 +0,0 @@
-package com.usky.ems.controller.web;
-
-import com.usky.common.core.bean.ApiResult;
-import com.usky.common.core.bean.CommonPage;
-import com.usky.ems.service.EmsGatewayQueryService;
-import com.usky.ems.service.EmsModelService;
-import com.usky.ems.service.EmsOverviewService;
-import com.usky.ems.service.vo.*;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-/**
- * 能源能耗系统 API
- *
- * 对应 API 文档基础路径:/prod-api/service-ems
- * GET  /prod-api/service-ems/overview/project           获取项目信息
- * GET  /prod-api/service-ems/overview/summary            获取项目数据概括
- * GET  /prod-api/service-ems/model/structure/tree        获取项目层级树
- * GET  /prod-api/service-ems/model/energy-type/list      能源类型列表
- * POST /prod-api/service-ems/model/building              新增建筑
- * PUT  /prod-api/service-ems/model/building/{id}        编辑建筑
- * DELETE /prod-api/service-ems/model/building/{id}      删除建筑
- * POST /prod-api/service-ems/model/region                新增区域
- * PUT  /prod-api/service-ems/model/region/{id}           编辑区域
- * DELETE /prod-api/service-ems/model/region/{id}         删除区域
- * POST /prod-api/service-ems/model/floor                 新增楼层
- * PUT  /prod-api/service-ems/model/floor/{id}            编辑楼层
- * DELETE /prod-api/service-ems/model/floor/{id}          删除楼层
- * POST /prod-api/service-ems/model/gateway               新增网关
- * PUT  /prod-api/service-ems/model/gateway/{id}          编辑网关
- * DELETE /prod-api/service-ems/model/gateway/{id}        删除网关
- * POST /prod-api/service-ems/model/channel               新增通道
- * PUT  /prod-api/service-ems/model/channel/{id}          编辑通道
- * DELETE /prod-api/service-ems/model/channel/{id}        删除通道
- * POST /prod-api/service-ems/model/device                新增设备
- * PUT  /prod-api/service-ems/model/device/{id}           编辑设备
- * DELETE /prod-api/service-ems/model/device/{id}         删除设备
- * POST /prod-api/service-ems/model/attribute-point      新增属性点位
- * PUT  /prod-api/service-ems/model/attribute-point/{id}  编辑属性点位
- * DELETE /prod-api/service-ems/model/attribute-point/{id} 删除属性点位
- * GET  /prod-api/service-ems/device/gateway/list         网关列表(分页)
- * GET  /prod-api/service-ems/device/gateway/{id}         网关详情
- */
-@RestController
-@RequestMapping("/prod-api/service-ems")
-public class EmsApiV1Controller {
-
-    @Autowired
-    private EmsOverviewService emsOverviewService;
-    @Autowired
-    private EmsModelService emsModelService;
-    @Autowired
-    private EmsGatewayQueryService emsGatewayQueryService;
-
-    /**
-     * 获取项目信息
-     */
-    @GetMapping("/overview/project")
-    public ApiResult<EmsProjectResponse> getProject(@RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsOverviewService.getProject(projectId));
-    }
-
-    /**
-     * 获取项目数据概括(时间维度联动)
-     */
-    @GetMapping("/overview/summary")
-    public ApiResult<EmsSummaryResponse> getSummary(EmsSummaryRequest request) {
-        return ApiResult.success(emsOverviewService.getSummary(
-                request.getProjectId(), request.getTimeDimension(), request.getTimeValue()));
-    }
-
-    /**
-     * 获取项目层级树(建筑、区域、楼层、网关)
-     */
-    @GetMapping("/model/structure/tree")
-    public ApiResult<EmsStructureTreeNode> getStructureTree(
-            @RequestParam(required = false) Long projectId,
-            @RequestParam(required = false, defaultValue = "true") Boolean includeGateway) {
-        return ApiResult.success(emsModelService.getStructureTree(projectId, includeGateway));
-    }
-
-    /**
-     * 能源类型列表(电、水、气)
-     */
-    @GetMapping("/model/energy-type/list")
-    public ApiResult<List<EmsEnergyTypeVO>> getEnergyTypeList() {
-        return ApiResult.success(emsModelService.getEnergyTypeList());
-    }
-
-    @PostMapping("/model/building")
-    public ApiResult<EmsIdResponse> createBuilding(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createBuilding(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/building/{id}")
-    public ApiResult<Void> updateBuilding(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateBuilding(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/building/{id}")
-    public ApiResult<Void> deleteBuilding(@PathVariable Long id) {
-        emsModelService.deleteBuilding(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/region")
-    public ApiResult<EmsIdResponse> createRegion(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createRegion(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/region/{id}")
-    public ApiResult<Void> updateRegion(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateRegion(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/region/{id}")
-    public ApiResult<Void> deleteRegion(@PathVariable Long id) {
-        emsModelService.deleteRegion(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/floor")
-    public ApiResult<EmsIdResponse> createFloor(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createFloor(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/floor/{id}")
-    public ApiResult<Void> updateFloor(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateFloor(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/floor/{id}")
-    public ApiResult<Void> deleteFloor(@PathVariable Long id) {
-        emsModelService.deleteFloor(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/gateway")
-    public ApiResult<EmsIdResponse> createGateway(@RequestBody EmsModelSaveRequest request) {
-        String id = emsModelService.createGateway(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/gateway/{id}")
-    public ApiResult<Void> updateGateway(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateGateway(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/gateway/{id}")
-    public ApiResult<Void> deleteGateway(@PathVariable String id) {
-        emsModelService.deleteGateway(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/channel")
-    public ApiResult<EmsIdResponse> createChannel(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createChannel(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/channel/{id}")
-    public ApiResult<Void> updateChannel(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateChannel(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/channel/{id}")
-    public ApiResult<Void> deleteChannel(@PathVariable Long id) {
-        emsModelService.deleteChannel(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/device")
-    public ApiResult<EmsIdResponse> createDevice(@RequestBody EmsModelSaveRequest request) {
-        String id = emsModelService.createDevice(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/device/{id}")
-    public ApiResult<Void> updateDevice(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateDevice(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/device/{id}")
-    public ApiResult<Void> deleteDevice(@PathVariable String id) {
-        emsModelService.deleteDevice(id);
-        return ApiResult.success();
-    }
-
-    @PostMapping("/model/attribute-point")
-    public ApiResult<EmsIdResponse> createAttributePoint(@RequestBody EmsModelSaveRequest request) {
-        Long id = emsModelService.createAttributePoint(request);
-        return ApiResult.success(new EmsIdResponse(id));
-    }
-
-    @PutMapping("/model/attribute-point/{id}")
-    public ApiResult<Void> updateAttributePoint(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
-        emsModelService.updateAttributePoint(id, request);
-        return ApiResult.success();
-    }
-
-    @DeleteMapping("/model/attribute-point/{id}")
-    public ApiResult<Void> deleteAttributePoint(@PathVariable Long id) {
-        emsModelService.deleteAttributePoint(id);
-        return ApiResult.success();
-    }
-
-    /**
-     * 网关列表(分页)
-     */
-    @GetMapping("/device/gateway/list")
-    public ApiResult<CommonPage<EmsGatewayListItem>> gatewayList(EmsGatewayPageRequest request) {
-        return ApiResult.success(emsGatewayQueryService.listGateways(request));
-    }
-
-    /**
-     * 网关详情
-     */
-    @GetMapping("/device/gateway/{id}")
-    public ApiResult<EmsGatewayDetailResponse> getGateway(@PathVariable String id) {
-        return ApiResult.success(emsGatewayQueryService.getGatewayById(id));
-    }
-}

+ 0 - 37
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAuthController.java

@@ -1,37 +0,0 @@
-package com.usky.ems.controller.web;
-
-import com.usky.common.core.bean.ApiResult;
-import com.usky.ems.service.EmsAuthService;
-import com.usky.ems.service.vo.EmsLoginRequest;
-import com.usky.ems.service.vo.EmsLoginResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * 能源系统认证接口
- *
- * POST /prod-api/service-ems/auth/login  用户登录
- * POST /prod-api/service-ems/auth/logout 退出登录
- */
-@RestController
-@RequestMapping("/prod-api/service-ems")
-public class EmsAuthController {
-
-    @Autowired
-    private EmsAuthService emsAuthService;
-
-    @PostMapping("/auth/login")
-    public ApiResult<EmsLoginResponse> login(@RequestBody EmsLoginRequest request) {
-        EmsLoginResponse data = emsAuthService.login(request);
-        return ApiResult.success("登录成功", data);
-    }
-
-    @PostMapping("/auth/logout")
-    public ApiResult<Void> logout() {
-        emsAuthService.logout();
-        return ApiResult.success("退出成功");
-    }
-}

+ 36 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeController.java

@@ -0,0 +1,36 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsChannelTypeService;
+import com.usky.ems.service.vo.EmsChannelTypeShowVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 通道类型 前端控制器
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@RestController
+@RequestMapping("/channelType")
+public class EmsChannelTypeController {
+
+    @Autowired
+    private EmsChannelTypeService emsChannelTypeService;
+
+    /**
+     * 通道配置数据查询:返回所有通道类型及其参数、参数可选值(树形结构)
+     */
+    @GetMapping("/show")
+    public ApiResult<List<EmsChannelTypeShowVO>> show() {
+        return ApiResult.success(emsChannelTypeService.show());
+    }
+}
+

+ 21 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterController.java

@@ -0,0 +1,21 @@
+package com.usky.ems.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 通道类型参数 前端控制器
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Controller
+@RequestMapping("/emsChannelTypeParameter")
+public class EmsChannelTypeParameterController {
+
+}
+

+ 21 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsChannelTypeParameterValueController.java

@@ -0,0 +1,21 @@
+package com.usky.ems.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 通道类型参数可选值 前端控制器
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Controller
+@RequestMapping("/emsChannelTypeParameterValue")
+public class EmsChannelTypeParameterValueController {
+
+}
+

+ 32 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsDeviceEventController.java

@@ -0,0 +1,32 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsDeviceEventService;
+import com.usky.ems.service.vo.DeviceEventDTO;
+import com.usky.ems.service.vo.DeviceEventVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 设备事件接口
+ *
+ * POST /device-event/list  查询设备事件列表
+ */
+@RestController
+@RequestMapping("/device-event")
+public class EmsDeviceEventController {
+
+    @Autowired
+    private EmsDeviceEventService emsDeviceEventService;
+
+    @PostMapping("/list")
+    public ApiResult<List<DeviceEventVO>> list(@RequestBody DeviceEventDTO dto) {
+        return ApiResult.success(emsDeviceEventService.list(dto));
+    }
+}
+

+ 210 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java

@@ -0,0 +1,210 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsModelService;
+import com.usky.ems.service.vo.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 能源能耗 - 模型/结构模块 API(项目层级树、能源类型、建筑/区域/网关/通道/设备/属性点位 CRUD)
+ * 空间与建筑/区域/网关分别通过 base_space、base_build、base_area 及
+ * base_space_build、base_space_area、base_space_gateway 关联;网关在 dmp_gateway。
+ * 基础路径前缀:/model(网关再加 /prod-api/service-ems)
+ */
+@RestController
+@RequestMapping("/model")
+public class EmsModelController {
+
+    @Autowired
+    private EmsModelService emsModelService;
+
+//    /**
+//     * 获取项目层级树(建筑、区域、网关)
+//     */
+//    @GetMapping("/structure/tree")
+//    public ApiResult<EmsStructureTreeNode> getStructureTree(
+//            @RequestParam(required = false) Long projectId,
+//            @RequestParam(required = false, defaultValue = "true") Boolean includeGateway) {
+//        return ApiResult.success(emsModelService.getStructureTree(projectId, includeGateway));
+//    }
+
+    /**
+     * 能源类型列表(电、水、气)
+     */
+    @GetMapping("/energy-type/list")
+    public ApiResult<List<EmsEnergyTypeVO>> getEnergyTypeList() {
+        return ApiResult.success(emsModelService.getEnergyTypeList());
+    }
+
+    /**
+     * 展示已关联的能源类型及其下属分项/产品列表
+     * 数据来源:leo.ems_energy_item_code.energy_type 分组
+     */
+    @GetMapping("/energy-type/associated")
+    public ApiResult<List<EnergyTypeWrapperProductVO>> showAssociatedEnergyTypes() {
+        return ApiResult.success(emsModelService.showAssociatedEnergyTypes());
+    }
+
+    @PostMapping("/building")
+    public ApiResult<EmsIdResponse> createBuilding(@RequestBody EmsModelSaveRequest request) {
+        Long id = emsModelService.createBuilding(request);
+        return ApiResult.success(new EmsIdResponse(id));
+    }
+
+    @PutMapping("/building/{id}")
+    public ApiResult<Void> updateBuilding(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
+        emsModelService.updateBuilding(id, request);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping("/building/{id}")
+    public ApiResult<Void> deleteBuilding(@PathVariable Long id) {
+        emsModelService.deleteBuilding(id);
+        return ApiResult.success();
+    }
+
+    @PostMapping("/region")
+    public ApiResult<EmsIdResponse> createRegion(@RequestBody EmsModelSaveRequest request) {
+        Long id = emsModelService.createRegion(request);
+        return ApiResult.success(new EmsIdResponse(id));
+    }
+
+    @PutMapping("/region/{id}")
+    public ApiResult<Void> updateRegion(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
+        emsModelService.updateRegion(id, request);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping("/region/{id}")
+    public ApiResult<Void> deleteRegion(@PathVariable Long id) {
+        emsModelService.deleteRegion(id);
+        return ApiResult.success();
+    }
+
+    @PostMapping("/gateway")
+    public ApiResult<EmsIdResponse> createGateway(@RequestBody EmsModelSaveRequest request) {
+        String id = emsModelService.createGateway(request);
+        return ApiResult.success(new EmsIdResponse(id));
+    }
+
+    @PutMapping("/gateway/{id}")
+    public ApiResult<Void> updateGateway(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
+        emsModelService.updateGateway(id, request);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping("/gateway/{id}")
+    public ApiResult<Void> deleteGateway(@PathVariable String id) {
+        emsModelService.deleteGateway(id);
+        return ApiResult.success();
+    }
+
+//    @PostMapping("/channel")
+//    public ApiResult<EmsIdResponse> createChannel(@RequestBody EmsModelSaveRequest request) {
+//        Long id = emsModelService.createChannel(request);
+//        return ApiResult.success(new EmsIdResponse(id));
+//    }
+//
+//    @PutMapping("/channel/{id}")
+//    public ApiResult<Void> updateChannel(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
+//        emsModelService.updateChannel(id, request);
+//        return ApiResult.success();
+//    }
+//
+//    @DeleteMapping("/channel/{id}")
+//    public ApiResult<Void> deleteChannel(@PathVariable Long id) {
+//        emsModelService.deleteChannel(id);
+//        return ApiResult.success();
+//    }
+//
+//    @PostMapping("/device")
+//    public ApiResult<EmsIdResponse> createDevice(@RequestBody EmsModelSaveRequest request) {
+//        String id = emsModelService.createDevice(request);
+//        return ApiResult.success(new EmsIdResponse(id));
+//    }
+//
+//    @PutMapping("/device/{id}")
+//    public ApiResult<Void> updateDevice(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
+//        emsModelService.updateDevice(id, request);
+//        return ApiResult.success();
+//    }
+//
+//    @DeleteMapping("/device/{id}")
+//    public ApiResult<Void> deleteDevice(@PathVariable String id) {
+//        emsModelService.deleteDevice(id);
+//        return ApiResult.success();
+//    }
+//
+//    @PostMapping("/attribute-point")
+//    public ApiResult<EmsIdResponse> createAttributePoint(@RequestBody EmsModelSaveRequest request) {
+//        Long id = emsModelService.createAttributePoint(request);
+//        return ApiResult.success(new EmsIdResponse(id));
+//    }
+//
+//    @PutMapping("/attribute-point/{id}")
+//    public ApiResult<Void> updateAttributePoint(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
+//        emsModelService.updateAttributePoint(id, request);
+//        return ApiResult.success();
+//    }
+//
+//    @DeleteMapping("/attribute-point/{id}")
+//    public ApiResult<Void> deleteAttributePoint(@PathVariable Long id) {
+//        emsModelService.deleteAttributePoint(id);
+//        return ApiResult.success();
+//    }
+
+    /**
+     * 网关-通道-设备树(中间面板)
+     * GET /prod-api/service-ems/model/gateway-device/tree(与类上 /model 前缀组合,勿再嵌套 /model)
+     * @param spaceId 空间ID(可选,不传返回全部网关)
+     * @param keyword 网关名称关键字(可选,模糊)
+     */
+    @GetMapping("/gateway-device/tree")
+    public ApiResult<List<EmsGatewayDeviceTreeNode>> getGatewayDeviceTree(
+            @RequestParam(required = false) Long spaceId,
+            @RequestParam(required = false) String keyword) {
+        return ApiResult.success(emsModelService.getGatewayDeviceTree(spaceId, keyword));
+    }
+
+    /**
+     * 属性点位列表(右侧面板)
+     * GET /prod-api/service-ems/model/attribute-point/list
+     * @param deviceId 设备ID(必填)
+     */
+    @GetMapping("/attribute-point/list")
+    public ApiResult<List<EmsAttributePointVO>> getAttributePointList(
+            @RequestParam String deviceId) {
+        return ApiResult.success(emsModelService.getAttributePointList(deviceId));
+    }
+
+    /**
+     * 空间绑定网关(一个空间绑定一个或多个网关)
+     */
+    @PostMapping("/space/gateway/bind")
+    public ApiResult<Void> bindSpaceGateways(@RequestBody SpaceGatewayBindRequest request) {
+        emsModelService.bindSpaceGateways(request.getSpaceId(), request.getGatewayUuids());
+        return ApiResult.success();
+    }
+
+    /**
+     * 空间解绑网关(一个空间解绑一个或多个网关)
+     */
+    @PostMapping("/space/gateway/unbind")
+    public ApiResult<Void> unbindSpaceGateways(@RequestBody SpaceGatewayBindRequest request) {
+        emsModelService.unbindSpaceGateways(request.getSpaceId(), request.getGatewayUuids());
+        return ApiResult.success();
+    }
+
+    /**
+     * 根据空间ID查询网关列表
+     */
+    @GetMapping("/space/{spaceId}/gateway/list")
+    public ApiResult<List<DmpGatewayDetailResponse>> getGatewayListBySpaceId(@PathVariable Long spaceId) {
+        return ApiResult.success(emsModelService.getGatewayListBySpaceId(spaceId));
+    }
+
+
+}

+ 134 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java

@@ -0,0 +1,134 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsOverviewService;
+import com.usky.ems.service.vo.EmsProjectResponse;
+import com.usky.ems.service.vo.EmsSummaryRequest;
+import com.usky.ems.service.vo.EmsSummaryResponse;
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
+import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 能源能耗 - 概览模块 API
+ * 建筑排名等涉及空间树的数据来自 base_space(见 {@link com.usky.ems.service.impl.EmsOverviewServiceImpl})。
+ * 基础路径前缀:/overview(网关再加 /prod-api/service-ems)
+ */
+@RestController
+@RequestMapping("/overview")
+public class EmsOverviewController {
+
+    @Autowired
+    private EmsOverviewService emsOverviewService;
+
+    /**
+     * 获取项目信息
+     */
+    @GetMapping("/project")
+    public ApiResult<EmsProjectResponse> getProject(@RequestParam(required = false) Integer projectId) {
+        return ApiResult.success(emsOverviewService.getProject(projectId));
+    }
+
+    /**
+     * 获取项目数据概括(时间维度联动)
+     */
+    @GetMapping("/summary")
+    public ApiResult<EmsSummaryResponse> getSummary(EmsSummaryRequest request) {
+        return ApiResult.success(emsOverviewService.getSummary(
+                request.getProjectId(), request.getTimeDimension(), request.getTimeValue()));
+    }
+
+    /**
+     * 获取概览页能源类型条目(如电/水/气)
+     */
+    @GetMapping("/item")
+    public ApiResult<java.util.List<EmsOverviewEnergyItemVO>> queryOverviewItem(
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewItem(projectId));
+    }
+
+    /**
+     * 获取概览页设备系统统计(每个系统下设备数量)
+     */
+    @GetMapping("/device-info")
+    public ApiResult<java.util.List<EmsOverviewDeviceSystemStatVO>> queryOverviewDeviceInfo(
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewDeviceInfo(projectId));
+    }
+
+    /**
+     * 分类能耗统计(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年等,暂仅作为占位,不影响当前模拟结果)
+     * - itemCode:能耗条目编码
+     * - energyType:能耗类型
+     * - projectId:项目 ID(可选,当前实现未做真实查询,仅占位)
+     */
+    @GetMapping("/classification-energy")
+    public ApiResult<java.util.Map<String, Object>> queryClassificationEnergy(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam Integer energyType,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryClassificationEnergy(dateType, itemCode, energyType, projectId));
+    }
+
+    /**
+     * 能耗用能趋势(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:能耗条目编码
+     * - spaceId:空间ID(可选,当前仍为模拟数据占位)
+     */
+    @GetMapping("/energy-trend")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryEnergyTrend(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long spaceId) {
+        return ApiResult.success(emsOverviewService.queryEnergyTrend(dateType, itemCode, spaceId));
+    }
+
+    /**
+     * 建筑能耗分析(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:能耗条目编码
+     * - spaceId:空间ID(可选;下钻时与 base_space 子节点一致)
+     */
+    @GetMapping("/building-ranking")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryBuildingRanking(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long spaceId) {
+        return ApiResult.success(emsOverviewService.queryBuildingRanking(dateType, itemCode, spaceId));
+    }
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览顶部)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - projectId:项目ID(可选,不传则取第一个项目)
+     */
+    @GetMapping("/top")
+    public ApiResult<java.util.Map<String, Object>> queryOverviewTop(
+            @RequestParam Integer dateType,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewTop(dateType, projectId));
+    }
+
+    /**
+     * 分项能耗占比
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:分项编码(作为父项,展开下级分项)
+     * - projectId:项目ID(可选,不传则取第一个项目)
+     */
+    @GetMapping("/item-ratio")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryItemRatio(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryItemRatio(dateType, itemCode, projectId));
+    }
+}

+ 37 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java

@@ -0,0 +1,37 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsProjectService;
+import com.usky.ems.service.vo.EmsProjectResponse;
+import com.usky.ems.service.vo.EmsProjectSaveRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 项目(ems_project)新增、修改、删除
+ * 基础路径前缀:/project(网关再加 /prod-api/service-ems)
+ */
+@RestController
+@RequestMapping("/project")
+public class EmsProjectController {
+
+    @Autowired
+    private EmsProjectService emsProjectService;
+
+    @PostMapping
+    public ApiResult<EmsProjectResponse> create(@RequestBody EmsProjectSaveRequest request) {
+        return ApiResult.success(emsProjectService.save(request));
+    }
+
+    @PutMapping
+    public ApiResult<Void> update(@RequestBody EmsProjectSaveRequest request) {
+        emsProjectService.update(request);
+        return ApiResult.success();
+    }
+
+    @DeleteMapping("/{spaceId}")
+    public ApiResult<Void> delete(@PathVariable Long spaceId) {
+        emsProjectService.remove(spaceId);
+        return ApiResult.success();
+    }
+}

+ 20 - 14
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java

@@ -2,6 +2,7 @@ package com.usky.ems.controller.web;
 
 
 import com.usky.common.core.bean.ApiResult;
 import com.usky.common.core.bean.ApiResult;
 import com.usky.ems.service.EmsReportService;
 import com.usky.ems.service.EmsReportService;
+import com.usky.ems.service.EmsModelService;
 import com.usky.ems.service.vo.*;
 import com.usky.ems.service.vo.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
@@ -12,29 +13,34 @@ import java.util.List;
 /**
 /**
  * 统计报表接口
  * 统计报表接口
  * 能源报表、区域报表、采集报表
  * 能源报表、区域报表、采集报表
+ * 基础路径前缀:/report(网关再加 /prod-api/service-ems)
  */
  */
 @RestController
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/report")
 public class EmsReportController {
 public class EmsReportController {
 
 
     @Autowired
     @Autowired
     private EmsReportService emsReportService;
     private EmsReportService emsReportService;
 
 
+    @Autowired
+    private EmsModelService emsModelService;
+
     // ---------- 能源报表 ----------
     // ---------- 能源报表 ----------
-    @GetMapping("/report/energy/devices")
-    public ApiResult<EmsReportDevicesResponse> getEnergyDevices(
-            @RequestParam Long energyTypeId,
+    @GetMapping("/energy/devices")
+    public ApiResult<List<EnergyTypeWrapperProductVO>> getEnergyDevices(
+            @RequestParam(required = false) Long energyTypeId,
             @RequestParam(required = false) String keyword,
             @RequestParam(required = false) String keyword,
             @RequestParam(required = false) Long projectId) {
             @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsReportService.getEnergyDevices(energyTypeId, keyword, projectId));
+        // TODO: 如需按 energyTypeId/keyword/projectId 过滤,可在 EmsModelService 中扩展方法
+        return ApiResult.success(emsModelService.showAssociatedEnergyTypes());
     }
     }
 
 
-    @PostMapping("/report/energy/statistics")
+    @PostMapping("/energy/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getEnergyStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
     public ApiResult<EmsEnergyStatisticsResponse> getEnergyStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
         return ApiResult.success(emsReportService.getEnergyStatistics(request));
         return ApiResult.success(emsReportService.getEnergyStatistics(request));
     }
     }
 
 
-    @GetMapping("/report/energy/export")
+    @GetMapping("/energy/export")
     public void exportEnergy(
     public void exportEnergy(
             @RequestParam String deviceIds,
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
             @RequestParam(required = false) String attributePointIds,
@@ -46,7 +52,7 @@ public class EmsReportController {
     }
     }
 
 
     // ---------- 区域报表 ----------
     // ---------- 区域报表 ----------
-    @GetMapping("/report/region/devices")
+    @GetMapping("/region/devices")
     public ApiResult<EmsReportDevicesResponse> getRegionDevices(
     public ApiResult<EmsReportDevicesResponse> getRegionDevices(
             @RequestParam Long energyTypeId,
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) Long regionId,
             @RequestParam(required = false) Long regionId,
@@ -55,14 +61,14 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getRegionDevices(energyTypeId, regionId, keyword, projectId));
         return ApiResult.success(emsReportService.getRegionDevices(energyTypeId, regionId, keyword, projectId));
     }
     }
 
 
-    @PostMapping("/report/region/statistics")
+    @PostMapping("/region/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getRegionStatistics(
     public ApiResult<EmsEnergyStatisticsResponse> getRegionStatistics(
             @RequestBody EmsEnergyStatisticsRequest request,
             @RequestBody EmsEnergyStatisticsRequest request,
             @RequestParam(required = false) List<Long> regionIds) {
             @RequestParam(required = false) List<Long> regionIds) {
         return ApiResult.success(emsReportService.getRegionStatistics(request, regionIds));
         return ApiResult.success(emsReportService.getRegionStatistics(request, regionIds));
     }
     }
 
 
-    @GetMapping("/report/region/export")
+    @GetMapping("/region/export")
     public void exportRegion(
     public void exportRegion(
             @RequestParam String deviceIds,
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
             @RequestParam(required = false) String attributePointIds,
@@ -75,7 +81,7 @@ public class EmsReportController {
     }
     }
 
 
     // ---------- 采集报表 ----------
     // ---------- 采集报表 ----------
-    @GetMapping("/report/collection/devices")
+    @GetMapping("/collection/devices")
     public ApiResult<EmsReportDevicesResponse> getCollectionDevices(
     public ApiResult<EmsReportDevicesResponse> getCollectionDevices(
             @RequestParam Long energyTypeId,
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) String keyword,
             @RequestParam(required = false) String keyword,
@@ -83,17 +89,17 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getCollectionDevices(energyTypeId, keyword, projectId));
         return ApiResult.success(emsReportService.getCollectionDevices(energyTypeId, keyword, projectId));
     }
     }
 
 
-    @PostMapping("/report/collection/realtime")
+    @PostMapping("/collection/realtime")
     public ApiResult<EmsCollectionRealtimeResponse> getCollectionRealtime(@RequestBody EmsCollectionRealtimeRequest request) {
     public ApiResult<EmsCollectionRealtimeResponse> getCollectionRealtime(@RequestBody EmsCollectionRealtimeRequest request) {
         return ApiResult.success(emsReportService.getCollectionRealtime(request));
         return ApiResult.success(emsReportService.getCollectionRealtime(request));
     }
     }
 
 
-    @PostMapping("/report/collection/statistics")
+    @PostMapping("/collection/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getCollectionStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
     public ApiResult<EmsEnergyStatisticsResponse> getCollectionStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
         return ApiResult.success(emsReportService.getCollectionStatistics(request));
         return ApiResult.success(emsReportService.getCollectionStatistics(request));
     }
     }
 
 
-    @GetMapping("/report/collection/export")
+    @GetMapping("/collection/export")
     public void exportCollection(
     public void exportCollection(
             @RequestParam String deviceIds,
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
             @RequestParam(required = false) String attributePointIds,

+ 54 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsSystemDictController.java

@@ -0,0 +1,54 @@
+package com.usky.ems.controller.web;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.domain.EmsSystemDictCode;
+import com.usky.ems.domain.EmsSystemDictRegion;
+import com.usky.ems.domain.EmsSystemDictValue;
+import com.usky.ems.service.EmsSystemDictCodeService;
+import com.usky.ems.service.EmsSystemDictRegionService;
+import com.usky.ems.service.EmsSystemDictValueService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 系统字典(ems_system_dict_code / ems_system_dict_region / ems_system_dict_value)查询接口
+ * 基础路径前缀:/system-dict
+ */
+@RestController
+@RequestMapping("/system-dict")
+public class EmsSystemDictController {
+
+    @Autowired
+    private EmsSystemDictCodeService emsSystemDictCodeService;
+    @Autowired
+    private EmsSystemDictRegionService emsSystemDictRegionService;
+    @Autowired
+    private EmsSystemDictValueService emsSystemDictValueService;
+
+    @GetMapping("/code/list")
+    public ApiResult<List<EmsSystemDictCode>> listCodes() {
+        return ApiResult.success(emsSystemDictCodeService.list());
+    }
+
+    /**
+     * @param parent 父级区域编码(如国标省市区上一级 code;省级可传约定根编码,需与字典数据一致)
+     */
+    @GetMapping("/region/list")
+    public ApiResult<List<EmsSystemDictRegion>> listRegions(@RequestParam String parent) {
+        return ApiResult.success(emsSystemDictRegionService.listByParentCode(parent));
+    }
+
+    @GetMapping("/value/list")
+    public ApiResult<List<EmsSystemDictValue>> listValues(@RequestParam String dictCode) {
+        return ApiResult.success(emsSystemDictValueService.list(
+                new LambdaQueryWrapper<EmsSystemDictValue>()
+                        .eq(EmsSystemDictValue::getDictCode, dictCode)
+                        .orderByAsc(EmsSystemDictValue::getSort)));
+    }
+}

+ 8 - 8
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpaceArea.java → service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseArea.java

@@ -12,20 +12,18 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 
 
 /**
 /**
- * 区域(leo.ems_space_area)
+ * 区域(base_area)
  */
  */
 @Data
 @Data
 @EqualsAndHashCode(callSuper = false)
 @EqualsAndHashCode(callSuper = false)
-@TableName("ems_space_area")
-public class EmsSpaceArea implements Serializable {
+@TableName("base_area")
+public class BaseArea implements Serializable {
 
 
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
     @TableId(value = "id", type = IdType.AUTO)
     @TableId(value = "id", type = IdType.AUTO)
     private Long id;
     private Long id;
 
 
-    @TableField("space_id")
-    private Long spaceId;
     private String name;
     private String name;
     private Integer type;
     private Integer type;
     private BigDecimal area;
     private BigDecimal area;
@@ -35,12 +33,14 @@ public class EmsSpaceArea implements Serializable {
     private BigDecimal airConditionedArea;
     private BigDecimal airConditionedArea;
     @TableField("resident_population")
     @TableField("resident_population")
     private Integer residentPopulation;
     private Integer residentPopulation;
+    @TableField("tenant_id")
+    private Integer tenantId;
     @TableField("updated_by")
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
+    @TableField("created_by")
+    private String createdBy;
     @TableField("update_time")
     @TableField("update_time")
     private LocalDateTime updateTime;
     private LocalDateTime updateTime;
-    @TableField("created_by")
-    private Long createdBy;
     @TableField("create_time")
     @TableField("create_time")
     private LocalDateTime createTime;
     private LocalDateTime createTime;
 }
 }

+ 97 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseBuild.java

@@ -0,0 +1,97 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 建筑信息(base_build),字段与表结构一致
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_build")
+public class BaseBuild implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("build_num")
+    private String buildNum;
+    @TableField("build_name")
+    private String buildName;
+    private String address;
+    @TableField("model_address")
+    private String modelAddress;
+    @TableField("above_floor")
+    private Integer aboveFloor;
+    @TableField("under_floor")
+    private Integer underFloor;
+    @TableField("build_area")
+    private Double buildArea;
+    @TableField("cover_area")
+    private Double coverArea;
+    @TableField("fire_rating")
+    private Integer fireRating;
+    @TableField("use_character")
+    private Integer useCharacter;
+    @TableField("build_structure")
+    private Integer buildStructure;
+    @TableField("build_high")
+    private Double buildHigh;
+    @TableField("high_type")
+    private Integer highType;
+    @TableField("complete_year")
+    private LocalDate completeYear;
+    @TableField("safe_person")
+    private String safePerson;
+    @TableField("manage_person")
+    private String managePerson;
+    @TableField("fire_risk")
+    private Integer fireRisk;
+    @TableField("fire_control_room")
+    private String fireControlRoom;
+    @TableField("build_inside")
+    private String buildInside;
+    @TableField("build_plan")
+    private String buildPlan;
+    @TableField("facility_id")
+    private Integer facilityId;
+    @TableField("bim_url")
+    private String bimUrl;
+    @TableField("contact_phone")
+    private String contactPhone;
+    @TableField("build_desc")
+    private String buildDesc;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("update_by")
+    private String updateBy;
+    @TableField("create_by")
+    private String createBy;
+    @TableField("delete_flag")
+    private Integer deleteFlag;
+    @TableField("under_space")
+    private Double underSpace;
+    /** 防火涂层:0 无、1 有 */
+    @TableField("fireproof_coat")
+    private Integer fireproofCoat;
+    @TableField("dept_id")
+    private Integer deptId;
+    @TableField("tenant_id")
+    private Integer tenantId;
+    private String longitude;
+    private String latitude;
+    @TableField("unit_count")
+    private Integer unitCount;
+}

+ 8 - 8
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpace.java → service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpace.java

@@ -11,13 +11,13 @@ import java.io.Serializable;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 
 
 /**
 /**
- * 空间(leo.ems_space)
- * 空间类型:1项目 2区域 3建筑 4楼层 5房间
+ * 空间(base_space)
+ * 空间类型:1项目 2区域 3建筑
  */
  */
 @Data
 @Data
 @EqualsAndHashCode(callSuper = false)
 @EqualsAndHashCode(callSuper = false)
-@TableName("ems_space")
-public class EmsSpace implements Serializable {
+@TableName("base_space")
+public class BaseSpace implements Serializable {
 
 
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
@@ -31,15 +31,15 @@ public class EmsSpace implements Serializable {
     @TableField("root_id")
     @TableField("root_id")
     private Long rootId;
     private Long rootId;
     private String path;
     private String path;
-    @TableField("path_name")
-    private String pathName;
     private Integer deep;
     private Integer deep;
     @TableField("updated_by")
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
     @TableField("update_time")
     @TableField("update_time")
     private LocalDateTime updateTime;
     private LocalDateTime updateTime;
     @TableField("created_by")
     @TableField("created_by")
-    private Long createdBy;
+    private String createdBy;
     @TableField("create_time")
     @TableField("create_time")
     private LocalDateTime createTime;
     private LocalDateTime createTime;
+    @TableField("tenant_id")
+    private Integer tenantId;
 }
 }

+ 34 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceArea.java

@@ -0,0 +1,34 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 空间-区域关联(base_space_area)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_space_area")
+public class BaseSpaceArea implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("space_id")
+    private Long spaceId;
+    @TableField("area_id")
+    private Long areaId;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+}

+ 34 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceBuild.java

@@ -0,0 +1,34 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 空间-建筑关联(base_space_build)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_space_build")
+public class BaseSpaceBuild implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("space_id")
+    private Long spaceId;
+    @TableField("build_id")
+    private Integer buildId;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+}

+ 34 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/BaseSpaceGateway.java

@@ -0,0 +1,34 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 空间-网关关联(base_space_gateway)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("base_space_gateway")
+public class BaseSpaceGateway implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("space_id")
+    private Long spaceId;
+    @TableField("gateway_uuid")
+    private String gatewayUuid;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+}

+ 27 - 19
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsGateway.java → service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpGateway.java

@@ -11,54 +11,62 @@ import java.io.Serializable;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 
 
 /**
 /**
- * 网关(leo.ems_gateway)
+ * 网关表(dmp_gateway)
  */
  */
 @Data
 @Data
 @EqualsAndHashCode(callSuper = false)
 @EqualsAndHashCode(callSuper = false)
-@TableName("ems_gateway")
-public class EmsGateway implements Serializable {
+@TableName("dmp_gateway")
+public class DmpGateway implements Serializable {
 
 
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
-    @TableId(value = "id", type = IdType.INPUT)
-    private String id;
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("device_uuid")
+    private String deviceUuid;
 
 
-    @TableField("project_id")
-    private Long projectId;
     private String name;
     private String name;
-    @TableField("space_id")
-    private Long spaceId;
-    private String version;
-    private String type;
+
     private String ip;
     private String ip;
+
     private Integer port;
     private Integer port;
+
+    private String installAddress;
+
     @TableField("comm_status")
     @TableField("comm_status")
     private Integer commStatus;
     private Integer commStatus;
+
     @TableField("online_time")
     @TableField("online_time")
     private LocalDateTime onlineTime;
     private LocalDateTime onlineTime;
+
     @TableField("offline_time")
     @TableField("offline_time")
     private LocalDateTime offlineTime;
     private LocalDateTime offlineTime;
+
     @TableField("update_config_time")
     @TableField("update_config_time")
     private LocalDateTime updateConfigTime;
     private LocalDateTime updateConfigTime;
+
     @TableField("update_protocol_time")
     @TableField("update_protocol_time")
     private LocalDateTime updateProtocolTime;
     private LocalDateTime updateProtocolTime;
+
     @TableField("upgrade_time")
     @TableField("upgrade_time")
     private LocalDateTime upgradeTime;
     private LocalDateTime upgradeTime;
-    @TableField("data_center_id")
-    private Long dataCenterId;
-    private String iccid;
-    private Integer rssi;
-    @TableField("secret_key")
-    private String secretKey;
+
     @TableField("virtual_device")
     @TableField("virtual_device")
     private Integer virtualDevice;
     private Integer virtualDevice;
+
     private String remark;
     private String remark;
+
     @TableField("updated_by")
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
+
     @TableField("update_time")
     @TableField("update_time")
     private LocalDateTime updateTime;
     private LocalDateTime updateTime;
+
     @TableField("created_by")
     @TableField("created_by")
-    private Long createdBy;
+    private String createdBy;
+
     @TableField("create_time")
     @TableField("create_time")
     private LocalDateTime createTime;
     private LocalDateTime createTime;
+    private Integer tenantId;
 }
 }

+ 92 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpProduct.java

@@ -0,0 +1,92 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 产品信息表(dmp_product)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("dmp_product")
+public class DmpProduct implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键id */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /** 产品名称 */
+    private String productName;
+
+    /** 接入方式(1 设备直连;2 网关接入) */
+    private Integer accessMode;
+
+    /** 网络类型(1 WIFI;2 移动蜂窝数据;3 NB-IoT;4 以太网) */
+    private Integer networkType;
+
+    /** 设备类型(501 监控系统;502 门禁系统;503 梯控系统;504 机房系统;509 环境系统;510 照明系统) */
+    private Integer deviceType;
+
+    /** 通信协议(1 MQTT;2 TCP 设备直连;3 HTTP) */
+    private Integer comProtocol;
+
+    /** 认证方式 */
+    private String authMode;
+
+    /** 设备型号 */
+    private String deviceModel;
+
+    /** 产品描述 */
+    private String productDescribe;
+
+    /** 厂家名称 */
+    private String factoryName;
+
+    /** 厂家联系人 */
+    private String factoryPerson;
+
+    /** 厂家联系电话 */
+    private String factoryPhone;
+
+    /** 资质证书1 */
+    private String certificateUrl1;
+
+    /** 资质证书2 */
+    private String certificateUrl2;
+
+    /** 资质证书3 */
+    private String certificateUrl3;
+
+    /** 协议文档 */
+    private String agreementUrl;
+
+    /** 删除标识 */
+    private Integer deleteFlag;
+
+    /** 创建人 */
+    private String createdBy;
+
+    /** 创建时间 */
+    private LocalDateTime createdTime;
+
+    /** 更新人 */
+    private String updatedBy;
+
+    /** 更新时间 */
+    private LocalDateTime updatedTime;
+
+    /** 租户号 */
+    private Integer tenantId;
+
+    /** 产品编码 */
+    private String productCode;
+}
+

+ 1 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannel.java

@@ -27,6 +27,7 @@ public class EmsChannel implements Serializable {
     private String gatewayId;
     private String gatewayId;
     private String name;
     private String name;
     @TableField("channel_type_id")
     @TableField("channel_type_id")
+    // 通道类型ID(1:Serial Port,2:TCP)
     private Integer channelTypeId;
     private Integer channelTypeId;
     @TableField("updated_by")
     @TableField("updated_by")
     private Long updatedBy;
     private Long updatedBy;

+ 61 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelType.java

@@ -0,0 +1,61 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 通道类型
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EmsChannelType implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 自增ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 类型名称
+     */
+    private String name;
+
+    /**
+     * 类型值
+     */
+    private String value;
+
+    /**
+     * 更新人
+     */
+    private Long updatedBy;
+
+    /**
+     * 记录更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private Long createdBy;
+
+    /**
+     * 记录创建时间
+     */
+    private LocalDateTime createTime;
+
+
+}

+ 71 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameter.java

@@ -0,0 +1,71 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 通道类型参数
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EmsChannelTypeParameter implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 自增ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 通道类型ID
+     */
+    private Integer channelTypeId;
+
+    /**
+     * 参数名称
+     */
+    private String name;
+
+    /**
+     * 参数值
+     */
+    private String value;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 更新人
+     */
+    private Long updatedBy;
+
+    /**
+     * 记录更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private Long createdBy;
+
+    /**
+     * 记录创建时间
+     */
+    private LocalDateTime createTime;
+
+
+}

+ 71 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsChannelTypeParameterValue.java

@@ -0,0 +1,71 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 通道类型参数可选值
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EmsChannelTypeParameterValue implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 自增ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 通道类型参数ID
+     */
+    private Integer channelTypeParameterId;
+
+    /**
+     * 可选值名称
+     */
+    private String name;
+
+    /**
+     * 可选值
+     */
+    private String value;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 更新人
+     */
+    private Long updatedBy;
+
+    /**
+     * 记录更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private Long createdBy;
+
+    /**
+     * 记录创建时间
+     */
+    private LocalDateTime createTime;
+
+
+}

+ 47 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDataUploadShanghaiProduct.java

@@ -0,0 +1,47 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 上海市区平台数据上传(产品功能标识对应的区平台功能标识)
+ * 表:ems_data_upload_shanghai_product
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_data_upload_shanghai_product")
+public class EmsDataUploadShanghaiProduct implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /** 产品ID */
+    private Long productId;
+
+    /** 功能标识 */
+    private String identifier;
+
+    /** 自定义标识符 */
+    private String customIdentifier;
+
+    /** 更新人 */
+    private Long updatedBy;
+
+    /** 记录更新时间 */
+    private LocalDateTime updateTime;
+
+    /** 创建人 */
+    private Long createdBy;
+
+    /** 记录创建时间 */
+    private LocalDateTime createTime;
+}
+

+ 71 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceEvent.java

@@ -0,0 +1,71 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 设备事件记录(leo.ems_device_event)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_device_event")
+public class EmsDeviceEvent implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    private String sn;
+
+    @TableField("device_id")
+    private String deviceId;
+
+    private String identifier;
+
+    /** 设备类型 1:设备 2:网关 */
+    @TableField("device_type")
+    private Integer deviceType;
+
+    /** 事件类型 1:通讯告警 2:功能告警 3:数据异常 */
+    @TableField("event_type")
+    private Integer eventType;
+
+    @TableField("suppressed_to")
+    private LocalDateTime suppressedTo;
+
+    /** 确认类型 0:未确认 1:自动确认 2:人工确认 */
+    @TableField("confirmation_type")
+    private Integer confirmationType;
+
+    @TableField("task_sn")
+    private String taskSn;
+
+    private String content;
+
+    @TableField("start_time")
+    private LocalDateTime startTime;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 61 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsEnergyConsumptionFormula.java

@@ -0,0 +1,61 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 空间能耗计算公式(ems_energy_consumption_formula)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_energy_consumption_formula")
+public class EmsEnergyConsumptionFormula implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("space_id")
+    private Long spaceId;
+
+    @TableField("item_code")
+    private String itemCode;
+
+    private String name;
+
+    private String formula;
+
+    @TableField("compute_start_time")
+    private LocalDateTime computeStartTime;
+
+    @TableField("compute_end_time")
+    private LocalDateTime computeEndTime;
+
+    @TableField("data_time")
+    private LocalDateTime dataTime;
+
+    private Integer status;
+
+    private String cause;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 6 - 3
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProject.java

@@ -12,7 +12,7 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 
 
 /**
 /**
- * 项目(leo.ems_project)
+ * 项目(ems_project)
  */
  */
 @Data
 @Data
 @EqualsAndHashCode(callSuper = false)
 @EqualsAndHashCode(callSuper = false)
@@ -26,6 +26,7 @@ public class EmsProject implements Serializable {
 
 
     @TableField("space_id")
     @TableField("space_id")
     private Long spaceId;
     private Long spaceId;
+
     private String name;
     private String name;
     @TableField("platform_name")
     @TableField("platform_name")
     private String platformName;
     private String platformName;
@@ -60,12 +61,14 @@ public class EmsProject implements Serializable {
     private String logo;
     private String logo;
     @TableField("logo_min")
     @TableField("logo_min")
     private String logoMin;
     private String logoMin;
+    @TableField("tenant_id")
+    private Integer tenantId;
     @TableField("updated_by")
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
     @TableField("update_time")
     @TableField("update_time")
     private LocalDateTime updateTime;
     private LocalDateTime updateTime;
     @TableField("created_by")
     @TableField("created_by")
-    private Long createdBy;
+    private String createdBy;
     @TableField("create_time")
     @TableField("create_time")
     private LocalDateTime createTime;
     private LocalDateTime createTime;
 }
 }

+ 39 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectDeviceSystem.java

@@ -0,0 +1,39 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 项目与设备系统关联(ems_project_device_system)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_project_device_system")
+public class EmsProjectDeviceSystem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    @TableField("device_system")
+    private Integer deviceSystem;
+    @TableField("updated_by")
+    private String updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private String createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 0 - 67
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpaceBuilding.java

@@ -1,67 +0,0 @@
-package com.usky.ems.domain;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-/**
- * 建筑(leo.ems_space_building)
- */
-@Data
-@EqualsAndHashCode(callSuper = false)
-@TableName("ems_space_building")
-public class EmsSpaceBuilding implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    @TableId(value = "id", type = IdType.AUTO)
-    private Long id;
-
-    @TableField("space_id")
-    private Long spaceId;
-    private String name;
-    private BigDecimal area;
-    @TableField("common_area")
-    private BigDecimal commonArea;
-    @TableField("air_conditioned_area")
-    private BigDecimal airConditionedArea;
-    @TableField("resident_population")
-    private Integer residentPopulation;
-    @TableField("province_code")
-    private String provinceCode;
-    @TableField("province_name")
-    private String provinceName;
-    @TableField("city_code")
-    private String cityCode;
-    @TableField("city_name")
-    private String cityName;
-    @TableField("district_code")
-    private String districtCode;
-    @TableField("district_name")
-    private String districtName;
-    private String location;
-    private String address;
-    private Integer floor;
-    private BigDecimal height;
-    @TableField("type_id")
-    private Integer typeId;
-    @TableField("type_name")
-    private String typeName;
-    private String image;
-    private String introduction;
-    @TableField("updated_by")
-    private Long updatedBy;
-    @TableField("update_time")
-    private LocalDateTime updateTime;
-    @TableField("created_by")
-    private Long createdBy;
-    @TableField("create_time")
-    private LocalDateTime createTime;
-}

+ 6 - 14
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSpaceFloor.java → service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictCode.java

@@ -8,32 +8,24 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.EqualsAndHashCode;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
-import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 
 
 /**
 /**
- * 楼层(leo.ems_space_floor
+ * 系统字典编码(ems_system_dict_code
  */
  */
 @Data
 @Data
 @EqualsAndHashCode(callSuper = false)
 @EqualsAndHashCode(callSuper = false)
-@TableName("ems_space_floor")
-public class EmsSpaceFloor implements Serializable {
+@TableName("ems_system_dict_code")
+public class EmsSystemDictCode implements Serializable {
 
 
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
     @TableId(value = "id", type = IdType.AUTO)
     @TableId(value = "id", type = IdType.AUTO)
-    private Long id;
+    private Integer id;
 
 
-    @TableField("space_id")
-    private Long spaceId;
     private String name;
     private String name;
-    private BigDecimal area;
-    @TableField("common_area")
-    private BigDecimal commonArea;
-    @TableField("air_conditioned_area")
-    private BigDecimal airConditionedArea;
-    @TableField("resident_population")
-    private Integer residentPopulation;
+    private String code;
+    private String remark;
     @TableField("updated_by")
     @TableField("updated_by")
     private Long updatedBy;
     private Long updatedBy;
     @TableField("update_time")
     @TableField("update_time")

+ 37 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictRegion.java

@@ -0,0 +1,37 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 省市区字典(ems_system_dict_region)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_system_dict_region")
+public class EmsSystemDictRegion implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    private String code;
+    private String name;
+    private String parent;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 43 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsSystemDictValue.java

@@ -0,0 +1,43 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 系统字典值(ems_system_dict_value)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_system_dict_value")
+public class EmsSystemDictValue implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("dict_code")
+    private String dictCode;
+    @TableField("parent_id")
+    private Integer parentId;
+    private String name;
+    private String value;
+    private String icon;
+    private Integer sort;
+    private String remark;
+    @TableField("updated_by")
+    private Long updatedBy;
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    @TableField("created_by")
+    private Long createdBy;
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 82 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/TemporalTypeEnum.java

@@ -0,0 +1,82 @@
+package com.usky.ems.enums;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+/**
+ * 时间维度枚举:D-日 / M-月 / Y-年
+ */
+public enum TemporalTypeEnum {
+
+    DAY("D"),
+    MONTH("M"),
+    YEAR("Y");
+
+    private final String code;
+
+    TemporalTypeEnum(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public static TemporalTypeEnum get(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (TemporalTypeEnum e : values()) {
+            if (e.code.equalsIgnoreCase(code)) {
+                return e;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 将开始时间归一化到当前时间维度的起点
+     */
+    public LocalDateTime normalizeStart(LocalDateTime time) {
+        if (time == null) {
+            return null;
+        }
+        LocalDate date = time.toLocalDate();
+        switch (this) {
+            case DAY:
+                return LocalDateTime.of(date, LocalTime.MIN);
+            case MONTH:
+                LocalDate firstDayOfMonth = date.withDayOfMonth(1);
+                return LocalDateTime.of(firstDayOfMonth, LocalTime.MIN);
+            case YEAR:
+                LocalDate firstDayOfYear = date.withDayOfYear(1);
+                return LocalDateTime.of(firstDayOfYear, LocalTime.MIN);
+            default:
+                return time;
+        }
+    }
+
+    /**
+     * 将结束时间归一化到当前时间维度的结束
+     */
+    public LocalDateTime normalizeEnd(LocalDateTime time) {
+        if (time == null) {
+            return null;
+        }
+        LocalDate date = time.toLocalDate();
+        switch (this) {
+            case DAY:
+                return LocalDateTime.of(date, LocalTime.MAX);
+            case MONTH:
+                LocalDate lastDayOfMonth = date.withDayOfMonth(date.lengthOfMonth());
+                return LocalDateTime.of(lastDayOfMonth, LocalTime.MAX);
+            case YEAR:
+                LocalDate lastDayOfYear = date.withDayOfYear(date.lengthOfYear());
+                return LocalDateTime.of(lastDayOfYear, LocalTime.MAX);
+            default:
+                return time;
+        }
+    }
+}
+

+ 55 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/enums/ValueTypeEnum.java

@@ -0,0 +1,55 @@
+package com.usky.ems.enums;
+
+import java.util.Objects;
+
+/**
+ * 数值类型枚举,对应 leo.sql 中 ems_device_function_formula.value_type 字段:
+ * 0: 其他  1: 累计值  2: 波动值
+ */
+public enum ValueTypeEnum {
+
+    OTHER("其他", 0),
+    CUMULATIVE("累计值", 1),
+    FLUCTUATION("波动值", 2);
+
+    private final String name;
+    private final Short value;
+
+    ValueTypeEnum(String name, Integer value) {
+        this.name = name;
+        this.value = value.shortValue();
+    }
+
+    /**
+     * 根据数值获取名称
+     */
+    public static String getName(Short type) {
+        for (ValueTypeEnum e : values()) {
+            if (Objects.equals(e.getValue(), type)) {
+                return e.getName();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 根据数值获取枚举实例;如果不存在则抛出异常
+     */
+    public static ValueTypeEnum getInstance(Short type) {
+        for (ValueTypeEnum e : values()) {
+            if (Objects.equals(e.getValue(), type)) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException("错误的值类型");
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Short getValue() {
+        return value;
+    }
+}
+

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseAreaMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseArea;
+
+/**
+ * 区域 Mapper(base_area)
+ */
+public interface BaseAreaMapper extends CrudMapper<BaseArea> {
+}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseBuildMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseBuild;
+
+/**
+ * 建筑 Mapper(base_build)
+ */
+public interface BaseBuildMapper extends CrudMapper<BaseBuild> {
+}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceAreaMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseSpaceArea;
+
+/**
+ * 空间-区域关联 Mapper(base_space_area)
+ */
+public interface BaseSpaceAreaMapper extends CrudMapper<BaseSpaceArea> {
+}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceBuildMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseSpaceBuild;
+
+/**
+ * 空间-建筑关联 Mapper(base_space_build)
+ */
+public interface BaseSpaceBuildMapper extends CrudMapper<BaseSpaceBuild> {
+}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceGatewayMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseSpaceGateway;
+
+/**
+ * 空间-网关关联 Mapper(base_space_gateway)
+ */
+public interface BaseSpaceGatewayMapper extends CrudMapper<BaseSpaceGateway> {
+}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/BaseSpaceMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.BaseSpace;
+
+/**
+ * 空间 Mapper(base_space)
+ */
+public interface BaseSpaceMapper extends CrudMapper<BaseSpace> {
+}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpGatewayMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.DmpGateway;
+
+/**
+ * 网关表 dmp_gateway Mapper
+ */
+public interface DmpGatewayMapper extends CrudMapper<DmpGateway> {
+}

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpProductMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.DmpProduct;
+
+/**
+ * 产品信息表 Mapper(dmp_product)
+ */
+public interface DmpProductMapper extends CrudMapper<DmpProduct> {
+}
+

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeMapper.java

@@ -0,0 +1,16 @@
+package com.usky.ems.mapper;
+
+import com.usky.ems.domain.EmsChannelType;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 通道类型 Mapper 接口
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeMapper extends CrudMapper<EmsChannelType> {
+
+}

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterMapper.java

@@ -0,0 +1,16 @@
+package com.usky.ems.mapper;
+
+import com.usky.ems.domain.EmsChannelTypeParameter;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 通道类型参数 Mapper 接口
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeParameterMapper extends CrudMapper<EmsChannelTypeParameter> {
+
+}

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsChannelTypeParameterValueMapper.java

@@ -0,0 +1,16 @@
+package com.usky.ems.mapper;
+
+import com.usky.ems.domain.EmsChannelTypeParameterValue;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 通道类型参数可选值 Mapper 接口
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeParameterValueMapper extends CrudMapper<EmsChannelTypeParameterValue> {
+
+}

+ 12 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDataUploadShanghaiProductMapper.java

@@ -0,0 +1,12 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDataUploadShanghaiProduct;
+
+/**
+ * 上海市区平台数据上传(产品功能标识对应的区平台功能标识)Mapper
+ * 表:ems_data_upload_shanghai_product
+ */
+public interface EmsDataUploadShanghaiProductMapper extends CrudMapper<EmsDataUploadShanghaiProduct> {
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceEventMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDeviceEvent;
+
+/**
+ * 设备事件记录 Mapper(leo.ems_device_event)
+ */
+public interface EmsDeviceEventMapper extends CrudMapper<EmsDeviceEvent> {
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsEnergyConsumptionFormulaMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsEnergyConsumptionFormula;
+
+/**
+ * 空间能耗计算公式 Mapper(ems_energy_consumption_formula)
+ */
+public interface EmsEnergyConsumptionFormulaMapper extends CrudMapper<EmsEnergyConsumptionFormula> {
+}
+

+ 0 - 10
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsGatewayMapper.java

@@ -1,10 +0,0 @@
-package com.usky.ems.mapper;
-
-import com.usky.common.mybatis.core.CrudMapper;
-import com.usky.ems.domain.EmsGateway;
-
-/**
- * 网关 Mapper(leo.ems_gateway)
- */
-public interface EmsGatewayMapper extends CrudMapper<EmsGateway> {
-}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectDeviceSystemMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsProjectDeviceSystem;
+
+/**
+ * 项目设备系统关联 Mapper(ems_project_device_system)
+ */
+public interface EmsProjectDeviceSystemMapper extends CrudMapper<EmsProjectDeviceSystem> {
+}

+ 1 - 1
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectMapper.java

@@ -4,7 +4,7 @@ import com.usky.common.mybatis.core.CrudMapper;
 import com.usky.ems.domain.EmsProject;
 import com.usky.ems.domain.EmsProject;
 
 
 /**
 /**
- * 项目 Mapper(leo.ems_project)
+ * 项目 Mapper(ems_project)
  */
  */
 public interface EmsProjectMapper extends CrudMapper<EmsProject> {
 public interface EmsProjectMapper extends CrudMapper<EmsProject> {
 }
 }

+ 0 - 10
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceAreaMapper.java

@@ -1,10 +0,0 @@
-package com.usky.ems.mapper;
-
-import com.usky.common.mybatis.core.CrudMapper;
-import com.usky.ems.domain.EmsSpaceArea;
-
-/**
- * 区域 Mapper(leo.ems_space_area)
- */
-public interface EmsSpaceAreaMapper extends CrudMapper<EmsSpaceArea> {
-}

+ 0 - 10
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceBuildingMapper.java

@@ -1,10 +0,0 @@
-package com.usky.ems.mapper;
-
-import com.usky.common.mybatis.core.CrudMapper;
-import com.usky.ems.domain.EmsSpaceBuilding;
-
-/**
- * 建筑 Mapper(leo.ems_space_building)
- */
-public interface EmsSpaceBuildingMapper extends CrudMapper<EmsSpaceBuilding> {
-}

+ 0 - 10
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceFloorMapper.java

@@ -1,10 +0,0 @@
-package com.usky.ems.mapper;
-
-import com.usky.common.mybatis.core.CrudMapper;
-import com.usky.ems.domain.EmsSpaceFloor;
-
-/**
- * 楼层 Mapper(leo.ems_space_floor)
- */
-public interface EmsSpaceFloorMapper extends CrudMapper<EmsSpaceFloor> {
-}

+ 0 - 10
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSpaceMapper.java

@@ -1,10 +0,0 @@
-package com.usky.ems.mapper;
-
-import com.usky.common.mybatis.core.CrudMapper;
-import com.usky.ems.domain.EmsSpace;
-
-/**
- * 空间 Mapper(leo.ems_space)
- */
-public interface EmsSpaceMapper extends CrudMapper<EmsSpace> {
-}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictCodeMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsSystemDictCode;
+
+/**
+ * 系统字典编码 Mapper(ems_system_dict_code)
+ */
+public interface EmsSystemDictCodeMapper extends CrudMapper<EmsSystemDictCode> {
+}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictRegionMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsSystemDictRegion;
+
+/**
+ * 省市区字典 Mapper(ems_system_dict_region)
+ */
+public interface EmsSystemDictRegionMapper extends CrudMapper<EmsSystemDictRegion> {
+}

+ 10 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsSystemDictValueMapper.java

@@ -0,0 +1,10 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsSystemDictValue;
+
+/**
+ * 系统字典值 Mapper(ems_system_dict_value)
+ */
+public interface EmsSystemDictValueMapper extends CrudMapper<EmsSystemDictValue> {
+}

+ 41 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/BaseSpaceService.java

@@ -0,0 +1,41 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.BaseSpace;
+import com.usky.ems.service.vo.BaseSpaceForestNodeVO;
+
+import java.util.List;
+
+/**
+ * 空间服务(base_space)
+ */
+public interface BaseSpaceService extends CrudService<BaseSpace> {
+
+    /**
+     * 构建空间树
+     *
+     * @return 根节点(如存在多个根,则返回一个虚拟根节点)
+     */
+    BaseSpaceForestNodeVO tree();
+
+    /**
+     * 获取当前用户在指定空间下有权限访问的空间ID列表
+     * 目前简单返回传入的 spaceId 本身,后续可根据实际权限模型扩展
+     */
+    List<Long> getAuthorizedSpaceIds(Long spaceId);
+
+    /**
+     * 递归获取某空间节点下的所有子节点(不包含自身)
+     */
+    List<BaseSpace> recursiveAllChildrenNode(Long spaceId);
+
+    /**
+     * 更新项目根空间名称(与 ems_project 名称同步)
+     */
+    void updateProjectSpaceName(Long spaceId, String newName);
+
+    /**
+     * 是否叶子节点:无子空间
+     */
+    boolean isLeafSpace(Long spaceId);
+}

+ 28 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpGatewayService.java

@@ -0,0 +1,28 @@
+package com.usky.ems.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.DmpGateway;
+import com.usky.ems.service.vo.DmpGatewayDevicePageRequest;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
+import com.usky.ems.service.vo.DmpGatewayListItem;
+import com.usky.ems.service.vo.DmpGatewayPageRequest;
+
+/**
+ * dmp_gateway 网关维护与查询
+ */
+public interface DmpGatewayService extends CrudService<DmpGateway> {
+
+    void add(DmpGateway entity);
+
+    void update(DmpGateway entity);
+
+    boolean remove(Integer id);
+
+    CommonPage<DmpGatewayListItem> pageGateways(DmpGatewayPageRequest request);
+
+    CommonPage<DmpDevice> pageGatewayDevices(DmpGatewayDevicePageRequest request);
+
+    DmpGatewayDetailResponse getGatewayDetail(Integer id);
+}

+ 23 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/DmpProductService.java

@@ -0,0 +1,23 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.DmpProduct;
+import com.usky.ems.enums.ValueTypeEnum;
+import com.usky.ems.service.vo.ProductFunctionVO;
+
+import java.util.List;
+
+/**
+ * 产品信息表 服务接口(service-ems)
+ */
+public interface DmpProductService extends CrudService<DmpProduct> {
+
+    /**
+     * 根据产品 ID 与数值类型获取功能列表
+     * 说明:当前实现基于 leo.sql 中 ems_data_upload_shanghai_product 表,
+     * 仅按 productId 过滤,不区分 valueTypeEnum。
+     */
+    List<ProductFunctionVO> getFunctions(Long id, ValueTypeEnum valueTypeEnum);
+
+}
+

+ 2 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAnalysisService.java

@@ -16,4 +16,6 @@ public interface EmsAnalysisService {
     EmsRegionAnalysisResponse getRegionAnalysis(Long projectId, String regionIds, String timeDimension, String timeValue, Long energyTypeId);
     EmsRegionAnalysisResponse getRegionAnalysis(Long projectId, String regionIds, String timeDimension, String timeValue, Long energyTypeId);
 
 
     EmsCompareResponse getCompare(EmsCompareRequest request);
     EmsCompareResponse getCompare(EmsCompareRequest request);
+
+    EmsAverageResponse getAverage(Long projectId, String timeDimension, String timeValue, Long energyTypeId);
 }
 }

+ 0 - 14
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAuthService.java

@@ -1,14 +0,0 @@
-package com.usky.ems.service;
-
-import com.usky.ems.service.vo.EmsLoginRequest;
-import com.usky.ems.service.vo.EmsLoginResponse;
-
-/**
- * 能源系统认证服务(登录/登出)
- */
-public interface EmsAuthService {
-
-    EmsLoginResponse login(EmsLoginRequest request);
-
-    void logout();
-}

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeParameterService.java

@@ -0,0 +1,16 @@
+package com.usky.ems.service;
+
+import com.usky.ems.domain.EmsChannelTypeParameter;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 通道类型参数 服务类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeParameterService extends CrudService<EmsChannelTypeParameter> {
+
+}

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeParameterValueService.java

@@ -0,0 +1,16 @@
+package com.usky.ems.service;
+
+import com.usky.ems.domain.EmsChannelTypeParameterValue;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 通道类型参数可选值 服务类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeParameterValueService extends CrudService<EmsChannelTypeParameterValue> {
+
+}

+ 23 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsChannelTypeService.java

@@ -0,0 +1,23 @@
+package com.usky.ems.service;
+
+import com.usky.ems.domain.EmsChannelType;
+import com.usky.ems.service.vo.EmsChannelTypeShowVO;
+import com.usky.common.mybatis.core.CrudService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 通道类型 服务类
+ * </p>
+ *
+ * @author ya
+ * @since 2026-03-16
+ */
+public interface EmsChannelTypeService extends CrudService<EmsChannelType> {
+
+    /**
+     * 查询通道配置数据(类型 + 参数 + 参数可选值树形结构)
+     */
+    List<EmsChannelTypeShowVO> show();
+}

+ 18 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceEventService.java

@@ -0,0 +1,18 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.DeviceEventDTO;
+import com.usky.ems.service.vo.DeviceEventVO;
+
+import java.util.List;
+
+/**
+ * 设备事件服务(EmsDeviceEvent 子模块)
+ */
+public interface EmsDeviceEventService {
+
+    /**
+     * 按条件查询设备事件列表
+     */
+    List<DeviceEventVO> list(DeviceEventDTO dto);
+}
+

+ 25 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceReportService.java

@@ -0,0 +1,25 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.DeviceEnergyReportRequest;
+import com.usky.ems.service.vo.DeviceEnergyReportItemVO;
+import com.usky.ems.service.vo.DeviceFunctionValueReportDTO;
+
+import java.util.List;
+
+/**
+ * 设备能耗报表相关服务(EmsDevice 子模块)
+ */
+public interface EmsDeviceReportService {
+
+    /**
+     * 查询能耗报表用的设备列表,并按名称进行排序。
+     */
+    List<DeviceEnergyReportItemVO> showEnergyReportDevices(DeviceEnergyReportRequest request);
+
+    /**
+     * 展示设备能耗报表数据(按时间维度与功能点进行统计)。
+     * 返回值结构后续可根据实际前端展示需求进行扩展,这里使用 Object 以保持灵活性。
+     */
+    Object showEnergyReportData(DeviceFunctionValueReportDTO dto);
+}
+

+ 19 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsEnergyItemCodeService.java

@@ -0,0 +1,19 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.service.vo.EnergyItemCodeVO;
+
+import java.util.List;
+
+/**
+ * 能源分项编码 服务接口
+ */
+public interface EmsEnergyItemCodeService extends CrudService<EmsEnergyItemCode> {
+
+    /**
+     * 查询指定空间下的分项能耗配置(基于能耗公式和分项编码组装树形结构)。
+     */
+    List<EnergyItemCodeVO> querySpaceEnergyItem(Long spaceId);
+}
+

+ 0 - 22
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsGatewayQueryService.java

@@ -1,22 +0,0 @@
-package com.usky.ems.service;
-
-import com.usky.common.core.bean.CommonPage;
-import com.usky.ems.service.vo.EmsGatewayDetailResponse;
-import com.usky.ems.service.vo.EmsGatewayListItem;
-import com.usky.ems.service.vo.EmsGatewayPageRequest;
-
-/**
- * 网关列表与详情查询服务
- */
-public interface EmsGatewayQueryService {
-
-    /**
-     * 分页查询网关列表
-     */
-    CommonPage<EmsGatewayListItem> listGateways(EmsGatewayPageRequest request);
-
-    /**
-     * 网关详情
-     */
-    EmsGatewayDetailResponse getGatewayById(String id);
-}

+ 64 - 28
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsModelService.java

@@ -1,26 +1,36 @@
 package com.usky.ems.service;
 package com.usky.ems.service;
 
 
+import com.usky.ems.service.vo.EmsAttributePointVO;
 import com.usky.ems.service.vo.EmsEnergyTypeVO;
 import com.usky.ems.service.vo.EmsEnergyTypeVO;
+import com.usky.ems.service.vo.EmsGatewayDeviceTreeNode;
 import com.usky.ems.service.vo.EmsModelSaveRequest;
 import com.usky.ems.service.vo.EmsModelSaveRequest;
 import com.usky.ems.service.vo.EmsStructureTreeNode;
 import com.usky.ems.service.vo.EmsStructureTreeNode;
+import com.usky.ems.service.vo.EnergyTypeWrapperProductVO;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
 
 
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
- * 基础建模服务(model:结构树、能源类型、建筑/区域/楼层/网关/通道/设备/属性点位)
+ * 基础建模服务(model:结构树、能源类型、建筑/区域/网关/通道/设备/属性点位)
  */
  */
 public interface EmsModelService {
 public interface EmsModelService {
 
 
     /**
     /**
-     * 获取项目层级树(建筑、区域、楼层、网关)
+     * 获取项目层级树(建筑、区域、网关)
      */
      */
-    EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway);
+//    EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway);
 
 
     /**
     /**
      * 能源类型列表(电、水、气)
      * 能源类型列表(电、水、气)
      */
      */
     List<EmsEnergyTypeVO> getEnergyTypeList();
     List<EmsEnergyTypeVO> getEnergyTypeList();
 
 
+    /**
+     * 展示已关联的能源类型及其下属分项/产品列表
+     * 数据来源:leo.ems_energy_item_code.energy_type 分组
+     */
+    List<EnergyTypeWrapperProductVO> showAssociatedEnergyTypes();
+
     /** 建筑:新增 */
     /** 建筑:新增 */
     Long createBuilding(EmsModelSaveRequest request);
     Long createBuilding(EmsModelSaveRequest request);
     /** 建筑:编辑 */
     /** 建筑:编辑 */
@@ -35,38 +45,64 @@ public interface EmsModelService {
     /** 区域:删除 */
     /** 区域:删除 */
     void deleteRegion(Long id);
     void deleteRegion(Long id);
 
 
-    /** 楼层:新增 */
-    Long createFloor(EmsModelSaveRequest request);
-    /** 楼层:编辑 */
-    void updateFloor(Long id, EmsModelSaveRequest request);
-    /** 楼层:删除 */
-    void deleteFloor(Long id);
-
     /** 网关:新增 */
     /** 网关:新增 */
     String createGateway(EmsModelSaveRequest request);
     String createGateway(EmsModelSaveRequest request);
     /** 网关:编辑 */
     /** 网关:编辑 */
     void updateGateway(String id, EmsModelSaveRequest request);
     void updateGateway(String id, EmsModelSaveRequest request);
     /** 网关:删除 */
     /** 网关:删除 */
     void deleteGateway(String id);
     void deleteGateway(String id);
+//
+//    /** 通道:新增 */
+//    Long createChannel(EmsModelSaveRequest request);
+//    /** 通道:编辑 */
+//    void updateChannel(Long id, EmsModelSaveRequest request);
+//    /** 通道:删除 */
+//    void deleteChannel(Long id);
+//
+//    /** 设备:新增 */
+//    String createDevice(EmsModelSaveRequest request);
+//    /** 设备:编辑 */
+//    void updateDevice(String id, EmsModelSaveRequest request);
+//    /** 设备:删除 */
+//    void deleteDevice(String id);
+//
+//    /** 属性点位:新增 */
+//    Long createAttributePoint(EmsModelSaveRequest request);
+//    /** 属性点位:编辑 */
+//    void updateAttributePoint(Long id, EmsModelSaveRequest request);
+//    /** 属性点位:删除 */
+//    void deleteAttributePoint(Long id);
+
+    /**
+     * 网关-通道-设备树(中间面板)
+     * @param spaceId 空间ID(可选,不传返回全部网关)
+     * @param keyword 网关名称关键字模糊搜索(可选)
+     */
+    List<EmsGatewayDeviceTreeNode> getGatewayDeviceTree(Long spaceId, String keyword);
+
+    /**
+     * 属性点位列表(右侧面板)
+     * @param deviceId 设备ID
+     */
+    List<EmsAttributePointVO> getAttributePointList(String deviceId);
 
 
-    /** 通道:新增 */
-    Long createChannel(EmsModelSaveRequest request);
-    /** 通道:编辑 */
-    void updateChannel(Long id, EmsModelSaveRequest request);
-    /** 通道:删除 */
-    void deleteChannel(Long id);
+    /**
+     * 空间绑定网关(一个空间可绑定多个网关)
+     * @param spaceId 空间ID
+     * @param gatewayUuids 网关uuid列表
+     */
+    void bindSpaceGateways(Long spaceId, List<String> gatewayUuids);
 
 
-    /** 设备:新增 */
-    String createDevice(EmsModelSaveRequest request);
-    /** 设备:编辑 */
-    void updateDevice(String id, EmsModelSaveRequest request);
-    /** 设备:删除 */
-    void deleteDevice(String id);
+    /**
+     * 空间解绑网关(一个空间可解绑多个网关)
+     * @param spaceId 空间ID
+     * @param gatewayUuids 网关uuid列表
+     */
+    void unbindSpaceGateways(Long spaceId, List<String> gatewayUuids);
 
 
-    /** 属性点位:新增 */
-    Long createAttributePoint(EmsModelSaveRequest request);
-    /** 属性点位:编辑 */
-    void updateAttributePoint(Long id, EmsModelSaveRequest request);
-    /** 属性点位:删除 */
-    void deleteAttributePoint(Long id);
+    /**
+     * 根据空间ID查询绑定的网关列表
+     * @param spaceId 空间ID
+     */
+    List<DmpGatewayDetailResponse> getGatewayListBySpaceId(Long spaceId);
 }
 }

+ 45 - 1
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java

@@ -2,6 +2,10 @@ package com.usky.ems.service;
 
 
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsSummaryResponse;
 import com.usky.ems.service.vo.EmsSummaryResponse;
+import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
+
+import java.util.Map;
 
 
 /**
 /**
  * 能源总览服务(overview)
  * 能源总览服务(overview)
@@ -11,10 +15,50 @@ public interface EmsOverviewService {
     /**
     /**
      * 获取项目信息(当前项目或指定 projectId)
      * 获取项目信息(当前项目或指定 projectId)
      */
      */
-    EmsProjectResponse getProject(Long projectId);
+    EmsProjectResponse getProject(Integer projectId);
 
 
     /**
     /**
      * 获取项目数据概括(时间维度联动)
      * 获取项目数据概括(时间维度联动)
      */
      */
     EmsSummaryResponse getSummary(Long projectId, String timeDimension, String timeValue);
     EmsSummaryResponse getSummary(Long projectId, String timeDimension, String timeValue);
+
+    /**
+     * 获取概览页能源类型条目(如电/水/气)
+     */
+    java.util.List<EmsOverviewEnergyItemVO> queryOverviewItem(Long projectId);
+
+    /**
+     * 获取概览页设备系统统计(每个系统下设备数量)
+     */
+    java.util.List<EmsOverviewDeviceSystemStatVO> queryOverviewDeviceInfo(Long projectId);
+
+    /**
+     * 分类能耗统计(按时间维度、能耗类型等)
+     * 先按 Map 结构返回,后续可以再封装 VO。
+     */
+    Map<String, Object> queryClassificationEnergy(Integer dateType, String itemCode, Integer energyType, Long projectId);
+
+    /**
+     * 能耗用能趋势(按时间维度:日/月/年)
+     * 返回当前期与去年同期的用能趋势数据列表(当前为模拟数据实现)。
+     */
+    java.util.List<java.util.Map<String, Object>> queryEnergyTrend(Integer dateType, String itemCode, Long spaceId);
+
+    /**
+     * 建筑能耗分析(建筑/楼层能耗排名)
+     * 返回按能耗从高到低排序的建筑(或楼层)名称及其能耗值列表(当前为模拟数据实现)。
+     */
+    java.util.List<java.util.Map<String, Object>> queryBuildingRanking(Integer dateType, String itemCode, Long spaceId);
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览页顶部)
+     * 部分数据来自真实表(项目、折标系数),能耗数值按规则模拟。
+     */
+    Map<String, Object> queryOverviewTop(Integer dateType, Long projectId);
+
+    /**
+     * 分项能耗占比(按分项编码 itemCode 展开下级条目)
+     * 部分数据(分项定义)来自 leo.ems_energy_item_code,能耗数值按规则模拟。
+     */
+    java.util.List<java.util.Map<String, Object>> queryItemRatio(Integer dateType, String itemCode, Long projectId);
 }
 }

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectService.java

@@ -0,0 +1,16 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.EmsProjectResponse;
+import com.usky.ems.service.vo.EmsProjectSaveRequest;
+
+/**
+ * 项目(ems_project)维护:新增、修改、删除(含空间树、省市区校验、设备系统关联)
+ */
+public interface EmsProjectService {
+
+    EmsProjectResponse save(EmsProjectSaveRequest request);
+
+    EmsProjectResponse update(EmsProjectSaveRequest request);
+
+    void remove(Long spaceId);
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов