3
2

56 Коммиты f60b5c2de0 ... a0df8c4b14

Автор SHA1 Сообщение Дата
  james a0df8c4b14 1、调整service-tsdb服务中对外查询实时和历史api接口封装体; 1 неделя назад
  james 7a47bf0dfb 查询日志详情中增加操作成功码的判断 2 недель назад
  james 9b2f8086f4 排查解决查询规则引擎日志详情报错的问题 2 недель назад
  james 68f77368c2 触发动作日志中增加获取规则详情中设备对应的租户Id作为日志记录的租户Id 2 недель назад
  james 79ae95495f 开发规则引擎中执行冷却机制,上报触发规则后进入冷却期,在冷却期范围内不再触发规则 2 недель назад
  james bfe41a0f09 订阅消息时增加定时约束条件的判断 2 недель назад
  james ff17274fc8 将规则详情信息中的属性编码字段在与上报数据进行对比或与时序数据库查询的数据进行对比时统一改为小写 2 недель назад
  james 8f43878f93 将规则详情信息中的属性编码字段在与上报数据进行对比或与时序数据库查询的数据进行对比时统一改为小写 2 недель назад
  james 9ca585decc 调整推送告警数据获取设备名称逻辑 2 недель назад
  james 6b93af26e7 调整推送告警数据获取设备名称逻辑 2 недель назад
  james 959ace362f Git 强制添加被 .gitignore 过滤的文件 2 недель назад
  james 3026794849 Git 强制添加被 .gitignore 过滤的文件 2 недель назад
  james 1c545a9562 优化规则执行日志详情接口中控制日志设备控制描述内容和告警推送内容 2 недель назад
  james 337c0bc08c 优化规则执行日志详情接口中控制日志描述内容 2 недель назад
  james b7b0c8a76f 优化订阅设备RocketMQ、定时触发、采集值三个条件下获取租户Id的逻辑;优化规则引擎-配置规则接口,在插入数据到规则设备表前增加设备Id、属性Id和规则Id唯一校验的判断,解决重复插入数据的问题; 2 недель назад
  james d74f09bde3 优化规则引擎-条件下拉接口,去除租户刷选的逻辑,做到通用;优化当触发条件中触发方式为采集量时,条件对应的开始时间和结束时间的处理,以及次日、次月、次周的处理逻辑; 3 недель назад
  james 360430740a 在订阅RocketMQ消息触发执行动作、定时任务触发执行动作、采集量定时任务触发执行动作增加日志采集 3 недель назад
  james f3e360e7bc 修改告警推送体Json结构,优化推送代码逻辑,可以选择一个产品的多个设备 3 недель назад
  james a9c430e0a7 1、消费RocketMQ消息到判断触发执行动作的处理逻辑;2、定时规则触发逻辑; 4 недель назад
  james 03d28fe251 优化规则执行日志分页接口,解决根据规则名称刷选无效的问题 1 месяц назад
  james 8697be4444 添加被git屏蔽掉的文件夹 1 месяц назад
  james d1e57ae8a2 优化规则引擎操作日志存储详情字段生成逻辑 1 месяц назад
  james 80f2d88851 开发数据规则引擎基础功能 1 месяц назад
  fuyuchuan 61b9928e04 Merge branch 'fu-dev' of uskycloud/usky-modules into master 2 месяцев назад
  fuyuchuan c2da1491e7 排查解决一氧化碳数据异常问题 2 месяцев назад
  fuyuchuan 2c3c9df4f9 Merge branch 'fu-dev' of uskycloud/usky-modules into master 2 месяцев назад
  fuyuchuan f5be979b80 人防代码报错排查解决 2 месяцев назад
  fuyuchuan c5cbd9bfcc Merge branch 'fu-dev' of uskycloud/usky-modules into master 2 месяцев назад
  fuyuchuan 57345c05c8 人防代码优化调整 2 месяцев назад
  fuyuchuan 994888a3bf 人防代码优化 2 месяцев назад
  fuyuchuan b03d835b0a Merge branch 'fu-dev' of uskycloud/usky-modules into master 2 месяцев назад
  fuyuchuan 3c22b899e5 解决服务报错问题 2 месяцев назад
  fuyuchuan 3681b67092 Merge branch 'fu-dev' of uskycloud/usky-modules into master 2 месяцев назад
  fuyuchuan 1e8c1c9bfd 人防代码修改 2 месяцев назад
  fuyuchuan 055e0bffd8 Merge branch 'fu-dev' of uskycloud/usky-modules into master 2 месяцев назад
  fuyuchuan 7fadcbdcbf 拉165 iot代码解决冲突 2 месяцев назад
  fuyuchuan 6270a39b81 暂剔除service-data服务代码 2 месяцев назад
  fuyuchuan 29b4f379d6 人防代码提交 2 месяцев назад
  guoenzhou 4641a3f4aa 优化 统一增加各个 service 模块 readme 说明 2 месяцев назад
  guoenzhou 7ed01a7895 修改 site-web 编译问题 2 месяцев назад
  fuyuchuan 68066c94e4 解决冲突 2 месяцев назад
  James 5a3236e42b Merge branch 'usky-zyj' of uskycloud/usky-modules into master 2 месяцев назад
  james 9fb8f3bb97 在synchronizeAlarmData方法增加GETTING注解 2 месяцев назад
  James d29599a11d Merge branch 'usky-zyj' of uskycloud/usky-modules into master 2 месяцев назад
  james 2be459c5a7 synchronizeAlarmData 方法添加 @GetMapping注解 2 месяцев назад
  James 61b04ab192 Merge branch 'usky-zyj' of uskycloud/usky-modules into master 2 месяцев назад
  james edd8a696aa 解决链接路径名称 2 месяцев назад
  James 53404509ae Merge branch 'usky-zyj' of uskycloud/usky-modules into master 2 месяцев назад
  james 5d97188938 解决冲突 2 месяцев назад
  james 1412affdcc 修改依赖ip 2 месяцев назад
  gez 8a6a428024 Merge branch 'usky-zyj' of uskycloud/usky-modules into master 2 месяцев назад
  james 190a04a186 解决冲突 2 месяцев назад
  james 4db104e692 解决冲突 2 месяцев назад
  fuyuchuan 33e5094c4a 数据中心代码第一次提交:数据接口-静态数据 2 месяцев назад
  fuyuchuan d9e2bc2eef Merge branch 'master' of http://47.111.81.118:3000/uskycloud/usky-modules into fu-dev 3 месяцев назад
  gez c2371d06ed Merge branch 'han' of uskycloud/usky-modules into master 3 месяцев назад
