Selaa lähdekoodia

Merge branch 'feature/iot' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into origin/feature/iot

alwayssuper 7 kuukautta sitten
vanhempi
sitoutus
1ce9420a8d
100 muutettua tiedostoa jossa 1766 lisäystä ja 1905 poistoa
  1. 2 0
      plugins/enabled.txt
  2. BIN
      plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar
  3. 3 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java
  4. 1 0
      yudao-module-iot/yudao-module-iot-api/pom.xml
  5. 2 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java
  6. 1 1
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java
  7. 0 1
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
  8. 4 6
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java
  9. 4 6
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java
  10. 4 7
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java
  11. 27 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotDataSpecsDataTypeEnum.java
  12. 2 2
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelAccessModeEnum.java
  13. 20 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelParamDirectionEnum.java
  14. 20 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceCallTypeEnum.java
  15. 21 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceEventTypeEnum.java
  16. 4 4
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelTypeEnum.java
  17. 1 7
      yudao-module-iot/yudao-module-iot-biz/pom.xml
  18. 3 4
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java
  19. 5 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
  20. 2 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java
  21. 11 18
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInfoController.java
  22. 19 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java
  23. 17 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoPageReqVO.java
  24. 5 5
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java
  25. 4 4
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java
  26. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstancePageReqVO.java
  27. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstanceRespVO.java
  28. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstanceSaveReqVO.java
  29. 0 116
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java
  30. 0 59
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java
  31. 0 94
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/PluginInstanceController.java
  32. 0 84
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotProductThingModelController.java
  33. 3 9
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.http
  34. 84 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java
  35. 19 6
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelEvent.java
  36. 50 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelParam.java
  37. 5 6
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelProperty.java
  38. 25 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelService.java
  39. 0 26
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelArgument.java
  40. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelDateOrTextDataSpecs.java
  41. 4 10
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelStructDataSpecs.java
  42. 3 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelPageReqVO.java
  43. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelRespVO.java
  44. 3 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelSaveReqVO.java
  45. 15 15
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/thingmodel/IotThingModelConvert.java
  46. 5 5
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDataDO.java
  47. 12 8
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java
  48. 4 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java
  49. 7 8
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/FieldParser.java
  50. 1 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/SelectDO.java
  51. 1 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/SelectVisualDO.java
  52. 1 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TagsSelectDO.java
  53. 2 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdFieldDO.java
  54. 1 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdResponse.java
  55. 1 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdRestApi.java
  56. 1 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdTableDO.java
  57. 6 6
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/IotThingModelDO.java
  58. 3 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
  59. 25 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java
  60. 8 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java
  61. 0 36
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininfo/PluginInfoMapper.java
  62. 3 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
  63. 0 62
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thingmodel/IotProductThingModelMapper.java
  64. 63 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thingmodel/IotThingModelMapper.java
  65. 77 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyDataMapper.java
  66. 0 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineDDLMapper.java
  67. 2 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java
  68. 0 34
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java
  69. 0 17
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java
  70. 39 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java
  71. 24 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/listener/CustomPluginStateListener.java
  72. 63 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/TDengineTableField.java
  73. 17 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/annotation/TDengineDS.java
  74. 4 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/package-info.java
  75. 29 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java
  76. 3 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/simulatesend/SimulateSendConsumer.java
  77. 11 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java
  78. 86 8
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java
  79. 6 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
  80. 13 4
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java
  81. 270 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java
  82. 15 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java
  83. 100 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java
  84. 0 241
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java
  85. 0 54
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceService.java
  86. 0 70
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java
  87. 15 10
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
  88. 0 19
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotSuperTableService.java
  89. 0 260
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotSuperTableServiceImpl.java
  90. 20 24
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java
  91. 0 442
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotProductThingModelServiceImpl.java
  92. 11 18
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelService.java
  93. 330 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java
  94. 9 16
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java
  95. 45 0
      yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyDataMapper.xml
  96. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininstance/PluginInstanceMapper.xml
  97. 0 1
      yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml
  98. 2 0
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml
  99. 10 23
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java
  100. 22 9
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java

+ 2 - 0
plugins/enabled.txt

@@ -0,0 +1,2 @@
+http-plugin
+http-plugin@0.0.1

BIN
plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar


+ 3 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java

