Procházet zdrojové kódy

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

fanghuisheng před 2 týdny
rodič
revize
d466f0d50f
100 změnil soubory, kde provedl 3495 přidání a 924 odebrání
  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-job</module>
 
 
@@ -88,6 +87,8 @@
 
         <module>service-tsdb</module>
 
+        <module>service-rule</module>
+
         <!--    <module>service-data</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")
-    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"));
     }
 }

+ 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,
-                 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)
         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)
         fieldMapping.put("707", "wd");
 
@@ -56,6 +59,9 @@ public class DeviceFieldConfig {
         // 位移传感器
         fieldMapping.put("714", "wy");
 
+        // 液位
+        fieldMapping.put("716", "sensorValue");
+
         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, "有倾斜"),
 
     // 裂缝检测告警
-    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 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.vo.alarm.AlarmMessageVO;
 import com.usky.cdi.service.enums.AlarmType;
+import com.usky.common.security.utils.SecurityUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,11 +22,9 @@ import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
+import java.text.SimpleDateFormat;
 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;
 
 /**
@@ -49,6 +48,7 @@ public class AlarmDataSyncService {
     private static final String WARTIME = "wartime";
     private static final String ALARM_DELIVERY_KEY_PREFIX = "alarm:delivery:";
     private final MqttConnectionTool mqttConnectionTool;
+    private final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
     @Autowired
     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 desc = MqttTopics.Alarm.MESSAGE.getDesc();
 
+        String userName = "自动同步";
+        try {
+            userName = SecurityUtils.getUsername();
+        } catch (Exception e) {
+            log.error("定时任务无法获取用户名,使用默认‘自动同步’", e);
+        }
+
         try {
             // 2.创建MQTT连接
             mqttConnectionTool.connectOrRefresh(username, password);
@@ -198,7 +205,7 @@ public class AlarmDataSyncService {
             endTime = System.currentTimeMillis();
 
             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) {
             log.error("租户{}的告警数据推送定时任务执行失败:{}", tenantId, e.getMessage(), e);
         } finally {
@@ -206,7 +213,7 @@ public class AlarmDataSyncService {
             log.info("结束时间:{}, 耗时:{}ms", getCurrentTime(), endTime - startTime);
 
             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
      */
     private Long generateDataPacketID() {
-        return idGenerator.nextPacketId();
+        return idGenerator.nextPacketId10();
     }
 
     /**
@@ -69,7 +69,7 @@ public class AlarmDataTransferService {
                 vo.setDataPacketID(generateDataPacketID());
             }
             if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
+                vo.setPublishTime(getCurrentTime());
             }
 
 //            HashMap<String, Object> map = new HashMap<>();
@@ -87,7 +87,7 @@ public class AlarmDataTransferService {
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             String json = jsonObject.toJSONString();
             System.out.println(json);
-            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
+            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101100017", "gjB4v1bh");
             String topic = "alarm/message";
             IQeIRyXG.sendToMqtt(topic, json);
 
@@ -106,17 +106,16 @@ public class AlarmDataTransferService {
      * @return 是否发送成功
      */
     public boolean sendAlarmMessage1(AlarmMessageVO vo) {
-        MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
 
         try {
             if (vo.getDataPacketID() == null) {
                 vo.setDataPacketID(generateDataPacketID());
             }
             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("engineeringID", vo.getEngineeringID());
 //            map.put("floor", vo.getFloor());
@@ -132,6 +131,7 @@ public class AlarmDataTransferService {
             String json = jsonObject.toJSONString();
             System.out.println(json);
             String topic = "alarm/message";
+            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101100017", "gjB4v1bh");
             IQeIRyXG.sendToMqtt(topic, json);
 
             return true;
@@ -147,13 +147,13 @@ public class AlarmDataTransferService {
                 vo.setDataPacketID(generateDataPacketID());
             }
             if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
+                vo.setPublishTime(getCurrentTime());
             }
 
             JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
             String json = jsonObject.toJSONString();
             System.out.println(json);
-            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
+            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101100017", "gjB4v1bh");
             String topic = "alarm/message";
             IQeIRyXG.sendToMqtt(topic, json);
 
@@ -170,10 +170,10 @@ public class AlarmDataTransferService {
                 vo.setDataPacketID(generateDataPacketID());
             }
             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);
             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.JSONObject;
 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.usky.cdi.domain.BaseBuildFacility;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.service.BaseBuildFacilityService;
-import com.usky.cdi.service.CdiDeliveryLogService;
 import com.usky.cdi.service.DmpDeviceInfoService;
 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.vo.info.EngineeringBaseVO;
 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 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.Paths;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 /**
@@ -48,8 +55,11 @@ public class BaseDataTransferService {
     @Resource
     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 SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@@ -122,7 +132,8 @@ public class BaseDataTransferService {
             String topic = "base/protectiveUnit";
 
             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;
         } catch (Exception e) {
@@ -140,57 +151,67 @@ public class BaseDataTransferService {
      */
     public boolean sendFloorPlane(FloorPlaneVO vo) {
         try {
+            // ========== 1. 基础参数填充 ==========
             if (vo.getDataPacketID() == null) {
                 vo.setDataPacketID(generateDataPacketID());
             }
             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));
 
-            // 检查文件大小(不超过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;
             }
 