100 измененных файлов с 4480 добавлено и 452 удалено
  1. 4 0
      .vscode/settings.json
  2. 83 104
      pom.xml
  3. 33 0
      service-agbox/README.md
  4. 35 0
      service-ai/README.md
  5. 35 0
      service-alarm/README.md
  6. 33 0
      service-backend/README.md
  7. 34 0
      service-cdi/README.md
  8. 4 4
      service-cdi/service-cdi-api/src/main/java/com/usky/cdi/RemotecdiTaskService.java
  9. 6 6
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/MybatisGeneratorUtils.java
  10. 3 3
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/cdiApplication.java
  11. 7 5
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/BaseDataController.java
  12. 21 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildController.java
  13. 21 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildPlaneController.java
  14. 21 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/BaseBuildUnitController.java
  15. 158 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/CdiDefenseProjectController.java
  16. 92 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/CdiDeliveryLogController.java
  17. 21 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/web/SysUserController.java
  18. 212 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuild.java
  19. 56 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuildPlane.java
  20. 101 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/BaseBuildUnit.java
  21. 135 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/CdiDefenseProject.java
  22. 90 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/CdiDeliveryLog.java
  23. 136 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/domain/SysUser.java
  24. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildMapper.java
  25. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildPlaneMapper.java
  26. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/BaseBuildUnitMapper.java
  27. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/CdiDefenseProjectMapper.java
  28. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/CdiDeliveryLogMapper.java
  29. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/mapper/SysUserMapper.java
  30. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildPlaneService.java
  31. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildService.java
  32. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/BaseBuildUnitService.java
  33. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDefenseProjectService.java
  34. 31 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDeliveryLogService.java
  35. 16 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/SysUserService.java
  36. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/AlarmType.java
  37. 103 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/DeliveryDataType.java
  38. 14 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/EnvMonitorMqttTopic.java
  39. 155 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/MqttTopics.java
  40. 44 72
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataSyncService.java
  41. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildPlaneServiceImpl.java
  42. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildServiceImpl.java
  43. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseBuildUnitServiceImpl.java
  44. 44 31
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java
  45. 63 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDefenseProjectServiceImpl.java
  46. 658 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java
  47. 244 63
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java
  48. 20 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/SysUserServiceImpl.java
  49. 4 7
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/mqtt/MqttConnectionTool.java
  50. 197 127
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java
  51. 68 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/SyncTaskStatisticsVO.java
  52. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/Co2VO.java
  53. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/CoVO.java
  54. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/CrackVO.java
  55. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/DeviationVO.java
  56. 2 3
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ElectricityLoadVO.java
  57. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/EngineeringBaseVO.java
  58. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ErrorMsgVO.java
  59. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FacilityDeviceVO.java
  60. 3 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FloorPlaneVO.java
  61. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/HumidityVO.java
  62. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/OxygenVO.java
  63. 2 3
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/PersonPresenceVO.java
  64. 3 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ProtectiveUnitVO.java
  65. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/SensorInfoVO.java
  66. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/TempVO.java
  67. 2 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/TiltVO.java
  68. 2 3
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/WaterLeakVO.java
  69. 46 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildMapper.xml
  70. 15 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildPlaneMapper.xml
  71. 24 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/BaseBuildUnitMapper.xml
  72. 26 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/CdiDefenseProjectMapper.xml
  73. 18 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/CdiDeliveryLogMapper.xml
  74. 31 0
      service-cdi/service-cdi-biz/src/main/resources/mapper/cdi/SysUserMapper.xml
  75. 31 0
      service-cockpit/README.md
  76. 33 0
      service-eg/README.md
  77. 2 2
      service-ems/service-ems-biz/src/main/resources/bootstrap.yml
  78. 34 0
      service-fire/README.md
  79. 34 0
      service-ids/README.md
  80. 35 0
      service-iot/README.md
  81. 2 1
      service-iot/service-iot-biz/src/main/java/com/usky/iot/service/impl/DmpDeviceInfoServiceImpl.java
  82. 33 0
      service-issue/README.md
  83. 35 0
      service-job/README.md
  84. 1 0
      service-job/pom.xml
  85. 33 0
      service-meeting/README.md
  86. 33 0
      service-oa/README.md
  87. 33 0
      service-park/README.md
  88. 33 0
      service-pm/README.md
  89. 17 0
      service-rule/pom.xml
  90. 26 0
      service-rule/service-rule-api/pom.xml
  91. 101 0
      service-rule/service-rule-biz/pom.xml
  92. 44 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/RuoYiSystemApplication.java
  93. 37 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/cache/DeviceAcqTriggerCooldownCache.java
  94. 113 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/cache/DeviceTriggerIncludeMinuteCache.java
  95. 32 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/cache/RuleEngineCache.java
  96. 132 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/config/CronTaskManager.java
  97. 22 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/constant/DateTimeConstants.java
  98. 36 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/constant/RegExpConstants.java
  99. 108 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/controller/MybatisGeneratorUtils.java
  100. 21 0
      service-rule/service-rule-biz/src/main/java/com/usky/rule/controller/web/BaseBuildController.java

+ 4 - 0
.vscode/settings.json

@@ -0,0 +1,4 @@
+{
+    "java.compile.nullAnalysis.mode": "automatic",
+    "java.configuration.updateBuildConfiguration": "automatic"
+}

+ 83 - 104
pom.xml

@@ -1,136 +1,115 @@
 <?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-oa</module>
 
-    
-    <module>service-park</module>
 
+        <module>service-ai</module>
 
+        <module>service-pm</module>
 
-    <module>service-issue</module>
+        <module>service-ids</module>
 
+        <module>service-cdi</module>
 
-    <module>service-meeting</module>
+        <module>service-ems</module>
 
+        <module>service-transfer</module>
 
-    <module>service-agbox</module>
+        <module>service-tsdb</module>
 
+        <module>service-rule</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>

+ 33 - 0
service-agbox/README.md

@@ -0,0 +1,33 @@
+# 巡更管理系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 巡更管理系统 |
+| 分区 | patrol |
+| 英文全称 | Patrol management |
+| 描述 | 提供智能巡更系统 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-agbox |
+
+## 功能说明
+
+- 提供智能巡更系统
+- 支持巡更路线管理
+- 提供巡更记录和状态监控
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-agbox/
+├── service-agbox-api/         # API 接口模块
+├── service-agbox-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 35 - 0
service-ai/README.md

@@ -0,0 +1,35 @@
+# AI 智能体系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | AI 智能体系统 |
+| 分区 | ai |
+| 英文全称 | AI |
+| 描述 | 提供 AI 文本推理,视频算法管理 |
+| 模块分类 | 通用模块 |
+| 工程模块 | service-ai |
+
+## 功能说明
+
+- 提供 AI 文本推理能力
+- 管理视频算法
+- 支持 AI 模型的集成和调用
+- 提供智能分析和处理服务
+
+## 技术栈
+
+- Java
+- Spring Boot
+- AI 模型集成
+- MyBatis
+
+## 模块结构
+
+```
+service-ai/
+├── service-ai-api/         # API 接口模块
+├── service-ai-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 35 - 0
service-alarm/README.md

@@ -0,0 +1,35 @@
+# 告警中心
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 告警中心 |
+| 分区 | alarm |
+| 英文全称 | Alarm |
+| 描述 | 提供告警服务 |
+| 模块分类 | 通用模块 |
+| 工程模块 | service-alarm |
+
+## 功能说明
+
+- 提供告警信息的采集、处理和分发
+- 支持多种告警级别和类型的管理
+- 提供告警通知和处理流程
+- 支持告警历史查询和统计分析
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+- 消息队列(可选)
+
+## 模块结构
+
+```
+service-alarm/
+├── service-alarm-api/         # API 接口模块
+├── service-alarm-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 33 - 0
service-backend/README.md

@@ -0,0 +1,33 @@
+# 后端服务
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 后端服务 |
+| 分区 | backend |
+| 英文全称 | Backend Service |
+| 描述 | 提供基础后端服务支持 |
+| 模块分类 | 通用模块 |
+| 工程模块 | service-backend |
+
+## 功能说明
+
+- 提供基础后端服务功能
+- 支持其他模块的后端需求
+- 提供通用的后端接口和服务
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-backend/
+├── service-backend-api/         # API 接口模块
+├── service-backend-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 34 - 0
service-cdi/README.md