@@ -66,7 +66,9 @@ public class CrmReceivableDO extends BaseDO {
      */
     private LocalDateTime returnTime;
     /**
-     * 回款方式,关联枚举{@link CrmReceivableReturnTypeEnum}
+     * 回款方式
+     *
+     * 枚举 {@link CrmReceivableReturnTypeEnum}
      */
     private Integer returnType;
     /**

+ 1 - 0
yudao-module-iot/yudao-module-iot-api/pom.xml

@@ -22,6 +22,7 @@
             <artifactId>yudao-common</artifactId>
         </dependency>
         <!-- PF4J -->
+        <!-- TODO 芋艿:这个依赖,要不要放在 api 包 -->
         <dependency>
             <groupId>org.pf4j</groupId>
             <artifactId>pf4j-spring</artifactId>

+ 2 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java

@@ -7,6 +7,7 @@ import java.util.Map;
  * 服务注册表 - 插架模块使用,无法使用 Spring 注入
  */
 public class ServiceRegistry {
+
     private static final Map<Class<?>, Object> services = new HashMap<>();
 
     /**
@@ -31,4 +32,5 @@ public class ServiceRegistry {
     public static <T> T getService(Class<T> serviceClass) {
         return (T) services.get(serviceClass);
     }
+
 }

+ 1 - 1
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/DeviceDataApi.java → yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.api;
+package cn.iocoder.yudao.module.iot.api.device;
 
 /**
  * 设备数据 API

+ 0 - 1
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java

@@ -14,7 +14,6 @@ public interface ErrorCodeConstants {
     ErrorCode PRODUCT_KEY_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
     ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
     ErrorCode PRODUCT_STATUS_NOT_ALLOW_THING_MODEL = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
-    ErrorCode PRODUCT_DEVICE_NOT_EXISTS = new ErrorCode(1_050_001_004, "产品设备类型不存在");
 
     // ========== 产品物模型 1-050-002-000 ============
     ErrorCode THING_MODEL_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");

+ 4 - 6
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java

@@ -34,12 +34,10 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable {
     }
 
     public static IotPluginDeployTypeEnum fromDeployType(Integer deployType) {
-        for (IotPluginDeployTypeEnum value : values()) {
-            if (value.getDeployType().equals(deployType)) {
-                return value;
-            }
-        }
-        return null;
+        return Arrays.stream(values())
+                .filter(value -> value.getDeployType().equals(deployType))
+                .findFirst()
+                .orElse(null);
     }
 
     public static boolean isValidDeployType(Integer deployType) {

+ 4 - 6
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java

@@ -34,12 +34,10 @@ public enum IotPluginStatusEnum implements IntArrayValuable {
     }
 
     public static IotPluginStatusEnum fromState(Integer state) {
-        for (IotPluginStatusEnum value : values()) {
-            if (value.getStatus().equals(state)) {
-                return value;
-            }
-        }
-        return null;
+        return Arrays.stream(values())
+                .filter(value -> value.getStatus().equals(state))
+                .findFirst()
+                .orElse(null);
     }
 
     @Override

+ 4 - 7
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java

@@ -35,14 +35,11 @@ public enum IotPluginTypeEnum implements IntArrayValuable {
         return ARRAYS;
     }
 
-    // TODO @haohao:可以使用 hutool 简化
     public static IotPluginTypeEnum fromType(Integer type) {
-        for (IotPluginTypeEnum value : values()) {
-            if (value.getType().equals(type)) {
-                return value;
-            }
-        }
-        return null;
+        return Arrays.stream(values())
+                .filter(value -> value.getType().equals(type))
+                .findFirst()
+                .orElse(null);
     }
 
     public static boolean isValidType(Integer type) {

+ 27 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotDataSpecsDataTypeEnum.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * IoT 数据定义的数据类型枚举类
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum IotDataSpecsDataTypeEnum {
+
+    INT("int"),
+    FLOAT("float"),
+    DOUBLE("double"),
+    ENUM("enum"),
+    BOOL("bool"),
+    TEXT("text"),
+    DATE("date"),
+    STRUCT("struct"),
+    ARRAY("array");
+
+    private final String dataType;
+
+}

+ 2 - 2
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotProductThingModelAccessModeEnum.java → yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelAccessModeEnum.java

@@ -4,13 +4,13 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 
 /**
- * IOT 访问方式枚举类
+ * IOT 产品物模型属性读取类型枚举
  *
  * @author ahh
  */
 @AllArgsConstructor
 @Getter
-public enum IotProductThingModelAccessModeEnum {
+public enum IotThingModelAccessModeEnum {
 
     READ_ONLY("r"),
     READ_WRITE("rw");

+ 20 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelParamDirectionEnum.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * IOT 产品物模型参数是输入参数还是输出参数枚举
+ *
+ * @author HUIHUI
+ */
+@AllArgsConstructor
+@Getter
+public enum IotThingModelParamDirectionEnum {
+
+    INPUT("input"), // 输入参数
+    OUTPUT("output"); // 输出参数
+
+    private final String direction;
+
+}

+ 20 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceCallTypeEnum.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * IOT 产品物模型服务调用方式枚举
+ *
+ * @author HUIHUI
+ */
+@AllArgsConstructor
+@Getter
+public enum IotThingModelServiceCallTypeEnum {
+
+    ASYNC("async"), // 异步调用
+    SYNC("sync"); // 同步调用
+
+    private final String type;
+
+}

+ 21 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceEventTypeEnum.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.iot.enums.thingmodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * IOT 产品物模型事件类型枚举
+ *
+ * @author HUIHUI
+ */
+@AllArgsConstructor
+@Getter
+public enum IotThingModelServiceEventTypeEnum {
+
+    INFO("info"), // 信息
+    ALERT("alert"), // 告警
+    ERROR("error"); // 故障
+
+    private final String type;
+
+}

+ 4 - 4
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotProductThingModelTypeEnum.java → yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelTypeEnum.java

@@ -13,13 +13,13 @@ import java.util.Arrays;
  */
 @AllArgsConstructor
 @Getter
-public enum IotProductThingModelTypeEnum implements IntArrayValuable {
+public enum IotThingModelTypeEnum implements IntArrayValuable {
 
     PROPERTY(1, "属性"),
     SERVICE(2, "服务"),
     EVENT(3, "事件");
 
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductThingModelTypeEnum::getType).toArray();
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotThingModelTypeEnum::getType).toArray();
 
     /**
      * 类型
@@ -30,8 +30,8 @@ public enum IotProductThingModelTypeEnum implements IntArrayValuable {
      */
     private final String description;
 
-    public static IotProductThingModelTypeEnum valueOfType(Integer type) {
-        for (IotProductThingModelTypeEnum value : values()) {
+    public static IotThingModelTypeEnum valueOfType(Integer type) {
+        for (IotThingModelTypeEnum value : values()) {
             if (value.getType().equals(type)) {
                 return value;
             }

+ 1 - 7
yudao-module-iot/yudao-module-iot-biz/pom.xml

@@ -25,13 +25,6 @@
             <version>${revision}</version>
         </dependency>
 
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-iot-plugin-api</artifactId>
-            <version>0.0.1</version>
-            <scope>compile</scope>
-        </dependency>
-
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
@@ -81,6 +74,7 @@
         <dependency>
             <groupId>org.pf4j</groupId>
             <artifactId>pf4j-spring</artifactId>
+            <!-- TODO @芋艿:可以放到 bom 里配置 -->
             <exclusions>
                 <exclusion>
                     <groupId>org.slf4j</groupId>

+ 3 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.iot.api.device;
 
-import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
-import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
+import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -15,11 +14,11 @@ import javax.annotation.Resource;
 public class DeviceDataApiImpl implements DeviceDataApi {
 
     @Resource
-    private IotDeviceDataService iotDeviceDataService;
+    private IotDevicePropertyDataService deviceDataService;
 
     @Override
     public void saveDeviceData(String productKey, String deviceName, String message) {
-        iotDeviceDataService.saveDeviceData(productKey, deviceName, message);
+        deviceDataService.saveDeviceData(productKey, deviceName, message);
     }
 
 }

+ 5 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java

@@ -1 +1,6 @@
+/**
+ * 占位
+ *
+ * TODO 芋艿:后续删除
+ */
 package cn.iocoder.yudao.module.iot.api;

+ 2 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java

@@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDevi
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO;
-import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
+import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
@@ -29,7 +29,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 public class IotDeviceDataController {
 
     @Resource
-    private IotDeviceDataService deviceDataService;
+    private IotDevicePropertyDataService deviceDataService;
 
     // TODO @浩浩:这里的 /latest-list,包括方法名。
     @GetMapping("/latest")

+ 11 - 18
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInfoController.java

@@ -1,13 +1,14 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
+package cn.iocoder.yudao.module.iot.controller.admin.plugin;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoRespVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoImportReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
-import cn.iocoder.yudao.module.iot.service.plugininfo.PluginInfoService;
+import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,11 +17,8 @@ import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
-import org.springframework.web.multipart.MultipartFile;
 
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
 
 @Tag(name = "管理后台 - IoT 插件信息")
 @RestController
@@ -72,16 +70,11 @@ public class PluginInfoController {
         return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class));
     }
 
-    @RequestMapping(value = "/update-jar",
-            method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题
-    @Operation(summary = "上传Jar包")
-    public CommonResult<Boolean> uploadJar(
-            @RequestParam("id") Long id,
-            @RequestParam("jar") MultipartFile file) throws Exception {
-        if (file.isEmpty()) {
-            throw exception(FILE_IS_EMPTY);
-        }
-        pluginInfoService.uploadJar(id, file);
+    @PostMapping("/upload-file")
+    @Operation(summary = "上传插件文件")
+    @PreAuthorize("@ss.hasPermission('iot:plugin-info:update')")
+    public CommonResult<Boolean> uploadFile(@Valid PluginInfoImportReqVO reqVO) {
+        pluginInfoService.uploadFile(reqVO.getId(), reqVO.getFile());
         return success(true);
     }
 

+ 19 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+@Schema(description = "管理后台 - IoT 插件上传 Request VO")
+@Data
+public class PluginInfoImportReqVO {
+
+    @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
+    private Long id;
+
+    @Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "插件文件不能为空")
+    private MultipartFile file;
+
+}

+ 17 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoPageReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 插件信息分页 Request VO")
+@Data
+public class PluginInfoPageReqVO extends PageParam {
+
+    @Schema(description = "插件名称", example = "http")
+    private String name;
+
+    @Schema(description = "状态")
+    private Integer status;
+
+}

+ 5 - 5
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
+package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
 
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
@@ -16,9 +16,9 @@ public class PluginInfoRespVO {
     @ExcelProperty("主键 ID")
     private Long id;
 
-    @Schema(description = "插件包 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
-    @ExcelProperty("插件包 ID")
-    private String pluginId;
+    @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
+    @ExcelProperty("插件包标识符")
+    private String pluginKey;
 
     @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
     @ExcelProperty("插件名称")
@@ -34,7 +34,7 @@ public class PluginInfoRespVO {
 
     @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("插件包文件名")
-    private String file;
+    private String fileName;
 
     @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("插件版本")

+ 4 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoSaveReqVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
+package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
@@ -10,8 +10,8 @@ public class PluginInfoSaveReqVO {
     @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
     private Long id;
 
-    @Schema(description = "插件包id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
-    private String pluginId;
+    @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
+    private String pluginKey;
 
     @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
     private String name;
@@ -23,7 +23,7 @@ public class PluginInfoSaveReqVO {
     private Integer deployType;
 
     @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
-    private String file;
+    private String fileName;
 
     @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
     private String version;

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstancePageReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
+package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
 
 import lombok.*;
 import io.swagger.v3.oas.annotations.media.Schema;

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstanceRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
+package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstanceSaveReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
+package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;

+ 0 - 116
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java

@@ -1,116 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
-
-import jakarta.annotation.Resource;
-import org.pf4j.spring.SpringPluginManager;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.annotation.security.PermitAll;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 插件 Controller 测试用例
- */
-@RestController
-@RequestMapping("/iot/plugins")
-public class PluginController {
-
-    @Resource
-    private SpringPluginManager springPluginManager;
-
-    @Value("${pf4j.pluginsDir}")
-    private String pluginsDir;
-
-    /**
-     * 上传插件 JAR 文件并加载插件
-     *
-     * @param file 上传的 JAR 文件
-     * @return 上传结果
-     */
-    @PermitAll
-    @PostMapping("/upload")
-    public ResponseEntity<String> uploadPlugin(@RequestParam("file") MultipartFile file) {
-        if (file.isEmpty()) {
-            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传的文件为空");
-        }
-
-        // 确保插件目录存在
-        Path pluginsPath = Paths.get(pluginsDir);
-        try {
-            if (!Files.exists(pluginsPath)) {
-                Files.createDirectories(pluginsPath);
-            }
-
-            // 保存上传的 JAR 文件到插件目录
-            String filename = file.getOriginalFilename();
-            if (filename == null || (!filename.endsWith(".jar") && !filename.endsWith(".zip"))) {
-                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传的文件不是 JAR 或 ZIP 文件");
-            }
-
-            Path jarPath = pluginsPath.resolve(filename);
-
-            Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING);
-
-            // 加载插件
-            String pluginId = springPluginManager.loadPlugin(jarPath.toAbsolutePath());
-
-            // 启动插件
-            springPluginManager.startPlugin(pluginId);
-
-            return ResponseEntity.ok("插件上传并加载成功");
-        } catch (IOException e) {
-            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传插件时发生错误: " + e.getMessage());
-        } catch (Exception e) {
-            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("加载插件时发生错误: " + e.getMessage());
-        }
-    }
-
-    /**
-     * 卸载指定插件
-     *
-     * @param pluginId 插件 ID
-     * @return 卸载结果
-     */
-    @PermitAll
-    @DeleteMapping("/unload/{pluginId}")
-    public ResponseEntity<String> unloadPlugin(@PathVariable String pluginId) {
-        if (springPluginManager.getPlugins().stream().noneMatch(plugin -> plugin.getDescriptor().getPluginId().equals(pluginId))) {
-            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("插件未加载: " + pluginId);
-        }
-
-        springPluginManager.stopPlugin(pluginId);
-        springPluginManager.unloadPlugin(pluginId);
-
-        // 删除插件 JAR 文件(可选)
-//            PluginWrapper plugin = pluginManager.getPlugin(pluginId);
-//            PluginDescriptor descriptor = plugin.getDescriptor();
-//            Path jarPath = Paths.get(pluginsDir).resolve(descriptor.getPluginId() + ".jar");
-//            Files.deleteIfExists(jarPath);
-
-        return ResponseEntity.ok("插件卸载成功: " + pluginId);
-    }
-
-    /**
-     * 列出所有已加载的插件
-     *
-     * @return 插件列表
-     */
-    @PermitAll
-    @GetMapping("/list")
-    public ResponseEntity<List<String>> listPlugins() {
-        List<String> plugins = springPluginManager.getPlugins().stream()
-                .map(plugin -> plugin.getDescriptor().getPluginId())
-                .collect(Collectors.toList());
-        return ResponseEntity.ok(plugins);
-    }
-
-}

+ 0 - 59
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java

@@ -1,59 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
-
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum;
-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;
-
-// TODO @haohao:只查询必要字段哈
-@Schema(description = "管理后台 - IoT 插件信息分页 Request VO")
-@Data
-public class PluginInfoPageReqVO extends PageParam {
-
-    @Schema(description = "插件包 ID ", example = "24627")
-    private String pluginId;
-
-    @Schema(description = "插件名称", example = "赵六")
-    private String name;
-
-    @Schema(description = "描述", example = "你猜")
-    private String description;
-
-    @Schema(description = "部署方式", example = "2")
-    private Integer deployType;
-
-    @Schema(description = "插件包文件名")
-    private String file;
-
-    @Schema(description = "插件版本")
-    private String version;
-
-    @Schema(description = "插件类型", example = "2")
-    @InEnum(IotPluginTypeEnum.class)
-    private Integer type;
-
-    @Schema(description = "设备插件协议类型")
-    private String protocol;
-
-    @Schema(description = "状态")
-    private Integer status;
-
-    @Schema(description = "插件配置项描述信息")
-    private String configSchema;
-
-    @Schema(description = "插件配置信息")
-    private String config;
-
-    @Schema(description = "插件脚本")
-    private String script;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
-}

+ 0 - 94
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/PluginInstanceController.java

@@ -1,94 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugininstance;
-
-import org.springframework.web.bind.annotation.*;
-import jakarta.annotation.Resource;
-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 jakarta.validation.*;
-import jakarta.servlet.http.*;
-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 static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
-
-import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
-import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
-import cn.iocoder.yudao.module.iot.service.plugininstance.PluginInstanceService;
-
-@Tag(name = "管理后台 - IoT 插件实例")
-@RestController
-@RequestMapping("/iot/plugin-instance")
-@Validated
-public class PluginInstanceController {
-
-    @Resource
-    private PluginInstanceService pluginInstanceService;
-
-    @PostMapping("/create")
-    @Operation(summary = "创建IoT 插件实例")
-    @PreAuthorize("@ss.hasPermission('iot:plugin-instance:create')")
-    public CommonResult<Long> createPluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO createReqVO) {
-        return success(pluginInstanceService.createPluginInstance(createReqVO));
-    }
-
-    @PutMapping("/update")
-    @Operation(summary = "更新IoT 插件实例")
-    @PreAuthorize("@ss.hasPermission('iot:plugin-instance:update')")
-    public CommonResult<Boolean> updatePluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO updateReqVO) {
-        pluginInstanceService.updatePluginInstance(updateReqVO);
-        return success(true);
-    }
-
-    @DeleteMapping("/delete")
-    @Operation(summary = "删除IoT 插件实例")
-    @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('iot:plugin-instance:delete')")
-    public CommonResult<Boolean> deletePluginInstance(@RequestParam("id") Long id) {
-        pluginInstanceService.deletePluginInstance(id);
-        return success(true);
-    }
-
-    @GetMapping("/get")
-    @Operation(summary = "获得IoT 插件实例")
-    @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')")
-    public CommonResult<PluginInstanceRespVO> getPluginInstance(@RequestParam("id") Long id) {
-        PluginInstanceDO pluginInstance = pluginInstanceService.getPluginInstance(id);
-        return success(BeanUtils.toBean(pluginInstance, PluginInstanceRespVO.class));
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得IoT 插件实例分页")
-    @PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')")
-    public CommonResult<PageResult<PluginInstanceRespVO>> getPluginInstancePage(@Valid PluginInstancePageReqVO pageReqVO) {
-        PageResult<PluginInstanceDO> pageResult = pluginInstanceService.getPluginInstancePage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, PluginInstanceRespVO.class));
-    }
-
-    @GetMapping("/export-excel")
-    @Operation(summary = "导出IoT 插件实例 Excel")
-    @PreAuthorize("@ss.hasPermission('iot:plugin-instance:export')")
-    @ApiAccessLog(operateType = EXPORT)
-    public void exportPluginInstanceExcel(@Valid PluginInstancePageReqVO pageReqVO,
-              HttpServletResponse response) throws IOException {
-        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<PluginInstanceDO> list = pluginInstanceService.getPluginInstancePage(pageReqVO).getList();
-        // 导出 Excel
-        ExcelUtils.write(response, "IoT 插件实例.xls", "数据", PluginInstanceRespVO.class,
-                        BeanUtils.toBean(list, PluginInstanceRespVO.class));
-    }
-
-}

+ 0 - 84
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotProductThingModelController.java

@@ -1,84 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.thingmodel;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelRespVO;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
-import cn.iocoder.yudao.module.iot.convert.thingmodel.IotProductThingModelConvert;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
-import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.annotation.Resource;
-import jakarta.validation.Valid;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "管理后台 - IoT 产品物模型")
-@RestController
-@RequestMapping("/iot/product-thing-model")
-@Validated
-public class IotProductThingModelController {
-
-    @Resource
-    private IotProductThingModelService thingModelService;
-
-    @PostMapping("/create")
-    @Operation(summary = "创建产品物模型")
-    @PreAuthorize("@ss.hasPermission('iot:product-thing-model:create')")
-    public CommonResult<Long> createProductThingModel(@Valid @RequestBody IotProductThingModelSaveReqVO createReqVO) {
-        return success(thingModelService.createProductThingModel(createReqVO));
-    }
-
-    @PutMapping("/update")
-    @Operation(summary = "更新产品物模型")
-    @PreAuthorize("@ss.hasPermission('iot:product-thing-model:update')")
-    public CommonResult<Boolean> updateProductThingModel(@Valid @RequestBody IotProductThingModelSaveReqVO updateReqVO) {
-        thingModelService.updateProductThingModel(updateReqVO);
-        return success(true);
-    }
-
-    @DeleteMapping("/delete")
-    @Operation(summary = "删除产品物模型")
-    @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('iot:product-thing-model:delete')")
-    public CommonResult<Boolean> deleteProductThingModel(@RequestParam("id") Long id) {
-        thingModelService.deleteProductThingModel(id);
-        return success(true);
-    }
-
-    @GetMapping("/get")
-    @Operation(summary = "获得产品物模型")
-    @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
-    public CommonResult<IotProductThingModelRespVO> getProductThingModel(@RequestParam("id") Long id) {
-        IotProductThingModelDO thingModel = thingModelService.getProductThingModel(id);
-        return success(IotProductThingModelConvert.INSTANCE.convert(thingModel));
-    }
-
-    @GetMapping("/list-by-product-id")
-    @Operation(summary = "获得产品物模型")
-    @Parameter(name = "productId", description = "产品ID", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
-    public CommonResult<List<IotProductThingModelRespVO>> getProductThingModelListByProductId(@RequestParam("productId") Long productId) {
-        List<IotProductThingModelDO> list = thingModelService.getProductThingModelListByProductId(productId);
-        return success(IotProductThingModelConvert.INSTANCE.convertList(list));
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得产品物模型分页")
-    @PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
-    public CommonResult<PageResult<IotProductThingModelRespVO>> getProductThingModelPage(@Valid IotProductThingModelPageReqVO pageReqVO) {
-        PageResult<IotProductThingModelDO> pageResult = thingModelService.getProductThingModelPage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, IotProductThingModelRespVO.class));
-    }
-
-}

+ 3 - 9
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotProductThingModelController.http → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.http

@@ -25,8 +25,7 @@ Authorization: Bearer {{token}}
       "defaultValue": "30",
       "unit": "%",
       "unitName": "百分比"
-    },
-    "description": "当前温度值"
+    }
   }
 }
 
@@ -46,7 +45,6 @@ Authorization: Bearer {{token}}
   "property": {
     "identifier": "switch",
     "name": "开关",
-    "description": "温度计开关",
     "accessMode": "rw",
     "required": true,
     "dataType": "bool",
@@ -81,7 +79,6 @@ Authorization: Bearer {{token}}
   "property": {
     "identifier": "argb",
     "name": "温度计 argb 颜色",
-    "description": "温度计 argb 颜色",
     "accessMode": "rw",
     "required": true,
     "dataType": "array",
@@ -93,7 +90,6 @@ Authorization: Bearer {{token}}
         {
           "identifier": "switch",
           "name": "开关",
-          "description": "温度计开关",
           "accessMode": "rw",
           "required": true,
           "dataType": "struct",
@@ -126,8 +122,7 @@ Authorization: Bearer {{token}}
             "defaultValue": "30",
             "unit": "%",
             "unitName": "百分比"
-          },
-          "description": "当前温度值"
+          }
         }
       ]
     }
@@ -151,7 +146,6 @@ Authorization: Bearer {{token}}
   "property": {
     "identifier": "switch",
     "name": "开关",
-    "description": "温度计开关",
     "accessMode": "r",
     "required": true,
     "dataType": "bool",
@@ -176,7 +170,7 @@ tenant-id: {{adminTenentId}}
 Authorization: Bearer {{token}}
 
 ### 请求 /iot/product-thing-model/get 接口 => 成功
-GET {{baseUrl}}/iot/product-thing-model/get?id=40
+GET {{baseUrl}}/iot/product-thing-model/get?id=67
 tenant-id: {{adminTenentId}}
 Authorization: Bearer {{token}}
 

+ 84 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java

@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.iot.controller.admin.thingmodel;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
+import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IoT 产品物模型")
+@RestController
+@RequestMapping("/iot/thing-model")
+@Validated
+public class IotThingModelController {
+
+    @Resource
+    private IotThingModelService thingModelService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建产品物模型")
+    @PreAuthorize("@ss.hasPermission('iot:thing-model:create')")
+    public CommonResult<Long> createThingModel(@Valid @RequestBody IotThingModelSaveReqVO createReqVO) {
+        return success(thingModelService.createThingModel(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新产品物模型")
+    @PreAuthorize("@ss.hasPermission('iot:thing-model:update')")
+    public CommonResult<Boolean> updateThingModel(@Valid @RequestBody IotThingModelSaveReqVO updateReqVO) {
+        thingModelService.updateThingModel(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除产品物模型")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('iot:thing-model:delete')")
+    public CommonResult<Boolean> deleteThingModel(@RequestParam("id") Long id) {
+        thingModelService.deleteThingModel(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得产品物模型")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
+    public CommonResult<IotThingModelRespVO> getThingModel(@RequestParam("id") Long id) {
+        IotThingModelDO thingModel = thingModelService.getThingModel(id);
+        return success(IotThingModelConvert.INSTANCE.convert(thingModel));
+    }
+
+    @GetMapping("/list-by-product-id")
+    @Operation(summary = "获得产品物模型")
+    @Parameter(name = "productId", description = "产品ID", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
+    public CommonResult<List<IotThingModelRespVO>> getThingModelListByProductId(@RequestParam("productId") Long productId) {
+        List<IotThingModelDO> list = thingModelService.getThingModelListByProductId(productId);
+        return success(IotThingModelConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得产品物模型分页")
+    @PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
+    public CommonResult<PageResult<IotThingModelRespVO>> getThingModelPage(@Valid IotThingModelPageReqVO pageReqVO) {
+        PageResult<IotThingModelDO> pageResult = thingModelService.getProductThingModelPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotThingModelRespVO.class));
+    }
+
+}

+ 19 - 6
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelEvent.java

@@ -1,9 +1,15 @@
 package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
 
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceEventTypeEnum;
 import lombok.Data;
+
 import java.util.List;
 
+/**
+ * 物模型中的事件
+ *
+ * @author HUIHUI
+ */
 @Data
 public class ThingModelEvent {
 
@@ -16,17 +22,24 @@ public class ThingModelEvent {
      */
     private String name;
     /**
-     * 事件描述
+     * 是否是标准品类的必选事件
      */
-    private String description;
-
+    private Boolean required;
     /**
      * 事件类型
      *
-     * "info"、"alert"、"error"
+     * 枚举 {@link IotThingModelServiceEventTypeEnum}
      */
     private String type;
-    private List<ThingModelArgument> outputData;
+    /**
+     * 事件的输出参数
+     *
+     * 输出参数定义事件调用后返回的结果或反馈信息,用于确认操作结果或提供额外的信息。
+     */
+    private List<ThingModelParam> outputParams;
+    /**
+     * 标识设备需要执行的具体操作
+     */
     private String method;
 
 }

+ 50 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelParam.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
+
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelParamDirectionEnum;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * IOT 产品物模型中的参数
+ *
+ * @author HUIHUI
+ */
+@Data
+public class ThingModelParam {
+
+    /**
+     * 参数标识符
+     */
+    private String identifier;
+    /**
+     * 参数名称
+     */
+    private String name;
+    /**
+     * 用于区分输入或输出参数
+     *
+     * 枚举 {@link IotThingModelParamDirectionEnum}
+     */
+    private String direction;
+    /**
+     * 参数的序号。从 0 开始排序,且不能重复。
+     *
+     * TODO 考虑要不要序号,感觉是要的, 先留一手看看
+     */
+    private Integer paraOrder;
+    /**
+     * 参数值的数据类型,与 dataSpecs 的 dataType 保持一致
+     */
+    private String dataType;
+    /**
+     * 参数值的数据类型(dataType)为非列表型(int、float、double、text、date、array)的数据规范存储在 dataSpecs 中
+     */
+    private ThingModelDataSpecs dataSpecs;
+    /**
+     * 参数值的数据类型(dataType)为列表型(enum、bool、struct)的数据规范存储在 dataSpecsList 中
+     */
+    private List<ThingModelDataSpecs> dataSpecsList;
+
+}

+ 5 - 6
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelProperty.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
 
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
 import lombok.Data;
 
 import java.util.List;
@@ -24,13 +24,10 @@ public class ThingModelProperty {
      * 属性名称
      */
     private String name;
-    /**
-     * 属性描述
-     */
-    private String description;
     /**
      * 云端可以对该属性进行的操作类型
-     * 关联枚举 {@link IotProductThingModelAccessModeEnum}
+     *
+     * 枚举 {@link IotThingModelAccessModeEnum}
      */
     private String accessMode;
     /**
@@ -42,6 +39,8 @@ public class ThingModelProperty {
     private Boolean required;
     /**
      * 数据类型,与 dataSpecs 的 dataType 保持一致
+     *
+     * 枚举 {@link cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum}
      */
     private String dataType;
     /**

+ 25 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelService.java

@@ -1,9 +1,15 @@
 package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
 
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceCallTypeEnum;
 import lombok.Data;
+
 import java.util.List;
 
+/**
+ * 物模型中的服务
+ *
+ * @author HUIHUI
+ */
 @Data
 public class ThingModelService {
 
@@ -16,18 +22,30 @@ public class ThingModelService {
      */
     private String name;
     /**
-     * 服务描述
+     * 是否是标准品类的必选服务
      */
-    private String description;
-
+    private Boolean required;
     /**
      * 调用类型
      *
-     * "sync"、"async"
+     * 枚举 {@link IotThingModelServiceCallTypeEnum}
      */
     private String callType;
-    private List<ThingModelArgument> inputData;
-    private List<ThingModelArgument> outputData;
+    /**
+     * 服务的输入参数
+     *
+     * 输入参数定义服务调用时所需提供的信息,用于控制设备行为或执行特定任务
+     */
+    private List<ThingModelParam> inputParams;
+    /**
+     * 服务的输出参数
+     *
+     * 输出参数定义服务调用后返回的结果或反馈信息,用于确认操作结果或提供额外的信息。
+     */
+    private List<ThingModelParam> outputParams;
+    /**
+     * 标识设备需要执行的具体操作
+     */
     private String method;
 
 }

+ 0 - 26
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelArgument.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
-
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import lombok.Data;
-
-@Data
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class ThingModelArgument {
-
-    public static final String DIRECTION_INPUT = "input";
-    public static final String DIRECTION_OUTPUT = "output";
-
-    private String identifier;
-    private String name;
-    /**
-     * 物模型中的属性
-     */
-    private ThingModelProperty property;
-    /**
-     * 用于区分输入或输出参数,"input" 或 "output"
-     */
-    private String direction;
-    private String description;
-
-}

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelDateOrTextDataSpecs.java

@@ -20,7 +20,7 @@ public class ThingModelDateOrTextDataSpecs extends ThingModelDataSpecs {
      * 数据长度,单位为字节。取值不能超过 2048。
      * 当 dataType 为 text 时,需传入该参数。
      */
-    private Long length;
+    private Integer length;
     /**
      * 默认值,可选参数,用于存储默认值。
      */

+ 4 - 10
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelStructDataSpecs.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
 
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -25,20 +25,14 @@ public class ThingModelStructDataSpecs extends ThingModelDataSpecs {
      * 属性名称
      */
     private String name;
-    /**
-     * 属性描述
-     */
-    private String description;
     /**
      * 云端可以对该属性进行的操作类型
-     * 关联枚举 {@link IotProductThingModelAccessModeEnum}
+     *
+     * 枚举 {@link IotThingModelAccessModeEnum}
      */
     private String accessMode;
     /**
-     * 是否是标准品类的必选服务。
-     *
-     * - true:是
-     * - false:否
+     * 是否是标准品类的必选服务
      */
     private Boolean required;
     /**

+ 3 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotProductThingModelPageReqVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelPageReqVO.java

@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
@@ -13,7 +13,7 @@ import lombok.ToString;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class IotProductThingModelPageReqVO extends PageParam {
+public class IotThingModelPageReqVO extends PageParam {
 
     @Schema(description = "功能标识")
     private String identifier;
@@ -22,7 +22,7 @@ public class IotProductThingModelPageReqVO extends PageParam {
     private String name;
 
     @Schema(description = "功能类型", example = "1")
-    @InEnum(IotProductThingModelTypeEnum.class)
+    @InEnum(IotThingModelTypeEnum.class)
     private Integer type;
 
     @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED)

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotProductThingModelRespVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelRespVO.java

@@ -13,7 +13,7 @@ import java.time.LocalDateTime;
 @Schema(description = "管理后台 - IoT 产品物模型 Response VO")
 @Data
 @ExcelIgnoreUnannotated
-public class IotProductThingModelRespVO {
+public class IotThingModelRespVO {
 
     @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21816")
     @ExcelProperty("产品ID")

+ 3 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotProductThingModelSaveReqVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelSaveReqVO.java

@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
@@ -12,7 +12,7 @@ import lombok.Data;
 
 @Schema(description = "管理后台 - IoT 产品物模型新增/修改 Request VO")
 @Data
-public class IotProductThingModelSaveReqVO {
+public class IotThingModelSaveReqVO {
 
     @Schema(description = "编号", example = "1")
     private Long id;
@@ -38,7 +38,7 @@ public class IotProductThingModelSaveReqVO {
 
     @Schema(description = "功能类型", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotNull(message = "功能类型不能为空")
-    @InEnum(IotProductThingModelTypeEnum.class)
+    @InEnum(IotThingModelTypeEnum.class)
     private Integer type;
 
     @Schema(description = "属性", requiredMode = Schema.RequiredMode.REQUIRED)

+ 15 - 15
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/thingmodel/IotProductThingModelConvert.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/thingmodel/IotThingModelConvert.java

@@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.iot.convert.thingmodel;
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelRespVO;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Named;
@@ -16,44 +16,44 @@ import java.util.List;
 import java.util.Objects;
 
 @Mapper
-public interface IotProductThingModelConvert {
+public interface IotThingModelConvert {
 
-    IotProductThingModelConvert INSTANCE = Mappers.getMapper(IotProductThingModelConvert.class);
+    IotThingModelConvert INSTANCE = Mappers.getMapper(IotThingModelConvert.class);
 
     // 将 SaveReqVO 转换为 DO
     @Mapping(target = "property", expression = "java(convertToProperty(bean))")
     @Mapping(target = "event", expression = "java(convertToEvent(bean))")
     @Mapping(target = "service", expression = "java(convertToService(bean))")
-    IotProductThingModelDO convert(IotProductThingModelSaveReqVO bean);
+    IotThingModelDO convert(IotThingModelSaveReqVO bean);
 
     // 将 DO 转换为 RespVO
     @Mapping(target = "property", source = "property")
     @Mapping(target = "event", source = "event")
     @Mapping(target = "service", source = "service")
-    IotProductThingModelRespVO convert(IotProductThingModelDO bean);
+    IotThingModelRespVO convert(IotThingModelDO bean);
 
     // 批量转换
-    List<IotProductThingModelRespVO> convertList(List<IotProductThingModelDO> list);
+    List<IotThingModelRespVO> convertList(List<IotThingModelDO> list);
 
     @Named("convertToProperty")
-    default ThingModelProperty convertToProperty(IotProductThingModelSaveReqVO bean) {
-        if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
+    default ThingModelProperty convertToProperty(IotThingModelSaveReqVO bean) {
+        if (Objects.equals(bean.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
             return bean.getProperty();
         }
         return null;
     }
 
     @Named("convertToEvent")
-    default ThingModelEvent convertToEvent(IotProductThingModelSaveReqVO bean) {
-        if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.EVENT.getType())) {
+    default ThingModelEvent convertToEvent(IotThingModelSaveReqVO bean) {
+        if (Objects.equals(bean.getType(), IotThingModelTypeEnum.EVENT.getType())) {
             return bean.getEvent();
         }
         return null;
     }
 
     @Named("convertToService")
-    default ThingModelService convertToService(IotProductThingModelSaveReqVO bean) {
-        if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.SERVICE.getType())) {
+    default ThingModelService convertToService(IotThingModelSaveReqVO bean) {
+        if (Objects.equals(bean.getType(), IotThingModelTypeEnum.SERVICE.getType())) {
             return bean.getService();
         }
         return null;

+ 5 - 5
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDataDO.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.iot.dal.dataobject.device;
 
 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -30,7 +30,7 @@ public class IotDeviceDataDO {
     /**
      * 物模型编号
      * <p>
-     * 关联 {@link IotProductThingModelDO#getId()}
+     * 关联 {@link IotThingModelDO#getId()}
      */
     private Long thingModelId;
 
@@ -51,21 +51,21 @@ public class IotDeviceDataDO {
     /**
      * 属性标识符
      * <p>
-     * 关联 {@link IotProductThingModelDO#getIdentifier()}
+     * 关联 {@link IotThingModelDO#getIdentifier()}
      */
     private String identifier;
 
     /**
      * 属性名称
      * <p>
-     * 关联 {@link IotProductThingModelDO#getName()}
+     * 关联 {@link IotThingModelDO#getName()}
      */
     private String name;
 
     /**
      * 数据类型
      * <p>
-     * 关联 {@link IotProductThingModelDO#getProperty()#getDataType()}
+     * 关联 {@link IotThingModelDO#getProperty()#getDataType()}
      */
     private String dataType;
 

+ 12 - 8
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
+import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -26,11 +29,10 @@ public class PluginInfoDO extends BaseDO {
      */
     @TableId
     private Long id;
-    // TODO @haohao:这个是不是改成类似 key 之类的字段哈?
     /**
-     * 插件包 ID
+     * 插件包标识符
      */
-    private String pluginId;
+    private String pluginKey;
     /**
      * 插件名称
      */
@@ -41,22 +43,23 @@ public class PluginInfoDO extends BaseDO {
     private String description;
     /**
      * 部署方式
+     * <p>
+     * 枚举 {@link IotPluginDeployTypeEnum}
      */
-    // TODO @haohao:枚举
     private Integer deployType;
     /**
      * 插件包文件名
      */
-    // TODO @haohao:是不是叫 fileName 哈?避免后续有别的字段,类似 fileUrl?
-    private String file;
+    private String fileName;
     /**
      * 插件版本
      */
     private String version;
     /**
      * 插件类型
+     * <p>
+     * 枚举 {@link IotPluginTypeEnum}
      */
-    // TODO @haohao:枚举
     private Integer type;
     /**
      * 设备插件协议类型
@@ -64,8 +67,9 @@ public class PluginInfoDO extends BaseDO {
     private String protocol;
     /**
      * 状态
+     * <p>
+     * 枚举 {@link IotPluginStatusEnum}
      */
-    // TODO @haohao:枚举
     private Integer status;
     /**
      * 插件配置项描述信息

+ 4 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java

@@ -1,12 +1,13 @@
 package cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
-// TODO @haohao:一些必要的关联、枚举
+
 /**
  * IoT 插件实例 DO
  *
@@ -33,6 +34,8 @@ public class PluginInstanceDO extends BaseDO {
     private String mainId;
     /**
      * 插件id
+     * <p>
+     * 关联 {@link PluginInfoDO#getId()}
      */
     private Long pluginId;
     /**

+ 7 - 8
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/FieldParser.java

@@ -5,7 +5,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelR
 
 import java.util.HashMap;
 import java.util.List;
-import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
 /**
  * FieldParser 类用于解析和转换物模型字段到 TDengine 字段
@@ -15,6 +16,7 @@ public class FieldParser {
     /**
      * 物模型到td数据类型映射
      */
+    @Deprecated
     private static final HashMap<String, String> TYPE_MAPPING = new HashMap<>() {
         {
             put("INT", "INT");
@@ -52,10 +54,7 @@ public class FieldParser {
      * @return 字段列表
      */
     public static List<TdFieldDO> parse(ThingModelRespVO thingModel) {
-        // TODO @puhui999:是不是使用 convertList
-        return thingModel.getModel().getProperties().stream()
-                .map(FieldParser::parse)
-                .collect(Collectors.toList());
+        return convertList(thingModel.getModel().getProperties(), FieldParser::parse);
     }
 
     /**
@@ -65,18 +64,18 @@ public class FieldParser {
      * @return 转换后的 TDengine 字段对象列表
      */
     public static List<TdFieldDO> parse(List<List<Object>> rows) {
-        // TODO @puhui999:是不是使用 convertList
-        return rows.stream().map(row -> {
+        return convertList(rows, row -> {
             String type = row.get(1).toString().toUpperCase();
             // TODO @puhui999:"NCHAR" 最好枚举下
             int dataLength = "NCHAR".equals(type) ? Integer.parseInt(row.get(2).toString()) : -1;
             return new TdFieldDO(row.get(0).toString(), type, dataLength);
-        }).collect(Collectors.toList());
+        });
     }
 
     /**
      * 获取字段字义
      */
+    @Deprecated
     public static String getFieldDefine(TdFieldDO field) {
         return "`" + field.getFieldName() + "`" + " "
                 + (field.getDataLength() > 0 ? String.format("%s(%d)", field.getDataType(), field.getDataLength())

+ 1 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/SelectDO.java

@@ -2,13 +2,12 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
 
 import lombok.Data;
 
-import java.util.Set;
-
 // TODO @haohao:类似这个,其实可以参考 mybatis plus,querywrapper,搞个 TdEngineQueryWrapper。这样看起来会更好懂。
 /**
  * 查询DO
  */
 @Data
+@Deprecated
 public class SelectDO {
 
     // TODO @haoha:database 是个单词

+ 1 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/SelectVisualDO.java

@@ -6,6 +6,7 @@ import java.util.Map;
 
 // TODO @haohao:类似 SelectDO 的想法,只是它是返回。ps:貌似可以在 tdengine 里面,创建一个 query 包,放这种比较特殊的查询和结果对象。dataobject 更多还是实际存储的结构化的 do
 @Data
+@Deprecated
 public class SelectVisualDO {
 
     /**

+ 1 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TagsSelectDO.java

@@ -6,6 +6,7 @@ import lombok.Data;
  * tags查询DO
  */
 @Data
+@Deprecated
 public class TagsSelectDO {
 
     /**

+ 2 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdFieldDO.java

@@ -5,6 +5,7 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+// TODO 芋艿:看看是不是后续简化掉。
 /**
  * TD 引擎的字段
  */
@@ -12,6 +13,7 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
+@Deprecated
 public class TdFieldDO {
 
     /**

+ 1 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdResponse.java

@@ -12,6 +12,7 @@ import java.util.List;
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
+@Deprecated
 public class TdResponse {
 
     public static final int CODE_SUCCESS = 0;

+ 1 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdRestApi.java

@@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
  */
 @Slf4j
 @Service
+@Deprecated // TODO 芋艿:貌似没用到
 public class TdRestApi {
 
     @Value("${spring.datasource.dynamic.datasource.tdengine.url}")

+ 1 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdTableDO.java

@@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
 
 import java.util.List;
 
+@Deprecated
 /**
  * TD 引擎的数据库
  */

+ 6 - 6
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/IotProductThingModelDO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/IotThingModelDO.java

@@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelE
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,17 +19,17 @@ import lombok.NoArgsConstructor;
 /**
  * IoT 产品物模型功能 DO
  * <p>
- * 每个 {@link IotProductDO} 和 {@link IotProductThingModelDO} 是“一对多”的关系,它的每个属性、事件、服务都对应一条记录
+ * 每个 {@link IotProductDO} 和 {@link IotThingModelDO} 是“一对多”的关系,它的每个属性、事件、服务都对应一条记录
  *
  * @author 芋道源码
  */
-@TableName(value = "iot_product_thing_model", autoResultMap = true)
-@KeySequence("iot_product_thing_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName(value = "iot_thing_model", autoResultMap = true)
+@KeySequence("iot_thing_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class IotProductThingModelDO extends BaseDO {
+public class IotThingModelDO extends BaseDO {
 
     /**
      * 物模型功能编号
@@ -66,7 +66,7 @@ public class IotProductThingModelDO extends BaseDO {
     /**
      * 功能类型
      * <p>
-     * 枚举 {@link IotProductThingModelTypeEnum}
+     * 枚举 {@link IotThingModelTypeEnum}
      */
     private Integer type;
 

+ 3 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
@@ -47,7 +48,8 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
     }
 
     default IotDeviceDO selectByDeviceKey(String deviceKey) {
-        return selectOne(IotDeviceDO::getDeviceKey, deviceKey);
+        return selectOne(new LambdaQueryWrapper<IotDeviceDO>()
+                .apply("LOWER(device_key) = {0}", deviceKey.toLowerCase()));
     }
 
     default List<IotDeviceDO> selectList(Integer deviceType) {

+ 25 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.plugin;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.*;
+
+/**
+ * IoT 插件信息 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface PluginInfoMapper extends BaseMapperX<PluginInfoDO> {
+
+    default PageResult<PluginInfoDO> selectPage(PluginInfoPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<PluginInfoDO>()
+                .likeIfPresent(PluginInfoDO::getName, reqVO.getName())
+                .eqIfPresent(PluginInfoDO::getStatus, reqVO.getStatus())
+                .orderByDesc(PluginInfoDO::getId));
+    }
+
+}

+ 8 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininstance/PluginInstanceMapper.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java

@@ -1,11 +1,11 @@
-package cn.iocoder.yudao.module.iot.dal.mysql.plugininstance;
+package cn.iocoder.yudao.module.iot.dal.mysql.plugin;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInstancePageReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
 import org.apache.ibatis.annotations.Mapper;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
 
 /**
  * IoT 插件实例 Mapper
@@ -15,6 +15,12 @@ import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
 @Mapper
 public interface PluginInstanceMapper extends BaseMapperX<PluginInstanceDO> {
 
+    default PluginInstanceDO selectByMainIdAndPluginId(String mainId, Long pluginId) {
+        return selectOne(new LambdaQueryWrapperX<PluginInstanceDO>()
+                .eq(PluginInstanceDO::getMainId, mainId)
+                .eq(PluginInstanceDO::getPluginId, pluginId));
+    }
+
     default PageResult<PluginInstanceDO> selectPage(PluginInstancePageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<PluginInstanceDO>()
                 .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId())

+ 0 - 36
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininfo/PluginInfoMapper.java

@@ -1,36 +0,0 @@
-package cn.iocoder.yudao.module.iot.dal.mysql.plugininfo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
-import org.apache.ibatis.annotations.Mapper;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.*;
-
-/**
- * IoT 插件信息 Mapper
- *
- * @author 芋道源码
- */
-@Mapper
-public interface PluginInfoMapper extends BaseMapperX<PluginInfoDO> {
-
-    default PageResult<PluginInfoDO> selectPage(PluginInfoPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<PluginInfoDO>()
-                .eqIfPresent(PluginInfoDO::getPluginId, reqVO.getPluginId())
-                .likeIfPresent(PluginInfoDO::getName, reqVO.getName())
-                .eqIfPresent(PluginInfoDO::getDescription, reqVO.getDescription())
-                .eqIfPresent(PluginInfoDO::getDeployType, reqVO.getDeployType())
-                .eqIfPresent(PluginInfoDO::getFile, reqVO.getFile())
-                .eqIfPresent(PluginInfoDO::getVersion, reqVO.getVersion())
-                .eqIfPresent(PluginInfoDO::getType, reqVO.getType())
-                .eqIfPresent(PluginInfoDO::getProtocol, reqVO.getProtocol())
-                .eqIfPresent(PluginInfoDO::getStatus, reqVO.getStatus())
-                .eqIfPresent(PluginInfoDO::getConfigSchema, reqVO.getConfigSchema())
-                .eqIfPresent(PluginInfoDO::getConfig, reqVO.getConfig())
-                .eqIfPresent(PluginInfoDO::getScript, reqVO.getScript())
-                .betweenIfPresent(PluginInfoDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(PluginInfoDO::getId));
-    }
-
-}

+ 3 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -23,7 +24,8 @@ public interface IotProductMapper extends BaseMapperX<IotProductDO> {
     }
 
     default IotProductDO selectByProductKey(String productKey) {
-        return selectOne(IotProductDO::getProductKey, productKey);
+        return selectOne(new LambdaQueryWrapper<IotProductDO>()
+                .apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
     }
 
 }

+ 0 - 62
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thingmodel/IotProductThingModelMapper.java

@@ -1,62 +0,0 @@
-package cn.iocoder.yudao.module.iot.dal.mysql.thingmodel;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
-
-/**
- * IoT 产品物模型 Mapper
- *
- * @author 芋道源码
- */
-@Mapper
-public interface IotProductThingModelMapper extends BaseMapperX<IotProductThingModelDO> {
-
-    default PageResult<IotProductThingModelDO> selectPage(IotProductThingModelPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<IotProductThingModelDO>()
-                .eqIfPresent(IotProductThingModelDO::getIdentifier, reqVO.getIdentifier())
-                .likeIfPresent(IotProductThingModelDO::getName, reqVO.getName())
-                .eqIfPresent(IotProductThingModelDO::getType, reqVO.getType())
-                .eqIfPresent(IotProductThingModelDO::getProductId, reqVO.getProductId())
-                .notIn(IotProductThingModelDO::getIdentifier, "get", "set", "post")
-                .orderByDesc(IotProductThingModelDO::getId));
-    }
-
-    default IotProductThingModelDO selectByProductIdAndIdentifier(Long productId, String identifier) {
-        return selectOne(IotProductThingModelDO::getProductId, productId,
-                IotProductThingModelDO::getIdentifier, identifier);
-    }
-
-    default List<IotProductThingModelDO> selectListByProductId(Long productId) {
-        return selectList(IotProductThingModelDO::getProductId, productId);
-    }
-
-    default List<IotProductThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
-        return selectList(IotProductThingModelDO::getProductId, productId,
-                IotProductThingModelDO::getType, type);
-    }
-
-    default List<IotProductThingModelDO> selectListByProductIdAndIdentifiersAndTypes(Long productId,
-                                                                                     List<String> identifiers,
-                                                                                     List<Integer> types) {
-        return selectList(new LambdaQueryWrapperX<IotProductThingModelDO>()
-                .eq(IotProductThingModelDO::getProductId, productId)
-                .in(IotProductThingModelDO::getIdentifier, identifiers)
-                .in(IotProductThingModelDO::getType, types));
-    }
-
-    default IotProductThingModelDO selectByProductIdAndName(Long productId, String name) {
-        return selectOne(IotProductThingModelDO::getProductId, productId,
-                IotProductThingModelDO::getName, name);
-    }
-
-    default List<IotProductThingModelDO> selectListByProductKey(String productKey) {
-        return selectList(IotProductThingModelDO::getProductKey, productKey);
-    }
-
-}

+ 63 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thingmodel/IotThingModelMapper.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.thingmodel;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 产品物模型 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotThingModelMapper extends BaseMapperX<IotThingModelDO> {
+
+    default PageResult<IotThingModelDO> selectPage(IotThingModelPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotThingModelDO>()
+                .eqIfPresent(IotThingModelDO::getIdentifier, reqVO.getIdentifier())
+                .likeIfPresent(IotThingModelDO::getName, reqVO.getName())
+                .eqIfPresent(IotThingModelDO::getType, reqVO.getType())
+                .eqIfPresent(IotThingModelDO::getProductId, reqVO.getProductId())
+                // TODO @芋艿:看看要不要加枚举
+                .notIn(IotThingModelDO::getIdentifier, "get", "set", "post")
+                .orderByDesc(IotThingModelDO::getId));
+    }
+
+    default IotThingModelDO selectByProductIdAndIdentifier(Long productId, String identifier) {
+        return selectOne(IotThingModelDO::getProductId, productId,
+                IotThingModelDO::getIdentifier, identifier);
+    }
+
+    default List<IotThingModelDO> selectListByProductId(Long productId) {
+        return selectList(IotThingModelDO::getProductId, productId);
+    }
+
+    default List<IotThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
+        return selectList(IotThingModelDO::getProductId, productId,
+                IotThingModelDO::getType, type);
+    }
+
+    default List<IotThingModelDO> selectListByProductIdAndIdentifiersAndTypes(Long productId,
+                                                                              List<String> identifiers,
+                                                                              List<Integer> types) {
+        return selectList(new LambdaQueryWrapperX<IotThingModelDO>()
+                .eq(IotThingModelDO::getProductId, productId)
+                .in(IotThingModelDO::getIdentifier, identifiers)
+                .in(IotThingModelDO::getType, types));
+    }
+
+    default IotThingModelDO selectByProductIdAndName(Long productId, String name) {
+        return selectOne(IotThingModelDO::getProductId, productId,
+                IotThingModelDO::getName, name);
+    }
+
+    default List<IotThingModelDO> selectListByProductKey(String productKey) {
+        return selectList(IotThingModelDO::getProductKey, productKey);
+    }
+
+}

+ 77 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyDataMapper.java

@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.iot.dal.tdengine;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
+import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Mapper
+@TDengineDS
+@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
+public interface IotDevicePropertyDataMapper {
+
+    List<TDengineTableField> getProductPropertySTableFieldList(@Param("productKey") String productKey);
+
+    void createProductPropertySTable(@Param("productKey") String productKey,
+                                     @Param("fields") List<TDengineTableField> fields);
+
+    @SuppressWarnings("SimplifyStreamApiCallChains") // 保持 JDK8 兼容性
+    default void alterProductPropertySTable(String productKey,
+                                            List<TDengineTableField> oldFields,
+                                            List<TDengineTableField> newFields) {
+        oldFields.removeIf(field -> TDengineTableField.FIELD_TS.equals(field.getField())
+                || TDengineTableField.FIELD_DEVICE_KEY.equals(field.getField()));
+        List<TDengineTableField> addFields = newFields.stream().filter( // 新增的字段
+                        newField -> oldFields.stream().noneMatch(oldField -> oldField.getField().equals(newField.getField())))
+                .collect(Collectors.toList());
+        List<TDengineTableField> dropFields = oldFields.stream().filter( // 删除的字段
+                        oldField -> newFields.stream().noneMatch(n -> n.getField().equals(oldField.getField())))
+                .collect(Collectors.toList());
+        List<TDengineTableField> modifyTypeFields = new ArrayList<>(); // 变更类型的字段
+        List<TDengineTableField> modifyLengthFields = new ArrayList<>(); // 变更长度的字段
+        newFields.forEach(newField -> {
+            TDengineTableField oldField = CollUtil.findOne(oldFields, field -> field.getField().equals(newField.getField()));
+            if (oldField == null) {
+                return;
+            }
+            if (ObjectUtil.notEqual(oldField.getType(), newField.getType())) {
+                modifyTypeFields.add(newField);
+                return;
+            }
+            if (newField.getLength()!= null) {
+                if (newField.getLength() > oldField.getLength()) {
+                    modifyLengthFields.add(newField);
+                } else if (newField.getLength() < oldField.getLength()) {
+                    // 特殊:TDengine 长度修改时,只允许变长,所以此时认为是修改类型
+                    modifyTypeFields.add(newField);
+                }
+            }
+        });
+
+        // 执行
+        addFields.forEach(field -> alterProductPropertySTableAddField(productKey, field));
+        dropFields.forEach(field -> alterProductPropertySTableDropField(productKey, field));
+        modifyLengthFields.forEach(field -> alterProductPropertySTableModifyField(productKey, field));
+        modifyTypeFields.forEach(field -> {
+            alterProductPropertySTableDropField(productKey, field);
+            alterProductPropertySTableAddField(productKey, field);
+        });
+    }
+
+    void alterProductPropertySTableAddField(@Param("productKey") String productKey,
+                                            @Param("field") TDengineTableField field);
+
+    void alterProductPropertySTableModifyField(@Param("productKey") String productKey,
+                                               @Param("field") TDengineTableField field);
+
+    void alterProductPropertySTableDropField(@Param("productKey") String productKey,
+                                             @Param("field") TDengineTableField field);
+
+}

+ 0 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineDDLMapper.java

@@ -96,7 +96,6 @@ public interface TdEngineDDLMapper {
     @TenantIgnore
     void modifyColumnWidthForSuperTable(TdTableDO superTable);
 
-
     /**
      * 修改超级表 - 为超级表添加标签
      *

+ 2 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.iot.emq.service;
 
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
+import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.eclipse.paho.client.mqttv3.MqttClient;
@@ -21,7 +20,7 @@ import org.springframework.stereotype.Service;
 public class EmqxServiceImpl implements EmqxService {
 
     @Resource
-    private IotDeviceDataService iotDeviceDataService;
+    private IotDevicePropertyDataService iotDeviceDataService;
 
     // TODO 多线程处理消息
     @Override

+ 0 - 34
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java

@@ -1,34 +0,0 @@
-package cn.iocoder.yudao.module.iot.framework.plugin;
-
-import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
-import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.Resource;
-
-@Slf4j
-@Configuration
-public class ServiceRegistryConfiguration {
-
-    @Resource
-    private DeviceDataApi deviceDataApi;
-
-    @PostConstruct
-    public void init() {
-        // 将主程序中的 DeviceDataApi 实例注册到 ServiceRegistry
-        ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi);
-        log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]");
-    }
-
-    /**
-     * 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成
-     */
-    @Bean("serviceRegistryInitializedMarker")
-    public Object serviceRegistryInitializedMarker() {
-        // 返回任意对象即可,这里返回null都可以,但最好返回个实际对象
-        return new Object();
-    }
-}

+ 0 - 17
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java

@@ -1,17 +0,0 @@
-package cn.iocoder.yudao.module.iot.framework.plugin;
-
-import org.pf4j.spring.SpringPluginManager;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.DependsOn;
-
-@Configuration
-public class SpringConfiguration {
-
-    @Bean
-    @DependsOn("serviceRegistryInitializedMarker")
-    public SpringPluginManager pluginManager() {
-        return new SpringPluginManager();
-    }
-
-}

+ 39 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.framework.plugin;
+
+import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
+import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
+import cn.iocoder.yudao.module.iot.framework.plugin.listener.CustomPluginStateListener;
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.spring.SpringPluginManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
+
+import javax.annotation.Resource;
+
+@Slf4j
+@Configuration
+public class UnifiedConfiguration {
+
+    private static final String SERVICE_REGISTRY_INITIALIZED_MARKER = "serviceRegistryInitializedMarker";
+
+    @Resource
+    private DeviceDataApi deviceDataApi;
+
+    @Bean(SERVICE_REGISTRY_INITIALIZED_MARKER)
+    public Object serviceRegistryInitializedMarker() {
+        ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi);
+        log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]");
+        return new Object();
+    }
+
+    @Bean
+    @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER)
+    public SpringPluginManager pluginManager() {
+        log.info("[init][实例化 SpringPluginManager]");
+        SpringPluginManager springPluginManager = new SpringPluginManager();
+        springPluginManager.addPluginStateListener(new CustomPluginStateListener());
+        return springPluginManager;
+    }
+
+}

+ 24 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/listener/CustomPluginStateListener.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.iot.framework.plugin.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.PluginStateEvent;
+import org.pf4j.PluginStateListener;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class CustomPluginStateListener implements PluginStateListener {
+
+    @Override
+    public void pluginStateChanged(PluginStateEvent event) {
+        // 1. 获取插件ID
+        String pluginId = event.getPlugin().getPluginId();
+        // 2. 获取插件旧状态
+        String oldState = event.getOldState().toString();
+        // 3. 获取插件新状态
+        String newState = event.getPluginState().toString();
+        // 4. 打印日志信息
+        log.info("插件的状态 '{}' 已更改为 '{}' 至 '{}'", pluginId, oldState, newState);
+    }
+
+}

+ 63 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/TDengineTableField.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.iot.framework.tdengine.core;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * TDEngine 表字段
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TDengineTableField {
+
+    /**
+     * 字段名 - TDengine 默认 ts 字段,默认会被 TDengine 创建
+     */
+    public static final String FIELD_TS = "ts";
+
+    /**
+     * 字段名 - 我们系统定义的 device_key 字段,非 TDengine 默认字段
+     */
+    public static final String FIELD_DEVICE_KEY = "device_key";
+
+    public static final String TYPE_TINYINT = "TINYINT";
+    public static final String TYPE_INT = "INT";
+    public static final String TYPE_FLOAT = "FLOAT";
+    public static final String TYPE_DOUBLE = "DOUBLE";
+    public static final String TYPE_BOOL = "BOOL";
+    public static final String TYPE_NCHAR = "NCHAR";
+    public static final String TYPE_TIMESTAMP = "TIMESTAMP";
+
+    /**
+     * 注释 - TAG 字段
+     */
+    public static final String NOTE_TAG = "TAG";
+
+    /**
+     * 字段名
+     */
+    private String field;
+
+    /**
+     * 字段类型
+     */
+    private String type;
+
+    /**
+     * 字段长度
+     */
+    private Integer length;
+
+    /**
+     * 注释
+     */
+    private String note;
+
+    public TDengineTableField(String field, String type) {
+        this.field = field;
+        this.type = type;
+    }
+
+}

+ 17 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/annotation/TDengineDS.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+
+import java.lang.annotation.*;
+
+/**
+ * TDEngine 数据源
+ *
+ * @author 芋道源码
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@DS("tdengine")
+public @interface TDengineDS {
+}

+ 4 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * iot 模块的 tdengine 拓展封装
+ */
+package cn.iocoder.yudao.module.iot.framework.tdengine;

+ 29 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.iot.job.plugin;
+
+
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 插件实例 Job
+ *
+ * @author 芋道源码
+ */
+@Component
+public class PluginInstancesJob {
+
+    @Resource
+    private PluginInstanceService pluginInstanceService;
+
+    @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
+    public void updatePluginInstances() {
+        TenantUtils.executeIgnore(() -> {
+            pluginInstanceService.updatePluginInstances();
+        });
+    }
+}

+ 3 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/simulatesend/SimulateSendConsumer.java

@@ -1,8 +1,10 @@
 package cn.iocoder.yudao.module.iot.mq.consumer.simulatesend;
 
 /**
+ * TODO @alwayssuper:记得实现,还有类注释哈
+ *
  * @author alwayssuper
- * @date 2024/12/20 8:04
+ * @since 2024/12/20 8:04
  */
 public class SimulateSendConsumer {
 }

+ 11 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java

@@ -9,11 +9,18 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * IoT 设备数据 Service 接口
+ * IoT 设备【属性】数据 Service 接口
  *
  * @author 芋道源码
  */
-public interface IotDeviceDataService {
+public interface IotDevicePropertyDataService {
+
+    /**
+     * 定义设备属性数据的结构
+     *
+     * @param productId 产品编号
+     */
+    void defineDevicePropertyData(Long productId);
 
     /**
      * 保存设备数据
@@ -21,7 +28,7 @@ public interface IotDeviceDataService {
      * @param productKey 产品 key
      * @param deviceName 设备名称
      * @param message    消息
-     *                   <p>JSON 格式,参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">...</a>
+     *                   <p>参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">JSON 格式</a>
      */
     void saveDeviceData(String productKey, String deviceName, String message);
 
@@ -40,4 +47,5 @@ public interface IotDeviceDataService {
      * @return 设备属性历史数据
      */
     PageResult<Map<String, Object>> getHistoryDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO);
+
 }

+ 86 - 8
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java

@@ -1,21 +1,29 @@
 package cn.iocoder.yudao.module.iot.service.device;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONObject;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper;
 import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
 import cn.iocoder.yudao.module.iot.enums.IotConstants;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
+import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
-import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
+import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
 import jakarta.annotation.Resource;
 import jakarta.validation.Valid;
 import lombok.extern.slf4j.Slf4j;
@@ -28,11 +36,32 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
 
-@Slf4j
+/**
+ * IoT 设备【属性】数据 Service 实现类
+ *
+ * @author 芋道源码
+ */
 @Service
-public class IotDeviceDataServiceImpl implements IotDeviceDataService {
+@Slf4j
+public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataService {
+
+    /**
+     * 物模型的数据类型,与 TDengine 数据类型的映射关系
+     */
+    private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
+            .put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)
+            .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
+            .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
+            .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
+            .put( IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
+            .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR)
+            .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
+            .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
+            .put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
+            .build();
 
     @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
     private String url;
@@ -42,13 +71,62 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
     @Resource
     private IotThingModelMessageService thingModelMessageService;
     @Resource
-    private IotProductThingModelService thingModelService;
+    private IotThingModelService thingModelService;
+    @Resource
+    private IotProductService productService;
+
     @Resource
     private TdEngineDMLMapper tdEngineDMLMapper;
 
     @Resource
     private DeviceDataRedisDAO deviceDataRedisDAO;
 
+    @Resource
+    private IotDevicePropertyDataMapper devicePropertyDataMapper;
+
+    @Override
+    public void defineDevicePropertyData(Long productId) {
+        // 1.1 查询产品和物模型
+        IotProductDO product = productService.validateProductExists(productId);
+        List<IotThingModelDO> thingModels = filterList(thingModelService.getThingModelListByProductId(productId),
+                thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
+        // 1.2 解析 DB 里的字段
+        List<TDengineTableField> oldFields = new ArrayList<>();
+        try {
+            oldFields.addAll(devicePropertyDataMapper.getProductPropertySTableFieldList(product.getProductKey()));
+        } catch (Exception e) {
+            if (!e.getMessage().contains("Table does not exist")) {
+                throw e;
+            }
+        }
+
+        // 2.1 情况一:如果是新增的时候,需要创建表
+        List<TDengineTableField> newFields = buildTableFieldList(thingModels);
+        if (CollUtil.isEmpty(oldFields)) {
+            if (CollUtil.isEmpty(newFields)) {
+                log.info("[defineDevicePropertyData][productId({}) 没有需要定义的属性]", productId);
+                return;
+            }
+            newFields.add(0, new TDengineTableField(TDengineTableField.FIELD_TS, TDengineTableField.TYPE_TIMESTAMP));
+            devicePropertyDataMapper.createProductPropertySTable(product.getProductKey(), newFields);
+            return;
+        }
+        // 2.2 情况二:如果是修改的时候,需要更新表
+        devicePropertyDataMapper.alterProductPropertySTable(product.getProductKey(), oldFields, newFields);
+    }
+
+    private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
+        return convertList(thingModels, thingModel -> {
+            TDengineTableField field = new TDengineTableField(
+                    StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
+                    TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
+            if (thingModel.getProperty().getDataType().equals(IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
+                field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());
+            }
+            return field;
+        });
+    }
+
     @Override
     public void saveDeviceData(String productKey, String deviceName, String message) {
         // 1. 根据产品 key 和设备名称,获得设备信息
@@ -75,8 +153,8 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
         // 1. 获取设备信息
         IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
         // 2. 获取设备属性最新数据
-        List<IotProductThingModelDO> thingModelList = thingModelService.getProductThingModelListByProductKey(device.getProductKey());
-        thingModelList = filterList(thingModelList, thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType()
+        List<IotThingModelDO> thingModelList = thingModelService.getProductThingModelListByProductKey(device.getProductKey());
+        thingModelList = filterList(thingModelList, thingModel -> IotThingModelTypeEnum.PROPERTY.getType()
                 .equals(thingModel.getType()));
 
         // 3. 过滤标识符和属性名称

+ 6 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java

@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
@@ -60,9 +61,11 @@ public class IotDeviceServiceImpl implements IotDeviceService {
             throw exception(PRODUCT_NOT_EXISTS);
         }
         // 1.2 校验设备标识是否唯一
-        if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
-            throw exception(DEVICE_KEY_EXISTS);
-        }
+        TenantUtils.executeIgnore(() -> {
+            if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
+                throw exception(PRODUCT_KEY_EXISTS);
+            }
+        });
         // 1.3 校验设备名称在同一产品下是否唯一
         if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) {
             throw exception(DEVICE_NAME_EXISTS);

+ 13 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java

@@ -1,12 +1,14 @@
-package cn.iocoder.yudao.module.iot.service.plugininfo;
+package cn.iocoder.yudao.module.iot.service.plugin;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
 import jakarta.validation.Valid;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.util.List;
+
 /**
  * IoT 插件信息 Service 接口
  *
@@ -58,7 +60,7 @@ public interface PluginInfoService {
      * @param id   插件id
      * @param file 文件
      */
-    void uploadJar(Long id, MultipartFile file);
+    void uploadFile(Long id, MultipartFile file);
 
     /**
      * 更新插件的状态
@@ -67,4 +69,11 @@ public interface PluginInfoService {
      * @param status 状态
      */
     void updatePluginStatus(Long id, Integer status);
+
+    /**
+     * 获得插件信息列表
+     *
+     * @return 插件信息列表
+     */
+    List<PluginInfoDO> getPluginInfoList();
 }

+ 270 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java

@@ -0,0 +1,270 @@
+package cn.iocoder.yudao.module.iot.service.plugin;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper;
+import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.PluginDescriptor;
+import org.pf4j.PluginState;
+import org.pf4j.PluginWrapper;
+import org.pf4j.spring.SpringPluginManager;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
+
+/**
+ * IoT 插件信息 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+@Slf4j
+public class PluginInfoServiceImpl implements PluginInfoService {
+
+    @Resource
+    private PluginInfoMapper pluginInfoMapper;
+    @Resource
+    private SpringPluginManager pluginManager;
+
+    @Value("${pf4j.pluginsDir}")
+    private String pluginsDir;
+
+    @Override
+    public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
+        // 插入
+        PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class);
+        pluginInfoMapper.insert(pluginInfo);
+        // 返回
+        return pluginInfo.getId();
+    }
+
+    @Override
+    public void updatePluginInfo(PluginInfoSaveReqVO updateReqVO) {
+        // 校验存在
+        validatePluginInfoExists(updateReqVO.getId());
+        // 更新
+        PluginInfoDO updateObj = BeanUtils.toBean(updateReqVO, PluginInfoDO.class);
+        pluginInfoMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deletePluginInfo(Long id) {
+        // 校验存在
+        PluginInfoDO pluginInfoDO = validatePluginInfoExists(id);
+
+        // 停止插件
+        if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
+            throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING);
+        }
+
+        // 卸载插件
+        PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey());
+        if (plugin != null) {
+            // 查询插件是否是启动状态
+            if (plugin.getPluginState().equals(PluginState.STARTED)) {
+                // 停止插件
+                pluginManager.stopPlugin(plugin.getPluginId());
+            }
+            // 卸载插件
+            pluginManager.unloadPlugin(plugin.getPluginId());
+        }
+
+        // 删除
+        pluginInfoMapper.deleteById(id);
+        // 删除插件文件
+        Executors.newSingleThreadExecutor().submit(() -> {
+            try {
+                TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
+                File file = new File(pluginsDir, pluginInfoDO.getFileName());
+                if (file.exists() && !file.delete()) {
+                    log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
+                }
+            } catch (InterruptedException e) {
+                log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
+            }
+        });
+
+    }
+
+    private PluginInfoDO validatePluginInfoExists(Long id) {
+        PluginInfoDO pluginInfo = pluginInfoMapper.selectById(id);
+        if (pluginInfo == null) {
+            throw exception(PLUGIN_INFO_NOT_EXISTS);
+        }
+        return pluginInfo;
+    }
+
+    @Override
+    public PluginInfoDO getPluginInfo(Long id) {
+        return pluginInfoMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<PluginInfoDO> getPluginInfoPage(PluginInfoPageReqVO pageReqVO) {
+        return pluginInfoMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void uploadFile(Long id, MultipartFile file) {
+        // 1. 校验插件信息是否存在
+        PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
+
+        // 2. 获取插件标识
+        String pluginKey = pluginInfoDo.getPluginKey();
+
+        // 3. 停止并卸载旧的插件
+        stopAndUnloadPlugin(pluginKey);
+
+        // 4. 上传新的插件文件
+        String pluginKeyNew = uploadAndLoadNewPlugin(file);
+
+        // 5. 更新插件启用状态文件
+        updatePluginStatusFile(pluginKeyNew, false);
+
+        // 6. 更新插件信息
+        updatePluginInfo(pluginInfoDo, pluginKeyNew, file);
+    }
+
+    // 停止并卸载旧的插件
+    private void stopAndUnloadPlugin(String pluginKey) {
+        PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
+        if (plugin != null) {
+            if (plugin.getPluginState().equals(PluginState.STARTED)) {
+                pluginManager.stopPlugin(pluginKey); // 停止插件
+            }
+            pluginManager.unloadPlugin(pluginKey); // 卸载插件
+        }
+    }
+
+    // 上传并加载新的插件文件
+    private String uploadAndLoadNewPlugin(MultipartFile file) {
+        Path pluginsPath = Paths.get(pluginsDir);
+        try {
+            if (!Files.exists(pluginsPath)) {
+                Files.createDirectories(pluginsPath); // 创建插件目录
+            }
+            String filename = file.getOriginalFilename();
+            if (filename != null) {
+                Path jarPath = pluginsPath.resolve(filename);
+                Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
+                return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
+            } else {
+                throw exception(PLUGIN_INSTALL_FAILED);
+            }
+        } catch (Exception e) {
+            throw exception(PLUGIN_INSTALL_FAILED);
+        }
+    }
+
+    // 更新插件状态文件
+    private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) {
+        Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt");
+        Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt");
+        Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath;
+        Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath;
+
+        try {
+            PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew);
+            if (pluginWrapper == null) {
+                throw exception(PLUGIN_INSTALL_FAILED);
+            }
+            String pluginInfo = pluginKeyNew + "@" + pluginWrapper.getDescriptor().getVersion();
+            List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath)
+                    : new ArrayList<>();
+            List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath)
+                    : new ArrayList<>();
+
+            if (!targetLines.contains(pluginInfo)) {
+                targetLines.add(pluginInfo);
+                Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE,
+                        StandardOpenOption.TRUNCATE_EXISTING);
+            }
+
+            if (oppositeLines.contains(pluginInfo)) {
+                oppositeLines.remove(pluginInfo);
+                Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE,
+                        StandardOpenOption.TRUNCATE_EXISTING);
+            }
+        } catch (IOException e) {
+            throw exception(PLUGIN_INSTALL_FAILED);
+        }
+    }
+
+    // 更新插件信息
+    private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) {
+        pluginInfoDo.setPluginKey(pluginKeyNew);
+        pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
+        pluginInfoDo.setFileName(file.getOriginalFilename());
+        pluginInfoDo.setScript("");
+
+        PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor();
+        pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription());
+        pluginInfoDo.setVersion(pluginDescriptor.getVersion());
+        pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
+        pluginInfoMapper.updateById(pluginInfoDo);
+    }
+
+    @Override
+    public void updatePluginStatus(Long id, Integer status) {
+        // 1. 校验插件信息是否存在
+        PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
+
+        // 2. 校验插件状态是否有效
+        if (!IotPluginStatusEnum.contains(status)) {
+            throw exception(PLUGIN_STATUS_INVALID);
+        }
+
+        // 3. 获取插件标识和插件实例
+        String pluginKey = pluginInfoDo.getPluginKey();
+        PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
+
+        // 4. 根据状态更新插件
+        if (plugin != null) {
+            // 4.1 如果目标状态是运行且插件未启动,则启动插件
+            if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
+                    && plugin.getPluginState() != PluginState.STARTED) {
+                pluginManager.startPlugin(pluginKey);
+                updatePluginStatusFile(pluginKey, true); // 更新插件状态文件为启用
+            }
+            // 4.2 如果目标状态是停止且插件已启动,则停止插件
+            else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
+                    && plugin.getPluginState() == PluginState.STARTED) {
+                pluginManager.stopPlugin(pluginKey);
+                updatePluginStatusFile(pluginKey, false); // 更新插件状态文件为禁用
+            }
+        } else {
+            // 5. 插件不存在且状态为停止,抛出异常
+            if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
+                throw exception(PLUGIN_STATUS_INVALID);
+            }
+        }
+
+        // 6. 更新数据库中的插件状态
+        pluginInfoDo.setStatus(status);
+        pluginInfoMapper.updateById(pluginInfoDo);
+    }
+
+    @Override
+    public List<PluginInfoDO> getPluginInfoList() {
+        return pluginInfoMapper.selectList(null);
+    }
+
+}

+ 15 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.iot.service.plugin;
+
+/**
+ * IoT 插件实例 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface PluginInstanceService {
+
+    /**
+     * 更新IoT 插件实例
+     */
+    void updatePluginInstances();
+
+}

+ 100 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java

@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.iot.service.plugin;
+
+import cn.hutool.core.net.NetUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.PluginWrapper;
+import org.pf4j.spring.SpringPluginManager;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+/**
+ * IoT 插件实例 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+@Slf4j
+public class PluginInstanceServiceImpl implements PluginInstanceService {
+
+    /**
+     * 主程序id
+     */
+    public static final String MAIN_ID = IdUtil.fastSimpleUUID();
+
+    @Resource
+    private PluginInfoService pluginInfoService;
+    @Resource
+    private PluginInstanceMapper pluginInstanceMapper;
+    @Resource
+    private SpringPluginManager pluginManager;
+
+    @Value("${server.port:48080}")
+    private int port;
+
+
+    @Override
+    public void updatePluginInstances() {
+        // 1. 查询 pf4j 插件列表
+        List<PluginWrapper> plugins = pluginManager.getPlugins();
+
+        // 2. 查询插件信息列表
+        List<PluginInfoDO> pluginInfos = pluginInfoService.getPluginInfoList();
+
+        // 动态获取主程序的 IP 和端口
+        String mainIp = getLocalIpAddress();
+
+        // 3. 遍历插件列表,并保存为插件实例
+        for (PluginWrapper plugin : plugins) {
+            String pluginKey = plugin.getPluginId();
+            PluginInfoDO pluginInfo = pluginInfos.stream()
+                    .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey))
+                    .findFirst()
+                    .orElse(null);
+
+            // 4. 如果插件信息不存在,则跳过
+            if (pluginInfo == null) {
+                continue;
+            }
+
+            // 5. 查询插件实例
+            PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId());
+
+            // 6. 如果插件实例不存在,则创建
+            if (pluginInstance == null) {
+                pluginInstance = new PluginInstanceDO();
+                pluginInstance.setPluginId(pluginInfo.getId());
+                pluginInstance.setMainId(MAIN_ID);
+                pluginInstance.setIp(mainIp);
+                pluginInstance.setPort(port);
+                pluginInstance.setHeartbeatAt(System.currentTimeMillis());
+                pluginInstanceMapper.insert(pluginInstance);
+            } else {
+                // 7. 如果插件实例存在,则更新
+                pluginInstance.setHeartbeatAt(System.currentTimeMillis());
+                pluginInstanceMapper.updateById(pluginInstance);
+            }
+        }
+    }
+
+    private String getLocalIpAddress() {
+        try {
+            List<String> ipList = NetUtil.localIpv4s().stream()
+                    .filter(ip -> !ip.startsWith("0.0") && !ip.startsWith("127.") && !ip.startsWith("169.254") && !ip.startsWith("255.255.255.255"))
+                    .toList();
+            return ipList.isEmpty() ? "127.0.0.1" : ipList.get(0);
+        } catch (Exception e) {
+            log.error("获取本地IP地址失败", e);
+            return "127.0.0.1"; // 默认值
+        }
+    }
+
+}

+ 0 - 241
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java

@@ -1,241 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.plugininfo;
-
-import cn.hutool.core.io.IoUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.infra.api.file.FileApi;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
-import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper;
-import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
-import jakarta.annotation.PostConstruct;
-import jakarta.annotation.Resource;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.pf4j.PluginDescriptor;
-import org.pf4j.PluginState;
-import org.pf4j.PluginWrapper;
-import org.pf4j.spring.SpringPluginManager;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.multipart.MultipartFile;
-
-import java.nio.file.Path;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
-
-/**
- * IoT 插件信息 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-@Slf4j
-public class PluginInfoServiceImpl implements PluginInfoService {
-
-    @Resource
-    private PluginInfoMapper pluginInfoMapper;
-
-    @Resource
-    private SpringPluginManager pluginManager;
-
-    @Resource
-    private FileApi fileApi;
-
-    @Value("${pf4j.pluginsDir}")
-    private String pluginsDir;
-
-    @Override
-    public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
-        // 插入
-        PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class);
-        pluginInfoMapper.insert(pluginInfo);
-        // 返回
-        return pluginInfo.getId();
-    }
-
-    @Override
-    public void updatePluginInfo(PluginInfoSaveReqVO updateReqVO) {
-        // 校验存在
-        validatePluginInfoExists(updateReqVO.getId());
-        // 更新
-        PluginInfoDO updateObj = BeanUtils.toBean(updateReqVO, PluginInfoDO.class);
-        pluginInfoMapper.updateById(updateObj);
-    }
-
-    @Override
-    public void deletePluginInfo(Long id) {
-        // 校验存在
-        PluginInfoDO pluginInfoDO = validatePluginInfoExists(id);
-
-        // 停止插件
-        if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
-            throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING);
-        }
-
-        // 卸载插件
-        PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginId());
-        if (plugin != null) {
-            // 查询插件是否是启动状态
-            if (plugin.getPluginState().equals(PluginState.STARTED)) {
-                // 停止插件
-                pluginManager.stopPlugin(plugin.getPluginId());
-            }
-            // 卸载插件
-            pluginManager.unloadPlugin(plugin.getPluginId());
-        }
-
-        // 删除
-        pluginInfoMapper.deleteById(id);
-    }
-
-    private PluginInfoDO validatePluginInfoExists(Long id) {
-        PluginInfoDO pluginInfo = pluginInfoMapper.selectById(id);
-        if (pluginInfo == null) {
-            throw exception(PLUGIN_INFO_NOT_EXISTS);
-        }
-        return pluginInfo;
-    }
-
-    @Override
-    public PluginInfoDO getPluginInfo(Long id) {
-        return pluginInfoMapper.selectById(id);
-    }
-
-    @Override
-    public PageResult<PluginInfoDO> getPluginInfoPage(PluginInfoPageReqVO pageReqVO) {
-        return pluginInfoMapper.selectPage(pageReqVO);
-    }
-
-    @Override
-    public void uploadJar(Long id, MultipartFile file) {
-        // 1. 校验存在
-        PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
-
-        // 2. 判断文件名称与插件 ID 是否匹配
-        String pluginId = pluginInfoDo.getPluginId();
-
-        // 3. 停止卸载旧的插件
-        // 3.1. 获取插件信息
-        PluginWrapper plugin = pluginManager.getPlugin(pluginId);
-        if (plugin != null) {
-            // 3.2. 如果插件状态是启动的,停止插件
-            if (plugin.getPluginState().equals(PluginState.STARTED)) {
-                pluginManager.stopPlugin(pluginId);
-            }
-            // 3.3. 卸载插件
-            pluginManager.unloadPlugin(pluginId);
-        }
-
-        // 4. 上传插件
-        String pluginIdNew;
-        try {
-            String path = fileApi.createFile(pluginsDir, IoUtil.readBytes(file.getInputStream()));
-            Path pluginPath = Path.of(path);
-            pluginIdNew = pluginManager.loadPlugin(pluginPath);
-        } catch (Exception e) {
-            throw exception(PLUGIN_INSTALL_FAILED);
-        }
-
-        PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew);
-        if (pluginWrapper == null) {
-            throw exception(PLUGIN_INSTALL_FAILED);
-        }
-
-        // 5. 读取配置文件和脚本
-        String configJson = "";
-        String script = "";
-        try (JarFile jarFile = new JarFile(pluginWrapper.getPluginPath().toFile())) {
-            // 5.1 获取config文件在jar包中的路径
-            String configFile = "classes/config.json";
-            JarEntry configEntry = jarFile.getJarEntry(configFile);
-
-            if (configEntry != null) {
-                // 5.2 读取配置文件
-                configJson = IoUtil.readUtf8(jarFile.getInputStream(configEntry));
-                log.info("configJson:{}", configJson);
-            }
-
-            // 5.3 读取script.js脚本
-            String scriptFile = "classes/script.js";
-            JarEntry scriptEntity = jarFile.getJarEntry(scriptFile);
-            if (scriptEntity != null) {
-                // 5.4 读取脚本文件
-                script = IoUtil.readUtf8(jarFile.getInputStream(scriptEntity));
-                log.info("script:{}", script);
-            }
-        } catch (Exception e) {
-            throw exception(PLUGIN_INSTALL_FAILED);
-        }
-
-        pluginInfoDo.setPluginId(pluginIdNew);
-        pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
-        pluginInfoDo.setFile(file.getOriginalFilename());
-        pluginInfoDo.setConfigSchema(configJson);
-        pluginInfoDo.setScript(script);
-
-        PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
-        pluginInfoDo.setVersion(pluginDescriptor.getVersion());
-        pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
-        pluginInfoMapper.updateById(pluginInfoDo);
-    }
-
-    @Override
-    public void updatePluginStatus(Long id, Integer status) {
-        // 1. 校验存在
-        PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
-
-        // 插件状态无效
-        if (!IotPluginStatusEnum.contains(status)) {
-            throw exception(PLUGIN_STATUS_INVALID);
-        }
-
-        String pluginId = pluginInfoDo.getPluginId();
-        PluginWrapper plugin = pluginManager.getPlugin(pluginId);
-        if (plugin != null) {
-            if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) && plugin.getPluginState() != PluginState.STARTED) {
-                // 启动插件
-                pluginManager.startPlugin(pluginId);
-            } else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) && plugin.getPluginState() == PluginState.STARTED) {
-                // 停止插件
-                pluginManager.stopPlugin(pluginId);
-            }
-        } else {
-            // 已经停止,未获取到插件
-            if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
-                throw exception(PLUGIN_STATUS_INVALID);
-            }
-        }
-        pluginInfoDo.setStatus(status);
-        pluginInfoMapper.updateById(pluginInfoDo);
-    }
-
-//    @PostConstruct
-//    public void init() {
-//        Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS);
-//    }
-//
-//    @SneakyThrows
-//    private void startPlugins() {
-//        for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) {
-//            if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
-//                continue;
-//            }
-//            log.info("start plugin:{}", pluginInfoDO.getPluginId());
-//            try {
-//                pluginManager.startPlugin(pluginInfoDO.getPluginId());
-//            } catch (Exception e) {
-//                log.error("start plugin error", e);
-//            }
-//        }
-//    }
-
-}

+ 0 - 54
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceService.java

@@ -1,54 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.plugininstance;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstancePageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
-import jakarta.validation.Valid;
-
-/**
- * IoT 插件实例 Service 接口
- *
- * @author 芋道源码
- */
-public interface PluginInstanceService {
-
-    /**
-     * 创建IoT 插件实例
-     *
-     * @param createReqVO 创建信息
-     * @return 编号
-     */
-    Long createPluginInstance(@Valid PluginInstanceSaveReqVO createReqVO);
-
-    /**
-     * 更新IoT 插件实例
-     *
-     * @param updateReqVO 更新信息
-     */
-    void updatePluginInstance(@Valid PluginInstanceSaveReqVO updateReqVO);
-
-    /**
-     * 删除IoT 插件实例
-     *
-     * @param id 编号
-     */
-    void deletePluginInstance(Long id);
-
-    /**
-     * 获得IoT 插件实例
-     *
-     * @param id 编号
-     * @return IoT 插件实例
-     */
-    PluginInstanceDO getPluginInstance(Long id);
-
-    /**
-     * 获得IoT 插件实例分页
-     *
-     * @param pageReqVO 分页查询
-     * @return IoT 插件实例分页
-     */
-    PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO);
-
-}

+ 0 - 70
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java

@@ -1,70 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.plugininstance;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstancePageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
-import cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper;
-import jakarta.annotation.Resource;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS;
-
-/**
- * IoT 插件实例 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-public class PluginInstanceServiceImpl implements PluginInstanceService {
-
-    @Resource
-    private PluginInstanceMapper pluginInstanceMapper;
-
-    @Override
-    public Long createPluginInstance(PluginInstanceSaveReqVO createReqVO) {
-        // 插入
-        PluginInstanceDO pluginInstance = BeanUtils.toBean(createReqVO, PluginInstanceDO.class);
-        pluginInstanceMapper.insert(pluginInstance);
-        // 返回
-        return pluginInstance.getId();
-    }
-
-    @Override
-    public void updatePluginInstance(PluginInstanceSaveReqVO updateReqVO) {
-        // 校验存在
-        validatePluginInstanceExists(updateReqVO.getId());
-        // 更新
-        PluginInstanceDO updateObj = BeanUtils.toBean(updateReqVO, PluginInstanceDO.class);
-        pluginInstanceMapper.updateById(updateObj);
-    }
-
-    @Override
-    public void deletePluginInstance(Long id) {
-        // 校验存在
-        validatePluginInstanceExists(id);
-        // 删除
-        pluginInstanceMapper.deleteById(id);
-    }
-
-    private void validatePluginInstanceExists(Long id) {
-        if (pluginInstanceMapper.selectById(id) == null) {
-            throw exception(PLUGIN_INSTANCE_NOT_EXISTS);
-        }
-    }
-
-    @Override
-    public PluginInstanceDO getPluginInstance(Long id) {
-        return pluginInstanceMapper.selectById(id);
-    }
-
-    @Override
-    public PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO) {
-        return pluginInstanceMapper.selectPage(pageReqVO);
-    }
-
-}

+ 15 - 10
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java

@@ -2,13 +2,14 @@ package cn.iocoder.yudao.module.iot.service.product;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
 import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
+import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
 import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
-import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
@@ -33,19 +34,23 @@ public class IotProductServiceImpl implements IotProductService {
     @Resource
     private IotProductMapper productMapper;
 
-    @Resource
-    @Lazy // 延迟加载,解决循环依赖
-    private IotProductThingModelService thingModelFunctionService;
     @Resource
     @Lazy  // 延迟加载,解决循环依赖
     private IotThingModelMessageService thingModelMessageService;
+    @Resource
+    @Lazy  // 延迟加载,解决循环依赖
+    private IotDevicePropertyDataService devicePropertyDataService;
 
     @Override
     public Long createProduct(IotProductSaveReqVO createReqVO) {
-        // 1. 生成 ProductKey
-        if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
-            throw exception(PRODUCT_KEY_EXISTS);
-        }
+        // 1. 校验 ProductKey
+        TenantUtils.executeIgnore(() -> {
+            // 为什么忽略租户?避免多个租户之间,productKey 重复,导致 TDengine 设备属性表重复
+            if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
+                throw exception(PRODUCT_KEY_EXISTS);
+            }
+        });
+
         // 2. 插入
         IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class)
                 .setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus());
@@ -119,8 +124,8 @@ public class IotProductServiceImpl implements IotProductService {
         // 3. 产品是发布状态
         if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) {
             // 3.1 创建产品超级表数据模型
-            thingModelFunctionService.createSuperTableDataModel(id);
-            // 3.2 创建物模型日志超级表数据模型
+            devicePropertyDataService.defineDevicePropertyData(id);
+            // 3.2 创建物模型日志超级表数据模型 TODO 待定:message 要不要分;
             thingModelMessageService.createSuperTable(id);
         }
         productMapper.updateById(updateObj);

+ 0 - 19
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotSuperTableService.java

@@ -1,19 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.tdengine;
-
-
-import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
-
-import java.util.List;
-
-/**
- * IoT 超级表服务,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
- */
-public interface IotSuperTableService {
-
-    /**
-     * 创建超级表数据模型
-     */
-    void createSuperTableDataModel(IotProductDO product, List<IotProductThingModelDO> thingModelList);
-
-}

+ 0 - 260
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotSuperTableServiceImpl.java

@@ -1,260 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.tdengine;
-
-import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelRespVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
-import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
-import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
-import jakarta.annotation.Resource;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-/**
- * IoT 超级表服务实现类,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
- */
-@Service
-@Slf4j
-public class IotSuperTableServiceImpl implements IotSuperTableService {
-
-    @Resource
-    private TdEngineDDLMapper tdEngineDDLMapper;
-
-    @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
-    private String url;
-
-    @Override
-    public void createSuperTableDataModel(IotProductDO product, List<IotProductThingModelDO> thingModelList) {
-        ThingModelRespVO thingModel = buildThingModel(product, thingModelList);
-
-        if (thingModel.getModel() == null || CollUtil.isEmpty(thingModel.getModel().getProperties())) {
-            log.warn("物模型属性列表为空,不创建超级表");
-            return;
-        }
-
-        String superTableName = getSuperTableName(product.getDeviceType(), product.getProductKey());
-        String databaseName = getDatabaseName();
-
-        List<Map<String, Object>> results = tdEngineDDLMapper.showSuperTables(new TdTableDO(databaseName, superTableName));
-        int tableExists = results == null || results.isEmpty() ? 0 : results.size();
-        if (tableExists > 0) {
-            updateSuperTable(thingModel, product.getDeviceType());
-        } else {
-            createSuperTable(thingModel, product.getDeviceType());
-        }
-    }
-
-    /**
-     * 创建超级表
-     */
-    private void createSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
-        // 解析物模型,获取字段列表
-        List<TdFieldDO> schemaFields = new ArrayList<>();
-        schemaFields.add(TdFieldDO.builder()
-                .fieldName("time")
-                .dataType("TIMESTAMP")
-                .build());
-        schemaFields.addAll(FieldParser.parse(thingModel));
-
-        // 设置超级表的标签
-        List<TdFieldDO> tagsFields = List.of(
-                TdFieldDO.builder().fieldName("product_key").dataType("NCHAR").dataLength(64).build(),
-                TdFieldDO.builder().fieldName("device_key").dataType("NCHAR").dataLength(64).build(),
-                TdFieldDO.builder().fieldName("device_name").dataType("NCHAR").dataLength(64).build(),
-                TdFieldDO.builder().fieldName("device_type").dataType("INT").build()
-        );
-
-        // 获取超级表的名称和数据库名称
-        String superTableName = getSuperTableName(deviceType, thingModel.getProductKey());
-        String databaseName = getDatabaseName();
-
-        // 创建超级表
-        tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
-    }
-
-    /**
-     * 更新超级表
-     */
-    private void updateSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
-        String superTableName = getSuperTableName(deviceType, thingModel.getProductKey());
-        try {
-            List<TdFieldDO> oldFields = getTableFields(superTableName);
-            List<TdFieldDO> newFields = FieldParser.parse(thingModel);
-
-            updateTableFields(superTableName, oldFields, newFields);
-        } catch (Exception e) {
-            log.error("更新物模型超级表失败: {}", e.getMessage(), e);
-        }
-    }
-
-    /**
-     * 获取表的字段信息
-     */
-    private List<TdFieldDO> getTableFields(String tableName) {
-        List<Map<String, Object>> tableDescription = tdEngineDDLMapper.describeSuperTable(new TdTableDO(getDatabaseName(), tableName));
-        if (CollUtil.isEmpty(tableDescription)) {
-            return Collections.emptyList();
-        }
-
-        return tableDescription.stream()
-                .filter(map -> !"TAG".equals(map.get("note")))
-                .filter(map -> !"time".equals(map.get("field")))
-                .map(map -> TdFieldDO.builder()
-                        .fieldName((String) map.get("field"))
-                        .dataType((String) map.get("type"))
-                        .dataLength((Integer) map.get("length"))
-                        .build())
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * 更新表的字段,包括新增、修改和删除字段
-     */
-    private void updateTableFields(String tableName, List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
-        String databaseName = getDatabaseName();
-
-        // 获取新增、修改、删除的字段
-        List<TdFieldDO> addFields = getAddFields(oldFields, newFields);
-        List<TdFieldDO> modifyFields = getModifyFields(oldFields, newFields);
-        List<TdFieldDO> dropFields = getDropFields(oldFields, newFields);
-
-        // 添加新增字段
-        if (CollUtil.isNotEmpty(addFields)) {
-            for (TdFieldDO addField : addFields) {
-                tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
-                        .dataBaseName(databaseName)
-                        .superTableName(tableName)
-                        .column(addField)
-                        .build());
-            }
-        }
-        // 删除旧字段
-        if (CollUtil.isNotEmpty(dropFields)) {
-            for (TdFieldDO dropField : dropFields) {
-                tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
-                        .dataBaseName(databaseName)
-                        .superTableName(tableName)
-                        .column(dropField)
-                        .build());
-            }
-        }
-        // 修改字段(先删除再添加)
-        if (CollUtil.isNotEmpty(modifyFields)) {
-            for (TdFieldDO modifyField : modifyFields) {
-                tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
-                        .dataBaseName(databaseName)
-                        .superTableName(tableName)
-                        .column(modifyField)
-                        .build());
-                tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
-                        .dataBaseName(databaseName)
-                        .superTableName(tableName)
-                        .column(modifyField)
-                        .build());
-            }
-        }
-    }
-
-    /**
-     * 获取需要新增的字段
-     */
-    private List<TdFieldDO> getAddFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
-        Set<String> oldFieldNames = oldFields.stream()
-                .map(TdFieldDO::getFieldName)
-                .collect(Collectors.toSet());
-        return newFields.stream()
-                .filter(f -> !oldFieldNames.contains(f.getFieldName()))
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * 获取需要修改的字段
-     */
-    private List<TdFieldDO> getModifyFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
-        Map<String, TdFieldDO> oldFieldMap = oldFields.stream()
-                .collect(Collectors.toMap(TdFieldDO::getFieldName, f -> f));
-
-        return newFields.stream()
-                .filter(f -> {
-                    TdFieldDO oldField = oldFieldMap.get(f.getFieldName());
-                    return oldField != null && (
-                            !oldField.getDataType().equals(f.getDataType()) ||
-                                    !Objects.equals(oldField.getDataLength(), f.getDataLength())
-                    );
-                })
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * 获取需要删除的字段
-     */
-    private List<TdFieldDO> getDropFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
-        Set<String> newFieldNames = newFields.stream()
-                .map(TdFieldDO::getFieldName)
-                .collect(Collectors.toSet());
-        return oldFields.stream()
-                .filter(f -> !"time".equals(f.getFieldName()))
-                .filter(f -> !newFieldNames.contains(f.getFieldName()))
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * 构建物模型
-     */
-    private ThingModelRespVO buildThingModel(IotProductDO product, List<IotProductThingModelDO> thingModelList) {
-        ThingModelRespVO thingModel = new ThingModelRespVO();
-        thingModel.setId(product.getId());
-        thingModel.setProductKey(product.getProductKey());
-
-        List<ThingModelProperty> properties = thingModelList.stream()
-                .filter(item -> IotProductThingModelTypeEnum.PROPERTY.equals(
-                        IotProductThingModelTypeEnum.valueOfType(item.getType())))
-                .map(this::buildThingModelProperty)
-                .collect(Collectors.toList());
-
-        ThingModelRespVO.Model model = new ThingModelRespVO.Model();
-        model.setProperties(properties);
-        thingModel.setModel(model);
-
-        return thingModel;
-    }
-
-    /**
-     * 构建物模型属性
-     */
-    private ThingModelProperty buildThingModelProperty(IotProductThingModelDO thingModel) {
-        ThingModelProperty property = BeanUtil.copyProperties(thingModel, ThingModelProperty.class);
-        property.setDataType(thingModel.getProperty().getDataType());
-        return property;
-    }
-
-    /**
-     * 获取数据库名称
-     */
-    private String getDatabaseName() {
-        int index = url.lastIndexOf("/");
-        return index != -1 ? url.substring(index + 1) : url;
-    }
-
-    /**
-     * 获取超级表名称
-     */
-    private String getSuperTableName(Integer deviceType, String productKey) {
-        String prefix = switch (deviceType) {
-            case 1 -> "gateway_sub_";
-            case 2 -> "gateway_";
-            default -> "device_";
-        };
-        return (prefix + productKey).toLowerCase();
-    }
-
-}

+ 20 - 24
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java

@@ -11,16 +11,16 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
 import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
 import cn.iocoder.yudao.module.iot.enums.IotConstants;
 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
-import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
+import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import cn.iocoder.yudao.module.iot.util.IotTdDatabaseUtils;
 import jakarta.annotation.Resource;
@@ -52,7 +52,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
     private String url;
 
     @Resource
-    private IotProductThingModelService iotProductThingModelService;
+    private IotThingModelService iotThingModelService;
     @Resource
     private IotDeviceService iotDeviceService;
     @Resource
@@ -83,7 +83,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
 
         // 2. 获取设备属性并进行物模型校验,过滤非物模型属性
         Map<String, Object> params = thingModelMessage.dataToMap();
-        List<IotProductThingModelDO> thingModelList = getValidThingModelList(thingModelMessage.getProductKey());
+        List<IotThingModelDO> thingModelList = getValidThingModelList(thingModelMessage.getProductKey());
         if (thingModelList.isEmpty()) {
             return;
         }
@@ -102,9 +102,9 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
                 .build());
     }
 
-    private List<IotProductThingModelDO> getValidThingModelList(String productKey) {
-        return filterList(iotProductThingModelService.getProductThingModelListByProductKey(productKey),
-                thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
+    private List<IotThingModelDO> getValidThingModelList(String productKey) {
+        return filterList(iotThingModelService.getProductThingModelListByProductKey(productKey),
+                thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
     }
 
     @Override
@@ -134,21 +134,17 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
         tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
     }
 
-    private List<IotProductThingModelDO> getValidFunctionList(String productKey) {
-        // TODO @puhui999:使用 convertList 会好点哈
-        return iotProductThingModelService
-                .getProductThingModelListByProductKey(productKey)
-                .stream()
-                .filter(function -> IotProductThingModelTypeEnum.PROPERTY.getType().equals(function.getType()))
-                .toList();
+    private List<IotThingModelDO> getValidFunctionList(String productKey) {
+        return filterList(iotThingModelService.getProductThingModelListByProductKey(productKey),
+                thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
     }
 
-    private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotProductThingModelDO> thingModelList, IotDeviceDO device, Long time) {
+    private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotThingModelDO> thingModelList, IotDeviceDO device, Long time) {
         // 1. 获取属性标识符集合
-        Set<String> propertyIdentifiers = convertSet(thingModelList, IotProductThingModelDO::getIdentifier);
+        Set<String> propertyIdentifiers = convertSet(thingModelList, IotThingModelDO::getIdentifier);
 
         // 2. 构建属性标识符和属性的映射
-        Map<String, IotProductThingModelDO> thingModelMap = convertMap(thingModelList, IotProductThingModelDO::getIdentifier);
+        Map<String, IotThingModelDO> thingModelMap = convertMap(thingModelList, IotThingModelDO::getIdentifier);
 
         // 3. 过滤并收集有效的属性字段
         List<TdFieldDO> schemaFieldValues = new ArrayList<>();
@@ -168,21 +164,21 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
      * 缓存设备属性
      *
      * @param device                 设备信息
-     * @param iotProductThingModelDO 物模型属性
+     * @param iotThingModelDO 物模型属性
      * @param val                    属性值
      * @param time                   时间
      */
-    private void setDeviceDataCache(IotDeviceDO device, IotProductThingModelDO iotProductThingModelDO, Object val, Long time) {
+    private void setDeviceDataCache(IotDeviceDO device, IotThingModelDO iotThingModelDO, Object val, Long time) {
         IotDeviceDataDO deviceData = IotDeviceDataDO.builder()
                 .productKey(device.getProductKey())
                 .deviceName(device.getDeviceName())
-                .identifier(iotProductThingModelDO.getIdentifier())
+                .identifier(iotThingModelDO.getIdentifier())
                 .value(val != null ? val.toString() : null)
                 .updateTime(DateUtil.toLocalDateTime(new Date(time)))
                 .deviceId(device.getId())
-                .thingModelId(iotProductThingModelDO.getId())
-                .name(iotProductThingModelDO.getName())
-                .dataType(iotProductThingModelDO.getProperty().getDataType())
+                .thingModelId(iotThingModelDO.getId())
+                .name(iotThingModelDO.getName())
+                .dataType(iotThingModelDO.getProperty().getDataType())
                 .build();
         deviceDataRedisDAO.set(deviceData);
     }

+ 0 - 442
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotProductThingModelServiceImpl.java

@@ -1,442 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.thingmodel;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArrayDataSpecs;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
-import cn.iocoder.yudao.module.iot.convert.thingmodel.IotProductThingModelConvert;
-import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
-import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotProductThingModelMapper;
-import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
-import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
-import cn.iocoder.yudao.module.iot.service.product.IotProductService;
-import cn.iocoder.yudao.module.iot.service.tdengine.IotSuperTableService;
-import jakarta.annotation.Resource;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.validation.annotation.Validated;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
-import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
-
-/**
- * IoT 产品物模型 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-@Slf4j
-public class IotProductThingModelServiceImpl implements IotProductThingModelService {
-
-    @Resource
-    private IotProductThingModelMapper productThingModelMapper;
-
-    @Resource
-    private IotProductService productService;
-    @Resource
-    private IotSuperTableService dbStructureDataService;
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Long createProductThingModel(IotProductThingModelSaveReqVO createReqVO) {
-        // 1. 校验功能标识符在同一产品下是否唯一
-        validateIdentifierUnique(createReqVO.getProductId(), createReqVO.getIdentifier());
-
-        // 2. 功能名称在同一产品下是否唯一
-        validateNameUnique(createReqVO.getProductId(), createReqVO.getName());
-
-        // 3. 系统保留字段,不能用于标识符定义
-        validateNotDefaultEventAndService(createReqVO.getIdentifier());
-
-        // 4. 校验产品状态,发布状态下,不允许新增功能
-        validateProductStatus(createReqVO.getProductId());
-
-        // 5. 插入数据库
-        IotProductThingModelDO thingModel = IotProductThingModelConvert.INSTANCE.convert(createReqVO);
-        productThingModelMapper.insert(thingModel);
-
-        // 6. 如果创建的是属性,需要更新默认的事件和服务
-        if (Objects.equals(createReqVO.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
-            createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
-        }
-        return thingModel.getId();
-    }
-
-    private void validateProductStatus(Long createReqVO) {
-        IotProductDO product = productService.getProduct(createReqVO);
-        if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
-            throw exception(PRODUCT_STATUS_NOT_ALLOW_THING_MODEL);
-        }
-    }
-
-    private void validateNotDefaultEventAndService(String identifier) {
-        // set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义
-        if (CollUtil.containsAny(Arrays.asList("set", "get", "post", "property", "event", "time", "value"), Collections.singletonList(identifier))) {
-            throw exception(THING_MODEL_IDENTIFIER_INVALID);
-        }
-    }
-
-    private void validateNameUnique(Long productId, String name) {
-        IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndName(productId, name);
-        if (thingModel != null) {
-            throw exception(THING_MODEL_NAME_EXISTS);
-        }
-    }
-
-    private void validateIdentifierUnique(Long productId, String identifier) {
-        IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
-        if (thingModel != null) {
-            throw exception(THING_MODEL_IDENTIFIER_EXISTS);
-        }
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateProductThingModel(IotProductThingModelSaveReqVO updateReqVO) {
-        // 1. 校验功能是否存在
-        validateProductThingModelMapperExists(updateReqVO.getId());
-
-        // 2. 校验功能标识符是否唯一
-        validateIdentifierUniqueForUpdate(updateReqVO.getId(), updateReqVO.getProductId(), updateReqVO.getIdentifier());
-
-        // 3. 校验产品状态,发布状态下,不允许操作功能
-        validateProductStatus(updateReqVO.getProductId());
-
-        // 4. 更新数据库
-        IotProductThingModelDO thingModel = IotProductThingModelConvert.INSTANCE.convert(updateReqVO);
-        productThingModelMapper.updateById(thingModel);
-
-        // 5. 如果更新的是属性,需要更新默认的事件和服务
-        if (Objects.equals(updateReqVO.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
-            createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
-        }
-    }
-
-    private void validateIdentifierUniqueForUpdate(Long id, Long productId, String identifier) {
-        IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
-        if (thingModel != null && ObjectUtil.notEqual(thingModel.getId(), id)) {
-            throw exception(THING_MODEL_IDENTIFIER_EXISTS);
-        }
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void deleteProductThingModel(Long id) {
-        // 1. 校验功能是否存在
-        IotProductThingModelDO thingModel = productThingModelMapper.selectById(id);
-        if (thingModel == null) {
-            throw exception(THING_MODEL_NOT_EXISTS);
-        }
-
-        // 3. 校验产品状态,发布状态下,不允许操作功能
-        validateProductStatus(thingModel.getProductId());
-
-        // 2. 删除功能
-        productThingModelMapper.deleteById(id);
-
-        // 3. 如果删除的是属性,需要更新默认的事件和服务
-        if (Objects.equals(thingModel.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
-            createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
-        }
-    }
-
-    /**
-     * 校验功能是否存在
-     *
-     * @param id 功能编号
-     */
-    private void validateProductThingModelMapperExists(Long id) {
-        if (productThingModelMapper.selectById(id) == null) {
-            throw exception(THING_MODEL_NOT_EXISTS);
-        }
-    }
-
-    @Override
-    public IotProductThingModelDO getProductThingModel(Long id) {
-        return productThingModelMapper.selectById(id);
-    }
-
-    @Override
-    public List<IotProductThingModelDO> getProductThingModelListByProductId(Long productId) {
-        return productThingModelMapper.selectListByProductId(productId);
-    }
-
-    @Override
-    public PageResult<IotProductThingModelDO> getProductThingModelPage(IotProductThingModelPageReqVO pageReqVO) {
-        return productThingModelMapper.selectPage(pageReqVO);
-    }
-
-    @Override
-    public void createSuperTableDataModel(Long productId) {
-        // 1. 查询产品
-        IotProductDO product = productService.getProduct(productId);
-
-        // 2. 查询产品的物模型功能列表
-        List<IotProductThingModelDO> thingModelList = productThingModelMapper.selectListByProductId(productId);
-
-        // 3. 生成 TDengine 的数据模型
-        dbStructureDataService.createSuperTableDataModel(product, thingModelList);
-    }
-
-    @Override
-    public List<IotProductThingModelDO> getProductThingModelListByProductKey(String productKey) {
-        return productThingModelMapper.selectListByProductKey(productKey);
-    }
-
-    /**
-     * 创建默认的事件和服务
-     */
-    public void createDefaultEventsAndServices(Long productId, String productKey) {
-        // 1. 获取当前属性列表
-        List<IotProductThingModelDO> propertyList = productThingModelMapper
-                .selectListByProductIdAndType(productId, IotProductThingModelTypeEnum.PROPERTY.getType());
-
-        // 2. 生成新的事件和服务列表
-        List<IotProductThingModelDO> newThingModelList = new ArrayList<>();
-        // 生成属性上报事件
-        ThingModelEvent propertyPostEvent = generatePropertyPostEvent(propertyList);
-        if (propertyPostEvent != null) {
-            IotProductThingModelDO eventThingModel = buildEventThingModelDO(productId, productKey, propertyPostEvent);
-            newThingModelList.add(eventThingModel);
-        }
-        // 生成属性设置服务
-        ThingModelService propertySetService = generatePropertySetService(propertyList);
-        if (propertySetService != null) {
-            IotProductThingModelDO setServiceThingModel = buildServiceThingModelDO(productId, productKey, propertySetService);
-            newThingModelList.add(setServiceThingModel);
-        }
-        // 生成属性获取服务
-        ThingModelService propertyGetService = generatePropertyGetService(propertyList);
-        if (propertyGetService != null) {
-            IotProductThingModelDO getServiceThingModel = buildServiceThingModelDO(productId, productKey, propertyGetService);
-            newThingModelList.add(getServiceThingModel);
-        }
-
-        // 3. 获取数据库中的默认的旧事件和服务列表
-        List<IotProductThingModelDO> oldThingModelList = productThingModelMapper.selectListByProductIdAndIdentifiersAndTypes(
-                productId,
-                Arrays.asList("post", "set", "get"),
-                Arrays.asList(IotProductThingModelTypeEnum.EVENT.getType(), IotProductThingModelTypeEnum.SERVICE.getType())
-        );
-
-        // 3.1 使用 diffList 方法比较新旧列表
-        List<List<IotProductThingModelDO>> diffResult = diffList(oldThingModelList, newThingModelList,
-                // 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。
-                (oldFunc, newFunc) -> Objects.equals(oldFunc.getIdentifier(), newFunc.getIdentifier())
-                        && Objects.equals(oldFunc.getType(), newFunc.getType()));
-        List<IotProductThingModelDO> createList = diffResult.get(0); // 需要新增的
-        List<IotProductThingModelDO> updateList = diffResult.get(1); // 需要更新的
-        List<IotProductThingModelDO> deleteList = diffResult.get(2); // 需要删除的
-
-        // 3.2 批量执行数据库操作
-        // 新增数据库中的新事件和服务列表
-        if (CollUtil.isNotEmpty(createList)) {
-            productThingModelMapper.insertBatch(createList);
-        }
-        // 更新数据库中的事件和服务列表
-        if (CollUtil.isNotEmpty(updateList)) {
-            // 首先,为每个需要更新的对象设置其对应的 ID
-            updateList.forEach(updateFunc -> {
-                IotProductThingModelDO oldFunc = findThingModelByIdentifierAndType(
-                        oldThingModelList, updateFunc.getIdentifier(), updateFunc.getType());
-                if (oldFunc != null) {
-                    updateFunc.setId(oldFunc.getId());
-                }
-            });
-            // 过滤掉没有设置 ID 的对象
-            List<IotProductThingModelDO> validUpdateList = updateList.stream()
-                    .filter(func -> func.getId() != null)
-                    .collect(Collectors.toList());
-            // 执行批量更新
-            if (CollUtil.isNotEmpty(validUpdateList)) {
-                productThingModelMapper.updateBatch(validUpdateList);
-            }
-        }
-
-        // 删除数据库中的旧事件和服务列表
-        if (CollUtil.isNotEmpty(deleteList)) {
-            Set<Long> idsToDelete = CollectionUtils.convertSet(deleteList, IotProductThingModelDO::getId);
-            productThingModelMapper.deleteByIds(idsToDelete);
-        }
-    }
-
-    /**
-     * 根据标识符和类型查找功能对象
-     */
-    private IotProductThingModelDO findThingModelByIdentifierAndType(List<IotProductThingModelDO> thingModelList,
-                                                                     String identifier, Integer type) {
-        return CollUtil.findOne(thingModelList, func ->
-                Objects.equals(func.getIdentifier(), identifier) && Objects.equals(func.getType(), type));
-    }
-
-    /**
-     * 构建事件功能对象
-     */
-    private IotProductThingModelDO buildEventThingModelDO(Long productId, String productKey, ThingModelEvent event) {
-        return new IotProductThingModelDO()
-                .setProductId(productId)
-                .setProductKey(productKey)
-                .setIdentifier(event.getIdentifier())
-                .setName(event.getName())
-                .setDescription(event.getDescription())
-                .setType(IotProductThingModelTypeEnum.EVENT.getType())
-                .setEvent(event);
-    }
-
-    /**
-     * 构建服务功能对象
-     */
-    private IotProductThingModelDO buildServiceThingModelDO(Long productId, String productKey, ThingModelService service) {
-        return new IotProductThingModelDO()
-                .setProductId(productId)
-                .setProductKey(productKey)
-                .setIdentifier(service.getIdentifier())
-                .setName(service.getName())
-                .setDescription(service.getDescription())
-                .setType(IotProductThingModelTypeEnum.SERVICE.getType())
-                .setService(service);
-    }
-
-    /**
-     * 生成属性上报事件
-     */
-    private ThingModelEvent generatePropertyPostEvent(List<IotProductThingModelDO> propertyList) {
-        if (CollUtil.isEmpty(propertyList)) {
-            return null;
-        }
-
-        ThingModelEvent event = new ThingModelEvent()
-                .setIdentifier("post")
-                .setName("属性上报")
-                .setType("info")
-                .setDescription("属性上报事件")
-                .setMethod("thing.event.property.post");
-
-        // 将属性列表转换为事件的输出参数
-        List<ThingModelArgument> outputData = new ArrayList<>();
-        for (IotProductThingModelDO thingModel : propertyList) {
-            ThingModelArgument arg = new ThingModelArgument()
-                    .setIdentifier(thingModel.getIdentifier())
-                    .setName(thingModel.getName())
-                    .setProperty(thingModel.getProperty())
-                    .setDescription(thingModel.getDescription())
-                    .setDirection("output"); // 设置为输出参数
-            outputData.add(arg);
-        }
-        event.setOutputData(outputData);
-        return event;
-    }
-
-    /**
-     * 生成属性设置服务
-     */
-    private ThingModelService generatePropertySetService(List<IotProductThingModelDO> propertyList) {
-        if (propertyList == null || propertyList.isEmpty()) {
-            return null;
-        }
-
-        List<ThingModelArgument> inputData = new ArrayList<>();
-        for (IotProductThingModelDO thingModel : propertyList) {
-            ThingModelProperty property = thingModel.getProperty();
-            if (IotProductThingModelAccessModeEnum.READ_WRITE.getMode().equals(property.getAccessMode())) {
-                ThingModelArgument arg = new ThingModelArgument()
-                        .setIdentifier(property.getIdentifier())
-                        .setName(property.getName())
-                        .setProperty(property)
-                        .setDescription(property.getDescription())
-                        .setDirection("input"); // 设置为输入参数
-                inputData.add(arg);
-            }
-        }
-        if (inputData.isEmpty()) {
-            // 如果没有可写属性,不生成属性设置服务
-            return null;
-        }
-
-        // 属性设置服务一般不需要输出参数
-        return new ThingModelService()
-                .setIdentifier("set")
-                .setName("属性设置")
-                .setCallType("async")
-                .setDescription("属性设置服务")
-                .setMethod("thing.service.property.set")
-                .setInputData(inputData)
-                // 属性设置服务一般不需要输出参数
-                .setOutputData(new ArrayList<>());
-    }
-
-    /**
-     * 生成属性获取服务
-     */
-    private ThingModelService generatePropertyGetService(List<IotProductThingModelDO> propertyList) {
-        if (propertyList == null || propertyList.isEmpty()) {
-            return null;
-        }
-
-        List<ThingModelArgument> outputData = new ArrayList<>();
-        for (IotProductThingModelDO thingModelDO : propertyList) {
-            ThingModelProperty property = thingModelDO.getProperty();
-            if (ObjectUtils.equalsAny(property.getAccessMode(),
-                    IotProductThingModelAccessModeEnum.READ_ONLY.getMode(), IotProductThingModelAccessModeEnum.READ_WRITE.getMode())) {
-                ThingModelArgument arg = new ThingModelArgument()
-                        .setIdentifier(property.getIdentifier())
-                        .setName(property.getName())
-                        .setProperty(property)
-                        .setDescription(property.getDescription())
-                        .setDirection("output"); // 设置为输出参数
-                outputData.add(arg);
-            }
-        }
-        if (outputData.isEmpty()) {
-            // 如果没有可读属性,不生成属性获取服务
-            return null;
-        }
-
-        ThingModelService service = new ThingModelService()
-                .setIdentifier("get")
-                .setName("属性获取")
-                .setCallType("async")
-                .setDescription("属性获取服务")
-                .setMethod("thing.service.property.get");
-
-        // 定义输入参数:属性标识符列表
-        ThingModelArgument inputArg = new ThingModelArgument()
-                .setIdentifier("properties")
-                .setName("属性标识符列表")
-                .setDescription("需要获取的属性标识符列表")
-                .setDirection("input"); // 设置为输入参数
-
-        // 创建数组类型,元素类型为文本类型(字符串)TODO @puhui999: 还得研究研究
-        ThingModelArrayDataSpecs arrayType = new ThingModelArrayDataSpecs();
-        arrayType.setDataType("array");
-        inputArg.setProperty(new ThingModelProperty().setIdentifier(inputArg.getIdentifier()).setName(inputArg.getName())
-                .setDescription(inputArg.getDescription()).setDataSpecs(arrayType));
-
-        ThingModelDateOrTextDataSpecs textType = new ThingModelDateOrTextDataSpecs();
-        textType.setDataType("text");
-        inputArg.setProperty(new ThingModelProperty().setIdentifier(inputArg.getIdentifier()).setName(inputArg.getName())
-                .setDescription(inputArg.getDescription()).setDataSpecs(textType));
-
-        service.setInputData(Collections.singletonList(inputArg));
-        service.setOutputData(outputData);
-        return service;
-    }
-
-}

+ 11 - 18
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotProductThingModelService.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelService.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.iot.service.thingmodel;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
 import jakarta.validation.Valid;
 
 import java.util.List;
@@ -13,7 +13,7 @@ import java.util.List;
  *
  * @author 芋道源码
  */
-public interface IotProductThingModelService {
+public interface IotThingModelService {
 
     /**
      * 创建产品物模型
@@ -21,21 +21,21 @@ public interface IotProductThingModelService {
      * @param createReqVO 创建信息
      * @return 编号
      */
-    Long createProductThingModel(@Valid IotProductThingModelSaveReqVO createReqVO);
+    Long createThingModel(@Valid IotThingModelSaveReqVO createReqVO);
 
     /**
      * 更新产品物模型
      *
      * @param updateReqVO 更新信息
      */
-    void updateProductThingModel(@Valid IotProductThingModelSaveReqVO updateReqVO);
+    void updateThingModel(@Valid IotThingModelSaveReqVO updateReqVO);
 
     /**
      * 删除产品物模型
      *
      * @param id 编号
      */
-    void deleteProductThingModel(Long id);
+    void deleteThingModel(Long id);
 
     /**
      * 获得产品物模型
@@ -43,7 +43,7 @@ public interface IotProductThingModelService {
      * @param id 编号
      * @return 产品物模型
      */
-    IotProductThingModelDO getProductThingModel(Long id);
+    IotThingModelDO getThingModel(Long id);
 
     /**
      * 获得产品物模型列表
@@ -51,7 +51,7 @@ public interface IotProductThingModelService {
      * @param productId 产品编号
      * @return 产品物模型列表
      */
-    List<IotProductThingModelDO> getProductThingModelListByProductId(Long productId);
+    List<IotThingModelDO> getThingModelListByProductId(Long productId);
 
     /**
      * 获得产品物模型分页
@@ -59,14 +59,7 @@ public interface IotProductThingModelService {
      * @param pageReqVO 分页查询
      * @return 产品物模型分页
      */
-    PageResult<IotProductThingModelDO> getProductThingModelPage(IotProductThingModelPageReqVO pageReqVO);
-
-    /**
-     * 创建超级表数据模型
-     *
-     * @param productId 产品编号
-     */
-    void createSuperTableDataModel(Long productId);
+    PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO);
 
     /**
      * 获得产品物模型列表
@@ -74,6 +67,6 @@ public interface IotProductThingModelService {
      * @param productKey 产品 Key
      * @return 产品物模型列表
      */
-    List<IotProductThingModelDO> getProductThingModelListByProductKey(String productKey);
+    List<IotThingModelDO> getProductThingModelListByProductKey(String productKey);
 
 }

+ 330 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java

@@ -0,0 +1,330 @@
+package cn.iocoder.yudao.module.iot.service.thingmodel;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
+import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
+import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.*;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
+
+/**
+ * IoT 产品物模型 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+@Slf4j
+public class IotThingModelServiceImpl implements IotThingModelService {
+
+    @Resource
+    private IotThingModelMapper thingModelMapper;
+
+    @Resource
+    private IotProductService productService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createThingModel(IotThingModelSaveReqVO createReqVO) {
+        // 1. 校验功能标识符在同一产品下是否唯一
+        validateIdentifierUnique(createReqVO.getProductId(), createReqVO.getIdentifier());
+
+        // 2. 功能名称在同一产品下是否唯一
+        validateNameUnique(createReqVO.getProductId(), createReqVO.getName());
+
+        // 3. 系统保留字段,不能用于标识符定义
+        validateNotDefaultEventAndService(createReqVO.getIdentifier());
+
+        // 4. 校验产品状态,发布状态下,不允许新增功能
+        validateProductStatus(createReqVO.getProductId());
+
+        // 5. 插入数据库
+        IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(createReqVO);
+        thingModelMapper.insert(thingModel);
+
+        // 6. 如果创建的是属性,需要更新默认的事件和服务
+        if (Objects.equals(createReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
+            createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
+        }
+        // TODO @puhui999: 服务和事件的情况 method 怎么设置?在前端设置还是后端设置?
+        return thingModel.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateThingModel(IotThingModelSaveReqVO updateReqVO) {
+        // 1. 校验功能是否存在
+        validateProductThingModelMapperExists(updateReqVO.getId());
+
+        // 2. 校验功能标识符是否唯一
+        validateIdentifierUniqueForUpdate(updateReqVO.getId(), updateReqVO.getProductId(), updateReqVO.getIdentifier());
+
+        // 3. 校验产品状态,发布状态下,不允许操作功能
+        validateProductStatus(updateReqVO.getProductId());
+
+        // 4. 更新数据库
+        IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(updateReqVO);
+        thingModelMapper.updateById(thingModel);
+
+        // 5. 如果更新的是属性,需要更新默认的事件和服务
+        if (Objects.equals(updateReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
+            createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteThingModel(Long id) {
+        // 1. 校验功能是否存在
+        IotThingModelDO thingModel = thingModelMapper.selectById(id);
+        if (thingModel == null) {
+            throw exception(THING_MODEL_NOT_EXISTS);
+        }
+
+        // 3. 校验产品状态,发布状态下,不允许操作功能
+        validateProductStatus(thingModel.getProductId());
+
+        // 2. 删除功能
+        thingModelMapper.deleteById(id);
+
+        // 3. 如果删除的是属性,需要更新默认的事件和服务
+        if (Objects.equals(thingModel.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
+            createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
+        }
+    }
+
+    @Override
+    public IotThingModelDO getThingModel(Long id) {
+        return thingModelMapper.selectById(id);
+    }
+
+    @Override
+    public List<IotThingModelDO> getThingModelListByProductId(Long productId) {
+        return thingModelMapper.selectListByProductId(productId);
+    }
+
+    @Override
+    public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) {
+        return thingModelMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<IotThingModelDO> getProductThingModelListByProductKey(String productKey) {
+        return thingModelMapper.selectListByProductKey(productKey);
+    }
+
+    /**
+     * 校验功能是否存在
+     *
+     * @param id 功能编号
+     */
+    private void validateProductThingModelMapperExists(Long id) {
+        if (thingModelMapper.selectById(id) == null) {
+            throw exception(THING_MODEL_NOT_EXISTS);
+        }
+    }
+
+    private void validateIdentifierUniqueForUpdate(Long id, Long productId, String identifier) {
+        IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
+        if (thingModel != null && ObjectUtil.notEqual(thingModel.getId(), id)) {
+            throw exception(THING_MODEL_IDENTIFIER_EXISTS);
+        }
+    }
+
+    private void validateProductStatus(Long createReqVO) {
+        IotProductDO product = productService.getProduct(createReqVO);
+        if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
+            throw exception(PRODUCT_STATUS_NOT_ALLOW_THING_MODEL);
+        }
+    }
+
+    private void validateNotDefaultEventAndService(String identifier) {
+        // set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义
+        if (CollUtil.containsAny(Arrays.asList("set", "get", "post", "property", "event", "time", "value"), Collections.singletonList(identifier))) {
+            throw exception(THING_MODEL_IDENTIFIER_INVALID);
+        }
+    }
+
+    private void validateNameUnique(Long productId, String name) {
+        IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndName(productId, name);
+        if (thingModel != null) {
+            throw exception(THING_MODEL_NAME_EXISTS);
+        }
+    }
+
+    private void validateIdentifierUnique(Long productId, String identifier) {
+        IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
+        if (thingModel != null) {
+            throw exception(THING_MODEL_IDENTIFIER_EXISTS);
+        }
+    }
+
+    /**
+     * 创建默认的事件和服务
+     */
+    public void createDefaultEventsAndServices(Long productId, String productKey) {
+        // 1. 获取当前属性列表
+        List<IotThingModelDO> properties = thingModelMapper
+                .selectListByProductIdAndType(productId, IotThingModelTypeEnum.PROPERTY.getType());
+
+        // 2. 生成新的事件和服务列表
+        List<IotThingModelDO> newThingModels = new ArrayList<>();
+        // 2.1 生成属性上报事件
+        ThingModelEvent propertyPostEvent = generatePropertyPostEvent(properties);
+        if (propertyPostEvent != null) {
+            newThingModels.add(buildEventThingModelDO(productId, productKey, propertyPostEvent, "属性上报事件"));
+        }
+        // 2.2 生成属性设置服务
+        ThingModelService propertySetService = generatePropertySetService(properties);
+        if (propertySetService != null) {
+            newThingModels.add(buildServiceThingModelDO(productId, productKey, propertySetService, "属性设置服务"));
+        }
+        // 2.3 生成属性获取服务
+        ThingModelService propertyGetService = generatePropertyGetService(properties);
+        if (propertyGetService != null) {
+            newThingModels.add(buildServiceThingModelDO(productId, productKey, propertyGetService, "属性获取服务"));
+        }
+
+        // 3.1 获取数据库中的默认的旧事件和服务列表
+        List<IotThingModelDO> oldThingModels = thingModelMapper.selectListByProductIdAndIdentifiersAndTypes(
+                productId,
+                Arrays.asList("post", "set", "get"),
+                Arrays.asList(IotThingModelTypeEnum.EVENT.getType(), IotThingModelTypeEnum.SERVICE.getType())
+        );
+        // 3.2 创建默认的事件和服务
+        createDefaultEventsAndServices(oldThingModels, newThingModels);
+    }
+
+    /**
+     * 创建默认的事件和服务
+     */
+    private void createDefaultEventsAndServices(List<IotThingModelDO> oldThingModels,
+                                                List<IotThingModelDO> newThingModels) {
+        // 使用 diffList 方法比较新旧列表
+        List<List<IotThingModelDO>> diffResult = diffList(oldThingModels, newThingModels,
+                (oldVal, newVal) -> {
+                    // 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。
+                    boolean same = Objects.equals(oldVal.getIdentifier(), newVal.getIdentifier())
+                            && Objects.equals(oldVal.getType(), newVal.getType());
+                    if (same) {
+                        newVal.setId(oldVal.getId()); // 设置编号
+                    }
+                    return same;
+                });
+        // 批量添加、修改、删除
+        if (CollUtil.isNotEmpty(diffResult.get(0))) {
+            thingModelMapper.insertBatch(diffResult.get(0));
+        }
+        if (CollUtil.isNotEmpty(diffResult.get(1))) {
+            thingModelMapper.updateBatch(diffResult.get(1));
+        }
+        if (CollUtil.isNotEmpty(diffResult.get(2))) {
+            thingModelMapper.deleteByIds(convertSet(diffResult.get(2), IotThingModelDO::getId));
+        }
+    }
+
+    /**
+     * 构建事件功能对象
+     */
+    private IotThingModelDO buildEventThingModelDO(Long productId, String productKey,
+                                                   ThingModelEvent event, String description) {
+        return new IotThingModelDO().setProductId(productId).setProductKey(productKey)
+                .setIdentifier(event.getIdentifier()).setName(event.getName()).setDescription(description)
+                .setType(IotThingModelTypeEnum.EVENT.getType()).setEvent(event);
+    }
+
+    /**
+     * 构建服务功能对象
+     */
+    private IotThingModelDO buildServiceThingModelDO(Long productId, String productKey,
+                                                     ThingModelService service, String description) {
+        return new IotThingModelDO().setProductId(productId).setProductKey(productKey)
+                .setIdentifier(service.getIdentifier()).setName(service.getName()).setDescription(description)
+                .setType(IotThingModelTypeEnum.SERVICE.getType()).setService(service);
+    }
+
+    /**
+     * 生成属性上报事件
+     */
+    private ThingModelEvent generatePropertyPostEvent(List<IotThingModelDO> thingModels) {
+        // 没有属性则不生成
+        if (CollUtil.isEmpty(thingModels)) {
+            return null;
+        }
+
+        // 生成属性上报事件
+        return new ThingModelEvent().setIdentifier("post").setName("属性上报").setMethod("thing.event.property.post")
+                .setType(IotThingModelServiceEventTypeEnum.INFO.getType())
+                .setOutputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.OUTPUT));
+    }
+
+    /**
+     * 生成属性设置服务
+     */
+    private ThingModelService generatePropertySetService(List<IotThingModelDO> thingModels) {
+        // 1.1 过滤出所有可写属性
+        thingModels = filterList(thingModels, thingModel ->
+                IotThingModelAccessModeEnum.READ_WRITE.getMode().equals(thingModel.getProperty().getAccessMode()));
+        // 1.2 没有可写属性则不生成
+        if (CollUtil.isEmpty(thingModels)) {
+            return null;
+        }
+
+        // 2. 生成属性设置服务
+        return new ThingModelService().setIdentifier("set").setName("属性设置").setMethod("thing.service.property.set")
+                .setCallType(IotThingModelServiceCallTypeEnum.ASYNC.getType())
+                .setInputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.INPUT))
+                .setOutputParams(Collections.emptyList()); // 属性设置服务一般不需要输出参数
+    }
+
+    /**
+     * 生成属性获取服务
+     */
+    private ThingModelService generatePropertyGetService(List<IotThingModelDO> thingModels) {
+        // 1.1 没有属性则不生成
+        if (CollUtil.isEmpty(thingModels)) {
+            return null;
+        }
+
+        // 1.2 生成属性获取服务
+        return new ThingModelService().setIdentifier("get").setName("属性获取").setMethod("thing.service.property.get")
+                .setCallType(IotThingModelServiceCallTypeEnum.ASYNC.getType())
+                .setInputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.INPUT))
+                .setOutputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.OUTPUT));
+    }
+
+    /**
+     * 构建输入/输出参数列表
+     *
+     * @param thingModels 属性列表
+     * @return 输入/输出参数列表
+     */
+    private List<ThingModelParam> buildInputOutputParam(List<IotThingModelDO> thingModels,
+                                                        IotThingModelParamDirectionEnum direction) {
+        return convertList(thingModels, thingModel ->
+                BeanUtils.toBean(thingModel.getProperty(), ThingModelParam.class).setParaOrder(0) // TODO @puhui999: 先搞个默认值看看怎么个事
+                        .setDirection(direction.getDirection()));
+    }
+
+}

+ 9 - 16
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java

@@ -1,13 +1,9 @@
 package cn.iocoder.yudao.module.iot.util;
 
+import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.module.iot.enums.IotConstants;
 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_DEVICE_NOT_EXISTS;
 
 // TODO @芋艿:可能要思索下,有没更好的处理方式
 // TODO @芋艿:怎么改成无状态
@@ -34,20 +30,17 @@ public class IotTdDatabaseUtils {
      * @return 产品超级表表名
      */
     public static String getProductSuperTableName(Integer deviceType, String productKey) {
-        // TODO @alwayssuper:枚举字段,不要 1、2、3;不符合预期,抛出异常
-        if (deviceType == null) {
-            throw exception(PRODUCT_DEVICE_NOT_EXISTS);
-        }
+        Assert.notNull(deviceType, "deviceType 不能为空");
         if (IotProductDeviceTypeEnum.GATEWAY_SUB.getType().equals(deviceType)) {
             return String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
-        } else if (IotProductDeviceTypeEnum.GATEWAY.getType().equals(deviceType)) {
+        }
+        if (IotProductDeviceTypeEnum.GATEWAY.getType().equals(deviceType)) {
             return String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
-        } else if (IotProductDeviceTypeEnum.DIRECT.getType().equals(deviceType)){
-            return String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
         }
-        else{
-            throw exception(PRODUCT_DEVICE_NOT_EXISTS);
+        if (IotProductDeviceTypeEnum.DIRECT.getType().equals(deviceType)){
+            return String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
         }
+        throw new IllegalArgumentException("deviceType 不正确");
     }
 
     /**
@@ -58,7 +51,6 @@ public class IotTdDatabaseUtils {
      *
      */
     public static String getThingModelMessageSuperTableName(String productKey) {
-        // TODO @alwayssuper:是不是应该 + 拼接就好,不用 format
         return "thing_model_message_" + productKey.toLowerCase();
     }
 
@@ -70,7 +62,8 @@ public class IotTdDatabaseUtils {
      * @return 物模型日志设备表名
      */
     public static String getThingModelMessageDeviceTableName(String productKey, String deviceName) {
-        return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT, productKey.toLowerCase(), deviceName.toLowerCase());
+        return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT,
+                productKey.toLowerCase(), deviceName.toLowerCase());
     }
 
 }

+ 45 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyDataMapper.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper">
+
+    <select id="getProductPropertySTableFieldList" resultType="cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField">
+        DESCRIBE product_property_${productKey}
+    </select>
+
+    <update id="createProductPropertySTable">
+        CREATE STABLE product_property_${productKey}
+        <foreach item="field" collection="fields" separator="," open="(" close=")">
+            ${field.field} ${field.type}
+            <if test="field.length != null and field.length > 0">
+                (${field.length})
+            </if>
+        </foreach>
+        TAGS (
+            device_key NCHAR(50)
+        )
+    </update>
+
+    <update id="alterProductPropertySTableAddField">
+        ALTER STABLE product_property_${productKey}
+        ADD COLUMN ${field.field} ${field.type}
+        <if test="field.length != null and field.length > 0">
+            (${field.length})
+        </if>
+    </update>
+
+    <update id="alterProductPropertySTableModifyField">
+        ALTER STABLE product_property_${productKey}
+        MODIFY COLUMN ${field.field} ${field.type}
+        <if test="field.length != null and field.length > 0">
+            (${field.length})
+        </if>
+    </update>
+
+    <update id="alterProductPropertySTableDropField">
+        ALTER STABLE product_property_${productKey}
+        DROP COLUMN ${field.field}
+    </update>
+
+</mapper>

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininstance/PluginInstanceMapper.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper">
+<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper">
 
     <!--
         一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。

+ 0 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml

@@ -5,7 +5,6 @@
 <mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper">
 
     <!-- 创建物模型消息日志超级表 -->
-    <!-- TODO @芋艿:捉摸下字段,特别是 sys、ts 这种缩写 -->
     <update id="createSuperTable">
         CREATE STABLE ${dataBaseName}.${superTableName}(
             ts TIMESTAMP,

+ 2 - 0
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml

@@ -24,6 +24,7 @@
         <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpPlugin</plugin.class>
         <plugin.version>0.0.1</plugin.version>
         <plugin.provider>ahh</plugin.provider>
+        <plugin.description>http-plugin-0.0.1</plugin.description>
         <plugin.dependencies/>
     </properties>
 
@@ -104,6 +105,7 @@
                             <Plugin-Class>${plugin.class}</Plugin-Class>
                             <Plugin-Version>${plugin.version}</Plugin-Version>
                             <Plugin-Provider>${plugin.provider}</Plugin-Provider>
+                            <Plugin-Description>${plugin.description}</Plugin-Description>
                             <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
                         </manifestEntries>
                     </archive>

+ 10 - 23
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java

@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin;
 
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
-import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
+import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelFutureListener;
 import io.netty.channel.ChannelHandlerContext;
@@ -12,12 +12,9 @@ import io.netty.util.CharsetUtil;
 
 /**
  * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。
- * <p>
- * 请求格式:
- * POST /sys/{productKey}/{deviceName}/thing/event/property/post
- * 请求体为 JSON 格式数据。
- * <p>
- * 返回结果为 JSON 格式,包含统一的 code、data、id、message、method、version 字段。
+ *
+ * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post
+ * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段
  */
 public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
 
@@ -29,10 +26,9 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
 
     @Override
     protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
-        String uri = request.uri();
-
         // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post
         // 使用 "/" 拆分路径
+        String uri = request.uri();
         String[] parts = uri.split("/");
 
         /*
@@ -52,25 +48,19 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
                 && "event".equals(parts[5])
                 && "property".equals(parts[6])
                 && "post".equals(parts[7]);
-
         if (!isCorrectPath) {
-            // 如果路径不匹配,返回 404 错误
             writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found");
             return;
         }
-
         String productKey = parts[2];
         String deviceName = parts[3];
 
-        // 从请求中获取原始数据
+        // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象
         String requestBody = request.content().toString(CharsetUtil.UTF_8);
-
-        // 尝试解析请求数据为 JSON 对象
         JSONObject jsonData;
         try {
             jsonData = JSONUtil.parseObj(requestBody);
         } catch (Exception e) {
-            // 数据不是合法的 JSON 格式,返回 400 错误
             JSONObject res = createResponseJson(
                     400,
                     new JSONObject(),
@@ -82,8 +72,6 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
             writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString());
             return;
         }
-
-        // 获取请求中的 id 字段,若不存在则为 null
         String id = jsonData.getStr("id", null);
 
         try {
@@ -101,7 +89,6 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
             );
             writeResponse(ctx, HttpResponseStatus.OK, successRes.toString());
         } catch (Exception e) {
-            // 保存数据过程中出现异常,返回 500 错误
             JSONObject errorRes = createResponseJson(
                     500,
                     new JSONObject(),
@@ -128,7 +115,7 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
     private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) {
         JSONObject res = new JSONObject();
         res.set("code", code);
-        res.set("data", data != null ? data : new JSONObject()); // 确保 data 不为 null
+        res.set("data", data != null ? data : new JSONObject());
         res.set("id", id);
         res.set("message", message);
         res.set("method", method);
@@ -137,24 +124,24 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
     }
 
     /**
-     * 向客户端返回 HTTP 响应的辅助方法
+     * 向客户端返回 HTTP 响应的辅助方法
      *
      * @param ctx     通道上下文
      * @param status  HTTP 响应状态码(网络层面的)
      * @param content 响应内容(JSON 字符串或其他文本)
      */
     private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) {
+        // 设置响应头为 JSON 类型和正确的编码
         FullHttpResponse response = new DefaultFullHttpResponse(
                 HttpVersion.HTTP_1_1,
                 status,
                 Unpooled.copiedBuffer(content, CharsetUtil.UTF_8)
         );
-
-        // 设置响应头为 JSON 类型和正确的编码
         response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
         response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
 
         // 发送响应并在发送完成后关闭连接
         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
     }
+
 }

+ 22 - 9
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.iot.plugin;
 
-import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
+import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
 import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.*;
@@ -18,18 +18,25 @@ import java.util.concurrent.Executors;
 public class HttpPlugin extends Plugin {
 
     private static final int PORT = 8092;
-    private final ExecutorService executorService;
-    private DeviceDataApi deviceDataApi; // 用于保存设备数据的 API
+
+    private ExecutorService executorService;
+    private DeviceDataApi deviceDataApi;
 
     public HttpPlugin(PluginWrapper wrapper) {
         super(wrapper);
-        this.executorService = Executors.newSingleThreadExecutor(); // 创建单线程池
+        // 初始化线程池
+        this.executorService = Executors.newSingleThreadExecutor();
     }
 
     @Override
     public void start() {
         log.info("HttpPlugin.start()");
 
+        // 重新初始化线程池,确保它是活跃的
+        if (executorService.isShutdown() || executorService.isTerminated()) {
+            executorService = Executors.newSingleThreadExecutor();
+        }
+
         // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例
         deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
         if (deviceDataApi == null) {
@@ -44,10 +51,13 @@ public class HttpPlugin extends Plugin {
     @Override
     public void stop() {
         log.info("HttpPlugin.stop()");
-        executorService.shutdownNow(); // 停止线程池
+        // 停止线程池
+        executorService.shutdownNow();
     }
 
-    // 启动 HTTP 服务
+    /**
+     * 启动 HTTP 服务
+     */
     private void startHttpServer() {
         EventLoopGroup bossGroup = new NioEventLoopGroup(1);
         EventLoopGroup workerGroup = new NioEventLoopGroup();
@@ -56,7 +66,8 @@ public class HttpPlugin extends Plugin {
             ServerBootstrap bootstrap = new ServerBootstrap();
             bootstrap.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class)
-                    .childHandler(new ChannelInitializer<Channel>() {
+                    .childHandler(new ChannelInitializer<>() {
+
                         @Override
                         protected void initChannel(Channel channel) {
                             channel.pipeline().addLast(new HttpServerCodec());
@@ -64,6 +75,7 @@ public class HttpPlugin extends Plugin {
                             // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器
                             channel.pipeline().addLast(new HttpHandler(deviceDataApi));
                         }
+
                     });
 
             // 绑定端口并启动服务器
@@ -72,10 +84,11 @@ public class HttpPlugin extends Plugin {
             future.channel().closeFuture().sync();
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
-            log.error("HTTP 服务启动中断", e);
+            log.warn("HTTP 服务启动被中断", e);
         } finally {
             bossGroup.shutdownGracefully();
             workerGroup.shutdownGracefully();
         }
     }
-}
+
+}