Browse Source

视频提交

lipenghui 2 days ago
parent
commit
bad801020b
100 changed files with 9454 additions and 4 deletions
  1. 3 3
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
  2. 20 1
      yudao-module-pms/yudao-module-pms-biz/pom.xml
  3. 32 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/config/ThreadPoolTaskConfig.java
  4. 114 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupController.java
  5. 95 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupDetailController.java
  6. 43 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupDetailPageReqVO.java
  7. 51 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupDetailRespVO.java
  8. 44 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupDetailSaveReqVO.java
  9. 33 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupPageReqVO.java
  10. 42 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupRespVO.java
  11. 31 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupSaveReqVO.java
  12. 98 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/measure/IotMeasureBookController.java
  13. 66 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/measure/vo/IotMeasureBookPageReqVO.java
  14. 79 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/measure/vo/IotMeasureBookRespVO.java
  15. 64 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/measure/vo/IotMeasureBookSaveReqVO.java
  16. 107 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/ITSLCache.java
  17. 41 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/ITSLValueCache.java
  18. 354 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/TSLCacheImpl.java
  19. 149 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/TSLValueCacheImpl.java
  20. 82 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/domain/ModbusConfig.java
  21. 115 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/category/IotYfCategoryController.java
  22. 51 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/category/vo/IotYfCategoryPageReqVO.java
  23. 62 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/category/vo/IotYfCategoryRespVO.java
  24. 48 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/category/vo/IotYfCategorySaveReqVO.java
  25. 68 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/config/MqttClientConfig.java
  26. 126 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/config/RuoYiConfig.java
  27. 157 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/constant/Constants.java
  28. 356 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/constant/YanfanConstant.java
  29. 126 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/core/BaseEntity.java
  30. 156 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/YfIotDeviceController.java
  31. 13 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/DeviceAlertCount.java
  32. 147 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/DeviceShortOutput.java
  33. 34 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/DeviceVo.java
  34. 118 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/YfIotDevicePageReqVO.java
  35. 146 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/YfIotDeviceRespVO.java
  36. 114 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/YfIotDeviceSaveReqVO.java
  37. 98 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/devicetemplate/IotYfDeviceTemplateController.java
  38. 19 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/devicetemplate/vo/IotYfDeviceTemplatePageReqVO.java
  39. 24 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/devicetemplate/vo/IotYfDeviceTemplateRespVO.java
  40. 19 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/devicetemplate/vo/IotYfDeviceTemplateSaveReqVO.java
  41. 20 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/AlarmMethod.java
  42. 61 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/AlarmType.java
  43. 22 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/ChannelStatus.java
  44. 5 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/ChannelType.java
  45. 30 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/DataEnum.java
  46. 15 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/DeviceChannelStatus.java
  47. 51 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/Direct.java
  48. 17 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/PTZType.java
  49. 8 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/SessionType.java
  50. 49 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/ThingsModelType.java
  51. 78 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/TopicType.java
  52. 98 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/group/IotYfGroupController.java
  53. 42 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/group/vo/IotYfGroupPageReqVO.java
  54. 50 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/group/vo/IotYfGroupRespVO.java
  55. 41 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/group/vo/IotYfGroupSaveReqVO.java
  56. 74 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/PlayerController.java
  57. 143 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/YfMediaServerController.java
  58. 91 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/vo/YfMediaServerPageReqVO.java
  59. 115 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/vo/YfMediaServerRespVO.java
  60. 104 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/vo/YfMediaServerSaveReqVO.java
  61. 120 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/PubMqttCallBack.java
  62. 246 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/PubMqttClient.java
  63. 16 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/Topics.java
  64. 14 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/TopicsPost.java
  65. 114 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/IotYfProductController.java
  66. 40 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/ChangeProductStatusModel.java
  67. 10 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/HttpParam.java
  68. 48 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/ImportThingsModelInput.java
  69. 150 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/IotYfProductPageReqVO.java
  70. 198 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/IotYfProductRespVO.java
  71. 153 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/IotYfProductSaveReqVO.java
  72. 14 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/userParmas.java
  73. 98 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/IotYfProtocolController.java
  74. 89 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/modbus/ModbusCode.java
  75. 45 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/vo/IotYfProtocolPageReqVO.java
  76. 54 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/vo/IotYfProtocolRespVO.java
  77. 47 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/vo/IotYfProtocolSaveReqVO.java
  78. 852 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/redis/RedisCache.java
  79. 161 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/redis/RedisKeyBuilder.java
  80. 195 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/server/VideoSessionManager.java
  81. 141 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/SipDate.java
  82. 119 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/config/IotYfSipConfigController.java
  83. 62 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/config/vo/IotYfSipConfigPageReqVO.java
  84. 77 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/config/vo/IotYfSipConfigRespVO.java
  85. 64 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/config/vo/IotYfSipConfigSaveReqVO.java
  86. 98 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/YfSipDeviceController.java
  87. 118 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/channel/YfSipDeviceChannelController.java
  88. 125 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/channel/vo/YfSipDeviceChannelPageReqVO.java
  89. 162 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/channel/vo/YfSipDeviceChannelRespVO.java
  90. 149 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/channel/vo/YfSipDeviceChannelSaveReqVO.java
  91. 85 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/vo/YfSipDevicePageReqVO.java
  92. 102 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/vo/YfSipDeviceRespVO.java
  93. 90 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/vo/YfSipDeviceSaveReqVO.java
  94. 210 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/CatalogHandler.java
  95. 15 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/IMessageHandler.java
  96. 27 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/MessageHandlerAbstract.java
  97. 94 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/MessageRequestProcessor.java
  98. 214 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/RegisterReqHandler.java
  99. 161 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/ReqAbstractHandler.java
  100. 18 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/ResponseMessageHandler.java

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java

@@ -63,9 +63,9 @@ public class BannerApplicationRunner implements ApplicationRunner {
                 System.out.println("[AI 大模型 yudao-module-ai - 已禁用][参考 https://doc.iocoder.cn/ai/build/ 开启]");
             }
             // IoT 物联网
-            if (isNotPresent("cn.iocoder.yudao.module.iot.framework.web.config.IotWebConfiguration")) {
-                System.out.println("[IoT 物联网 yudao-module-iot - 已禁用][参考 https://doc.iocoder.cn/iot/build/ 开启]");
-            }
+//            if (isNotPresent("cn.iocoder.yudao.module.iot.framework.web.config.IotWebConfiguration")) {
+//                System.out.println("[IoT 物联网 yudao-module-iot - 已禁用][参考 https://doc.iocoder.cn/iot/build/ 开启]");
+//            }
         });
     }
 

+ 20 - 1
yudao-module-pms/yudao-module-pms-biz/pom.xml

@@ -66,7 +66,12 @@
             <version>2.4.1-jdk8-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
-
+        <!-- sip -->
+        <dependency>
+            <groupId>javax.sip</groupId>
+            <artifactId>jain-sip-ri</artifactId>
+            <version>1.3.0-91</version>
+        </dependency>
         <!-- SAP接口相关 -->
         <dependency>
             <groupId>com.sap</groupId>
@@ -126,6 +131,20 @@
             <version>2.4.2-jdk8-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-xml</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.paho</groupId>
+            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
+            <version>1.2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.sip</groupId>
+            <artifactId>jain-sip-ri</artifactId>
+            <version>1.3.0-91</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 32 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/config/ThreadPoolTaskConfig.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.pms.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+@Configuration
+@EnableAsync(proxyTargetClass = true)
+public class ThreadPoolTaskConfig {
+    public static final int cpuNum = Runtime.getRuntime().availableProcessors();
+    private static final int corePoolSize = cpuNum;
+    private static final int maxPoolSize = cpuNum*2;
+    private static final int keepAliveTime = 30;
+    private static final int queueCapacity = 10000;
+    private static final String threadNamePrefix = "sip-";
+
+    @Bean("taskExecutor")
+    public ThreadPoolTaskExecutor taskExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(corePoolSize);
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setQueueCapacity(queueCapacity);
+        executor.setKeepAliveSeconds(keepAliveTime);
+        executor.setThreadNamePrefix(threadNamePrefix);
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        executor.initialize();
+        return executor;
+    }
+}

+ 114 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupController.java

@@ -0,0 +1,114 @@
+package cn.iocoder.yudao.module.pms.controller.admin.devicegroup;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.iocoder.yudao.module.pms.controller.admin.inspect.order.vo.IotInspectOrderRespVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.devicegroup.IotDeviceGroupDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.devicegroup.IotDeviceGroupDetailDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.inspect.IotInspectOrderDetailDO;
+import cn.iocoder.yudao.module.pms.service.devicegroup.IotDeviceGroupDetailService;
+import cn.iocoder.yudao.module.pms.service.devicegroup.IotDeviceGroupService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - PMS成套")
+@RestController
+@RequestMapping("/rq/iot-device-group")
+@Validated
+public class IotDeviceGroupController {
+
+    @Resource
+    private IotDeviceGroupService iotDeviceGroupService;
+    @Autowired
+    private IotDeviceGroupDetailService iotDeviceGroupDetailService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建PMS成套")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group:create')")
+    public CommonResult<Long> createIotDeviceGroup(@Valid @RequestBody IotDeviceGroupSaveReqVO createReqVO) {
+        return success(iotDeviceGroupService.createIotDeviceGroup(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新PMS成套")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group:update')")
+    public CommonResult<Boolean> updateIotDeviceGroup(@Valid @RequestBody IotDeviceGroupSaveReqVO updateReqVO) {
+        iotDeviceGroupService.updateIotDeviceGroup(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除PMS成套")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group:delete')")
+    public CommonResult<Boolean> deleteIotDeviceGroup(@RequestParam("id") Long id) {
+        iotDeviceGroupService.deleteIotDeviceGroup(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得PMS成套")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group:query')")
+    public CommonResult<IotDeviceGroupRespVO> getIotDeviceGroup(@RequestParam("id") Long id) {
+        IotDeviceGroupDO iotDeviceGroup = iotDeviceGroupService.getIotDeviceGroup(id);
+        IotDeviceGroupRespVO bean = BeanUtils.toBean(iotDeviceGroup, IotDeviceGroupRespVO.class);
+        List<IotDeviceGroupDetailDO> iotDeviceGroupDetailListByGroupId = iotDeviceGroupDetailService.getIotDeviceGroupDetailListByGroupId(iotDeviceGroup.getId());
+        bean.setDetails(iotDeviceGroupDetailListByGroupId);
+        return success(bean);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得PMS成套分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group:query')")
+    public CommonResult<PageResult<IotDeviceGroupRespVO>> getIotDeviceGroupPage(@Valid IotDeviceGroupPageReqVO pageReqVO) {
+        PageResult<IotDeviceGroupDO> pageResult = iotDeviceGroupService.getIotDeviceGroupPage(pageReqVO);
+        List<IotDeviceGroupRespVO> collect = pageResult.getList().stream().map(e -> {
+            IotDeviceGroupRespVO bean = BeanUtil.toBean(e, IotDeviceGroupRespVO.class);
+            List<IotDeviceGroupDetailDO> iotDeviceGroupDetailListByGroupId = iotDeviceGroupDetailService.getIotDeviceGroupDetailListByGroupId(e.getId());
+            bean.setDetails(iotDeviceGroupDetailListByGroupId);
+            return bean;
+        }).collect(Collectors.toList());
+        return success(new PageResult<>(collect, pageResult.getTotal()));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出PMS成套 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotDeviceGroupExcel(@Valid IotDeviceGroupPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotDeviceGroupDO> list = iotDeviceGroupService.getIotDeviceGroupPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "PMS成套.xls", "数据", IotDeviceGroupRespVO.class,
+                        BeanUtils.toBean(list, IotDeviceGroupRespVO.class));
+    }
+
+}

+ 95 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupDetailController.java

@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.pms.controller.admin.devicegroup;
+
+import cn.iocoder.yudao.module.pms.dal.dataobject.devicegroup.IotDeviceGroupDetailDO;
+import cn.iocoder.yudao.module.pms.service.devicegroup.IotDeviceGroupDetailService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - PMS成套明细")
+@RestController
+@RequestMapping("/rq/iot-device-group-detail")
+@Validated
+public class IotDeviceGroupDetailController {
+
+    @Resource
+    private IotDeviceGroupDetailService iotDeviceGroupDetailService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建PMS成套明细")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group-detail:create')")
+    public CommonResult<Long> createIotDeviceGroupDetail(@Valid @RequestBody IotDeviceGroupDetailSaveReqVO createReqVO) {
+        return success(iotDeviceGroupDetailService.createIotDeviceGroupDetail(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新PMS成套明细")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group-detail:update')")
+    public CommonResult<Boolean> updateIotDeviceGroupDetail(@Valid @RequestBody IotDeviceGroupDetailSaveReqVO updateReqVO) {
+        iotDeviceGroupDetailService.updateIotDeviceGroupDetail(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除PMS成套明细")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group-detail:delete')")
+    public CommonResult<Boolean> deleteIotDeviceGroupDetail(@RequestParam("id") Long id) {
+        iotDeviceGroupDetailService.deleteIotDeviceGroupDetail(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得PMS成套明细")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group-detail:query')")
+    public CommonResult<IotDeviceGroupDetailRespVO> getIotDeviceGroupDetail(@RequestParam("id") Long id) {
+        IotDeviceGroupDetailDO iotDeviceGroupDetail = iotDeviceGroupDetailService.getIotDeviceGroupDetail(id);
+        return success(BeanUtils.toBean(iotDeviceGroupDetail, IotDeviceGroupDetailRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得PMS成套明细分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group-detail:query')")
+    public CommonResult<PageResult<IotDeviceGroupDetailRespVO>> getIotDeviceGroupDetailPage(@Valid IotDeviceGroupDetailPageReqVO pageReqVO) {
+        PageResult<IotDeviceGroupDetailDO> pageResult = iotDeviceGroupDetailService.getIotDeviceGroupDetailPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotDeviceGroupDetailRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出PMS成套明细 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-device-group-detail:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotDeviceGroupDetailExcel(@Valid IotDeviceGroupDetailPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotDeviceGroupDetailDO> list = iotDeviceGroupDetailService.getIotDeviceGroupDetailPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "PMS成套明细.xls", "数据", IotDeviceGroupDetailRespVO.class,
+                        BeanUtils.toBean(list, IotDeviceGroupDetailRespVO.class));
+    }
+
+}

+ 43 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupDetailPageReqVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.pms.controller.admin.devicegroup;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - PMS成套明细分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotDeviceGroupDetailPageReqVO extends PageParam {
+
+    @Schema(description = "成套名称", example = "17288")
+    private Long groupId;
+
+    @Schema(description = "设备id", example = "29616")
+    private Long deviceId;
+
+    @Schema(description = "设备名称", example = "王五")
+    private String deviceName;
+
+    @Schema(description = "设备编码")
+    private String deviceCode;
+
+    @Schema(description = "是否主设备")
+    private Boolean ifMaster;
+
+    @Schema(description = "描述", example = "随便")
+    private String remark;
+
+    @Schema(description = "部门id", example = "2916")
+    private Long deptId;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 51 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupDetailRespVO.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.pms.controller.admin.devicegroup;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - PMS成套明细 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotDeviceGroupDetailRespVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "18152")
+    @ExcelProperty("主键")
+    private Long id;
+
+    @Schema(description = "成套名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "17288")
+    @ExcelProperty("成套名称")
+    private Long groupId;
+
+    @Schema(description = "设备id", requiredMode = Schema.RequiredMode.REQUIRED, example = "29616")
+    @ExcelProperty("设备id")
+    private Long deviceId;
+
+    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @ExcelProperty("设备名称")
+    private String deviceName;
+
+    @Schema(description = "设备编码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("设备编码")
+    private String deviceCode;
+
+    @Schema(description = "是否主设备", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("是否主设备")
+    private Boolean ifMaster;
+
+    @Schema(description = "描述", example = "随便")
+    @ExcelProperty("描述")
+    private String remark;
+
+    @Schema(description = "部门id", requiredMode = Schema.RequiredMode.REQUIRED, example = "2916")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 44 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupDetailSaveReqVO.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.pms.controller.admin.devicegroup;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.*;
+
+@Schema(description = "管理后台 - PMS成套明细新增/修改 Request VO")
+@Data
+public class IotDeviceGroupDetailSaveReqVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "18152")
+    private Long id;
+
+    @Schema(description = "成套名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "17288")
+    @NotNull(message = "成套名称不能为空")
+    private Long groupId;
+
+    @Schema(description = "设备id", requiredMode = Schema.RequiredMode.REQUIRED, example = "29616")
+    @NotNull(message = "设备id不能为空")
+    private Long deviceId;
+
+    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @NotEmpty(message = "设备名称不能为空")
+    private String deviceName;
+
+    @Schema(description = "设备编码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "设备编码不能为空")
+    private String deviceCode;
+
+    @Schema(description = "是否主设备", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "是否主设备不能为空")
+    private Boolean ifMaster;
+
+    @Schema(description = "描述", example = "随便")
+    private String remark;
+
+    @Schema(description = "部门id", requiredMode = Schema.RequiredMode.REQUIRED, example = "2916")
+    @NotNull(message = "部门id不能为空")
+    private Long deptId;
+
+}

+ 33 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupPageReqVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.pms.controller.admin.devicegroup;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - PMS成套分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotDeviceGroupPageReqVO extends PageParam {
+
+    @Schema(description = "成套名称", example = "赵六")
+    private String name;
+
+    @Schema(description = "成套编码")
+    private String code;
+
+    @Schema(description = "描述", example = "你说的对")
+    private String remark;
+
+    @Schema(description = "部门id", example = "646")
+    private Long deptId;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 42 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupRespVO.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.pms.controller.admin.devicegroup;
+
+import cn.iocoder.yudao.module.pms.dal.dataobject.devicegroup.IotDeviceGroupDetailDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - PMS成套 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotDeviceGroupRespVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "12540")
+    @ExcelProperty("主键")
+    private Long id;
+
+    @Schema(description = "成套名称", example = "赵六")
+    @ExcelProperty("成套名称")
+    private String name;
+
+    @Schema(description = "成套编码")
+    @ExcelProperty("成套编码")
+    private String code;
+
+    @Schema(description = "描述", example = "你说的对")
+    @ExcelProperty("描述")
+    private String remark;
+
+    @Schema(description = "部门id", requiredMode = Schema.RequiredMode.REQUIRED, example = "646")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    private List<IotDeviceGroupDetailDO> details;
+}

+ 31 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/devicegroup/IotDeviceGroupSaveReqVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.pms.controller.admin.devicegroup;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - PMS成套新增/修改 Request VO")
+@Data
+public class IotDeviceGroupSaveReqVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "12540")
+    private Long id;
+
+    @Schema(description = "成套名称", example = "赵六")
+    private String name;
+
+    @Schema(description = "成套编码")
+    private String code;
+
+    @Schema(description = "描述", example = "你说的对")
+    private String remark;
+
+    @Schema(description = "部门id", requiredMode = Schema.RequiredMode.REQUIRED, example = "646")
+    @NotNull(message = "部门id不能为空")
+    private Long deptId;
+
+
+    private List<IotDeviceGroupDetailSaveReqVO> details;
+}

+ 98 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/measure/IotMeasureBookController.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.measure;
+
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.measure.vo.IotMeasureBookPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.measure.vo.IotMeasureBookRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.measure.vo.IotMeasureBookSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.qhse.measure.IotMeasureBookDO;
+import cn.iocoder.yudao.module.pms.service.qhse.measure.IotMeasureBookService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - 计量器具台账")
+@RestController
+@RequestMapping("/rq/iot-measure-book")
+@Validated
+public class IotMeasureBookController {
+
+    @Resource
+    private IotMeasureBookService iotMeasureBookService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建计量器具台账")
+    @PreAuthorize("@ss.hasPermission('rq:iot-measure-book:create')")
+    public CommonResult<Long> createIotMeasureBook(@Valid @RequestBody IotMeasureBookSaveReqVO createReqVO) {
+        return success(iotMeasureBookService.createIotMeasureBook(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新计量器具台账")
+    @PreAuthorize("@ss.hasPermission('rq:iot-measure-book:update')")
+    public CommonResult<Boolean> updateIotMeasureBook(@Valid @RequestBody IotMeasureBookSaveReqVO updateReqVO) {
+        iotMeasureBookService.updateIotMeasureBook(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除计量器具台账")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-measure-book:delete')")
+    public CommonResult<Boolean> deleteIotMeasureBook(@RequestParam("id") Long id) {
+        iotMeasureBookService.deleteIotMeasureBook(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得计量器具台账")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-measure-book:query')")
+    public CommonResult<IotMeasureBookRespVO> getIotMeasureBook(@RequestParam("id") Long id) {
+        IotMeasureBookDO iotMeasureBook = iotMeasureBookService.getIotMeasureBook(id);
+        return success(BeanUtils.toBean(iotMeasureBook, IotMeasureBookRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得计量器具台账分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-measure-book:query')")
+    public CommonResult<PageResult<IotMeasureBookRespVO>> getIotMeasureBookPage(@Valid IotMeasureBookPageReqVO pageReqVO) {
+        PageResult<IotMeasureBookDO> pageResult = iotMeasureBookService.getIotMeasureBookPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotMeasureBookRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出计量器具台账 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-measure-book:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotMeasureBookExcel(@Valid IotMeasureBookPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotMeasureBookDO> list = iotMeasureBookService.getIotMeasureBookPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "计量器具台账.xls", "数据", IotMeasureBookRespVO.class,
+                        BeanUtils.toBean(list, IotMeasureBookRespVO.class));
+    }
+
+}

+ 66 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/measure/vo/IotMeasureBookPageReqVO.java

@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.measure.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 计量器具台账分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotMeasureBookPageReqVO extends PageParam {
+
+    @Schema(description = "计量器具编码")
+    private String measureCode;
+
+    @Schema(description = "计量器具名称", example = "王五")
+    private String measureName;
+
+    @Schema(description = "分类")
+    private String classify;
+
+    @Schema(description = "责任人")
+    private String dutyPerson;
+
+    @Schema(description = "采购日期")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private String[] buyDate;
+
+    @Schema(description = "品牌")
+    private String brand;
+
+    @Schema(description = "规格型号", example = "芋艿")
+    private String modelName;
+
+    @Schema(description = "有效期")
+    private LocalDateTime validity;
+
+    @Schema(description = "上次检验/校准日期")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private String[] lastTime;
+
+    @Schema(description = "单位")
+    private String measureUnit;
+
+    @Schema(description = "价格", example = "7760")
+    private Double measurePrice;
+
+    @Schema(description = "图片")
+    private String measurePic;
+
+    @Schema(description = "备注", example = "你猜")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "部门id", example = "26945")
+    private Long deptId;
+
+}

+ 79 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/measure/vo/IotMeasureBookRespVO.java

@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.measure.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - 计量器具台账 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotMeasureBookRespVO {
+
+    @Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "31994")
+    @ExcelProperty("主键id")
+    private Long id;
+
+    @Schema(description = "计量器具编码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("计量器具编码")
+    private String measureCode;
+
+    @Schema(description = "计量器具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @ExcelProperty("计量器具名称")
+    private String measureName;
+
+    @Schema(description = "分类", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("分类")
+    private String classify;
+
+    @Schema(description = "责任人", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("责任人")
+    private String dutyPerson;
+
+    @Schema(description = "采购日期")
+    @ExcelProperty("采购日期")
+    private String buyDate;
+
+    @Schema(description = "品牌")
+    @ExcelProperty("品牌")
+    private String brand;
+
+    @Schema(description = "规格型号", example = "芋艿")
+    @ExcelProperty("规格型号")
+    private String modelName;
+
+    @Schema(description = "有效期")
+    @ExcelProperty("有效期")
+    private LocalDateTime validity;
+
+    @Schema(description = "上次检验/校准日期")
+    @ExcelProperty("上次检验/校准日期")
+    private String lastTime;
+
+    @Schema(description = "单位")
+    @ExcelProperty("单位")
+    private String measureUnit;
+
+    @Schema(description = "价格", example = "7760")
+    @ExcelProperty("价格")
+    private Double measurePrice;
+
+    @Schema(description = "图片")
+    @ExcelProperty("图片")
+    private String measurePic;
+
+    @Schema(description = "备注", example = "你猜")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "部门id", example = "26945")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+}

+ 64 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/measure/vo/IotMeasureBookSaveReqVO.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.measure.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotEmpty;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 计量器具台账新增/修改 Request VO")
+@Data
+public class IotMeasureBookSaveReqVO {
+
+    @Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "31994")
+    private Long id;
+
+    @Schema(description = "计量器具编码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "计量器具编码不能为空")
+    private String measureCode;
+
+    @Schema(description = "计量器具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @NotEmpty(message = "计量器具名称不能为空")
+    private String measureName;
+
+    @Schema(description = "分类", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "分类不能为空")
+    private String classify;
+
+    @Schema(description = "责任人", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "责任人不能为空")
+    private String dutyPerson;
+
+    @Schema(description = "采购日期")
+    private String buyDate;
+
+    @Schema(description = "品牌")
+    private String brand;
+
+    @Schema(description = "规格型号", example = "芋艿")
+    private String modelName;
+
+    @Schema(description = "有效期")
+    private LocalDateTime validity;
+
+    @Schema(description = "上次检验/校准日期")
+    private String lastTime;
+
+    @Schema(description = "单位")
+    private String measureUnit;
+
+    @Schema(description = "价格", example = "7760")
+    private Double measurePrice;
+
+    @Schema(description = "图片")
+    private String measurePic;
+
+    @Schema(description = "备注", example = "你猜")
+    private String remark;
+
+    @Schema(description = "部门id", example = "26945")
+    private Long deptId;
+
+}

+ 107 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/ITSLCache.java

@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.cache;
+
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.ThingsModelValueItem;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.things.IotYfThingsModelDO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 物模型缓存
+ *
+ * @author gsb
+ * @date 2024/6/12 15:57
+ */
+public interface ITSLCache {
+
+
+    /**
+     * 获取JSON物模型 -组装的方式是为了兼容前端数据结构
+     *
+     * @param productId 产品id
+     * @return <p>
+     * {
+     * "functions": [
+     * ],
+     * "events": [
+     * ],
+     * "properties": [
+     * {
+     * "datatype": {
+     * "max": 100,
+     * "min": 1,
+     * "step": 1,
+     * "type": "integer",
+     * "unit": "流明"
+     * },
+     * "id": "light",
+     * "isChart": 1,
+     * "isHistory": 1,
+     * "isMonitor": 1,
+     * "isReadonly": 1,
+     * "name": "光强",
+     * "order": 0,
+     * "regId": "light",
+     * "type": 1
+     * }
+     * ]
+     * }
+     * </p>
+     */
+    String getCacheThingsModelByProductId(Long productId);
+
+
+    /**
+     * 缓存物模型
+     *
+     * @param productId 产品id
+     * @return <p>
+     * {\"functions\":[],\"events\":[],\"properties\":[{\"datatype\":{\"max\":100,\"min\":1
+     * ,\"step\":1,\"type\":\"integer\",\"unit\":\"流明\"},
+     * \"id\":\"light\",\"isChart\":1,\"isHistory\":1,\"isMonitor\":1,\"isReadonly\":1,
+     * \"name\":\"光强\",\"order\":0,\"regId\":\"light\",\"type\":1}]}
+     * </p>
+     */
+    public String setCacheThingsModelByProductId(Long productId);
+
+    /**
+     * 获取map物模型 --返回 Map<String, PropertyDto>
+     *
+     * @param productId
+     * @return <p>
+     * 返回key-value
+     * key: 物模型标识符
+     * value:PropertyDto
+     * </p>
+     */
+    Map<String, ThingsModelValueItem> getCacheThMapByProductId(Long productId);
+
+
+    /**
+     * 获取List集合物模型
+     */
+    List<ThingsModelValueItem> getThingsModelList(Long productId);
+
+    /**
+     * 缓存物模型-返回Map
+     *
+     * @param productId <p>
+     *                  返回key-value
+     *                  key: 物模型标识符
+     *                  value:PropertyDto
+     *                  </p>
+     */
+    public Map<String, ThingsModelValueItem> setCacheThMapByProductId(Long productId);
+
+    /**
+     * 获取单个物模型缓存值
+     *
+     * @param productId 产品id
+     * @param identify  标识符
+     * @return PropertyDto
+     */
+    public ThingsModelValueItem getSingleThingModels(Long productId, String identify);
+
+    IotYfThingsModelDO selectSingleThingsModel(IotYfThingsModelDO model);
+}

+ 41 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/ITSLValueCache.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.cache;
+
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.ValueItem;
+
+import java.util.List;
+
+/**
+ * @author gsb
+ * @date 2024/3/22 16:37
+ */
+public interface ITSLValueCache {
+
+
+    /**
+     * 获取Redis缓存的设备物模型值
+     *
+     * @param productId    产品ID
+     * @param deviceNumber 设备编号
+     * @return
+     */
+    public List<ValueItem> getCacheDeviceStatus(Long productId, String deviceNumber);
+
+
+    /**
+     * 缓存设备物模型值到redis
+     *
+     * @return
+     */
+    public List<ValueItem> addCacheDeviceStatus(Long productId, String serialNumber);
+
+    /**
+     * 获取单个物模型的值
+     *
+     * @param productId 产品id
+     * @return java.lang.String
+     * @param: deviceNumber 设备编号
+     * @param: identifier 物模型标识
+     */
+    String getCacheIdentifierValue(Long productId, String serialNumber, String identifier);
+}

+ 354 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/TSLCacheImpl.java

@@ -0,0 +1,354 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.cache;
+
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.cache.domain.ModbusConfig;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums.ThingsModelType;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisCache;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisKeyBuilder;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.Datatype;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.IotYfThingsModelPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.PropertyDto;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.ThingsModelValueItem;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.product.IotYfProductDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.things.IotYfThingsModelDO;
+import cn.iocoder.yudao.module.pms.dal.mysql.yanfan.product.IotYfProductMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.yanfan.things.IotYfThingsModelMapper;
+import cn.iocoder.yudao.module.pms.service.yanfan.thingsmodel.IotYfThingsModelService;
+import cn.iocoder.yudao.module.supplier.dal.dataobject.product.ProductDO;
+import cn.iocoder.yudao.module.supplier.dal.mysql.product.ProductMapper;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.module.pms.controller.admin.yanfan.constant.Constants.EN_US;
+import static cn.iocoder.yudao.module.pms.controller.admin.yanfan.constant.Constants.ZH_CN;
+
+
+/**
+ * 物模型缓存
+ *
+ * @author gsb
+ * @date 2024/6/12 15:58
+ */
+@Service
+public class TSLCacheImpl implements ITSLCache {
+
+    @Resource
+    private RedisCache redisCache;
+    @Resource
+    private IotYfProductMapper iotYfProductMapper;
+    @Resource
+    private IotYfThingsModelService thingsModelService;
+//    @Resource
+//    private IModbusConfigService modbusConfigService;
+    @Autowired
+    private IotYfThingsModelMapper iotYfThingsModelMapper;
+
+    /**
+     * 将物模型名称转换为对应语言
+     *
+     * @param item
+     * @param language
+     */
+    private static void convertModelName(ThingsModelValueItem item, String language) {
+        switch (language) {
+            case EN_US:
+                if (StringUtils.isNotEmpty(item.getName_en_US())) {
+                    item.setName(item.getName_en_US());
+                }
+                break;
+            case ZH_CN:
+                if (StringUtils.isNotEmpty(item.getName_zh_CN())) {
+                    item.setName(item.getName_zh_CN());
+                }
+                break;
+        }
+    }
+
+    /**
+     * 将物模型名称转换为对应语言
+     *
+     * @param item
+     * @param language
+     */
+    private static void convertModelName(PropertyDto item, String language) {
+        switch (language) {
+            case EN_US:
+                if (Objects.nonNull(item.getName_en_US())) {
+                    item.setName(item.getName_en_US());
+                }
+                break;
+            case ZH_CN:
+                if (Objects.nonNull(item.getName_zh_CN())) {
+                    item.setName(item.getName_zh_CN());
+                }
+                break;
+        }
+    }
+
+    /**
+     * 根据产品ID获取JSON物模型 -组装的方式是为了兼容前端数据结构
+     *
+     * @param productId
+     * @return <p>
+     * {
+     * "functions": [
+     * ],
+     * "events": [
+     * ],
+     * "properties": [
+     * {
+     * "datatype": {
+     * "max": 100,
+     * "min": 1,
+     * "step": 1,
+     * "type": "integer",
+     * "unit": "流明"
+     * },
+     * "id": "light",
+     * "isChart": 1,
+     * "isHistory": 1,
+     * "isMonitor": 1,
+     * "isReadonly": 1,
+     * "name": "光强",
+     * "order": 0,
+     * "regId": "light",
+     * "type": 1
+     * }
+     * ]
+     * }
+     * </p>
+     */
+    @Override
+    public String getCacheThingsModelByProductId(Long productId) {
+        assert !Objects.isNull(productId) : "产品id为空";
+        // redis获取物模型
+        Map<String, Object> map = redisCache.getCacheMap(RedisKeyBuilder.buildTSLCacheKey(productId));
+        if (!CollectionUtils.isEmpty(map)) {
+            //兼容原页面物模型的数据格式
+            Map<String, List<PropertyDto>> listMap = map
+                    .values()
+                    .stream()
+                    .map(v -> JSON.parseObject(v.toString(), PropertyDto.class))
+                    .collect(Collectors.groupingBy(dto -> ThingsModelType.getName(dto.getType())));
+            if (listMap != null) {
+                for (List<PropertyDto> dtoList : listMap.values()) {
+                    for (PropertyDto dto : dtoList) {
+                        convertModelName(dto, "zh-CN");
+                    }
+                }
+            }
+            return JSON.toJSONString(listMap);
+        }
+        return setCacheThingsModelByProductId(productId);
+    }
+
+    /**
+     * 缓存物模型 -返回JSON字符串
+     *
+     * @param productId
+     * @return <p>
+     * {\"functions\":[],\"events\":[],\"properties\":[{\"datatype\":{\"max\":100,\"min\":1
+     * ,\"step\":1,\"type\":\"integer\",\"unit\":\"流明\"},
+     * \"id\":\"light\",\"isChart\":1,\"isHistory\":1,\"isMonitor\":1,\"isReadonly\":1,
+     * \"name\":\"光强\",\"order\":0,\"regId\":\"light\",\"type\":1}]}
+     * </p>
+     */
+    public String setCacheThingsModelByProductId(Long productId) {
+        Map<String, String> thingsModelMap = setThingModelAndModbusConfig(productId);
+        /*组装成原格式数据*/
+        Map<String, List<ThingsModelValueItem>> result = thingsModelMap
+                .values()
+                .stream()
+                .map(x -> JSON.parseObject(x, ThingsModelValueItem.class))
+                .collect(Collectors.groupingBy(dto -> ThingsModelType.getName(dto.getType())));
+        if (result != null) {
+            for (List<ThingsModelValueItem> itemList : result.values()) {
+                for (ThingsModelValueItem item : itemList) {
+                    convertModelName(item, "zh-CN");
+                }
+            }
+        }
+        String jsonString = JSON.toJSONString(result);
+        IotYfProductDO product = new IotYfProductDO();
+        product.setProductId(productId);
+        IotYfProductDO iotYfProductDO = iotYfProductMapper.selectById(product);
+        iotYfProductDO.setThingsModelsJson(jsonString);
+        iotYfProductMapper.updateById(iotYfProductDO);
+        return jsonString;
+    }
+
+    /**
+     * 获取map物模型
+     *
+     * @param productId
+     * @return <p>
+     * 返回key-value
+     * key: 物模型标识符
+     * value:PropertyDto
+     * </p>
+     */
+    public Map<String, ThingsModelValueItem> getCacheThMapByProductId(Long productId) {
+        assert !Objects.isNull(productId) : "产品id为空";
+        // redis获取物模型
+        Map<String, Object> map = redisCache.getCacheMap(RedisKeyBuilder.buildTSLCacheKey(productId));
+        if (!CollectionUtils.isEmpty(map)) {
+            Map<String, ThingsModelValueItem> itemMap = map
+                    .values()
+                    .stream()
+                    .map(v -> JSON.parseObject(v.toString(), ThingsModelValueItem.class))
+                    .collect(Collectors.toMap(ThingsModelValueItem::getId, Function.identity()));
+            for (ThingsModelValueItem item : itemMap.values()) {
+                convertModelName(item, "zh-CN");
+            }
+            return itemMap;
+        }
+        return setCacheThMapByProductId(productId);
+    }
+
+    /**
+     * 获取List集合物模型
+     */
+    @Override
+    public List<ThingsModelValueItem> getThingsModelList(Long productId) {
+        Map<String, ThingsModelValueItem> thingsModelMap = this.getCacheThMapByProductId(productId);
+        if (thingsModelMap != null) {
+            for (ThingsModelValueItem item : thingsModelMap.values()) {
+                convertModelName(item, "zh-CN");
+            }
+        }
+        return new ArrayList<>(thingsModelMap.values());
+    }
+
+    /**
+     * 缓存物模型-返回Map
+     *
+     * @param productId <p>
+     *                  返回key-value
+     *                  key: 物模型标识符
+     *                  value:PropertyDto
+     *                  </p>
+     */
+    public Map<String, ThingsModelValueItem> setCacheThMapByProductId(Long productId) {
+        Map<String, String> thingsModelMap = setThingModelAndModbusConfig(productId);
+        Map<String, ThingsModelValueItem> itemMap = thingsModelMap
+                .values()
+                .stream()
+                .map(value -> JSON.parseObject(value, ThingsModelValueItem.class))
+                .collect(Collectors.toMap(ThingsModelValueItem::getId, Function.identity()));
+        if (itemMap != null) {
+            for (ThingsModelValueItem item : itemMap.values()) {
+                convertModelName(item, "zh-CN");
+            }
+        }
+        return itemMap;
+    }
+
+    /**
+     * 获取单个物模型缓存值
+     *
+     * @param productId 产品id
+     * @param identify  标识符
+     * @return PropertyDto
+     */
+    @Override
+    public ThingsModelValueItem getSingleThingModels(Long productId, String identify) {
+        String cacheKey = RedisKeyBuilder.buildTSLCacheKey(productId);
+        String value = redisCache.getCacheMapValue(cacheKey, identify);
+        if (StringUtils.isEmpty(value)) {
+            return null;
+        }
+        ThingsModelValueItem item = JSONObject.parseObject(value, ThingsModelValueItem.class);
+        convertModelName(item, "zh-CN");
+        return item;
+    }
+
+    /**
+     * 整合物模型 & 整合modbus配置参数 & redis缓存
+     *
+     * @param productId
+     */
+    private Map<String, String> setThingModelAndModbusConfig(Long productId) {
+        // 数据库查询物模型集合
+//        IotYfThingsModelDO model = new IotYfThingsModelDO();
+//        model.setProductId(productId);
+//        model.setLimitValue("zh-CN");
+        IotYfThingsModelPageReqVO pageReqVO = new IotYfThingsModelPageReqVO();
+        pageReqVO.setProductId(productId);
+        pageReqVO.setLimitValue("zh-CN");
+        List<IotYfThingsModelDO> thingsModels = iotYfThingsModelMapper.selectList();
+        //modbus配置参数整合到物模型
+        ModbusConfig config = new ModbusConfig();
+        config.setProductId(productId);
+//        List<ModbusConfig> modbusConfigList = modbusConfigService.selectShortListByProductId(config);
+//        if (!CollectionUtils.isEmpty(modbusConfigList)) {
+//            Map<String, ModbusConfig> modbusMap = modbusConfigList
+//                    .stream()
+//                    .collect(Collectors.toMap(ModbusConfig::getIdentifier, Function.identity()));
+//            for (IotYfThingsModelDO thingsModel : thingsModels) {
+//                if (modbusMap.containsKey(thingsModel.getIdentifier())) {
+//                    ModbusConfig modbusConfig = modbusMap.get(thingsModel.getIdentifier());
+//                    thingsModel.setModbusConfig(modbusConfig);
+//                }
+//            }
+//        }
+        //List -> MAP
+        Map<String, String> thingsModelMap = thingsModels
+                .stream()
+                .collect(Collectors.toMap(IotYfThingsModelDO::getIdentifier, thingsModel -> {
+                    //转换数据,减少不必要数据
+                    ThingsModelValueItem dto = new ThingsModelValueItem();
+                    BeanUtils.copyProperties(thingsModel, dto);
+                    dto
+                            .setDatatype(JSONObject.parseObject(thingsModel.getSpecs(), Datatype.class))
+                            .setId(thingsModel.getIdentifier())
+                            .setName(thingsModel.getModelName())
+//                            .setName_zh_CN(thingsModel.getModelName_zh_CN())
+//                            .setName_en_US(thingsModel.getModelName_en_US())
+                            .setOrder(thingsModel.getModelOrder())
+                            .setModelId(thingsModel.getModelId())
+                            //todo yf
+//                            .setConfig(thingsModel.getModbusConfig())
+                            .setTempSlaveId(thingsModel.getTempSlaveId());
+                    return JSONObject.toJSONString(dto);
+                }));
+
+        /*缓存到redis*/
+        String cacheKey = RedisKeyBuilder.buildTSLCacheKey(productId);
+        //先删除缓存
+        if (redisCache.containsKey(cacheKey)) {
+            redisCache.deleteObject(cacheKey);
+        }
+        redisCache.hashPutAll(cacheKey, thingsModelMap);
+        return thingsModelMap;
+    }
+
+
+    /**
+     * 查询单个物模型
+     *
+     * @param model 物模型
+     * @return 单个物模型
+     */
+    @Override
+    @Cacheable(value = "thingsModel", key = "#root.methodName + '_' + #model.productId + '_' + #model.identifier + '_' + #model.tempSlaveId", unless = "#result == null")
+    public IotYfThingsModelDO selectSingleThingsModel(IotYfThingsModelDO model) {
+//        model.setLanguage("zh-CN");
+        IotYfThingsModelPageReqVO pageReqVO = new IotYfThingsModelPageReqVO();
+        pageReqVO.setLanguage("zh-CN");
+        return iotYfThingsModelMapper.selectList(pageReqVO).get(0);
+    }
+}

+ 149 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/TSLValueCacheImpl.java

@@ -0,0 +1,149 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.cache;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums.DataEnum;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisCache;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisKeyBuilder;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.ThingsModelValueItem;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.ValueItem;
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 物模型值缓存
+ *
+ * @author gsb
+ * @date 2024/3/22 16:37
+ */
+@Service
+public class TSLValueCacheImpl implements ITSLValueCache {
+
+    @Resource
+    private RedisCache redisCache;
+    @Resource
+    private ITSLCache itslCache;
+
+
+    /**
+     * 获取Redis缓存的设备物模型值
+     *
+     * @param productId    产品ID
+     * @param deviceNumber 设备编号
+     * @return
+     */
+    @Override
+    public List<ValueItem> getCacheDeviceStatus(Long productId, String deviceNumber) {
+        String key = RedisKeyBuilder.buildTSLVCacheKey(productId, deviceNumber);
+        Map<String, String> map = redisCache.hashEntity(key);
+        List<ValueItem> valueList = new ArrayList<>();
+        if (map == null || map.size() == 0) {
+            // 缓存设备状态(物模型值)到redis
+            valueList = addCacheDeviceStatus(productId, deviceNumber);
+        } else {
+            // 获取redis缓存的物模型值
+            valueList = map
+                    .values()
+                    .stream()
+                    .map(s -> JSONObject.parseObject(s, ValueItem.class))
+                    .collect(Collectors.toList());
+        }
+        return valueList;
+    }
+
+
+    /**
+     * 缓存设备物模型值到redis
+     *
+     * @return
+     */
+    @Override
+    public List<ValueItem> addCacheDeviceStatus(Long productId, String serialNumber) {
+        List<ValueItem> resultList = new ArrayList<>();
+        // 获取物模型值
+        List<ThingsModelValueItem> valueList = itslCache.getThingsModelList(productId);
+        // redis存储设备默认状态 键:产品ID_设备编号
+        String key = RedisKeyBuilder.buildTSLVCacheKey(productId, serialNumber);
+        Map<String, String> maps = new HashMap<>();
+        for (ThingsModelValueItem item : valueList) {
+            ValueItem valueItem = new ValueItem();
+            valueItem.setId(item.getId());
+            valueItem.setValue("");
+            valueItem.setShadow("");
+            DataEnum dataType = DataEnum.convert(item.getDatatype().getType());
+            if (dataType.equals(DataEnum.ARRAY) || dataType.equals(DataEnum.OBJECT)) {
+                //数组和对象类型额外处理
+                this.handleOtherType(maps, item, dataType, valueItem);
+            } else {
+                maps.put(item.getId(), JSONObject.toJSONString(valueItem));
+            }
+            resultList.add(valueItem);
+        }
+        redisCache.hashPutAll(key, maps);
+        return resultList;
+    }
+
+    @Override
+    public String getCacheIdentifierValue(Long productId, String serialNumber, String identifier) {
+        String key = RedisKeyBuilder.buildTSLVCacheKey(productId, serialNumber);
+        Object cacheMapValue = redisCache.getCacheMapValue(key, identifier);
+        if (Objects.isNull(cacheMapValue)){
+            return null;
+        }
+        JSONObject jsonObject = JSONObject.parseObject(redisCache.getCacheMapValue(key, identifier));
+        if (Objects.isNull(jsonObject)){
+            return null;
+        }
+        return jsonObject.get("value").toString();
+    }
+
+    /**
+     * 数组和对象类型额外处理
+     *
+     * @param maps
+     * @param item
+     * @param dataType
+     * @param valueItem
+     */
+    private void handleOtherType(Map<String, String> maps, ThingsModelValueItem item, DataEnum dataType, ValueItem valueItem) {
+        List<ThingsModelValueItem> params = item.getDatatype().getParams();
+        switch (dataType) {
+            case ARRAY:
+                // 数组元素赋值(英文逗号分割的字符串,包含简单类型数组和对象类型数组,数组元素ID格式:array_01_humidity)
+                StringBuilder defaultValue = new StringBuilder(" ");
+                if (item.getDatatype().getArrayType().equals("object")) {
+                    for (int i = 0; i < item.getDatatype().getArrayCount(); i++) {
+                        // 默认值需要保留为空格,便于解析字符串为数组
+                        defaultValue.append(", ");
+                    }
+                    for (ThingsModelValueItem param : params) {
+                        valueItem.setValue(defaultValue.toString());
+                        valueItem.setShadow(defaultValue.toString());
+                        valueItem.setName(param.getName());
+                        valueItem.setId(param.getId());
+                        maps.put(param.getId(), JSONObject.toJSONString(valueItem));
+                    }
+                } else {
+                    for (int i = 0; i < item.getDatatype().getArrayCount(); i++) {
+                        defaultValue.append(", ");
+                    }
+                    valueItem.setValue(defaultValue.toString());
+                    valueItem.setShadow(defaultValue.toString());
+                    valueItem.setName(item.getName());
+                    valueItem.setId(item.getId());
+                    maps.put(item.getId(), JSONObject.toJSONString(valueItem));
+                }
+                break;
+            case OBJECT:
+                for (ThingsModelValueItem param : params) {
+                    valueItem.setName(param.getName());
+                    valueItem.setId(param.getId());
+                    maps.put(param.getId(), JSONObject.toJSONString(valueItem));
+                }
+                break;
+        }
+    }
+
+}

+ 82 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/cache/domain/ModbusConfig.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.cache.domain;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.core.BaseEntity;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol.modbus.ModbusCode;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * modbus配置对象 iot_modbus_config
+ * * @date 2024-05-22
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ModbusConfig extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 业务id
+     */
+    private Long id;
+
+    /**
+     * 所属产品id
+     */
+    //@ApiModelProperty(value = "所属产品id")
+    private Long productId;
+
+    /**
+     * 从机地址
+     */
+    //@ApiModelProperty(value = "从机地址")
+    private Integer slave;
+    /**
+     * 关联属性
+     */
+    //@ApiModelProperty(value = "关联属性")
+    private String identifier;
+
+    /**
+     * 寄存器地址
+     */
+    //@ApiModelProperty(value = "寄存器地址")
+    private Integer address;
+
+    /**
+     * 是否只读(0-否,1-是)
+     */
+    //@ApiModelProperty(value = "是否只读(0-否,1-是)")
+    private Integer isReadonly;
+
+    /**
+     * modbus数据类型
+     */
+    //@ApiModelProperty(value = "modbus数据类型")
+    private String dataType;
+
+    /**
+     * 读取个数
+     */
+    //@ApiModelProperty(value = "读取个数")
+    private Integer quantity;
+
+    /**
+     * 寄存器类型 1-IO寄存器 2-数据寄存器
+     */
+    //@ApiModelProperty(value = "寄存器类型 1-IO寄存器 2-数据寄存器")
+    private Integer type;
+
+    //@ApiModelProperty(value = "BIT位")
+    private Integer bitOrder;
+
+    //@ApiModelProperty(value = "排序")
+    private Integer sort;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    private String delFlag;
+
+    private ModbusCode modbusCode;
+
+}

+ 115 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/category/IotYfCategoryController.java

@@ -0,0 +1,115 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.category;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.category.vo.IotYfCategoryPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.category.vo.IotYfCategoryRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.category.vo.IotYfCategorySaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.category.IotYfCategoryDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.category.IotCategoryService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - yf产品分类")
+@RestController
+@RequestMapping("/rq/iot-category")
+@Validated
+public class IotYfCategoryController {
+
+    @Resource
+    private IotCategoryService iotCategoryService;
+
+    @PostMapping
+    @Operation(summary = "创建yf产品分类")
+    @PreAuthorize("@ss.hasPermission('rq:iot-category:create')")
+    public CommonResult<Long> createIotCategory(@Valid @RequestBody IotYfCategorySaveReqVO createReqVO) {
+        return success(iotCategoryService.createIotCategory(createReqVO));
+    }
+
+    @PutMapping
+    @Operation(summary = "更新yf产品分类")
+    @PreAuthorize("@ss.hasPermission('rq:iot-category:update')")
+    public CommonResult<Boolean> updateIotCategory(@Valid @RequestBody IotYfCategorySaveReqVO updateReqVO) {
+        iotCategoryService.updateIotCategory(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除yf产品分类")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-category:delete')")
+    public CommonResult<Boolean> deleteIotCategory(@RequestParam("id") Long id) {
+        iotCategoryService.deleteIotCategory(id);
+        return success(true);
+    }
+
+    @GetMapping("/{categoryId}")
+    @Operation(summary = "获得yf产品分类")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-category:query')")
+    public CommonResult<IotYfCategoryRespVO> getIotCategory(@RequestParam("id") Long id) {
+        IotYfCategoryDO iotCategory = iotCategoryService.getIotCategory(id);
+        return success(BeanUtils.toBean(iotCategory, IotYfCategoryRespVO.class));
+    }
+
+    @GetMapping("/shortlist")
+    @Operation(summary = "获得yf产品分类")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-category:query')")
+    public CommonResult<List<Map<String, Object>>> getIotCategoryShort(@RequestParam("id") Long id) {
+        IotYfCategoryPageReqVO pageReqVO = new IotYfCategoryPageReqVO();
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotYfCategoryDO> iotCategory = iotCategoryService.getIotCategoryPage(pageReqVO).getList();
+        List<Map<String, Object>> result = new ArrayList<>();
+        iotCategory.forEach(e ->{
+            Map<String, Object> map = new HashMap<>();
+            map.put("id",e.getCategoryId());
+            result.add(map);
+        });
+        return success(result);
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得yf产品分类分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-category:query')")
+    public CommonResult<PageResult<IotYfCategoryRespVO>> getIotCategoryPage(@Valid IotYfCategoryPageReqVO pageReqVO) {
+        PageResult<IotYfCategoryDO> pageResult = iotCategoryService.getIotCategoryPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotYfCategoryRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出yf产品分类 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-category:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotCategoryExcel(@Valid IotYfCategoryPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotYfCategoryDO> list = iotCategoryService.getIotCategoryPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "yf产品分类.xls", "数据", IotYfCategoryRespVO.class,
+                        BeanUtils.toBean(list, IotYfCategoryRespVO.class));
+    }
+
+}

+ 51 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/category/vo/IotYfCategoryPageReqVO.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.category.vo;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - yf产品分类分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotYfCategoryPageReqVO extends PageParam {
+
+    @Schema(description = "产品分类名称", example = "赵六")
+    private String categoryName;
+
+    @Schema(description = "租户名称", example = "赵六")
+    private String tenantName;
+
+    @Schema(description = "是否系统通用(0-否,1-是)")
+    private Boolean isSys;
+
+    @Schema(description = "父级ID", example = "19957")
+    private Long parentId;
+
+    @Schema(description = "显示顺序")
+    private Integer orderNum;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "部门id", example = "32675")
+    private Long deptId;
+
+}

+ 62 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/category/vo/IotYfCategoryRespVO.java

@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - yf产品分类 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotYfCategoryRespVO {
+
+    @Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "30221")
+    @ExcelProperty("主键id")
+    private Long categoryId;
+
+    @Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @ExcelProperty("产品分类名称")
+    private String categoryName;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @ExcelProperty("租户名称")
+    private String tenantName;
+
+    @Schema(description = "是否系统通用(0-否,1-是)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("是否系统通用(0-否,1-是)")
+    private Boolean isSys;
+
+    @Schema(description = "父级ID", example = "19957")
+    @ExcelProperty("父级ID")
+    private Long parentId;
+
+    @Schema(description = "显示顺序")
+    @ExcelProperty("显示顺序")
+    private Integer orderNum;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    @ExcelProperty("删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    @ExcelProperty("创建者")
+    private String createBy;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "部门id", example = "32675")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+}

+ 48 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/category/vo/IotYfCategorySaveReqVO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - yf产品分类新增/修改 Request VO")
+@Data
+public class IotYfCategorySaveReqVO {
+
+    @Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "30221")
+    private Long categoryId;
+
+    @Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @NotEmpty(message = "产品分类名称不能为空")
+    private String categoryName;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+//    @NotEmpty(message = "租户名称不能为空")
+    private String tenantName;
+
+    @Schema(description = "是否系统通用(0-否,1-是)", requiredMode = Schema.RequiredMode.REQUIRED)
+//    @NotNull(message = "是否系统通用(0-否,1-是)不能为空")
+    private Boolean isSys;
+
+    @Schema(description = "父级ID", example = "19957")
+    private Long parentId;
+
+    @Schema(description = "显示顺序")
+    private Integer orderNum;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "部门id", example = "32675")
+    private Long deptId;
+
+}

+ 68 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/config/MqttClientConfig.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * mqtt配置信息
+ */
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "spring.mqtt")
+public class MqttClientConfig {
+
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+    /**
+     * 连接地址
+     */
+    private String hostUrl;
+    /**
+     * 客户Id
+     */
+    private String clientId;
+    /**
+     * 默认连接话题
+     */
+    private String defaultTopic;
+    /**
+     * 超时时间
+     */
+    private int timeout;
+    /**
+     * 保持连接数
+     */
+    private int keepalive;
+
+    /**
+     * 是否清除session
+     */
+    private boolean clearSession;
+    /**
+     * 是否共享订阅
+     */
+    private boolean isShared;
+    /**
+     * 分组共享订阅
+     */
+    private boolean isSharedGroup;
+
+    /**
+     * true: 使用netty搭建的mqttBroker  false: 使用emq
+     */
+    @Value("${server.broker.enabled}")
+    private Boolean enabled;
+
+    private int maxInflight;
+
+}
+

+ 126 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/config/RuoYiConfig.java

@@ -0,0 +1,126 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取项目相关配置
+ *
+ * @author ruoyi
+ */
+@Component
+@ConfigurationProperties(prefix = "yanfan")
+public class RuoYiConfig {
+    /**
+     * 上传路径
+     */
+    private static String profile;
+    /**
+     * 获取地址开关
+     */
+    private static boolean addressEnabled;
+    /**
+     * 验证码类型
+     */
+    private static String captchaType;
+    /**
+     * 项目名称
+     */
+    private String name;
+    /**
+     * 版本
+     */
+    private String version;
+    /**
+     * 版权年份
+     */
+    private String copyrightYear;
+    /**
+     * 实例演示开关
+     */
+    private boolean demoEnabled;
+
+    public static String getProfile() {
+        return profile;
+    }
+
+    public void setProfile(String profile) {
+        RuoYiConfig.profile = profile;
+    }
+
+    public static boolean isAddressEnabled() {
+        return addressEnabled;
+    }
+
+    public void setAddressEnabled(boolean addressEnabled) {
+        RuoYiConfig.addressEnabled = addressEnabled;
+    }
+
+    public static String getCaptchaType() {
+        return captchaType;
+    }
+
+    public void setCaptchaType(String captchaType) {
+        RuoYiConfig.captchaType = captchaType;
+    }
+
+    /**
+     * 获取导入上传路径
+     */
+    public static String getImportPath() {
+        return getProfile() + "/import";
+    }
+
+    /**
+     * 获取头像上传路径
+     */
+    public static String getAvatarPath() {
+        return getProfile() + "/avatar";
+    }
+
+    /**
+     * 获取下载路径
+     */
+    public static String getDownloadPath() {
+        return getProfile() + "/download/";
+    }
+
+    /**
+     * 获取上传路径
+     */
+    public static String getUploadPath() {
+        return getProfile() + "/upload";
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public String getCopyrightYear() {
+        return copyrightYear;
+    }
+
+    public void setCopyrightYear(String copyrightYear) {
+        this.copyrightYear = copyrightYear;
+    }
+
+    public boolean isDemoEnabled() {
+        return demoEnabled;
+    }
+
+    public void setDemoEnabled(boolean demoEnabled) {
+        this.demoEnabled = demoEnabled;
+    }
+}

+ 157 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/constant/Constants.java

@@ -0,0 +1,157 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.constant;
+
+
+import cn.hutool.jwt.Claims;
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+public class Constants {
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * www主域
+     */
+    public static final String WWW = "www.";
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    public static final String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    public static final String LOGOUT = "Logout";
+
+    /**
+     * 注册
+     */
+    public static final String REGISTER = "Register";
+
+    /**
+     * 登录失败
+     */
+    public static final String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    /**
+     * 用户ID
+     */
+    public static final String JWT_USERID = "userid";
+
+    /**
+     * 用户名称
+     */
+    //public static final String JWT_USERNAME = Claims.SUBJECT;
+    public static final String JWT_USERNAME = "sub";
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+
+    /**
+     * RMI 远程方法调用
+     */
+    public static final String LOOKUP_RMI = "rmi:";
+
+    /**
+     * LDAP 远程方法调用
+     */
+    public static final String LOOKUP_LDAP = "ldap:";
+
+    /**
+     * LDAPS 远程方法调用
+     */
+    public static final String LOOKUP_LDAPS = "ldaps:";
+
+    /**
+     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
+     */
+    public static final String[] JOB_WHITELIST_STR = {"com.yanfan"};
+
+    /**
+     * 定时任务违规的字符
+     */
+    public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", "org.springframework", "org.apache", "com.yanfan.common.utils.file", "com.yanfan.common.config"};
+
+    /**
+     * 语言类型
+     */
+    public static final String LANGUAGE = "language";
+    public static final String ZH_CN = "zh-CN";
+    public static final String EN_US = "en-US";
+
+    /**
+     * 翻译数据类型
+     */
+    public static final String MENU = "menu";
+    public static final String DICT_DATA = "dict_data";
+    public static final String DICT_TYPE = "dict_type";
+    public static final String THINGS_MODEL = "things_model";
+    public static final String THINGS_MODEL_TEMPLATE = "things_model_template";
+}

+ 356 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/constant/YanfanConstant.java

@@ -0,0 +1,356 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.constant;
+
+/**
+ * 常量
+ *
+ * @author bill
+ */
+public interface YanfanConstant {
+
+    interface SERVER {
+        String UFT8 = "UTF-8";
+        String GB2312 = "GB2312";
+
+
+        String MQTT = "mqtt";
+        String PORT = "port";
+        String ADAPTER = "adapter";
+        String FRAMEDECODER = "frameDecoder";
+        String DISPATCHER = "dispatcher";
+        String DECODER = "decoder";
+        String ENCODER = "encoder";
+        String MAXFRAMELENGTH = "maxFrameLength";
+        String SLICER = "slicer";
+        String DELIMITERS = "delimiters";
+        String IDLE = "idle";
+        String WS_PREFIX = "web-";
+        String WM_PREFIX = "server-";
+        String FAST_PHONE = "phone-";
+
+        /*MQTT平台判定离线时间 keepAlive*1.5 */ Long DEVICE_PING_EXPIRED = 90000L;
+    }
+
+    interface CLIENT {
+        //加盐
+        String TOKEN = "yanfan-smart!@#$123";
+    }
+
+    /*webSocket配置*/
+    interface WS {
+        String HEART_BEAT = "heartbeat";
+        String HTTP_SERVER_CODEC = "httpServerCodec";
+        String AGGREGATOR = "aggregator";
+        String COMPRESSOR = "compressor";
+        String PROTOCOL = "protocol";
+        String MQTT_WEBSOCKET = "mqttWebsocket";
+        String DECODER = "decoder";
+        String ENCODER = "encoder";
+        String BROKER_HANDLER = "brokerHandler";
+
+    }
+
+    interface TASK {
+        /**
+         * 设备上下线任务
+         */
+        String DEVICE_STATUS_TASK = "deviceStatusTask";
+        /**
+         * 设备主动上报任务
+         */
+        String DEVICE_UP_MESSAGE_TASK = "deviceUpMessageTask";
+        /**
+         * 设备回调任务
+         */
+        String DEVICE_REPLY_MESSAGE_TASK = "deviceReplyMessageTask";
+        /**
+         * 设备下行任务
+         */
+        String DEVICE_DOWN_MESSAGE_TASK = "deviceDownMessageTask";
+        /**
+         * 服务调用(指令下发)任务
+         */
+        String FUNCTION_INVOKE_TASK = "functionInvokeTask";
+        /**
+         * 属性读取任务,区分服务调用
+         */
+        String DEVICE_FETCH_PROP_TASK = "deviceFetchPropTask";
+        /**
+         * 设备其他消息处理
+         */
+        String DEVICE_OTHER_TASK = "deviceOtherMsgTask";
+        /**
+         * 数据调试任务
+         */
+        String DEVICE_TEST_TASK = "deviceTestMsgTask";
+        /**
+         * 消息消费线程
+         */
+        String MESSAGE_CONSUME_TASK = "messageConsumeTask";
+        /**
+         * 内部消费线程publish
+         */
+        String MESSAGE_CONSUME_TASK_PUB = "messageConsumeTaskPub";
+        /**
+         * 内部消费线程Fetch
+         */
+        String MESSAGE_CONSUME_TASK_FETCH = "messageConsumeTaskFetch";
+        /**
+         * OTA升级延迟队列
+         */
+        String DELAY_UPGRADE_TASK = "delayUpgradeTask";
+
+        /**
+         * 内部MQTT任务
+         */
+        String INNER_MQTT_TASK = "inner_mqtt_task";
+
+    }
+
+    interface MQTT {
+        //*上报平台前缀*//*
+        String UP_TOPIC_SUFFIX = "post";
+        //*下发设备前缀*//*
+        String DOWN_TOPIC_SUFFIX = "get";
+
+        /*模拟设备后缀*/ String PROPERTY_GET_SIMULATE = "simulate";
+
+        String PREDIX = "/+/+";
+
+        String DUP = "dup";
+        String QOS = "qos";
+        String RETAIN = "retain";
+        String CLEAN_SESSION = "cleanSession";
+
+        /*集群方式*/ String REDIS_CHANNEL = "redis";
+        String ROCKET_MQ = "rocketmq";
+    }
+
+    /*集群,全局发布的消息类型*/
+    interface CHANNEL {
+        /*设备状态*/ String DEVICE_STATUS = "device_status";
+        /*平台读取属性*/ String PROP_READ = "prop_read";
+        /*推送消息*/ String PUBLISH = "publish";
+        /*服务下发*/ String FUNCTION_INVOKE = "function_invoke";
+        /*事件*/ String EVENT = "event";
+        /*other*/ String OTHER = "other";
+        /*Qos1 推送应答*/ String PUBLISH_ACK = "publish_ack";
+        /*Qos2 发布消息收到*/ String PUB_REC = "pub_rec";
+        /*Qos 发布消息释放*/ String PUB_REL = "pub_rel";
+        /*Qos2 发布消息完成*/ String PUB_COMP = "pub_comp";
+
+        String UPGRADE = "upgrade";
+
+        /*-------------------------ROCKETMQ-------------------------*/ String SUFFIX = "group";
+        /*设备状态*/ String DEVICE_STATUS_GROUP = DEVICE_STATUS + SUFFIX;
+        String PROP_READ_GROUP = PROP_READ + SUFFIX;
+        /*服务下发*/ String FUNCTION_INVOKE_GROUP = FUNCTION_INVOKE + SUFFIX;
+        /*推送消息*/ String PUBLISH_GROUP = PUBLISH + SUFFIX;
+        /*Qos1 推送应答*/ String PUBLISH_ACK_GROUP = PUBLISH_ACK + SUFFIX;
+        /*Qos2 发布消息收到*/ String PUB_REC_GROUP = PUB_REC + SUFFIX;
+        /*Qos 发布消息释放*/ String PUB_REL_GROUP = PUB_REL + SUFFIX;
+        /*Qos2 发布消息完成*/ String PUB_COMP_GROUP = PUB_COMP + SUFFIX;
+        /*OTA升级*/ String UPGRADE_GROUP = UPGRADE + SUFFIX;
+    }
+
+
+    /**
+     * redisKey 定义
+     */
+    interface REDIS {
+        /*redis全局前缀*/ String GLOBAL_PREFIX_KEY = "yanfan:";
+        /*设备在线状态*/ String DEVICE_STATUS_KEY = "device:status";
+        /*在线设备列表*/ String DEVICE_ONLINE_LIST = "device:online:list";
+        /*设备实时状态key*/ String DEVICE_RUNTIME_DATA = "device:runtime:";
+        /*通讯协议参数*/ String DEVICE_PROTOCOL_PARAM = "device:param:";
+        /**
+         * 设备消息id缓存key
+         */
+        String DEVICE_MESSAGE_ID = "device:messageId:";
+        /**
+         * 设备消息id缓存key
+         */
+        String DEVICE_MESSAGE_USER_ID = "device:messageuserid";
+        /**
+         * 固件版本key
+         */
+        String FIRMWARE_VERSION = "device:firmware:";
+        /**
+         * 设备信息
+         */
+        String DEVICE_MSG = "device:msg:";
+
+        /**
+         * 采集点变更记录缓存key
+         */
+        String COLLECT_POINT_CHANGE = "collect:point:change:";
+        /**
+         * 属性下发回调
+         */
+        String PROP_READ_STORE = "prop:read:store:";
+        /**
+         * sip
+         */
+        String RECORDINFO_KEY = "sip:recordinfo:";
+        String DEVICEID_KEY = "sip:deviceid:";
+        String STREAM_KEY = "sip:stream:";
+        String INVITE_KEY = "sip:invite:";
+        String SIP_CSEQ_PREFIX = "sip:CSEQ:";
+        String DEFAULT_SIP_CONFIG = "sip:config";
+        String DEFAULT_MEDIA_CONFIG = "sip:mediaconfig";
+
+        /**
+         * rule
+         */
+        String RULE_SILENT_TIME = "rule:SilentTime";
+
+
+        /**
+         * 当前连接数
+         */
+        String MESSAGE_CONNECT_COUNT = "messages:connect:count";
+        /**
+         * 总保留消息
+         */
+        String MESSAGE_RETAIN_TOTAL = "message:retain:total";
+
+        /**
+         * 主题数
+         */
+        String MESSAGE_TOPIC_TOTAL = "message:topic:total";
+        /*发送消息数*/ String MESSAGE_SEND_TOTAL = "message:send:total";
+        /*接收消息数*/ String MESSAGE_RECEIVE_TOTAL = "message:receive:total";
+        /*连接次数*/ String MESSAGE_CONNECT_TOTAL = "message:connect:total";
+        /**
+         * 认证次数
+         */
+        String MESSAGE_AUTH_TOTAL = "message:auth:total";
+        /**
+         * 订阅次数
+         */
+        String MESSAGE_SUBSCRIBE_TOTAL = "message:subscribe:total";
+
+        /**
+         * 今日接收消息
+         */
+        String MESSAGE_RECEIVE_TODAY = "message:receive:today";
+        /**
+         * 今日发送消息
+         */
+        String MESSAGE_SEND_TODAY = "message:send:today";
+
+
+        // 物模型值命名空间:Key:TSLV:{productId}_{deviceNumber}   HKey:{identity#V/identity#S/identity#M/identity#N}
+        /**
+         * v-值
+         * s-影子值
+         * m-是否为检测值
+         * n-名称
+         */
+        String DEVICE_PRE_KEY = "TSLV:";
+
+        // 物模型命名空间:Key:TSL:{productId}
+        String TSL_PRE_KEY = "TSL:";
+
+        String MODBUS_PRE_KEY = "MODBUS:";
+
+        /**
+         * modbus缓存指令
+         */
+        String POLL_MODBUS_KEY = "MODBUS:POLL:";
+        /**
+         * modbus运行时数据
+         */
+        String MODBUS_RUNTIME = "MODBUS:RUNTIME:";
+
+        String MODBUS_LOCK = "MODBUS:LOCK:";
+
+        /**
+         * 通知企业微信应用消息accessToken缓存key
+         */
+        String NOTIFY_WECOM_APPLY_ACCESSTOKEN = "notify:wecom:apply:";
+
+        // 场景变量命名空间:Key:SMTV:{sceneModelTagId}
+        String SCENE_MODEL_TAG_ID = "SMTV:";
+
+
+    }
+
+    interface TOPIC {
+        /*属性上报*/ String PROP = "properties";
+        //事件
+        String EVENT = "events";
+        //功能
+        String FUNCTION = "functions";
+        /*非OTA消息回复*/ String MSG_REPLY = "message/reply";
+        /*OTA升级回复*/ String UPGRADE_REPLY = "upgrade/reply";
+        /*网关子设备结尾*/ String SUB = "/sub";
+    }
+
+    interface PROTOCOL {
+        String HJ212 = "HJ-212";
+        String DLT698_45 = "DLT698-45";
+        String ModbusTcp = "MODBUS-TCP";
+        String ModbusRtu = "MODBUS-RTU";
+        String YinErDa = "YinErDa";
+        String JsonObject = "JSONOBJECT";
+        String JsonArray = "JSON";
+        String ModbusRtuPak = "MODBUS-RTU-PAK";
+        String FlowMeter = "FlowMeter";
+        String RJ45 = "RJ45";
+        String ModbusToJson = "MODBUS-JSON";
+        String ModbusToJsonHP = "MODBUS-JSON-HP";
+        String ModbusToJsonZQWL = "MODBUS-JSON-ZQWL";
+        String JsonObject_ChenYi = "JSONOBJECT-CHENYI";
+        String GEC6100D = "MODBUS-JSON-GEC6100D";
+        String SGZ = "SGZ";
+        String CH = "CH";
+        String SZY206 = "SZY206";
+        String JT808 = "JT808";
+
+
+    }
+
+    interface URL {
+        /**
+         * 微信小程序订阅消息推送url前缀
+         */
+        String WX_MINI_PROGRAM_PUSH_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send";
+        /**
+         * 微信网站、移动应用登录获取用户access_token
+         */
+        String WX_GET_ACCESS_TOKEN_URL_PREFIX = "https://api.weixin.qq.com/sns/oauth2/access_token";
+        /**
+         * 微信小程序登录获取用户会话参数
+         */
+        String WX_MINI_PROGRAM_GET_USER_SESSION_URL_PREFIX = "https://api.weixin.qq.com/sns/jscode2session";
+        /**
+         * 微信小程序、公众号获取access_token
+         */
+        String WX_MINI_PROGRAM_GET_ACCESS_TOKEN_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
+        /**
+         * 微信获取用户信息
+         */
+        String WX_GET_USER_INFO_URL_PREFIX = "https://api.weixin.qq.com/sns/userinfo";
+        /**
+         * 获取用户手机号信息
+         */
+        String WX_GET_USER_PHONE_URL_PREFIX = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=";
+        /**
+         * 企业微信获取accessToken
+         */
+        String WECOM_GET_ACCESSTOKEN = "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
+        /**
+         * 企业微信发送应用消息
+         */
+        String WECOM_APPLY_SEND = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=";
+        /**
+         * 微信公众号获取用户信息
+         */
+        String WX_PUBLIC_ACCOUNT_GET_USER_INFO_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/user/info";
+        /**
+         * 微信公众号发送模版消息
+         */
+        String WX_PUBLIC_ACCOUNT_TEMPLATE_SEND_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
+    }
+
+}

+ 126 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/core/BaseEntity.java

@@ -0,0 +1,126 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.core;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity基类
+ *
+ * @author ruoyi
+ */
+public class BaseEntity implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 搜索值
+     */
+    //@ApiModelProperty("搜索值")
+    @JsonIgnore
+    private String searchValue;
+
+    /**
+     * 创建者
+     */
+    //@ApiModelProperty("创建者")
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    //@ApiModelProperty("创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     * 更新者
+     */
+    //@ApiModelProperty("更新者")
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    //@ApiModelProperty("更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /**
+     * 备注
+     */
+    //@ApiModelProperty("备注")
+    private String remark;
+
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    //@ApiModelProperty("请求参数")
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private Map<String, Object> params;
+
+    public String getSearchValue() {
+        return searchValue;
+    }
+
+    public void setSearchValue(String searchValue) {
+        this.searchValue = searchValue;
+    }
+
+    public String getCreateBy() {
+        return createBy;
+    }
+
+    public void setCreateBy(String createBy) {
+        this.createBy = createBy;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getUpdateBy() {
+        return updateBy;
+    }
+
+    public void setUpdateBy(String updateBy) {
+        this.updateBy = updateBy;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Map<String, Object> getParams() {
+        if (params == null) {
+            params = new HashMap<>();
+        }
+        return params;
+    }
+
+    public void setParams(Map<String, Object> params) {
+        this.params = params;
+    }
+}

+ 156 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/YfIotDeviceController.java

@@ -0,0 +1,156 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.device;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.TableDataInfo;
+import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceSaveReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo.DeviceShortOutput;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo.YfIotDevicePageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo.YfIotDeviceRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo.YfIotDeviceSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.device.YfIotDeviceDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.device.YfIotDeviceService;
+import lombok.extern.java.Log;
+import org.quartz.SchedulerException;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
+
+
+@Tag(name = "管理后台 - 设备")
+@RestController
+@RequestMapping("/rq/yf-iot-device")
+@Validated
+public class YfIotDeviceController {
+
+    @Resource
+    private YfIotDeviceService yfIotDeviceService;
+
+    @PostMapping
+    @Operation(summary = "创建设备")
+    @PreAuthorize("@ss.hasPermission('rq:yf-iot-device:create')")
+    public CommonResult<YfIotDeviceDO> createYfIotDevice(@Valid @RequestBody YfIotDeviceSaveReqVO createReqVO) {
+        return success(yfIotDeviceService.createYfIotDevice(createReqVO));
+    }
+
+    @PutMapping
+    @Operation(summary = "更新设备")
+    @PreAuthorize("@ss.hasPermission('rq:yf-iot-device:update')")
+    public CommonResult<Boolean> updateYfIotDevice(@Valid @RequestBody YfIotDeviceSaveReqVO updateReqVO) {
+        yfIotDeviceService.updateYfIotDevice(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除设备")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:yf-iot-device:delete')")
+    public CommonResult<Boolean> deleteYfIotDevice(@RequestParam("id") Long id) {
+        yfIotDeviceService.deleteYfIotDevice(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得设备")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:yf-iot-device:query')")
+    public CommonResult<YfIotDeviceRespVO> getYfIotDevice(@RequestParam("id") Long id) {
+        YfIotDeviceDO yfIotDevice = yfIotDeviceService.getYfIotDevice(id);
+        return success(BeanUtils.toBean(yfIotDevice, YfIotDeviceRespVO.class));
+    }
+
+    @GetMapping(value = "/{deviceId}")
+    @Operation(summary = "获取设备详情")
+    public CommonResult<YfIotDeviceRespVO> getInfo(@PathVariable("deviceId") Long deviceId) {
+        YfIotDeviceRespVO device = yfIotDeviceService.selectDeviceByDeviceId(deviceId);
+        // 判断当前用户是否有设备分享权限 (设备所属机构管理员和设备所属用户有权限)
+        //todo yf
+//        LoginUser loginUser = getLoginUser();
+//        List<SysRole> roles = loginUser.getUser().getRoles();
+//        //判断当前用户是否为设备所属机构管理员
+//        if (roles.stream().anyMatch(a -> "admin".equals(a.getRoleKey()))) {
+//            device.setIsOwner(1);
+//        } else {
+//            //判断当前用户是否是设备所属用户
+//            if (Objects.equals(device.getTenantId(), loginUser.getUserId())) {
+//                device.setIsOwner(1);
+//            } else {
+//                device.setIsOwner(0);
+//            }
+//        }
+        return success(device);
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得设备分页")
+    @PreAuthorize("@ss.hasPermission('rq:yf-iot-device:query')")
+    public CommonResult<PageResult<YfIotDeviceRespVO>> getYfIotDevicePage(@Valid YfIotDevicePageReqVO pageReqVO) {
+        PageResult<YfIotDeviceDO> pageResult = yfIotDeviceService.getYfIotDevicePage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, YfIotDeviceRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出设备 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:yf-iot-device:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportYfIotDeviceExcel(@Valid YfIotDevicePageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<YfIotDeviceDO> list = yfIotDeviceService.getYfIotDevicePage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "设备.xls", "数据", YfIotDeviceRespVO.class,
+                        BeanUtils.toBean(list, YfIotDeviceRespVO.class));
+    }
+
+    @GetMapping("/generator")
+    @Operation(summary = "生成设备编号")
+    public CommonResult<String> generatorDeviceNum(Integer type) {
+        return success(yfIotDeviceService.generationDeviceNum(type));
+//        return AjaxResult.success(MessageUtils.message("operate.success"), yfIotDeviceService.generationDeviceNum(type));
+    }
+
+
+    @GetMapping("/shortList")
+    @Operation(summary = "设备分页简短列表")
+    public CommonResult<List<DeviceShortOutput>> shortList(YfIotDeviceSaveReqVO device) {
+        if (null == device.getDeptId()) {
+            device.setDeptId(SecurityFrameworkUtils.getLoginUserDeptId());
+        }
+        if (null == device.getShowChild()) {
+            device.setShowChild(false);
+        }
+        List<DeviceShortOutput> deviceShortOutputs = yfIotDeviceService.selectDeviceShortList(device);
+        return success(deviceShortOutputs);
+    }
+    @DeleteMapping("/{deviceIds}")
+    @Operation(summary = "批量删除设备")
+    public CommonResult<Boolean> remove(@PathVariable Long[] deviceIds) throws SchedulerException {
+        yfIotDeviceService.deleteDeviceByDeviceId(deviceIds[0]);
+        return success(true);
+    }
+}

+ 13 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/DeviceAlertCount.java

@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo;
+
+import lombok.Data;
+
+@Data
+public class DeviceAlertCount {
+    private String serialNumber;
+    private String sceneId;
+    private Integer alertCount;
+    private Integer noprocessedCount;
+    private Integer processedCount;
+    private Integer unprocessedCount;
+}

+ 147 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/DeviceShortOutput.java

@@ -0,0 +1,147 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 设备对象 iot_device
+ *
+ * @author kerwincui
+ * @date 2021-12-16
+ */
+@Getter
+@Setter
+public class DeviceShortOutput {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 产品分类ID
+     */
+    private Long deviceId;
+    /**
+     * 产品分类名称
+     */
+    //@Excel(name = "设备名称")
+    private String deviceName;
+    /**
+     * 产品ID
+     */
+    //@Excel(name = "产品ID")
+    private Long productId;
+    /**
+     * 产品名称
+     */
+    //@Excel(name = "产品名称")
+    private String productName;
+    /**
+     * 设备类型(1-直连设备、2-网关子设备、3-网关设备)
+     */
+    private Integer deviceType;
+    /**
+     * 租户ID
+     */
+    //@Excel(name = "租户ID")
+    private Long tenantId;
+    /**
+     * 租户名称
+     */
+    //@Excel(name = "租户名称")
+    private String tenantName;
+    /**
+     * 设备编号
+     */
+    //@Excel(name = "设备编号")
+    private String serialNumber;
+    /**
+     * 固件版本
+     */
+    //@Excel(name = "固件版本")
+    private BigDecimal firmwareVersion;
+    /**
+     * 设备状态(1-未激活,2-禁用,3-在线,4-离线)
+     */
+    //@Excel(name = "设备状态")
+    private Integer status;
+    /**
+     * 设备影子
+     */
+    private Integer isShadow;
+    private Integer isSimulate;
+    /**
+     * wifi信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85])
+     */
+    //@Excel(name = "wifi信号强度")
+    private Integer rssi;
+    //@Excel(name = "物模型")
+    private String thingsModelValue;
+    /**
+     * 激活时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    //@Excel(name = "激活时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date activeTime;
+    /**
+     * 激活时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    //@Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createTime;
+    //@Excel(name = "网关设备编号(子设备使用)")
+    private String gwDevCode;
+    /**
+     * 是否自定义位置
+     **/
+    private Integer locationWay;
+    /**
+     * 图片地址
+     */
+    private String imgUrl;
+    /**
+     * 是否设备所有者,用于查询
+     **/
+    private Integer isOwner;
+    /**
+     * 子设备数量
+     */
+    private Integer subDeviceCount;
+    /**
+     * 子设备地址
+     */
+    private Integer slaveId;
+    /*传输协议*/
+    private String transport;
+    /*设备通讯协议*/
+    private String protocolCode;
+    private DeviceAlertCount alertCount;
+    /**
+     * 产品guid
+     */
+    private String guid;
+    private List<ThingsModelValueItem> thingsModels;
+    private List<StringModelOutput> stringList;
+    private List<IntegerModelOutput> integerList;
+    private List<DecimalModelOutput> decimalList;
+    private List<EnumModelOutput> enumList;
+    private List<ArrayModelOutput> arrayList;
+    private List<BoolModelOutput> boolList;
+    private List<ReadOnlyModelOutput> readOnlyList;
+    private List<DeviceVo> deviceVoList;
+
+    public DeviceShortOutput() {
+        this.stringList = new ArrayList<>();
+        this.integerList = new ArrayList<>();
+        this.decimalList = new ArrayList<>();
+        this.enumList = new ArrayList<>();
+        this.arrayList = new ArrayList<>();
+        this.readOnlyList = new ArrayList<>();
+        this.boolList = new ArrayList<>();
+        this.deviceVoList = new ArrayList<>();
+    }
+
+}

+ 34 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/DeviceVo.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo;
+
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.device.YfIotDeviceDO;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class DeviceVo extends YfIotDeviceDO {
+    /**
+     * 上报时间
+     */
+    private Date reportTime;
+    /**
+     * 脚本ID
+     */
+    private String scriptIds;
+    /**
+     * 物模型编号
+     */
+    private String scriptId;
+    /**
+     * 物模型名称
+     */
+    private String scriptName;
+    /**
+     * 物模型值
+     */
+    private String scriptValue;
+    /**
+     * 类型
+     */
+    private String scriptOperator;
+}

+ 118 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/YfIotDevicePageReqVO.java

@@ -0,0 +1,118 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo;
+
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.device.YfIotDeviceDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.relation.IotYfSipRelationDO;
+import liquibase.pro.packaged.Y;
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 设备分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class YfIotDevicePageReqVO extends PageParam {
+
+    @Schema(description = "设备名称", example = "赵六")
+    private String deviceName;
+
+    @Schema(description = "产品ID", example = "10997")
+    private Long productId;
+
+    @Schema(description = "产品名称", example = "李四")
+    private String productName;
+
+    @Schema(description = "租户名称", example = "张三")
+    private String tenantName;
+
+    @Schema(description = "设备编号")
+    private String serialNumber;
+
+    @Schema(description = "子设备网关编号")
+    private String gwDevCode;
+
+    @Schema(description = "固件版本")
+    private Double firmwareVersion;
+    private Long deptId;
+
+    @Schema(description = "设备状态(1-未激活,2-禁用,3-在线,4-离线)", example = "2")
+    private Boolean status;
+
+    @Schema(description = "信号强度(	信号极好4格[-55— 0],	信号好3格[-70— -55],	信号一般2格[-85— -70],	信号差1格[-100— -85])")
+    private Integer rssi;
+
+    @Schema(description = "是否启用设备影子(0=禁用,1=启用)")
+    private Boolean isShadow;
+
+    @Schema(description = "定位方式(1=ip自动定位,2=设备定位,3=自定义)")
+    private Boolean locationWay;
+
+    @Schema(description = "物模型值")
+    private String thingsModelValue;
+
+    @Schema(description = "设备所在地址")
+    private String networkAddress;
+
+    @Schema(description = "设备入网IP")
+    private String networkIp;
+
+    @Schema(description = "设备经度")
+    private Double longitude;
+
+    @Schema(description = "设备纬度")
+    private Double latitude;
+
+    @Schema(description = "激活时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] activeTime;
+
+    @Schema(description = "设备摘要,格式")
+    private String summary;
+
+    @Schema(description = "图片地址", example = "https://www.iocoder.cn")
+    private String imgUrl;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "是否是模拟设备")
+    private Integer isSimulate;
+
+    @Schema(description = "从机id", example = "5004")
+    private Integer slaveId;
+
+    @Schema(description = "clientId编号", example = "8991")
+    private String clientId;
+
+//    @Schema(description = "客户端id", example = "7515")
+//    private String clientId;
+
+    @Schema(description = "发布")
+    private String postDev;
+
+    @Schema(description = "订阅")
+    private String getDev;
+
+    @Schema(description = "mqtt是否自定义状态(1为初始化,0为自定义)")
+    private Integer mqttstats;
+    private List<IotYfSipRelationDO> sipRelationList;
+    private List<YfIotDeviceDO> subDeviceList;
+}

+ 146 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/YfIotDeviceRespVO.java

@@ -0,0 +1,146 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo;
+
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.device.YfIotDeviceDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.relation.IotYfSipRelationDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import liquibase.pro.packaged.Y;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - 设备 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class YfIotDeviceRespVO {
+
+    @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26093")
+    @ExcelProperty("设备ID")
+    private Long deviceId;
+
+    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @ExcelProperty("设备名称")
+    private String deviceName;
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10997")
+    @ExcelProperty("产品ID")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @ExcelProperty("产品名称")
+    private String productName;
+    private List<YfIotDeviceRespVO> subDeviceList;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @ExcelProperty("租户名称")
+    private String tenantName;
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("设备编号")
+    private String serialNumber;
+
+    @Schema(description = "子设备网关编号")
+    @ExcelProperty("子设备网关编号")
+    private String gwDevCode;
+
+    @Schema(description = "固件版本", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("固件版本")
+    private Double firmwareVersion;
+
+    @Schema(description = "设备状态(1-未激活,2-禁用,3-在线,4-离线)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @ExcelProperty("设备状态(1-未激活,2-禁用,3-在线,4-离线)")
+    private Boolean status;
+
+    @Schema(description = "信号强度(	信号极好4格[-55— 0],	信号好3格[-70— -55],	信号一般2格[-85— -70],	信号差1格[-100— -85])")
+    @ExcelProperty("信号强度(	信号极好4格[-55— 0],	信号好3格[-70— -55],	信号一般2格[-85— -70],	信号差1格[-100— -85])")
+    private Integer rssi;
+    private Long deptId;
+    @Schema(description = "是否启用设备影子(0=禁用,1=启用)")
+    @ExcelProperty("是否启用设备影子(0=禁用,1=启用)")
+    private Boolean isShadow;
+
+    @Schema(description = "定位方式(1=ip自动定位,2=设备定位,3=自定义)")
+    @ExcelProperty("定位方式(1=ip自动定位,2=设备定位,3=自定义)")
+    private Boolean locationWay;
+
+    @Schema(description = "物模型值")
+    @ExcelProperty("物模型值")
+    private String thingsModelValue;
+
+    @Schema(description = "设备所在地址")
+    @ExcelProperty("设备所在地址")
+    private String networkAddress;
+
+    @Schema(description = "设备入网IP")
+    @ExcelProperty("设备入网IP")
+    private String networkIp;
+
+    @Schema(description = "设备经度")
+    @ExcelProperty("设备经度")
+    private Double longitude;
+
+    @Schema(description = "设备纬度")
+    @ExcelProperty("设备纬度")
+    private Double latitude;
+
+    @Schema(description = "激活时间")
+    @ExcelProperty("激活时间")
+    private LocalDateTime activeTime;
+
+    @Schema(description = "设备摘要")
+    @ExcelProperty("设备摘要,格式")
+    private String summary;
+
+    @Schema(description = "图片地址", example = "https://www.iocoder.cn")
+    @ExcelProperty("图片地址")
+    private String imgUrl;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    @ExcelProperty("删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    @ExcelProperty("创建者")
+    private String createBy;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "是否是模拟设备")
+    @ExcelProperty("是否是模拟设备")
+    private Integer isSimulate;
+
+    @Schema(description = "从机id", example = "5004")
+    @ExcelProperty("从机id")
+    private Integer slaveId;
+
+    @Schema(description = "clientId编号", example = "8991")
+    @ExcelProperty("clientId编号")
+    private String clientId;
+
+    @Schema(description = "发布")
+    @ExcelProperty("发布")
+    private String postDev;
+
+    @Schema(description = "订阅")
+    @ExcelProperty("订阅")
+    private String getDev;
+
+    @Schema(description = "mqtt是否自定义状态(1为初始化,0为自定义)")
+    @ExcelProperty("mqtt是否自定义状态(1为初始化,0为自定义)")
+    private Integer mqttstats;
+
+    private List<IotYfSipRelationDO> sipRelationList;
+
+    private Integer deviceType;
+}

+ 114 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/device/vo/YfIotDeviceSaveReqVO.java

@@ -0,0 +1,114 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.device.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 设备新增/修改 Request VO")
+@Data
+public class YfIotDeviceSaveReqVO {
+
+    @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26093")
+    private Long deviceId;
+
+    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @NotEmpty(message = "设备名称不能为空")
+    private String deviceName;
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10997")
+    @NotNull(message = "产品ID不能为空")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @NotEmpty(message = "产品名称不能为空")
+    private String productName;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+//    @NotEmpty(message = "租户名称不能为空")
+    private String tenantName;
+    private Boolean showChild;
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "设备编号不能为空")
+    private String serialNumber;
+    private Long tenantId;
+
+    @Schema(description = "子设备网关编号")
+    private String gwDevCode;
+
+    @Schema(description = "固件版本", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "固件版本不能为空")
+    private Double firmwareVersion;
+
+    @Schema(description = "设备状态(1-未激活,2-禁用,3-在线,4-离线)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "设备状态(1-未激活,2-禁用,3-在线,4-离线)不能为空")
+    private Boolean status;
+
+    @Schema(description = "信号强度(	信号极好4格[-55— 0],	信号好3格[-70— -55],	信号一般2格[-85— -70],	信号差1格[-100— -85])")
+    private Integer rssi;
+
+    @Schema(description = "是否启用设备影子(0=禁用,1=启用)")
+    private Boolean isShadow;
+
+    @Schema(description = "定位方式(1=ip自动定位,2=设备定位,3=自定义)")
+    private Boolean locationWay;
+
+    @Schema(description = "物模型值")
+    private String thingsModelValue;
+
+    @Schema(description = "设备所在地址")
+    private String networkAddress;
+
+    @Schema(description = "设备入网IP")
+    private String networkIp;
+
+    @Schema(description = "设备经度")
+    private Double longitude;
+
+    @Schema(description = "设备纬度")
+    private Double latitude;
+
+    @Schema(description = "激活时间")
+    private LocalDateTime activeTime;
+
+    @Schema(description = "设备摘要")
+    private String summary;
+
+    @Schema(description = "图片地址", example = "https://www.iocoder.cn")
+    private String imgUrl;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+    private Long deptId;
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "是否是模拟设备")
+    private Integer isSimulate;
+
+    @Schema(description = "从机id", example = "5004")
+    private Integer slaveId;
+
+    @Schema(description = "clientId编号", example = "8991")
+    private String clientId;
+
+    @Schema(description = "发布")
+    private String postDev;
+
+    @Schema(description = "订阅")
+    private String getDev;
+
+    @Schema(description = "mqtt是否自定义状态(1为初始化,0为自定义)")
+    private Integer mqttstats;
+
+}

+ 98 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/devicetemplate/IotYfDeviceTemplateController.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.devicetemplate;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.devicetemplate.vo.IotYfDeviceTemplatePageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.devicetemplate.vo.IotYfDeviceTemplateRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.devicetemplate.vo.IotYfDeviceTemplateSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.devicetemplate.IotYfDeviceTemplateDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.devicetemplate.IotYfDeviceTemplateService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - yf设备采集点模板关联对象")
+@RestController
+@RequestMapping("/yf/iot-device-template")
+@Validated
+public class IotYfDeviceTemplateController {
+
+    @Resource
+    private IotYfDeviceTemplateService iotYfDeviceTemplateService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建yf设备采集点模板关联对象")
+    @PreAuthorize("@ss.hasPermission('yf:iot-device-template:create')")
+    public CommonResult<Long> createIotDeviceTemplate(@Valid @RequestBody IotYfDeviceTemplateSaveReqVO createReqVO) {
+        return success(iotYfDeviceTemplateService.createIotDeviceTemplate(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新yf设备采集点模板关联对象")
+    @PreAuthorize("@ss.hasPermission('yf:iot-device-template:update')")
+    public CommonResult<Boolean> updateIotDeviceTemplate(@Valid @RequestBody IotYfDeviceTemplateSaveReqVO updateReqVO) {
+        iotYfDeviceTemplateService.updateIotDeviceTemplate(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除yf设备采集点模板关联对象")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('yf:iot-device-template:delete')")
+    public CommonResult<Boolean> deleteIotDeviceTemplate(@RequestParam("id") Long id) {
+        iotYfDeviceTemplateService.deleteIotDeviceTemplate(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得yf设备采集点模板关联对象")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('yf:iot-device-template:query')")
+    public CommonResult<IotYfDeviceTemplateRespVO> getIotDeviceTemplate(@RequestParam("id") Long id) {
+        IotYfDeviceTemplateDO iotDeviceTemplate = iotYfDeviceTemplateService.getIotDeviceTemplate(id);
+        return success(BeanUtils.toBean(iotDeviceTemplate, IotYfDeviceTemplateRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得yf设备采集点模板关联对象分页")
+    @PreAuthorize("@ss.hasPermission('yf:iot-device-template:query')")
+    public CommonResult<PageResult<IotYfDeviceTemplateRespVO>> getIotDeviceTemplatePage(@Valid IotYfDeviceTemplatePageReqVO pageReqVO) {
+        PageResult<IotYfDeviceTemplateDO> pageResult = iotYfDeviceTemplateService.getIotDeviceTemplatePage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotYfDeviceTemplateRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出yf设备采集点模板关联对象 Excel")
+    @PreAuthorize("@ss.hasPermission('yf:iot-device-template:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotDeviceTemplateExcel(@Valid IotYfDeviceTemplatePageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotYfDeviceTemplateDO> list = iotYfDeviceTemplateService.getIotDeviceTemplatePage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "yf设备采集点模板关联对象.xls", "数据", IotYfDeviceTemplateRespVO.class,
+                        BeanUtils.toBean(list, IotYfDeviceTemplateRespVO.class));
+    }
+
+}

+ 19 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/devicetemplate/vo/IotYfDeviceTemplatePageReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.devicetemplate.vo;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+
+@Schema(description = "管理后台 - yf设备采集点模板关联对象分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotYfDeviceTemplatePageReqVO extends PageParam {
+
+    @Schema(description = "产品id", example = "14386")
+    private Long productId;
+
+    @Schema(description = "采集点模板id", example = "2720")
+    private Long templateId;
+
+}

+ 24 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/devicetemplate/vo/IotYfDeviceTemplateRespVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.devicetemplate.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - yf设备采集点模板关联对象 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotYfDeviceTemplateRespVO {
+
+    @Schema(description = "自增id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1341")
+    @ExcelProperty("自增id")
+    private Long id;
+
+    @Schema(description = "产品id", example = "14386")
+    @ExcelProperty("产品id")
+    private Long productId;
+
+    @Schema(description = "采集点模板id", example = "2720")
+    @ExcelProperty("采集点模板id")
+    private Long templateId;
+
+}

+ 19 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/devicetemplate/vo/IotYfDeviceTemplateSaveReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.devicetemplate.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+@Schema(description = "管理后台 - yf设备采集点模板关联对象新增/修改 Request VO")
+@Data
+public class IotYfDeviceTemplateSaveReqVO {
+
+    @Schema(description = "自增id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1341")
+    private Long id;
+
+    @Schema(description = "产品id", example = "14386")
+    private Long productId;
+
+    @Schema(description = "采集点模板id", example = "2720")
+    private Long templateId;
+
+}

+ 20 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/AlarmMethod.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum AlarmMethod {
+    unknown("0", "未知"),
+    telAlarm("1", "电话报警"),
+    devAlarm("2", "设备报警"),
+    smsAlarm("3", "短信报警"),
+    gpsAlarm("4", "GPS报警"),
+    videoAlarm("5", "视频报警"),
+    devErrorAlarm("6", "设备故障报警"),
+    other("7", "其他报警");
+    private final String value;
+
+    private final String text;
+}

+ 61 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/AlarmType.java

@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@AllArgsConstructor
+@Getter
+public enum AlarmType {
+    //设备报警
+    videoLost("1", "视频丢失报警", AlarmMethod.devAlarm),
+    videoBroken("2", "设备防拆报警", AlarmMethod.devAlarm),
+    diskFull("3", "存储设备磁盘满报警", AlarmMethod.devAlarm),
+    hot("4", "设备高温报警", AlarmMethod.devAlarm),
+    cold("5", "设备低温报警", AlarmMethod.devAlarm),
+    //视频报警
+    manual("1", "人工视频报警", AlarmMethod.videoAlarm),
+    moving("2", "运动目标检测报警", AlarmMethod.videoAlarm),
+    residual("3", "遗留物检测报警", AlarmMethod.videoAlarm),
+    remove("4", "物体移除检测报警", AlarmMethod.videoAlarm),
+    tripLine("5", "绊线检测报警", AlarmMethod.videoAlarm),
+    intrusion("6", "入侵检测报警", AlarmMethod.videoAlarm),
+    retrograde("7", "逆行检测报警", AlarmMethod.videoAlarm),
+    wandering("8", "徘徊检测报警", AlarmMethod.videoAlarm),
+    density("9", "密度检测报警", AlarmMethod.videoAlarm),
+    error("10", "视频异常检测报警", AlarmMethod.videoAlarm),
+    fastMoving("11", "快速移动报警", AlarmMethod.videoAlarm),
+
+    //存储设备
+    storageDiskError("1", "存储设备磁盘故障报警", AlarmMethod.devErrorAlarm),
+
+    storageFanError("2", "存储设备风扇故障报警", AlarmMethod.devErrorAlarm);
+
+    private final String code;
+
+    private final String text;
+
+    private final AlarmMethod method;
+
+    private static final Map<AlarmMethod, Map<String, AlarmType>> fastMap = new EnumMap<>(AlarmMethod.class);
+
+    static {
+        for (AlarmType value : AlarmType.values()) {
+            fastMap.computeIfAbsent(value.method, (ignore) -> new HashMap<>())
+                    .put(value.code, value);
+        }
+    }
+
+    public static Optional<AlarmType> of(AlarmMethod method, String code) {
+        Map<String, AlarmType> mapping = fastMap.get(method);
+        if (mapping == null) {
+            return Optional.empty();
+        }
+        return Optional.ofNullable(mapping.get(code));
+    }
+
+}

+ 22 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/ChannelStatus.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum ChannelStatus {
+    online("ON", "在线"),
+
+    lost("VLOST", "视频丢失"),
+    defect("DEFECT", "故障"),
+    add("ADD", "新增"),
+    delete("DEL", "删除"),
+    update("UPDATE", "更新"),
+    offline("OFF", "离线"),
+    ;
+
+    private final String code;
+    private final String text;
+
+}

+ 5 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/ChannelType.java

@@ -0,0 +1,5 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+public enum ChannelType {
+    CivilCode, BusinessGroup,VirtualOrganization,Other
+}

+ 30 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/DataEnum.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * @author gsb
+ * @date 2023/6/3 14:09
+ */
+@Getter
+@AllArgsConstructor
+public enum DataEnum {
+
+    DECIMAL("decimal", "十进制"), DOUBLE("double", "双精度"), ENUM("enum", "枚举"), BOOLEAN("bool", "布尔类型"), INTEGER("integer", "整形"), OBJECT("object", "对象"), STRING("string", "字符串"), ARRAY("array", "数组");
+
+    String type;
+    String msg;
+
+    public static DataEnum convert(String type) {
+        for (DataEnum value : DataEnum.values()) {
+            if (Objects.equals(value.type, type)) {
+                return value;
+            }
+        }
+        return DataEnum.STRING;
+    }
+
+}

+ 15 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/DeviceChannelStatus.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum DeviceChannelStatus {
+    //1=-未使用,2-禁用,3-在线,4-离线
+    notused(1),
+    off(2),
+    online(3),
+    offline(4);
+    private final Integer value;
+}

+ 51 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/Direct.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+
+import java.util.Arrays;
+
+@AllArgsConstructor
+public enum Direct {
+    UP(0x08),
+    DOWN(0x04),
+    LEFT(0x02),
+    RIGHT(0x01),
+    ZOOM_IN(0x10),
+    ZOOM_OUT(0x20),
+    STOP(0) {
+        @Override
+        public int merge(int code) {
+            return code;
+        }
+
+        @Override
+        public boolean match(int code) {
+            return code == 0;
+        }
+    };
+    private final int code;
+
+    public int merge(int code) {
+        return code | this.code;
+    }
+
+    public boolean match(int code) {
+        return (code & this.code) != 0;
+    }
+
+    public static Direct[] values(int code) {
+        return Arrays
+                .stream(values())
+                .filter(direct -> direct.match(code))
+                .toArray(Direct[]::new);
+    }
+
+    public static Direct[] values(String code) {
+        String[] codes = code.toUpperCase().split(",");
+
+        return Arrays
+                .stream(codes)
+                .map(Direct::valueOf)
+                .toArray(Direct[]::new);
+    }
+}

+ 17 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/PTZType.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum PTZType {
+    unknown(0, "未知"),
+    ball(1, "球机"),
+    hemisphere(2, "半球机"),
+    fixed(3, "固定抢机"),
+    remoteControl(4, "遥控抢机");
+
+    private final Integer value;
+    private final String text;
+}

+ 8 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/SessionType.java

@@ -0,0 +1,8 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+public enum SessionType {
+    play,
+    playrecord,
+    playback,
+    download
+}

+ 49 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/ThingsModelType.java

@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 物模型类型
+ *
+ * @author bill
+ */
+@Getter
+@AllArgsConstructor
+public enum ThingsModelType {
+
+    PROP(1, "PROPERTY", "属性", "properties"), SERVICE(2, "FUNCTION", "服务", "functions"), EVENT(3, "EVENT", "事件", "events"),
+    ;
+
+    int code;
+    String type;
+    String name;
+    String list;
+
+    public static ThingsModelType getType(int code) {
+        for (ThingsModelType value : ThingsModelType.values()) {
+            if (value.code == code) {
+                return value;
+            }
+        }
+        return ThingsModelType.PROP;
+    }
+
+    public static ThingsModelType getType(String type) {
+        for (ThingsModelType value : ThingsModelType.values()) {
+            if (value.type.equals(type)) {
+                return value;
+            }
+        }
+        return ThingsModelType.PROP;
+    }
+
+    public static String getName(int code) {
+        for (ThingsModelType value : ThingsModelType.values()) {
+            if (value.code == code) {
+                return value.list;
+            }
+        }
+        return ThingsModelType.PROP.list;
+    }
+}

+ 78 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/enums/TopicType.java

@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * topic类型
+ *
+ * @author gsb
+ */
+@Getter
+@AllArgsConstructor
+public enum TopicType {
+
+    /**
+     * @param     type  0:标记是订阅主题  1:标记是发布属性
+     * @param     order 排序
+     * @param     topicSuffix topic后缀
+     * @param     msg  描述信息
+     */
+
+    /*** 通用设备上报主题(平台订阅) ***/
+    PROPERTY_POST(0, 1, "/property/post", "订阅属性"),
+    EVENT_POST(0, 2, "/event/post", "订阅事件"),
+    FUNCTION_POST(0, 3, "/function/post", "订阅功能"),
+    INFO_POST(0, 4, "/info/post", "订阅设备信息"),
+    NTP_POST(0, 5, "/ntp/post", "订阅时钟同步"),
+    SERVICE_INVOKE_REPLY(0, 8, "/service/reply", "订阅功能调用返回结果"),
+    FIRMWARE_UPGRADE_REPLY(0, 9, "/upgrade/reply", "订阅设备OTA升级结果"),
+    MESSAGE_POST(0, 26, "/message/post", "订阅设备上报消息"),
+
+    /*** 通用设备订阅主题(平台下发)***/
+    FUNCTION_GET(1, 17, "/function/get", "发布功能"),
+    PROPERTY_GET(1, 12, "/property/get", "发布设备属性读取"),
+    PROPERTY_SET(1, 15, "/property/set", "设置设备属性读取"),
+    FIRMWARE_SET(1, 14, "/upgrade/set", "发布OTA升级"),
+    STATUS_POST(1, 11, "/status/post", "发布状态"),
+    NTP_GET(1, 15, "/ntp/get", "发布时钟同步"),
+    INFO_GET(1, 18, "/info/get", "发布设备信息"),
+
+
+    /*** 视频监控设备转协议发布 ***/
+    DEV_INFO_POST(3, 19, "/info/post", "设备端发布设备信息"),
+    DEV_EVENT_POST(3, 20, "/event/post", "设备端发布事件"),
+    DEV_FUNCTION_POST(3, 21, "/function/post", "设备端发布功能"),
+    DEV_PROPERTY_POST(3, 22, "/property/post", "设备端发布属性"),
+
+
+    /*** webSocket转发前端使用  ***/
+    WS_SERVICE_INVOKE(2, 16, "/ws/service", "WS服务调用"),
+    WS_LOG_INVOKE(2, 17, "/ws/log", "ws下发指令日志"),
+
+
+    /*** 模拟设备使用 ***/
+    PROPERTY_GET_SIMULATE(4, 23, "/property/get/simulate", "发布属性读取"),
+    PROPERTY_SET_SIMULATE(4, 13, "/property/set/simulate", "发布属性写入"),
+    WS_SERVICE_INVOKE_SIMULATE(2, 24, "/ws/post/simulate", "模拟设备WS推送"),
+    PROPERTY_POST_SIMULATE(2, 25, "/property/simulate/post", "订阅属性"),
+
+    WS_TEST_POST(2, 24, "/ws/test/post", "数据调试设备下发"),
+    WS_TEST_POLL(2, 25, "/ws/test/poll", "数据调试轮询指令下发"),
+    WS_TEST_GET(2, 25, "/ws/test/get", "数据调试指令下发");
+
+    Integer type;
+    Integer order;
+    String topicSuffix;
+    String msg;
+
+    public static TopicType getType(String topicSuffix) {
+        for (TopicType value : TopicType.values()) {
+            if (value.topicSuffix.equals(topicSuffix)) {
+                return value;
+            }
+        }
+        return TopicType.PROPERTY_POST;
+    }
+
+}

+ 98 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/group/IotYfGroupController.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.group;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.group.vo.IotYfGroupPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.group.vo.IotYfGroupRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.group.vo.IotYfGroupSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.group.IotYfGroupDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.group.IotYfGroupService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - yf设备分组")
+@RestController
+@RequestMapping("/rq/iot-group")
+@Validated
+public class IotYfGroupController {
+
+    @Resource
+    private IotYfGroupService iotGroupService;
+
+    @PostMapping
+    @Operation(summary = "创建yf设备分组")
+    @PreAuthorize("@ss.hasPermission('rq:iot-group:create')")
+    public CommonResult<Long> createIotGroup(@Valid @RequestBody IotYfGroupSaveReqVO createReqVO) {
+        return success(iotGroupService.createIotGroup(createReqVO));
+    }
+
+    @PutMapping
+    @Operation(summary = "更新yf设备分组")
+    @PreAuthorize("@ss.hasPermission('rq:iot-group:update')")
+    public CommonResult<Boolean> updateIotGroup(@Valid @RequestBody IotYfGroupSaveReqVO updateReqVO) {
+        iotGroupService.updateIotGroup(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除yf设备分组")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-group:delete')")
+    public CommonResult<Boolean> deleteIotGroup(@RequestParam("id") Long id) {
+        iotGroupService.deleteIotGroup(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得yf设备分组")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-group:query')")
+    public CommonResult<IotYfGroupRespVO> getIotGroup(@RequestParam("id") Long id) {
+        IotYfGroupDO iotGroup = iotGroupService.getIotGroup(id);
+        return success(BeanUtils.toBean(iotGroup, IotYfGroupRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得yf设备分组分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-group:query')")
+    public CommonResult<PageResult<IotYfGroupRespVO>> getIotGroupPage(@Valid IotYfGroupPageReqVO pageReqVO) {
+        PageResult<IotYfGroupDO> pageResult = iotGroupService.getIotGroupPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotYfGroupRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出yf设备分组 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-group:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotGroupExcel(@Valid IotYfGroupPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotYfGroupDO> list = iotGroupService.getIotGroupPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "yf设备分组.xls", "数据", IotYfGroupRespVO.class,
+                        BeanUtils.toBean(list, IotYfGroupRespVO.class));
+    }
+
+}

+ 42 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/group/vo/IotYfGroupPageReqVO.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.group.vo;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - yf设备分组分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotYfGroupPageReqVO extends PageParam {
+
+    @Schema(description = "分组名称", example = "赵六")
+    private String groupName;
+
+    @Schema(description = "分组排序")
+    private Integer groupOrder;
+
+    @Schema(description = "用户ID", example = "11789")
+    private Long userId;
+
+    @Schema(description = "用户昵称", example = "张三")
+    private String userName;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "备注", example = "你猜")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "部门id", example = "19777")
+    private Long deptId;
+
+}

+ 50 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/group/vo/IotYfGroupRespVO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.group.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - yf设备分组 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotYfGroupRespVO {
+
+    @Schema(description = "分组ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25395")
+    @ExcelProperty("分组ID")
+    private Long groupId;
+
+    @Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @ExcelProperty("分组名称")
+    private String groupName;
+
+    @Schema(description = "分组排序", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("分组排序")
+    private Integer groupOrder;
+
+    @Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11789")
+    @ExcelProperty("用户ID")
+    private Long userId;
+
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @ExcelProperty("用户昵称")
+    private String userName;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    @ExcelProperty("删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "备注", example = "你猜")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "部门id", example = "19777")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+}

+ 41 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/group/vo/IotYfGroupSaveReqVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.group.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - yf设备分组新增/修改 Request VO")
+@Data
+public class IotYfGroupSaveReqVO {
+
+    @Schema(description = "分组ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25395")
+    private Long groupId;
+
+    @Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @NotEmpty(message = "分组名称不能为空")
+    private String groupName;
+
+    @Schema(description = "分组排序", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "分组排序不能为空")
+    private Integer groupOrder;
+
+    @Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11789")
+    @NotNull(message = "用户ID不能为空")
+    private Long userId;
+
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @NotEmpty(message = "用户昵称不能为空")
+    private String userName;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "备注", example = "你猜")
+    private String remark;
+
+    @Schema(description = "部门id", example = "19777")
+    private Long deptId;
+
+}

+ 74 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/PlayerController.java

@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.media;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.pms.service.yanfan.play.IPlayService;
+import cn.iocoder.yudao.module.pms.service.yanfan.play.model.Stream;
+import io.swagger.v3.oas.annotations.Operation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Slf4j
+@RestController
+@RequestMapping("/rq/sip/player")
+public class PlayerController {
+    @Autowired
+    private IPlayService playService;
+
+    @GetMapping("/getBigScreenUrl/{deviceId}/{channelId}")
+    public CommonResult<String> getBigScreenUrl(@PathVariable String deviceId, @PathVariable String channelId) {
+//        return AjaxResult.success(MessageUtils.message("success"), playService
+//                .play(deviceId, channelId, false)
+//                .getHttps_fmp4());
+        return success(playService
+                .play(deviceId, channelId, false)
+                .getHttps_fmp4());
+    }
+
+    @Operation(summary="直播播放")
+    @GetMapping("/play/{deviceId}/{channelId}")
+    public CommonResult<Stream> play(@PathVariable String deviceId, @PathVariable String channelId) {
+        return success(playService.play(deviceId, channelId, false));
+    }
+
+    @Operation(summary="回放播放")
+    @GetMapping("/playback/{deviceId}/{channelId}")
+    public CommonResult<Stream> playback(@PathVariable String deviceId, @PathVariable String channelId, String start, String end) {
+        return success(playService.playback(deviceId, channelId, start, end));
+    }
+
+    @Operation(summary="停止推流")
+    @GetMapping("/closeStream/{deviceId}/{channelId}/{streamId}")
+    public CommonResult<String> closeStream(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId) {
+        return success(playService.closeStream(deviceId, channelId, streamId));
+    }
+
+    @Operation(summary="回放暂停")
+    @GetMapping("/playbackPause/{deviceId}/{channelId}/{streamId}")
+    public CommonResult<String> playbackPause(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId) {
+        return success(playService.playbackPause(deviceId, channelId, streamId));
+    }
+
+    @Operation(summary="回放恢复")
+    @GetMapping("/playbackReplay/{deviceId}/{channelId}/{streamId}")
+    public CommonResult<String> playbackReplay(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId) {
+        return success(playService.playbackReplay(deviceId, channelId, streamId));
+    }
+
+    @Operation(summary="录像回放定位")
+    @GetMapping("/playbackSeek/{deviceId}/{channelId}/{streamId}")
+    public CommonResult<String> playbackSeek(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId, long seek) {
+        return success(playService.playbackSeek(deviceId, channelId, streamId, seek));
+    }
+
+    @Operation(summary="录像倍速播放")
+    @GetMapping("/playbackSpeed/{deviceId}/{channelId}/{streamId}")
+    public CommonResult<String> playbackSpeed(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String streamId, Integer speed) {
+        return success(playService.playbackSpeed(deviceId, channelId, streamId, speed));
+    }
+}

+ 143 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/YfMediaServerController.java

@@ -0,0 +1,143 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.media;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.media.vo.YfMediaServerPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.media.vo.YfMediaServerRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.media.vo.YfMediaServerSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.media.YfMediaServerDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.media.YfMediaServerService;
+import lombok.extern.java.Log;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - yf流媒体服务器配置")
+@RestController
+@RequestMapping("/rq/yf-media-server")
+@Validated
+public class YfMediaServerController {
+
+    @Resource
+    private YfMediaServerService yfMediaServerService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建yf流媒体服务器配置")
+    @PreAuthorize("@ss.hasPermission('rq:yf-media-server:create')")
+    public CommonResult<Integer> createYfMediaServer(@Valid @RequestBody YfMediaServerSaveReqVO createReqVO) {
+        return success(yfMediaServerService.createYfMediaServer(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新yf流媒体服务器配置")
+    @PreAuthorize("@ss.hasPermission('rq:yf-media-server:update')")
+    public CommonResult<Boolean> updateYfMediaServer(@Valid @RequestBody YfMediaServerSaveReqVO updateReqVO) {
+        yfMediaServerService.updateMediaServer(updateReqVO);
+        return success(true);
+    }
+
+    /**
+     * 获取流媒体服务器配置详细信息,只获取第一条
+     */
+    @Operation(summary = "获取流媒体服务器配置详细信息")
+    //@PreAuthorize("@ss.hasPermi('iot:video:query')")
+    @GetMapping()
+    public CommonResult<YfMediaServerDO> getInfo() {
+        List<YfMediaServerDO> list = yfMediaServerService.selectMediaServer();
+        if (list == null || list.size() == 0) {
+            YfMediaServerDO mediaServer = new YfMediaServerDO();
+            // 设置默认值
+            mediaServer.setEnabled(1);
+            mediaServer.setDomain("");
+            mediaServer.setIp("");
+            mediaServer.setPortHttp(8082L);
+            mediaServer.setPortHttps(8443L);
+            mediaServer.setPortRtmp(1935L);
+            mediaServer.setPortRtsp(554L);
+            mediaServer.setProtocol("HTTP");
+            mediaServer.setSecret("035c73f7-bb6b-4889-a715-d9eb2d192xxx");
+            mediaServer.setRtpPortRange("30000,30500");
+            list = new ArrayList<>();
+            list.add(mediaServer);
+        }
+        return success(list.get(0));
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除yf流媒体服务器配置")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:yf-media-server:delete')")
+    public CommonResult<Boolean> deleteYfMediaServer(@RequestParam("id") Long id) {
+        yfMediaServerService.deleteYfMediaServer(id);
+        return success(true);
+    }
+
+    /**
+     * 删除流媒体服务器配置
+     */
+    @Operation(summary = "删除流媒体服务器配置")
+    //@PreAuthorize("@ss.hasPermi('iot:video:remove')")
+    @DeleteMapping("/{ids}")
+    public CommonResult<Integer> remove(@PathVariable Long[] ids) {
+        return success(yfMediaServerService.deleteMediaServerByIds(ids));
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得yf流媒体服务器配置")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:yf-media-server:query')")
+    public CommonResult<YfMediaServerRespVO> getYfMediaServer(@RequestParam("id") Long id) {
+        YfMediaServerDO yfMediaServer = yfMediaServerService.getYfMediaServer(id);
+        return success(BeanUtils.toBean(yfMediaServer, YfMediaServerRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得yf流媒体服务器配置分页")
+    @PreAuthorize("@ss.hasPermission('rq:yf-media-server:query')")
+    public CommonResult<PageResult<YfMediaServerRespVO>> getYfMediaServerPage(@Valid YfMediaServerPageReqVO pageReqVO) {
+        PageResult<YfMediaServerDO> pageResult = yfMediaServerService.getYfMediaServerPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, YfMediaServerRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出yf流媒体服务器配置 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:yf-media-server:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportYfMediaServerExcel(@Valid YfMediaServerPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<YfMediaServerDO> list = yfMediaServerService.getYfMediaServerPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "yf流媒体服务器配置.xls", "数据", YfMediaServerRespVO.class,
+                        BeanUtils.toBean(list, YfMediaServerRespVO.class));
+    }
+
+
+    @Operation(summary = "检验流媒体服务")
+    //@PreAuthorize("@ss.hasPermi('iot:video:list')")
+    @GetMapping(value = "/check")
+    public CommonResult<YfMediaServerDO> checkMediaServer(@RequestParam String ip, @RequestParam Long port, @RequestParam String secret) {
+        YfMediaServerDO mediaServer = yfMediaServerService.checkMediaServer(ip, port, secret);
+        return success(mediaServer);
+    }
+}

+ 91 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/vo/YfMediaServerPageReqVO.java

@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.media.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - yf流媒体服务器配置分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class YfMediaServerPageReqVO extends PageParam {
+
+    @Schema(description = "服务器标识", example = "10688")
+    private String serverId;
+
+    @Schema(description = "租户名称", example = "芋艿")
+    private String tenantName;
+
+    @Schema(description = "使能开关")
+    private Integer enabled;
+
+    @Schema(description = "默认播放协议")
+    private String protocol;
+
+    @Schema(description = "服务器ip")
+    private String ip;
+
+    @Schema(description = "服务器域名")
+    private String domain;
+
+    @Schema(description = "回调服务器地址", example = "https://www.iocoder.cn")
+    private String hookurl;
+
+    @Schema(description = "流媒体密钥")
+    private String secret;
+
+    @Schema(description = "http端口")
+    private Integer portHttp;
+
+    @Schema(description = "https端口")
+    private Integer portHttps;
+
+    @Schema(description = "rtmp端口")
+    private Integer portRtmp;
+
+    @Schema(description = "rtsp端口")
+    private Integer portRtsp;
+
+    @Schema(description = "RTP收流端口")
+    private Integer rtpProxyPort;
+
+    @Schema(description = "是否使用多端口模式")
+    private Boolean rtpEnable;
+
+    @Schema(description = "rtp端口范围")
+    private String rtpPortRange;
+
+    @Schema(description = "录像服务端口")
+    private Integer recordPort;
+
+    @Schema(description = "是否自动同步配置ZLM")
+    private Boolean autoConfig;
+
+    @Schema(description = "状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你说的对")
+    private String remark;
+
+    @Schema(description = "ws端口")
+    private Integer portWs;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 115 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/vo/YfMediaServerRespVO.java

@@ -0,0 +1,115 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.media.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - yf流媒体服务器配置 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class YfMediaServerRespVO {
+
+    @Schema(description = "流媒体配置ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28202")
+    @ExcelProperty("流媒体配置ID")
+    private Long id;
+
+    @Schema(description = "服务器标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "10688")
+    @ExcelProperty("服务器标识")
+    private String serverId;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @ExcelProperty("租户名称")
+    private String tenantName;
+
+    @Schema(description = "使能开关")
+    @ExcelProperty("使能开关")
+    private Integer enabled;
+
+    @Schema(description = "默认播放协议", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("默认播放协议")
+    private String protocol;
+
+    @Schema(description = "服务器ip", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("服务器ip")
+    private String ip;
+
+    @Schema(description = "服务器域名", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("服务器域名")
+    private String domain;
+
+    @Schema(description = "回调服务器地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    @ExcelProperty("回调服务器地址")
+    private String hookurl;
+
+    @Schema(description = "流媒体密钥", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("流媒体密钥")
+    private String secret;
+
+    @Schema(description = "http端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("http端口")
+    private Integer portHttp;
+
+    @Schema(description = "https端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("https端口")
+    private Integer portHttps;
+
+    @Schema(description = "rtmp端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("rtmp端口")
+    private Integer portRtmp;
+
+    @Schema(description = "rtsp端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("rtsp端口")
+    private Integer portRtsp;
+
+    @Schema(description = "RTP收流端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("RTP收流端口")
+    private Integer rtpProxyPort;
+
+    @Schema(description = "是否使用多端口模式", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("是否使用多端口模式")
+    private Boolean rtpEnable;
+
+    @Schema(description = "rtp端口范围", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("rtp端口范围")
+    private String rtpPortRange;
+
+    @Schema(description = "录像服务端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("录像服务端口")
+    private Integer recordPort;
+
+    @Schema(description = "是否自动同步配置ZLM", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("是否自动同步配置ZLM")
+    private Boolean autoConfig;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("状态")
+    private Integer status;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你说的对")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "ws端口")
+    @ExcelProperty("ws端口")
+    private Integer portWs;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 104 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/media/vo/YfMediaServerSaveReqVO.java

@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.media.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.*;
+
+@Schema(description = "管理后台 - yf流媒体服务器配置新增/修改 Request VO")
+@Data
+public class YfMediaServerSaveReqVO {
+
+    @Schema(description = "流媒体配置ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28202")
+    private Long id;
+
+    @Schema(description = "服务器标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "10688")
+    @NotEmpty(message = "服务器标识不能为空")
+    private String serverId;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+//    @NotEmpty(message = "租户名称不能为空")
+    private String tenantName;
+
+    @Schema(description = "使能开关")
+    private Integer enabled;
+
+    @Schema(description = "默认播放协议", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "默认播放协议不能为空")
+    private String protocol;
+
+    @Schema(description = "服务器ip", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "服务器ip不能为空")
+    private String ip;
+
+    @Schema(description = "服务器域名", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "服务器域名不能为空")
+    private String domain;
+
+    @Schema(description = "回调服务器地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    @NotEmpty(message = "回调服务器地址不能为空")
+    private String hookurl;
+
+    @Schema(description = "流媒体密钥", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "流媒体密钥不能为空")
+    private String secret;
+
+    @Schema(description = "http端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "http端口不能为空")
+    private Integer portHttp;
+
+    @Schema(description = "https端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "https端口不能为空")
+    private Integer portHttps;
+
+    @Schema(description = "rtmp端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "rtmp端口不能为空")
+    private Integer portRtmp;
+
+    @Schema(description = "rtsp端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "rtsp端口不能为空")
+    private Integer portRtsp;
+
+    @Schema(description = "RTP收流端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "RTP收流端口不能为空")
+    private Integer rtpProxyPort;
+
+    @Schema(description = "是否使用多端口模式", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "是否使用多端口模式不能为空")
+    private Integer rtpEnable;
+
+    @Schema(description = "rtp端口范围", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "rtp端口范围不能为空")
+    private String rtpPortRange;
+
+    @Schema(description = "录像服务端口", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "录像服务端口不能为空")
+    private Integer recordPort;
+
+    @Schema(description = "是否自动同步配置ZLM", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "是否自动同步配置ZLM不能为空")
+    private Integer autoConfig;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+//    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)", requiredMode = Schema.RequiredMode.REQUIRED)
+//    @NotEmpty(message = "删除标志(0代表存在 2代表删除)不能为空")
+    private String delFlag;
+
+    @Schema(description = "创建者", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你说的对")
+    private String remark;
+
+    @Schema(description = "ws端口")
+    private Integer portWs;
+    private Long tenantId;
+}

+ 120 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/PubMqttCallBack.java

@@ -0,0 +1,120 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.utils.TopicsUtils;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+
+/**
+ * mqtt客户端回调
+ */
+@Slf4j
+@Component
+@Data
+@NoArgsConstructor
+public class PubMqttCallBack implements MqttCallbackExtended {
+    /**
+     * mqtt客户端
+     */
+    private MqttAsyncClient client;
+    /**
+     * 创建客户端参数
+     */
+    private MqttConnectOptions options;
+
+    @Resource
+    private TopicsUtils topicsUtils;
+
+    private Boolean enabled;
+
+    private IMqttMessageListener listener;
+
+
+    public PubMqttCallBack(MqttAsyncClient client, MqttConnectOptions options, Boolean enabled, IMqttMessageListener listener) {
+        this.client = client;
+        this.options = options;
+        this.enabled = enabled;
+        this.listener = listener;
+    }
+
+    /**
+     * mqtt客户端连接
+     *
+     * @param cause 错误
+     */
+    @Override
+    public void connectionLost(Throwable cause) {
+        // 连接丢失后,一般在这里面进行重连
+        log.debug("=>mqtt 连接丢失", cause);
+        int count = 1;
+        // int sleepTime = 0;
+        boolean willConnect = true;
+        while (willConnect) {
+            try {
+                Thread.sleep(1000);
+                log.debug("=>连接[{}]断开,尝试重连第{}次", this.client.getServerURI(), count++);
+                this.client.connect(this.options);
+                log.debug("=>重连成功");
+                willConnect = false;
+            } catch (Exception e) {
+                log.error("=>重连异常", e);
+            }
+        }
+    }
+
+    /**
+     * 客户端订阅主题回调消息
+     *
+     * @param topic   主题
+     * @param message 消息
+     */
+    @Override
+    public void messageArrived(String topic, MqttMessage message) throws Exception {
+        // subscribe后得到的消息会执行到这里面
+        try {
+            listener.messageArrived(topic, message);
+        } catch (Exception e) {
+            log.warn("mqtt 订阅消息异常", e);
+        }
+    }
+
+    @Override
+    public void deliveryComplete(IMqttDeliveryToken token) {
+
+    }
+
+
+    /**
+     * 监听mqtt连接消息
+     */
+    @Override
+    public void connectComplete(boolean reconnect, String serverURI) {
+        log.info("MQTT内部客户端已经连接!");
+        System.out.print("\n"
+                         + " * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *      \n"
+                         + " *                           __                 _              _  _      *     \n"
+                         + " *                          / _|               | |            (_)(_)     *     \n"
+                         + " *    _   _   __ _  _ __   | |_   __ _  _ __   | | __  ___     _  _      *     \n"
+                         + " *   | | | | / _` || '_    |  _| / _` || '_    | |/ / / _     | || |     *     \n"
+                         + " *   | |_| || (_| || | | | | |  | (_| || | | | |   < |  __/   | || |     *     \n"
+                         + " *     __, |  __,_||_| |_| |_|    __,_||_| |_| |_| _   ___|   | ||_|     *     \n"
+                         + " *     __/ |                                                 _/ |        *     \n"
+                         + " * * * * * * * * * * * * 延凡科技物联网平台[✔启动成功] * * * * * * * * * * * *      \n");
+
+        //连接后订阅, enable为false表示使用emq
+        if (!enabled) {
+            try {
+                TopicsPost allPost = topicsUtils.getAllPost();
+                client.subscribe(allPost.getTopics(), allPost.getQos());
+                log.info("mqtt监控主题,{}", Arrays.asList(allPost.getTopics()));
+            } catch (MqttException e) {
+                log.error("=>订阅主题失败 error={}", e.getMessage());
+            }
+        }
+    }
+}

+ 246 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/PubMqttClient.java

@@ -0,0 +1,246 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.config.MqttClientConfig;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.constant.YanfanConstant;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisCache;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 发布服务mqtt客户端
+ */
+@Component
+@Slf4j
+public class PubMqttClient {
+
+    @Resource
+    private MqttClientConfig mqttConfig;
+    @Resource(name = "pubMqttCallBack")
+    private PubMqttCallBack mqttCallBack;
+    /**
+     * 连接配置
+     */
+    private MqttConnectOptions options;
+    /**
+     * MQTT异步客户端
+     */
+    private MqttAsyncClient client;
+    /**
+     * 是否连接标记
+     */
+    private boolean isConnected = false;
+    @Resource
+    private RedisCache redisCache;
+    @Setter
+    private IMqttMessageListener listener;
+
+    /**
+     * 启动MQTT客户端
+     */
+    @Async(YanfanConstant.TASK.INNER_MQTT_TASK)
+    public synchronized void initialize() {
+
+        try {
+            setOptions();
+            createClient();
+            while (!client.isConnected()) {
+                IMqttToken token = client.connect(options);
+                if (token != null && token.isComplete()) {
+                    log.debug("=>内部MQTT客户端启动成功");
+                    this.isConnected = true;
+                    break;
+                }
+                log.debug("=>内部mqtt客户端连接中...");
+                Thread.sleep(20000);
+            }
+        } catch (MqttException ex) {
+            log.error("=>MQTT客户端初始化异常", ex);
+        } catch (Exception e) {
+            log.error("=>连接MQTT服务器异常", e);
+            this.isConnected = false;
+        }
+
+    }
+
+    public boolean isConnected() {
+        return this.isConnected;
+    }
+
+    private void createClient() {
+        try {
+            if (client == null) {
+                /*host为主机名,clientId是连接MQTT的客户端ID*/
+                client = new MqttAsyncClient(mqttConfig.getHostUrl(), getClientId(), new MemoryPersistence());
+                //设置回调函数
+                client.setCallback(mqttCallBack);
+                mqttCallBack.setClient(client);
+                mqttCallBack.setOptions(this.options);
+                mqttCallBack.setEnabled(mqttConfig.getEnabled());
+                mqttCallBack.setListener(this.listener);
+            }
+        } catch (Exception e) {
+            log.error("=>mqtt客户端创建错误");
+        }
+    }
+
+    /**
+     * 设置连接属性
+     */
+    private void setOptions() {
+
+        if (options != null) {
+            options = null;
+        }
+        options = new MqttConnectOptions();
+        options.setConnectionTimeout(mqttConfig.getTimeout());
+        options.setKeepAliveInterval(mqttConfig.getKeepalive());
+        options.setUserName(mqttConfig.getUsername());
+        options.setPassword(mqttConfig.getPassword().toCharArray());
+        options.setMaxInflight(mqttConfig.getMaxInflight());
+        //设置自动重新连接
+        options.setAutomaticReconnect(true);
+            /*设置为false,断开连接,不清除session,重连后还是原来的session
+              保留订阅的主题,能接收离线期间的消息*/
+        options.setCleanSession(true);
+    }
+
+    /**
+     * 断开与mqtt的连接
+     */
+    public synchronized void disconnect() {
+        //判断客户端是否null 是否连接
+        if (client != null && client.isConnected()) {
+            try {
+                IMqttToken token = client.disconnect();
+                token.waitForCompletion();
+            } catch (MqttException e) {
+                log.error("=>断开mqtt连接发生错误 message={}", e.getMessage());
+                throw new ServiceException(new ErrorCode(1,"断开mqtt连接发生错误" + e.getMessage()));
+            }
+        }
+        client = null;
+    }
+
+    /**
+     * 重新连接MQTT
+     */
+    public synchronized void refresh() {
+        disconnect();
+        initialize();
+    }
+
+    /**
+     * 拼接客户端id
+     */
+    public final String getClientId() {
+        return YanfanConstant.SERVER.WM_PREFIX + System.currentTimeMillis();
+    }
+
+
+    /**
+     * 发布主题
+     *
+     * @param message  payload消息体
+     * @param topic    主题
+     * @param retained 是否保留消息
+     * @param qos      消息质量
+     *                 Qos1:消息发送一次,不确保
+     *                 Qos2:至少分发一次,服务器确保接收消息进行确认
+     *                 Qos3:只分发一次,确保消息送达和只传递一次
+     */
+    public void publish(byte[] message, String topic, boolean retained, int qos) {
+        //设置mqtt消息
+        MqttMessage mqttMessage = new MqttMessage();
+        mqttMessage.setQos(qos);
+        mqttMessage.setRetained(retained);
+        mqttMessage.setPayload(message);
+
+        IMqttDeliveryToken token;
+        try {
+            token = client.publish(topic, mqttMessage);
+            token.waitForCompletion();
+        } catch (MqttPersistenceException e) {
+            log.error("=>发布主题时发生错误 topic={},message={}", topic, e.getMessage());
+            throw new ServiceException(new ErrorCode(1,e.getMessage()));
+        } catch (MqttException ex) {
+            throw new ServiceException(new ErrorCode(1,ex.getMessage()));
+        }
+    }
+
+
+    /**
+     * 发布
+     *
+     * @param qos         连接方式
+     * @param retained    是否保留
+     * @param topic       主题
+     * @param pushMessage 消息体
+     */
+    public void publish(int qos, boolean retained, String topic, String pushMessage) {
+        redisCache.incr2(YanfanConstant.REDIS.MESSAGE_SEND_TOTAL, -1L);
+        redisCache.incr2(YanfanConstant.REDIS.MESSAGE_SEND_TODAY, 60 * 60 * 24);
+        MqttMessage message = new MqttMessage();
+        message.setQos(qos);
+        message.setRetained(retained);
+        message.setPayload(pushMessage.getBytes());
+
+        try {
+            IMqttDeliveryToken token = client.publish(topic, message);
+            token.waitForCompletion();
+            log.info("发布主题[{}],发布消息{}", topic, pushMessage);
+        } catch (MqttPersistenceException e) {
+            e.printStackTrace();
+        } catch (MqttException e) {
+            log.error("=>发布主题时发生错误 topic={},message={}", topic, e.getMessage());
+        }
+    }
+
+    /**
+     * 订阅主题
+     *
+     * @param topic qos默认为1
+     */
+    public void subTopic(String topic) {
+        try {
+            client.subscribe(topic, 1);
+        } catch (MqttException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 订阅某个主题
+     *
+     * @param topic
+     * @param gos
+     */
+    public void subTopic(String topic, int gos) {
+        try {
+            client.subscribe(topic, gos);
+        } catch (MqttException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 清空主题
+     *
+     * @param topic
+     */
+    public void cleanTopic(String topic) {
+        try {
+            client.unsubscribe(topic);
+        } catch (MqttException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 16 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/Topics.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt;
+
+import lombok.Data;
+
+/**
+ * @author bill
+ */
+@Data
+public class Topics {
+
+
+    private String topicName;
+    private Integer qos = 0;
+    private String desc;
+
+}

+ 14 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/TopicsPost.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt;
+
+import lombok.Data;
+
+/**
+ * @author gsb
+ * @date 2023/2/27 13:41
+ */
+@Data
+public class TopicsPost {
+
+    private String[] topics;
+    private int[] qos;
+}

+ 114 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/IotYfProductController.java

@@ -0,0 +1,114 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.product;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo.ChangeProductStatusModel;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo.IotYfProductPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo.IotYfProductRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo.IotYfProductSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.product.IotYfProductDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.product.IotYfProductService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - 产品")
+@RestController
+@RequestMapping("/rq/iot-product")
+@Validated
+public class IotYfProductController {
+
+    @Resource
+    private IotYfProductService iotYfProductService;
+
+    @PostMapping
+    @Operation(summary = "创建产品")
+    @PreAuthorize("@ss.hasPermission('rq:iot-product:create')")
+    public CommonResult<Long> createIotProduct(@Valid @RequestBody IotYfProductSaveReqVO createReqVO) {
+        return success(iotYfProductService.createIotProduct(createReqVO));
+    }
+
+    @PutMapping
+    @Operation(summary = "更新产品")
+    @PreAuthorize("@ss.hasPermission('rq:iot-product:update')")
+    public CommonResult<Boolean> updateIotProduct(@Valid @RequestBody IotYfProductSaveReqVO updateReqVO) {
+        iotYfProductService.updateIotProduct(updateReqVO);
+        return success(true);
+    }
+
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除产品")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-product:delete')")
+    public CommonResult<Boolean> deleteIotProduct(@RequestParam("id") Long id) {
+        iotYfProductService.deleteIotProduct(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得产品")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-product:query')")
+    public CommonResult<IotYfProductRespVO> getIotProduct(@RequestParam("id") Long id) {
+        IotYfProductRespVO iotProduct = iotYfProductService.getIotProduct(id);
+        return success(iotProduct);
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得产品分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-product:query')")
+    public CommonResult<PageResult<IotYfProductRespVO>> getIotProductPage(@Valid IotYfProductPageReqVO pageReqVO) {
+        PageResult<IotYfProductDO> pageResult = iotYfProductService.getIotProductPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotYfProductRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出产品 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-product:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotProductExcel(@Valid IotYfProductPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotYfProductDO> list = iotYfProductService.getIotProductPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "产品.xls", "数据", IotYfProductRespVO.class,
+                        BeanUtils.toBean(list, IotYfProductRespVO.class));
+    }
+
+
+    @PutMapping("/status")
+    @Operation(summary = "更新产品状态")
+    public CommonResult<Boolean> changeProductStatus(@RequestBody ChangeProductStatusModel model) {
+        iotYfProductService.changeProductStatus(model);
+        return success(true);
+    }
+
+    @DeleteMapping("/{productIds}")
+    @Operation(summary = "批量删除产品")
+    public CommonResult<Boolean> remove(@PathVariable Long[] productIds) {
+        iotYfProductService.deleteProductByProductIds(productIds);
+        return success(true);
+    }
+}

+ 40 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/ChangeProductStatusModel.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo;
+
+/**
+ * id和name
+ *
+ * @author kerwincui
+ * @date 2021-12-16
+ */
+public class ChangeProductStatusModel {
+    private Long productId;
+
+    private Integer status;
+
+    private Integer deviceType;
+
+    public Integer getDeviceType() {
+        return deviceType;
+    }
+
+    public void setDeviceType(Integer deviceType) {
+        this.deviceType = deviceType;
+    }
+
+    public Long getProductId() {
+        return productId;
+    }
+
+    public void setProductId(Long productId) {
+        this.productId = productId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+}

+ 10 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/HttpParam.java

@@ -0,0 +1,10 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo;
+
+import lombok.Data;
+
+@Data
+public class HttpParam {
+
+    private String key;
+    private String value;
+}

+ 48 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/ImportThingsModelInput.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo;
+
+/**
+ * 导入产品物模型的输入对象
+ *
+ * @author kerwincui
+ * @date 2021-12-16
+ */
+public class ImportThingsModelInput {
+    /**
+     * 产品ID
+     */
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    private String ProductName;
+
+    /**
+     * 通用物模型ID集合
+     */
+    private Long[] templateIds;
+
+    public Long getProductId() {
+        return productId;
+    }
+
+    public void setProductId(Long productId) {
+        this.productId = productId;
+    }
+
+    public String getProductName() {
+        return ProductName;
+    }
+
+    public void setProductName(String productName) {
+        ProductName = productName;
+    }
+
+    public Long[] getTemplateIds() {
+        return templateIds;
+    }
+
+    public void setTemplateIds(Long[] templateIds) {
+        this.templateIds = templateIds;
+    }
+}

+ 150 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/IotYfProductPageReqVO.java

@@ -0,0 +1,150 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 产品分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotYfProductPageReqVO extends PageParam {
+
+    @Schema(description = "产品名称", example = "王五")
+    private String productName;
+
+    @Schema(description = "协议编号")
+    private String protocolCode;
+
+    @Schema(description = "产品分类ID", example = "4785")
+    private Long categoryId;
+
+    @Schema(description = "产品分类名称", example = "张三")
+    private String categoryName;
+
+    @Schema(description = "租户名称", example = "李四")
+    private String tenantName;
+
+    @Schema(description = "是否系统通用(0-否,1-是)")
+    private Boolean isSys;
+
+    @Schema(description = "是否启用授权码(0-否,1-是)")
+    private Boolean isAuthorize;
+
+    @Schema(description = "mqtt账号", example = "11203")
+    private String mqttAccount;
+
+    @Schema(description = "mqtt密码")
+    private String mqttPassword;
+
+    @Schema(description = "产品秘钥")
+    private String mqttSecret;
+
+    @Schema(description = "状态(1-未发布,2-已发布)", example = "2")
+    private Integer status;
+
+    @Schema(description = "物模型JSON(属性、功能、事件)")
+    private String thingsModelsJson;
+
+    @Schema(description = "设备类型(1-直连设备、2-网关设备、3-监控设备)", example = "2")
+    private Integer deviceType;
+
+    @Schema(description = "联网方式(1=wifi、2=蜂窝(2G/3G/4G/5G)、3=以太网、4=其他)")
+    private Integer networkMethod;
+
+    @Schema(description = "认证方式(1-简单认证、2-加密认证、3-简单+加密)")
+    private Integer vertificateMethod;
+
+    @Schema(description = "图片地址", example = "https://www.iocoder.cn")
+    private String imgUrl;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你说的对")
+    private String remark;
+
+    @Schema(description = "产品支持的传输协议")
+    private String transport;
+
+    @Schema(description = "定位方式(1=ip自动定位,2=设备定位,3=自定义)")
+    private Integer locationWay;
+
+    @Schema(description = "产品关联的组态id", example = "15371")
+    private String guid;
+
+    @Schema(description = "请求路径", example = "https://www.iocoder.cn")
+    private String url;
+
+    @Schema(description = "请求类型", example = "2")
+    private String methodType;
+
+    @Schema(description = "get请求参数")
+    private String paramsData;
+
+    @Schema(description = "请求头参数")
+    private String headers;
+
+    @Schema(description = "post、put请求参数")
+    private String data;
+
+    @Schema(description = "body请求参数类型", example = "1")
+    private String contentType;
+
+    @Schema(description = "请求间隔时间 cron表达式")
+    private String cronExpression;
+
+    @Schema(description = "参数类型", example = "1")
+    private String requestType;
+
+    @Schema(description = "body请求参数类型")
+    private Integer contentTypeNumber;
+
+    @Schema(description = "响应数据key")
+    private String responseKey;
+
+    @Schema(description = "远程设备地址")
+    private String ipAddress;
+
+    @Schema(description = "设备名称", example = "芋艿")
+    private String equipmentName;
+
+    @Schema(description = "SNMP v1/v2c 团体名")
+    private String community;
+
+    @Schema(description = "oid数组")
+    private String oidListStr;
+
+    @Schema(description = "HTTP用户名", example = "李四")
+    private String username;
+
+    @Schema(description = "HTTP密码")
+    private String userpassword;
+
+    @Schema(description = "1为启动,0为不启动,http接口是否需要先登录", example = "1")
+    private Integer httptype;
+
+    @Schema(description = "http密钥")
+    private String userkey;
+
+    @Schema(description = "http地址", example = "https://www.iocoder.cn")
+    private String httpurl;
+
+    @Schema(description = "部门id", example = "3672")
+    private Long deptId;
+
+}

+ 198 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/IotYfProductRespVO.java

@@ -0,0 +1,198 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - 产品 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotYfProductRespVO {
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26889")
+    @ExcelProperty("产品ID")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @ExcelProperty("产品名称")
+    private String productName;
+
+    @Schema(description = "协议编号")
+    @ExcelProperty("协议编号")
+    private String protocolCode;
+
+    @Schema(description = "产品分类ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4785")
+    @ExcelProperty("产品分类ID")
+    private Long categoryId;
+
+    @Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @ExcelProperty("产品分类名称")
+    private String categoryName;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @ExcelProperty("租户名称")
+    private String tenantName;
+
+    @Schema(description = "是否系统通用(0-否,1-是)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("是否系统通用(0-否,1-是)")
+    private Boolean isSys;
+
+    @Schema(description = "是否启用授权码(0-否,1-是)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("是否启用授权码(0-否,1-是)")
+    private Boolean isAuthorize;
+
+    @Schema(description = "mqtt账号", example = "11203")
+    @ExcelProperty("mqtt账号")
+    private String mqttAccount;
+
+    @Schema(description = "mqtt密码")
+    @ExcelProperty("mqtt密码")
+    private String mqttPassword;
+
+    @Schema(description = "产品秘钥")
+    @ExcelProperty("产品秘钥")
+    private String mqttSecret;
+
+    @Schema(description = "状态(1-未发布,2-已发布)", example = "2")
+    @ExcelProperty("状态(1-未发布,2-已发布)")
+    private Integer status;
+
+    @Schema(description = "物模型JSON(属性、功能、事件)")
+    @ExcelProperty("物模型JSON(属性、功能、事件)")
+    private String thingsModelsJson;
+
+    @Schema(description = "设备类型(1-直连设备、2-网关设备、3-监控设备)", example = "2")
+    @ExcelProperty("设备类型(1-直连设备、2-网关设备、3-监控设备)")
+    private Integer deviceType;
+
+    @Schema(description = "联网方式(1=wifi、2=蜂窝(2G/3G/4G/5G)、3=以太网、4=其他)")
+    @ExcelProperty("联网方式(1=wifi、2=蜂窝(2G/3G/4G/5G)、3=以太网、4=其他)")
+    private Integer networkMethod;
+
+    @Schema(description = "认证方式(1-简单认证、2-加密认证、3-简单+加密)")
+    @ExcelProperty("认证方式(1-简单认证、2-加密认证、3-简单+加密)")
+    private Integer vertificateMethod;
+
+    @Schema(description = "图片地址", example = "https://www.iocoder.cn")
+    @ExcelProperty("图片地址")
+    private String imgUrl;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    @ExcelProperty("删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    @ExcelProperty("创建者")
+    private String createBy;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你说的对")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "产品支持的传输协议")
+    @ExcelProperty("产品支持的传输协议")
+    private String transport;
+
+    @Schema(description = "定位方式(1=ip自动定位,2=设备定位,3=自定义)")
+    @ExcelProperty("定位方式(1=ip自动定位,2=设备定位,3=自定义)")
+    private Integer locationWay;
+
+    @Schema(description = "产品关联的组态id", example = "15371")
+    @ExcelProperty("产品关联的组态id")
+    private String guid;
+
+    @Schema(description = "请求路径", example = "https://www.iocoder.cn")
+    @ExcelProperty("请求路径")
+    private String url;
+
+    @Schema(description = "请求类型", example = "2")
+    @ExcelProperty("请求类型")
+    private String methodType;
+
+    @Schema(description = "get请求参数")
+    @ExcelProperty("get请求参数")
+    private String paramsData;
+
+    @Schema(description = "请求头参数")
+    @ExcelProperty("请求头参数")
+    private String headers;
+
+    @Schema(description = "post、put请求参数")
+    @ExcelProperty("post、put请求参数")
+    private String data;
+
+    @Schema(description = "body请求参数类型", example = "1")
+    @ExcelProperty("body请求参数类型")
+    private String contentType;
+
+    @Schema(description = "请求间隔时间 cron表达式")
+    @ExcelProperty("请求间隔时间 cron表达式")
+    private String cronExpression;
+
+    @Schema(description = "参数类型", example = "1")
+    @ExcelProperty("参数类型")
+    private String requestType;
+
+    @Schema(description = "body请求参数类型")
+    @ExcelProperty("body请求参数类型")
+    private Integer contentTypeNumber;
+
+    @Schema(description = "响应数据key")
+    @ExcelProperty("响应数据key")
+    private String responseKey;
+
+    @Schema(description = "远程设备地址")
+    @ExcelProperty("远程设备地址")
+    private String ipAddress;
+
+    @Schema(description = "设备名称", example = "芋艿")
+    @ExcelProperty("设备名称")
+    private String equipmentName;
+
+    @Schema(description = "SNMP v1/v2c 团体名")
+    @ExcelProperty("SNMP v1/v2c 团体名")
+    private String community;
+
+    @Schema(description = "oid数组")
+    @ExcelProperty("oid数组")
+    private String oidListStr;
+
+    @Schema(description = "HTTP用户名", example = "李四")
+    @ExcelProperty("HTTP用户名")
+    private String username;
+
+    @Schema(description = "HTTP密码")
+    @ExcelProperty("HTTP密码")
+    private String userpassword;
+
+    @Schema(description = "1为启动,0为不启动,http接口是否需要先登录", example = "1")
+    @ExcelProperty("1为启动,0为不启动,http接口是否需要先登录")
+    private Integer httptype;
+
+    @Schema(description = "http密钥")
+    @ExcelProperty("http密钥")
+    private String userkey;
+
+    @Schema(description = "http地址", example = "https://www.iocoder.cn")
+    @ExcelProperty("http地址")
+    private String httpurl;
+
+    @Schema(description = "部门id", example = "3672")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+    private Long templateId;
+    /**是否显示上级*/
+    private Boolean showSenior;
+    private userParmas userParmas;
+}

+ 153 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/IotYfProductSaveReqVO.java

@@ -0,0 +1,153 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - 产品新增/修改 Request VO")
+@Data
+public class IotYfProductSaveReqVO {
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26889")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @NotEmpty(message = "产品名称不能为空")
+    private String productName;
+
+    @Schema(description = "协议编号")
+    private String protocolCode;
+
+    @Schema(description = "产品分类ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4785")
+    @NotNull(message = "产品分类ID不能为空")
+    private Long categoryId;
+    private List<HttpParam> paramsDataList;
+    private userParmas userParmas;
+    @Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @NotEmpty(message = "产品分类名称不能为空")
+    private String categoryName;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+//    @NotEmpty(message = "租户名称不能为空")
+    private String tenantName;
+
+    @Schema(description = "是否系统通用(0-否,1-是)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "是否系统通用(0-否,1-是)不能为空")
+    private Boolean isSys;
+
+    @Schema(description = "是否启用授权码(0-否,1-是)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "是否启用授权码(0-否,1-是)不能为空")
+    private Boolean isAuthorize;
+
+    @Schema(description = "mqtt账号", example = "11203")
+    private String mqttAccount;
+
+    @Schema(description = "mqtt密码")
+    private String mqttPassword;
+
+    @Schema(description = "产品秘钥")
+    private String mqttSecret;
+
+    @Schema(description = "状态(1-未发布,2-已发布)", example = "2")
+    private Integer status;
+
+    @Schema(description = "物模型JSON(属性、功能、事件)")
+    private String thingsModelsJson;
+
+    @Schema(description = "设备类型(1-直连设备、2-网关设备、3-监控设备)", example = "2")
+    private Integer deviceType;
+
+    @Schema(description = "联网方式(1=wifi、2=蜂窝(2G/3G/4G/5G)、3=以太网、4=其他)")
+    private Integer networkMethod;
+
+    @Schema(description = "认证方式(1-简单认证、2-加密认证、3-简单+加密)")
+    private Integer vertificateMethod;
+
+    @Schema(description = "图片地址", example = "https://www.iocoder.cn")
+    private String imgUrl;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你说的对")
+    private String remark;
+
+    @Schema(description = "产品支持的传输协议")
+    private String transport;
+
+    @Schema(description = "定位方式(1=ip自动定位,2=设备定位,3=自定义)")
+    private Integer locationWay;
+
+    @Schema(description = "产品关联的组态id", example = "15371")
+    private String guid;
+
+    @Schema(description = "请求路径", example = "https://www.iocoder.cn")
+    private String url;
+
+    @Schema(description = "请求类型", example = "2")
+    private String methodType;
+
+    @Schema(description = "get请求参数")
+    private String paramsData;
+
+    @Schema(description = "请求头参数")
+    private String headers;
+
+    @Schema(description = "post、put请求参数")
+    private String data;
+
+    @Schema(description = "body请求参数类型", example = "1")
+    private String contentType;
+
+    @Schema(description = "请求间隔时间 cron表达式")
+    private String cronExpression;
+
+    @Schema(description = "参数类型", example = "1")
+    private String requestType;
+
+    @Schema(description = "body请求参数类型")
+    private Integer contentTypeNumber;
+
+    @Schema(description = "响应数据key")
+    private String responseKey;
+
+    @Schema(description = "远程设备地址")
+    private String ipAddress;
+
+    @Schema(description = "设备名称", example = "芋艿")
+    private String equipmentName;
+
+    @Schema(description = "SNMP v1/v2c 团体名")
+    private String community;
+
+    @Schema(description = "oid数组")
+    private String oidListStr;
+
+    @Schema(description = "HTTP用户名", example = "李四")
+    private String username;
+
+    @Schema(description = "HTTP密码")
+    private String userpassword;
+
+    @Schema(description = "1为启动,0为不启动,http接口是否需要先登录", example = "1")
+    private Integer httptype;
+
+    @Schema(description = "http密钥")
+    private String userkey;
+
+    @Schema(description = "http地址", example = "https://www.iocoder.cn")
+    private String httpurl;
+
+    @Schema(description = "部门id", example = "3672")
+    private Long deptId;
+    private List<HttpParam> headersList;
+}

+ 14 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/product/vo/userParmas.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.product.vo;
+
+import lombok.Data;
+
+@Data
+public class userParmas {
+     String user;
+
+     String password;
+
+     Boolean isLogin;
+
+     String url;
+}

+ 98 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/IotYfProtocolController.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol.vo.IotYfProtocolPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol.vo.IotYfProtocolRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol.vo.IotYfProtocolSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.protocol.IotYfProtocolDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.protocol.IotYfProtocolService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - yf协议")
+@RestController
+@RequestMapping("/rq/iot-protocol")
+@Validated
+public class IotYfProtocolController {
+
+    @Resource
+    private IotYfProtocolService iotYfProtocolService;
+
+    @PostMapping
+    @Operation(summary = "创建yf协议")
+    @PreAuthorize("@ss.hasPermission('rq:iot-protocol:create')")
+    public CommonResult<Long> createIotProtocol(@Valid @RequestBody IotYfProtocolSaveReqVO createReqVO) {
+        return success(iotYfProtocolService.createIotProtocol(createReqVO));
+    }
+
+    @PutMapping
+    @Operation(summary = "更新yf协议")
+    @PreAuthorize("@ss.hasPermission('rq:iot-protocol:update')")
+    public CommonResult<Boolean> updateIotProtocol(@Valid @RequestBody IotYfProtocolSaveReqVO updateReqVO) {
+        iotYfProtocolService.updateIotProtocol(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除yf协议")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-protocol:delete')")
+    public CommonResult<Boolean> deleteIotProtocol(@RequestParam("id") Long id) {
+        iotYfProtocolService.deleteIotProtocol(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得yf协议")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-protocol:query')")
+    public CommonResult<IotYfProtocolRespVO> getIotProtocol(@RequestParam("id") Long id) {
+        IotYfProtocolDO iotProtocol = iotYfProtocolService.getIotProtocol(id);
+        return success(BeanUtils.toBean(iotProtocol, IotYfProtocolRespVO.class));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得yf协议分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-protocol:query')")
+    public CommonResult<PageResult<IotYfProtocolRespVO>> getIotProtocolPage(@Valid IotYfProtocolPageReqVO pageReqVO) {
+        PageResult<IotYfProtocolDO> pageResult = iotYfProtocolService.getIotProtocolPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotYfProtocolRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出yf协议 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-protocol:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotProtocolExcel(@Valid IotYfProtocolPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotYfProtocolDO> list = iotYfProtocolService.getIotProtocolPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "yf协议.xls", "数据", IotYfProtocolRespVO.class,
+                        BeanUtils.toBean(list, IotYfProtocolRespVO.class));
+    }
+
+}

+ 89 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/modbus/ModbusCode.java

@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol.modbus;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Modbus功能码
+ *
+ * @author bill
+ * <p>
+ * {bit 位操作}
+ * 线圈寄存器:   bit对应一个信号的开关状态。功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01  0x05  0x0f
+ * 离散输入寄存器:离散输入寄存器就 是 只读线圈寄存器,每个bit表示一个开关量,是不能够写的。 功能码: 0x02
+ * <p>
+ * {byte 字节操作}
+ * 保持寄存器:  两个byte,可读写的 写也分为单个写和多个写对应的三个:0x03 0x06 0x10
+ * 输入寄存器:  和保持寄存器类似,只支持读而不能写,一般是读取各种实时数据。一个寄存器也是占据两个byte的空间。对应的功能码: 0x04
+ */
+@Getter
+@AllArgsConstructor
+public enum ModbusCode {
+
+    Read01("读线圈", (byte) 0x01, "1"), // 读线圈(读写位模式)
+    Read02("读离散量输入", (byte) 0x02, "2"), // 读离散量输入(位只读模式)
+    Read03("读保持寄存器", (byte) 0x03, "3"), // 读保持寄存器(字节读写模式)
+    Read04("读输入寄存器", (byte) 0x04, "4"), // 读输入寄存器(字节只读模式)
+
+    Write05("写单个线圈(读写位模式)", (byte) 0x05, "5"), // 写单个线圈(读写位模式)
+    Write06("写多个线圈", (byte) 0x06, "6"), // 写单个保持寄存器
+    Write0F("写多个线圈", (byte) 0x0F, "15"), // 写多个线圈
+    Write10("写多个保持寄存器", (byte) 0x10, "16"), // 写多个保持寄存器
+    UnKnow("未知功能码", (byte) 0x0000, "100");
+
+    private String desc;
+    private byte code;
+    private String hex;
+
+    public static ModbusCode getInstance(int code) {
+        switch ((byte) code) {
+            case 0x01:
+                return Read01;
+            case 0x02:
+                return Read02;
+            case 0x03:
+                return Read03;
+            case 0x04:
+                return Read04;
+
+            case 0x05:
+                return Write05;
+            case 0x06:
+                return Write06;
+            case 0x0F:
+                return Write0F;
+            case 0x10:
+                return Write10;
+
+            default:
+                throw new ServiceException(new ErrorCode(1,"功能码[" + code + "],未定义"));
+        }
+    }
+
+    public static String getDes(int code) {
+        switch ((byte) code) {
+            case 0x01:
+                return Read01.desc;
+            case 0x02:
+                return Read02.desc;
+            case 0x03:
+                return Read03.desc;
+            case 0x04:
+                return Read04.desc;
+            case 0x05:
+                return Write05.desc;
+            case 0x06:
+                return Write06.desc;
+            case 0x0F:
+                return Write0F.desc;
+            case 0x10:
+                return Write10.desc;
+
+            default:
+                return "UNKOWN";
+        }
+    }
+
+}

+ 45 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/vo/IotYfProtocolPageReqVO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol.vo;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - yf协议分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotYfProtocolPageReqVO extends PageParam {
+
+    @Schema(description = "协议编码")
+    private String protocolCode;
+
+    @Schema(description = "协议名称", example = "张三")
+    private String protocolName;
+
+    @Schema(description = "协议jar包,js包,c程序上传地址", example = "https://www.iocoder.cn")
+    private String protocolFileUrl;
+
+    @Schema(description = "协议类型 0:未知 1:jar,2.js,3.c", example = "2")
+    private Integer protocolType;
+
+    @Schema(description = "协议文件摘要(文件的md5)")
+    private String jarSign;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "0:草稿 1:启用 2:停用", example = "2")
+    private Integer protocolStatus;
+
+    @Schema(description = "0:正常 1:删除")
+    private Integer delFlag;
+
+    @Schema(description = "部门id", example = "8215")
+    private Long deptId;
+
+}

+ 54 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/vo/IotYfProtocolRespVO.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - yf协议 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotYfProtocolRespVO {
+
+    @Schema(description = "自增id", requiredMode = Schema.RequiredMode.REQUIRED, example = "8132")
+    @ExcelProperty("自增id")
+    private Long id;
+
+    @Schema(description = "协议编码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("协议编码")
+    private String protocolCode;
+
+    @Schema(description = "协议名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @ExcelProperty("协议名称")
+    private String protocolName;
+
+    @Schema(description = "协议jar包,js包,c程序上传地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    @ExcelProperty("协议jar包,js包,c程序上传地址")
+    private String protocolFileUrl;
+
+    @Schema(description = "协议类型 0:未知 1:jar,2.js,3.c", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @ExcelProperty("协议类型 0:未知 1:jar,2.js,3.c")
+    private Integer protocolType;
+
+    @Schema(description = "协议文件摘要(文件的md5)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("协议文件摘要(文件的md5)")
+    private String jarSign;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "0:草稿 1:启用 2:停用", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @ExcelProperty("0:草稿 1:启用 2:停用")
+    private Integer protocolStatus;
+
+    @Schema(description = "0:正常 1:删除", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("0:正常 1:删除")
+    private Integer delFlag;
+
+    @Schema(description = "部门id", example = "8215")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+}

+ 47 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/protocol/vo/IotYfProtocolSaveReqVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.protocol.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - yf协议新增/修改 Request VO")
+@Data
+public class IotYfProtocolSaveReqVO {
+
+    @Schema(description = "自增id", requiredMode = Schema.RequiredMode.REQUIRED, example = "8132")
+    private Long id;
+
+    @Schema(description = "协议编码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "协议编码不能为空")
+    private String protocolCode;
+
+    @Schema(description = "协议名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @NotEmpty(message = "协议名称不能为空")
+    private String protocolName;
+
+    @Schema(description = "协议jar包,js包,c程序上传地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    @NotEmpty(message = "协议jar包,js包,c程序上传地址不能为空")
+    private String protocolFileUrl;
+
+    @Schema(description = "协议类型 0:未知 1:jar,2.js,3.c", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "协议类型 0:未知 1:jar,2.js,3.c不能为空")
+    private Integer protocolType;
+
+    @Schema(description = "协议文件摘要(文件的md5)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "协议文件摘要(文件的md5)不能为空")
+    private String jarSign;
+
+    @Schema(description = "0:草稿 1:启用 2:停用", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "0:草稿 1:启用 2:停用不能为空")
+    private Integer protocolStatus;
+
+    @Schema(description = "0:正常 1:删除", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "0:正常 1:删除不能为空")
+    private Integer delFlag;
+
+    @Schema(description = "部门id", example = "8215")
+    private Long deptId;
+
+}

+ 852 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/redis/RedisCache.java

@@ -0,0 +1,852 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.support.atomic.RedisAtomicLong;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import redis.clients.jedis.util.SafeEncoder;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.regex.Pattern.compile;
+
+/**
+ * spring redis 工具类
+ *
+ * @author ruoyi
+ **/
+@SuppressWarnings(value = {"unchecked", "rawtypes"})
+@Component
+@Slf4j
+public class RedisCache {
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key   缓存的键值
+     * @param value 缓存的值
+     */
+    public <T> void setCacheObject(final String key, final T value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key      缓存的键值
+     * @param value    缓存的值
+     * @param timeout  时间
+     * @param timeUnit 时间颗粒度
+     */
+    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key     Redis键
+     * @param timeout 超时时间
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout) {
+        return expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key     Redis键
+     * @param timeout 超时时间
+     * @param unit    时间单位
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 获取有效时间
+     *
+     * @param key Redis键
+     * @return 有效时间
+     */
+    public long getExpire(final String key) {
+        return redisTemplate.getExpire(key);
+    }
+
+    /**
+     * 判断 key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public Boolean hasKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObject(final String key) {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        return operation.get(key);
+    }
+
+    /**
+     * 删除单个对象
+     *
+     * @param key
+     */
+    public boolean deleteObject(final String key) {
+        return redisTemplate.delete(key);
+    }
+
+    /**
+     * 删除集合对象
+     *
+     * @param collection 多个对象
+     * @return
+     */
+    public boolean deleteObject(final Collection collection) {
+        return redisTemplate.delete(collection) > 0;
+    }
+
+    /**
+     * 缓存List数据
+     *
+     * @param key      缓存的键值
+     * @param dataList 待缓存的List数据
+     * @return 缓存的对象
+     */
+    public <T> long setCacheList(final String key, final List<T> dataList) {
+        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
+        return count == null ? 0 : count;
+    }
+
+    /**
+     * 获得缓存的list对象
+     *
+     * @param key 缓存的键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> List<T> getCacheList(final String key) {
+        return redisTemplate.opsForList().range(key, 0, -1);
+    }
+
+    /**
+     * 缓存Set
+     *
+     * @param key     缓存键值
+     * @param dataSet 缓存的数据
+     * @return 缓存数据的对象
+     */
+    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
+        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
+        Iterator<T> it = dataSet.iterator();
+        while (it.hasNext()) {
+            setOperation.add(it.next());
+        }
+        return setOperation;
+    }
+
+    /**
+     * 获得缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> Set<T> getCacheSet(final String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 缓存Map
+     *
+     * @param key
+     * @param dataMap
+     */
+    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
+        if (dataMap != null) {
+            redisTemplate.opsForHash().putAll(key, dataMap);
+        }
+    }
+
+    /**
+     * 获得缓存的Map
+     *
+     * @param key
+     * @return
+     */
+    public <T> Map<String, T> getCacheMap(final String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key   Redis键
+     * @param hKey  Hash键
+     * @param value 值
+     */
+    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+    /**
+     * 获取Hash中的数据
+     *
+     * @param key  Redis键
+     * @param hKey Hash键
+     * @return Hash中的对象
+     */
+    public <T> T getCacheMapValue(final String key, final String hKey) {
+        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
+        return opsForHash.get(key, hKey);
+    }
+
+    /**
+     * 获取多个Hash中的数据
+     *
+     * @param key   Redis键
+     * @param hKeys Hash键集合
+     * @return Hash对象集合
+     */
+    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
+        return redisTemplate.opsForHash().multiGet(key, hKeys);
+    }
+
+    /**
+     * 删除Hash中的某条数据
+     *
+     * @param key  Redis键
+     * @param hKey Hash键
+     * @return 是否成功
+     */
+    public boolean deleteCacheMapValue(final String key, final String hKey) {
+        return redisTemplate.opsForHash().delete(key, hKey) > 0;
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern) {
+        return redisTemplate.keys(pattern);
+    }
+
+    /**
+     * 是否存在key
+     *
+     * @param key 缓存key
+     * @return true:存在key ;false:key不存在或者已过期
+     */
+    public boolean containsKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+
+    /**
+     * 递增
+     *
+     * @param key   键
+     * @param delta 要增加几(大于0)
+     * @return
+     */
+    public long incr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递增因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * redis 计数器自增
+     *
+     * @param key      key
+     * @param liveTime 过期时间,null不设置过期时间
+     * @return 自增数
+     */
+    public Long incr2(String key, long liveTime) {
+        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
+        Long increment = entityIdCounter.getAndIncrement();
+
+        if (increment == 0 && liveTime > 0) {//初始设置过期时间
+            entityIdCounter.expire(liveTime, TimeUnit.HOURS);
+        }
+
+        return increment;
+    }
+
+    /**
+     * 将数据放入set缓存
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sAdd(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 将set数据放入缓存
+     *
+     * @param key    键
+     * @param time   时间(秒)
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSetAndTime(String key, long time, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().add(key, values);
+            if (time > 0) expire(key, time);
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 移除set集合值为value的
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 移除的个数
+     */
+    public long setRemove(String key, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().remove(key, values);
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能;  zadd
+     *
+     * @param key   键
+     * @param value 值
+     * @param score 分数
+     */
+    public boolean zSetAdd(String key, String value, double score) {
+        try {
+            Boolean aBoolean = stringRedisTemplate.opsForZSet().add(key, value, score);
+            return BooleanUtils.isTrue(aBoolean);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 移除一个zset有序集合的key的一个或者多个值
+     * zrem key member [member ...] :移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。当 key 存在但不是有序集类型时,返回一个错误。
+     *
+     * @param key    集合的键key
+     * @param values 需要移除的value
+     * @return
+     */
+    public boolean zRem(String key, Object... values) {
+        try {
+            Long aLong = stringRedisTemplate.opsForZSet().remove(key, values);
+            return aLong != null ? true : false;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 移除有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。
+     *
+     * @param key   String
+     * @param start double 最小score
+     * @param end   double 最大score
+     */
+    public Long zRemBySocre(String key, double start, double end) {
+        try {
+            return stringRedisTemplate.opsForZSet().removeRangeByScore(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 判断value在zset中的排名  zrank命令
+     *
+     * @param key   键
+     * @param value 值
+     * @return score 越小排名越高;
+     */
+    public Long zRank(String key, String value) {
+        try {
+            return stringRedisTemplate.opsForZSet().rank(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 查询zSet集合中指定顺序的值, 0 -1 表示获取全部的集合内容  zrange
+     *
+     * @param key   键
+     * @param start 开始
+     * @param end   结束
+     * @return 返回有序的集合,score小的在前面
+     */
+    public Set<String> zRange(String key, int start, int end) {
+        try {
+            return stringRedisTemplate.opsForZSet().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。
+     * 有序集成员按 score 值递增(从小到大)次序排列。
+     *
+     * @param key   String
+     * @param start double 最小score
+     * @param end   double 最大score
+     */
+    public Set<String> zRangeByScore(String key, double start, double end) {
+        try {
+            return stringRedisTemplate.opsForZSet().rangeByScore(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取集合的元素, 从小到大排序
+     *
+     * @param key   键
+     * @param start 开始位置
+     * @param end   结束位置, -1查询所有
+     * @return
+     */
+    public Set<String> zRange(String key, long start, long end) {
+        try {
+            return stringRedisTemplate.opsForZSet().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+    /**
+     * 返回set集合的长度
+     *
+     * @param key
+     * @return
+     */
+    public Long zSize(String key) {
+        try {
+            return stringRedisTemplate.opsForZSet().zCard(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 根据前缀获取所有的key
+     * 例如:pro_*
+     */
+    public Set<String> getListKeyByPrefix(String prefix) {
+        Set<String> keys = redisTemplate.keys(prefix.concat("*"));
+        return keys;
+    }
+
+    /**
+     * 匹配获取键值对,ScanOptions.NONE为获取全部键对
+     *
+     * @param key
+     * @param options
+     * @return
+     */
+    public Cursor<Map.Entry<Object, Object>> hashScan(String key, ScanOptions options) {
+        return redisTemplate.opsForHash().scan(key, options);
+    }
+
+    /**
+     * 获取所有键值对集合
+     *
+     * @param key
+     */
+    public Map hashEntity(String key) {
+        return redisTemplate.boundHashOps(key).entries();
+    }
+
+    /**
+     * 以map集合的形式添加键值对
+     *
+     * @param key
+     * @param maps
+     */
+    public void hashPutAll(String key, Map<String, String> maps) {
+        redisTemplate.opsForHash().putAll(key, maps);
+    }
+
+    /**
+     * 以map集合的形式添加键值对
+     *
+     * @param key
+     * @param maps
+     */
+    public void hashPutAllObj(String key, Map<String, Object> maps) {
+        redisTemplate.opsForHash().putAll(key, maps);
+    }
+
+    /**
+     * 批量获取设备物模型值
+     *
+     * @param keys          键的集合
+     * @param hkeyCondition 筛选字段
+     * @return
+     */
+    public Map<String, Map> hashGetAllByKeys(Set<String> keys, String hkeyCondition) {
+        return (Map<String, Map>) redisTemplate.execute((RedisCallback) con -> {
+            Iterator<String> it = keys.iterator();
+            Map<String, Map> mapList = new HashMap<>();
+            while (it.hasNext()) {
+                String key = it.next();
+                Map<byte[], byte[]> result = con.hGetAll(key.getBytes());
+                Map ans;
+                if (CollectionUtils.isEmpty(result)) {
+                    return new HashMap<>(0);
+                }
+                ans = new HashMap<>(result.size());
+                for (Map.Entry entry : result.entrySet()) {
+                    String field = new String((byte[]) entry.getKey());
+                    if (!"".equals(hkeyCondition)) {
+                        if (field.endsWith(hkeyCondition)) {
+                            ans.put(new String((byte[]) entry.getKey()), new String((byte[]) entry.getValue()));
+                        }
+                    } else {
+                        ans.put(new String((byte[]) entry.getKey()), new String((byte[]) entry.getValue()));
+                    }
+                }
+                mapList.put(key, ans);
+            }
+            return mapList;
+        });
+    }
+
+    /**
+     * 批量获取匹配触发器的物模型值(定时告警使用)
+     *
+     * @param keys         键的集合
+     * @param operator     操作符
+     * @param triggerValue 触发的值
+     * @return
+     */
+    public Map<String, String> hashGetAllMatchByKeys(Set<String> keys, String operator, String id, String triggerValue, String modelIndex) {
+        // 数组或数组对象拼接id和获取值索引
+        String matchId;
+        int indexValue;
+        if (id.startsWith("array_")) {
+            int index = id.indexOf("_", id.indexOf("_") + 1);
+            matchId = id.substring(index + 1);
+            List<String> list = str2List(id, "_", true, true);
+            indexValue = Integer.parseInt(list.get(1));
+        } else {
+            indexValue = -1;
+            matchId = id;
+        }
+        return (Map<String, String>) redisTemplate.execute((RedisCallback) con -> {
+            Iterator<String> it = keys.iterator();
+            Map<String, String> mapList = new HashMap<>();
+            while (it.hasNext()) {
+                String key = it.next();
+                Map<byte[], byte[]> result = con.hGetAll(key.getBytes());
+                if (CollectionUtils.isEmpty(result)) {
+                    return new HashMap<>(0);
+                }
+                for (Map.Entry entry : result.entrySet()) {
+                    String field = new String((byte[]) entry.getKey());
+                    // 获取物模型值并且匹配规则,获取值的类型和匹配规则后续还要仔细测了然后优化
+                    if (field.equals(matchId) || field.equals(matchId + "#V")) {
+                        String valueStr = new String((byte[]) entry.getValue());
+                        JSONObject jsonObject = JSONObject.parseObject((String) JSON.parse(valueStr));
+                        String value = (String) jsonObject.get("value");
+                        // 数组或数组对象元素索引
+                        if (indexValue >= 0) {
+                            List<String> list = str2List(value, ",", true, true);
+                            value = org.apache.commons.collections4.CollectionUtils.isEmpty(list) ? "" : list.get(indexValue);
+                        }
+                        if (ruleResult(operator, value, triggerValue)) {
+                            mapList.put(key, value);
+                        }
+                    }
+                }
+            }
+            return mapList;
+        });
+    }
+
+    /**
+     * 批量获取匹配触发器的物模型值
+     *
+     * @param productId    productId
+     * @param operator     操作符
+     * @param triggerValue 触发的值
+     * @return
+     */
+    public Map<String, String> CheckMatchByProductId(Long productId, String operator, String id, String triggerValue) {
+        Set<String> keys = getListKeyByPrefix("TSLV:" + productId);
+        return (Map<String, String>) redisTemplate.execute((RedisCallback) con -> {
+            Iterator<String> it = keys.iterator();
+            Map<String, String> mapList = new HashMap<>();
+            while (it.hasNext()) {
+                String key = it.next();
+                String value = CheckMatchByCacheKey(key, operator, id, triggerValue);
+                if (!Objects.equals(value, "")) {
+                    mapList.put(key, value);
+                }
+            }
+            return mapList;
+        });
+    }
+
+    /**
+     * 获取匹配触发器的物模型值
+     *
+     * @param cacheKey     设备key
+     * @param operator     操作符
+     * @param triggerValue 触发的值
+     * @return
+     */
+    public String CheckMatchByCacheKey(String cacheKey, String operator, String id, String triggerValue) {
+        String cacheValue = getCacheMapValue(cacheKey, id);
+        Map<byte[], byte[]> result = JSON.parseObject(cacheValue, Map.class);
+        if (CollectionUtils.isEmpty(result)) {
+            return "";
+        }
+        for (Map.Entry entry : result.entrySet()) {
+            String field = (String) entry.getKey();
+            if (field.equals("value")) {
+                String value = (String) entry.getValue();
+                value = value.replace("\"", "");
+                if (ruleResult(operator, value, triggerValue)) {
+                    return value;
+                }
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 根据key集合获取字符串
+     *
+     * @param keys 键的集合
+     * @return
+     */
+    public Map<String, String> getStringAllByKeys(Set<String> keys) {
+        return (Map<String, String>) redisTemplate.execute((RedisCallback) con -> {
+            Iterator<String> it = keys.iterator();
+            Map<String, String> mapList = new HashMap<>();
+            while (it.hasNext()) {
+                String key = it.next();
+                byte[] result = con.get(key.getBytes());
+                if (result == null) {
+                    return new HashMap<>(0);
+                }
+                String ans = new String(result);
+                mapList.put(key, ans);
+            }
+            return mapList;
+        });
+    }
+
+    /**
+     * 根据条件返回所有键
+     *
+     * @param query
+     * @return
+     */
+    public List<Object> scan(String query) {
+        // 修复关键:使用 ScanOptions.scanOptions() 静态方法创建构建器
+        Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
+            Set<String> keysTmp = new HashSet<>();
+            // 正确的 ScanOptions 创建方式:使用 ScanOptions.scanOptions() 工厂方法
+            ScanOptions scanOptions = ScanOptions.scanOptions()
+                    .match("*" + query + "*") // 匹配包含 query 的所有 key
+                    .count(1000) // 每次扫描的数量(非结果数量限制,仅提示Redis分批返回)
+                    .build();
+
+            // 获取游标并遍历
+            Cursor<byte[]> cursor = connection.scan(scanOptions);
+            while (cursor.hasNext()) {
+                // 将 byte[] 转换为 String(处理Redis key的编码问题)
+                keysTmp.add(SafeEncoder.encode(cursor.next()));
+            }
+
+            // 关闭游标释放资源(重要!避免连接泄漏)
+            if (cursor != null) {
+                cursor.close();
+            }
+
+            return keysTmp;
+        });
+
+        // 根据key集合查询对应的value值并返回
+        return new ArrayList<>(redisTemplate.opsForValue().multiGet(keys));
+    }
+
+    /**
+     * 规则匹配结果
+     *
+     * @param operator     操作符
+     * @param value        上报的值
+     * @param triggerValue 触发器的值
+     * @return
+     */
+    private boolean ruleResult(String operator, String value, String triggerValue) {
+        boolean result = false;
+        if ("".equals(value)) {
+            return result;
+        }
+        // 操作符比较
+        switch (operator) {
+            case "=":
+                result = value.equals(triggerValue);
+                break;
+            case "!=":
+                result = !value.equals(triggerValue);
+                break;
+            case ">":
+                if (isNumeric(value) && isNumeric(triggerValue)) {
+                    result = Double.parseDouble(value) > Double.parseDouble(triggerValue);
+                }
+                break;
+            case "<":
+                if (isNumeric(value) && isNumeric(triggerValue)) {
+                    result = Double.parseDouble(value) < Double.parseDouble(triggerValue);
+                }
+                break;
+            case ">=":
+                if (isNumeric(value) && isNumeric(triggerValue)) {
+                    result = Double.parseDouble(value) >= Double.parseDouble(triggerValue);
+                }
+                break;
+            case "<=":
+                if (isNumeric(value) && isNumeric(triggerValue)) {
+                    result = Double.parseDouble(value) <= Double.parseDouble(triggerValue);
+                }
+                break;
+            case "contain":
+                result = value.contains(triggerValue);
+                break;
+            case "notcontain":
+                result = !value.contains(triggerValue);
+                break;
+            default:
+                break;
+        }
+        return result;
+    }
+
+    /**
+     * 判断字符串是否为整数或小数
+     */
+    private boolean isNumeric(String str) {
+        Pattern pattern = compile("[0-9]*\\.?[0-9]+");
+        Matcher isNum = pattern.matcher(str);
+        if (!isNum.matches()) {
+            return false;
+        }
+        return true;
+    }
+
+    public void publish(Object message, String channel) {
+        try {
+            redisTemplate.convertAndSend(channel, message);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key   Redis键
+     * @param hKey  Hash键
+     * @param value 值
+     */
+    public <T> void setHashValue(final String key, final String hKey, final T value) {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+
+    /**
+     * 删除Hash中的数据
+     *
+     * @param key
+     * @param hkey
+     */
+    public void delHashValue(final String key, final String hkey) {
+        HashOperations hashOperations = redisTemplate.opsForHash();
+        hashOperations.delete(key, hkey);
+    }
+
+
+    public List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) {
+        List<String> list = new ArrayList<String>();
+        if (StringUtils.isEmpty(str)) {
+            return list;
+        }
+
+        // 过滤空白字符串
+        if (filterBlank && StringUtils.isBlank(str)) {
+            return list;
+        }
+        String[] split = str.split(sep);
+        for (String string : split) {
+            if (filterBlank && StringUtils.isBlank(string)) {
+                continue;
+            }
+            if (trim) {
+                string = string.trim();
+            }
+            list.add(string);
+        }
+
+        return list;
+    }
+}

+ 161 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/redis/RedisKeyBuilder.java

@@ -0,0 +1,161 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis;
+
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.constant.YanfanConstant;
+
+/**
+ * 缓存key生成器
+ *
+ * @author bill
+ */
+public class RedisKeyBuilder {
+
+    /**
+     * 设备在线列表缓存key
+     */
+    public static String buildDeviceOnlineListKey() {
+        return YanfanConstant.REDIS.DEVICE_ONLINE_LIST;
+    }
+
+    /**
+     * 设备实时数据key
+     */
+    public static String buildDeviceRtCacheKey(String serialNumber) {
+        return YanfanConstant.REDIS.DEVICE_RUNTIME_DATA + serialNumber;
+    }
+
+    /**
+     * 设备通讯协议参数
+     */
+    public static String buildDeviceRtParamsKey(String serialNumber) {
+        return YanfanConstant.REDIS.DEVICE_PROTOCOL_PARAM + serialNumber;
+    }
+
+    /**
+     * 固件版本缓存key
+     */
+    public static String buildFirmwareCachedKey(Long firmwareId) {
+        return YanfanConstant.REDIS.FIRMWARE_VERSION + firmwareId;
+    }
+
+    /**
+     * 属性读取回调缓存key
+     */
+    public static String buildPropReadCacheKey(String serialNumber) {
+        return YanfanConstant.REDIS.PROP_READ_STORE + serialNumber;
+    }
+
+    /**
+     * 物模型值命名缓存key
+     * Key:TSLV:{productId}_{deviceNumber}   HKey:{identity#V/identity#S/identity#M/identity#N}
+     */
+    public static String buildTSLVCacheKey(Long productId, String serialNumber) {
+        return YanfanConstant.REDIS.DEVICE_PRE_KEY + productId + "_" + serialNumber.toUpperCase();
+    }
+
+    /**
+     * 物模型缓存key
+     * 物模型命名空间:Key:TSL:{productId}  hkey: identity  value: thingsModel
+     */
+    public static String buildTSLCacheKey(Long productId) {
+        return YanfanConstant.REDIS.TSL_PRE_KEY + productId;
+    }
+
+    public static String buildModbusKey(Long productId) {
+        return YanfanConstant.REDIS.MODBUS_PRE_KEY + productId;
+    }
+
+    /**
+     * 录像缓存key
+     */
+    public static String buildSipRecordinfoCacheKey(String recordKey) {
+        return YanfanConstant.REDIS.RECORDINFO_KEY + recordKey;
+    }
+
+    /**
+     * 设备id缓存key
+     */
+    public static String buildSipDeviceidCacheKey(String id) {
+        return YanfanConstant.REDIS.DEVICEID_KEY + id;
+    }
+
+    /**
+     * ipCSEQ缓存key
+     */
+    public static String buildStreamCacheKey(String steamId) {
+        return YanfanConstant.REDIS.STREAM_KEY + steamId;
+    }
+
+    public static String buildStreamCacheKey(String deviceId, String channelId, String stream, String ssrc) {
+        return YanfanConstant.REDIS.STREAM_KEY + deviceId + ":" + channelId + ":" + stream + ":" + ssrc;
+    }
+
+    public static String buildInviteCacheKey(String type, String deviceId, String channelId, String stream, String ssrc) {
+        return YanfanConstant.REDIS.INVITE_KEY + type + ":" + deviceId + ":" + channelId + ":" + stream + ":" + ssrc;
+    }
+
+    /**
+     * ipCSEQ缓存key
+     */
+    public static String buildSipCSEQCacheKey(String CSEQ) {
+        return YanfanConstant.REDIS.SIP_CSEQ_PREFIX + CSEQ;
+    }
+
+    /**
+     * rule静默时间缓存key
+     */
+    public static String buildSilentTimeacheKey(String key) {
+        return YanfanConstant.REDIS.RULE_SILENT_TIME + key;
+    }
+
+    /**
+     * modbus指令缓存key
+     */
+    public static String buildModbusPollCacheKey(String serialNumebr) {
+        return YanfanConstant.REDIS.POLL_MODBUS_KEY + serialNumebr;
+    }
+
+    /**
+     * modbus指令缓存可以
+     */
+    public static String buildModbusCacheKey(Long productId) {
+        return YanfanConstant.REDIS.POLL_MODBUS_KEY + productId;
+    }
+
+    /*缓存设备下发指令消息ID*/
+    public static String buildDownMessageIdCacheKey(String serialNumber) {
+        return YanfanConstant.REDIS.DEVICE_MESSAGE_ID + serialNumber;
+    }
+
+    /**
+     * 缓存设备下发指令消息ID
+     *
+     * @value userid
+     */
+    public static String buildDownMessageUserIdCacheKey(String messageId) {
+        return YanfanConstant.REDIS.DEVICE_MESSAGE_USER_ID + messageId;
+    }
+
+    /**
+     * 缓存产品id,设备编号,协议编号
+     */
+    public static String buildDeviceMsgCacheKey(String serialNumber) {
+        return YanfanConstant.REDIS.DEVICE_MSG + serialNumber;
+    }
+
+    /**
+     * 缓存产品id,设备编号,协议编号
+     */
+    public static String buildSceneModelTagCacheKey(Long sceneModelId) {
+        return YanfanConstant.REDIS.SCENE_MODEL_TAG_ID + sceneModelId;
+    }
+
+    public static String buildModbusRuntimeCacheKey(String serialNumber) {
+        return YanfanConstant.REDIS.MODBUS_RUNTIME + serialNumber;
+    }
+
+    public static String buildModbusLockCacheKey(String serialNumber) {
+        return YanfanConstant.REDIS.MODBUS_LOCK + serialNumber;
+    }
+
+}

+ 195 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/server/VideoSessionManager.java

@@ -0,0 +1,195 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.server;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums.SessionType;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisCache;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisKeyBuilder;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.vo.InviteInfo;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.vo.VideoSessionInfo;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.utils.SipUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+
+import javax.sip.ClientTransaction;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static java.util.Collections.emptyList;
+
+@Component
+public class VideoSessionManager {
+    @Autowired
+    private RedisCache redisCache;
+
+    private final ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
+
+    public String createPlaySsrc(String domain) {
+        return SipUtil.getPlaySsrc(domain);
+    }
+
+    public String createPlayBackSsrc(String domain) {
+        return SipUtil.getPlayBackSsrc(domain);
+    }
+
+    public void put(VideoSessionInfo info, ClientTransaction client) {
+        String ssrc = info.getSsrc();
+        if (info.getType() == SessionType.play || info.getType() == SessionType.playrecord) {
+            ssrc = info.getType().name();
+        }
+        String key = RedisKeyBuilder.buildStreamCacheKey(info.getDeviceId(), info.getChannelId(), info.getStream(), ssrc);
+        redisCache.setCacheObject(key, info);
+        if (!ObjectUtils.isEmpty(client)) {
+            key = RedisKeyBuilder.buildStreamCacheKey(info.getDeviceId(), info.getChannelId(), info.getStream(), info.getSsrc());
+            sessionMap.put(key, client);
+        }
+    }
+
+    public ClientTransaction getclientTransaction(VideoSessionInfo info) {
+        String key = RedisKeyBuilder.buildStreamCacheKey(info.getDeviceId(), info.getChannelId(), info.getStream(), info.getSsrc());
+        return sessionMap.get(key);
+    }
+
+    public ClientTransaction getclientTransaction(VideoSessionInfo info, InviteInfo invite) {
+        String key = RedisKeyBuilder.buildStreamCacheKey(info.getDeviceId(), info.getChannelId(), info.getStream(), invite.getSsrc());
+        return sessionMap.get(key);
+    }
+
+    public VideoSessionInfo getSessionInfo(String deviceId, String channelId, String stream, String callId) {
+        if (ObjectUtils.isEmpty(deviceId)) {
+            deviceId = "*";
+        }
+        if (ObjectUtils.isEmpty(channelId)) {
+            channelId = "*";
+        }
+        if (ObjectUtils.isEmpty(stream)) {
+            stream = "*";
+        }
+        if (ObjectUtils.isEmpty(callId)) {
+            callId = "*";
+        }
+        String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, stream, callId);
+        List<Object> scanResult = redisCache.scan(key);
+        if (scanResult.size() == 0) {
+            return null;
+        }
+        return (VideoSessionInfo) redisCache.getCacheObject((String) scanResult.get(0));
+    }
+
+    public VideoSessionInfo getSessionInfoByCallId(String callId) {
+        if (ObjectUtils.isEmpty(callId)) {
+            return null;
+        }
+        String key = RedisKeyBuilder.buildStreamCacheKey("*", "*", "*", callId);
+        List<Object> scanResult = redisCache.scan(key);
+        if (!scanResult.isEmpty()) {
+            return (VideoSessionInfo) redisCache.getCacheObject((String) scanResult.get(0));
+        } else {
+            return null;
+        }
+    }
+
+    public VideoSessionInfo getSessionInfoBySSRC(String SSRC) {
+        if (ObjectUtils.isEmpty(SSRC)) {
+            return null;
+        }
+        String key = RedisKeyBuilder.buildStreamCacheKey("*", "*", SSRC, "*");
+        List<Object> scanResult = redisCache.scan(key);
+        if (!scanResult.isEmpty()) {
+            return (VideoSessionInfo) redisCache.getCacheObject((String) scanResult.get(0));
+        } else {
+            return null;
+        }
+
+    }
+
+    public List<VideoSessionInfo> getSessionInfoForAll(String deviceId, String channelId, String stream, String callId) {
+        if (ObjectUtils.isEmpty(deviceId)) {
+            deviceId = "*";
+        }
+        if (ObjectUtils.isEmpty(channelId)) {
+            channelId = "*";
+        }
+        if (ObjectUtils.isEmpty(stream)) {
+            stream = "*";
+        }
+        if (ObjectUtils.isEmpty(callId)) {
+            callId = "*";
+        }
+        String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, stream, callId);
+        List<Object> scanResult = redisCache.scan(key);
+        if (scanResult.size() == 0) {
+            return emptyList();
+        }
+        List<VideoSessionInfo> result = new ArrayList<>();
+        for (Object keyObj : scanResult) {
+            result.add((VideoSessionInfo) redisCache.getCacheObject((String) keyObj));
+        }
+        return result;
+    }
+
+    public String getMediaServerId(String deviceId, String channelId, String stream) {
+        VideoSessionInfo ssrcTransaction = getSessionInfo(deviceId, channelId, null, stream);
+        if (ssrcTransaction == null) {
+            return null;
+        }
+        return ssrcTransaction.getMediaServerId();
+    }
+
+    public String getSSRC(String deviceId, String channelId, String stream) {
+        VideoSessionInfo ssrcTransaction = getSessionInfo(deviceId, channelId, null, stream);
+        if (ssrcTransaction == null) {
+            return null;
+        }
+        return ssrcTransaction.getSsrc();
+    }
+
+    public void remove(String deviceId, String channelId, String stream, String callId) {
+        String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, stream, callId);
+        if (!Objects.equals(callId, "play")) {
+            redisCache.deleteObject(key);
+        }
+        sessionMap.remove(key);
+    }
+
+    public void remove(String deviceId, String channelId, String stream) {
+        List<VideoSessionInfo> sinfoList = getSessionInfoForAll(deviceId, channelId, stream, null);
+        if (sinfoList == null || sinfoList.isEmpty()) {
+            return;
+        }
+        for (VideoSessionInfo sinfo : sinfoList) {
+            String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, stream, sinfo.getSsrc());
+            if (sinfo.getType() != SessionType.play) {
+                redisCache.deleteObject(key);
+            }
+            sessionMap.remove(key);
+        }
+    }
+
+    public void removeByCallId(String deviceId, String channelId, String callId) {
+        VideoSessionInfo sinfo = getSessionInfo(deviceId, channelId, null, callId);
+        if (sinfo == null) {
+            return;
+        }
+        String key = RedisKeyBuilder.buildStreamCacheKey(deviceId, channelId, sinfo.getStream(), sinfo.getSsrc());
+        if (sinfo.getType() != SessionType.play) {
+            redisCache.deleteObject(key);
+        }
+        sessionMap.remove(key);
+    }
+
+    public List<VideoSessionInfo> getAllSsrc() {
+        String allkey = RedisKeyBuilder.buildStreamCacheKey("*", "*", "*", "*");
+        List<Object> scanResult = redisCache.scan(allkey);
+        if (scanResult.size() == 0) {
+            return null;
+        }
+        List<VideoSessionInfo> result = new ArrayList<>();
+        for (Object ssrcTransactionKey : scanResult) {
+            String key = (String) ssrcTransactionKey;
+            result.add((VideoSessionInfo) redisCache.getCacheObject((String) key));
+        }
+        return result;
+    }
+}

+ 141 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/SipDate.java

@@ -0,0 +1,141 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip;
+
+import gov.nist.core.InternalErrorHandler;
+import gov.nist.javax.sip.header.SIPDate;
+
+import java.util.*;
+
+public class SipDate extends SIPDate {
+
+    private final Calendar javaCal;
+
+    public SipDate(long timeMillis) {
+        this.javaCal = new GregorianCalendar(TimeZone.getDefault(), Locale.getDefault());
+        Date date = new Date(timeMillis);
+        this.javaCal.setTime(date);
+        this.wkday = this.javaCal.get(Calendar.DAY_OF_WEEK);
+        switch(this.wkday) {
+            case 1:
+                this.sipWkDay = "Sun";
+                break;
+            case 2:
+                this.sipWkDay = "Mon";
+                break;
+            case 3:
+                this.sipWkDay = "Tue";
+                break;
+            case 4:
+                this.sipWkDay = "Wed";
+                break;
+            case 5:
+                this.sipWkDay = "Thu";
+                break;
+            case 6:
+                this.sipWkDay = "Fri";
+                break;
+            case 7:
+                this.sipWkDay = "Sat";
+                break;
+            default:
+                InternalErrorHandler.handleException("No date map for wkday " + this.wkday);
+        }
+
+        this.day = this.javaCal.get(Calendar.DATE);
+        this.month = this.javaCal.get(Calendar.MONTH);
+        switch(this.month) {
+            case 0:
+                this.sipMonth = "Jan";
+                break;
+            case 1:
+                this.sipMonth = "Feb";
+                break;
+            case 2:
+                this.sipMonth = "Mar";
+                break;
+            case 3:
+                this.sipMonth = "Apr";
+                break;
+            case 4:
+                this.sipMonth = "May";
+                break;
+            case 5:
+                this.sipMonth = "Jun";
+                break;
+            case 6:
+                this.sipMonth = "Jul";
+                break;
+            case 7:
+                this.sipMonth = "Aug";
+                break;
+            case 8:
+                this.sipMonth = "Sep";
+                break;
+            case 9:
+                this.sipMonth = "Oct";
+                break;
+            case 10:
+                this.sipMonth = "Nov";
+                break;
+            case 11:
+                this.sipMonth = "Dec";
+                break;
+            default:
+                InternalErrorHandler.handleException("No date map for month " + this.month);
+        }
+
+        this.year = this.javaCal.get(Calendar.YEAR);
+        this.hour = this.javaCal.get(Calendar.HOUR_OF_DAY);
+        this.minute = this.javaCal.get(Calendar.MINUTE);
+        this.second = this.javaCal.get(Calendar.SECOND);
+    }
+
+    @Override
+    public StringBuilder encode(StringBuilder var1) {
+        String var2;
+        if (this.month < 9) {
+            var2 = "0" + (this.month + 1);
+        } else {
+            var2 = "" + (this.month + 1);
+        }
+
+        String var3;
+        if (this.day < 10) {
+            var3 = "0" + this.day;
+        } else {
+            var3 = "" + this.day;
+        }
+
+        String var4;
+        if (this.hour < 10) {
+            var4 = "0" + this.hour;
+        } else {
+            var4 = "" + this.hour;
+        }
+
+        String var5;
+        if (this.minute < 10) {
+            var5 = "0" + this.minute;
+        } else {
+            var5 = "" + this.minute;
+        }
+
+        String var6;
+        if (this.second < 10) {
+            var6 = "0" + this.second;
+        } else {
+            var6 = "" + this.second;
+        }
+
+        int var8 = this.javaCal.get(Calendar.MILLISECOND);
+        String var7;
+        if (var8 < 10) {
+            var7 = "00" + var8;
+        } else if (var8 < 100) {
+            var7 = "0" + var8;
+        } else {
+            var7 = "" + var8;
+        }
+
+        return var1.append(this.year).append("-").append(var2).append("-").append(var3).append("T").append(var4).append(":").append(var5).append(":").append(var6).append(".").append(var7);
+    }
+}

+ 119 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/config/IotYfSipConfigController.java

@@ -0,0 +1,119 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.config;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.config.vo.IotYfSipConfigPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.config.vo.IotYfSipConfigRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.config.vo.IotYfSipConfigSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.config.IotSipConfigDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.config.IotSipConfigService;
+import lombok.extern.java.Log;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - yf-sip系统配置")
+@RestController
+@RequestMapping("/rq/iot-sip-config")
+@Validated
+public class IotYfSipConfigController {
+
+    @Resource
+    private IotSipConfigService iotSipConfigService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建yf-sip系统配置")
+    @PreAuthorize("@ss.hasPermission('rq:iot-sip-config:create')")
+    public CommonResult<Long> createIotSipConfig(@Valid @RequestBody IotYfSipConfigSaveReqVO createReqVO) {
+        return success(iotSipConfigService.createIotSipConfig(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新yf-sip系统配置")
+    @PreAuthorize("@ss.hasPermission('rq:iot-sip-config:update')")
+    public CommonResult<Boolean> updateIotSipConfig(@Valid @RequestBody IotYfSipConfigSaveReqVO updateReqVO) {
+        iotSipConfigService.updateIotSipConfig(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除yf-sip系统配置")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-sip-config:delete')")
+    public CommonResult<Boolean> deleteIotSipConfig(@RequestParam("id") Long id) {
+        iotSipConfigService.deleteIotSipConfig(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得yf-sip系统配置")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-sip-config:query')")
+    public CommonResult<IotYfSipConfigRespVO> getIotSipConfig(@RequestParam("id") Long id) {
+        IotSipConfigDO iotSipConfig = iotSipConfigService.getIotSipConfig(id);
+        return success(BeanUtils.toBean(iotSipConfig, IotYfSipConfigRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得yf-sip系统配置分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-sip-config:query')")
+    public CommonResult<PageResult<IotYfSipConfigRespVO>> getIotSipConfigPage(@Valid IotYfSipConfigPageReqVO pageReqVO) {
+        PageResult<IotSipConfigDO> pageResult = iotSipConfigService.getIotSipConfigPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotYfSipConfigRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出yf-sip系统配置 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-sip-config:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotSipConfigExcel(@Valid IotYfSipConfigPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotSipConfigDO> list = iotSipConfigService.getIotSipConfigPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "yf-sip系统配置.xls", "数据", IotYfSipConfigRespVO.class,
+                        BeanUtils.toBean(list, IotYfSipConfigRespVO.class));
+    }
+
+
+    @Operation(summary = "批量删除sip系统配置")
+    @DeleteMapping("/product/{productIds}")
+    public CommonResult<Boolean> removeByProductId(@PathVariable Long[] productIds) {
+        // 设备可能不存在通道,可以返回0
+        int result = iotSipConfigService.deleteSipConfigByProductIds(productIds);
+        return success(true);
+    }
+
+    /**
+     * 获取产品下第一条sip系统配置详细信息
+     */
+    @Operation(summary = "获取产品下sip系统配置信息")
+    //@PreAuthorize("@ss.hasPermi('iot:video:query')")
+    @GetMapping(value = "/{productId}")
+    public CommonResult<IotSipConfigDO> getInfo(@PathVariable("productId") Long productId) {
+        IotSipConfigDO iotSipConfigDO = iotSipConfigService.selectSipConfigByProductId(productId);
+        return success(iotSipConfigDO);
+//        return AjaxResult.success(iotSipConfigService.selectSipConfigByProductId(productId));
+    }
+}

+ 62 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/config/vo/IotYfSipConfigPageReqVO.java

@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.config.vo;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - yf-sip系统配置分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotYfSipConfigPageReqVO extends PageParam {
+
+    @Schema(description = "产品ID", example = "1005")
+    private Long productId;
+    @Schema(description = "产品名称", example = "李四")
+    private String productName;
+
+    @Schema(description = "使能开关")
+    private Boolean enabled;
+
+    @Schema(description = "系统默认配置")
+    private Boolean isdefault;
+
+    @Schema(description = "拓展sdp")
+    private Boolean seniorSdp;
+
+    @Schema(description = "服务器域")
+    private String domain;
+
+    @Schema(description = "服务器sipid", example = "348")
+    private String serverSipid;
+
+    @Schema(description = "sip认证密码")
+    private String password;
+
+    @Schema(description = "sip接入IP")
+    private String ip;
+
+    @Schema(description = "sip接入端口号")
+    private Long port;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你猜")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 77 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/config/vo/IotYfSipConfigRespVO.java

@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - yf-sip系统配置 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotYfSipConfigRespVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "9475")
+    @ExcelProperty("主键")
+    private Long id;
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1005")
+    @ExcelProperty("产品ID")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @ExcelProperty("产品名称")
+    private String productName;
+
+    @Schema(description = "使能开关")
+    @ExcelProperty("使能开关")
+    private Boolean enabled;
+
+    @Schema(description = "系统默认配置")
+    @ExcelProperty("系统默认配置")
+    private Boolean isdefault;
+
+    @Schema(description = "拓展sdp")
+    @ExcelProperty("拓展sdp")
+    private Boolean seniorSdp;
+
+    @Schema(description = "服务器域", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("服务器域")
+    private String domain;
+
+    @Schema(description = "服务器sipid", requiredMode = Schema.RequiredMode.REQUIRED, example = "348")
+    @ExcelProperty("服务器sipid")
+    private String serverSipid;
+
+    @Schema(description = "sip认证密码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("sip认证密码")
+    private String password;
+
+    @Schema(description = "sip接入IP")
+    @ExcelProperty("sip接入IP")
+    private String ip;
+
+    @Schema(description = "sip接入端口号")
+    @ExcelProperty("sip接入端口号")
+    private Long port;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你猜")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 64 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/config/vo/IotYfSipConfigSaveReqVO.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - yf-sip系统配置新增/修改 Request VO")
+@Data
+public class IotYfSipConfigSaveReqVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "9475")
+    private Long id;
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1005")
+    @NotNull(message = "产品ID不能为空")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @NotEmpty(message = "产品名称不能为空")
+    private String productName;
+
+    @Schema(description = "使能开关")
+    private Boolean enabled;
+
+    @Schema(description = "系统默认配置")
+    private Boolean isdefault;
+
+    @Schema(description = "拓展sdp")
+    private Boolean seniorSdp;
+
+    @Schema(description = "服务器域", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "服务器域不能为空")
+    private String domain;
+
+    @Schema(description = "服务器sipid", requiredMode = Schema.RequiredMode.REQUIRED, example = "348")
+    @NotEmpty(message = "服务器sipid不能为空")
+    private String serverSipid;
+
+    @Schema(description = "sip认证密码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "sip认证密码不能为空")
+    private String password;
+
+    @Schema(description = "sip接入IP")
+    private String ip;
+
+    @Schema(description = "sip接入端口号")
+    private Long port;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)", requiredMode = Schema.RequiredMode.REQUIRED)
+//    @NotEmpty(message = "删除标志(0代表存在 2代表删除)不能为空")
+    private String delFlag;
+
+    @Schema(description = "创建者", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "创建者不能为空")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "你猜")
+    private String remark;
+
+}

+ 98 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/YfSipDeviceController.java

@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.vo.YfSipDevicePageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.vo.YfSipDeviceRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.vo.YfSipDeviceSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.YfSipDeviceDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.device.YfSipDeviceService;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - yf监控设备")
+@RestController
+@RequestMapping("/rq/yf-sip-device")
+@Validated
+public class YfSipDeviceController {
+
+    @Resource
+    private YfSipDeviceService yfSipDeviceService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建yf监控设备")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device:create')")
+    public CommonResult<Long> createYfSipDevice(@Valid @RequestBody YfSipDeviceSaveReqVO createReqVO) {
+        return success(yfSipDeviceService.createYfSipDevice(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新yf监控设备")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device:update')")
+    public CommonResult<Boolean> updateYfSipDevice(@Valid @RequestBody YfSipDeviceSaveReqVO updateReqVO) {
+        yfSipDeviceService.updateYfSipDevice(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除yf监控设备")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device:delete')")
+    public CommonResult<Boolean> deleteYfSipDevice(@RequestParam("id") Long id) {
+        yfSipDeviceService.deleteYfSipDevice(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得yf监控设备")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device:query')")
+    public CommonResult<YfSipDeviceRespVO> getYfSipDevice(@RequestParam("id") Long id) {
+        YfSipDeviceDO yfSipDevice = yfSipDeviceService.getYfSipDevice(id);
+        return success(BeanUtils.toBean(yfSipDevice, YfSipDeviceRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得yf监控设备分页")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device:query')")
+    public CommonResult<PageResult<YfSipDeviceRespVO>> getYfSipDevicePage(@Valid YfSipDevicePageReqVO pageReqVO) {
+        PageResult<YfSipDeviceDO> pageResult = yfSipDeviceService.getYfSipDevicePage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, YfSipDeviceRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出yf监控设备 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportYfSipDeviceExcel(@Valid YfSipDevicePageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<YfSipDeviceDO> list = yfSipDeviceService.getYfSipDevicePage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "yf监控设备.xls", "数据", YfSipDeviceRespVO.class,
+                        BeanUtils.toBean(list, YfSipDeviceRespVO.class));
+    }
+
+}

+ 118 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/channel/YfSipDeviceChannelController.java

@@ -0,0 +1,118 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.channel;
+
+import cn.iocoder.yudao.module.pms.controller.admin.TableDataInfo;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.channel.vo.YfSipDeviceChannelPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.channel.vo.YfSipDeviceChannelRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.channel.vo.YfSipDeviceChannelSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.channel.YfSipDeviceChannelDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.device.channel.YfSipDeviceChannelService;
+import lombok.extern.java.Log;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
+
+
+@Tag(name = "管理后台 - yf监控设备通道信息")
+@RestController
+@RequestMapping("/rq/yf-sip-device-channel")
+@Validated
+public class YfSipDeviceChannelController {
+
+    @Resource
+    private YfSipDeviceChannelService yfSipDeviceChannelService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建yf监控设备通道信息")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device-channel:create')")
+    public CommonResult<Long> createYfSipDeviceChannel(@Valid @RequestBody YfSipDeviceChannelSaveReqVO createReqVO) {
+        return success(yfSipDeviceChannelService.createYfSipDeviceChannel(createReqVO));
+    }
+
+    @Operation(summary = "新增监控设备通道信息")
+    @PostMapping(value = "/{createNum}")
+    public CommonResult<String> add(@PathVariable("createNum") Long createNum, @RequestBody YfSipDeviceChannelSaveReqVO sipDeviceChannel) {
+        return success(yfSipDeviceChannelService.insertSipDeviceChannelGen(createNum, sipDeviceChannel));
+//        return AjaxResult.success(MessageUtils.message("operate.success"), );
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新yf监控设备通道信息")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device-channel:update')")
+    public CommonResult<Boolean> updateYfSipDeviceChannel(@Valid @RequestBody YfSipDeviceChannelSaveReqVO updateReqVO) {
+        yfSipDeviceChannelService.updateYfSipDeviceChannel(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除yf监控设备通道信息")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device-channel:delete')")
+    public CommonResult<Boolean> deleteYfSipDeviceChannel(@RequestParam("id") Long id) {
+        yfSipDeviceChannelService.deleteYfSipDeviceChannel(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得yf监控设备通道信息")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device-channel:query')")
+    public CommonResult<YfSipDeviceChannelRespVO> getYfSipDeviceChannel(@RequestParam("id") Long id) {
+        YfSipDeviceChannelDO yfSipDeviceChannel = yfSipDeviceChannelService.getYfSipDeviceChannel(id);
+        return success(BeanUtils.toBean(yfSipDeviceChannel, YfSipDeviceChannelRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得yf监控设备通道信息分页")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device-channel:query')")
+    public CommonResult<PageResult<YfSipDeviceChannelRespVO>> getYfSipDeviceChannelPage(@Valid YfSipDeviceChannelPageReqVO pageReqVO) {
+        PageResult<YfSipDeviceChannelDO> pageResult = yfSipDeviceChannelService.getYfSipDeviceChannelPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, YfSipDeviceChannelRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出yf监控设备通道信息 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:yf-sip-device-channel:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportYfSipDeviceChannelExcel(@Valid YfSipDeviceChannelPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<YfSipDeviceChannelDO> list = yfSipDeviceChannelService.getYfSipDeviceChannelPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "yf监控设备通道信息.xls", "数据", YfSipDeviceChannelRespVO.class,
+                        BeanUtils.toBean(list, YfSipDeviceChannelRespVO.class));
+    }
+
+
+    /**
+     * 查询监控设备通道信息列表
+     */
+    @Operation(summary = "查询监控设备通道信息列表")
+    //@PreAuthorize("@ss.hasPermi('iot:video:list')")
+    @GetMapping("/list")
+    public CommonResult<List<YfSipDeviceChannelRespVO>> list(YfSipDeviceChannelDO sipDeviceChannelDO) {
+        List<YfSipDeviceChannelRespVO> yfSipDeviceChannelDOS = yfSipDeviceChannelService.selectSipDeviceChannelList(sipDeviceChannelDO);
+        return success(yfSipDeviceChannelDOS);
+    }
+}

+ 125 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/channel/vo/YfSipDeviceChannelPageReqVO.java

@@ -0,0 +1,125 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.channel.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - yf监控设备通道信息分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class YfSipDeviceChannelPageReqVO extends PageParam {
+
+    @Schema(description = "租户名称", example = "芋艿")
+    private String tenantName;
+
+    @Schema(description = "产品ID", example = "4664")
+    private Long productId;
+
+    @Schema(description = "产品名称", example = "张三")
+    private String productName;
+
+    @Schema(description = "产品ID", example = "16646")
+    private Long userId;
+
+    @Schema(description = "产品名称", example = "芋艿")
+    private String userName;
+
+    @Schema(description = "通道SipID", example = "29445")
+    private String channelSipId;
+
+    @Schema(description = "通道名称", example = "王五")
+    private String channelName;
+
+    @Schema(description = "注册时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] registerTime;
+
+    @Schema(description = "设备类型", example = "2")
+    private String deviceType;
+
+    @Schema(description = "通道类型", example = "1")
+    private String channelType;
+
+    @Schema(description = "城市编码")
+    private String cityCode;
+
+    @Schema(description = "行政区域")
+    private String civilCode;
+
+    @Schema(description = "厂商名称")
+    private String manufacture;
+
+    @Schema(description = "产品型号")
+    private String model;
+
+    @Schema(description = "设备归属")
+    private String owner;
+
+    @Schema(description = "警区")
+    private String block;
+
+    @Schema(description = "安装地址")
+    private String address;
+
+    @Schema(description = "父级id", example = "27805")
+    private String parentId;
+
+    @Schema(description = "设备入网IP")
+    private String ipAddress;
+
+    @Schema(description = "设备接入端口号")
+    private Long port;
+
+    @Schema(description = "密码")
+    private String password;
+
+    @Schema(description = "PTZ类型", example = "2")
+    private Long ptzType;
+
+    @Schema(description = "PTZ类型描述字符串")
+    private String ptzTypeText;
+
+    @Schema(description = "设备状态(1-未激活,2-禁用,3-在线,4-离线)", example = "2")
+    private Integer status;
+
+    @Schema(description = "设备经度")
+    private Double longitude;
+
+    @Schema(description = "设备纬度")
+    private Double latitude;
+
+    @Schema(description = "流媒体ID", example = "20881")
+    private String streamId;
+
+    @Schema(description = "子设备数", example = "8801")
+    private Long subCount;
+
+    @Schema(description = "是否有子设备(1-有, 0-没有)")
+    private Integer parental;
+
+    @Schema(description = "是否含有音频(1-有, 0-没有)")
+    private Integer hasAudio;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 162 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/channel/vo/YfSipDeviceChannelRespVO.java

@@ -0,0 +1,162 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.channel.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - yf监控设备通道信息 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class YfSipDeviceChannelRespVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "24531")
+    @ExcelProperty("主键")
+    private Long id;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @ExcelProperty("租户名称")
+    private String tenantName;
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4664")
+    @ExcelProperty("产品ID")
+    private Long productId;
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @ExcelProperty("产品名称")
+    private String productName;
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "16646")
+    @ExcelProperty("产品ID")
+    private Long userId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @ExcelProperty("产品名称")
+    private String userName;
+
+    @Schema(description = "设备SipID", requiredMode = Schema.RequiredMode.REQUIRED, example = "761")
+    @ExcelProperty("设备SipID")
+    private String deviceSipId;
+
+    @Schema(description = "通道SipID", requiredMode = Schema.RequiredMode.REQUIRED, example = "29445")
+    @ExcelProperty("通道SipID")
+    private String channelSipId;
+
+    @Schema(description = "通道名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @ExcelProperty("通道名称")
+    private String channelName;
+
+    @Schema(description = "注册时间")
+    @ExcelProperty("注册时间")
+    private LocalDateTime registerTime;
+
+    @Schema(description = "设备类型", example = "2")
+    @ExcelProperty("设备类型")
+    private String deviceType;
+
+    @Schema(description = "通道类型", example = "1")
+    @ExcelProperty("通道类型")
+    private String channelType;
+
+    @Schema(description = "城市编码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("城市编码")
+    private String cityCode;
+
+    @Schema(description = "行政区域", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("行政区域")
+    private String civilCode;
+
+    @Schema(description = "厂商名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("厂商名称")
+    private String manufacture;
+
+    @Schema(description = "产品型号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("产品型号")
+    private String model;
+
+    @Schema(description = "设备归属", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("设备归属")
+    private String owner;
+
+    @Schema(description = "警区", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("警区")
+    private String block;
+
+    @Schema(description = "安装地址", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("安装地址")
+    private String address;
+
+    @Schema(description = "父级id", requiredMode = Schema.RequiredMode.REQUIRED, example = "27805")
+    @ExcelProperty("父级id")
+    private String parentId;
+
+    @Schema(description = "设备入网IP")
+    @ExcelProperty("设备入网IP")
+    private String ipAddress;
+
+    @Schema(description = "设备接入端口号")
+    @ExcelProperty("设备接入端口号")
+    private Long port;
+
+    @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("密码")
+    private String password;
+
+    @Schema(description = "PTZ类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @ExcelProperty("PTZ类型")
+    private Long ptzType;
+
+    @Schema(description = "PTZ类型描述字符串", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("PTZ类型描述字符串")
+    private String ptzTypeText;
+
+    @Schema(description = "设备状态(1-未激活,2-禁用,3-在线,4-离线)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @ExcelProperty("设备状态(1-未激活,2-禁用,3-在线,4-离线)")
+    private Integer status;
+
+    @Schema(description = "设备经度")
+    @ExcelProperty("设备经度")
+    private Double longitude;
+
+    @Schema(description = "设备纬度")
+    @ExcelProperty("设备纬度")
+    private Double latitude;
+
+    @Schema(description = "流媒体ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20881")
+    @ExcelProperty("流媒体ID")
+    private String streamId;
+
+    @Schema(description = "子设备数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8801")
+    @ExcelProperty("子设备数")
+    private Long subCount;
+
+    @Schema(description = "是否有子设备(1-有, 0-没有)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("是否有子设备(1-有, 0-没有)")
+    private Integer parental;
+
+    @Schema(description = "是否含有音频(1-有, 0-没有)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("是否含有音频(1-有, 0-没有)")
+    private Integer hasAudio;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+    private Integer streamPush = 0;
+    private Integer streamRecord = 0;
+}

+ 149 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/channel/vo/YfSipDeviceChannelSaveReqVO.java

@@ -0,0 +1,149 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.channel.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - yf监控设备通道信息新增/修改 Request VO")
+@Data
+public class YfSipDeviceChannelSaveReqVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "24531")
+    private Long id;
+
+    @Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @NotEmpty(message = "租户名称不能为空")
+    private String tenantName;
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4664")
+    @NotNull(message = "产品ID不能为空")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    @NotEmpty(message = "产品名称不能为空")
+    private String productName;
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "16646")
+    @NotNull(message = "产品ID不能为空")
+    private Long userId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @NotEmpty(message = "产品名称不能为空")
+    private String userName;
+
+    @Schema(description = "设备SipID", requiredMode = Schema.RequiredMode.REQUIRED, example = "761")
+    private String deviceSipId;
+
+    @Schema(description = "通道SipID", requiredMode = Schema.RequiredMode.REQUIRED, example = "29445")
+    @NotEmpty(message = "通道SipID不能为空")
+    private String channelSipId;
+
+    @Schema(description = "通道名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @NotEmpty(message = "通道名称不能为空")
+    private String channelName;
+
+    @Schema(description = "注册时间")
+    private LocalDateTime registerTime;
+
+    @Schema(description = "设备类型", example = "2")
+    private String deviceType;
+
+    @Schema(description = "通道类型", example = "1")
+    private String channelType;
+
+    @Schema(description = "城市编码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "城市编码不能为空")
+    private String cityCode;
+
+    @Schema(description = "行政区域", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "行政区域不能为空")
+    private String civilCode;
+
+    @Schema(description = "厂商名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "厂商名称不能为空")
+    private String manufacture;
+
+    @Schema(description = "产品型号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "产品型号不能为空")
+    private String model;
+
+    @Schema(description = "设备归属", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "设备归属不能为空")
+    private String owner;
+
+    @Schema(description = "警区", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "警区不能为空")
+    private String block;
+
+    @Schema(description = "安装地址", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "安装地址不能为空")
+    private String address;
+
+    @Schema(description = "父级id", requiredMode = Schema.RequiredMode.REQUIRED, example = "27805")
+    @NotEmpty(message = "父级id不能为空")
+    private String parentId;
+
+    @Schema(description = "设备入网IP")
+    private String ipAddress;
+
+    @Schema(description = "设备接入端口号")
+    private Long port;
+
+    @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "密码不能为空")
+    private String password;
+
+    @Schema(description = "PTZ类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "PTZ类型不能为空")
+    private Long ptzType;
+
+    @Schema(description = "PTZ类型描述字符串", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "PTZ类型描述字符串不能为空")
+    private String ptzTypeText;
+
+    @Schema(description = "设备状态(1-未激活,2-禁用,3-在线,4-离线)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "设备状态(1-未激活,2-禁用,3-在线,4-离线)不能为空")
+    private Integer status;
+
+    @Schema(description = "设备经度")
+    private Double longitude;
+
+    @Schema(description = "设备纬度")
+    private Double latitude;
+
+    @Schema(description = "流媒体ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "20881")
+    @NotEmpty(message = "流媒体ID不能为空")
+    private String streamId;
+
+    @Schema(description = "子设备数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8801")
+    @NotNull(message = "子设备数不能为空")
+    private Long subCount;
+
+    @Schema(description = "是否有子设备(1-有, 0-没有)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "是否有子设备(1-有, 0-没有)不能为空")
+    private Integer parental;
+
+    @Schema(description = "是否含有音频(1-有, 0-没有)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "是否含有音频(1-有, 0-没有)不能为空")
+    private Integer hasAudio;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "删除标志(0代表存在 2代表删除)不能为空")
+    private String delFlag;
+
+    @Schema(description = "创建者", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "创建者不能为空")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+}

+ 85 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/vo/YfSipDevicePageReqVO.java

@@ -0,0 +1,85 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - yf监控设备分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class YfSipDevicePageReqVO extends PageParam {
+
+    @Schema(description = "产品ID", example = "11116")
+    private Long productId;
+
+    @Schema(description = "产品名称", example = "王五")
+    private String productName;
+
+    @Schema(description = "设备SipID", example = "8965")
+    private String deviceSipId;
+
+    @Schema(description = "设备名称", example = "芋艿")
+    private String deviceName;
+
+    @Schema(description = "厂商名称")
+    private String manufacturer;
+
+    @Schema(description = "产品型号")
+    private String model;
+
+    @Schema(description = "固件版本")
+    private String firmware;
+
+    @Schema(description = "传输模式")
+    private String transport;
+
+    @Schema(description = "流模式")
+    private String streamMode;
+
+    @Schema(description = "在线状态")
+    private String online;
+
+    @Schema(description = "注册时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] registerTime;
+
+    @Schema(description = "最后上线时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] lastConnectTime;
+
+    @Schema(description = "激活时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] activeTime;
+
+    @Schema(description = "设备入网IP")
+    private String ip;
+
+    @Schema(description = "设备接入端口号")
+    private Long port;
+
+    @Schema(description = "设备地址")
+    private String hostAddress;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 102 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/vo/YfSipDeviceRespVO.java

@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - yf监控设备 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class YfSipDeviceRespVO {
+
+    @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4153")
+    @ExcelProperty("设备ID")
+    private Long deviceId;
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11116")
+    @ExcelProperty("产品ID")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @ExcelProperty("产品名称")
+    private String productName;
+
+    @Schema(description = "设备SipID", requiredMode = Schema.RequiredMode.REQUIRED, example = "8965")
+    @ExcelProperty("设备SipID")
+    private String deviceSipId;
+
+    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @ExcelProperty("设备名称")
+    private String deviceName;
+
+    @Schema(description = "厂商名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("厂商名称")
+    private String manufacturer;
+
+    @Schema(description = "产品型号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("产品型号")
+    private String model;
+
+    @Schema(description = "固件版本", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("固件版本")
+    private String firmware;
+
+    @Schema(description = "传输模式", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("传输模式")
+    private String transport;
+
+    @Schema(description = "流模式", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("流模式")
+    private String streamMode;
+
+    @Schema(description = "在线状态", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("在线状态")
+    private String online;
+
+    @Schema(description = "注册时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("注册时间")
+    private LocalDateTime registerTime;
+
+    @Schema(description = "最后上线时间")
+    @ExcelProperty("最后上线时间")
+    private LocalDateTime lastConnectTime;
+
+    @Schema(description = "激活时间")
+    @ExcelProperty("激活时间")
+    private LocalDateTime activeTime;
+
+    @Schema(description = "设备入网IP")
+    @ExcelProperty("设备入网IP")
+    private String ip;
+
+    @Schema(description = "设备接入端口号")
+    @ExcelProperty("设备接入端口号")
+    private Long port;
+
+    @Schema(description = "设备地址")
+    @ExcelProperty("设备地址")
+    private String hostAddress;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    @ExcelProperty("删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    @ExcelProperty("创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    @ExcelProperty("更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+}

+ 90 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/device/vo/YfSipDeviceSaveReqVO.java

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.device.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - yf监控设备新增/修改 Request VO")
+@Data
+public class YfSipDeviceSaveReqVO {
+
+    @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4153")
+    private Long deviceId;
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11116")
+    @NotNull(message = "产品ID不能为空")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @NotEmpty(message = "产品名称不能为空")
+    private String productName;
+
+    @Schema(description = "设备SipID", requiredMode = Schema.RequiredMode.REQUIRED, example = "8965")
+    @NotEmpty(message = "设备SipID不能为空")
+    private String deviceSipId;
+
+    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @NotEmpty(message = "设备名称不能为空")
+    private String deviceName;
+
+    @Schema(description = "厂商名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "厂商名称不能为空")
+    private String manufacturer;
+
+    @Schema(description = "产品型号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "产品型号不能为空")
+    private String model;
+
+    @Schema(description = "固件版本", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "固件版本不能为空")
+    private String firmware;
+
+    @Schema(description = "传输模式", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "传输模式不能为空")
+    private String transport;
+
+    @Schema(description = "流模式", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "流模式不能为空")
+    private String streamMode;
+
+    @Schema(description = "在线状态", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "在线状态不能为空")
+    private String online;
+
+    @Schema(description = "注册时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "注册时间不能为空")
+    private LocalDateTime registerTime;
+
+    @Schema(description = "最后上线时间")
+    private LocalDateTime lastConnectTime;
+
+    @Schema(description = "激活时间")
+    private LocalDateTime activeTime;
+
+    @Schema(description = "设备入网IP")
+    private String ip;
+
+    @Schema(description = "设备接入端口号")
+    private Long port;
+
+    @Schema(description = "设备地址")
+    private String hostAddress;
+
+    @Schema(description = "删除标志(0代表存在 2代表删除)")
+    private String delFlag;
+
+    @Schema(description = "创建者")
+    private String createBy;
+
+    @Schema(description = "更新者")
+    private String updateBy;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+}

+ 210 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/CatalogHandler.java

@@ -0,0 +1,210 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.handler;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums.ChannelType;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums.DeviceChannelStatus;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.utils.XmlUtil;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.YfSipDeviceDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.channel.YfSipDeviceChannelDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.device.channel.YfSipDeviceChannelService;
+import lombok.extern.slf4j.Slf4j;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.time.LocalDateTime;
+import java.util.Iterator;
+import java.util.Objects;
+
+@Slf4j
+@Component
+public class CatalogHandler extends ReqAbstractHandler implements InitializingBean, IMessageHandler {
+
+    @Autowired
+    private ResponseMessageHandler responseMessageHandler;
+
+    @Autowired
+    private YfSipDeviceChannelService sipDeviceChannelService;
+    @Override
+    public void handlerCmdType(RequestEvent evt, YfSipDeviceDO device, Element element) {
+        try {
+            Element rootElement = getRootElement(evt);
+            Element deviceListElement = rootElement.element("DeviceList");
+            if (deviceListElement == null) {
+                return;
+            }
+            Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
+            //List<SipDeviceChannel> channels = new ArrayList<>();
+            if (deviceListIterator != null) {
+                // 遍历DeviceList
+                while (deviceListIterator.hasNext()) {
+                    YfSipDeviceChannelDO deviceChannel = new YfSipDeviceChannelDO();
+                    Element itemDevice = deviceListIterator.next();
+                    Element channelDeviceElement = itemDevice.element("DeviceID");
+                    if (channelDeviceElement == null) {
+                        log.warn("缺少 DeviceID");
+                        continue;
+                    }
+                    String channelId = channelDeviceElement.getTextTrim();
+                    if (ObjectUtils.isEmpty(channelId)) {
+                        log.warn("缺少 DeviceID");
+                        continue;
+                    }
+                    //Status
+                    Element statusElement = itemDevice.element("Status");
+                    if (statusElement != null) {
+                        String status = statusElement.getTextTrim().trim();
+                        if (status.equals("ON") || status.equals("On") || status.equals("ONLINE") || status.equals("OK")) {
+                            deviceChannel.setStatus(DeviceChannelStatus.online.getValue());
+                        }
+                        if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) {
+                            deviceChannel.setStatus(DeviceChannelStatus.offline.getValue());
+                        }
+                    } else {
+                        deviceChannel.setStatus(DeviceChannelStatus.offline.getValue());
+                    }
+
+                    ChannelType channelType = ChannelType.Other;
+                    if (channelId.length() <= 8) {
+                        channelType = ChannelType.CivilCode;
+                        deviceChannel.setHasAudio(0);
+                    }else {
+                        if (channelId.length() == 20) {
+                            int code = Integer.parseInt(channelId.substring(10, 13));
+                            switch (code){
+                                case 215:
+                                    channelType = ChannelType.BusinessGroup;
+                                    deviceChannel.setHasAudio(0);
+                                    break;
+                                case 216:
+                                    channelType = ChannelType.VirtualOrganization;
+                                    deviceChannel.setHasAudio(0);
+                                    break;
+                                case 136:
+                                case 137:
+                                case 138:
+                                    deviceChannel.setHasAudio(1);
+                                    break;
+                                default:
+                                    deviceChannel.setHasAudio(0);
+                                    break;
+
+                            }
+                        }
+                    }
+                    deviceChannel.setDeviceSipId(device.getDeviceSipId());
+                    deviceChannel.setChannelSipId(channelId);
+
+                    //name
+                    Element channdelNameElement = itemDevice.element("Name");
+                    String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim() : "";
+                    deviceChannel.setChannelName(channelName);
+                    //CivilCode
+                    String civilCode = XmlUtil.getText(itemDevice, "CivilCode");
+                    deviceChannel.setCivilCode(civilCode);
+                    if (channelType == ChannelType.CivilCode && civilCode == null) {
+                        deviceChannel.setParental(1);
+                        // 行政区划如果没有传递具体值,则推测一个
+                        if (channelId.length() > 2) {
+                            deviceChannel.setCivilCode(channelId.substring(0, channelId.length() - 2));
+                        }
+                    }
+                    if (channelType.equals(ChannelType.CivilCode)) {
+                        // 行政区划其他字段没必要识别了,默认在线即可
+                        deviceChannel.setStatus(1);
+                        deviceChannel.setParental(1);
+                        sipDeviceChannelService.updateChannel(device.getDeviceSipId(), deviceChannel);
+                        continue;
+                    }
+                    //parentID
+                    String parentId = XmlUtil.getText(itemDevice, "ParentID");
+                    //String businessGroupID = XmlUtil.getText(itemDevice, "BusinessGroupID");
+                    if (parentId != null) {
+                        if (parentId.contains("/")) {
+                            String lastParentId = parentId.substring(parentId.lastIndexOf("/") + 1);
+                            deviceChannel.setParentId(lastParentId);
+                        }else {
+                            deviceChannel.setParentId(parentId);
+                        }
+                        // 兼容设备通道信息中自己为自己父节点的情况
+                        if (deviceChannel.getParentId().equals(deviceChannel.getChannelSipId())) {
+                            deviceChannel.setParentId(null);
+                        }
+                    }
+
+                    // Parental
+                    String parental = XmlUtil.getText(itemDevice, "Parental");
+                    // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1
+                    if (!ObjectUtils.isEmpty(parental) && parental.length() == 1 && Integer.parseInt(parental) == 0) {
+                        deviceChannel.setParental(0);
+                    }else {
+                        deviceChannel.setParental(1);
+                    }
+
+                    if (deviceChannel.getRegisterTime() == null) {
+                        deviceChannel.setRegisterTime(LocalDateTime.now());
+                    }
+                    deviceChannel.setManufacture(XmlUtil.getText(itemDevice, "Manufacturer"));
+                    deviceChannel.setModel(XmlUtil.getText(itemDevice, "Model"));
+                    deviceChannel.setOwner(XmlUtil.getText(itemDevice, "Owner"));
+                    deviceChannel.setBlock(XmlUtil.getText(itemDevice, "Block"));
+                    deviceChannel.setAddress(XmlUtil.getText(itemDevice, "Address"));
+                    deviceChannel.setIpAddress(XmlUtil.getText(itemDevice, "IPAddress"));
+                    if (XmlUtil.getText(itemDevice, "Port") == null || Objects.equals(XmlUtil.getText(itemDevice, "Port"), "")) {
+                        deviceChannel.setPort(0);
+                    } else {
+                        deviceChannel.setPort(Integer.parseInt(XmlUtil.getText(itemDevice, "Port")));
+                    }
+                    deviceChannel.setPassword(XmlUtil.getText(itemDevice, "Password"));
+
+                    if (XmlUtil.getText(itemDevice, "Longitude") == null || Objects.equals(XmlUtil.getText(itemDevice, "Longitude"), "")) {
+                        deviceChannel.setLongitude(BigDecimal.valueOf(0.00));
+                    } else {
+                        deviceChannel.setLongitude(BigDecimal.valueOf(Double.parseDouble(XmlUtil.getText(itemDevice, "Longitude"))));
+                    }
+                    if (XmlUtil.getText(itemDevice, "Latitude") == null || Objects.equals(XmlUtil.getText(itemDevice, "Latitude"), "")) {
+                        deviceChannel.setLatitude(BigDecimal.valueOf(0.00));
+                    } else {
+                        deviceChannel.setLatitude(BigDecimal.valueOf(Double.parseDouble(XmlUtil.getText(itemDevice, "Latitude"))));
+                    }
+
+                    if (XmlUtil.getText(itemDevice, "PTZType") == null || "".equals(XmlUtil.getText(itemDevice, "PTZType"))) {
+                        //兼容INFO中的信息
+                        Element info = itemDevice.element("Info");
+                        if(XmlUtil.getText(info, "PTZType") == null || "".equals(XmlUtil.getText(info, "PTZType"))){
+                            deviceChannel.setPtzType(0L);
+                        } else {
+                            deviceChannel.setPtzType(Long.parseLong(XmlUtil.getText(info, "PTZType")));
+                        }
+                    } else {
+                        deviceChannel.setPtzType(Long.parseLong(XmlUtil.getText(itemDevice, "PTZType")));
+                    }
+
+                    //更新到数据库
+                    sipDeviceChannelService.updateChannel(device.getDeviceSipId(), deviceChannel);
+                    //channels.add(deviceChannel);
+                }
+                // 发布设备property到emqx
+                //mqttService.publishChannelsProperty(device.getDeviceSipId(),channels);
+                // 回复200 OK
+                responseAck(evt);
+            }
+
+        } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        String cmdType = "Catalog";
+        responseMessageHandler.addHandler(cmdType, this);
+    }
+}

+ 15 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/IMessageHandler.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.handler;
+
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.YfSipDeviceDO;
+import org.dom4j.Element;
+
+import javax.sip.RequestEvent;
+
+public interface IMessageHandler {
+    /**
+     * 处理来自设备的信息
+     * @param evt
+     * @param device
+     */
+    void handlerCmdType(RequestEvent evt, YfSipDeviceDO device, Element element);
+}

+ 27 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/MessageHandlerAbstract.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.handler;
+
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.utils.XmlUtil;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.YfSipDeviceDO;
+import org.dom4j.Element;
+
+import javax.sip.RequestEvent;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public abstract class MessageHandlerAbstract implements IMessageHandler {
+    public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
+
+    public void addHandler(String cmdType, IMessageHandler messageHandler) {
+        messageHandlerMap.put(cmdType, messageHandler);
+    }
+
+    @Override
+    public void handlerCmdType(RequestEvent evt, YfSipDeviceDO device, Element element) {
+        String cmd = XmlUtil.getText(element, "CmdType");
+        IMessageHandler messageHandler = messageHandlerMap.get(cmd);
+        if (messageHandler != null) {
+            messageHandler.handlerCmdType(evt, device, element);
+        }
+    }
+}

+ 94 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/MessageRequestProcessor.java

@@ -0,0 +1,94 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.handler;
+
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.utils.SipUtil;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.YfSipDeviceDO;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.IGBListener;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.IReqHandler;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.device.YfSipDeviceService;
+import gov.nist.javax.sip.message.SIPRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipException;
+import javax.sip.message.Response;
+import java.text.ParseException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@Component
+public class MessageRequestProcessor extends ReqAbstractHandler implements InitializingBean, IReqHandler {
+    @Autowired
+    private IGBListener sipListener;
+
+    @Autowired
+    private YfSipDeviceService sipDeviceService;
+
+    private static Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
+
+    public void addHandler(String name, IMessageHandler handler) {
+        messageHandlerMap.put(name, handler);
+    }
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        String method = "MESSAGE";
+        sipListener.addRequestProcessor(method, this);
+    }
+
+    @Override
+    public void processMsg(RequestEvent evt) {
+        TenantUtils.execute(1L, () -> {
+            SIPRequest sipRequest = (SIPRequest) evt.getRequest();
+            String deviceId = SipUtil.getUserIdFromFromHeader(sipRequest);
+            ServerTransaction serverTransaction = getServerTransaction(evt);
+            // 查询设备是否存在
+            YfSipDeviceDO device = sipDeviceService.selectSipDeviceBySipId(deviceId);
+            try {
+                if (device == null) {
+                    // 不存在则回复404
+                    responseAck(serverTransaction, Response.NOT_FOUND, "device " + deviceId + " not found");
+                    log.warn("[设备未找到 ]: {}", deviceId);
+                } else {
+                    Element rootElement = null;
+                    try {
+                        rootElement = getRootElement(evt);
+                        if (rootElement == null) {
+                            log.error("处理MESSAGE请求  未获取到消息体{}", evt.getRequest());
+                            responseAck(serverTransaction, Response.BAD_REQUEST, "content is null");
+                            return;
+                        }
+                    } catch (DocumentException e) {
+                        log.warn("解析XML消息内容异常", e);
+                        // 不存在则回复404
+                        responseAck(serverTransaction, Response.BAD_REQUEST, e.getMessage());
+                    }
+                    assert rootElement != null;
+                    String name = rootElement.getName();
+                    //log.debug("接收到消息:" + evt.getRequest());
+                    IMessageHandler messageHandler = messageHandlerMap.get(name);
+                    if (messageHandler != null) {
+                        messageHandler.handlerCmdType(evt, device, rootElement);
+                    } else {
+                        // 不支持的message
+                        // 不存在则回复415
+                        responseAck(serverTransaction, Response.UNSUPPORTED_MEDIA_TYPE, "Unsupported message type, must Control/Notify/Query/Response");
+                    }
+                }
+            } catch (SipException e) {
+                log.warn("SIP 回复错误", e);
+            } catch (InvalidArgumentException e) {
+                log.warn("参数无效", e);
+            } catch (ParseException e) {
+                log.warn("SIP回复时解析异常", e);
+            }
+        });
+    }
+}

+ 214 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/RegisterReqHandler.java

@@ -0,0 +1,214 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.handler;
+
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.SipDate;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.utils.DigestAuthUtil;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.device.YfIotDeviceDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.config.IotSipConfigDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.YfSipDeviceDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.channel.YfSipDeviceChannelDO;
+import cn.iocoder.yudao.module.pms.dal.mysql.yanfan.sip.device.YfSipDeviceMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.yanfan.sip.device.channel.YfSipDeviceChannelMapper;
+import cn.iocoder.yudao.module.pms.service.IDeviceService;
+import cn.iocoder.yudao.module.pms.service.yanfan.YfDeviceService;
+import cn.iocoder.yudao.module.pms.service.yanfan.config.SysSipConfig;
+import cn.iocoder.yudao.module.pms.service.yanfan.device.YfIotDeviceService;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.IGBListener;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.IMqttService;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.IReqHandler;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.MessageInvoker;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.config.IotSipConfigService;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.device.YfSipDeviceService;
+import cn.iocoder.yudao.module.pms.service.yanfan.sip.device.channel.YfSipDeviceChannelService;
+import gov.nist.javax.sip.address.AddressImpl;
+import gov.nist.javax.sip.address.SipUri;
+import gov.nist.javax.sip.header.Expires;
+import gov.nist.javax.sip.header.SIPDateHeader;
+import liquibase.pro.packaged.Y;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
+import javax.sip.header.*;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+import java.security.NoSuchAlgorithmException;
+import java.text.ParseException;
+import java.time.LocalDateTime;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+
+@Slf4j
+@Component
+public class RegisterReqHandler extends ReqAbstractHandler implements InitializingBean, IReqHandler {
+
+    @Autowired
+    private YfSipDeviceService sipDeviceService;
+    @Autowired
+    private YfSipDeviceChannelService sipDeviceChannelService;
+    @Autowired
+    private YfIotDeviceService yfIotDeviceService;
+
+    @Autowired
+    private IotSipConfigService sipConfigService;
+
+    @Autowired
+    private IGBListener sipListener;
+
+    @Autowired
+    private MessageInvoker messageInvoker;
+
+    @Autowired
+    private SysSipConfig sysSipConfig;
+
+    @Autowired
+    private IMqttService mqttService;
+    @Autowired
+    private YfSipDeviceService yfSipDeviceService;
+    @Autowired
+    private YfSipDeviceMapper yfSipDeviceMapper;
+    @Autowired
+    private YfSipDeviceChannelMapper yfSipDeviceChannelMapper;
+
+    @Override
+    public void processMsg(RequestEvent evt) {
+        TenantUtils.executeIgnore(() -> {
+            try {
+                log.info("收到注册请求,开始处理");
+                Request request = evt.getRequest();
+                Response response;
+                // 注册标志  0:未携带授权头或者密码错误  1:注册成功   2:注销成功
+                int registerFlag;
+                FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
+                AddressImpl address = (AddressImpl) fromHeader.getAddress();
+                SipUri uri = (SipUri) address.getURI();
+                String sipId = uri.getUser();
+                //取默认Sip配置
+                IotSipConfigDO sipConfig = sipConfigService.selectSipConfigBydeviceSipId(sipId);
+                if (sipConfig != null) {
+                    AuthorizationHeader authorhead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
+                    // 校验密码是否正确
+                    if (authorhead == null && !ObjectUtils.isEmpty(sipConfig.getPassword())) {
+                        log.info("未携带授权头 回复401,sipId:"+ sipId);
+                        response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
+                        new DigestAuthUtil().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
+                        getServerTransaction(evt).sendResponse(response);
+                        return;
+                    }
+
+                    boolean pcheck = new DigestAuthUtil().doAuthenticatePlainTextPassword(request,
+                            sipConfig.getPassword());
+
+                    boolean syscheck = new DigestAuthUtil().doAuthenticatePlainTextPassword(request,
+                            sysSipConfig.getPassword());
+                    if (!pcheck && !syscheck) {
+                        // 注册失败
+                        response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
+                        response.setReasonPhrase("wrong password");
+                        log.info("[注册请求] 密码/SIP服务器ID错误, 回复403 sipId:" + sipId);
+                        getServerTransaction(evt).sendResponse(response);
+                        return;
+                    }
+                    response = getMessageFactory().createResponse(Response.OK, request);
+                    // 添加date头
+                    SIPDateHeader dateHeader = new SIPDateHeader();
+                    // 使用自己修改的
+                    SipDate sipDate = new SipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
+                    dateHeader.setDate(sipDate);
+                    response.addHeader(dateHeader);
+
+                    ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
+                    // 添加Contact头
+                    response.addHeader(request.getHeader(ContactHeader.NAME));
+                    // 添加Expires头
+                    response.addHeader(request.getExpires());
+                    ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+                    String received = viaHeader.getReceived();
+                    int rPort = viaHeader.getRPort();
+                    // 本地模拟设备 received 为空 rPort 为 -1
+                    // 解析本地地址替代
+                    if (StringUtils.isEmpty(received) || rPort == -1) {
+                        log.warn("本地地址替代! received:{},rPort:{} [{}:{}]", received, rPort, viaHeader.getHost(), viaHeader.getPort());
+                        received = viaHeader.getHost();
+                        rPort = viaHeader.getPort();
+                    }
+                    YfSipDeviceDO device = new YfSipDeviceDO();
+                    ;
+                    device.setStreamMode("UDP");
+                    device.setDeviceSipId(sipId);
+                    device.setIp(received);
+                    device.setPort(rPort);
+                    device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
+                    // 注销成功
+                    if (expiresHeader != null && expiresHeader.getExpires() == 0) {
+                        registerFlag = 2;
+                    } else {
+                        registerFlag = 1;
+                        // 判断TCP还是UDP
+                        boolean isTcp = false;
+                        ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+                        String transport = reqViaHeader.getTransport();
+                        if (transport.equals("TCP")) {
+                            isTcp = true;
+                        }
+                        device.setTransport(isTcp ? "TCP" : "UDP");
+                    }
+                    getServerTransaction(evt).sendResponse(response);
+                    // 注册成功
+                    if (registerFlag == 1) {
+                        log.info("注册成功! sipId:" + device.getDeviceSipId());
+                        device.setRegisterTime(LocalDateTime.now());
+                        yfSipDeviceService.updateDevice(device);
+                        List<YfSipDeviceChannelDO> channels = yfSipDeviceChannelMapper.selectList("device_sip_id",device.getDeviceSipId());
+                        if (channels.size() > 0) {
+                            YfIotDeviceDO iotdev = yfIotDeviceService.selectDeviceBySerialNumber(device.getDeviceSipId());
+                            if (iotdev == null) {
+                                //添加到统一设备管理 默认添加到admin账号下
+                                int result = yfIotDeviceService.insertDeviceAuto(device.getDeviceSipId(), 1L, sipConfig.getProductId());
+                                if (result == 1) {
+                                    log.info("-----------sip设备认证成功,并自动添加设备到系统,SipId:" + device.getDeviceSipId() + "---------------");
+                                }
+                                iotdev = new YfIotDeviceDO();
+                            }
+                            if (iotdev.getStatus() != 3 && iotdev.getStatus() != 2) {
+                                iotdev.setStatus(3);
+                                yfIotDeviceService.updateDeviceStatusAndLocation(iotdev,device.getIp());
+                            }
+                            // 重新注册更新设备和通道,以免设备替换或更新后信息无法更新
+                            onRegister(device);
+                        } else {
+                            log.info("未找到改设备! deviceId:" + device.getDeviceId());
+                        }
+                    } else if (registerFlag == 2) {
+                        log.info("注销成功! deviceId:" + device.getDeviceId());
+                        mqttService.publishStatus(device, 4);
+                    }
+
+                }
+            } catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) {
+                e.printStackTrace();
+            }
+        });
+    }
+
+    public void onRegister(YfSipDeviceDO device) {
+        // TODO 查询设备信息和下挂设备信息 自动拉流
+        messageInvoker.deviceInfoQuery(device);
+        messageInvoker.catalogQuery(device);
+        messageInvoker.subscribe(device,0,0,3600,"0",null,null);
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        String method = "REGISTER";
+        sipListener.addRequestProcessor(method, this);
+    }
+}

+ 161 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/ReqAbstractHandler.java

@@ -0,0 +1,161 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.handler;
+
+import gov.nist.javax.sip.SipStackImpl;
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
+import gov.nist.javax.sip.stack.SIPServerTransaction;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ArrayUtils;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import javax.sip.*;
+import javax.sip.header.FromHeader;
+import javax.sip.header.HeaderFactory;
+import javax.sip.header.ToHeader;
+import javax.sip.header.ViaHeader;
+import javax.sip.message.MessageFactory;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+import java.io.ByteArrayInputStream;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Slf4j
+public abstract class ReqAbstractHandler {
+    @Autowired
+    @Qualifier(value = "udpSipServer")
+    private SipProvider udpSipServer;
+
+    public ServerTransaction getServerTransaction(RequestEvent evt) {
+        Request request = evt.getRequest();
+        ServerTransaction serverTransaction = evt.getServerTransaction();
+        // 判断TCP还是UDP
+        boolean isTcp = false;
+        ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
+        String transport = reqViaHeader.getTransport();
+        if (transport.equals("TCP")) {
+            isTcp = true;
+        }
+
+        if (serverTransaction == null) {
+            try {
+                if (!isTcp) {
+                    SipStackImpl stack = (SipStackImpl)udpSipServer.getSipStack();
+                    serverTransaction = (SIPServerTransaction) stack.findTransaction((SIPRequest)request, true);
+                    if (serverTransaction == null) {
+                        serverTransaction = udpSipServer.getNewServerTransaction(request);
+                    }
+                }
+            } catch (TransactionAlreadyExistsException | TransactionUnavailableException e) {
+                e.printStackTrace();
+            }
+        }
+        return serverTransaction;
+    }
+
+    public HeaderFactory getHeaderFactory() {
+        try {
+            return SipFactory.getInstance().createHeaderFactory();
+        } catch (PeerUnavailableException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public MessageFactory getMessageFactory() {
+        try {
+            return SipFactory.getInstance().createMessageFactory();
+        } catch (PeerUnavailableException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public void responseAck(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException {
+        Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest());
+        getServerTransaction(evt).sendResponse(response);
+    }
+
+    public SIPResponse responseAck(ServerTransaction serverTransaction, int statusCode) throws SipException, InvalidArgumentException, ParseException {
+        return responseAck(serverTransaction, statusCode, null);
+    }
+
+    public SIPResponse responseAck(ServerTransaction serverTransaction, int statusCode, String msg) throws SipException, InvalidArgumentException, ParseException {
+        ToHeader toHeader = (ToHeader) serverTransaction.getRequest().getHeader(ToHeader.NAME);
+        if (toHeader.getTag() == null) {
+            toHeader.setTag(String.valueOf(System.currentTimeMillis()));
+        }
+        SIPResponse response = (SIPResponse)getMessageFactory().createResponse(statusCode, serverTransaction.getRequest());
+        if (msg != null) {
+            response.setReasonPhrase(msg);
+        }
+        serverTransaction.sendResponse(response);
+        if (statusCode >= 200 && !"NOTIFY".equalsIgnoreCase(serverTransaction.getRequest().getMethod())) {
+            if (serverTransaction.getDialog() != null) {
+                serverTransaction.getDialog().delete();
+            }
+        }
+        return response;
+    }
+
+    public Element getRootElement(RequestEvent evt) throws DocumentException {
+        return getRootElement(evt, "gb2312");
+    }
+    public Element getRootElement(RequestEvent evt, String charset) throws DocumentException {
+        if (charset == null) {
+            charset = "gb2312";
+        }
+        Request request = evt.getRequest();
+        SAXReader reader = new SAXReader();
+        reader.setEncoding(charset);
+        // 对海康出现的未转义字符做处理。
+        String[] destStrArray = new String[]{"&lt;","&gt;","&amp;","&apos;","&quot;"};
+        char despChar = '&'; // 或许可扩展兼容其他字符
+        byte destBye = (byte) despChar;
+        List<Byte> result = new ArrayList<>();
+        byte[] rawContent = request.getRawContent();
+        if (rawContent == null) {
+            FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
+            log.error("rawContent is null,from:{} length:{}",fromHeader.toString(),request.getContentLength());
+            return null;
+        }
+        for (int i = 0; i < rawContent.length; i++) {
+            if (rawContent[i] == destBye) {
+                boolean resul = false;
+                for (String destStr : destStrArray) {
+                    if (i + destStr.length() <= rawContent.length) {
+                        byte[] bytes = Arrays.copyOfRange(rawContent, i, i + destStr.length());
+                        resul = resul || (Arrays.equals(bytes,destStr.getBytes()));
+                    }
+                }
+                if (resul) {
+                    result.add(rawContent[i]);
+                }
+            } else {
+                result.add(rawContent[i]);
+            }
+        }
+        Byte[] bytes = new Byte[0];
+        byte[] bytesResult = ArrayUtils.toPrimitive(result.toArray(bytes));
+        String content = new String(bytesResult);
+        //过滤掉非法字符
+        String ret = XMLChars(content);
+        Document xml = reader.read(new ByteArrayInputStream(bytesResult));
+        return xml.getRootElement();
+
+    }
+    public String XMLChars(String s) {
+        if (s == null || "".equals(s)) {
+            return s;
+        }
+        return s.replaceAll("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]", "");
+    }
+
+}

+ 18 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/sip/handler/ResponseMessageHandler.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.handler;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ResponseMessageHandler extends MessageHandlerAbstract implements InitializingBean {
+
+    @Autowired
+    private MessageRequestProcessor messageRequestProcessor;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        String messageType = "Response";
+        messageRequestProcessor.addHandler(messageType, this);
+    }
+}

Some files were not shown because too many files changed in this diff