@@ -0,0 +1,34 @@
+# 人防物联系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 人防物联系统 |
+| 分区 | cdi |
+| 英文全称 | Civil Defense IoT |
+| 描述 | 提供人防物联场景应用,对接国动办平台 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-cdi |
+
+## 功能说明
+
+- 提供人防物联场景应用
+- 对接国动办平台
+- 管理人防物联网设备和数据
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+- IoT 协议支持
+
+## 模块结构
+
+```
+service-cdi/
+├── service-cdi-api/         # API 接口模块
+├── service-cdi-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 4 - 4
service-cdi/service-cdi-api/src/main/java/com/usky/cdi/RemotecdiTaskService.java

@@ -8,10 +8,10 @@ import org.springframework.web.bind.annotation.RequestParam;
 @FeignClient(contextId = "RemotecdiTaskService", value = "service-cdi", fallbackFactory = RemotecdiTaskFactory.class)
 public interface RemotecdiTaskService {
     @GetMapping("/synchronizeDeviceData")
-    void synchronizeDeviceData(@RequestParam("tenantId") Integer tenantId,
-                               @RequestParam("engineeringId") Long engineeringId,
-                               @RequestParam("username") String username,
-                               @RequestParam("password") String password);
+    void synchronizeDeviceData(@RequestParam(value = "tenantId") Integer tenantId,
+                               @RequestParam(value = "engineeringId") Long engineeringId,
+                               @RequestParam(value = "username") String username,
+                               @RequestParam(value = "password") String password);
 
     @GetMapping("/allData")
     void allData(@RequestParam("engineeringId") Long engineeringId,

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

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

+ 44 - 72
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;
@@ -25,7 +26,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -47,10 +47,7 @@ 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 static final String ALARM_LOCK_KEY_PREFIX = "alarm:lock:";
-    private static final long LOCK_EXPIRE_TIME = 1 * 60 * 1000; // 锁过期时间1分钟
     private final MqttConnectionTool mqttConnectionTool;
 
     @Autowired
@@ -58,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;
 
@@ -83,75 +80,47 @@ public class AlarmDataSyncService {
         return idGenerator.nextPacketId();
     }
 
-    /**
-     * 尝试获取分布式锁
-     * 使用Redis的setIfAbsent原子操作,确保线程安全
-     *
-     * @param lockKey 锁的Key
-     * @return 是否获取到锁
-     */
-    private boolean tryLock(String lockKey) {
-        // 使用setIfAbsent原子操作设置锁,同时设置过期时间,避免死锁
-        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS);
-        return Boolean.TRUE.equals(result);
-    }
-
-    /**
-     * 释放分布式锁
-     *
-     * @param lockKey 锁的Key
-     */
-    private void releaseLock(String lockKey) {
-        // 直接删除锁Key
-        stringRedisTemplate.delete(lockKey);
-    }
-
     @Async
     public void synchronizeAlarmData(Integer tenantId, Long engineeringId, String username, String password, String status) {
-        // 生成锁key,基于租户ID,确保同一租户同一时间只能执行一个任务
-        String lockKey = ALARM_LOCK_KEY_PREFIX + tenantId;
-        
-        // 尝试获取锁
-        if (!tryLock(lockKey)) {
-            log.info("租户:{}的告警数据推送任务正在执行中,本次请求跳过", tenantId);
-            return;
-        }
-        
-        Long startTime = System.currentTimeMillis();
-        
-        try {
-            log.info("租户:{}的人防告警数据推送定时任务开始执行,平战时状态:{}", tenantId, PEACETIME.equals(status) ? "平时" : "战时");
+        log.info("租户:{}的人防告警数据推送定时任务开始执行,平战时状态:{}", tenantId, PEACETIME.equals(status) ? "平时" : "战时");
 
-            log.info("开始时间:{}", getCurrentTime());
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+        long endTime;
+        log.info("开始时间:{}", getCurrentTime());
 
-            // 1.查询 base_alarm 表中的告警数据,筛选条件为 tenant_id = tenantId
-            LambdaQueryWrapper<BaseAlarm> queryWrapper = new LambdaQueryWrapper<>();
-            queryWrapper.eq(BaseAlarm::getTenantId, tenantId)
-                    .eq(BaseAlarm::getAlarmGrade, 1);
-            List<BaseAlarm> alarmList = baseAlarmMapper.selectList(queryWrapper);
+        // 1.查询 base_alarm 表中的告警数据,筛选条件为 tenant_id = tenantId
+        LambdaQueryWrapper<BaseAlarm> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(BaseAlarm::getTenantId, tenantId)
+                .eq(BaseAlarm::getAlarmGrade, 1);
+        List<BaseAlarm> alarmList = baseAlarmMapper.selectList(queryWrapper);
 
-            if (alarmList.isEmpty()) {
-                log.warn("租户{}没有新的告警数据,任务结束", tenantId);
-                return;
-            }
-            log.info("查询到租户{}的告警数据总数:{}", tenantId, alarmList.size());
+        if (alarmList.isEmpty()) {
+            log.warn("租户{}没有新的告警数据,任务结束", tenantId);
+            return;
+        }
+        int size = alarmList.size();
+        log.info("查询到租户{}的告警数据总数:{}", tenantId, size);
 
-            // 批量查询建筑设施数据,构建设备ID到设施的映射
-            Map<String, BaseBuildFacility> facilityMap = buildFacilityMap(alarmList, tenantId);
+        // 批量查询建筑设施数据,构建设备ID到设施的映射
+        Map<String, BaseBuildFacility> facilityMap = buildFacilityMap(alarmList, tenantId);
 
-            // 记录每一条告警数据
-            for (BaseAlarm alarm : alarmList) {
-                log.info("告警数据:ID={}, 设备ID={}, 告警类型={}, 告警时间={}, 处理状态={}, 是否误报={}",
-                        alarm.getId(), alarm.getDeviceId(), alarm.getAlarmType(),
-                        alarm.getAlarmTime(), alarm.getHandleStatus(), alarm.getAlarmFalse());
-            }
+        // 记录每一条告警数据
+        for (BaseAlarm alarm : alarmList) {
+            log.info("告警数据:ID={}, 设备ID={}, 告警类型={}, 告警时间={}, 处理状态={}, 是否误报={}",
+                    alarm.getId(), alarm.getDeviceId(), alarm.getAlarmType(),
+                    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 {
@@ -206,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++;
 
@@ -225,16 +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 {
-            // 释放锁
-            releaseLock(lockKey);
-            
-            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.getOrDefault(log.getUserName(), 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);
+        }
+    }
+
+
+}

+ 244 - 63
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -3,22 +3,32 @@ 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.baomidou.mybatisplus.core.toolkit.StringUtils;
+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.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.ApplicationContext;
 import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
@@ -26,6 +36,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;
@@ -79,11 +90,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");
+
     /**
      * 获取当前时间
      */
@@ -113,6 +132,9 @@ public class IotDataTransferService {
             return result;
         }
 
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+
         try {
             List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
             log.warn("获取到的数据:{}", deviceData);
@@ -125,10 +147,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, SecurityUtils.getUsername());
                 return result;
             }
 
             Long engineeringId = transferVO.getEngineeringId();
+
+            // ✅ 先处理所有设备,不记录日志
             for (JSONObject deviceDataItem : deviceData) {
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
                 if (dataEndTime == null) {
@@ -153,7 +181,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());
@@ -161,13 +189,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, SecurityUtils.getUsername());
+
             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, SecurityUtils.getUsername());
             return result;
         }
     }
@@ -187,44 +227,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:
@@ -244,11 +271,10 @@ public class IotDataTransferService {
                             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 {
@@ -256,17 +282,84 @@ 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, SecurityUtils.getUsername());
+
+            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, SecurityUtils.getUsername());
+
             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, String userName) {
+
+        Integer deviceType = transferVO.getDeviceType();
+
+        if (StringUtils.isBlank(userName)) {
+            userName = "自动同步";
+        }
+
+        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(userName);
+        log.setNickName(userName);
+        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)
      *
@@ -281,6 +374,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();
@@ -289,13 +385,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, SecurityUtils.getUsername());
                 return result;
             }
 
             Long engineeringId = transferVO.getEngineeringId();
+
+            // 遍历所有设备数据,仅执行推送,不记录日志
             for (JSONObject deviceDataItem : deviceData) {
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
                 if (dataEndTime == null) {
@@ -311,11 +415,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());
@@ -323,13 +431,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, SecurityUtils.getUsername());
+
             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, SecurityUtils.getUsername());
+
             return result;
         }
     }
@@ -348,21 +474,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, SecurityUtils.getUsername());
                 return result;
             }
 
             Long engineeringId = transferVO.getEngineeringId();
+
+            // 遍历设备数据,仅执行推送,不记录日志
             for (JSONObject deviceDataItem : deviceData) {
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
                 if (dataEndTime == null) {
@@ -371,6 +508,7 @@ public class IotDataTransferService {
                 }
 
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
+
                 ElectricityLoadVO vo = new ElectricityLoadVO();
                 vo.setDataPacketID(generateDataPacketID());
                 vo.setSensorID(deviceId);
@@ -389,12 +527,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());
@@ -402,13 +546,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, SecurityUtils.getUsername());
+
             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, SecurityUtils.getUsername());
+
             return result;
         }
     }
@@ -435,7 +597,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);
     }
 
     /**
@@ -460,11 +622,11 @@ 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);
     }
 
     /**
-     * 推送氧气浓度信息(705
+     * 推送氧气浓度信息(709
      *
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
@@ -485,7 +647,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);
     }
 
     /**
@@ -510,7 +672,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);
     }
 
     /**
@@ -535,7 +697,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);
     }
 
     /**
@@ -696,10 +858,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());
@@ -735,7 +902,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());
@@ -746,10 +913,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, SecurityUtils.getUsername());
             return result;
         } catch (Exception e) {
             log.error("位移数据推送发生异常", e);
             result.put("failureCount", transferVO.getDevices().size());
+            endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    result.get("successCount"), result.get("failureCount"),
+                    totalDevices - result.get("successCount") - result.get("failureCount"), 0, SecurityUtils.getUsername());
             return result;
         }
     }
@@ -843,6 +1018,12 @@ public class IotDataTransferService {
                     result = sendPersonPresence(transferVO);
                     break;
                 case 704:
+                    if (tenantId == 1205) {
+                        // 设置默认值,避免空指针
+                        result.put("successCount", 0);
+                        result.put("failureCount", 0);
+                        break;
+                    }
                     result = sendElectricityLoad(transferVO);
                     break;
                 // case 712:
@@ -861,8 +1042,8 @@ public class IotDataTransferService {
             }
 
             // 累加成功数和失败数
-            totalSuccessCount += result.get("successCount");
-            totalFailureCount += result.get("failureCount");
+            totalSuccessCount += result.getOrDefault("successCount", 0);
+            totalFailureCount += result.getOrDefault("failureCount", 0);
         }
 
         // 任务完成总结
@@ -870,6 +1051,7 @@ public class IotDataTransferService {
         log.info("总共涉及产品类型数:{}个,产品代码为:{}", totalProductTypes, codeDeviceUuidsMap.keySet());
         log.info("总共推送设备数量:{}个,成功:{}个,失败:{}个",
                 totalDevices, totalSuccessCount, totalFailureCount);
+
     }
 
     /**
@@ -923,7 +1105,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);
         }
@@ -954,7 +1136,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"));
@@ -965,14 +1147,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>

+ 31 - 0
service-cockpit/README.md

@@ -0,0 +1,31 @@
+# 驾驶舱管理系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 驾驶舱管理系统 |
+| 分区 | ckpt |
+| 英文全称 | Cockpit |
+| 描述 | 提供驾驶舱配置 |
+| 模块分类 | 通用模块 |
+| 工程模块 | service-cockpit |
+
+## 功能说明
+
+- 提供驾驶舱配置管理
+- 支持数据可视化展示
+- 提供仪表盘定制功能
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-cockpit/
+└── pom.xml                  # 模块依赖配置
+```

+ 33 - 0
service-eg/README.md

@@ -0,0 +1,33 @@
+# 门禁控制系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 门禁控制系统 |
+| 分区 | eg |
+| 英文全称 | entrance guard |
+| 描述 | 提供门禁系统应用 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-eg |
+
+## 功能说明
+
+- 提供门禁系统应用
+- 支持门禁设备管理
+- 提供门禁记录管理
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-eg/
+├── service-eg-api/         # API 接口模块
+├── service-eg-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 2 - 2
service-ems/service-ems-biz/src/main/resources/bootstrap.yml

@@ -14,10 +14,10 @@ spring:
     nacos:
       discovery:
         # 服务注册地址
-        server-addr: 192.168.10.165:8848
+        server-addr: usky-cloud-nacos:8848
       config:
         # 配置中心地址
-        server-addr: 192.168.10.165:8848
+        server-addr: usky-cloud-nacos:8848
         # 配置文件格式
         file-extension: yml
         # 共享配置

+ 34 - 0
service-fire/README.md

@@ -0,0 +1,34 @@
+# 消防业务
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 消防业务 |
+| 分区 | dem |
+| 英文全称 | Detachment business data |
+| 描述 | 提供消防业务数据管理 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-fire |
+
+## 功能说明
+
+- 提供消防业务数据管理
+- 支持消防设备监控和告警
+- 提供消防业务流程管理
+- 支持消防数据统计和分析
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-fire/
+├── service-fire-api/         # API 接口模块
+├── service-fire-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 34 - 0
service-ids/README.md

@@ -0,0 +1,34 @@
+# 停车管理系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 停车管理系统 |
+| 分区 | ids |
+| 英文全称 | Parking Management System |
+| 描述 | 提供停车管理系统 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-ids |
+
+## 功能说明
+
+- 提供停车管理系统
+- 支持停车场设备管理
+- 提供停车收费和计费功能
+- 支持车辆进出管理和监控
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-ids/
+├── service-ids-api/         # API 接口模块
+├── service-ids-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 35 - 0
service-iot/README.md

@@ -0,0 +1,35 @@
+# 物联网中心
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 物联网中心 |
+| 分区 | dmp |
+| 英文全称 | Data Management Platform |
+| 描述 | 提供物联网产品和设备管理 |
+| 模块分类 | 通用模块 |
+| 工程模块 | service-iot |
+
+## 功能说明
+
+- 提供物联网产品和设备管理
+- 支持设备数据采集和监控
+- 提供视频中心功能,管理视频摄像头和视频流接口
+- 支持物联网设备的接入和管理
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+- 物联网协议支持
+
+## 模块结构
+
+```
+service-iot/
+├── service-iot-api/         # API 接口模块
+├── service-iot-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

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

@@ -611,7 +611,8 @@ public class DmpDeviceInfoServiceImpl extends AbstractCrudService<DmpDeviceInfoM
 
     @Override
     public void updateDeviceStatus(LastInnerQueryVO queryVO) {
-        List<LastInnerResultVO> list = remoteTsdbProxyService.queryLastDeviceData(queryVO);
+        ApiResult<List<LastInnerResultVO>> lastApi = remoteTsdbProxyService.queryLastDeviceData(queryVO);
+        List<LastInnerResultVO> list = lastApi != null ? lastApi.getData() : null;
         if (CollectionUtils.isNotEmpty(list)) {
             for (int i = 0; i < list.size(); i++) {
                 if (Objects.nonNull(list.get(i).getMetrics())) {

+ 33 - 0
service-issue/README.md

@@ -0,0 +1,33 @@
+# 问题管理系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 问题管理系统 |
+| 分区 | issue |
+| 英文全称 | Issue Management |
+| 描述 | 提供问题管理和跟踪功能 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-issue |
+
+## 功能说明
+
+- 提供问题的创建、管理和跟踪
+- 支持问题状态流转和分配
+- 提供问题统计和分析功能
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-issue/
+├── service-issue-api/         # API 接口模块
+├── service-issue-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 35 - 0
service-job/README.md

@@ -0,0 +1,35 @@
+# 定时服务
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 定时服务 |
+| 分区 | qrt |
+| 英文全称 | FILE |
+| 描述 | 提供定时任务工具管理 |
+| 模块分类 | 服务模块 |
+| 工程模块 | service-job |
+
+## 功能说明
+
+- 提供定时任务的创建、管理和执行
+- 支持多种任务调度策略
+- 提供任务执行状态监控和日志记录
+- 支持任务失败重试机制
+
+## 技术栈
+
+- Java
+- Spring Boot
+- Quartz 或 Spring Scheduler
+- MyBatis
+
+## 模块结构
+
+```
+service-job/
+├── service-job-api/         # API 接口模块
+├── service-job-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 1 - 0
service-job/pom.xml

@@ -69,6 +69,7 @@
             <version>0.0.1</version>
             <scope>compile</scope>
         </dependency>
+
         <dependency>
             <groupId>com.usky</groupId>
             <artifactId>service-pm-api</artifactId>

+ 33 - 0
service-meeting/README.md

@@ -0,0 +1,33 @@
+# 智能会议系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 智能会议系统 |
+| 分区 | meeting |
+| 英文全称 | Meeting |
+| 描述 | 提供会议预约系统应用 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-meeting |
+
+## 功能说明
+
+- 提供会议预约系统应用
+- 支持会议管理和资源预约
+- 提供会议通知和提醒功能
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-meeting/
+├── service-meeting-api/         # API 接口模块
+├── service-meeting-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 33 - 0
service-oa/README.md

@@ -0,0 +1,33 @@
+# 智能办公系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 智能办公系统 |
+| 分区 | oa |
+| 英文全称 | oa |
+| 描述 | 提供会议预约系统,工作审批管理 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-oa |
+
+## 功能说明
+
+- 提供会议预约系统
+- 支持工作审批管理
+- 提供办公流程自动化
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-oa/
+├── service-oa-api/         # API 接口模块
+├── service-oa-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 33 - 0
service-park/README.md

@@ -0,0 +1,33 @@
+# 园区管理系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 园区管理系统 |
+| 分区 | park |
+| 英文全称 | Park Management |
+| 描述 | 提供园区综合管理功能 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-park |
+
+## 功能说明
+
+- 提供园区综合管理功能
+- 支持用户管理和认证
+- 提供园区设备和资源管理
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-park/
+├── service-park-api/         # API 接口模块
+├── service-park-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 33 - 0
service-pm/README.md

@@ -0,0 +1,33 @@
+# 项目管理系统
+
+## 模块信息
+
+| 项目 | 内容 |
+|------|------|
+| 所属系统(模块) | 项目管理系统 |
+| 分区 | pm |
+| 英文全称 | Project Management |
+| 描述 | 提供项目管理模块 |
+| 模块分类 | 应用模块 |
+| 工程模块 | service-pm |
+
+## 功能说明
+
+- 提供项目管理模块
+- 支持项目的创建、管理和跟踪
+- 提供项目进度和资源管理
+
+## 技术栈
+
+- Java
+- Spring Boot
+- MyBatis
+
+## 模块结构
+
+```
+service-pm/
+├── service-pm-api/         # API 接口模块
+├── service-pm-biz/         # 业务逻辑模块
+└── pom.xml                  # 模块依赖配置
+```

+ 17 - 0
service-rule/pom.xml

@@ -0,0 +1,17 @@
+<?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>
+        <artifactId>usky-modules</artifactId>
+        <groupId>com.usky</groupId>
+        <version>0.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>service-rule</artifactId>
+    <packaging>pom</packaging>
+    <modules>
+        <module>service-rule-biz</module>
+        <module>service-rule-api</module>
+    </modules>
+</project>

+ 26 - 0
service-rule/service-rule-api/pom.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+        <artifactId>service-rule</artifactId>
+        <groupId>com.usky</groupId>
+        <version>0.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>service-rule-api</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>usky-common-core</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

+ 101 - 0
service-rule/service-rule-biz/pom.xml

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+        <artifactId>service-rule</artifactId>
+        <groupId>com.usky</groupId>
+        <version>0.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>service-rule-biz</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>common-cloud-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-rule-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>ruoyi-common-swagger</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.28</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.rocketmq</groupId>
+            <artifactId>rocketmq-spring-boot-starter</artifactId>
+            <version>2.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-quartz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.cache2k</groupId>
+            <artifactId>cache2k-core</artifactId>
+            <version>2.6.1.Final</version> <!-- 稳定版,兼容JDK8+ -->
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <version>13.0</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-tsdb-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-transfer-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+        <!-- FastJSON2(高性能 JSON 解析) -->
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+            <version>2.0.48</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.2.6.RELEASE</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 44 - 0
service-rule/service-rule-biz/src/main/java/com/usky/rule/RuoYiSystemApplication.java

@@ -0,0 +1,44 @@
+package com.usky.rule;
+
+import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
+import org.mybatis.spring.annotation.MapperScan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.core.env.Environment;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 数据规则引擎服务(对标 leo-rule-engine + MQTT 上报链路,RocketMQ 订阅触发)
+ */
+@EnableCustomSwagger2
+@EnableFeignClients(basePackages = "com.usky")
+@EnableScheduling
+@MapperScan("com.usky.rule.mapper")
+@ComponentScan("com.usky")
+@SpringBootApplication
+public class RuoYiSystemApplication {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RuoYiSystemApplication.class);
+
+    public static void main(String[] args) throws UnknownHostException {
+        ConfigurableApplicationContext application = SpringApplication.run(RuoYiSystemApplication.class, args);
+        Environment env = application.getEnvironment();
+        String ip = InetAddress.getLocalHost().getHostAddress();
+        String port = env.getProperty("server.port");
+        String path = env.getProperty("server.servlet.context-path");
+        LOGGER.info("\n----------------------------------------------------------\n\t" +
+                "Application is running! Access URLs:\n\t" +
+                "Local: \t\thttp://localhost:" + port + (path == null ? "" : path) + "/\n\t" +
+                "External: \thttp://" + ip + ":" + port + (path == null ? "" : path) + "/\n\t" +
+                "Api: \t\thttp://" + ip + ":" + port + (path == null ? "" : path) + "/swagger-ui/index.html\n\t" +
+                "----------------------------------------------------------");
+    }
+}