-            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("engineeringID", vo.getEngineeringID());
             map.put("floor", vo.getFloor());
             map.put("floorFileID", vo.getFloorFileID());
             map.put("floorFileName", vo.getFloorFileName());
             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("publishTime", vo.getPublishTime());
+
+            //使用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";
+            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;
+
         } catch (Exception e) {
-            log.error("发送楼层平面图信息失败,FileID: {}", vo.getFloorFileID(), e);
+            // 打印完整异常
+            e.printStackTrace();
+            System.err.println("❌ 发送失败:" + e.getMessage());
             return false;
         }
     }
@@ -223,19 +244,21 @@ public class BaseDataTransferService {
             userIdToName.put(702, 31);
             userIdToName.put(703, 33);
             userIdToName.put(704, 11);
+            userIdToName.put(705, 11);
             userIdToName.put(707, 19);
             userIdToName.put(708, 19);
             userIdToName.put(709, 15);
             userIdToName.put(710, 16);
             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(716, 26);
 
             HashMap<String, Object> map = new HashMap<>();
             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("sensorID", Integer.parseInt(vo.getDeviceId()));
             map.put("sensorNo", vo.getDeviceUuid());
@@ -253,7 +276,8 @@ public class BaseDataTransferService {
             String topic = "base/sensorInfo";
             System.out.println(gson.toJson(map));
 //            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;
         } catch (Exception e) {
@@ -290,7 +314,7 @@ public class BaseDataTransferService {
      * @param tenantId 租户ID
      * @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<DmpDevice> list1 = dmpDeviceInfoService.deviceInfo(tenantId);
         List<FacilityDeviceVO> list2 = new ArrayList<>();
@@ -309,6 +333,9 @@ public class BaseDataTransferService {
                         facilityDeviceVO.setDeviceUuid(list1.get(k).getDeviceUuid());
                         facilityDeviceVO.setFacilityDesc(list.get(j).getFacilityDesc());
                         facilityDeviceVO.setDeviceType(list1.get(k).getDeviceType());
+                        facilityDeviceVO.setEngineeringID(engineeringId);
+                        facilityDeviceVO.setUserName(username);
+                        facilityDeviceVO.setPassword(password);
                         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.date.DateUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 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.metadata.IPage;
 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.service.CdiDeliveryLogService;
 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.info.FloorPlaneVO;
 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.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Value;
 
+import javax.annotation.PostConstruct;
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
+import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
+import java.net.URLEncoder;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -84,108 +91,60 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     @Autowired
     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
     public List<SyncTaskStatisticsVO> selectById(Long id) {
-        // 1. 租户ID校验(必须非空,无租户直接返回空列表)
         Integer tenantId = SecurityUtils.getTenantId();
-        if (tenantId == null) {
+        if (tenantId == null || tenantId <= 0) {
             log.warn("未获取到当前租户ID,无法查询人防投递日志");
             return Collections.emptyList();
         }
 
-        // 2. 动态构建查询条件:id为null时只查租户,id不为null时租户+id精准查
-        // 【小优化】按ID倒序,后续取最新数据更直观(ID自增则大ID是最新)
         List<CdiDeliveryLog> logList = lambdaQuery()
                 .eq(CdiDeliveryLog::getTenantId, tenantId)
                 .eq(id != null, CdiDeliveryLog::getId, id)
-                .orderByDesc(CdiDeliveryLog::getId) // 改为倒序,优先最新数据
+                .orderByDesc(CdiDeliveryLog::getId)
                 .list();
 
         LambdaQueryWrapper<CdiDefenseProject> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(CdiDefenseProject::getTenantId, tenantId);
-        // 【空指针防护】新增非空判断,避免selectOne返回null时报错
         CdiDefenseProject defenseProject = cdiDefenseProjectMapper.selectOne(queryWrapper);
         boolean isEnable = defenseProject != null && defenseProject.getIsEnable() == 0;
 
         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;
         }
 
-        // 优先自动解析: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()
                 .filter(logEntity -> logEntity != null
                         && StrUtil.isNotBlank(logEntity.getInfoContent())
@@ -193,130 +152,185 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 .collect(Collectors.toList());
 
         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++) {
-            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));
-
-        // 6. 返回结果
         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
     public CommonPage<CdiDeliveryLog> logList(Long id, Integer pageNum, Integer pageSize, Integer dataType, Integer
             logType, String startTime, String endTime) {
@@ -432,6 +446,13 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         Integer tenantId = one.getTenantId();
         LocalDateTime now = LocalDateTime.now();
 
+        String userName = "自动同步";
+        try {
+            userName = SecurityUtils.getUsername();
+        } catch (Exception e) {
+            log.error("无法获取用户名或姓名,使用默认‘自动同步’", e);
+        }
+
         switch (vo.getDataType()) {
             // 单元信息
             case 1:
@@ -460,7 +481,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 endTime = System.currentTimeMillis();
 
                 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;
             // 平面图信息
@@ -472,10 +493,16 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 int total2 = 0, success2 = 0, failure2 = 0, notSynced2 = 0;
 
                 List<FloorPlaneVO> floorPlaneVOS = buildPlanes(tenantId, engineeringId);
+                if (CollUtil.isEmpty(floorPlaneVOS)) {
+                    log.error("未找到楼层平面图信息!");
+                    break;
+                }
                 total2 = floorPlaneVOS.size();
                 iotDataTransferService.createMqttConnection(username, password);
                 String topic1 = MqttTopics.Base.FLOOR_PLANE.getTopic();
                 String desc1 = MqttTopics.Base.FLOOR_PLANE.getDesc();
+
+
                 for (FloorPlaneVO floorPlaneVO : floorPlaneVOS) {
                     try {
                         iotDataTransferService.sendMqttMessage(topic1, floorPlaneVO, desc1, username);
@@ -488,7 +515,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 endTime2 = System.currentTimeMillis();
                 notSynced2 = total2 - success2 - failure2;
                 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;
             // 推送设施信息
@@ -498,11 +525,11 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 long startTime3, endTime3;
                 startTime3 = System.currentTimeMillis();
 
-                Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId);
+                Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId, engineeringId, username, password);
 
                 endTime3 = System.currentTimeMillis();
                 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;
             // 推送监测数据
@@ -518,7 +545,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     @Override
     @Async("asyncServiceExecutor")
     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();
         vo.setDataType(dataType);
@@ -537,8 +564,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         log.setDataType(dataType);
         log.setTopic(topic);
         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.setTenantId(tenantId);
         log.setPushFlag(pushFlag);
@@ -559,6 +585,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         List<ProtectiveUnitVO> result = new ArrayList<>(buildUnitList.size());
         for (BaseBuildUnit buildUnit : buildUnitList) {
             ProtectiveUnitVO vo = new ProtectiveUnitVO();
+            vo.setDataPacketID(generateDataPacketID());
             vo.setEngineeringID(engineeringId);
             vo.setUnitName(buildUnit.getUnitName());
             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<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();
             checkFileSize(vo, planeViewUrl);
+            fillImageInfo(vo, planeViewUrl);
+
+            vo.setDataPacketID(generateDataPacketID());
             vo.setEngineeringID(engineeringId);
             vo.setFloor(buildPlane.getFloor());
             vo.setFloorFileID(Long.valueOf(buildPlane.getId()));
-            fillImageInfo(vo, planeViewUrl);
             vo.setPublishTime(time);
+
+            result.add(vo);
         }
 
         return result;
@@ -613,42 +661,141 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     private void checkFileSize(FloorPlaneVO vo, String 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) {
         if (StrUtil.isBlank(imageUrl)) {
             return;
         }
 
-        // 提取文件名信息
         String fileName = FileUtil.getName(imageUrl);
         vo.setFloorFileName(FileUtil.mainName(fileName));
         vo.setFloorFileSuffix(FileUtil.extName(fileName));
 
-        // 读取像素尺寸(带超时控制)
         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) {
-            log.error("获取图片尺寸失败: {}", imageUrl);
+            log.error("获取图片尺寸失败: {}", imageUrl, e);
             vo.setFilePixWidth(7016);
             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.DmpDeviceMapper;
 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.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.util.DeviceDataQuery;
@@ -50,8 +48,6 @@ import java.util.stream.Collectors;
 @Service
 public class IotDataTransferService {
 
-    private MqttOutConfig.MqttGateway mqttGateway;
-
     @Autowired
     private MqttConnectionTool mqttConnectionTool;
 
@@ -242,6 +238,7 @@ public class IotDataTransferService {
 
             Long engineeringId = transferVO.getEngineeringId();
 
+            int skippedNullField = 0;
             for (JSONObject deviceDataItem : deviceData) {
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
@@ -251,6 +248,32 @@ public class IotDataTransferService {
                     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;
                 try {
                     switch (deviceType) {
@@ -271,7 +294,7 @@ public class IotDataTransferService {
                             break;
                     }
                 } catch (Exception e) {
-                    log.warn("设备{}推送失败:{}", deviceId, e.getMessage());
+                    log.warn("设备{}[类型{}]推送异常:{}", deviceId, deviceType, e.getMessage(), e);
                     deviceSuccess = false;
                 }
 
@@ -282,6 +305,10 @@ public class IotDataTransferService {
                 }
             }
 
+            if (skippedNullField > 0) {
+                log.warn("[类型{}] 因业务字段为空而跳过的设备数量:{}", deviceType, skippedNullField);
+            }
+
             int success = result.get("successCount");
             int failure = result.get("failureCount");
             int notSynced = totalDevices - success - failure;
@@ -576,7 +603,7 @@ public class IotDataTransferService {
     }
 
     /**
-     * 推送温度信息(701
+     * 推送温度信息(707
      *
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
@@ -597,11 +624,12 @@ public class IotDataTransferService {
         tempVO.setPublishTime(getCurrentTime());
         tempVO.setSensorValue(value);
         tempVO.setDataEndTime(dataEndTime);
+        System.out.println("监测时间:" + dataEndTime);
         sendMqttMessage(MqttTopics.IotInfo.TEMP.getTopic(), tempVO, MqttTopics.IotInfo.TEMP.getDesc(), username);
     }
 
     /**
-     * 推送湿度信息(702
+     * 推送湿度信息(708
      *
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
@@ -651,7 +679,7 @@ public class IotDataTransferService {
     }
 
     /**
-     * 推送一氧化碳浓度信息(706
+     * 推送一氧化碳浓度信息(711
      *
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
@@ -676,7 +704,7 @@ public class IotDataTransferService {
     }
 
     /**
-     * 推送二氧化碳浓度信息(707
+     * 推送二氧化碳浓度信息(710)
      *
      * @param deviceDataItem 设备数据
      * @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
@@ -1035,15 +1148,25 @@ public class IotDataTransferService {
                 case 714:
                     result = sendDeviationData(transferVO);
                     break;
-
+                case 716:
+                    result = sendWaterLevel(transferVO);
+                    break;
                 default:
                     log.debug("不支持的设备类型:{}", deviceType);
                     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
      */
     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());
     }
 
@@ -1153,8 +1282,17 @@ public class IotDataTransferService {
      * @param 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);
         if (gateway != null) {
             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) {
         Integer tenantId = 0;
         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.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.mapper.DmpDeviceMapper;
 import com.usky.cdi.service.config.DeviceFieldConfig;
@@ -40,9 +41,14 @@ public class DeviceDataQuery {
     @Autowired
     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_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_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_MIN1 = 0.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;
 
@@ -104,14 +112,56 @@ public class DeviceDataQuery {
 
             log.debug("请求设备数据接口,设备数量:{}", deviceUuids.size());
             String response = HttpClientUtils.doPostJson(baseUrl, requestBody.toJSONString());
-            log.warn("接口返回数据:{}", response);
+            log.info("接口返回数据:{}", response);
 
             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) {
                 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()) {
                 log.warn("接口返回数据数量与请求数量不一致,设备类型:{}", transferVO.getDeviceType());
 
@@ -136,22 +186,22 @@ public class DeviceDataQuery {
                 List<DmpDevice> missingDevices = missingDeviceIds.stream()
                         .map(requestDeviceMap::get)
                         .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;
@@ -231,8 +281,15 @@ public class DeviceDataQuery {
                 }
 
                 // 添加设备标识信息
-                targetData.put("deviceuuid", deviceUuid);
                 String deviceId = deviceUuidToIdMap.get(deviceUuid);
+
+                // 如果找不到,直接丢弃这条数据,不放入结果
+                if (deviceId == null) {
+                    continue;
+                }
+
+                // 能走到这里,说明一定有 deviceId
+                targetData.put("deviceuuid", deviceUuid);
                 targetData.put("device_id", deviceId);
 
                 if (hasValidData) {
@@ -287,10 +344,15 @@ public class DeviceDataQuery {
     private List<JSONObject> generateSimulationData(Integer deviceType, List<DmpDevice> devices, JSONObject standard) {
         List<JSONObject> simulationList = new ArrayList<>();
         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;
-        if (standard == null && (deviceType == 707 || deviceType == 704)) {
+        if (needWeatherForTemp || needWeatherForHumi || needWeatherForElectric) {
             weatherData = WeatherFetcher.fetchWeather();
         }
 
@@ -298,18 +360,46 @@ public class DeviceDataQuery {
             JSONObject simData;
 
             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("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 {
                 // ❌ 无标准模板,走原始随机逻辑
                 simData = new JSONObject();
@@ -320,16 +410,23 @@ public class DeviceDataQuery {
                     case 707:
                         double temp707 = 0.0;
                         if (weatherData != null && !weatherData.isEmpty()) {
-                            double floating = ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN, FLOATING_RANGE_MAX);
-                            temp707 = weatherData.get("temperature") + 0.5 + floating;
+                            double floating = BasementClimateUtil.getTempDiffWithOutdoor();
+                            temp707 = weatherData.get("temperature") + floating;
                         } 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;
 
                     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;
 
                     case 709:
@@ -355,6 +452,7 @@ public class DeviceDataQuery {
                         break;
 
                     case 704:
+                    case 705:
                         double aVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
                         simData.put("aVoltage", formatNumber(aVoltage, FORMAT_3_2));
                         double bVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
@@ -399,6 +497,12 @@ public class DeviceDataQuery {
                         simData.put("wy", 0);
                         break;
 
+                    case 716:
+                        String deviceId1 = device.getDeviceId();
+                        double value = FixedWaterLevelGenerator.getSensorValue(deviceId1);
+                        simData.put("sensorValue", formatNumber(value, FORMAT_1_2));
+                        break;
+
                     default:
                         log.warn("未知设备类型:{},无法生成模拟数据", deviceType);
                         continue;
@@ -413,33 +517,87 @@ public class DeviceDataQuery {
     }
 
     private void addNoiseToNumericFields(JSONObject data, Integer deviceType) {
-        // 定义各字段的噪声比例或范围(可根据设备类型定制)
         Map<String, Double> noiseConfig = getNoiseConfigByDeviceType(deviceType);
 
+        // 这些字段永远不加噪
+        Set<String> skipFields = new HashSet<>(Arrays.asList("realtime", "device_id", "deviceuuid"));
+
         for (String key : data.keySet()) {
+            if (skipFields.contains(key)) continue;
+            if (isDiscreteField(key)) continue;
+
             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);
             }
         }
     }
 
+    /**
+     * 根据字段名获取对应的格式化器
+     */
+    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) {
-        // 这些字段是状态码,不应加噪
-        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("qx")
                 || field.equalsIgnoreCase("cd")
@@ -452,7 +610,7 @@ public class DeviceDataQuery {
         Map<String, Double> config = new HashMap<>();
         switch (deviceType) {
             case 707: // 温度
-                config.put("wd", 0.03); // ±3%
+                config.put("wd", 0.03); //
                 break;
             case 708: // 湿度
                 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分钟执行下一次
      * initialDelay:初始化后立即执行第一次任务
      */
-    // @Scheduled(fixedDelay = 26 * 60 * 1000, initialDelay = 0)
+    // @Scheduled(fixedDelay = 10 * 60 * 1000, initialDelay = 0)
     // 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);
     //
     //     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.List;
 import java.util.Map;
+import java.util.UUID;
 
 @Slf4j
 public class HttpClientUtils {
@@ -111,7 +112,9 @@ public class HttpClientUtils {
     }
 
     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;
         String resultString = "";
@@ -128,19 +131,44 @@ public class HttpClientUtils {
             httpPost.setEntity(entity);
             // 执行http请求
             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) {
-            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 {
             try {
                 if (response != null) {
                     response.close();
                 }
             } catch (IOException e) {
-                e.printStackTrace();
+                log.warn("[{}] 关闭HTTP响应失败: {}", requestId, e.getMessage(), e);
             }
         }
 
         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位
         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;
 
-import com.fasterxml.jackson.annotation.JsonFormat;
+import com.alibaba.fastjson.annotation.JSONField;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import lombok.Data;
 
@@ -93,7 +93,7 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * 查询告警表 base_alarm 时新增告警取 alarm_type 字段,更新数据则取 handle_time 字段
      * 时间型(带毫秒),格式为 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;
 
     /**
@@ -113,8 +113,8 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * 获取当前时间
      * 时间型(带毫秒),格式为 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:有水

+ 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;
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
-import java.io.Serializable;
 
 /**
  * 人防工程基础信息VO
@@ -11,7 +11,7 @@ import java.io.Serializable;
  * @date 2025/03/20
  */
 @Data
-public class EngineeringBaseVO implements Serializable {
+public class EngineeringBaseVO extends BaseMqttInfo {
     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;
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
+
 import java.io.Serializable;
 import java.time.LocalDateTime;
 
 /**
  * 楼层平面图信息VO
  * Topic: base/floorPlane
- * 
+ *
  * @author han
  * @date 2025/03/20
  */
 @Data
-public class FacilityDeviceVO implements Serializable {
+public class FacilityDeviceVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
 
     private Integer id;
@@ -157,5 +159,6 @@ public class FacilityDeviceVO implements Serializable {
      * 设备UUID
      */
     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;
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -12,19 +13,9 @@ import java.io.Serializable;
  * @date 2025/03/20
  */
 @Data
-public class FloorPlaneVO implements Serializable {
+public class FloorPlaneVO extends BaseMqttInfo {
     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 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;
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -13,19 +14,9 @@ import java.math.BigDecimal;
  * @date 2025/03/20
  */
 @Data
-public class ProtectiveUnitVO implements Serializable {
+public class ProtectiveUnitVO extends BaseMqttInfo {
     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 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.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);
 
         // 关闭默认 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
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/analysis")
 public class EmsAnalysisController {
 
     @Autowired
     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(
             @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) {
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
         return ApiResult.success(emsAnalysisService.getTrend(projectId, timeDimension, timeValue, energyTypeId));
     }
 
-    @GetMapping("/analysis/trend/indicators")
+    @GetMapping("/trend/indicators")
     public ApiResult<EmsTrendIndicatorsResponse> getTrendIndicators(
             @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) {
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
         return ApiResult.success(emsAnalysisService.getTrendIndicators(projectId, timeDimension, timeValue, energyTypeId));
     }
 
-    @GetMapping("/analysis/trend/category")
+    @GetMapping("/trend/category")
     public ApiResult<EmsTrendCategoryResponse> getTrendCategory(
             @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) {
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
         return ApiResult.success(emsAnalysisService.getTrendCategory(projectId, timeDimension, timeValue, energyTypeId));
     }
 
-    @GetMapping("/analysis/region")
+    @GetMapping("/region")
     public ApiResult<EmsRegionAnalysisResponse> getRegionAnalysis(
             @RequestParam(required = false) Long projectId,
             @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) {
+        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
+        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
         return ApiResult.success(emsAnalysisService.getRegionAnalysis(projectId, regionIds, timeDimension, timeValue, energyTypeId));
     }
 
-    @PostMapping("/analysis/compare")
+    @PostMapping("/compare")
     public ApiResult<EmsCompareResponse> getCompare(@RequestBody EmsCompareRequest 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.ems.service.EmsReportService;
+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.*;
@@ -12,29 +13,34 @@ import java.util.List;
 /**
  * 统计报表接口
  * 能源报表、区域报表、采集报表
+ * 基础路径前缀:/report(网关再加 /prod-api/service-ems)
  */
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/report")
 public class EmsReportController {
 
     @Autowired
     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) 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) {
         return ApiResult.success(emsReportService.getEnergyStatistics(request));
     }
 
-    @GetMapping("/report/energy/export")
+    @GetMapping("/energy/export")
     public void exportEnergy(
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
@@ -46,7 +52,7 @@ public class EmsReportController {
     }
 
     // ---------- 区域报表 ----------
-    @GetMapping("/report/region/devices")
+    @GetMapping("/region/devices")
     public ApiResult<EmsReportDevicesResponse> getRegionDevices(
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) Long regionId,
@@ -55,14 +61,14 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getRegionDevices(energyTypeId, regionId, keyword, projectId));
     }
 
-    @PostMapping("/report/region/statistics")
+    @PostMapping("/region/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getRegionStatistics(
             @RequestBody EmsEnergyStatisticsRequest request,
             @RequestParam(required = false) List<Long> regionIds) {
         return ApiResult.success(emsReportService.getRegionStatistics(request, regionIds));
     }
 
-    @GetMapping("/report/region/export")
+    @GetMapping("/region/export")
     public void exportRegion(
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
@@ -75,7 +81,7 @@ public class EmsReportController {
     }
 
     // ---------- 采集报表 ----------
-    @GetMapping("/report/collection/devices")
+    @GetMapping("/collection/devices")
     public ApiResult<EmsReportDevicesResponse> getCollectionDevices(
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) String keyword,
@@ -83,17 +89,17 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getCollectionDevices(energyTypeId, keyword, projectId));
     }
 
-    @PostMapping("/report/collection/realtime")
+    @PostMapping("/collection/realtime")
     public ApiResult<EmsCollectionRealtimeResponse> getCollectionRealtime(@RequestBody EmsCollectionRealtimeRequest request) {
         return ApiResult.success(emsReportService.getCollectionRealtime(request));
     }
 
-    @PostMapping("/report/collection/statistics")
+    @PostMapping("/collection/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getCollectionStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
         return ApiResult.success(emsReportService.getCollectionStatistics(request));
     }
 
-    @GetMapping("/report/collection/export")
+    @GetMapping("/collection/export")
     public void exportCollection(
             @RequestParam String deviceIds,
             @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;
 
 /**
- * 区域(leo.ems_space_area)
+ * 区域(base_area)
  */
 @Data
 @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;
 
     @TableId(value = "id", type = IdType.AUTO)
     private Long id;
 
-    @TableField("space_id")
-    private Long spaceId;
     private String name;
     private Integer type;
     private BigDecimal area;
@@ -35,12 +33,14 @@ public class EmsSpaceArea implements Serializable {
     private BigDecimal airConditionedArea;
     @TableField("resident_population")
     private Integer residentPopulation;
+    @TableField("tenant_id")
+    private Integer tenantId;
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
+    @TableField("created_by")
+    private String createdBy;
     @TableField("update_time")
     private LocalDateTime updateTime;
-    @TableField("created_by")
-    private Long createdBy;
     @TableField("create_time")
     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;
 
 /**
- * 空间(leo.ems_space)
- * 空间类型:1项目 2区域 3建筑 4楼层 5房间
+ * 空间(base_space)
+ * 空间类型:1项目 2区域 3建筑
  */
 @Data
 @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;
 
@@ -31,15 +31,15 @@ public class EmsSpace implements Serializable {
     @TableField("root_id")
     private Long rootId;
     private String path;
-    @TableField("path_name")
-    private String pathName;
     private Integer deep;
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
     @TableField("update_time")
     private LocalDateTime updateTime;
     @TableField("created_by")
-    private Long createdBy;
+    private String createdBy;
     @TableField("create_time")
     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;
 
 /**
- * 网关(leo.ems_gateway)
+ * 网关表(dmp_gateway)
  */
 @Data
 @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;
 
-    @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;
-    @TableField("space_id")
-    private Long spaceId;
-    private String version;
-    private String type;
+
     private String ip;
+
     private Integer port;
+
+    private String installAddress;
+
     @TableField("comm_status")
     private Integer commStatus;
+
     @TableField("online_time")
     private LocalDateTime onlineTime;
+
     @TableField("offline_time")
     private LocalDateTime offlineTime;
+
     @TableField("update_config_time")
     private LocalDateTime updateConfigTime;
+
     @TableField("update_protocol_time")
     private LocalDateTime updateProtocolTime;
+
     @TableField("upgrade_time")
     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")
     private Integer virtualDevice;
+
     private String remark;
+
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
+
     @TableField("update_time")
     private LocalDateTime updateTime;
+
     @TableField("created_by")
-    private Long createdBy;
+    private String createdBy;
+
     @TableField("create_time")
     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 name;
     @TableField("channel_type_id")
+    // 通道类型ID(1:Serial Port,2:TCP)
     private Integer channelTypeId;
     @TableField("updated_by")
     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;
 
 /**
- * 项目(leo.ems_project)
+ * 项目(ems_project)
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
@@ -26,6 +26,7 @@ public class EmsProject implements Serializable {
 
     @TableField("space_id")
     private Long spaceId;
+
     private String name;
     @TableField("platform_name")
     private String platformName;
@@ -60,12 +61,14 @@ public class EmsProject implements Serializable {
     private String logo;
     @TableField("logo_min")
     private String logoMin;
+    @TableField("tenant_id")
+    private Integer tenantId;
     @TableField("updated_by")
-    private Long updatedBy;
+    private String updatedBy;
     @TableField("update_time")
     private LocalDateTime updateTime;
     @TableField("created_by")
-    private Long createdBy;
+    private String createdBy;
     @TableField("create_time")
     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 java.io.Serializable;
-import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 /**
- * 楼层(leo.ems_space_floor
+ * 系统字典编码(ems_system_dict_code
  */
 @Data
 @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;
 
     @TableId(value = "id", type = IdType.AUTO)
-    private Long id;
+    private Integer 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;
+    private String code;
+    private String remark;
     @TableField("updated_by")
     private Long updatedBy;
     @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;
 
 /**
- * 项目 Mapper(leo.ems_project)
+ * 项目 Mapper(ems_project)
  */
 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);
 
     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;
 
+import com.usky.ems.service.vo.EmsAttributePointVO;
 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.EmsStructureTreeNode;
+import com.usky.ems.service.vo.EnergyTypeWrapperProductVO;
+import com.usky.ems.service.vo.DmpGatewayDetailResponse;
 
 import java.util.List;
 
 /**
- * 基础建模服务(model:结构树、能源类型、建筑/区域/楼层/网关/通道/设备/属性点位)
+ * 基础建模服务(model:结构树、能源类型、建筑/区域/网关/通道/设备/属性点位)
  */
 public interface EmsModelService {
 
     /**
-     * 获取项目层级树(建筑、区域、楼层、网关)
+     * 获取项目层级树(建筑、区域、网关)
      */
-    EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway);
+//    EmsStructureTreeNode getStructureTree(Long projectId, Boolean includeGateway);
 
     /**
      * 能源类型列表(电、水、气)
      */
     List<EmsEnergyTypeVO> getEnergyTypeList();
 
+    /**
+     * 展示已关联的能源类型及其下属分项/产品列表
+     * 数据来源:leo.ems_energy_item_code.energy_type 分组
+     */
+    List<EnergyTypeWrapperProductVO> showAssociatedEnergyTypes();
+
     /** 建筑:新增 */
     Long createBuilding(EmsModelSaveRequest request);
     /** 建筑:编辑 */
@@ -35,38 +45,64 @@ public interface EmsModelService {
     /** 区域:删除 */
     void deleteRegion(Long id);
 
-    /** 楼层:新增 */
-    Long createFloor(EmsModelSaveRequest request);
-    /** 楼层:编辑 */
-    void updateFloor(Long id, EmsModelSaveRequest request);
-    /** 楼层:删除 */
-    void deleteFloor(Long id);
-
     /** 网关:新增 */
     String createGateway(EmsModelSaveRequest request);
     /** 网关:编辑 */
     void updateGateway(String id, EmsModelSaveRequest request);
     /** 网关:删除 */
     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.EmsSummaryResponse;
+import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
+
+import java.util.Map;
 
 /**
  * 能源总览服务(overview)
@@ -11,10 +15,50 @@ public interface EmsOverviewService {
     /**
      * 获取项目信息(当前项目或指定 projectId)
      */
-    EmsProjectResponse getProject(Long projectId);
+    EmsProjectResponse getProject(Integer projectId);
 
     /**
      * 获取项目数据概括(时间维度联动)
      */
     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);
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů