瀏覽代碼

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

fuyuchuan 1 天之前
父節點
當前提交
055e0bffd8
共有 100 個文件被更改,包括 4531 次插入415 次删除
  1. 82 104
      pom.xml
  2. 14 5
      service-cdi/service-cdi-api/src/main/java/com/usky/cdi/AlarmDataSyncTaskService.java
  3. 6 6
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/MybatisGeneratorUtils.java
  4. 3 3
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/cdiApplication.java
  5. 7 5
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/BaseDataController.java
  6. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/api/AlarmDataSyncTaskApi.java
  7. 21 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildController.java
  8. 21 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildPlaneController.java
  9. 21 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildUnitController.java
  10. 158 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/CdiDefenseProjectController.java
  11. 92 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/CdiDeliveryLogController.java
  12. 21 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/SysUserController.java
  13. 212 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuild.java
  14. 56 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuildPlane.java
  15. 101 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuildUnit.java
  16. 135 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/CdiDefenseProject.java
  17. 90 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/CdiDeliveryLog.java
  18. 136 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/SysUser.java
  19. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildMapper.java
  20. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildPlaneMapper.java
  21. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildUnitMapper.java
  22. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/CdiDefenseProjectMapper.java
  23. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/CdiDeliveryLogMapper.java
  24. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/SysUserMapper.java
  25. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildPlaneService.java
  26. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildService.java
  27. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildUnitService.java
  28. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDefenseProjectService.java
  29. 31 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDeliveryLogService.java
  30. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/SysUserService.java
  31. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/AlarmType.java
  32. 103 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/DeliveryDataType.java
  33. 14 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/EnvMonitorMqttTopic.java
  34. 155 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/MqttTopics.java
  35. 24 13
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataSyncService.java
  36. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildPlaneServiceImpl.java
  37. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildServiceImpl.java
  38. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildUnitServiceImpl.java
  39. 44 31
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java
  40. 63 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDefenseProjectServiceImpl.java
  41. 658 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java
  42. 230 77
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java
  43. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/SysUserServiceImpl.java
  44. 4 7
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/mqtt/MqttConnectionTool.java
  45. 197 127
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java
  46. 68 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/SyncTaskStatisticsVO.java
  47. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/Co2VO.java
  48. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/CoVO.java
  49. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/CrackVO.java
  50. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/DeviationVO.java
  51. 2 3
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ElectricityLoadVO.java
  52. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/EngineeringBaseVO.java
  53. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ErrorMsgVO.java
  54. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FacilityDeviceVO.java
  55. 3 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FloorPlaneVO.java
  56. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/HumidityVO.java
  57. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/OxygenVO.java
  58. 2 3
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/PersonPresenceVO.java
  59. 3 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ProtectiveUnitVO.java
  60. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/SensorInfoVO.java
  61. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/TempVO.java
  62. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/TiltVO.java
  63. 2 3
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/WaterLeakVO.java
  64. 46 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildMapper.xml
  65. 15 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildPlaneMapper.xml
  66. 24 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildUnitMapper.xml
  67. 26 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/CdiDefenseProjectMapper.xml
  68. 18 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/CdiDeliveryLogMapper.xml
  69. 31 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/SysUserMapper.xml
  70. 11 0
      service-fire/service-fire-biz/src/main/java/com/usky/fire/controller/web/PatrolInspectionPlanController.java
  71. 2 2
      service-fire/service-fire-biz/src/main/java/com/usky/fire/service/impl/PatrolInspectionAreaServiceImpl.java
  72. 75 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/BaseBuildEngineeringController.java
  73. 72 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/BaseBuildUnitController.java
  74. 84 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/BaseScreenController.java
  75. 5 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseBuild.java
  76. 146 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseBuildEngineering.java
  77. 101 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseBuildUnit.java
  78. 101 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseScreen.java
  79. 16 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/BaseBuildEngineeringMapper.java
  80. 16 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/BaseBuildUnitMapper.java
  81. 16 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/BaseScreenMapper.java
  82. 25 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/BaseBuildEngineeringService.java
  83. 25 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/BaseBuildUnitService.java
  84. 23 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/BaseScreenService.java
  85. 59 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseBuildEngineeringServiceImpl.java
  86. 83 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseBuildUnitServiceImpl.java
  87. 86 0
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseScreenServiceImpl.java
  88. 1 1
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/PmTimeConfServiceImpl.java
  89. 33 0
      service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseBuildEngineeringMapper.xml
  90. 1 0
      service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseBuildMapper.xml
  91. 24 0
      service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseBuildUnitMapper.xml
  92. 24 0
      service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseScreenMapper.xml
  93. 6 5
      service-job/src/main/java/com/ruoyi/job/task/RyTask.java
  94. 6 0
      service-meeting/service-meeting-biz/pom.xml
  95. 22 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/config/WebSocketConfig.java
  96. 77 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/MeetingDeviceWebSocketService.java
  97. 21 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/vo/WebSocketConnectionResponseVO.java
  98. 72 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketController.java
  99. 76 0
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketHandler.java
  100. 1 1
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmTimeConfServiceImpl.java

+ 82 - 104
pom.xml

@@ -1,136 +1,114 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-          
-  
-  
-  <parent>
-                    
-    
-    
-    <groupId>com.usky</groupId>
-                    
-    
-    
-    <artifactId>uskycloud</artifactId>
-                    
-    
-    
-    <version>0.0.1</version>
-                
-  
-  
-  </parent>
-          
-  
-  
-  <modelVersion>4.0.0</modelVersion>
-          
-  
-  
-  <artifactId>usky-modules</artifactId>
-          
-  
-  
-  <packaging>pom</packaging>
-          
-  
-  
-  <description>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+
+    <parent>
+
+
+        <groupId>com.usky</groupId>
+
+
+        <artifactId>uskycloud</artifactId>
+
+
+        <version>0.0.1</version>
+
+
+    </parent>
+
+
+    <modelVersion>4.0.0</modelVersion>
+
+
+    <artifactId>usky-modules</artifactId>
+
+
+    <packaging>pom</packaging>
+
+
+    <description>
         usky-modules
     </description>
-          
-  
-  
-  <modules>
-                
-    
-    
-    <module>usky-module-demo</module>
-                
-    
-    
-    <module>service-backend</module>
-                
-    
-    
-    <module>service-website</module>
-                
-    
-    
-    <module>service-iot</module>
 
 
+    <modules>
+
+
+        <module>usky-module-demo</module>
+
+
+        <module>service-backend</module>
+
+
+        <module>service-website</module>
+
+
+        <module>service-iot</module>
+
+
+        <module>service-alarm</module>
+
+
+        <module>service-fire</module>
+
+
+        <module>service-park</module>
+
+
+        <module>service-issue</module>
+
+
+        <module>service-meeting</module>
+
+
+        <module>service-agbox</module>
+
+
+        <module>service-job</module>
+
+
+        <module>service-eg</module>
 
-    <module>service-alarm</module>
-                
-    
-    
-    <module>service-fire</module>
-                
 
-    
-    <module>service-park</module>
+        <module>service-oa</module>
 
 
+        <module>service-ai</module>
 
-    <module>service-issue</module>
+        <module>service-pm</module>
 
+        <module>service-ids</module>
 
-    <module>service-meeting</module>
+        <module>service-cdi</module>
 
+        <module>service-ems</module>
 
-    <module>service-agbox</module>
+        <module>service-transfer</module>
 
+        <module>service-tsdb</module>
 
-    <module>service-job</module>
+        <!--    <module>service-data</module>-->
 
+    </modules>
 
-    <module>service-eg</module>
 
+    <dependencies>
 
-    <module>service-oa</module>
 
+        <dependency>
 
-    <module>service-ai</module>
 
-    <module>service-pm</module>
+            <groupId>org.projectlombok</groupId>
 
-    <module>service-ids</module>
 
-    <module>service-cdi</module>
+            <artifactId>lombok</artifactId>
 
-    <module>service-ems</module>
 
-    <module>service-transfer</module>
+        </dependency>
 
-    <module>service-tsdb</module>
 
-  </modules>
-          
-  
-  
-  <dependencies>
-                    
-    
-    
-    <dependency>
-                              
-      
-      
-      <groupId>org.projectlombok</groupId>
-                              
-      
-      
-      <artifactId>lombok</artifactId>
-                          
-    
-    
-    </dependency>
-                
-  
-  
-  </dependencies>
-      
+    </dependencies>
 
 
 </project>

+ 14 - 5
service-cdi/service-cdi-api/src/main/java/com/usky/cdi/AlarmDataSyncTaskService.java

@@ -13,10 +13,19 @@ import org.springframework.web.bind.annotation.RequestParam;
  */
 @FeignClient(contextId = "AlarmDataSyncTaskService", value = "service-cdi", fallbackFactory = AlarmDataSyncTaskFactory.class)
 public interface AlarmDataSyncTaskService {
+    /**
+     * 同步告警数据
+     *
+     * @param tenantId      租户ID
+     * @param engineeringId 工程ID
+     * @param username      mqtt用户名
+     * @param password      mqtt密码
+     * @param status        peacetime:平时 wartime:战时
+     */
     @GetMapping("/synchronizeAlarmData")
-    void synchronizeAlarmData(@RequestParam("tenantId") Integer tenantId,
-                              @RequestParam("engineeringId") Long engineeringId,
-                              @RequestParam("username") String username,
-                              @RequestParam("password") String password,
-                              @RequestParam("status") String status);
+    void synchronizeAlarmData(@RequestParam(value = "tenantId") Integer tenantId,
+                              @RequestParam(value = "engineeringId") Long engineeringId,
+                              @RequestParam(value = "username") String username,
+                              @RequestParam(value = "password") String password,
+                              @RequestParam(value = "status") String status);
 }

+ 6 - 6
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/MybatisGeneratorUtils.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/MybatisGeneratorUtils.java

@@ -1,4 +1,4 @@
-package com.usky.cdi.controller;//package com.usky.iot.controller;//package com.usky.dm.controller.web.business;//package com.usky.dm.controller.web;
+package com.usky.cdi;//package com.usky.iot.controller;//package com.usky.dm.controller.web.business;//package com.usky.dm.controller.web;
 
 
 import com.baomidou.mybatisplus.core.toolkit.StringPool;
@@ -43,10 +43,10 @@ public class MybatisGeneratorUtils {
         //2、数据源配置
         //修改数据源
         DataSourceConfig dsc = new DataSourceConfig();
-        dsc.setUrl("jdbc:mysql://47.111.81.118:13307/usky-cloud?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf8");
+        dsc.setUrl("jdbc:mysql://192.168.10.165:3306/usky-cloud?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf8");
         dsc.setDriverName("com.mysql.jdbc.Driver");
-        dsc.setUsername("usky");
-        dsc.setPassword("Yt#75Usky");
+        dsc.setUsername("root");
+        dsc.setPassword("yt123456");
         mpg.setDataSource(dsc);
 
         // 3、包配置
@@ -69,9 +69,9 @@ public class MybatisGeneratorUtils {
         strategy.setSuperServiceClass("com.usky.common.mybatis.core.CrudService");
         strategy.setSuperServiceImplClass("com.usky.common.mybatis.core.AbstractCrudService");
         // strategy.setTablePrefix("t_"); // 表名前缀
-        strategy.setEntityLombokModel(true); //使用lombok
+        strategy.setEntityLombokModel(true); //使用lombokbase_build_plane
         //修改自己想要生成的表
-        strategy.setInclude("dmp_product");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+        strategy.setInclude("base_build");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
         mpg.setStrategy(strategy);
 
         // 关闭默认 xml 生成,调整生成 至 根目录

+ 3 - 3
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/RuoYiSystemApplication.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/cdiApplication.java

@@ -30,12 +30,12 @@ import java.net.UnknownHostException;
 @ComponentScan("com.usky")
 @EnableIntegration // 启用Spring Integration
 @SpringBootApplication
-public class RuoYiSystemApplication
+public class cdiApplication
 {
-    private static final Logger LOGGER = LoggerFactory.getLogger(RuoYiSystemApplication.class);
+    private static final Logger LOGGER = LoggerFactory.getLogger(cdiApplication.class);
 
     public static void main(String[] args) throws UnknownHostException {
-        ConfigurableApplicationContext application = SpringApplication.run(RuoYiSystemApplication.class, args);
+        ConfigurableApplicationContext application = SpringApplication.run(cdiApplication.class, args);
         Environment env = application.getEnvironment();
         String ip = InetAddress.getLocalHost().getHostAddress();
         String port = env.getProperty("server.port");

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

@@ -1,15 +1,17 @@
 package com.usky.cdi.controller;
 
-import com.usky.cdi.domain.BaseBuildFacility;
 import com.usky.cdi.service.impl.BaseDataTransferService;
-import com.usky.cdi.service.vo.base.*;
+import com.usky.cdi.service.vo.info.EngineeringBaseVO;
+import com.usky.cdi.service.vo.info.FacilityDeviceVO;
+import com.usky.cdi.service.vo.info.FloorPlaneVO;
+import com.usky.cdi.service.vo.info.ProtectiveUnitVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.web.bind.annotation.*;
 
-import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 基础类数据传输控制器
@@ -77,8 +79,8 @@ public class BaseDataController {
      */
     @GetMapping("/sensorInfos")
     public String batchSendSensorInfos(@RequestParam(value = "tenantId",required = false) Integer tenantId) {
-        int successCount = baseDataTransferService.batchSendSensorInfos(tenantId);
-        return String.format("上报成功 %d", successCount);
+        Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId);
+        return String.format("上报成功 %d", map.get("success"));
     }
 }
 

+ 1 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/api/AlarmDataSyncTaskApi.java

@@ -25,7 +25,7 @@ public class AlarmDataSyncTaskApi implements AlarmDataSyncTaskService {
      * engineeringId: 工程ID
      * username: mqtt用户名
      * password: mqtt密码
-     * status: 状态 0:平时 1:战时
+     * status: peacetime:平时 wartime:战时
      */
     @Override
     public void synchronizeAlarmData(Integer tenantId, Long engineeringId, String username, String password, String status) {

+ 21 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildController.java

@@ -0,0 +1,21 @@
+package com.usky.cdi.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 建筑信息 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+@Controller
+@RequestMapping("/baseBuild")
+public class BaseBuildController {
+
+}
+

+ 21 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildPlaneController.java

@@ -0,0 +1,21 @@
+package com.usky.cdi.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 建筑楼层平面图 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+@Controller
+@RequestMapping("/baseBuildPlane")
+public class BaseBuildPlaneController {
+
+}
+

+ 21 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildUnitController.java

@@ -0,0 +1,21 @@
+package com.usky.cdi.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+@Controller
+@RequestMapping("/baseBuildUnit")
+public class BaseBuildUnitController {
+
+}
+

+ 158 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/CdiDefenseProjectController.java

@@ -0,0 +1,158 @@
+package com.usky.cdi.controller.web;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.usky.cdi.domain.CdiDefenseProject;
+import com.usky.cdi.service.CdiDefenseProjectService;
+import com.usky.cdi.service.mqtt.MqttConnectionTool;
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.common.security.utils.SecurityUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 人防_工程表(人防工程基础信息及MQTT连接配置) 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+@Api(tags = "人防_工程表")
+@Slf4j
+@RestController
+@RequestMapping("/cdiDefenseProject")
+@RequiredArgsConstructor
+public class CdiDefenseProjectController {
+
+    private final MqttConnectionTool mqttConnectionTool;
+
+    @Autowired
+    private CdiDefenseProjectService cdiDefenseProjectService;
+
+    @ApiOperation("查询人防工程配置")
+    @GetMapping("config")
+    public ApiResult<CdiDefenseProject> query() {
+        Integer tenantId = SecurityUtils.getTenantId();
+        if (tenantId == null) {
+            return null;
+        }
+        return ApiResult.success(cdiDefenseProjectService.listByTenantId(tenantId));
+    }
+
+    /**
+     * 新增人防工程
+     */
+    @ApiOperation("新增人防工程配置")
+    @Log(title = "新增人防工程配置", businessType = BusinessType.INSERT)
+    @PostMapping("config")
+    public ApiResult<String> add(@RequestBody CdiDefenseProject cdiDefenseProject) {
+        cdiDefenseProject.setCreateBy(SecurityUtils.getUsername());
+        cdiDefenseProject.setCreateTime(LocalDateTime.now());
+        cdiDefenseProject.setTenantId(SecurityUtils.getTenantId());
+        cdiDefenseProject.setIsEnable(1);
+        boolean save = cdiDefenseProjectService.save(cdiDefenseProject);
+
+        if (save) {
+            mqttConnectionTool.connectOrRefresh(cdiDefenseProject.getMqttUserName(), cdiDefenseProject.getMqttPassword());
+            return ApiResult.success("新增成功!");
+        } else {
+            return ApiResult.error("新增失败!");
+        }
+    }
+
+    /**
+     * 修改人防工程
+     */
+    @ApiOperation("修改人防工程")
+    @Log(title = "修改人防工程", businessType = BusinessType.UPDATE)
+    @PutMapping("config")
+    public ApiResult<String> edit(@RequestBody CdiDefenseProject cdiDefenseProject) {
+        Long id = cdiDefenseProject.getId();
+        if (id == null || id <= 0) {
+            return ApiResult.error("参数错误,请检查ID");
+        }
+        CdiDefenseProject existCdiDefenseProject = cdiDefenseProjectService.getById(id);
+        if (existCdiDefenseProject == null) {
+            return ApiResult.error("工程配置不存在!");
+        }
+
+        cdiDefenseProject.setUpdateBy(SecurityUtils.getUsername());
+        cdiDefenseProject.setUpdateTime(LocalDateTime.now());
+        boolean b = cdiDefenseProjectService.updateById(cdiDefenseProject);
+        if (b) {
+            return ApiResult.success("修改成功!");
+        } else {
+            return ApiResult.error("修改失败!");
+        }
+    }
+
+    /**
+     * 更改人防工程状态
+     */
+    @ApiOperation("更改人防工程状态")
+    @Log(title = "更改人防工程状态", businessType = BusinessType.UPDATE)
+    @PutMapping("/configState")
+    public ApiResult<String> changeStatus(@RequestBody CdiDefenseProject cdiDefenseProject) {
+        Long id = cdiDefenseProject.getId();
+        if (id == null || id <= 0) {
+            return ApiResult.error("参数错误,请检查ID");
+        }
+        Integer isEnable = cdiDefenseProject.getIsEnable();
+        CdiDefenseProject cdiDefenseProject1 = cdiDefenseProjectService.getById(id);
+        if (cdiDefenseProject1 == null) {
+            return ApiResult.error("工程配置不存在!");
+        }
+        if (isEnable < 0 || isEnable > 1) {
+            return ApiResult.error("参数错误,请检查状态值!");
+        }
+
+        cdiDefenseProject1.setId(id);
+        cdiDefenseProject1.setIsEnable(isEnable);
+        cdiDefenseProject1.setUpdateBy(SecurityUtils.getUsername());
+        cdiDefenseProject1.setUpdateTime(LocalDateTime.now());
+        boolean b = cdiDefenseProjectService.updateById(cdiDefenseProject1);
+
+        if (b && isEnable == 1) {
+            return ApiResult.success("启用成功!");
+        } else if (b) {
+            return ApiResult.success("禁用成功!");
+        } else {
+            return ApiResult.error("操作失败!");
+        }
+
+    }
+
+    /**
+     * MQTT连通性测试
+     */
+    @ApiOperation("MQTT连通性测试")
+    @GetMapping("configVerify")
+    public ApiResult<String> testMqttConnection(@ApiParam("工程ID") @RequestParam Long id) {
+        CdiDefenseProject cdiDefenseProject = cdiDefenseProjectService.getById(id);
+        if (cdiDefenseProject == null) {
+            return ApiResult.error("工程配置不存在!");
+        }
+
+        String broker = cdiDefenseProject.getPushAddress();
+        String user = cdiDefenseProject.getMqttUserName();
+        String pass = cdiDefenseProject.getMqttPassword();
+        String clientId = cdiDefenseProject.getMqttClientId();
+        boolean b = cdiDefenseProjectService.tryConnectAndPublish(broker, user, pass, "PING", clientId);
+
+        if (b) {
+            return ApiResult.success("MQTT 连通性测试成功!");
+        } else {
+            return ApiResult.error("MQTT 连通性测试失败,请检查网络、账号和密码!");
+        }
+    }
+}
+

+ 92 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/CdiDeliveryLogController.java

@@ -0,0 +1,92 @@
+package com.usky.cdi.controller.web;
+
+import com.usky.cdi.domain.CdiDeliveryLog;
+import com.usky.cdi.mapper.CdiDefenseProjectMapper;
+import com.usky.cdi.service.CdiDeliveryLogService;
+import com.usky.cdi.service.vo.SyncTaskStatisticsVO;
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.security.utils.SecurityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 人防_投递日志表(记录数据的MQTT投递情况) 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+@RestController
+@RequestMapping("/cdiDeliveryLog")
+public class CdiDeliveryLogController {
+
+    @Autowired
+    private CdiDeliveryLogService cdiDeliveryLogService;
+    @Autowired
+    private CdiDefenseProjectMapper cdpMapper;
+
+    /**
+     * 同步数据查询
+     * @param id 主键id
+     * @return 同步数据
+     */
+    @GetMapping("synchronizeData")
+    public List<SyncTaskStatisticsVO> synchronizeData(@RequestParam(value = "id", required = false) Long id) {
+        return cdiDeliveryLogService.selectById(id);
+    }
+
+    /**
+     * 同步数据
+     * @param vo 同步数据
+     * @return 同步结果
+     */
+    @PutMapping("synchronizeData")
+    public ApiResult<Void> synchronizeData(@RequestBody SyncTaskStatisticsVO vo) {
+        cdiDeliveryLogService.synchronizeData(vo);
+        return ApiResult.success();
+    }
+
+    /**
+     * 日志列表
+     * @param id 主键id
+     * @param pageNum     页码
+     * @param pageSize     每页数量
+     * @param dataType 数据类型
+     * @param logType 日志类型
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @return 日志列表
+     */
+    @GetMapping("logList")
+    public ApiResult<CommonPage<CdiDeliveryLog>> logList(@RequestParam(value = "id", required = false) Long id,
+                                                         @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
+                                                         @RequestParam(value = "pageSize", required = false, defaultValue = "20") Integer pageSize,
+                                                         @RequestParam(value = "dataType", required = false) Integer dataType,
+                                                         @RequestParam(value = "logType", required = false) Integer logType,
+                                                         @RequestParam(value = "startTime", required = false) String startTime,
+                                                         @RequestParam(value = "endTime", required = false) String endTime) {
+        return ApiResult.success(cdiDeliveryLogService.logList(id, pageNum, pageSize, dataType, logType, startTime, endTime));
+    }
+
+    /**
+     * 日志详情
+     * @param id 主键id
+     * @return 日志详情
+     */
+    @GetMapping("logId")
+    public ApiResult<String> logId(@RequestParam(value = "id") Long id) {
+        CdiDeliveryLog log = cdiDeliveryLogService.lambdaQuery()
+                .eq(CdiDeliveryLog::getTenantId, SecurityUtils.getTenantId())
+                .eq(CdiDeliveryLog::getId, id)
+                .one();
+        if (log == null) {
+            return ApiResult.error("日志不存在");
+        }
+        return ApiResult.success(log.getInfoContent());
+    }
+}
+

+ 21 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/SysUserController.java

@@ -0,0 +1,21 @@
+package com.usky.cdi.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 用户信息表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-04
+ */
+@Controller
+@RequestMapping("/sysUser")
+public class SysUserController {
+
+}
+

+ 212 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuild.java

@@ -0,0 +1,212 @@
+package com.usky.cdi.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import java.time.LocalDate;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 建筑信息
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class BaseBuild implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 建筑编号
+     */
+    private String buildNum;
+
+    /**
+     * 建筑名称
+     */
+    private String buildName;
+
+    /**
+     * 详细地址
+     */
+    private String address;
+
+    /**
+     * 模型地址
+     */
+    private String modelAddress;
+
+    /**
+     * 地上楼层
+     */
+    private Integer aboveFloor;
+
+    /**
+     * 地下楼层
+     */
+    private Integer underFloor;
+
+    /**
+     * 建筑面积
+     */
+    private Double buildArea;
+
+    /**
+     * 占地面积
+     */
+    private Double coverArea;
+
+    /**
+     * 耐火等级
+     */
+    private Integer fireRating;
+
+    /**
+     * 使用性质
+     */
+    private Integer useCharacter;
+
+    /**
+     * 建筑结构
+     */
+    private Integer buildStructure;
+
+    /**
+     * 建筑高度
+     */
+    private Double buildHigh;
+
+    /**
+     * 建筑高度分类
+     */
+    private Integer highType;
+
+    /**
+     * 竣工年份
+     */
+    private LocalDate completeYear;
+
+    /**
+     * 安全责任人
+     */
+    private String safePerson;
+
+    /**
+     * 安全管理人
+     */
+    private String managePerson;
+
+    /**
+     * 火灾危险性
+     */
+    private Integer fireRisk;
+
+    /**
+     * 消防控制室位置
+     */
+    private String fireControlRoom;
+
+    /**
+     * 建筑立面图
+     */
+    private String buildInside;
+
+    /**
+     * 建筑平面图
+     */
+    private String buildPlan;
+
+    /**
+     * 设施ID
+     */
+    private Integer facilityId;
+
+    /**
+     * BIM地址
+     */
+    private String bimUrl;
+
+    /**
+     * 联系人电话
+     */
+    private String contactPhone;
+
+    /**
+     * 建筑备注
+     */
+    private String buildDesc;
+
+    /**
+     * 单元数量
+     */
+    private Integer unitCount;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 更新人
+     */
+    private String updateBy;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 删除标识
+     */
+    private Integer deleteFlag;
+
+    /**
+     * 地下空间
+     */
+    private Double underSpace;
+
+    /**
+     * 防火涂层(0、无 1、有)
+     */
+    private Integer fireproofCoat;
+
+    /**
+     * 组织机构ID
+     */
+    private Integer deptId;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+    /**
+     * 经度(当设施类型为点时使用该字段)
+     */
+    private String longitude;
+
+    /**
+     * 纬度(当设施类型为点时使用该字段)
+     */
+    private String latitude;
+
+
+}

+ 56 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuildPlane.java

@@ -0,0 +1,56 @@
+package com.usky.cdi.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 fu
+ * @since 2026-02-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class BaseBuildPlane implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 建筑ID
+     */
+    private Integer buildId;
+
+    /**
+     * 所属楼层
+     */
+    private String floor;
+
+    /**
+     * 平面图路径
+     */
+    private String planeViewUrl;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+
+}

+ 101 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuildUnit.java

@@ -0,0 +1,101 @@
+package com.usky.cdi.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 fu
+ * @since 2026-02-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class BaseBuildUnit implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 单元信息表
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 单元名称
+     */
+    private String unitName;
+
+    /**
+     * 单元主要出入口
+     */
+    private String unitMainExit;
+
+    /**
+     * 单元次要出入口
+     */
+    private String unitOtherExit;
+
+    /**
+     * 单元面积
+     */
+    private Double unitArea;
+
+    /**
+     * 楼层
+     */
+    private String floor;
+
+    /**
+     * 防护单元用途
+     */
+    private String unitUsage;
+
+    /**
+     * 掩蔽人数上限
+     */
+    private Integer peopleNumber;
+
+    /**
+     * 建筑ID
+     */
+    private Integer buildId;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 组织机构ID
+     */
+    private Integer deptId;
+
+    /**
+     * 租户号
+     */
+    private Integer tenantId;
+
+
+}

+ 135 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/CdiDefenseProject.java

@@ -0,0 +1,135 @@
+package com.usky.cdi.domain;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+import java.time.LocalDateTime;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * <p>
+ * 人防_工程表(人防工程基础信息及MQTT连接配置)
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+//@TableName("cdi_defense_project")
+public class CdiDefenseProject implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 人防_工程表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 工程名称
+     */
+    @NotBlank(message = "工程名称不能为空")
+    private String engineeringName;
+
+    /**
+     * 工程id
+     */
+    @NotNull(message = "工程id不能为空")
+    private Long engineeringId;
+
+    /**
+     * 密码
+     */
+    @NotBlank(message = "密码不能为空")
+    private String passwd;
+
+    /**
+     * 推送地址(带端口)
+     */
+    @NotBlank(message = "推送地址不能为空")
+    private String pushAddress;
+
+    /**
+     * mqtt用户名
+     */
+    @NotBlank(message = "mqtt用户名不能为空")
+    private String mqttUserName;
+
+    /**
+     * mqtt密码
+     */
+    @NotBlank(message = "mqtt密码不能为空")
+    private String mqttPassword;
+
+    /**
+     * mqtt客户端id
+     */
+    @NotBlank(message = "mqtt客户端id不能为空")
+    private String mqttClientId;
+
+    /**
+     * 基础数据MQTT消息主题
+     */
+    //@NotBlank(message = "基础数据MQTT消息主题不能为空")
+    private String baseTopic;
+
+    /**
+     * 监测数据MQTT消息主题
+     */
+    //@NotBlank(message = "监测数据MQTT消息主题不能为空")
+    //@TableField("iotInfo_topic")
+    private String iotinfoTopic;
+
+    /**
+     * 告警数据MQTT消息主题
+     */
+    //@NotBlank(message = "告警数据MQTT消息主题不能为空")
+    private String alarmTopic;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 是否启用(0:未启用,1:已启用)
+     */
+    @NotNull(message = "是否启用不能为空")
+    private Integer isEnable;
+
+    /**
+     * 租户ID
+     */
+    @NotNull(message = "租户ID不能为空")
+    private Integer tenantId;
+
+
+}

+ 90 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/CdiDeliveryLog.java

@@ -0,0 +1,90 @@
+package com.usky.cdi.domain;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+import java.time.LocalDateTime;
+import java.io.Serializable;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 人防_投递日志表(记录数据的MQTT投递情况)
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+// @DS("second")
+public class CdiDeliveryLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 人防_投递日志表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 工程id
+     */
+    private Long engineeringId;
+
+    /**
+     * 数据类型:1-单元,2-平面图,3-设施,4-监测, 5-告警
+     */
+    private Integer dataType;
+
+    /**
+     * 数据类型名称
+     */
+    @TableField(exist = false)
+    private String dataTypeName;
+
+    /**
+     * MQTT消息主题(base/engineering、iotInfo/temp、alarm/message等)
+     */
+    private String topic;
+
+    /**
+     * 数据内容(JSON格式字符串,存储此次上报数据推送情况)
+     */
+    @TableField("data_content")
+    private String infoContent;
+
+    /**
+     * 推送结果(0:失败,1:成功)
+     */
+    private Integer pushFlag;
+
+    /**
+     * 创建者
+     */
+    @TableField("create_by")
+    private String userName;
+
+    /**
+     * 创建者昵称
+     */
+    @TableField(exist = false)
+    private String nickName;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+
+}

+ 136 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/SysUser.java

@@ -0,0 +1,136 @@
+package com.usky.cdi.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 fu
+ * @since 2026-02-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class SysUser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @TableId(value = "user_id", type = IdType.AUTO)
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 用户账号
+     */
+    private String userName;
+
+    /**
+     * 用户昵称
+     */
+    private String nickName;
+
+    /**
+     * 用户类型(00系统用户, 01 租户管理员)
+     */
+    private String userType;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String phonenumber;
+
+    /**
+     * 用户性别(0男 1女 2未知)
+     */
+    private String sex;
+
+    /**
+     * 姓名
+     */
+    private String fullName;
+
+    /**
+     * 头像地址
+     */
+    private String avatar;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 帐号状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    private String delFlag;
+
+    /**
+     * 最后登录IP
+     */
+    private String loginIp;
+
+    /**
+     * 最后登录时间
+     */
+    private LocalDateTime loginDate;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+    /**
+     * 地址
+     */
+    private String address;
+
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildMapper.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.mapper;
+
+import com.usky.cdi.domain.BaseBuild;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 建筑信息 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+public interface BaseBuildMapper extends CrudMapper<BaseBuild> {
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildPlaneMapper.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.mapper;
+
+import com.usky.cdi.domain.BaseBuildPlane;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 建筑楼层平面图 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+public interface BaseBuildPlaneMapper extends CrudMapper<BaseBuildPlane> {
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildUnitMapper.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.mapper;
+
+import com.usky.cdi.domain.BaseBuildUnit;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+public interface BaseBuildUnitMapper extends CrudMapper<BaseBuildUnit> {
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/CdiDefenseProjectMapper.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.mapper;
+
+import com.usky.cdi.domain.CdiDefenseProject;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 人防_工程表(人防工程基础信息及MQTT连接配置) Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+public interface CdiDefenseProjectMapper extends CrudMapper<CdiDefenseProject> {
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/CdiDeliveryLogMapper.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.mapper;
+
+import com.usky.cdi.domain.CdiDeliveryLog;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 人防_投递日志表(记录数据的MQTT投递情况) Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+public interface CdiDeliveryLogMapper extends CrudMapper<CdiDeliveryLog> {
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/SysUserMapper.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.mapper;
+
+import com.usky.cdi.domain.SysUser;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 用户信息表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-04
+ */
+public interface SysUserMapper extends CrudMapper<SysUser> {
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildPlaneService.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.service;
+
+import com.usky.cdi.domain.BaseBuildPlane;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 建筑楼层平面图 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+public interface BaseBuildPlaneService extends CrudService<BaseBuildPlane> {
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildService.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.service;
+
+import com.usky.cdi.domain.BaseBuild;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 建筑信息 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+public interface BaseBuildService extends CrudService<BaseBuild> {
+
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildUnitService.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.service;
+
+import com.usky.cdi.domain.BaseBuildUnit;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+public interface BaseBuildUnitService extends CrudService<BaseBuildUnit> {
+
+}

+ 20 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDefenseProjectService.java

@@ -0,0 +1,20 @@
+package com.usky.cdi.service;
+
+import com.usky.cdi.domain.CdiDefenseProject;
+import com.usky.common.mybatis.core.CrudService;
+
+
+/**
+ * <p>
+ * 人防_工程表(人防工程基础信息及MQTT连接配置) 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+public interface CdiDefenseProjectService extends CrudService<CdiDefenseProject> {
+
+    boolean tryConnectAndPublish(String broker, String user, String pass, String ping, String clientId);
+
+    CdiDefenseProject listByTenantId(Integer tenantId);
+}

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

@@ -0,0 +1,31 @@
+package com.usky.cdi.service;
+
+import com.usky.cdi.domain.CdiDeliveryLog;
+import com.usky.cdi.service.vo.SyncTaskStatisticsVO;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.mybatis.core.CrudService;
+import org.springframework.scheduling.annotation.Async;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 人防_投递日志表(记录数据的MQTT投递情况) 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+public interface CdiDeliveryLogService extends CrudService<CdiDeliveryLog> {
+
+    List<SyncTaskStatisticsVO> selectById(Long id);
+
+    CommonPage<CdiDeliveryLog> logList(Long id, Integer pageNum, Integer pageSize, Integer dataType, Integer logType, String startTime, String endTime);
+
+    void synchronizeData(SyncTaskStatisticsVO vo);
+
+    // 存储日志
+    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);
+}

+ 16 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/SysUserService.java

@@ -0,0 +1,16 @@
+package com.usky.cdi.service;
+
+import com.usky.cdi.domain.SysUser;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 用户信息表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-04
+ */
+public interface SysUserService extends CrudService<SysUser> {
+
+}

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

@@ -1,4 +1,4 @@
-package com.usky.cdi.domain.enums;
+package com.usky.cdi.service.enums;
 
 /**
  * 告警类型枚举类

+ 103 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/DeliveryDataType.java

@@ -0,0 +1,103 @@
+package com.usky.cdi.service.enums;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/2/5
+ */
+
+/**
+ * 人防投递日志-数据类型枚举(对应cdi_delivery_log表的data_type字段)
+ */
+public enum DeliveryDataType {
+
+    /**
+     * 防护单元基础信息
+     */
+    BASE_DATA(1, "防护单元基础信息"),
+
+    /**
+     * 楼层平面图信息
+     */
+    IOT_MONITOR_DATA(2, "楼层平面图信息"),
+
+    /**
+     * 智能监管物联设施信息
+     */
+    HIDDEN_DANGER(3, "智能监管物联设施信息");
+
+
+
+
+    /**
+     * 数据库存储的编码(对应data_type字段值)
+     */
+    private final Integer code;
+
+    /**
+     * 编码对应的业务描述
+     */
+    private final String desc;
+
+    // 构造方法(枚举类构造方法默认私有,无需显式写private)
+    DeliveryDataType(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    // ========== 基础getter方法 ==========
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public static Map<Integer, String> getAllDesc() {
+        return Arrays.stream(values())
+                .collect(java.util.stream.Collectors.toMap(DeliveryDataType::getCode, DeliveryDataType::getDesc));
+    }
+
+    // ========== 核心工具方法(日常开发高频使用) ==========
+
+    /**
+     * 根据编码获取对应的枚举对象(推荐使用,空值/无效编码返回Optional.empty(),避免空指针)
+     * @param code 数据类型编码(1/2/3)
+     * @return 对应枚举的Optional对象
+     */
+    public static Optional<DeliveryDataType> getByCode(Integer code) {
+        // 空值直接返回空,避免遍历判断
+        if (code == null) {
+            return Optional.empty();
+        }
+        // 遍历枚举数组匹配编码
+        return Arrays.stream(values())
+                .filter(enumObj -> enumObj.getCode().equals(code))
+                .findFirst();
+    }
+
+    /**
+     * 根据编码获取描述(简化版,无匹配返回空字符串,适合接口返回/日志打印)
+     * @param code 数据类型编码(1/2/3)
+     * @return 编码对应的描述,无匹配返回""
+     */
+    public static String getDescByCode(Integer code) {
+        return getByCode(code)
+                .map(DeliveryDataType::getDesc)
+                .orElse("");
+    }
+
+    /**
+     * 判断编码是否为有效的数据类型编码(适合入参校验/数据校验)
+     * @param code 数据类型编码
+     * @return true-有效,false-无效/空值
+     */
+    public static boolean isValidCode(Integer code) {
+        return getByCode(code).isPresent();
+    }
+}

+ 14 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/EnvMonitorMqttTopic.java

@@ -40,7 +40,20 @@ public enum EnvMonitorMqttTopic {
     CRACK("iotInfo/crack"),
 
     // 位移
-    DEVIATION("iotInfo/deviation");
+    DEVIATION("iotInfo/deviation"),
+
+    // 工程信息
+    ENGINEERING("base/engineering"),
+
+    // 防化单元
+    PROTECTIVE_UNIT("base/protectiveUnit"),
+
+    // 平面图
+    FLOOR_PLANE("base/floorPlane"),
+
+    // 物联设施
+    SENSOR_INFO("base/sensorInfo");
+
 
     private final String topic;
 

+ 155 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/MqttTopics.java

@@ -0,0 +1,155 @@
+package com.usky.cdi.service.enums;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/2/3
+ */
+public final class MqttTopics {
+
+    /* ========================== 1. base ========================== */
+    @Getter
+    @RequiredArgsConstructor
+    public enum Base {
+        ENGINEERING("base/engineering", "人防工程基础信息"),
+        PROTECTIVE_UNIT("base/protectiveUnit", "防护单元基础信息"),
+        FLOOR_PLANE("base/floorPlane", "楼层平面图信息"),
+        SENSOR_INFO("base/sensorInfo", "智能监管物联设施数据");
+
+        private final String topic;
+        private final String desc;
+    }
+
+    /* ========================== 2. iotInfo ========================== */
+    @Getter
+    @RequiredArgsConstructor
+    public enum IotInfo {
+        MONITORING_DATA("iotInfo", "物联监测数据"),
+        CIVIL_DEFENSE_DOOR("iotInfo/civilDefenseDoor", "人防门姿态"),
+        WATER_TANK_CAPACITY("iotInfo/waterTankCapacity", "战时水箱水量"),
+        PIPELINE_VALVE("iotInfo/pipelineValve", "给排水管道阀门启闭状态"),
+        VENTILATION_PIPELINE_VALVE("iotInfo/ventilationPipelineValve", "战时通风管道密闭阀启闭状态"),
+        OVER_PRESSURE_VALVE("iotInfo/overPressureValve", "超压排气活门启闭状态"),
+        MICRO_PRESSURE_DIFFERENCE("iotInfo/microPressureDifference", "微压差"),
+        BLOWER_STARTUP_TIME("iotInfo/blowerStartupTime", "战时风机每日运行时长"),
+        VENTILATION_RUN_STATUS("iotInfo/ventilationRunStatus", "战时通风系统运行工况"),
+        ALTERNATOR_STARTUP_TIME("iotInfo/alternatorStartupTime", "战时柴油发电机每日启动时长"),
+        ALTERNATOR_STATUS("iotInfo/alternatorStatus", "战时柴油发电机实时工况"),
+        DIESEL_CAPACITY("iotInfo/dieselCapacity", "战时储油容量"),
+        PERSON("iotInfo/person", "工程内掩蔽人数"),
+        SIGNAL_STRENGTH("iotInfo/sig", "工程外移动通信信号强度"),
+        MATERIAL("war/material", "工程战备物资储备情况"),
+        SEWAGE_LEVEL("iotInfo/sewageLevel", "集水井水位"),
+        H2S("iotInfo/h2S", "硫化氢浓度"),
+        NH3("iotInfo/nh3", "氨浓度"),
+        HCHO("iotInfo/hcho", "甲醛浓度"),
+        C6H6("iotInfo/c6h6", "苯浓度"),
+        RADIATION("iotInfo/radiation", "空气放射性物质含量"),
+        RN("iotInfo/rn", "氡活度浓度"),
+        CCL2F2("iotInfo/ccl2f2", "二氯二氟甲烷浓度"),
+        TVOC("iotInfo/tvoc", "总挥发性有机化合物浓度"),
+        PM("iotInfo/pm", "可吸入颗粒物浓度"),
+        NO2("iotInfo/no2", "二氧化氮浓度"),
+        SO2("iotInfo/so2", "二氧化硫浓度"),
+        BACTERIAL("iotInfo/bacterial", "细菌总数"),
+        TILT("iotInfo/tilt", "倾斜"),
+        CRACK("iotInfo/crack", "裂缝"),
+        DEVIATION("iotInfo/deviation", "位移"),
+        // 水浸状态
+        WATER_LEAK("iotInfo/flooded", "水浸状态"),
+        // 温度
+        TEMP("iotInfo/temp", "空气温度"),
+        // 湿度
+        HUMIDITY("iotInfo/rh", "空气湿度"),
+        // 氧气
+        OXYGEN("iotInfo/o2", "氧气浓度"),
+        // 二氧化碳
+        CO2("iotInfo/co2", "二氧化碳浓度"),
+        // 一氧化碳
+        CO("iotInfo/co", "一氧化碳浓度"),
+        // 人员闯入情况
+        PERSON_PRESENCE("iotInfo/personPresence", "人员闯入情况"),
+        // 用电负荷
+        ELECTRICITY_LOAD("iotInfo/electricityLoad", "人防用电负荷");
+
+        private final String topic;
+        private final String desc;
+    }
+
+    /* ========================== 3. alarm ========================== */
+    @Getter
+    @RequiredArgsConstructor
+    public enum Alarm {
+        // 通用告警事件(人防门、超压、滤尘器、油库、水浸、环境气体、电气、结构、人员、火险等全部走此 Topic)
+        MESSAGE("alarm/message", "告警信息");
+
+        private final String topic;
+        private final String desc;
+    }
+
+
+    /* =============== 工具方法:根据字符串 topic 反查枚举 =============== */
+    public static Base baseOf(String topic) {
+        for (Base b : Base.values()) {
+            if (b.topic.equals(topic)) return b;
+        }
+        return null;
+    }
+
+    public static IotInfo iotInfoOf(String topic) {
+        for (IotInfo i : IotInfo.values()) {
+            if (i.topic.equals(topic)) return i;
+        }
+        return null;
+    }
+
+    public static Alarm alarmOf(String topic) {
+        for (Alarm a : Alarm.values()) {
+            if (a.topic.equals(topic)) return a;
+        }
+        return null;
+    }
+
+    public static String getDescByTopic(String topic) {
+        String desc = "";
+        Base base = baseOf(topic);
+        if (base != null) desc = base.getDesc();
+        IotInfo iotInfo = iotInfoOf(topic);
+        if (iotInfo != null) desc = iotInfo.getDesc();
+        Alarm alarm = alarmOf(topic);
+        if (alarm != null) desc = alarm.getDesc();
+        return desc;
+    }
+
+    private MqttTopics() {
+    }
+
+    public static IotInfo getIotInfoByTypeCode(Integer typeCode) {
+        switch (typeCode) {
+            case 707:
+                return IotInfo.TEMP;
+            case 708:
+                return IotInfo.HUMIDITY;
+            case 709:
+                return IotInfo.OXYGEN;
+            case 710:
+                return IotInfo.CO2;
+            case 711:
+                return IotInfo.CO;
+            case 703:
+                return IotInfo.PERSON_PRESENCE;
+            case 704:
+                return IotInfo.ELECTRICITY_LOAD;
+            case 702:
+                return IotInfo.WATER_LEAK;
+            case 714:
+                return IotInfo.DEVIATION;
+            default:
+                return IotInfo.MONITORING_DATA;
+        }
+    }
+}

+ 24 - 13
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataSyncService.java

@@ -6,11 +6,12 @@ import com.usky.cdi.domain.BaseAlarm;
 import com.usky.cdi.domain.BaseBuildFacility;
 import com.usky.cdi.mapper.BaseAlarmMapper;
 import com.usky.cdi.mapper.BaseBuildFacilityMapper;
-import com.usky.cdi.mapper.DmpProductMapper;
+import com.usky.cdi.service.CdiDeliveryLogService;
+import com.usky.cdi.service.enums.MqttTopics;
 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.domain.enums.AlarmType;
+import com.usky.cdi.service.enums.AlarmType;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -46,7 +47,6 @@ public class AlarmDataSyncService {
 
     private static final String PEACETIME = "peacetime";
     private static final String WARTIME = "wartime";
-    private static final String MQTT_TOPIC = "alarm/message";
     private static final String ALARM_DELIVERY_KEY_PREFIX = "alarm:delivery:";
     private final MqttConnectionTool mqttConnectionTool;
 
@@ -55,9 +55,9 @@ public class AlarmDataSyncService {
     @Autowired
     private BaseAlarmMapper baseAlarmMapper;
     @Autowired
-    private DmpProductMapper dmpProductMapper;
-    @Autowired
     private BaseBuildFacilityMapper baseBuildFacilityMapper;
+    @Autowired
+    private CdiDeliveryLogService cdiDeliveryLogService;
 
     private SnowflakeIdGenerator idGenerator;
 
@@ -84,7 +84,9 @@ public class AlarmDataSyncService {
     public void synchronizeAlarmData(Integer tenantId, Long engineeringId, String username, String password, String status) {
         log.info("租户:{}的人防告警数据推送定时任务开始执行,平战时状态:{}", tenantId, PEACETIME.equals(status) ? "平时" : "战时");
 
-        Long startTime = System.currentTimeMillis();
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+        long endTime;
         log.info("开始时间:{}", getCurrentTime());
 
         // 1.查询 base_alarm 表中的告警数据,筛选条件为 tenant_id = tenantId
@@ -97,7 +99,8 @@ public class AlarmDataSyncService {
             log.warn("租户{}没有新的告警数据,任务结束", tenantId);
             return;
         }
-        log.info("查询到租户{}的告警数据总数:{}", tenantId, alarmList.size());
+        int size = alarmList.size();
+        log.info("查询到租户{}的告警数据总数:{}", tenantId, size);
 
         // 批量查询建筑设施数据,构建设备ID到设施的映射
         Map<String, BaseBuildFacility> facilityMap = buildFacilityMap(alarmList, tenantId);
@@ -109,13 +112,15 @@ public class AlarmDataSyncService {
                     alarm.getAlarmTime(), alarm.getHandleStatus(), alarm.getAlarmFalse());
         }
 
+        int successCount = 0;
+        int failureCount = 0;
+        String topic = MqttTopics.Alarm.MESSAGE.getTopic();
+        String desc = MqttTopics.Alarm.MESSAGE.getDesc();
+
         try {
             // 2.创建MQTT连接
             mqttConnectionTool.connectOrRefresh(username, password);
 
-            int successCount = 0;
-            int failureCount = 0;
-
             // 3.遍历告警数据,转换为AlarmMessageVO并发送
             for (BaseAlarm alarm : alarmList) {
                 try {
@@ -170,7 +175,7 @@ public class AlarmDataSyncService {
                     // 4.将AlarmMessageVO转换为JSON字符串并发送MQTT消息
                     String jsonMessage = JSON.toJSONString(alarmMessageVO);
                     MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(username, password);
-                    gateway.sendToMqtt(MQTT_TOPIC, jsonMessage);
+                    gateway.sendToMqtt(topic, jsonMessage);
 
                     successCount++;
 
@@ -189,13 +194,19 @@ public class AlarmDataSyncService {
 
             // 6.打印统计信息
             log.info("租户{}的告警数据推送任务完成", tenantId);
-            log.info("告警数据总数:{}, 成功推送:{}, 失败:{}", alarmList.size(), successCount, failureCount);
+            log.info("告警数据总数:{}, 成功推送:{}, 失败:{}", size, successCount, failureCount);
+            endTime = System.currentTimeMillis();
 
+            cdiDeliveryLogService.saveLog(topic, desc, 5, tenantId, engineeringId, now, startTime, endTime, size,
+                    successCount, failureCount, size - successCount - failureCount, 1);
         } catch (Exception e) {
             log.error("租户{}的告警数据推送定时任务执行失败:{}", tenantId, e.getMessage(), e);
         } finally {
-            Long endTime = System.currentTimeMillis();
+            endTime = System.currentTimeMillis();
             log.info("结束时间:{}, 耗时:{}ms", getCurrentTime(), endTime - startTime);
+
+            cdiDeliveryLogService.saveLog(topic, desc, 5, tenantId, engineeringId, now, startTime, endTime, size,
+                    successCount, failureCount, size - successCount - failureCount, 0);
         }
     }
 

+ 20 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildPlaneServiceImpl.java

@@ -0,0 +1,20 @@
+package com.usky.cdi.service.impl;
+
+import com.usky.cdi.domain.BaseBuildPlane;
+import com.usky.cdi.mapper.BaseBuildPlaneMapper;
+import com.usky.cdi.service.BaseBuildPlaneService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 建筑楼层平面图 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+@Service
+public class BaseBuildPlaneServiceImpl extends AbstractCrudService<BaseBuildPlaneMapper, BaseBuildPlane> implements BaseBuildPlaneService {
+
+}

+ 20 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildServiceImpl.java

@@ -0,0 +1,20 @@
+package com.usky.cdi.service.impl;
+
+import com.usky.cdi.domain.BaseBuild;
+import com.usky.cdi.mapper.BaseBuildMapper;
+import com.usky.cdi.service.BaseBuildService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 建筑信息 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+@Service
+public class BaseBuildServiceImpl extends AbstractCrudService<BaseBuildMapper, BaseBuild> implements BaseBuildService {
+
+}

+ 20 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildUnitServiceImpl.java

@@ -0,0 +1,20 @@
+package com.usky.cdi.service.impl;
+
+import com.usky.cdi.domain.BaseBuildUnit;
+import com.usky.cdi.mapper.BaseBuildUnitMapper;
+import com.usky.cdi.service.BaseBuildUnitService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-05
+ */
+@Service
+public class BaseBuildUnitServiceImpl extends AbstractCrudService<BaseBuildUnitMapper, BaseBuildUnit> implements BaseBuildUnitService {
+
+}

+ 44 - 31
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java

@@ -7,10 +7,14 @@ 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.util.SnowflakeIdGenerator;
-import com.usky.cdi.service.vo.base.*;
+import com.usky.cdi.service.vo.info.EngineeringBaseVO;
+import com.usky.cdi.service.vo.info.FacilityDeviceVO;
+import com.usky.cdi.service.vo.info.FloorPlaneVO;
+import com.usky.cdi.service.vo.info.ProtectiveUnitVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
@@ -26,7 +30,7 @@ import java.util.*;
 /**
  * 基础类数据传输服务
  * 负责向市适配平台发送基础类数据
- * 
+ *
  * @author han
  * @date 2025/03/20
  */
@@ -72,7 +76,7 @@ public class BaseDataTransferService {
     /**
      * 发送人防工程基础信息
      * Topic: base/engineering
-     * 
+     *
      * @param vo 人防工程基础信息
      * @return 是否发送成功
      */
@@ -87,10 +91,10 @@ public class BaseDataTransferService {
 
             String json = JSON.toJSONString(vo);
             String topic = "base/engineering";
-            
+
             log.info("发送人防工程基础信息,Topic: {}, Data: {}", topic, json);
             mqttGateway.sendToMqtt(topic, json);
-            
+
             return true;
         } catch (Exception e) {
             log.error("发送人防工程基础信息失败", e);
@@ -101,7 +105,7 @@ public class BaseDataTransferService {
     /**
      * 发送防护单元基础信息
      * Topic: base/protectiveUnit
-     * 
+     *
      * @param vo 防护单元基础信息
      * @return 是否发送成功
      */
@@ -116,10 +120,10 @@ public class BaseDataTransferService {
 
             String json = JSON.toJSONString(vo);
             String topic = "base/protectiveUnit";
-            
+
             log.info("发送防护单元基础信息,Topic: {}, Data: {}", topic, json);
             mqttGateway.sendToMqtt(topic, json);
-            
+
             return true;
         } catch (Exception e) {
             log.error("发送防护单元基础信息失败", e);
@@ -130,7 +134,7 @@ public class BaseDataTransferService {
     /**
      * 发送楼层平面图信息
      * Topic: base/floorPlane
-     * 
+     *
      * @param vo 楼层平面图信息
      * @return 是否发送成功
      */
@@ -178,12 +182,12 @@ public class BaseDataTransferService {
             String json = jsonObject.toJSONString();
             System.out.println(gson.toJson(map));
             String topic = "base/floorPlane";
-            
-            log.info("发送楼层平面图信息,Topic: {}, FileID: {}, FileSize: {} bytes", 
-                    topic, vo.getFloorFileID(), 
+
+            log.info("发送楼层平面图信息,Topic: {}, FileID: {}, FileSize: {} bytes",
+                    topic, vo.getFloorFileID(),
                     vo.getFloorFile() != null ? vo.getFloorFile().length : 0);
             mqttGateway.sendToMqtt(topic, gson.toJson(map));
-            
+
             return true;
         } catch (Exception e) {
             log.error("发送楼层平面图信息失败,FileID: {}", vo.getFloorFileID(), e);
@@ -209,7 +213,7 @@ public class BaseDataTransferService {
     /**
      * 发送智能监管物联设施信息
      * Topic: base/sensorInfo
-     * 
+     *
      * @param vo 智能监管物联设施信息
      * @return 是否发送成功
      */
@@ -224,8 +228,8 @@ public class BaseDataTransferService {
             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);
 
             HashMap<String, Object> map = new HashMap<>();
@@ -250,7 +254,7 @@ public class BaseDataTransferService {
             System.out.println(gson.toJson(map));
 //            log.info("发送智能监管物联设施信息,Topic: {}, SensorID: {}", topic, vo.getSensorID());
             mqttGateway.sendToMqtt(topic, gson.toJson(map));
-            
+
             return true;
         } catch (Exception e) {
             log.error("发送智能监管物联设施信息失败", e);
@@ -260,7 +264,7 @@ public class BaseDataTransferService {
 
     /**
      * 批量发送防护单元基础信息
-     * 
+     *
      * @param units 防护单元列表
      * @return 成功发送的数量
      */
@@ -268,32 +272,32 @@ public class BaseDataTransferService {
         if (units == null || units.isEmpty()) {
             return 0;
         }
-        
+
         int successCount = 0;
         for (ProtectiveUnitVO unit : units) {
             if (sendProtectiveUnit(unit)) {
                 successCount++;
             }
         }
-        
+
         log.info("批量发送防护单元基础信息,总数: {}, 成功: {}", units.size(), successCount);
         return successCount;
     }
 
     /**
      * 批量发送智能监管物联设施信息
-     * 
+     *
      * @param tenantId 租户ID
      * @return 成功发送的数量
      */
-    public int batchSendSensorInfos(Integer tenantId) {
+    public Map<String, Integer> batchSendSensorInfos(Integer tenantId) {
         List<BaseBuildFacility> list = baseBuildFacilityService.facilityInfo(tenantId);
         List<DmpDevice> list1 = dmpDeviceInfoService.deviceInfo(tenantId);
         List<FacilityDeviceVO> list2 = new ArrayList<>();
-        if(CollectionUtils.isNotEmpty(list)&&CollectionUtils.isNotEmpty(list1)){
-            for (int j=0;j<list.size();j++) {
-                for (int k=0;k<list1.size();k++) {
-                    if (list.get(j).getDeviceId().equals(list1.get(k).getDeviceId())){
+        if (CollectionUtils.isNotEmpty(list) && CollectionUtils.isNotEmpty(list1)) {
+            for (int j = 0; j < list.size(); j++) {
+                for (int k = 0; k < list1.size(); k++) {
+                    if (list.get(j).getDeviceId().equals(list1.get(k).getDeviceId())) {
                         FacilityDeviceVO facilityDeviceVO = new FacilityDeviceVO();
                         facilityDeviceVO.setFloor(list.get(j).getFloor());
                         facilityDeviceVO.setFacilityName(list.get(j).getFacilityName());
@@ -310,16 +314,25 @@ public class BaseDataTransferService {
                 }
             }
         }
-        int successCount = 0;
-        if(CollectionUtils.isNotEmpty(list2)){
-            for (int i=0;i<list2.size();i++) {
+        int successCount = 0, failCount = 0, notSynced = 0;
+        if (CollectionUtils.isNotEmpty(list2)) {
+            for (int i = 0; i < list2.size(); i++) {
                 if (sendSensorInfo(list2.get(i))) {
                     successCount++;
+                } else {
+                    failCount++;
                 }
             }
         }
-        log.info("批量发送智能监管物联设施信息,总数: {}, 成功: {}", list2.size(), successCount);
-        return successCount;
+        notSynced = list2.size() - successCount - failCount;
+        log.info("批量发送智能监管物联设施信息,总数: {}, 成功: {}, 失败: {}, 未同步: {}", list2.size(), successCount, failCount, notSynced);
+
+        Map<String, Integer> map = new HashMap<>();
+        map.put("total", list2.size());
+        map.put("success", successCount);
+        map.put("failure", failCount);
+        map.put("notSynced", notSynced);
+        return map;
     }
 }
 

+ 63 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDefenseProjectServiceImpl.java

@@ -0,0 +1,63 @@
+package com.usky.cdi.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.cdi.domain.CdiDefenseProject;
+import com.usky.cdi.mapper.CdiDefenseProjectMapper;
+import com.usky.cdi.service.CdiDefenseProjectService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 人防_工程表(人防工程基础信息及MQTT连接配置) 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+@Service
+public class CdiDefenseProjectServiceImpl extends AbstractCrudService<CdiDefenseProjectMapper, CdiDefenseProject> implements CdiDefenseProjectService {
+
+    @Override
+    public boolean tryConnectAndPublish(String brokerUrl, String username, String password, String payload, String tmpClientId) {
+
+        MqttClient client = null;
+        try {
+            client = new MqttClient(brokerUrl, tmpClientId, new MemoryPersistence());
+            MqttConnectOptions options = new MqttConnectOptions();
+            options.setUserName(username);
+            options.setPassword(password.toCharArray());
+            options.setConnectionTimeout(3);
+            options.setAutomaticReconnect(false);
+            client.connect(options);
+
+            return true;
+        } catch (Exception e) {
+            log.error("MQTT连接测试失败", e);
+            return false;
+        } finally {
+            if (client != null) {
+                try {
+                    client.disconnect();
+                } catch (Exception ignore) {
+                }
+                try {
+                    client.close();
+                } catch (Exception ignore) {
+                }
+                // 再加一行:把实例从缓存里强行踢掉
+                // MqttClient.removeClient(client.getClientId());
+            }
+        }
+    }
+
+    @Override
+    public CdiDefenseProject listByTenantId(Integer tenantId) {
+        LambdaQueryWrapper<CdiDefenseProject> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(CdiDefenseProject::getTenantId, tenantId);
+        return baseMapper.selectOne(queryWrapper);
+    }
+}

+ 658 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java

@@ -0,0 +1,658 @@
+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.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.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.usky.cdi.domain.*;
+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.vo.SyncTaskStatisticsVO;
+import com.usky.cdi.service.vo.info.FloorPlaneVO;
+import com.usky.cdi.service.vo.info.ProtectiveUnitVO;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.common.security.utils.SecurityUtils;
+import lombok.extern.slf4j.Slf4j;
+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 javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.net.URLConnection;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 人防_投递日志表(记录数据的MQTT投递情况) 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-02
+ */
+@Slf4j
+@Service
+public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLogMapper, CdiDeliveryLog> implements CdiDeliveryLogService {
+
+    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Autowired
+    private BaseBuildFacilityMapper baseBuildFacilityMapper;
+
+    @Autowired
+    private BaseBuildUnitMapper baseBuildUnitMapper;
+
+    @Autowired
+    private CdiDefenseProjectMapper cdiDefenseProjectMapper;
+
+    @Autowired
+    private BaseBuildPlaneMapper baseBuildPlaneMapper;
+
+    @Autowired
+    private BaseBuildMapper baseBuildMapper;
+
+    @Autowired
+    private BaseDataTransferService baseDataTransferService;
+
+    @Autowired
+    private IotDataTransferService iotDataTransferService;
+
+    @Autowired
+    private MqttConnectionTool mqttConnectionTool;
+
+    @Override
+    public List<SyncTaskStatisticsVO> selectById(Long id) {
+        // 1. 租户ID校验(必须非空,无租户直接返回空列表)
+        Integer tenantId = SecurityUtils.getTenantId();
+        if (tenantId == null) {
+            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) // 改为倒序,优先最新数据
+                .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);
+
+            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())
+                        && logEntity.getDataType() != null)
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(validLogList)) {
+            log.warn("租户ID:{} 查询到{}条日志,但所有日志的JSON内容为空或dataType为空,查询ID:{}", tenantId, logList.size(), id);
+            return Collections.emptyList();
+        }
+
+        // 步骤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) // 空值处理
+                        )
+                ));
+
+        // 步骤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);
+            }
+            // 若某类型无日志,无需处理:后续解析后若该类型无数据,是否补空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;
+                    }
+                    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);
+            }
+        }
+
+        // // 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;
+    }
+
+    @Override
+    public CommonPage<CdiDeliveryLog> logList(Long id, Integer pageNum, Integer pageSize, Integer dataType, Integer
+            logType, String startTime, String endTime) {
+        IPage<CdiDeliveryLog> page = new Page<>(pageNum, pageSize);
+
+        LambdaQueryWrapper<SysUser> queryUser = new LambdaQueryWrapper<>();
+        queryUser.eq(SysUser::getTenantId, SecurityUtils.getTenantId());
+        Map<String, String> userMap = sysUserMapper.selectList(queryUser).stream().collect(
+                Collectors.toMap(SysUser::getUserName, SysUser::getNickName)
+        );
+
+        LambdaQueryWrapper<CdiDeliveryLog> queryWrapper = new LambdaQueryWrapper<>();
+        if (id != null) {
+            queryWrapper.eq(CdiDeliveryLog::getId, id);
+            page = this.page(page, queryWrapper);
+        } else {
+            LocalDateTime start = StrUtil.isNotBlank(startTime) ? LocalDateTime.parse(startTime, DATETIME_FORMATTER) : null;
+            LocalDateTime end = StrUtil.isNotBlank(endTime) ? LocalDateTime.parse(endTime, DATETIME_FORMATTER) : null;
+
+            queryWrapper.eq(CdiDeliveryLog::getTenantId, SecurityUtils.getTenantId())
+                    .eq(dataType != null, CdiDeliveryLog::getDataType, dataType)
+                    .eq(logType != null, CdiDeliveryLog::getPushFlag, logType)
+                    .between(start != null && end != null && start.isBefore(end),
+                            CdiDeliveryLog::getCreateTime, start, end)
+                    .orderByDesc(CdiDeliveryLog::getCreateTime);
+            page = this.page(page, queryWrapper);
+        }
+        page.getRecords().forEach(log -> {
+            log.setNickName(userMap.get(log.getUserName()));
+            log.setDataTypeName((MqttTopics.getDescByTopic(log.getTopic())));
+        });
+
+        return new CommonPage<>(page.getRecords(), page.getTotal(), pageSize, pageNum);
+    }
+
+    /**
+     * 获取建筑设施列表
+     *
+     * @param tenantId 租户ID
+     * @return 结果列表
+     */
+    private List<BaseBuildFacility> getBuildFacilityList(Integer tenantId) {
+        LambdaQueryWrapper<BaseBuildFacility> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(BaseBuildFacility::getTenantId, tenantId)
+                .eq(BaseBuildFacility::getDeleteFlag, 0);
+        return baseBuildFacilityMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 获取建筑单元列表
+     *
+     * @param tenantId 租户ID
+     * @return 结果列表
+     */
+    private List<BaseBuildUnit> getBuildUnitList(Integer tenantId) {
+        LambdaQueryWrapper<BaseBuildUnit> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(BaseBuildUnit::getTenantId, tenantId);
+        return baseBuildUnitMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 获取建筑列表
+     *
+     * @param tenantId 租户ID
+     * @return 列表
+     */
+    private List<BaseBuild> getBuildList(Integer tenantId) {
+        LambdaQueryWrapper<BaseBuild> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(BaseBuild::getTenantId, tenantId);
+        return baseBuildMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 获取建筑平面列表
+     *
+     * @param buildIds 建筑ID列表
+     * @return 结果列表
+     */
+    private List<BaseBuildPlane> getBuildPlaneList(List<Integer> buildIds) {
+        LambdaQueryWrapper<BaseBuildPlane> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(BaseBuildPlane::getBuildId, buildIds);
+        return baseBuildPlaneMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 同步数据
+     *
+     * @param vo 同步任务统计信息
+     * @return null
+     */
+    @Override
+    public void synchronizeData(SyncTaskStatisticsVO vo) {
+
+        if (vo.getDataType() == null) {
+            throw new BusinessException("数据类型不能为空!");
+        } else if (vo.getDataType() < 1 || vo.getDataType() > 4) {
+            throw new BusinessException("同步数据类型错误!");
+        }
+
+        LambdaQueryWrapper<CdiDefenseProject> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(CdiDefenseProject::getTenantId, SecurityUtils.getTenantId());
+        CdiDefenseProject one = cdiDefenseProjectMapper.selectOne(queryWrapper);
+
+        if (one == null) {
+            throw new BusinessException("未找到对应的工程配置!无法同步");
+        } else if (!one.getIsEnable().equals(1)) {
+            throw new BusinessException("工程配置未启用!无法同步");
+        }
+
+        Long engineeringId = one.getEngineeringId();
+        String username = one.getMqttUserName();
+        String password = one.getMqttPassword();
+        Integer tenantId = one.getTenantId();
+        LocalDateTime now = LocalDateTime.now();
+
+        switch (vo.getDataType()) {
+            // 单元信息
+            case 1:
+                log.info("开始同步单元信息");
+
+                long startTime, endTime;
+                startTime = System.currentTimeMillis();
+                int total = 0, success = 0, failure = 0, notSynced = 0;
+
+                List<ProtectiveUnitVO> unitVOS = synchronizeBuildUnit(tenantId, engineeringId);
+                total = unitVOS.size();
+                mqttConnectionTool.connectOrRefresh(username, password);
+                iotDataTransferService.createMqttConnection(username, password);
+                String topic = MqttTopics.Base.PROTECTIVE_UNIT.getTopic();
+                String desc = MqttTopics.Base.PROTECTIVE_UNIT.getDesc();
+
+                for (ProtectiveUnitVO unitVO : unitVOS) {
+                    try {
+                        iotDataTransferService.sendMqttMessage(topic, unitVO, desc, username);
+                        success++;
+                    } catch (Exception e) {
+                        log.error("同步单元信息失败!", e);
+                        failure++;
+                    }
+                }
+                endTime = System.currentTimeMillis();
+
+                notSynced = total - success - failure;
+                saveLog(topic, desc, 1, tenantId, engineeringId, now, startTime, endTime, total, success, failure, notSynced, failure > 0 ? 0 : 1);
+
+                break;
+            // 平面图信息
+            case 2:
+                log.info("开始同步楼层平面图信息");
+
+                long startTime2, endTime2;
+                startTime2 = System.currentTimeMillis();
+                int total2 = 0, success2 = 0, failure2 = 0, notSynced2 = 0;
+
+                List<FloorPlaneVO> floorPlaneVOS = buildPlanes(tenantId, engineeringId);
+                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);
+                        success2++;
+                    } catch (Exception e) {
+                        log.error("同步楼层平面图信息失败!", e);
+                        failure2++;
+                    }
+                }
+                endTime2 = System.currentTimeMillis();
+                notSynced2 = total2 - success2 - failure2;
+                saveLog(topic1, desc1, 2, tenantId, engineeringId, now, startTime2, endTime2, total2, success2,
+                        failure2, notSynced2, failure2 > 0 ? 0 : 1);
+
+                break;
+            // 推送设施信息
+            case 3:
+                log.info("开始同步设施信息");
+
+                long startTime3, endTime3;
+                startTime3 = System.currentTimeMillis();
+
+                Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId);
+
+                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);
+
+                break;
+            // 推送监测数据
+            case 4:
+                log.info("开始同步监测数据");
+                // iotDataTransferService.synchronizeDeviceData(tenantId, engineeringId, username, password);
+                iotDataTransferService.synchronizeDeviceData(1205, 3101070011L, "3101070011", "5RqhJ7VG");
+                break;
+        }
+    }
+
+    // 存储日志
+    @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) {
+
+        SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
+        vo.setDataType(dataType);
+        vo.setTotal(total);
+        vo.setSuccessNumber(success);
+        vo.setFailNumber(failure);
+        vo.setNotSynced(notSynced);
+        vo.setCreateTime(now.format(DATETIME_FORMATTER));
+        vo.setCostTime((float) (endTime - startTime) / 1000.0f);
+        vo.setState(1);
+        vo.setTopic(topic);
+        vo.setDataTypeName(dataTypeName);
+
+        CdiDeliveryLog log = new CdiDeliveryLog();
+        log.setEngineeringId(engineeringId);
+        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.setCreateTime(now);
+        log.setTenantId(tenantId);
+        log.setPushFlag(pushFlag);
+        log.setInfoContent(JSON.toJSONString(vo));
+
+        this.save(log);
+    }
+
+    // 单元信息
+    private List<ProtectiveUnitVO> synchronizeBuildUnit(Integer tenantId, Long engineeringId) {
+        List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
+        if (CollUtil.isEmpty(buildUnitList)) {
+            return Collections.emptyList();
+        }
+
+        String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
+
+        List<ProtectiveUnitVO> result = new ArrayList<>(buildUnitList.size());
+        for (BaseBuildUnit buildUnit : buildUnitList) {
+            ProtectiveUnitVO vo = new ProtectiveUnitVO();
+            vo.setEngineeringID(engineeringId);
+            vo.setUnitName(buildUnit.getUnitName());
+            vo.setFloor(buildUnit.getFloor());
+            vo.setUnitArea(buildUnit.getUnitArea() != null
+                    ? BigDecimal.valueOf(buildUnit.getUnitArea())
+                    : BigDecimal.ZERO);
+            vo.setUnitUsage(buildUnit.getUnitUsage());
+            vo.setPeopleNumber(buildUnit.getPeopleNumber());
+            vo.setUnitmainexit(buildUnit.getUnitMainExit());
+            vo.setUnitotherexit(buildUnit.getUnitOtherExit());
+            vo.setPublishTime(time);
+            result.add(vo);
+        }
+
+        return result;
+    }
+
+    // 平面图信息
+    private List<FloorPlaneVO> buildPlanes(Integer tenantId, Long engineeringId) {
+        List<BaseBuild> buildList = getBuildList(tenantId);
+        if (CollUtil.isEmpty(buildList)) {
+            return Collections.emptyList();
+        }
+
+        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");
+
+        List<FloorPlaneVO> result = new ArrayList<>(buildPlaneList.size());
+        for (BaseBuildPlane buildPlane : buildPlaneList) {
+
+            String planeViewUrl = buildPlane.getPlaneViewUrl();
+
+
+            FloorPlaneVO vo = new FloorPlaneVO();
+            checkFileSize(vo, planeViewUrl);
+            vo.setEngineeringID(engineeringId);
+            vo.setFloor(buildPlane.getFloor());
+            vo.setFloorFileID(Long.valueOf(buildPlane.getId()));
+            fillImageInfo(vo, planeViewUrl);
+            vo.setPublishTime(time);
+        }
+
+        return result;
+    }
+
+    // 校验文件大小不超过5MB,然后存入vo中
+    private static final long MAX_FILE_SIZE_MB = 5;
+    private static final long MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
+
+    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)
+            );
+        }
+        vo.setFloorFile(FileUtil.readBytes(filePath));
+    }
+
+    // 获取图片信息
+    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());
+                }
+            }
+        } catch (IOException e) {
+            log.error("获取图片尺寸失败: {}", imageUrl);
+            vo.setFilePixWidth(7016);
+            vo.setFilePixHeight(9933);
+        }
+    }
+
+
+}

+ 230 - 77
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -3,30 +3,31 @@ package com.usky.cdi.service.impl;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.cdi.domain.CdiDefenseProject;
+import com.usky.cdi.domain.CdiDeliveryLog;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.domain.DmpProduct;
+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.EnvMonitorMqttTopic;
+import com.usky.cdi.service.enums.MqttTopics;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.util.DeviceDataQuery;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.IotDataTransferVO;
-import com.usky.cdi.service.vo.base.*;
+import com.usky.cdi.service.vo.SyncTaskStatisticsVO;
+import com.usky.cdi.service.vo.info.*;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
 import lombok.extern.slf4j.Slf4j;
-import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.beans.factory.support.DefaultListableBeanFactory;
 import org.springframework.context.ApplicationContext;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.context.support.GenericApplicationContext;
-import org.springframework.integration.dsl.IntegrationFlow;
-import org.springframework.integration.dsl.IntegrationFlows;
 import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
-import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
-import org.springframework.messaging.MessageChannel;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
@@ -34,6 +35,7 @@ import javax.annotation.PostConstruct;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
@@ -87,11 +89,19 @@ public class IotDataTransferService {
     @Value("${snowflake.data-center-id:2}")
     private long dataCenterId;
 
+    @Autowired
+    private CdiDefenseProjectMapper cdiDefenseProjectMapper;
+
+    @Autowired
+    private CdiDeliveryLogMapper cdiDeliveryLogMapper;
+
     @PostConstruct
     public void init() {
         this.idGenerator = new SnowflakeIdGenerator(workerId, dataCenterId);
     }
 
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
     /**
      * 获取当前时间
      */
@@ -121,6 +131,9 @@ public class IotDataTransferService {
             return result;
         }
 
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+
         try {
             List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
             log.warn("获取到的数据:{}", deviceData);
@@ -133,10 +146,16 @@ public class IotDataTransferService {
             if (deviceData.isEmpty()) {
                 log.warn("没有获取到水浸数据!设备类型:{}", deviceType);
                 result.put("failureCount", totalDevices);
+
+                // ✅ 在空数据时也记录一条日志(可选)
+                long endTime = System.currentTimeMillis();
+                saveLog(transferVO, now, startTime, endTime, totalDevices, 0, totalDevices, 0, 0);
                 return result;
             }
 
             Long engineeringId = transferVO.getEngineeringId();
+
+            // ✅ 先处理所有设备,不记录日志
             for (JSONObject deviceDataItem : deviceData) {
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
                 if (dataEndTime == null) {
@@ -161,7 +180,7 @@ public class IotDataTransferService {
                 vo.setDataEndTime(dataEndTime);
 
                 try {
-                    sendMqttMessage(EnvMonitorMqttTopic.WATER_LEAK, vo, "水浸状态信息", transferVO.getUsername());
+                    sendMqttMessage(MqttTopics.IotInfo.WATER_LEAK.getTopic(), vo, MqttTopics.IotInfo.WATER_LEAK.getDesc(), transferVO.getUsername());
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
                     log.warn("设备{}的水浸状态数据推送失败:{}", deviceId, e.getMessage());
@@ -169,13 +188,25 @@ public class IotDataTransferService {
                 }
             }
 
+            // ✅ 所有设备处理完毕后,统一记录一条日志
+            long endTime = System.currentTimeMillis();
+            int success = result.get("successCount");
+            int failure = result.get("failureCount");
+            int pending = totalDevices - success - failure;
+
+            saveLog(transferVO, now, startTime, endTime, totalDevices, success, failure, pending, success > 0 ? 1 : 0);
+
             log.info("水浸状态数据推送完成,设备类型:{},成功:{},失败:{}",
-                    deviceType, result.get("successCount"), result.get("failureCount"));
+                    deviceType, success, failure);
 
             return result;
         } catch (Exception e) {
             log.error("水浸状态数据推送发生异常", e);
             result.put("failureCount", transferVO.getDevices().size());
+
+            // ✅ 异常时也记录一条日志
+            long endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, transferVO.getDevices().size(), 0, transferVO.getDevices().size(), 0, 0);
             return result;
         }
     }
@@ -195,44 +226,31 @@ public class IotDataTransferService {
             return result;
         }
 
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+        long endTime;
+
+        Integer deviceType = transferVO.getDeviceType();
+        Integer totalDevices = transferVO.getDevices().size();
+
         try {
-            Integer deviceType = transferVO.getDeviceType();
             List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
-            Integer totalDevices = transferVO.getDevices().size();
-
-            // 统计各类型设备数据数量
-            int tempCount = 0, humidityCount = 0, coCount = 0, o2Count = 0, co2Count = 0;
-            for (JSONObject dataItem : deviceData) {
-                if (dataItem.containsKey("wd") && dataItem.getFloat("wd") != null) tempCount++;
-                if (dataItem.containsKey("sd") && dataItem.getFloat("sd") != null) humidityCount++;
-                if (dataItem.containsKey("co") && dataItem.getFloat("co") != null) coCount++;
-                if (dataItem.containsKey("o2") && dataItem.getFloat("o2") != null) o2Count++;
-                if (dataItem.containsKey("co2") && dataItem.getFloat("co2") != null) co2Count++;
-            }
 
-            log.info("开始推送温湿度及气体浓度数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
+            log.info("开始推送环境数据,设备类型:{},总设备数:{},获取到数据条数:{}",
                     deviceType, totalDevices, deviceData.size());
-            log.info("各类型设备数据数量:温度{}条,湿度{}条,一氧化碳{}条,氧气{}条,二氧化碳{}条",
-                    tempCount, humidityCount, coCount, o2Count, co2Count);
-
-            if (deviceData.isEmpty()) {
-                log.warn("没有获取到空气质量数据!设备类型:{}", deviceType);
-                result.put("failureCount", totalDevices);
-                return result;
-            }
 
             Long engineeringId = transferVO.getEngineeringId();
+
             for (JSONObject deviceDataItem : deviceData) {
+                Integer deviceId = deviceDataItem.getIntValue("device_id");
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
+
                 if (dataEndTime == null) {
                     result.put("failureCount", result.get("failureCount") + 1);
                     continue;
                 }
 
-                Integer deviceId = deviceDataItem.getIntValue("device_id");
                 boolean deviceSuccess = true;
-
-                // 根据设备类型发送对应的数据
                 try {
                     switch (deviceType) {
                         case 707:
@@ -241,22 +259,12 @@ public class IotDataTransferService {
                         case 708:
                             sendHumidityData(deviceId, dataEndTime, deviceDataItem, engineeringId, transferVO.getUsername());
                             break;
-                        case 709:
-                            sendOxygenData(deviceId, dataEndTime, deviceDataItem, engineeringId, transferVO.getUsername());
-                            break;
-                        case 710:
-                            sendCo2Data(deviceId, dataEndTime, deviceDataItem, engineeringId, transferVO.getUsername());
-                            break;
-                        case 711:
-                            sendCoData(deviceId, dataEndTime, deviceDataItem, engineeringId, transferVO.getUsername());
-                            break;
                     }
                 } catch (Exception e) {
-                    log.warn("设备{}的环境数据推送失败:{}", deviceId, e.getMessage());
+                    log.warn("设备{}推送失败:{}", deviceId, e.getMessage());
                     deviceSuccess = false;
                 }
 
-                // 统计设备推送结果
                 if (deviceSuccess) {
                     result.put("successCount", result.get("successCount") + 1);
                 } else {
@@ -264,17 +272,80 @@ public class IotDataTransferService {
                 }
             }
 
-            log.info("温湿度及气体浓度数据推送完成,设备类型:{},成功:{},失败:{}",
-                    deviceType, result.get("successCount"), result.get("failureCount"));
+            int success = result.get("successCount");
+            int failure = result.get("failureCount");
+            int notSynced = totalDevices - success - failure;
+
+            endTime = System.currentTimeMillis();
+
+            // ✅ 确保这里会被执行,且deviceType被正确记录
+            saveLog(transferVO, now, startTime, endTime, totalDevices, success, failure, notSynced, 1);
+
+            log.info("空气质量推送完成,设备类型:{},成功:{},失败:{},未同步:{}",
+                    deviceType, success, failure, notSynced);
 
             return result;
+
         } catch (Exception e) {
-            log.error("温湿度及气体浓度数据推送发生异常", e);
-            result.put("failureCount", transferVO.getDevices().size());
+            log.error("空气质量推送异常", e);
+            result.put("failureCount", totalDevices - result.get("successCount"));
+
+            endTime = System.currentTimeMillis();
+            int success = result.get("successCount");
+            int failure = result.get("failureCount");
+
+            // ✅ 异常情况下也要记录日志
+            saveLog(transferVO, now, startTime, endTime, totalDevices, success, failure,
+                    totalDevices - success - failure, 0);
+
             return result;
         }
     }
 
+    @Async("asyncServiceExecutor")
+    public void saveLog(IotDataTransferVO transferVO, LocalDateTime now, long startTime, long endTime,
+                        int total, int success, int failure, int notSynced, int pushFlag) {
+
+        Integer deviceType = transferVO.getDeviceType();
+
+        SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
+        vo.setDataType(4);
+        vo.setTotal(total);
+        vo.setSuccessNumber(success);
+        vo.setFailNumber(failure);
+        vo.setNotSynced(notSynced);
+        vo.setCreateTime(now.format(DATE_TIME_FORMATTER));
+        vo.setCostTime((float) (endTime - startTime) / 1000.0f);
+        vo.setState(1);
+        vo.setTopic(MqttTopics.getIotInfoByTypeCode(deviceType).getTopic());
+        vo.setDataTypeName(MqttTopics.getIotInfoByTypeCode(deviceType).getDesc());
+
+        CdiDeliveryLog log = new CdiDeliveryLog();
+        log.setEngineeringId(transferVO.getEngineeringId());
+        log.setDataType(4);
+        log.setTopic(vo.getTopic());
+        log.setDataTypeName(vo.getDataTypeName());
+        log.setUserName(SecurityUtils.getUsername() == null ? "自动同步" : SecurityUtils.getUsername());
+        log.setNickName(SecurityUtils.getUsername() == null ? "自动同步" : SecurityUtils.getLoginUser().getSysUser().getNickName());
+        log.setCreateTime(now);
+        log.setTenantId(getTenantId(transferVO.getEngineeringId()));
+        log.setPushFlag(pushFlag);
+        log.setInfoContent(JSON.toJSONString(vo));
+
+        cdiDeliveryLogMapper.insert(log);
+    }
+
+    private Integer getTenantId(Long engineeringId) {
+        LambdaQueryWrapper<CdiDefenseProject> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(CdiDefenseProject::getEngineeringId, engineeringId)
+                .eq(CdiDefenseProject::getIsEnable, 1);
+        CdiDefenseProject cdiDefenseProject = cdiDefenseProjectMapper.selectOne(queryWrapper);
+        if (cdiDefenseProject == null) {
+            throw new BusinessException("未找到工程信息!无法同步");
+        }
+        return cdiDefenseProject.getTenantId();
+    }
+
     /**
      * 发送人员闯入情况(703)
      *
@@ -289,6 +360,9 @@ public class IotDataTransferService {
             return result;
         }
 
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+
         try {
             List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
             Integer deviceType = transferVO.getDeviceType();
@@ -297,13 +371,21 @@ public class IotDataTransferService {
             log.info("开始推送人员闯入情况数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
                     deviceType, totalDevices, deviceData.size());
 
+            // 处理无数据情况
             if (deviceData.isEmpty()) {
                 log.warn("没有获取到人员闯入情况数据!设备类型:{}", deviceType);
                 result.put("failureCount", totalDevices);
+
+                // 记录一条汇总日志
+                long endTime = System.currentTimeMillis();
+                saveLog(transferVO, now, startTime, endTime, totalDevices,
+                        0, totalDevices, 0, 0);
                 return result;
             }
 
             Long engineeringId = transferVO.getEngineeringId();
+
+            // 遍历所有设备数据,仅执行推送,不记录日志
             for (JSONObject deviceDataItem : deviceData) {
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
                 if (dataEndTime == null) {
@@ -319,11 +401,15 @@ public class IotDataTransferService {
                 vo.setDataEndTime(dataEndTime);
                 vo.setPublishTime(getCurrentTime());
                 vo.setEngineeringID(engineeringId);
-                // 传感器值固定为0(可能是默认值或占位符)
-                vo.setSensorValue(0);
+                vo.setSensorValue(0); // 固定值(根据业务需求)
 
                 try {
-                    sendMqttMessage(EnvMonitorMqttTopic.PERSON_PRESENCE, vo, "人员闯入情况", transferVO.getUsername());
+                    sendMqttMessage(
+                            MqttTopics.IotInfo.PERSON_PRESENCE.getTopic(),
+                            vo,
+                            MqttTopics.IotInfo.PERSON_PRESENCE.getDesc(),
+                            transferVO.getUsername()
+                    );
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
                     log.warn("设备{}的人员闯入情况数据推送失败:{}", deviceId, e.getMessage());
@@ -331,13 +417,31 @@ public class IotDataTransferService {
                 }
             }
 
+            // 所有设备处理完毕,统一记录一条汇总日志
+            long endTime = System.currentTimeMillis();
+            int success = result.get("successCount");
+            int failure = result.get("failureCount");
+            int pending = totalDevices - success - failure;
+            int status = (success > 0) ? 1 : 0; // 1=部分或全部成功,0=全失败
+
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    success, failure, pending, status);
+
             log.info("人员闯入情况数据推送完成,设备类型:{},成功:{},失败:{}",
-                    deviceType, result.get("successCount"), result.get("failureCount"));
+                    deviceType, success, failure);
 
             return result;
+
         } catch (Exception e) {
             log.error("人员闯入情况数据推送发生异常", e);
-            result.put("failureCount", transferVO.getDevices().size());
+            int totalDevices = transferVO.getDevices() != null ? transferVO.getDevices().size() : 0;
+            result.put("failureCount", totalDevices);
+
+            // 异常时也记录一条日志
+            long endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    0, totalDevices, 0, 0);
+
             return result;
         }
     }
@@ -356,21 +460,32 @@ public class IotDataTransferService {
             return result;
         }
 
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+
         try {
             List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
             Integer deviceType = transferVO.getDeviceType();
-            Integer totalDevices = transferVO.getDevices().size();
+            Integer totalDevices = (transferVO.getDevices() != null) ? transferVO.getDevices().size() : 0;
 
             log.info("开始推送人防用电负荷情况数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
                     deviceType, totalDevices, deviceData.size());
 
+            // 处理无数据情况
             if (deviceData.isEmpty()) {
                 log.warn("没有获取到人防用电负荷情况数据!设备类型:{}", deviceType);
                 result.put("failureCount", totalDevices);
+
+                // 记录一条汇总日志
+                long endTime = System.currentTimeMillis();
+                saveLog(transferVO, now, startTime, endTime, totalDevices,
+                        0, totalDevices, 0, 0);
                 return result;
             }
 
             Long engineeringId = transferVO.getEngineeringId();
+
+            // 遍历设备数据,仅执行推送,不记录日志
             for (JSONObject deviceDataItem : deviceData) {
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
                 if (dataEndTime == null) {
@@ -379,6 +494,7 @@ public class IotDataTransferService {
                 }
 
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
+
                 ElectricityLoadVO vo = new ElectricityLoadVO();
                 vo.setDataPacketID(generateDataPacketID());
                 vo.setSensorID(deviceId);
@@ -397,12 +513,18 @@ public class IotDataTransferService {
                 vo.setLeakageCurrent(deviceDataItem.getFloat("leakageCurrent"));
 
                 // 根据模拟模式选择不同的功率字段
-                vo.setTotalPower(simulation ?
-                        deviceDataItem.getFloat("totalPower") :
-                        deviceDataItem.getFloat("active_power"));
+                Float totalPower = simulation
+                        ? deviceDataItem.getFloat("totalPower")
+                        : deviceDataItem.getFloat("active_power");
+                vo.setTotalPower(totalPower);
 
                 try {
-                    sendMqttMessage(EnvMonitorMqttTopic.ELECTRICITY_LOAD, vo, "人防用电负荷情况", transferVO.getUsername());
+                    sendMqttMessage(
+                            MqttTopics.IotInfo.ELECTRICITY_LOAD.getTopic(),
+                            vo,
+                            MqttTopics.IotInfo.ELECTRICITY_LOAD.getDesc(),
+                            transferVO.getUsername()
+                    );
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
                     log.warn("设备{}的人防用电负荷情况数据推送失败:{}", deviceId, e.getMessage());
@@ -410,13 +532,31 @@ public class IotDataTransferService {
                 }
             }
 
+            // 所有设备处理完毕,统一记录一条汇总日志
+            long endTime = System.currentTimeMillis();
+            int success = result.get("successCount");
+            int failure = result.get("failureCount");
+            int pending = totalDevices - success - failure;
+            int status = (success > 0) ? 1 : 0; // 1=有成功,0=全失败
+
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    success, failure, pending, status);
+
             log.info("人防用电负荷情况数据推送完成,设备类型:{},成功:{},失败:{}",
-                    deviceType, result.get("successCount"), result.get("failureCount"));
+                    deviceType, success, failure);
 
             return result;
+
         } catch (Exception e) {
             log.error("人防用电负荷情况数据推送发生异常", e);
-            result.put("failureCount", transferVO.getDevices().size());
+            int totalDevices = (transferVO.getDevices() != null) ? transferVO.getDevices().size() : 0;
+            result.put("failureCount", totalDevices);
+
+            // 异常时也记录一条日志
+            long endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    0, totalDevices, 0, 0);
+
             return result;
         }
     }
@@ -443,7 +583,7 @@ public class IotDataTransferService {
         tempVO.setPublishTime(getCurrentTime());
         tempVO.setSensorValue(value);
         tempVO.setDataEndTime(dataEndTime);
-        sendMqttMessage(EnvMonitorMqttTopic.TEMP, tempVO, "温度信息", username);
+        sendMqttMessage(MqttTopics.IotInfo.TEMP.getTopic(), tempVO, MqttTopics.IotInfo.TEMP.getDesc(), username);
     }
 
     /**
@@ -468,7 +608,7 @@ public class IotDataTransferService {
         humidityVO.setPublishTime(getCurrentTime());
         humidityVO.setSensorValue(value);
         humidityVO.setDataEndTime(dataEndTime);
-        sendMqttMessage(EnvMonitorMqttTopic.HUMIDITY, humidityVO, "湿度信息", username);
+        sendMqttMessage(MqttTopics.IotInfo.HUMIDITY.getTopic(), humidityVO, MqttTopics.IotInfo.HUMIDITY.getDesc(), username);
     }
 
     /**
@@ -493,7 +633,7 @@ public class IotDataTransferService {
         oxygenVO.setPublishTime(getCurrentTime());
         oxygenVO.setSensorValue(value);
         oxygenVO.setDataEndTime(dataEndTime);
-        sendMqttMessage(EnvMonitorMqttTopic.OXYGEN, oxygenVO, "氧气浓度信息", username);
+        sendMqttMessage(MqttTopics.IotInfo.OXYGEN.getTopic(), oxygenVO, MqttTopics.IotInfo.OXYGEN.getDesc(), username);
     }
 
     /**
@@ -518,7 +658,7 @@ public class IotDataTransferService {
         coVO.setPublishTime(getCurrentTime());
         coVO.setSensorValue(value);
         coVO.setDataEndTime(dataEndTime);
-        sendMqttMessage(EnvMonitorMqttTopic.CO, coVO, "一氧化碳浓度信息", username);
+        sendMqttMessage(MqttTopics.IotInfo.CO.getTopic(), coVO, MqttTopics.IotInfo.CO.getDesc(), username);
     }
 
     /**
@@ -543,7 +683,7 @@ public class IotDataTransferService {
         co2VO.setPublishTime(getCurrentTime());
         co2VO.setSensorValue(value);
         co2VO.setDataEndTime(dataEndTime);
-        sendMqttMessage(EnvMonitorMqttTopic.CO2, co2VO, "二氧化碳浓度信息", username);
+        sendMqttMessage(MqttTopics.IotInfo.CO2.getTopic(), co2VO, MqttTopics.IotInfo.CO2.getDesc(), username);
     }
 
     /**
@@ -704,10 +844,15 @@ public class IotDataTransferService {
             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 {
-            List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
-            Integer deviceType = transferVO.getDeviceType();
-            Integer totalDevices = transferVO.getDevices().size();
 
             log.info("开始推送位移数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
                     deviceType, totalDevices, deviceData.size());
@@ -743,7 +888,7 @@ public class IotDataTransferService {
                 vo.setSensorValue(value == 0 ? 0 : 1);
 
                 try {
-                    sendMqttMessage(EnvMonitorMqttTopic.DEVIATION, vo, "位移信息", transferVO.getUsername());
+                    sendMqttMessage(MqttTopics.IotInfo.DEVIATION.getTopic(), vo, MqttTopics.IotInfo.DEVIATION.getDesc(), transferVO.getUsername());
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
                     log.warn("设备{}的位移数据推送失败:{}", deviceId, e.getMessage());
@@ -754,10 +899,18 @@ public class IotDataTransferService {
             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);
             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);
             return result;
         }
     }
@@ -878,6 +1031,7 @@ public class IotDataTransferService {
         log.info("总共涉及产品类型数:{}个,产品代码为:{}", totalProductTypes, codeDeviceUuidsMap.keySet());
         log.info("总共推送设备数量:{}个,成功:{}个,失败:{}个",
                 totalDevices, totalSuccessCount, totalFailureCount);
+
     }
 
     /**
@@ -931,7 +1085,7 @@ public class IotDataTransferService {
             // 存储到映射中
             mqttGatewayMap.put(username, gateway);
             log.info("MQTT连接创建/刷新成功,用户名:{}", username);
-            } catch (Exception e) {
+        } catch (Exception e) {
             log.error("初始化MQTT连接失败: {}", e.getMessage(), e);
             throw new RuntimeException("初始化MQTT连接失败", e);
         }
@@ -962,7 +1116,7 @@ public class IotDataTransferService {
      * @return 解析后的时间,如果解析失败返回null
      */
     private LocalDateTime parseDataTime(JSONObject deviceDataItem) {
-        log.warn("解析的json{}", deviceDataItem.toString());
+        // log.warn("解析的json{}", deviceDataItem.toString());
         Long dataTime = deviceDataItem.getLong("realtime");
         if (dataTime == null) {
             log.warn("设备{}的time为空", deviceDataItem.getString("device_id"));
@@ -973,14 +1127,13 @@ public class IotDataTransferService {
 
     /**
      * 发送MQTT消息
-     * @param topicEnum 主题枚举
+     * @param topic MQTT topic
      * @param vo 消息对象
      * @param messageType 消息类型描述
      * @param username 用户名
      */
-    private void sendMqttMessage(EnvMonitorMqttTopic topicEnum, Object vo, String messageType, String username) {
+    void sendMqttMessage(String topic, Object vo, String messageType, String username) {
         String json = JSON.toJSONString(vo);
-        String topic = topicEnum.getTopic();
         // 不再记录每条数据的详情,只记录发送操作
         MqttConnectionTool.MqttGateway gateway = mqttGatewayMap.get(username);
         if (gateway != null) {

+ 20 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/SysUserServiceImpl.java

@@ -0,0 +1,20 @@
+package com.usky.cdi.service.impl;
+
+import com.usky.cdi.domain.SysUser;
+import com.usky.cdi.mapper.SysUserMapper;
+import com.usky.cdi.service.SysUserService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 用户信息表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-04
+ */
+@Service
+public class SysUserServiceImpl extends AbstractCrudService<SysUserMapper, SysUser> implements SysUserService {
+
+}

+ 4 - 7
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/mqtt/MqttConnectionTool.java

@@ -3,12 +3,9 @@ package com.usky.cdi.service.mqtt;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.support.GenericApplicationContext;
-import org.springframework.integration.endpoint.EventDrivenConsumer;
 import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
 import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
 import org.springframework.integration.mqtt.support.MqttHeaders;
-import org.springframework.messaging.MessageHandler;
-import org.springframework.messaging.SubscribableChannel;
 import org.springframework.integration.annotation.MessagingGateway;
 import org.springframework.messaging.handler.annotation.Header;
 import org.springframework.stereotype.Component;
@@ -136,7 +133,7 @@ public class MqttConnectionTool {
 
                 @Override
                 public void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
-                                        @Header(MqttHeaders.QOS) int qos, String payload) {
+                                       @Header(MqttHeaders.QOS) int qos, String payload) {
                     try {
                         handler.handleMessage(org.springframework.messaging.support.MessageBuilder
                                 .withPayload(payload)
@@ -162,7 +159,7 @@ public class MqttConnectionTool {
 
     private org.eclipse.paho.client.mqttv3.MqttConnectOptions
     buildOptions(String u, String p, String url) {
-        org.eclipse.paho.client.mqttv3.MqttConnectOptions opt = 
+        org.eclipse.paho.client.mqttv3.MqttConnectOptions opt =
                 new org.eclipse.paho.client.mqttv3.MqttConnectOptions();
         opt.setServerURIs(new String[]{url});
         opt.setUserName(u);
@@ -187,7 +184,7 @@ public class MqttConnectionTool {
             context.removeBeanDefinition(connectionInfo.handlerBeanName);
             log.info("已移除旧的 MQTT 处理器 -> {}", connectionInfo.handlerBeanName);
         }
-        
+
         // 从单例缓存中移除旧的 Handler
         if (context.getDefaultListableBeanFactory().containsSingleton(connectionInfo.handlerBeanName)) {
             context.getDefaultListableBeanFactory().destroySingleton(connectionInfo.handlerBeanName);
@@ -198,7 +195,7 @@ public class MqttConnectionTool {
             context.removeBeanDefinition(connectionInfo.factoryBeanName);
             log.info("已移除旧的 MQTT 工厂 -> {}", connectionInfo.factoryBeanName);
         }
-        
+
         // 从单例缓存中移除旧的 Factory
         if (context.getDefaultListableBeanFactory().containsSingleton(connectionInfo.factoryBeanName)) {
             context.getDefaultListableBeanFactory().destroySingleton(connectionInfo.factoryBeanName);

+ 197 - 127
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java

@@ -111,7 +111,7 @@ public class DeviceDataQuery {
             // 若返回数据为空且开启模拟模式,则生成模拟数据
             if (resultList.isEmpty() && simulation) {
                 log.info("接口返回数据为空,生成模拟数据,设备类型:{}", transferVO.getDeviceType());
-                resultList = generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices());
+                resultList = generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices(), null);
             } else if (resultList.size() < transferVO.getDevices().size()) {
                 log.warn("接口返回数据数量与请求数量不一致,设备类型:{}", transferVO.getDeviceType());
 
@@ -131,16 +131,14 @@ public class DeviceDataQuery {
                         .filter(deviceId -> !returnedDeviceIds.contains(deviceId))
                         .collect(Collectors.toList());
 
-                if (!missingDeviceIds.isEmpty() && simulation) {
-                    log.info("发现缺少的设备数据,生成模拟数据,设备数量:{}", missingDeviceIds.size());
-                    // 为缺少的设备生成模拟数据
-                    List<DmpDevice> missingDevices = missingDeviceIds.stream()
-                            .map(requestDeviceMap::get)
-                            .collect(Collectors.toList());
-                    List<JSONObject> missingSimulationData = generateSimulationData(transferVO.getDeviceType(), missingDevices);
-                    // 将模拟数据与返回数据结合
-                    resultList.addAll(missingSimulationData);
-                }
+                log.info("发现缺少的设备数据,生成模拟数据,设备数量:{}", missingDeviceIds.size());
+                // 为缺少的设备生成模拟数据
+                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()
@@ -162,7 +160,7 @@ public class DeviceDataQuery {
             // 异常情况下若开启模拟模式,则生成模拟数据
             if (simulation) {
                 log.info("接口调用异常,生成模拟数据,设备类型:{}", transferVO.getDeviceType());
-                return generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices());
+                return generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices(), null);
             }
             return Collections.emptyList();
         }
@@ -286,134 +284,206 @@ public class DeviceDataQuery {
      * @param devices 设备列表
      * @return 模拟数据列表
      */
-    private List<JSONObject> generateSimulationData(Integer deviceType, List<DmpDevice> devices) {
-
-        Map<String, Double> stringDoubleMap = WeatherFetcher.fetchWeather();
-
+    private List<JSONObject> generateSimulationData(Integer deviceType, List<DmpDevice> devices, JSONObject standard) {
         List<JSONObject> simulationList = new ArrayList<>();
         long currentTime = System.currentTimeMillis();
 
-        for (DmpDevice device : devices) {
-            JSONObject simulationData = new JSONObject();
-            simulationData.put("realtime", currentTime);
-            simulationData.put("device_id", device.getDeviceId());
+        // 获取天气数据(仅在需要时)
+        Map<String, Double> weatherData = null;
+        if (standard == null && (deviceType == 707 || deviceType == 704)) {
+            weatherData = WeatherFetcher.fetchWeather();
+        }
 
-            if (deviceType == null) {
-                log.warn("生成模拟数据失败:设备类型为空,设备ID:{}", device.getId());
-                continue;
-            }
+        for (DmpDevice device : devices) {
+            JSONObject simData;
+
+            if (standard != null) {
+                // ✅ 使用标准模板进行模拟(深拷贝)
+                simData = JSONObject.parseObject(standard.toJSONString());
+                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());
+                }
 
-            switch (deviceType) {
-                // 单一温度传感器(707)
-                case 707:
-                    double temp707 = 0.0;
-                    if (stringDoubleMap.isEmpty()) {
-                        temp707 = ThreadLocalRandom.current().nextDouble(TEMP_RANGE_MIN, TEMP_RANGE_MAX);
-                    } else {
-                        double floating = ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN, FLOATING_RANGE_MAX);
-                        temp707 = stringDoubleMap.get("temperature") + 0.5 + floating;
-                    }
-                    simulationData.put("wd", formatNumber(temp707, FORMAT_2_2));
-                    break;
-
-                // 单一湿度传感器(708)
-                case 708:
-                    simulationData.put("sd", formatNumber(ThreadLocalRandom.current().nextDouble(HUMIDITY_RANGE_MIN, HUMIDITY_RANGE_MAX), FORMAT_2_2));
-                    break;
-
-                // 单一氧气传感器(709)
-                case 709:
-                    double o2709 = ThreadLocalRandom.current().nextDouble(OXYGEN_RANGE_MIN, OXYGEN_RANGE_MAX);
-                    simulationData.put("o2", formatNumber(o2709, FORMAT_2_2));
-                    break;
-
-                // 单一二氧化碳传感器(710)
-                case 710:
-                    double co2710 = ThreadLocalRandom.current().nextDouble(CO2_RANGE_MIN, CO2_RANGE_MAX);
-                    simulationData.put("co2", formatNumber(co2710, FORMAT_0_3));
-                    break;
-
-                // 单一一氧化碳传感器(711)
-                case 711:
-                    simulationData.put("co", 0);
-                    break;
-
-                // 水浸(702)
-                case 702:
-                    simulationData.put("leach_status", 0);
-                    break;
-
-                // 人员统计(703)
-                case 703:
-                    simulationData.put("sensorValue", 0);
-                    break;
-
-                // 电气火灾(704)
-                case 704:
-                    // A/B/C相电压:3位整数+2位小数(220.00~230.00V)
-                    double aVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
-                    simulationData.put("aVoltage", formatNumber(aVoltage, FORMAT_3_2));
-                    double bVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
-                    simulationData.put("bVoltage", formatNumber(bVoltage, FORMAT_3_2));
-                    double cVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
-                    simulationData.put("cVoltage", formatNumber(cVoltage, FORMAT_3_2));
-
-                    // A/B/C相电流:3位整数+2位小数(0.00~1.50A)
-                    String deviceId = device.getDeviceId();
-                    double aElectricity = getCurrentValue(deviceId, "a");
-                    simulationData.put("aElectricity", formatNumber(aElectricity, FORMAT_3_2));
-                    double bElectricity = getCurrentValue(deviceId, "b");
-                    simulationData.put("bElectricity", formatNumber(bElectricity, FORMAT_3_2));
-                    double cElectricity = getCurrentValue(deviceId, "c");
-                    simulationData.put("cElectricity", formatNumber(cElectricity, FORMAT_3_2));
-
-                    // 总功率:4位整数+2位小数(1.00~20.00)
-                    double totalPower = (aVoltage * aElectricity + bVoltage * bElectricity + cVoltage * cElectricity) / 1000;
-                    simulationData.put("totalPower", formatNumber(totalPower, FORMAT_4_2));
-
-                    // 线温1-4:2位整数+2位小数(20.00~50.00℃)+1.0 +0.5
-                    double lineTemp = 0.0;
-                    if (stringDoubleMap.isEmpty()) {
-                        lineTemp = ThreadLocalRandom.current().nextDouble(TEMP_LINE_RANGE_MIN, TEMP_LINE_RANGE_MAX);
-                    } else {
-                        lineTemp = stringDoubleMap.get("temperature") + 1.0;
-                    }
-                    simulationData.put("line1TEMP", formatNumber(lineTemp + ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN1, FLOATING_RANGE_MAX1), FORMAT_2_2));
-                    simulationData.put("Line2TEMP", formatNumber(lineTemp + ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN1, FLOATING_RANGE_MAX1), FORMAT_2_2));
-                    simulationData.put("Line3TEMP", formatNumber(lineTemp + ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN1, FLOATING_RANGE_MAX1), FORMAT_2_2));
-                    simulationData.put("Line4TEMP", formatNumber(lineTemp + ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN1, FLOATING_RANGE_MAX1), FORMAT_2_2));
-
-                    // 剩余电流:4位整数+2位小数(0.00~100.00mA)
-                    simulationData.put("leakageCurrent", formatNumber(ThreadLocalRandom.current().nextDouble(LEAKAGE_CURRENT_RANGE_MIN, LEAKAGE_CURRENT_RANGE_MAX), FORMAT_4_2));
-                    break;
-
-                // 倾斜(712)
-                case 712:
-                    simulationData.put("qx", 0);
-                    break;
-
-                // 裂缝(713)
-                case 713:
-                    simulationData.put("cd", 0);
-                    break;
-
-                // 位移(714)
-                case 714:
-                    simulationData.put("wy", 0);
-                    break;
-
-                default:
-                    log.warn("未知设备类型:{},无法生成模拟数据", deviceType);
-                    break;
+                // 对数值字段添加微小扰动(±2% 或固定范围)
+                addNoiseToNumericFields(simData, deviceType);
+
+            } else {
+                // ❌ 无标准模板,走原始随机逻辑
+                simData = new JSONObject();
+                simData.put("realtime", currentTime);
+                simData.put("device_id", device.getDeviceId());
+
+                switch (deviceType) {
+                    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;
+                        } else {
+                            temp707 = ThreadLocalRandom.current().nextDouble(TEMP_RANGE_MIN, TEMP_RANGE_MAX);
+                        }
+                        simData.put("wd", formatNumber(temp707, FORMAT_2_2));
+                        break;
+
+                    case 708:
+                        simData.put("sd", formatNumber(ThreadLocalRandom.current().nextDouble(HUMIDITY_RANGE_MIN, HUMIDITY_RANGE_MAX), FORMAT_2_2));
+                        break;
+
+                    case 709:
+                        double o2709 = ThreadLocalRandom.current().nextDouble(OXYGEN_RANGE_MIN, OXYGEN_RANGE_MAX);
+                        simData.put("o2", formatNumber(o2709, FORMAT_2_2));
+                        break;
+
+                    case 710:
+                        double co2710 = ThreadLocalRandom.current().nextDouble(CO2_RANGE_MIN, CO2_RANGE_MAX);
+                        simData.put("co2", formatNumber(co2710, FORMAT_0_3));
+                        break;
+
+                    case 711:
+                        simData.put("co", 0);
+                        break;
+
+                    case 702:
+                        simData.put("leach_status", 0);
+                        break;
+
+                    case 703:
+                        simData.put("sensorValue", 0);
+                        break;
+
+                    case 704:
+                        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);
+                        simData.put("bVoltage", formatNumber(bVoltage, FORMAT_3_2));
+                        double cVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
+                        simData.put("cVoltage", formatNumber(cVoltage, FORMAT_3_2));
+
+                        String deviceId = device.getDeviceId();
+                        double aElectricity = getCurrentValue(deviceId, "a");
+                        simData.put("aElectricity", formatNumber(aElectricity, FORMAT_3_2));
+                        double bElectricity = getCurrentValue(deviceId, "b");
+                        simData.put("bElectricity", formatNumber(bElectricity, FORMAT_3_2));
+                        double cElectricity = getCurrentValue(deviceId, "c");
+                        simData.put("cElectricity", formatNumber(cElectricity, FORMAT_3_2));
+
+                        double totalPower = (aVoltage * aElectricity + bVoltage * bElectricity + cVoltage * cElectricity) / 1000;
+                        simData.put("totalPower", formatNumber(totalPower, FORMAT_4_2));
+
+                        double lineTemp = 0.0;
+                        if (weatherData != null && !weatherData.isEmpty()) {
+                            lineTemp = weatherData.get("temperature") + 1.0;
+                        } else {
+                            lineTemp = ThreadLocalRandom.current().nextDouble(TEMP_LINE_RANGE_MIN, TEMP_LINE_RANGE_MAX);
+                        }
+                        simData.put("line1TEMP", formatNumber(lineTemp + ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN1, FLOATING_RANGE_MAX1), FORMAT_2_2));
+                        simData.put("Line2TEMP", formatNumber(lineTemp + ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN1, FLOATING_RANGE_MAX1), FORMAT_2_2));
+                        simData.put("Line3TEMP", formatNumber(lineTemp + ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN1, FLOATING_RANGE_MAX1), FORMAT_2_2));
+                        simData.put("Line4TEMP", formatNumber(lineTemp + ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN1, FLOATING_RANGE_MAX1), FORMAT_2_2));
+
+                        simData.put("leakageCurrent", formatNumber(ThreadLocalRandom.current().nextDouble(LEAKAGE_CURRENT_RANGE_MIN, LEAKAGE_CURRENT_RANGE_MAX), FORMAT_4_2));
+                        break;
+
+                    case 712:
+                        simData.put("qx", 0);
+                        break;
+
+                    case 713:
+                        simData.put("cd", 0);
+                        break;
+
+                    case 714:
+                        simData.put("wy", 0);
+                        break;
+
+                    default:
+                        log.warn("未知设备类型:{},无法生成模拟数据", deviceType);
+                        continue;
+                }
             }
 
-            simulationList.add(simulationData);
+            simulationList.add(simData);
         }
 
         log.info("生成模拟数据完成,设备类型:{},数量:{}", deviceType, simulationList.size());
         return simulationList;
     }
 
+    private void addNoiseToNumericFields(JSONObject data, Integer deviceType) {
+        // 定义各字段的噪声比例或范围(可根据设备类型定制)
+        Map<String, Double> noiseConfig = getNoiseConfigByDeviceType(deviceType);
+
+        for (String key : data.keySet()) {
+            Object value = data.get(key);
+            if (value instanceof Number) {
+                double original = ((Number) value).doubleValue();
+                // 默认 ±2%
+                double noiseFactor = noiseConfig.getOrDefault(key, 0.02);
+
+                // 避免对状态类字段(如 0/1)加噪
+                if (isDiscreteField(key)) {
+                    continue;
+                }
+
+                double noise = original * noiseFactor * (ThreadLocalRandom.current().nextDouble(-1, 1));
+                double newValue = original + noise;
+
+                // 根据原格式保留小数位(这里简化处理,也可记录原格式)
+                data.put(key, newValue);
+            }
+        }
+    }
+
+    private boolean isDiscreteField(String field) {
+        // 这些字段是状态码,不应加噪
+        return field.equalsIgnoreCase("leach_status")
+                || field.equalsIgnoreCase("sensorValue")
+                || field.equalsIgnoreCase("qx")
+                || field.equalsIgnoreCase("cd")
+                || field.equalsIgnoreCase("wy")
+                || field.equalsIgnoreCase("co");
+    }
+
+    // 获取各字段的浮动模拟配置
+    private Map<String, Double> getNoiseConfigByDeviceType(Integer deviceType) {
+        Map<String, Double> config = new HashMap<>();
+        switch (deviceType) {
+            case 707: // 温度
+                config.put("wd", 0.03); // ±3%
+                break;
+            case 708: // 湿度
+                config.put("sd", 0.05);
+                break;
+            case 709: // 氧气
+                config.put("o2", 0.02);
+                break;
+            case 710: // CO2
+                config.put("co2", 0.02);
+                break;
+            case 704: // 电气火灾
+                config.put("aVoltage", 0.01);
+                config.put("bVoltage", 0.01);
+                config.put("cVoltage", 0.01);
+                config.put("aElectricity", 0.05);
+                config.put("bElectricity", 0.05);
+                config.put("cElectricity", 0.05);
+                config.put("totalPower", 0.05);
+                config.put("line1TEMP", 0.03);
+                config.put("Line2TEMP", 0.03);
+                config.put("Line3TEMP", 0.03);
+                config.put("Line4TEMP", 0.03);
+                config.put("leakageCurrent", 0.1); // 电流波动大些
+                break;
+            default:
+                // 默认所有数值字段 ±2%
+                break;
+        }
+        return config;
+    }
+
     /**
      * 通用数值格式化方法
      * @param value 原始数值

+ 68 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/SyncTaskStatisticsVO.java

@@ -0,0 +1,68 @@
+package com.usky.cdi.service.vo;
+
+import lombok.Data;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/2/3
+ */
+@Data
+public class SyncTaskStatisticsVO {
+
+    /**
+     * 同步任务ID
+     */
+    private Long id;
+
+    /**
+     * 同步类型名称
+     */
+    private String dataTypeName;
+
+    /**
+     * 同步类型(1:单元数据,2:楼层平面图,3:物联设施,4:监测数据, 5:告警数据)
+     */
+    private Integer dataType;
+
+    /**
+     * 同步Topic
+     */
+    private String topic;
+
+    /**
+     * 同步总数
+     */
+    private Integer total;
+
+    /**
+     * 同步成功数量
+     */
+    private Integer successNumber;
+
+    /**
+     * 同步失败数量
+     */
+    private Integer failNumber;
+
+    /**
+     * 未同步数量
+     */
+    private Integer notSynced;
+
+    /**
+     * 同步时间(格式:yyyy-MM-dd HH:mm:ss)
+     */
+    private String createTime;
+
+    /**
+     * 耗时(单位:秒)
+     */
+    private float costTime;
+
+    /**
+     * 状态(0:未同步,1:已同步)
+     */
+    private Integer state;
+}

+ 2 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/Co2VO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/Co2VO.java

@@ -1,5 +1,6 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 

+ 2 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/CoVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/CoVO.java

@@ -1,5 +1,6 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 

+ 2 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/CrackVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/CrackVO.java

@@ -1,5 +1,6 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 
 /**

+ 2 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/DeviationVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/DeviationVO.java

@@ -1,5 +1,6 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 
 /**

+ 2 - 3
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/ElectricityLoadVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ElectricityLoadVO.java

@@ -1,10 +1,9 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-import java.io.Serializable;
-
 /**
  * 人防用电负荷推送VO(兼容2021版)
  * 用途:每半小时上报配电箱监测数据(电压、电流、功率等)

+ 1 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/EngineeringBaseVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/EngineeringBaseVO.java

@@ -1,4 +1,4 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
 import lombok.Data;
 import java.io.Serializable;

+ 1 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/ErrorMsgVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ErrorMsgVO.java

@@ -1,4 +1,4 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
 import lombok.Data;
 import java.io.Serializable;

+ 1 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/FacilityDeviceVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FacilityDeviceVO.java

@@ -1,4 +1,4 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
 import lombok.Data;
 import java.io.Serializable;

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

@@ -1,12 +1,13 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
 import lombok.Data;
+
 import java.io.Serializable;
 
 /**
  * 楼层平面图信息VO
  * Topic: base/floorPlane
- * 
+ *
  * @author han
  * @date 2025/03/20
  */

+ 2 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/HumidityVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/HumidityVO.java

@@ -1,5 +1,6 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 

+ 2 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/OxygenVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/OxygenVO.java

@@ -1,5 +1,6 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 

+ 2 - 3
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/PersonPresenceVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/PersonPresenceVO.java

@@ -1,10 +1,9 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-import java.io.Serializable;
-
 /**
  * 人员闯入情况推送VO(兼容2021版)
  * 用途:每半小时上报最新人员闯入状态,设防时段闯入需同步作为告警事件上传

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

@@ -1,13 +1,14 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
 import lombok.Data;
+
 import java.io.Serializable;
 import java.math.BigDecimal;
 
 /**
  * 防护单元基础信息VO
  * Topic: base/protectiveUnit
- * 
+ *
  * @author han
  * @date 2025/03/20
  */

+ 1 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/SensorInfoVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/SensorInfoVO.java

@@ -1,4 +1,4 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
 import lombok.Data;
 import java.io.Serializable;

+ 2 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/TempVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/TempVO.java

@@ -1,5 +1,6 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 

+ 2 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/TiltVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/TiltVO.java

@@ -1,5 +1,6 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 
 /**

+ 2 - 3
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/WaterLeakVO.java → service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/WaterLeakVO.java

@@ -1,10 +1,9 @@
-package com.usky.cdi.service.vo.base;
+package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-import java.io.Serializable;
-
 /**
  * 水浸状态定时上报 VO(兼容2021版)
  * 用途:每半小时上报传感器最新水浸状态

+ 46 - 0
service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildMapper.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.cdi.mapper.BaseBuildMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.cdi.domain.BaseBuild">
+        <id column="id" property="id" />
+        <result column="build_num" property="buildNum" />
+        <result column="build_name" property="buildName" />
+        <result column="address" property="address" />
+        <result column="model_address" property="modelAddress" />
+        <result column="above_floor" property="aboveFloor" />
+        <result column="under_floor" property="underFloor" />
+        <result column="build_area" property="buildArea" />
+        <result column="cover_area" property="coverArea" />
+        <result column="fire_rating" property="fireRating" />
+        <result column="use_character" property="useCharacter" />
+        <result column="build_structure" property="buildStructure" />
+        <result column="build_high" property="buildHigh" />
+        <result column="high_type" property="highType" />
+        <result column="complete_year" property="completeYear" />
+        <result column="safe_person" property="safePerson" />
+        <result column="manage_person" property="managePerson" />
+        <result column="fire_risk" property="fireRisk" />
+        <result column="fire_control_room" property="fireControlRoom" />
+        <result column="build_inside" property="buildInside" />
+        <result column="build_plan" property="buildPlan" />
+        <result column="facility_id" property="facilityId" />
+        <result column="bim_url" property="bimUrl" />
+        <result column="contact_phone" property="contactPhone" />
+        <result column="build_desc" property="buildDesc" />
+        <result column="unit_count" property="unitCount" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="create_by" property="createBy" />
+        <result column="delete_flag" property="deleteFlag" />
+        <result column="under_space" property="underSpace" />
+        <result column="fireproof_coat" property="fireproofCoat" />
+        <result column="dept_id" property="deptId" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="longitude" property="longitude" />
+        <result column="latitude" property="latitude" />
+    </resultMap>
+
+</mapper>

+ 15 - 0
service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildPlaneMapper.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.cdi.mapper.BaseBuildPlaneMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.cdi.domain.BaseBuildPlane">
+        <id column="id" property="id" />
+        <result column="build_id" property="buildId" />
+        <result column="floor" property="floor" />
+        <result column="plane_view_url" property="planeViewUrl" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+    </resultMap>
+
+</mapper>

+ 24 - 0
service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildUnitMapper.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.cdi.mapper.BaseBuildUnitMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.cdi.domain.BaseBuildUnit">
+        <id column="id" property="id" />
+        <result column="unit_name" property="unitName" />
+        <result column="unit_main_exit" property="unitMainExit" />
+        <result column="unit_other_exit" property="unitOtherExit" />
+        <result column="unit_area" property="unitArea" />
+        <result column="floor" property="floor" />
+        <result column="unit_usage" property="unitUsage" />
+        <result column="people_number" property="peopleNumber" />
+        <result column="build_id" property="buildId" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+        <result column="tenant_id" property="tenantId" />
+    </resultMap>
+
+</mapper>

+ 26 - 0
service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/CdiDefenseProjectMapper.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.cdi.mapper.CdiDefenseProjectMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.cdi.domain.CdiDefenseProject">
+        <id column="id" property="id" />
+        <result column="engineering_name" property="engineeringName" />
+        <result column="engineering_id" property="engineeringId" />
+        <result column="passwd" property="passwd" />
+        <result column="push_address" property="pushAddress" />
+        <result column="mqtt_user_name" property="mqttUserName" />
+        <result column="mqtt_password" property="mqttPassword" />
+        <result column="mqtt_client_id" property="mqttClientId" />
+        <result column="base_topic" property="baseTopic" />
+        <result column="iotInfo_topic" property="iotinfoTopic" />
+        <result column="alarm_topic" property="alarmTopic" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="is_enable" property="isEnable" />
+        <result column="tenant_id" property="tenantId" />
+    </resultMap>
+
+</mapper>

+ 18 - 0
service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/CdiDeliveryLogMapper.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.cdi.mapper.CdiDeliveryLogMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.cdi.domain.CdiDeliveryLog">
+        <id column="id" property="id" />
+        <result column="engineering_id" property="engineeringId" />
+        <result column="data_type" property="dataType" />
+        <result column="topic" property="topic" />
+        <result column="data_content" property="dataContent" />
+        <result column="push_flag" property="pushFlag" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="tenant_id" property="tenantId" />
+    </resultMap>
+
+</mapper>

+ 31 - 0
service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/SysUserMapper.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.cdi.mapper.SysUserMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.cdi.domain.SysUser">
+        <id column="user_id" property="userId" />
+        <result column="dept_id" property="deptId" />
+        <result column="user_name" property="userName" />
+        <result column="nick_name" property="nickName" />
+        <result column="user_type" property="userType" />
+        <result column="email" property="email" />
+        <result column="phonenumber" property="phonenumber" />
+        <result column="sex" property="sex" />
+        <result column="full_name" property="fullName" />
+        <result column="avatar" property="avatar" />
+        <result column="password" property="password" />
+        <result column="status" property="status" />
+        <result column="del_flag" property="delFlag" />
+        <result column="login_ip" property="loginIp" />
+        <result column="login_date" property="loginDate" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="remark" property="remark" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="address" property="address" />
+    </resultMap>
+
+</mapper>

+ 11 - 0
service-fire/service-fire-biz/src/main/java/com/usky/fire/controller/web/PatrolInspectionPlanController.java

@@ -112,5 +112,16 @@ public class PatrolInspectionPlanController {
         return ApiResult.success(patrolInspectionPlanService.patrolInspectionSiteVoList(planId, areaId));
     }
 
+    /**
+     * 巡查自检-巡检子计划-手动生成
+     *
+     * @return
+     */
+    @GetMapping("addPlanSon")
+    public ApiResult<Void> addPlanSon() {
+        patrolInspectionPlanService.addPatrolInspectionPlanSon();
+        return ApiResult.success();
+    }
+
 }
 

+ 2 - 2
service-fire/service-fire-biz/src/main/java/com/usky/fire/service/impl/PatrolInspectionAreaServiceImpl.java

@@ -176,6 +176,8 @@ public class PatrolInspectionAreaServiceImpl extends AbstractCrudService<PatrolI
             siteNum = patrolInspectionSiteMapper.selectSiteCount(areaIdList);
         }
         if (siteNum>0){
+            throw new BusinessException("区域下有绑定的点位,请解绑后再删除!");
+        }else {
             for (int i = 0; i < patrolInspectionArealist.size(); i++) {
                 PatrolInspectionArea patrolInspectionAreaz = new PatrolInspectionArea();
                 patrolInspectionAreaz.setId(patrolInspectionArealist.get(i).getId());
@@ -186,8 +188,6 @@ public class PatrolInspectionAreaServiceImpl extends AbstractCrudService<PatrolI
             patrolInspectionArea.setId(id);
             patrolInspectionArea.setEnable(0);
             this.updateById(patrolInspectionArea);
-        }else {
-            throw new BusinessException("区域下有绑定的点位,请解绑后再删除!");
         }
     }
 

+ 75 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/BaseBuildEngineeringController.java

@@ -0,0 +1,75 @@
+package com.usky.iot.controller.web;
+
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.iot.domain.BaseBuildEngineering;
+import com.usky.iot.service.BaseBuildEngineeringService;
+import com.usky.iot.service.BaseBuildEngineeringService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import org.springframework.stereotype.Controller;
+
+import java.util.List;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author han
+ * @since 2026-02-03
+ */
+@RestController
+@RequestMapping("/baseBuildEngineering")
+public class BaseBuildEngineeringController {
+
+    @Autowired
+    private BaseBuildEngineeringService baseBuildEngineeringService;
+
+    /**
+     * 新增
+     * @param baseBuildEngineering
+     * @return
+     */
+    @PostMapping
+    ApiResult<Void> add(@RequestBody BaseBuildEngineering baseBuildEngineering){
+        baseBuildEngineeringService.add(baseBuildEngineering);
+        return ApiResult.success();
+    }
+
+    /**
+     * 修改
+     * @param baseBuildEngineering
+     * @return
+     */
+    @PutMapping
+    ApiResult<Void> update(@RequestBody BaseBuildEngineering baseBuildEngineering){
+        baseBuildEngineeringService.update(baseBuildEngineering);
+        return ApiResult.success();
+    }
+
+    /**
+     * 删除
+     * @param id
+     * @return
+     */
+    @DeleteMapping("/{id}")
+    ApiResult<Void> remove(@PathVariable("id") Integer id){
+        baseBuildEngineeringService.remove(id);
+        return ApiResult.success();
+    }
+
+    /**
+     * 列表
+     * @param id
+     * @param buildId
+     * @return
+     */
+    @GetMapping("baseBuildEngineeringList")
+    ApiResult<List<BaseBuildEngineering>> baseBuildEngineeringList(@RequestParam(value = "id",required = false) Integer id,
+                                                     @RequestParam(value = "buildId") Integer buildId){
+        return ApiResult.success(baseBuildEngineeringService.baseBuildEngineeringList(id,buildId));
+    }
+}
+

+ 72 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/BaseBuildUnitController.java

@@ -0,0 +1,72 @@
+package com.usky.iot.controller.web;
+
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.iot.domain.BaseBuildUnit;
+import com.usky.iot.service.BaseBuildUnitService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+
+import java.util.List;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author han
+ * @since 2026-02-03
+ */
+@RestController
+@RequestMapping("/baseBuildUnit")
+public class BaseBuildUnitController {
+    @Autowired
+    private BaseBuildUnitService baseBuildUnitService;
+
+    /**
+     * 新增
+     * @param baseBuildUnits
+     * @return
+     */
+    @PostMapping
+    ApiResult<Void> add(@RequestBody List<BaseBuildUnit> baseBuildUnits){
+        baseBuildUnitService.add(baseBuildUnits);
+        return ApiResult.success();
+    }
+
+    /**
+     * 修改
+     * @param baseBuildUnits
+     * @return
+     */
+    @PutMapping
+    ApiResult<Void> update(@RequestBody List<BaseBuildUnit> baseBuildUnits){
+        baseBuildUnitService.update(baseBuildUnits);
+        return ApiResult.success();
+    }
+
+    /**
+     * 删除
+     * @param id
+     * @return
+     */
+    @DeleteMapping("/{id}")
+    ApiResult<Void> remove(@PathVariable("id") Integer id){
+        baseBuildUnitService.remove(id);
+        return ApiResult.success();
+    }
+
+    /**
+     * 列表
+     * @param id
+     * @param buildId
+     * @return
+     */
+    @GetMapping("baseBuildUnitList")
+    ApiResult<List<BaseBuildUnit>> baseBuildUnitList(@RequestParam(value = "id",required = false) Integer id,
+                                                     @RequestParam(value = "buildId") Integer buildId){
+        return ApiResult.success(baseBuildUnitService.baseBuildUnitList(id,buildId));
+    }
+}
+

+ 84 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/controller/web/BaseScreenController.java

@@ -0,0 +1,84 @@
+package com.usky.iot.controller.web;
+
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.iot.domain.BaseGgpFacility;
+import com.usky.iot.domain.BaseScreen;
+import com.usky.iot.domain.DmpDeviceInfo;
+import com.usky.iot.service.BaseGgpFacilityService;
+import com.usky.iot.service.BaseScreenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import org.springframework.stereotype.Controller;
+
+import java.util.List;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author han
+ * @since 2026-01-20
+ */
+@RestController
+@RequestMapping("/baseScreen")
+public class BaseScreenController {
+    @Autowired
+    private BaseScreenService baseScreenService;
+
+    /**
+     * 新增
+     * @param baseScreen
+     * @return
+     */
+    @PostMapping("/add")
+    public ApiResult<Void> add(@RequestBody BaseScreen baseScreen) {
+        baseScreenService.add(baseScreen);
+        return ApiResult.success();
+    }
+
+    /**
+     * 修改
+     * @param baseScreen
+     * @return
+     */
+    @PutMapping("/edit")
+    public ApiResult<Void> edit(@RequestBody BaseScreen baseScreen) {
+        baseScreenService.update(baseScreen);
+        return ApiResult.success();
+    }
+
+    /**
+     * 关联设备查看分页
+     * @param screenGroup  设备ID
+     * @param id  设施主键ID
+     * @return
+     */
+    @Log(title = "大屏查看列表", businessType = BusinessType.OTHER)
+    @GetMapping("/screenList")
+    public ApiResult<List<BaseScreen>> screenList(@RequestParam(value = "screenGroup", required = false) Integer screenGroup,
+                                                  @RequestParam(value = "id", required = false) Integer id) {
+        return ApiResult.success(baseScreenService.screenList(screenGroup, id));
+    }
+
+    /**
+     * 删除
+     * @param id
+     * @return
+     */
+    @Log(title = "删除大屏", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{id}")
+    public ApiResult<Void> remove(@PathVariable("id") Integer id) {
+        try {
+            return baseScreenService.removeById(id) ? ApiResult.success() : ApiResult.error("删除失败!");
+        } catch (Exception e) {
+            return ApiResult.error("删除失败:" + e.getMessage());
+        }
+    }
+}
+

+ 5 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseBuild.java

@@ -150,6 +150,11 @@ public class BaseBuild implements Serializable {
      */
     private String buildDesc;
 
+    /**
+     * 单元数量
+     */
+    private Integer unitCount;
+
     /**
      * 创建时间
      */

+ 146 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseBuildEngineering.java

@@ -0,0 +1,146 @@
+package com.usky.iot.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 fu
+ * @since 2026-02-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class BaseBuildEngineering implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 工程基础信息表
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 工程名称
+     */
+    private String engineeringName;
+
+    /**
+     * 工程地址
+     */
+    private String engineeringAddress;
+
+    /**
+     * 工程区划
+     */
+    private String engineeringArea;
+
+    /**
+     * 联审编号
+     */
+    private String reviewCode;
+
+    /**
+     * 施工许可证工程单体编号
+     */
+    private String projectCode;
+
+    /**
+     * 单元数量
+     */
+    private Integer unitCount;
+
+    /**
+     * 平时阶段用途
+     */
+    private String normalUsage;
+
+    /**
+     * 建设单位
+     */
+    private String constructionCompany;
+
+    /**
+     * 建设单位联系人
+     */
+    private String constructionCompanyPerson;
+
+    /**
+     * 建设单位联系电话
+     */
+    private String constructionCompanyPhone;
+
+    /**
+     * 总包单位
+     */
+    private String contractorCompany;
+
+    /**
+     * 总包单位联系人
+     */
+    private String contractorCompanyPerson;
+
+    /**
+     * 总包单位电话
+     */
+    private String contractorCompanyPhone;
+
+    /**
+     * 物联系统建设单位
+     */
+    private String iotCompany;
+
+    /**
+     * 物联系统联系人
+     */
+    private String iotCompanyPerson;
+
+    /**
+     * 物联系统单位电话
+     */
+    private String iotCompanyPhone;
+
+    /**
+     * 建筑ID
+     */
+    private Integer buildId;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 组织机构ID
+     */
+    private Integer deptId;
+
+    /**
+     * 租户号
+     */
+    private Integer tenantId;
+
+
+}

+ 101 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseBuildUnit.java

@@ -0,0 +1,101 @@
+package com.usky.iot.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 fu
+ * @since 2026-02-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class BaseBuildUnit implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 单元信息表
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 单元名称
+     */
+    private String unitName;
+
+    /**
+     * 单元主要出入口
+     */
+    private String unitMainExit;
+
+    /**
+     * 单元次要出入口
+     */
+    private String unitOtherExit;
+
+    /**
+     * 单元面积
+     */
+    private Double unitArea;
+
+    /**
+     * 楼层
+     */
+    private String floor;
+
+    /**
+     * 防护单元用途
+     */
+    private String unitUsage;
+
+    /**
+     * 掩蔽人数上限
+     */
+    private Integer peopleNumber;
+
+    /**
+     * 建筑ID
+     */
+    private Integer buildId;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 组织机构ID
+     */
+    private Integer deptId;
+
+    /**
+     * 租户号
+     */
+    private Integer tenantId;
+
+
+}

+ 101 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/domain/BaseScreen.java

@@ -0,0 +1,101 @@
+package com.usky.iot.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 han
+ * @since 2026-01-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class BaseScreen implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 大屏管理信息表
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 大屏编号
+     */
+    private String screenCode;
+
+    /**
+     * 大屏标题
+     */
+    private String screenTitle;
+
+    /**
+     * 副标题
+     */
+    private String screenSubtitle;
+
+    /**
+     * 分组
+     */
+    private Integer screenGroup;
+
+    /**
+     * 是否默认
+     */
+    private Integer isDefault;
+
+    /**
+     * 大屏地址
+     */
+    private String screenAddress;
+
+    /**
+     * 显示顺序
+     */
+    private Integer orderNum;
+
+    /**
+     * 缩略图URL
+     */
+    private String thumbnailUrl;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建人
+     */
+    private String createdBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createdTime;
+
+    /**
+     * 更新人
+     */
+    private String updatedBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updatedTime;
+
+    /**
+     * 租户号
+     */
+    private Integer tenantId;
+
+
+}

+ 16 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/BaseBuildEngineeringMapper.java

@@ -0,0 +1,16 @@
+package com.usky.iot.mapper;
+
+import com.usky.iot.domain.BaseBuildEngineering;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-03
+ */
+public interface BaseBuildEngineeringMapper extends CrudMapper<BaseBuildEngineering> {
+
+}

+ 16 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/BaseBuildUnitMapper.java

@@ -0,0 +1,16 @@
+package com.usky.iot.mapper;
+
+import com.usky.iot.domain.BaseBuildUnit;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-03
+ */
+public interface BaseBuildUnitMapper extends CrudMapper<BaseBuildUnit> {
+
+}

+ 16 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/mapper/BaseScreenMapper.java

@@ -0,0 +1,16 @@
+package com.usky.iot.mapper;
+
+import com.usky.iot.domain.BaseScreen;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author han
+ * @since 2026-01-20
+ */
+public interface BaseScreenMapper extends CrudMapper<BaseScreen> {
+
+}

+ 25 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/BaseBuildEngineeringService.java

@@ -0,0 +1,25 @@
+package com.usky.iot.service;
+
+import com.usky.iot.domain.BaseBuildEngineering;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.iot.domain.BaseBuildUnit;
+
+import java.util.List;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-03
+ */
+public interface BaseBuildEngineeringService extends CrudService<BaseBuildEngineering> {
+    void add(BaseBuildEngineering baseBuildEngineering);
+
+    void update(BaseBuildEngineering baseBuildEngineering);
+
+    void remove(Integer id);
+
+    List<BaseBuildEngineering> baseBuildEngineeringList(Integer id, Integer buildId);
+}

+ 25 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/BaseBuildUnitService.java

@@ -0,0 +1,25 @@
+package com.usky.iot.service;
+
+import com.usky.iot.domain.BaseBuild;
+import com.usky.iot.domain.BaseBuildUnit;
+import com.usky.common.mybatis.core.CrudService;
+
+import java.util.List;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author han
+ * @since 2026-02-03
+ */
+public interface BaseBuildUnitService extends CrudService<BaseBuildUnit> {
+    void add(List<BaseBuildUnit> baseBuildUnits);
+
+    void update(List<BaseBuildUnit> baseBuildUnits);
+
+    void remove(Integer id);
+
+    List<BaseBuildUnit> baseBuildUnitList(Integer id,Integer buildId);
+}

+ 23 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/BaseScreenService.java

@@ -0,0 +1,23 @@
+package com.usky.iot.service;
+
+import com.usky.iot.domain.BaseGgpFacility;
+import com.usky.iot.domain.BaseScreen;
+import com.usky.common.mybatis.core.CrudService;
+
+import java.util.List;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author han
+ * @since 2026-01-20
+ */
+public interface BaseScreenService extends CrudService<BaseScreen> {
+    boolean add(BaseScreen baseGgpFacility);
+
+    void update(BaseScreen baseGgpFacility);
+
+    List<BaseScreen> screenList(Integer screenGroup,Integer id);
+}

+ 59 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseBuildEngineeringServiceImpl.java

@@ -0,0 +1,59 @@
+package com.usky.iot.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.iot.domain.BaseBuildEngineering;
+import com.usky.iot.domain.BaseBuildUnit;
+import com.usky.iot.mapper.BaseBuildEngineeringMapper;
+import com.usky.iot.service.BaseBuildEngineeringService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-02-03
+ */
+@Service
+public class BaseBuildEngineeringServiceImpl extends AbstractCrudService<BaseBuildEngineeringMapper, BaseBuildEngineering> implements BaseBuildEngineeringService {
+    @Override
+    public void add(BaseBuildEngineering baseBuildEngineering){
+        baseBuildEngineering.setCreateBy(SecurityUtils.getUsername());
+        baseBuildEngineering.setCreateTime(LocalDateTime.now());
+        baseBuildEngineering.setTenantId(SecurityUtils.getTenantId());
+        this.save(baseBuildEngineering);
+    }
+
+    @Override
+    public void update(BaseBuildEngineering baseBuildEngineering){
+        baseBuildEngineering.setUpdateBy(SecurityUtils.getUsername());
+        baseBuildEngineering.setUpdateTime(LocalDateTime.now());
+        this.updateById(baseBuildEngineering);
+    }
+
+    @Override
+    public void remove(Integer id){
+
+        this.removeById(id);
+
+    }
+
+    @Override
+    public List<BaseBuildEngineering> baseBuildEngineeringList(Integer id, Integer buildId){
+        LambdaQueryWrapper<BaseBuildEngineering> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(!ObjectUtils.isEmpty(id),BaseBuildEngineering::getId,id)
+                .eq(BaseBuildEngineering::getBuildId,buildId)
+                .eq(BaseBuildEngineering::getTenantId,SecurityUtils.getTenantId())
+                .orderByDesc(BaseBuildEngineering::getId);
+        List<BaseBuildEngineering> list = this.list(queryWrapper);
+        return list;
+    }
+}

+ 83 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseBuildUnitServiceImpl.java

@@ -0,0 +1,83 @@
+package com.usky.iot.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.iot.domain.BaseBuild;
+import com.usky.iot.domain.BaseBuildFacilityRelate;
+import com.usky.iot.domain.BaseBuildUnit;
+import com.usky.iot.domain.DmpDeviceInfo;
+import com.usky.iot.mapper.BaseBuildUnitMapper;
+import com.usky.iot.service.BaseBuildUnitService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author han
+ * @since 2026-02-03
+ */
+@Service
+public class BaseBuildUnitServiceImpl extends AbstractCrudService<BaseBuildUnitMapper, BaseBuildUnit> implements BaseBuildUnitService {
+    @Override
+    public void add(List<BaseBuildUnit> baseBuildUnits){
+        if(CollectionUtils.isNotEmpty(baseBuildUnits)){
+            for(int i=0;i<baseBuildUnits.size();i++){
+                BaseBuildUnit baseBuildUnit = baseBuildUnits.get(i);
+                baseBuildUnit.setCreateBy(SecurityUtils.getUsername());
+                baseBuildUnit.setCreateTime(LocalDateTime.now());
+                baseBuildUnit.setTenantId(SecurityUtils.getTenantId());
+                this.save(baseBuildUnit);
+            }
+        }else {
+            throw new BusinessException("提交内容不能为空");
+        }
+    }
+
+    @Override
+    public void update(List<BaseBuildUnit> baseBuildUnits){
+        if(CollectionUtils.isNotEmpty(baseBuildUnits)){
+            this.remove(1);
+            for(int i=0;i<baseBuildUnits.size();i++){
+                BaseBuildUnit baseBuildUnit = baseBuildUnits.get(i);
+                baseBuildUnit.setUpdateBy(SecurityUtils.getUsername());
+                baseBuildUnit.setUpdateTime(LocalDateTime.now());
+                baseBuildUnit.setTenantId(SecurityUtils.getTenantId());
+                this.save(baseBuildUnit);
+            }
+        }else {
+            throw new BusinessException("提交内容不能为空");
+        }
+
+
+    }
+
+    @Override
+    public void remove(Integer id){
+        LambdaQueryWrapper<BaseBuildUnit> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(BaseBuildUnit::getTenantId,SecurityUtils.getTenantId());
+        this.remove(queryWrapper);
+
+    }
+
+    @Override
+    public List<BaseBuildUnit> baseBuildUnitList(Integer id,Integer buildId){
+        LambdaQueryWrapper<BaseBuildUnit> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(!ObjectUtils.isEmpty(id),BaseBuildUnit::getId,id)
+                .eq(BaseBuildUnit::getBuildId,buildId)
+                .eq(BaseBuildUnit::getTenantId,SecurityUtils.getTenantId())
+                .orderByDesc(BaseBuildUnit::getId);
+        List<BaseBuildUnit> list = this.list(queryWrapper);
+        return list;
+    }
+}

+ 86 - 0
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/BaseScreenServiceImpl.java

@@ -0,0 +1,86 @@
+package com.usky.iot.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.iot.domain.BaseFacilityDevice;
+import com.usky.iot.domain.BaseGgpFacility;
+import com.usky.iot.domain.BaseScreen;
+import com.usky.iot.domain.DmpDeviceInfo;
+import com.usky.iot.mapper.BaseScreenMapper;
+import com.usky.iot.service.BaseScreenService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author han
+ * @since 2026-01-20
+ */
+@Service
+public class BaseScreenServiceImpl extends AbstractCrudService<BaseScreenMapper, BaseScreen> implements BaseScreenService {
+    @Override
+    public boolean add(BaseScreen baseScreen) {
+        Calendar cal = Calendar.getInstance();
+        Date date = cal.getTime();
+        baseScreen.setScreenCode(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(date));
+        baseScreen.setCreatedBy(SecurityUtils.getUsername());
+        baseScreen.setCreatedTime(LocalDateTime.now());
+        baseScreen.setTenantId(SecurityUtils.getTenantId());
+        this.checkRequiredData(baseScreen);
+        return this.save(baseScreen);
+    }
+
+    @Override
+    public void update(BaseScreen baseScreen) {
+        baseScreen.setUpdatedTime(LocalDateTime.now());
+        baseScreen.setUpdatedBy(SecurityUtils.getUsername());
+        this.checkRequiredData(baseScreen);
+        this.updateById(baseScreen);
+    }
+
+    @Override
+    public List<BaseScreen> screenList(Integer screenGroup, Integer id) {
+        LambdaQueryWrapper<BaseScreen> lambdaQuery = Wrappers.lambdaQuery();
+        lambdaQuery.eq(id != null, BaseScreen::getId, id)
+                .eq(screenGroup != null, BaseScreen::getScreenGroup, screenGroup)
+                .eq(BaseScreen::getTenantId, SecurityUtils.getTenantId());
+        List<BaseScreen> list = this.list(lambdaQuery);
+        return list;
+    }
+
+    /**
+     * 必填数据校验
+     */
+    private void checkRequiredData(BaseScreen baseScreen) {
+
+        if (StringUtils.isBlank(baseScreen.getScreenTitle())) {
+            throw new BusinessException("大屏标题不能为空!");
+        }
+
+        if (StringUtils.isBlank(baseScreen.getScreenCode())) {
+            throw new BusinessException("大屏编号不能为空!");
+        }
+
+        if (baseScreen.getScreenGroup() == null) {
+            throw new BusinessException("分组不能为空!");
+        }
+
+        if (baseScreen.getIsDefault() == null) {
+            throw new BusinessException("是否默认不能为空!");
+        }
+    }
+
+}

+ 1 - 1
service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/PmTimeConfServiceImpl.java

@@ -66,7 +66,7 @@ public class PmTimeConfServiceImpl extends AbstractCrudService<PmTimeConfMapper,
         // 查询时间配置
         PmTimeConf timeConf = getTimeConf(tenantId);
         if (timeConf == null) {
-            throw new RuntimeException("未找到工作报提交统计告时间配置,请联系管理员");
+            throw new BusinessException("未找到工作报提交统计告时间配置,请联系管理员");
         } else if (countDate.equals(LocalDate.now()) && timeConf.getStartTime().isAfter(LocalTime.now()) || countDate.isAfter(LocalDate.now())) {
             responseVO.setSubmitOnTime(0);
             responseVO.setSubmitLate(0);

+ 33 - 0
service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseBuildEngineeringMapper.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.iot.mapper.BaseBuildEngineeringMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.iot.domain.BaseBuildEngineering">
+        <id column="id" property="id" />
+        <result column="engineering_name" property="engineeringName" />
+        <result column="engineering_address" property="engineeringAddress" />
+        <result column="engineering_area" property="engineeringArea" />
+        <result column="review_code" property="reviewCode" />
+        <result column="project_code" property="projectCode" />
+        <result column="unit_count" property="unitCount" />
+        <result column="normal_usage" property="normalUsage" />
+        <result column="construction_company" property="constructionCompany" />
+        <result column="construction_company_person" property="constructionCompanyPerson" />
+        <result column="construction_company_phone" property="constructionCompanyPhone" />
+        <result column="contractor_company" property="contractorCompany" />
+        <result column="contractor_company_person" property="contractorCompanyPerson" />
+        <result column="contractor_company_phone" property="contractorCompanyPhone" />
+        <result column="iot_company" property="iotCompany" />
+        <result column="iot_company_person" property="iotCompanyPerson" />
+        <result column="iot_company_phone" property="iotCompanyPhone" />
+        <result column="build_id" property="buildId" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+        <result column="tenant_id" property="tenantId" />
+    </resultMap>
+
+</mapper>

+ 1 - 0
service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseBuildMapper.xml

@@ -29,6 +29,7 @@
         <result column="bim_url" property="bimUrl" />
         <result column="contact_phone" property="contactPhone" />
         <result column="build_desc" property="buildDesc" />
+        <result column="unit_count" property="unitCount" />
         <result column="create_time" property="createTime" />
         <result column="update_time" property="updateTime" />
         <result column="update_by" property="updateBy" />

+ 24 - 0
service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseBuildUnitMapper.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.iot.mapper.BaseBuildUnitMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.iot.domain.BaseBuildUnit">
+        <id column="id" property="id" />
+        <result column="unit_name" property="unitName" />
+        <result column="unit_main_exit" property="unitMainExit" />
+        <result column="unit_other_exit" property="unitOtherExit" />
+        <result column="unit_area" property="unitArea" />
+        <result column="floor" property="floor" />
+        <result column="unit_usage" property="unitUsage" />
+        <result column="people_number" property="peopleNumber" />
+        <result column="build_id" property="buildId" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+        <result column="tenant_id" property="tenantId" />
+    </resultMap>
+
+</mapper>

+ 24 - 0
service-iot/service-iot-biz/src/main/resources/mapper/iot/BaseScreenMapper.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.iot.mapper.BaseScreenMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.iot.domain.BaseScreen">
+        <id column="id" property="id"/>
+        <result column="screen_code" property="screenCode"/>
+        <result column="screen_title" property="screenTitle"/>
+        <result column="screen_subtitle" property="screenSubtitle"/>
+        <result column="screen_group" property="screenGroup"/>
+        <result column="is_default" property="isDefault"/>
+        <result column="screen_address" property="screenAddress"/>
+        <result column="order_num" property="orderNum"/>
+        <result column="thumbnail_url" property="thumbnailUrl"/>
+        <result column="remark" property="remark"/>
+        <result column="created_by" property="createdBy"/>
+        <result column="created_time" property="createdTime"/>
+        <result column="updated_by" property="updatedBy"/>
+        <result column="updated_time" property="updatedTime"/>
+        <result column="tenant_id" property="tenantId"/>
+    </resultMap>
+
+</mapper>

+ 6 - 5
service-job/src/main/java/com/ruoyi/job/task/RyTask.java

@@ -39,6 +39,7 @@ public class RyTask {
     @Autowired
     private RemoteEmsTaskService remoteEmsTaskService;
 
+
     public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) {
         System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
     }
@@ -67,28 +68,28 @@ public class RyTask {
         remoteIotTaskService.dataOverviewJobData();
     }
 
-    public void deviceStatus(){
+    public void deviceStatus() {
         System.out.println("deviceStatus start......");
         remoteIotTaskService.deviceStatus();
     }
 
-    public void deviceOffLineAlarm(){
+    public void deviceOffLineAlarm() {
         System.out.println("deviceOffLineAlarm start......");
         remoteIotTaskService.deviceOffLineAlarm();
     }
 
-    public void customInfoStatus(){
+    public void customInfoStatus() {
         System.out.println("customInfoStatus start......");
         remoteIotTaskService.customInfoStatus();
     }
 
-    public void meetingInfoStatus(){
+    public void meetingInfoStatus() {
         System.out.println("meetingInfoStatus start......");
         remoteMeetingService.meetingInfoStatus();
     }
 
     // 报告提交提醒
-    public void reportSubmissionReminder(){
+    public void reportSubmissionReminder() {
         System.out.println("reportSubmissionReminder start......");
         remotePmService.reportSubmissionReminder();
     }

+ 6 - 0
service-meeting/service-meeting-biz/pom.xml

@@ -73,6 +73,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
+        
+        <!-- WebSocket依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>com.auth0</groupId>

+ 22 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/config/WebSocketConfig.java

@@ -0,0 +1,22 @@
+package com.usky.meeting.config;
+
+import com.usky.meeting.websocket.MeetingDeviceWebSocketHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig {
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+    @Bean
+    public MeetingDeviceWebSocketHandler meetingDeviceWebSocketHandler() {
+        return new MeetingDeviceWebSocketHandler();
+    }
+}

+ 77 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/MeetingDeviceWebSocketService.java

@@ -0,0 +1,77 @@
+package com.usky.meeting.service;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.meeting.domain.MeetingDevice;
+import com.usky.meeting.domain.MeetingDeviceHeartbeat;
+import com.usky.meeting.mapper.MeetingDeviceHeartbeatMapper;
+import com.usky.meeting.mapper.MeetingDeviceMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class MeetingDeviceWebSocketService {
+
+    private static final int HEARTBEAT_TIMEOUT_MINUTES = 5;
+
+    @Autowired
+    private MeetingDeviceMapper meetingDeviceMapper;
+
+    @Autowired
+    private MeetingDeviceHeartbeatMapper heartbeatMapper;
+
+    public Map<String, Object> validateDevice(String deviceCode) {
+        Map<String, Object> result = new HashMap<>();
+
+        MeetingDevice device = validateDeviceExists(deviceCode);
+        boolean isOnline = validateDeviceOnline(deviceCode);
+
+        result.put("deviceCode", deviceCode);
+        result.put("deviceName", device.getDeviceName());
+        result.put("deviceStatus", isOnline ? 1 : 0);
+        result.put("isOnline", isOnline);
+        result.put("validateSuccess", true);
+
+        log.info("设备验证成功, deviceCode: {}, isOnline: {}", deviceCode, isOnline);
+        return result;
+    }
+
+    private MeetingDevice validateDeviceExists(String deviceCode) {
+        MeetingDevice device = meetingDeviceMapper.selectOne(
+                Wrappers.<MeetingDevice>lambdaQuery().eq(MeetingDevice::getDeviceCode, deviceCode)
+        );
+        if (device == null) {
+            log.warn("设备不存在, deviceCode: {}", deviceCode);
+            throw new BusinessException("设备不存在: " + deviceCode);
+        }
+        return device;
+    }
+
+    private boolean validateDeviceOnline(String deviceCode) {
+        MeetingDeviceHeartbeat heartbeat = heartbeatMapper.selectOne(
+                Wrappers.<MeetingDeviceHeartbeat>lambdaQuery()
+                        .eq(MeetingDeviceHeartbeat::getDeviceCode, deviceCode)
+                        .orderByDesc(MeetingDeviceHeartbeat::getCreateTime)
+                        .last("LIMIT 1")
+        );
+        if (heartbeat == null) {
+            log.warn("设备心跳记录不存在, deviceCode: {}", deviceCode);
+            throw new BusinessException("设备离线: " + deviceCode + ", 未找到心跳记录");
+        }
+        LocalDateTime heartbeatTime = heartbeat.getCreateTime();
+        LocalDateTime now = LocalDateTime.now();
+        if (heartbeatTime == null || now.isAfter(heartbeatTime.plusMinutes(HEARTBEAT_TIMEOUT_MINUTES))) {
+            log.warn("设备心跳超时, deviceCode: {}, heartbeatTime: {}", deviceCode, heartbeatTime);
+            throw new BusinessException("设备离线: " + deviceCode + ", 心跳超时");
+        }
+        return true;
+    }
+}

+ 21 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/service/vo/WebSocketConnectionResponseVO.java

@@ -0,0 +1,21 @@
+package com.usky.meeting.service.vo;
+
+import lombok.Data;
+
+@Data
+public class WebSocketConnectionResponseVO {
+
+    private String sessionId;
+
+    private String deviceCode;
+
+    private String deviceName;
+
+    private Integer deviceStatus;
+
+    private Boolean isOnline;
+
+    private String message;
+
+    private Long timestamp;
+}

+ 72 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketController.java

@@ -0,0 +1,72 @@
+package com.usky.meeting.websocket;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.meeting.service.MeetingDeviceWebSocketService;
+import com.usky.meeting.service.vo.WebSocketConnectionResponseVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Controller
+@RequiredArgsConstructor
+@RequestMapping("/meetingDevice/websocket")
+public class MeetingDeviceWebSocketController {
+
+    @Autowired
+    private MeetingDeviceWebSocketService deviceWebSocketService;
+
+    @GetMapping("/connect")
+    @ResponseBody
+    public ApiResult<WebSocketConnectionResponseVO> connect(
+            @RequestParam String deviceCode
+            //,@RequestHeader(value = "X-Real-IP", required = false) String realIp
+    ) {
+        log.info("收到WebSocket连接请求, deviceCode: {}", deviceCode);
+        WebSocketConnectionResponseVO response = new WebSocketConnectionResponseVO();
+        try {
+            Map<String, Object> validateResult = deviceWebSocketService.validateDevice(deviceCode);
+            response.setDeviceCode(deviceCode);
+            response.setDeviceName((String) validateResult.get("deviceName"));
+            response.setDeviceStatus((Integer) validateResult.get("deviceStatus"));
+            response.setIsOnline((Boolean) validateResult.get("isOnline"));
+            response.setMessage("设备验证通过,可建立WebSocket连接");
+            response.setTimestamp(System.currentTimeMillis());
+            log.info("设备验证成功, deviceCode: {}", deviceCode);
+            return ApiResult.success(response);
+        } catch (Exception e) {
+            log.error("设备验证失败, deviceCode: {}", deviceCode, e);
+            response.setDeviceCode(deviceCode);
+            response.setMessage("设备验证失败: " + e.getMessage());
+            response.setTimestamp(System.currentTimeMillis());
+            return ApiResult.error(e.getMessage());
+        }
+    }
+
+    @GetMapping("/status/{sessionId}")
+    @ResponseBody
+    public ApiResult<Map<String, Object>> getConnectionStatus(@PathVariable String sessionId) {
+        Map<String, Object> status = new HashMap<>();
+        boolean isOpen = MeetingDeviceWebSocketHandler.isSessionOpen(sessionId);
+        String deviceCode = MeetingDeviceWebSocketHandler.getDeviceCodeBySession(sessionId);
+        status.put("sessionId", sessionId);
+        status.put("deviceCode", deviceCode);
+        status.put("isOpen", isOpen);
+        status.put("checkTime", LocalDateTime.now().toString());
+        return ApiResult.success(status);
+    }
+
+    @GetMapping("/close/{sessionId}")
+    @ResponseBody
+    public ApiResult<Void> closeConnection(@PathVariable String sessionId) {
+        MeetingDeviceWebSocketHandler.removeSession(sessionId);
+        log.info("WebSocket连接已手动关闭, sessionId: {}", sessionId);
+        return ApiResult.success();
+    }
+}

+ 76 - 0
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/websocket/MeetingDeviceWebSocketHandler.java

@@ -0,0 +1,76 @@
+package com.usky.meeting.websocket;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+//@Component
+public class MeetingDeviceWebSocketHandler extends TextWebSocketHandler {
+
+    private static final Map<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>();
+    private static final Map<String, String> SESSION_DEVICE_MAP = new ConcurrentHashMap<>();
+
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        String sessionId = session.getId();
+        String deviceCode = (String) session.getAttributes().get("deviceCode");
+        SESSION_MAP.put(sessionId, session);
+        if (deviceCode != null) {
+            SESSION_DEVICE_MAP.put(sessionId, deviceCode);
+        }
+        log.info("WebSocket连接已建立, sessionId: {}, deviceCode: {}", sessionId, deviceCode);
+    }
+
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        String payload = message.getPayload();
+        log.info("收到消息, sessionId: {}, message: {}", session.getId(), payload);
+        String deviceCode = SESSION_DEVICE_MAP.get(session.getId());
+        session.sendMessage(new TextMessage("{\"type\":\"ack\",\"deviceCode\":\"" + deviceCode + "\",\"message\":\"消息已收到\"}"));
+    }
+
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
+        String sessionId = session.getId();
+        SESSION_MAP.remove(sessionId);
+        SESSION_DEVICE_MAP.remove(sessionId);
+        log.info("WebSocket连接已关闭, sessionId: {}, status: {}", sessionId, status);
+    }
+
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+        log.error("WebSocket传输错误, sessionId: {}", session.getId(), exception);
+        if (session.isOpen()) {
+            session.close();
+        }
+    }
+
+    public static void sendMessage(String sessionId, String message) throws IOException {
+        WebSocketSession session = SESSION_MAP.get(sessionId);
+        if (session != null && session.isOpen()) {
+            session.sendMessage(new TextMessage(message));
+        }
+    }
+
+    public static boolean isSessionOpen(String sessionId) {
+        WebSocketSession session = SESSION_MAP.get(sessionId);
+        return session != null && session.isOpen();
+    }
+
+    public static void removeSession(String sessionId) {
+        SESSION_MAP.remove(sessionId);
+        SESSION_DEVICE_MAP.remove(sessionId);
+    }
+
+    public static String getDeviceCodeBySession(String sessionId) {
+        return SESSION_DEVICE_MAP.get(sessionId);
+    }
+}

+ 1 - 1
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmTimeConfServiceImpl.java

@@ -66,7 +66,7 @@ public class PmTimeConfServiceImpl extends AbstractCrudService<PmTimeConfMapper,
         // 查询时间配置
         PmTimeConf timeConf = getTimeConf(tenantId);
         if (timeConf == null) {
-            throw new RuntimeException("未找到工作报提交统计告时间配置,请联系管理员");
+            throw new BusinessException("未找到工作报提交统计告时间配置,请联系管理员");
         } else if (countDate.equals(LocalDate.now()) && timeConf.getStartTime().isAfter(LocalTime.now()) || countDate.isAfter(LocalDate.now())) {
             responseVO.setSubmitOnTime(0);
             responseVO.setSubmitLate(0);