+ 37 - 0
service-rule/service-rule-biz/src/main/java/com/usky/rule/cache/DeviceAcqTriggerCooldownCache.java

@@ -0,0 +1,37 @@
+package com.usky.rule.cache;
+
+import com.usky.common.redis.core.RedisHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 设备采集触发规则成功执行动作后的冷却窗口,避免短时间内频繁上报反复执行同一规则。
+ */
+@Component
+public class DeviceAcqTriggerCooldownCache {
+
+    private static final String KEY_PREFIX = "ruleEngine:deviceAcqCooldown:";
+
+    @Autowired
+    private RedisHelper redisHelper;
+
+    public boolean isInCooldown(Long ruleEngineId, String deviceId) {
+        return redisHelper.hasKey(buildKey(ruleEngineId, deviceId));
+    }
+
+    /**
+     * 执行成功后调用,在 {@code cooldownSeconds} 秒内同规则同设备不再触发动作。
+     */
+    public void startCooldown(Long ruleEngineId, String deviceId, long cooldownSeconds) {
+        if (cooldownSeconds <= 0) {
+            return;
+        }
+        redisHelper.set(buildKey(ruleEngineId, deviceId), "1", cooldownSeconds, TimeUnit.SECONDS);
+    }
+
+    private static String buildKey(Long ruleEngineId, String deviceId) {
+        return KEY_PREFIX + ruleEngineId + ":" + deviceId;
+    }
+}

+ 113 - 0
service-rule/service-rule-biz/src/main/java/com/usky/rule/cache/DeviceTriggerIncludeMinuteCache.java

@@ -0,0 +1,113 @@
+package com.usky.rule.cache;
+
+import com.usky.common.redis.core.RedisHelper;
+import com.usky.rule.util.JsonUtil;
+import com.usky.rule.vo.ConditionExpression;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 设备触发“包含分钟”条件缓存,使用 RedisHelper 存储。
+ * 单条条件过期时间 1 天;按 ruleEngineId 可批量删除。
+ */
+@Component
+public class DeviceTriggerIncludeMinuteCache {
+
+    private static final String KEY_PREFIX = "ruleEngine:condition:";
+    private static final String INDEX_KEY_PREFIX = "ruleEngine:keys:";
+    private static final long EXPIRE_DAYS = 1L;
+
+    @Autowired
+    private RedisHelper redisHelper;
+
+    public ConditionExpression getConditions(Long ruleEngineId, String device, String identifier, String expression) {
+        String key = buildConditionKey(ruleEngineId, device, identifier, expression);
+        if (!redisHelper.hasKey(key)) {
+            return null;
+        }
+        Object val = redisHelper.get(key);
+        if (val == null) {
+            return null;
+        }
+        return JsonUtil.toObject(val.toString(), ConditionExpression.class);
+    }
+
+    public void setCondition(Long ruleEngineId, String device, String identifier, ConditionExpression condition) {
+        String key = buildConditionKey(ruleEngineId, device, identifier, condition.getExpression());
+        redisHelper.set(key, JsonUtil.toJson(condition), EXPIRE_DAYS, TimeUnit.DAYS);
+        addToIndex(ruleEngineId, key);
+    }
+
+    public void removeCondition(Long ruleEngineId, String device, String identifier, String expression) {
+        String key = buildConditionKey(ruleEngineId, device, identifier, expression);
+        redisHelper.delete(key);
+        removeFromIndex(ruleEngineId, key);
+    }
+
+    public void removeConditions(Long ruleEngineId, String device, String identifier, List<String> expressionList) {
+        if (expressionList == null || expressionList.isEmpty()) {
+            return;
+        }
+        for (String expression : expressionList) {
+            removeCondition(ruleEngineId, device, identifier, expression);
+        }
+    }
+
+    public void deleteConditions(Long ruleEngineId) {
+        String indexKey = INDEX_KEY_PREFIX + ruleEngineId;
+        if (!redisHelper.hasKey(indexKey)) {
+            return;
+        }
+        Object val = redisHelper.get(indexKey);
+        if (val != null) {
+            List<String> keys = JsonUtil.parseJsonArray(val.toString(), String.class);
+            if (keys != null) {
+                for (String key : keys) {
+                    redisHelper.delete(key);
+                }
+            }
+        }
+        redisHelper.delete(indexKey);
+    }
+
+    private static String buildConditionKey(Long ruleEngineId, String device, String identifier, String expression) {
+        return KEY_PREFIX + ruleEngineId + ":" + device + ":" + identifier + ":" + expression;
+    }
+
+    private void addToIndex(Long ruleEngineId, String conditionKey) {
+        String indexKey = INDEX_KEY_PREFIX + ruleEngineId;
+        List<String> keys = getIndexKeys(indexKey);
+        if (!keys.contains(conditionKey)) {
+            keys.add(conditionKey);
+            redisHelper.set(indexKey, JsonUtil.toJson(keys));
+        }
+    }
+
+    private void removeFromIndex(Long ruleEngineId, String conditionKey) {
+        String indexKey = INDEX_KEY_PREFIX + ruleEngineId;
+        List<String> keys = getIndexKeys(indexKey);
+        if (keys.remove(conditionKey)) {
+            if (keys.isEmpty()) {
+                redisHelper.delete(indexKey);
+            } else {
+                redisHelper.set(indexKey, JsonUtil.toJson(keys));
+            }
+        }
+    }
+
+    private List<String> getIndexKeys(String indexKey) {
+        if (!redisHelper.hasKey(indexKey)) {
+            return new ArrayList<>();
+        }
+        Object val = redisHelper.get(indexKey);
+        if (val == null) {
+            return new ArrayList<>();
+        }
+        List<String> list = JsonUtil.parseJsonArray(val.toString(), String.class);
+        return list != null ? list : new ArrayList<>();
+    }
+}

+ 32 - 0
service-rule/service-rule-biz/src/main/java/com/usky/rule/cache/RuleEngineCache.java

@@ -0,0 +1,32 @@
+package com.usky.rule.cache;
+
+import com.usky.rule.vo.trigger.DeviceTrigger;
+import com.usky.rule.vo.trigger.SpaceTrigger;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.cache2k.Cache;
+import org.cache2k.Cache2kBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RuleEngineCache {
+    public RuleEngineCache() {
+    }
+
+    @Bean(
+            name = {"consumptionTriggerCache"}
+    )
+    public Cache<Long, List<DeviceTrigger>> consumptionTriggerCache() {
+        return (new Cache2kBuilder<Long, List<DeviceTrigger>>() {
+        }).name("consumptionTriggerCache").eternal(false).expireAfterWrite(1L, TimeUnit.HOURS).entryCapacity(100000L).build();
+    }
+
+    @Bean(
+            name = {"spaceTriggerCache"}
+    )
+    public Cache<Long, List<SpaceTrigger>> spaceTriggerCache() {
+        return (new Cache2kBuilder<Long, List<SpaceTrigger>>() {
+        }).name("spaceTriggerCache").eternal(false).expireAfterWrite(1L, TimeUnit.HOURS).entryCapacity(100000L).build();
+    }
+}

+ 132 - 0
service-rule/service-rule-biz/src/main/java/com/usky/rule/config/CronTaskManager.java

@@ -0,0 +1,132 @@
+package com.usky.rule.config;
+
+import com.usky.rule.util.RuleEngineUtil;
+import com.usky.rule.vo.action.RuleEngineAction;
+import com.usky.rule.vo.constraint.CronConstraint;
+import com.usky.rule.vo.constraint.DeviceConstraint;
+import com.usky.rule.jobs.ConsumptionJob;
+import com.usky.rule.listeners.CommonListener;
+import com.usky.rule.subscribe.TriggerDeviceUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.CronTrigger;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.TriggerBuilder;
+import org.quartz.TriggerKey;
+import org.quartz.impl.matchers.GroupMatcher;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CronTaskManager {
+    private Scheduler scheduler;
+    private TriggerDeviceUtil triggerDeviceUtil;
+
+    public void addJob(String jobName, String jobGroup, String cronExpression, Class<? extends Job> jobClass, List<CronConstraint> cronConstraintList, List<DeviceConstraint> deviceConstraints, List<RuleEngineAction> actions, Long ruleEngineId, Long projectId, Long spaceId) {
+        JobDataMap jobDataMap = new JobDataMap();
+        jobDataMap.put("actions", actions);
+        jobDataMap.put("ruleEngineId", ruleEngineId);
+        jobDataMap.put("projectId", projectId);
+        jobDataMap.put("spaceId", spaceId);
+        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).usingJobData(jobDataMap).build();
+        CronTrigger cronTrigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)).build();
+
+        try {
+            if (cronConstraintList != null || deviceConstraints != null) {
+                CommonListener jobListener = new CommonListener(this.getListenerName(ruleEngineId), this.triggerDeviceUtil, cronConstraintList, deviceConstraints);
+                this.scheduler.getListenerManager().addJobListener(jobListener, GroupMatcher.jobGroupEquals(jobGroup));
+            }
+
+            this.scheduler.scheduleJob(jobDetail, cronTrigger);
+        } catch (SchedulerException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    public String getListenerName(Long ruleEngineId) {
+        return "ruleEngineListener-" + ruleEngineId;
+    }
+
+    public void updateJob(String jobName, String jobGroup, String cronExpression) throws SchedulerException {
+        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
+        CronTrigger cronTrigger = (CronTrigger)this.scheduler.getTrigger(triggerKey);
+        if (cronTrigger == null) {
+            throw new SchedulerException("定时任务不存在");
+        } else {
+            String oldCronExpression = cronTrigger.getCronExpression();
+            if (!oldCronExpression.equalsIgnoreCase(cronExpression)) {
+                CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
+                cronTrigger = (CronTrigger)cronTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+                this.scheduler.rescheduleJob(triggerKey, cronTrigger);
+            }
+
+        }
+    }
+
+    public void deleteJob(String jobName, String jobGroup) throws SchedulerException {
+        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
+        if (this.scheduler.checkExists(jobKey)) {
+            this.scheduler.deleteJob(jobKey);
+        }
+
+    }
+
+    public boolean deleteAllJobsInJobGroup(Long ruleEngineId) {
+        String jobGroup = RuleEngineUtil.getJobGroup(ruleEngineId);
+
+        try {
+            List<JobKey> jobKeys = new ArrayList(this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(jobGroup)));
+            if (!jobKeys.isEmpty()) {
+                this.scheduler.deleteJobs(jobKeys);
+                this.scheduler.getListenerManager().removeJobListenerMatcher(this.getListenerName(ruleEngineId), GroupMatcher.jobGroupEquals(jobGroup));
+            }
+
+            return true;
+        } catch (SchedulerException var4) {
+            return false;
+        }
+    }
+
+    public void pauseAllJobs() throws SchedulerException {
+        this.scheduler.pauseAll();
+    }
+
+    public void resumeAllJobs() throws SchedulerException {
+        this.scheduler.resumeAll();
+    }
+
+    public void resumePausedJobs() throws SchedulerException {
+        for(String groupName : this.scheduler.getPausedTriggerGroups()) {
+            for(JobKey jobKey : this.scheduler.getJobKeys(GroupMatcher.groupEquals(groupName))) {
+                this.scheduler.resumeJob(jobKey);
+            }
+        }
+
+    }
+
+    public void performConsumptionTask() {
+        JobDetail consumptionJob = JobBuilder.newJob(ConsumptionJob.class).withIdentity("consumption", "device").build();
+        CronTrigger consumptionTrigger = (CronTrigger)TriggerBuilder.newTrigger().forJob(consumptionJob).withIdentity("consumptionTrigger").withSchedule(CronScheduleBuilder.cronSchedule("0 0 * * * ?")).build();
+//        JobDetail spaceJob = JobBuilder.newJob(SpaceJob.class).withIdentity("space").build();
+//        CronTrigger spaceTrigger = (CronTrigger)TriggerBuilder.newTrigger().forJob(spaceJob).withIdentity("spaceTrigger", "space").withSchedule(CronScheduleBuilder.cronSchedule("0 0 * * * ?")).build();
+
+        try {
+            this.scheduler.scheduleJob(consumptionJob, consumptionTrigger);
+//            this.scheduler.scheduleJob(spaceJob, spaceTrigger);
+        } catch (SchedulerException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public CronTaskManager(final Scheduler scheduler, final TriggerDeviceUtil triggerDeviceUtil) {
+        this.scheduler = scheduler;
+        this.triggerDeviceUtil = triggerDeviceUtil;
+    }
+}

+ 22 - 0
service-rule/service-rule-biz/src/main/java/com/usky/rule/constant/DateTimeConstants.java

@@ -0,0 +1,22 @@
+package com.usky.rule.constant;
+
+import com.usky.rule.util.DateTimeUtil;
+import java.time.format.DateTimeFormatter;
+
+public interface DateTimeConstants {
+    String PATTERN_NUMERIC_DATETIME = "yyyyMMddHHmmss";
+    String PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss";
+    String PATTERN_DATE = "yyyy-MM-dd";
+    String PATTERN_TIME = "HH:mm:ss";
+    DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter DATE_TIME_HOUR_FORMATTER_ZH_CN = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter DATE_TIME_DAY_FORMATTER_ZH_CN = DateTimeFormatter.ofPattern("yyyy年MM月dd日").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter DATE_TIME_MONTH_FORMATTER_ZH_CN = DateTimeFormatter.ofPattern("yyyy年MM月").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter DATE_TIME_YEAR_FORMATTER_ZH_CN = DateTimeFormatter.ofPattern("yyyy年").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter SIMPLE_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter SIMPLE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter SIMPLE_TIME_FORMATTER = DateTimeFormatter.ofPattern("HHmmss").withZone(DateTimeUtil.getZoneId());
+    DateTimeFormatter DB_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(DateTimeUtil.getZoneId());
+}

+ 36 - 0
service-rule/service-rule-biz/src/main/java/com/usky/rule/constant/RegExpConstants.java

@@ -0,0 +1,36 @@
+package com.usky.rule.constant;
+
+import java.util.regex.Pattern;
+
+public interface RegExpConstants {
+    String DATE_TIME = "^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)\\s+([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$";
+    Pattern DATE_TIME_PATTERN = Pattern.compile("^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)\\s+([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$");
+    String DATE = "^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$";
+    Pattern DATE_PATTERN = Pattern.compile("^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$");
+    String TIME = "^([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$";
+    Pattern TIME_PATTERN = Pattern.compile("^([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$");
+    String SIMPLE_DATE_TIME = "^(?:(?!0000)[0-9]{4}(?:(?:0[1-9]|1[0-2])(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])(?:29|30)|(?:0[13578]|1[02])31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)0229)([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]$";
+    Pattern SIMPLE_DATE_TIME_PATTERN = Pattern.compile("^(?:(?!0000)[0-9]{4}(?:(?:0[1-9]|1[0-2])(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])(?:29|30)|(?:0[13578]|1[02])31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)0229)([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]$");
+    String SIMPLE_DATE = "^(?:(?!0000)[0-9]{4}(?:(?:0[1-9]|1[0-2])(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])(?:29|30)|(?:0[13578]|1[02])31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)0229)$";
+    Pattern SIMPLE_DATE_PATTERN = Pattern.compile("^(?:(?!0000)[0-9]{4}(?:(?:0[1-9]|1[0-2])(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])(?:29|30)|(?:0[13578]|1[02])31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)0229)$");
+    String SIMPLE_TIME = "^([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]$";
+    Pattern SIMPLE_TIME_PATTERN = Pattern.compile("^([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]$");
+    String IP = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$";
+    Pattern IP_PATTERN = Pattern.compile("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$");
+    String DEVICE_ID = "^[0-9a-zA-Z]{13}$";
+    Pattern DEVICE_ID_PATTERN = Pattern.compile("^[0-9a-zA-Z]{13}$");
+    String GATEWAY_ID = "^[0-9a-zA-Z]{13}$";
+    Pattern GATEWAY_ID_PATTERN = Pattern.compile("^[0-9a-zA-Z]{13}$");
+    String PRODUCT_IDENTIFIER = "^[0-9A-Z]{7}$";
+    Pattern PRODUCT_IDENTIFIER_PATTERN = Pattern.compile("^[0-9A-Z]{7}$");
+    String GATEWAY_SECRET = "^(?![0-9]+$)[0-9A-Za-z]{16}$";
+    Pattern GATEWAY_SECRET_PATTERN = Pattern.compile("^(?![0-9]+$)[0-9A-Za-z]{16}$");
+    String PRODUCT_TEMPLATE_IDENTIFIER = "[0-9a-zA-Z]{18}$";
+    String USERNAME = "^(?![0-9]+$)[0-9A-Za-z]{4,20}$";
+    String isNumeric = "-?\\d+(\\.\\d+)?";
+    String isInteger = "-?\\d+";
+    String PASSWORD = "^(?:(?=.*\\d)(?=.*[a-zA-Z])|(?=.*\\d)(?=.*[^a-zA-Z\\d])|(?=.*[a-zA-Z])(?=.*[^a-zA-Z\\d]))[a-zA-Z\\d\\S]{8,18}$";
+    String PHONE = "^[1]\\d{10}$";
+    String HOUR = "^(2[0-3]|[0-1]?\\d)$";
+    String MONTH = "^(2[0-8]|[0-1]?[1-9])$";
+}

+ 108 - 0
service-rule/service-rule-biz/src/main/java/com/usky/rule/controller/MybatisGeneratorUtils.java

@@ -0,0 +1,108 @@
+package com.usky.rule.controller;//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;
+import com.baomidou.mybatisplus.generator.AutoGenerator;
+import com.baomidou.mybatisplus.generator.InjectionConfig;
+import com.baomidou.mybatisplus.generator.config.*;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author yq
+ * @date 2021/7/6 11:42
+ */
+public class MybatisGeneratorUtils {
+    public static void main(String[] args) {
+
+            shell("service-rule","service-rule-biz");
+    }
+
+    private static void shell(String parentName,String model) {
+
+        AutoGenerator mpg = new AutoGenerator();
+        //1、全局配置
+        GlobalConfig gc = new GlobalConfig();
+//        File file = new File(model);
+//        String path = file.getAbsolutePath();
+        String projectPath = System.getProperty("user.dir");
+        projectPath+="/"+parentName;
+        projectPath+="/"+model;
+        gc.setOutputDir(projectPath+ "/src/main/java");  //生成路径(一般都是生成在此项目的src/main/java下面)
+        //修改为自己的名字
+        gc.setAuthor("zyj"); //设置作者
+        gc.setOpen(false);
+        gc.setFileOverride(true); //第二次生成会把第一次生成的覆盖掉
+        gc.setServiceName("%sService"); //生成的service接口名字首字母是否为I,这样设置就没有
+        gc.setBaseResultMap(true); //生成resultMap
+        mpg.setGlobalConfig(gc);
+
+        //2、数据源配置
+        //修改数据源
+        DataSourceConfig dsc = new DataSourceConfig();
+        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("root");
+        dsc.setPassword("yt123456");
+        mpg.setDataSource(dsc);
+
+        // 3、包配置
+        PackageConfig pc = new PackageConfig();
+        pc.setParent("com.usky.rule");
+        pc.setController("controller.web");
+        pc.setEntity("domain");
+        pc.setMapper("mapper");
+        pc.setService("service");
+        pc.setServiceImpl("service.impl");
+//        pc.setXml("mapper.demo");
+        //pc.setModuleName("test");
+        mpg.setPackageInfo(pc);
+
+        // 4、策略配置
+        StrategyConfig strategy = new StrategyConfig();
+        strategy.setNaming(NamingStrategy.underline_to_camel);
+        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
+        strategy.setSuperMapperClass("com.usky.common.mybatis.core.CrudMapper");
+        strategy.setSuperServiceClass("com.usky.common.mybatis.core.CrudService");
+        strategy.setSuperServiceImplClass("com.usky.common.mybatis.core.AbstractCrudService");
+        // strategy.setTablePrefix("t_"); // 表名前缀
+        strategy.setEntityLombokModel(true); //使用lombok
+        //修改自己想要生成的表
+        strategy.setInclude("base_space");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+        mpg.setStrategy(strategy);
+
+        // 关闭默认 xml 生成,调整生成 至 根目录
+        //修改对应的模块名称
+        TemplateConfig tc = new TemplateConfig();
+        // 自定义配置
+        InjectionConfig cfg = new InjectionConfig() {
+            @Override
+            public void initMap() {
+                // to do nothing
+            }
+        };
+        //如果模板引擎是 velocity
+        String templatePath = "/templates/mapper.xml.vm";
+        // 自定义输出配置
+        List<FileOutConfig> focList = new ArrayList<>();
+        // 自定义配置会被优先输出
+        String finalProjectPath = projectPath;
+        focList.add(new FileOutConfig(templatePath) {
+            @Override
+            public String outputFile(TableInfo tableInfo) {
+                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
+                return finalProjectPath + "/src/main/resources/mapper/rule" + "/"
+                        + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
+            }
+        });
+        cfg.setFileOutConfigList(focList);
+        mpg.setCfg(cfg);
+        tc.setXml(null);
+        mpg.setTemplate(tc);
+        //5、执行
+        mpg.execute();
+    }
+}

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

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

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