Browse Source

Merge remote-tracking branch 'yudao/feature/iot' into iot

# Conflicts:
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java
puhui999 7 months ago
parent
commit
ed901bc97f
38 changed files with 750 additions and 341 deletions
  1. 0 0
      plugins/disabled.txt
  2. 0 0
      plugins/enabled.txt
  3. BIN
      plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar
  4. 0 1
      yudao-module-iot/pom.xml
  5. 12 0
      yudao-module-iot/yudao-module-iot-api/pom.xml
  6. 36 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java
  7. 17 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java
  8. 1 1
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java
  9. 7 0
      yudao-module-iot/yudao-module-iot-biz/pom.xml
  10. 24 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java
  11. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
  12. 0 41
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/Greetings.java
  13. 1 19
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java
  14. 0 34
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/WhazzupGreeting.java
  15. 0 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java
  16. 0 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java
  17. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java
  18. 35 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java
  19. 1 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java
  20. 10 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/simulatesend/SimulateSendConsumer.java
  21. 2 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java
  22. 19 21
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java
  23. 1 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java
  24. 30 22
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java
  25. 20 19
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java
  26. 3 4
      yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml
  27. 0 30
      yudao-module-iot/yudao-module-iot-plugin-api/pom.xml
  28. 0 27
      yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/Greeting.java
  29. 17 7
      yudao-module-iot/yudao-module-iot-plugin/pom.xml
  30. 6 0
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/plugin.properties
  31. 148 0
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/pom.xml
  32. 31 0
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/assembly/assembly.xml
  33. 77 0
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/DemoPlugin.java
  34. 1 0
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties
  35. 46 50
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml
  36. 0 6
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/assembly/assembly.xml
  37. 147 0
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java
  38. 56 46
      yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java

+ 0 - 0
plugins/disabled.txt


+ 0 - 0
plugins/enabled.txt


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


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

@@ -10,7 +10,6 @@
     <modules>
         <module>yudao-module-iot-api</module>
         <module>yudao-module-iot-biz</module>
-        <module>yudao-module-iot-plugin-api</module>
         <module>yudao-module-iot-plugin</module>
     </modules>
     <modelVersion>4.0.0</modelVersion>

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

@@ -21,6 +21,18 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-common</artifactId>
         </dependency>
+        <!-- PF4J -->
+        <!-- TODO 芋艿:这个依赖,要不要放在 api 包 -->
+        <dependency>
+            <groupId>org.pf4j</groupId>
+            <artifactId>pf4j-spring</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
     </dependencies>
 
 </project>

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

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.iot.api;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 服务注册表 - 插架模块使用,无法使用 Spring 注入
+ */
+public class ServiceRegistry {
+
+    private static final Map<Class<?>, Object> services = new HashMap<>();
+
+    /**
+     * 注册服务
+     *
+     * @param serviceClass 服务类
+     * @param serviceImpl  服务实现
+     * @param <T>          服务类
+     */
+    public static <T> void registerService(Class<T> serviceClass, T serviceImpl) {
+        services.put(serviceClass, serviceImpl);
+    }
+
+    /**
+     * 获得服务
+     *
+     * @param serviceClass 服务类
+     * @param <T>          服务类
+     * @return 服务实现
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getService(Class<T> serviceClass) {
+        return (T) services.get(serviceClass);
+    }
+
+}

+ 17 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.iot.api.device;
+
+/**
+ * 设备数据 API
+ */
+public interface DeviceDataApi {
+
+    /**
+     * 保存设备数据
+     *
+     * @param productKey 产品 key
+     * @param deviceName 设备名称
+     * @param message    消息
+     */
+    void saveDeviceData(String productKey, String deviceName, String message);
+
+}

+ 1 - 1
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java

@@ -16,7 +16,7 @@ import java.util.Arrays;
 public enum IotProductDeviceTypeEnum implements IntArrayValuable {
 
     DIRECT(0, "直连设备"),
-    GATEWAY_CHILD(1, "网关子设备"),
+    GATEWAY_SUB(1, "网关子设备"),
     GATEWAY(2, "网关设备");
 
     /**

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

@@ -81,6 +81,13 @@
         <dependency>
             <groupId>org.pf4j</groupId>
             <artifactId>pf4j-spring</artifactId>
+            <!-- TODO @芋艿:可以放到 bom 里配置 -->
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
     </dependencies>

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

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.iot.api.device;
+
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 设备数据 API 实现类
+ */
+@Service
+@Validated
+public class DeviceDataApiImpl implements DeviceDataApi {
+
+    @Resource
+    private IotDeviceDataService deviceDataService;
+
+    @Override
+    public void saveDeviceData(String productKey, String deviceName, String message) {
+        deviceDataService.saveDeviceData(productKey, deviceName, message);
+    }
+
+}

+ 1 - 1
yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java

@@ -3,4 +3,4 @@
  *
  * TODO 芋艿:后续删除
  */
-package cn.iocoder.yudao.module.iot.api;
+package cn.iocoder.yudao.module.iot.api;

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

@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2012-present the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
-
-import cn.iocoder.yudao.module.iot.api.Greeting;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-
-/**
- * 打招呼 测试用例
- */
-@Component
-public class Greetings {
-
-    @Autowired
-    private List<Greeting> greetings;
-
-    public Integer printGreetings() {
-        System.out.printf("找到扩展点的 %d 个扩展 '%s'%n", greetings.size(), Greeting.class.getName());
-        for (Greeting greeting : greetings) {
-            System.out.println(">>> " + greeting.getGreeting());
-        }
-        return greetings.size();
-    }
-
-}

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

@@ -3,7 +3,6 @@ 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.context.ApplicationContext;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
@@ -25,12 +24,8 @@ import java.util.stream.Collectors;
 @RequestMapping("/iot/plugins")
 public class PluginController {
 
-    @Resource
-    private ApplicationContext applicationContext;
     @Resource
     private SpringPluginManager springPluginManager;
-    @Resource
-    private Greetings greetings;
 
     @Value("${pf4j.pluginsDir}")
     private String pluginsDir;
@@ -73,10 +68,8 @@ public class PluginController {
 
             return ResponseEntity.ok("插件上传并加载成功");
         } catch (IOException e) {
-            e.printStackTrace();
             return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传插件时发生错误: " + e.getMessage());
         } catch (Exception e) {
-            e.printStackTrace();
             return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("加载插件时发生错误: " + e.getMessage());
         }
     }
@@ -120,15 +113,4 @@ public class PluginController {
         return ResponseEntity.ok(plugins);
     }
 
-    /**
-     * 打印问候语
-     *
-     * @return 问候语数量
-     */
-    @PermitAll
-    @GetMapping("/printGreetings")
-    public ResponseEntity<Integer> printGreetings() {
-        Integer count = greetings.printGreetings();
-        return ResponseEntity.ok(count);
-    }
-}
+}

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

@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2012-present the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
-
-import cn.iocoder.yudao.module.iot.api.Greeting;
-import org.pf4j.Extension;
-import org.springframework.stereotype.Component;
-
-/**
- * 打招呼 测试用例
- */
-@Extension
-@Component
-public class WhazzupGreeting implements Greeting {
-
-    @Override
-    public String getGreeting() {
-        return "Whazzup";
-    }
-
-}

+ 0 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java

@@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
-import java.util.*;
-import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDateTime;
 import com.alibaba.excel.annotation.*;
 

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

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
-import java.util.*;
 import jakarta.validation.constraints.*;
 
 @Schema(description = "管理后台 - IoT 插件实例新增/修改 Request VO")

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThinkModelMessageMapper.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java

@@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper;
  */
 @Mapper
 @DS("tdengine")
-public interface TdThinkModelMessageMapper {
+public interface TdThingModelMessageMapper {
 
     /**
      * 创建物模型消息日志超级表超级表

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

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.framework.plugin;
+
+import cn.iocoder.yudao.module.iot.api.device.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") // TODO @haohao:1)这个名字,可以搞个 public static final 常量;2)是不是 conditionBefore 啥
+    public Object serviceRegistryInitializedMarker() {
+        // 返回任意对象即可,这里返回 null 都可以,但最好返回个实际对象
+        return new Object();
+    }
+
+}

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

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.iot.framework.plugin;
 
-import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.Greetings;
 import org.pf4j.spring.SpringPluginManager;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -10,14 +9,9 @@ import org.springframework.context.annotation.DependsOn;
 public class SpringConfiguration {
 
     @Bean
+    @DependsOn("serviceRegistryInitializedMarker")
     public SpringPluginManager pluginManager() {
         return new SpringPluginManager();
     }
 
-    @Bean
-    @DependsOn("pluginManager")
-    public Greetings greetings() {
-        return new Greetings();
-    }
-
 }

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

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

+ 2 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java

@@ -20,7 +20,8 @@ public interface IotDeviceDataService {
      *
      * @param productKey 产品 key
      * @param deviceName 设备名称
-     * @param message     消息
+     * @param message    消息
+     *                   <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);
 

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

@@ -22,9 +22,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.jar.JarEntry;
@@ -220,24 +218,24 @@ public class PluginInfoServiceImpl implements PluginInfoService {
         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);
-            }
-        }
-    }
+//    @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);
+//            }
+//        }
+//    }
 
 }

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

@@ -13,6 +13,7 @@ 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;
 
+// TODO @haohao:可以搞个 plugin 包,然后把 plugininfo、plugininstance
 /**
  * IoT 插件实例 Service 实现类
  *

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

@@ -7,19 +7,21 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
 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.*;
+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.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.dal.tdengine.TdThinkModelMessageMapper;
 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.service.device.IotDeviceService;
-import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import cn.iocoder.yudao.module.iot.util.IotTdDatabaseUtils;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -64,11 +66,6 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
     @Resource
     private DeviceDataRedisDAO deviceDataRedisDAO;
 
-    @Resource
-    private IotTdDatabaseUtils iotTdDatabaseUtils;
-
-    @Resource
-    private TdThinkModelMessageMapper tdThinkModelMessageMapper;
 
     // TODO @haohao:这个方法,可以考虑加下 1. 2. 3. 更有层次感
     @Override
@@ -81,7 +78,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
             iotDeviceService.updateDeviceStatus(new IotDeviceStatusUpdateReqVO()
                     .setId(device.getId()).setStatus(IotDeviceStatusEnum.ONLINE.getStatus()));
             // 1.2 创建物模型日志设备表
-            createThinkModelMessageDeviceTable(device.getProductKey(), device.getDeviceName(), device.getDeviceKey());
+            createThingModelMessageDeviceTable(device.getProductKey(), device.getDeviceName(), device.getDeviceKey());
         }
 
         // 2. 获取设备属性并进行物模型校验,过滤非物模型属性
@@ -118,13 +115,23 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
 
         // 2. 获取超级表的名称和数据库名称
         // TODO @alwayssuper:最好 databaseName、superTableName 的处理,放到 tdThinkModelMessageMapper 里。可以考虑,弄个 default 方法
-        String databaseName = iotTdDatabaseUtils.getDatabaseName();
+        String databaseName = IotTdDatabaseUtils.getDatabaseName(url);
         String superTableName = IotTdDatabaseUtils.getThingModelMessageSuperTableName(product.getProductKey());
 
+        // 解析物模型,获取字段列表
+        List<TdFieldDO> schemaFields = List.of(
+                TdFieldDO.builder().fieldName("time").dataType("TIMESTAMP").build(),
+                TdFieldDO.builder().fieldName("id").dataType("NCHAR").dataLength(64).build(),
+                TdFieldDO.builder().fieldName("sys").dataType("NCHAR").dataLength(2048).build(),
+                TdFieldDO.builder().fieldName("method").dataType("NCHAR").dataLength(256).build(),
+                TdFieldDO.builder().fieldName("params").dataType("NCHAR").dataLength(2048).build()
+        );
+        // 设置超级表的标签
+        List<TdFieldDO> tagsFields = List.of(
+                TdFieldDO.builder().fieldName("device_key").dataType("NCHAR").dataLength(64).build()
+        );
         // 3. 创建超级表
-        tdThinkModelMessageMapper.createSuperTable(ThingModelMessageDO.builder().build()
-                .setDataBaseName(databaseName)
-                .setSuperTableName(superTableName));
+        tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
     }
 
     private List<IotProductThingModelDO> getValidFunctionList(String productKey) {
@@ -227,21 +234,22 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
      * @param productKey 产品 Key
      * @param deviceName 设备名称
      * @param deviceKey  设备 Key
+     *
      */
-    private void createThinkModelMessageDeviceTable(String productKey, String deviceName, String deviceKey) {
+    private void createThingModelMessageDeviceTable(String productKey, String deviceName, String deviceKey){
 
         // 1. 获取超级表的名称、数据库名称、设备日志表名称
-        String databaseName = iotTdDatabaseUtils.getDatabaseName();
+        String databaseName = IotTdDatabaseUtils.getDatabaseName(url);
         String superTableName = IotTdDatabaseUtils.getThingModelMessageSuperTableName(productKey);
         // TODO @alwayssuper:最好 databaseName、superTableName、thinkModelMessageDeviceTableName 的处理,放到 tdThinkModelMessageMapper 里。可以考虑,弄个 default 方法
-        String thinkModelMessageDeviceTableName = IotTdDatabaseUtils.getThinkModelMessageDeviceTableName(productKey, deviceName);
+        String thinkModelMessageDeviceTableName = IotTdDatabaseUtils.getThingModelMessageDeviceTableName(productKey, deviceName);
 
         // 2. 创建物模型日志设备数据表
-        tdThinkModelMessageMapper.createTableWithTag(ThingModelMessageDO.builder().build()
-                .setDataBaseName(databaseName)
-                .setSuperTableName(superTableName)
-                .setTableName(thinkModelMessageDeviceTableName)
-                .setDeviceKey(deviceKey));
+//        tdThingModelMessageMapper.createTableWithTag(ThingModelMessageDO.builder().build()
+//                .setDataBaseName(databaseName)
+//                .setSuperTableName(superTableName)
+//                .setTableName(thinkModelMessageDeviceTableName)
+//                .setDeviceKey(deviceKey));
     }
 
     /**

+ 20 - 19
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java

@@ -1,8 +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 org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
+import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
 
 // TODO @芋艿:可能要思索下,有没更好的处理方式
 // TODO @芋艿:怎么改成无状态
@@ -11,19 +12,14 @@ import org.springframework.stereotype.Component;
  *
  * @author AlwaysSuper
  */
-@Component
 public class IotTdDatabaseUtils {
 
-    @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
-    private String url;
-
     /**
      * 获取数据库名称
      */
-    public String getDatabaseName() {
+    public static String getDatabaseName(String url) {
 //       TODO @alwayssuper:StrUtil.subAfter("/")
-        int index = url.lastIndexOf("/");
-        return index != -1 ? url.substring(index + 1) : url;
+        return StrUtil.subAfter(url, "/", true);
     }
 
     /**
@@ -34,12 +30,17 @@ public class IotTdDatabaseUtils {
      * @return 产品超级表表名
      */
     public static String getProductSuperTableName(Integer deviceType, String productKey) {
-        // TODO @alwayssuper:枚举字段,不要 1、2、3;不符合预期,抛出异常
-        return switch (deviceType) {
-            case 1 -> String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
-            case 2 -> String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
-            default -> String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
-        };
+        Assert.notNull(deviceType, "deviceType 不能为空");
+        if (IotProductDeviceTypeEnum.GATEWAY_SUB.getType().equals(deviceType)) {
+            return String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
+        }
+        if (IotProductDeviceTypeEnum.GATEWAY.getType().equals(deviceType)) {
+            return String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
+        }
+        if (IotProductDeviceTypeEnum.DIRECT.getType().equals(deviceType)){
+            return String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
+        }
+        throw new IllegalArgumentException("deviceType 不正确");
     }
 
     /**
@@ -50,8 +51,7 @@ public class IotTdDatabaseUtils {
      *
      */
     public static String getThingModelMessageSuperTableName(String productKey) {
-        // TODO @alwayssuper:是不是应该 + 拼接就好,不用 format
-        return String.format("thing_model_message_", productKey).toLowerCase();
+        return "thing_model_message_" + productKey.toLowerCase();
     }
 
     /**
@@ -61,8 +61,9 @@ public class IotTdDatabaseUtils {
      * @param deviceName 设备名称
      * @return 物模型日志设备表名
      */
-    public static String getThinkModelMessageDeviceTableName(String productKey, String deviceName) {
-        return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT, productKey.toLowerCase(), deviceName.toLowerCase());
+    public static String getThingModelMessageDeviceTableName(String productKey, String deviceName) {
+        return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT,
+                productKey.toLowerCase(), deviceName.toLowerCase());
     }
 
 }

+ 3 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml

@@ -2,10 +2,9 @@
 <!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.TdThinkModelMessageMapper">
+<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper">
 
     <!-- 创建物模型消息日志超级表 -->
-    <!-- TODO @芋艿:捉摸下字段,特别是 sys、ts 这种缩写 -->
     <update id="createSuperTable">
         CREATE STABLE ${dataBaseName}.${superTableName}(
             ts TIMESTAMP,
@@ -14,7 +13,7 @@
             method VARCHAR(255),
             params VARCHAR(2048)
         )TAGS (
-            deviceKey VARCHAR(255)
+            device_key VARCHAR(255)
         )
     </update>
 
@@ -28,7 +27,7 @@
             method ,
             params
         )TAGS(
-            #{deviceKey}
+            #{device_key}
         )
     </update>
 </mapper>

+ 0 - 30
yudao-module-iot/yudao-module-iot-plugin-api/pom.xml

@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xmlns="http://maven.apache.org/POM/4.0.0"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>cn.iocoder.boot</groupId>
-    <artifactId>yudao-module-iot-plugin-api</artifactId>
-    <version>0.0.1</version>
-    <packaging>jar</packaging>
-
-    <name>${project.artifactId}</name>
-    <description>
-        物联网 模块插件 API,暴露给其它模块调用
-    </description>
-
-    <properties>
-        <pf4j-spring.version>0.9.0</pf4j-spring.version>
-    </properties>
-
-    <dependencies>
-        <!-- PF4J Spring 集成 -->
-        <dependency>
-            <groupId>org.pf4j</groupId>
-            <artifactId>pf4j-spring</artifactId>
-            <version>${pf4j-spring.version}</version>
-            <scope>provided</scope>
-        </dependency>
-    </dependencies>
-
-</project>

+ 0 - 27
yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/Greeting.java

@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2012-present the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package cn.iocoder.yudao.module.iot.api;
-
-import org.pf4j.ExtensionPoint;
-
-/**
- * @author Decebal Suiu
- */
-public interface Greeting extends ExtensionPoint {
-
-    String getGreeting();
-
-}

+ 17 - 7
yudao-module-iot/yudao-module-iot-plugin/pom.xml

@@ -2,19 +2,29 @@
 <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://maven.apache.org/POM/4.0.0"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>cn.iocoder.boot</groupId>
-    <artifactId>yudao-module-iot-plugin</artifactId>
-    <version>0.0.1</version>
-    <packaging>pom</packaging>
-
+<!--    <modelVersion>4.0.0</modelVersion>-->
+<!--    <groupId>cn.iocoder.boot</groupId>-->
+<!--    <artifactId>yudao-module-iot-plugin</artifactId>-->
+<!--    <version>0.0.1</version>-->
+<!--    <packaging>pom</packaging>-->
+    <parent>
+        <artifactId>yudao-module-iot</artifactId>
+        <groupId>cn.iocoder.boot</groupId>
+        <version>${revision}</version>
+    </parent>
     <modules>
+        <module>yudao-module-iot-demo-plugin</module>
         <module>yudao-module-iot-http-plugin</module>
     </modules>
 
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>yudao-module-iot-plugin</artifactId>
+    <packaging>pom</packaging>
+
     <name>${project.artifactId}</name>
     <description>
-        物联网模块 - 插件模块
+        物联网 插件 模块
     </description>
 
 </project>

+ 6 - 0
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/plugin.properties

@@ -0,0 +1,6 @@
+plugin.id=demo-plugin
+plugin.class=cn.iocoder.yudao.module.iot.plugin.DemoPlugin
+plugin.version=0.0.1
+plugin.provider=ahh
+plugin.dependencies=
+plugin.description=demo-plugin

+ 148 - 0
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/pom.xml

@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
+         http://maven.apache.org/POM/4.0.0
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>yudao-module-iot-plugin</artifactId>
+        <groupId>cn.iocoder.boot</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+
+    <artifactId>yudao-module-iot-demo-plugin</artifactId>
+
+    <name>${project.artifactId}</name>
+    <description>
+        物联网 插件模块 - demo 插件
+    </description>
+
+    <properties>
+        <!-- 插件相关 -->
+        <plugin.id>demo-plugin</plugin.id>
+        <plugin.class>cn.iocoder.yudao.module.iot.plugin.DemoPlugin</plugin.class>
+        <plugin.version>0.0.1</plugin.version>
+        <plugin.provider>ahh</plugin.provider>
+        <plugin.dependencies/>
+    </properties>
+
+    <build>
+        <plugins>
+            <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>properties-maven-plugin</artifactId>
+                <version>1.0-alpha-2</version>
+                <executions>
+                  <execution>
+                    <phase>initialize</phase>
+                    <goals>
+                      <goal>read-project-properties</goal>
+                    </goals>
+                    <configuration>
+                      <files>
+                        <file>plugin.properties</file>
+                      </files>
+                    </configuration>
+                  </execution>
+                </executions>
+            </plugin>
+            -->
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <version>1.6</version>
+                <executions>
+                    <execution>
+                        <id>unzip jar file</id>
+                        <phase>package</phase>
+                        <configuration>
+                            <target>
+                                <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}" dest="target/plugin-classes" />
+                            </target>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.3</version>
+                <configuration>
+                    <descriptors>
+                        <descriptor>
+                            src/main/assembly/assembly.xml
+                        </descriptor>
+                    </descriptors>
+                    <appendAssemblyId>false</appendAssemblyId>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>attached</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.4</version>
+                <configuration>
+                    <archive>
+                        <manifestEntries>
+                            <Plugin-Id>${plugin.id}</Plugin-Id>
+                            <Plugin-Class>${plugin.class}</Plugin-Class>
+                            <Plugin-Version>${plugin.version}</Plugin-Version>
+                            <Plugin-Provider>${plugin.provider}</Plugin-Provider>
+                            <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <!-- 其他依赖项 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>${spring.boot.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <!-- PF4J Spring 集成 -->
+        <dependency>
+            <groupId>org.pf4j</groupId>
+            <artifactId>pf4j-spring</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- 项目依赖 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-iot-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 31 - 0
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/assembly/assembly.xml

@@ -0,0 +1,31 @@
+<assembly>
+	<id>plugin</id>
+	<formats>
+		<format>zip</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+	<dependencySets>
+		<dependencySet>
+            <useProjectArtifact>false</useProjectArtifact>
+            <scope>runtime</scope>
+			<outputDirectory>lib</outputDirectory>
+			<includes>
+				<include>*:jar:*</include>
+			</includes>
+		</dependencySet>
+	</dependencySets>
+    <!--
+    <fileSets>
+        <fileSet>
+            <directory>target/classes</directory>
+            <outputDirectory>classes</outputDirectory>
+        </fileSet>
+    </fileSets>
+    -->
+	<fileSets>
+		<fileSet>
+			<directory>target/plugin-classes</directory>
+			<outputDirectory>classes</outputDirectory>
+		</fileSet>
+	</fileSets>
+</assembly>

+ 77 - 0
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/DemoPlugin.java

@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.iot.plugin;
+
+import com.sun.net.httpserver.HttpServer;
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.Plugin;
+import org.pf4j.PluginWrapper;
+import org.pf4j.RuntimeMode;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+
+/**
+ * 一个启动 HTTP 服务器的简单插件。
+ */
+@Slf4j
+public class DemoPlugin extends Plugin {
+
+    private HttpServer server;
+
+    public DemoPlugin(PluginWrapper wrapper) {
+        super(wrapper);
+    }
+
+    @Override
+    public void start() {
+        log.info("Demo 插件启动");
+        // for testing the development mode
+        if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
+            log.info("DemoPlugin in DEVELOPMENT mode");
+        }
+        startDemoServer();
+    }
+
+    @Override
+    public void stop() {
+        log.info("Demo 插件停止");
+        stopDemoServer();
+    }
+
+    private void startDemoServer() {
+        try {
+            server = HttpServer.create(new InetSocketAddress(9081), 0);
+            server.createContext("/", exchange -> {
+                String response = "Hello from DemoPlugin";
+                exchange.sendResponseHeaders(200, response.getBytes().length);
+                OutputStream os = exchange.getResponseBody();
+                os.write(response.getBytes());
+                os.close();
+            });
+            server.setExecutor(null);
+            server.start();
+            log.info("HTTP 服务器启动成功,端口为 9081");
+            log.info("访问地址为 http://127.0.0.1:9081/");
+        } catch (IOException e) {
+            log.error("HTTP 服务器启动失败", e);
+        }
+    }
+
+    private void stopDemoServer() {
+        if (server != null) {
+            server.stop(0);
+            log.info("HTTP 服务器停止成功");
+        }
+    }
+
+//    @Extension
+//    public static class WelcomeGreeting implements Greeting {
+//
+//        @Override
+//        public String getGreeting() {
+//            return "Welcome to DemoPlugin";
+//        }
+//
+//    }
+
+}

+ 1 - 0
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties

@@ -3,3 +3,4 @@ plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpPlugin
 plugin.version=0.0.1
 plugin.provider=ahh
 plugin.dependencies=
+plugin.description=http-plugin-0.0.1

+ 46 - 50
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml

@@ -3,14 +3,20 @@
          xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
          http://maven.apache.org/POM/4.0.0
          http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>yudao-module-iot-plugin</artifactId>
+        <groupId>cn.iocoder.boot</groupId>
+        <version>${revision}</version>
+    </parent>
     <modelVersion>4.0.0</modelVersion>
-    <groupId>cn.iocoder.boot</groupId>
-    <artifactId>yudao-module-iot-http-plugin</artifactId>
-    <version>0.0.1</version>
     <packaging>jar</packaging>
 
+    <artifactId>yudao-module-iot-http-plugin</artifactId>
+
     <name>${project.artifactId}</name>
-    <description>物联网 模块 - http 插件</description>
+    <description>
+        物联网 插件模块 - http 插件
+    </description>
 
     <properties>
         <!-- 插件相关 -->
@@ -19,50 +25,8 @@
         <plugin.version>0.0.1</plugin.version>
         <plugin.provider>ahh</plugin.provider>
         <plugin.dependencies/>
-
-        <!-- Maven 相关 -->
-        <java.version>17</java.version>
-        <maven.compiler.source>${java.version}</maven.compiler.source>
-        <maven.compiler.target>${java.version}</maven.compiler.target>
-        <maven-antrun-plugin.version>1.6</maven-antrun-plugin.version>
-        <maven-assembly-plugin.version>2.3</maven-assembly-plugin.version>
-        <maven-jar-plugin.version>2.4</maven-jar-plugin.version>
-        <pf4j-spring.version>0.9.0</pf4j-spring.version>
-        <!-- 看看咋放到 bom 里 -->
-        <lombok.version>1.18.34</lombok.version>
-        <spring.boot.version>3.3.1</spring.boot.version>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
 
-    <dependencies>
-        <!-- 其他依赖项 -->
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-            <version>${spring.boot.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <!-- PF4J Spring 集成 -->
-        <dependency>
-            <groupId>org.pf4j</groupId>
-            <artifactId>pf4j-spring</artifactId>
-            <version>${pf4j-spring.version}</version>
-            <scope>provided</scope>
-        </dependency>
-        <!-- 项目依赖 -->
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-iot-plugin-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.projectlombok</groupId>
-            <artifactId>lombok</artifactId>
-            <version>${lombok.version}</version>
-            <scope>provided</scope>
-        </dependency>
-    </dependencies>
-
     <build>
         <plugins>
             <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
@@ -89,14 +53,15 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-antrun-plugin</artifactId>
-                <version>${maven-antrun-plugin.version}</version>
+                <version>1.6</version>
                 <executions>
                     <execution>
                         <id>unzip jar file</id>
                         <phase>package</phase>
                         <configuration>
                             <target>
-                                <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}" dest="target/plugin-classes" />
+                                <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}"
+                                       dest="target/plugin-classes"/>
                             </target>
                         </configuration>
                         <goals>
@@ -108,7 +73,7 @@
 
             <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
-                <version>${maven-assembly-plugin.version}</version>
+                <version>2.3</version>
                 <configuration>
                     <descriptors>
                         <descriptor>
@@ -131,7 +96,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
-                <version>${maven-jar-plugin.version}</version>
+                <version>2.4</version>
                 <configuration>
                     <archive>
                         <manifestEntries>
@@ -153,4 +118,35 @@
             </plugin>
         </plugins>
     </build>
+
+    <dependencies>
+        <!-- 其他依赖项 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- PF4J Spring 集成 -->
+        <dependency>
+            <groupId>org.pf4j</groupId>
+            <artifactId>pf4j-spring</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- 项目依赖 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-iot-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>4.1.63.Final</version> <!-- 版本可根据需要调整 -->
+        </dependency>
+    </dependencies>
 </project>

+ 0 - 6
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/assembly/assembly.xml

@@ -1,9 +1,3 @@
-<!--
- Describes the plugin archive
-
- @author Decebal Suiu
- @version 1.0
--->
 <assembly>
 	<id>plugin</id>
 	<formats>

+ 147 - 0
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java

@@ -0,0 +1,147 @@
+package cn.iocoder.yudao.module.iot.plugin;
+
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.*;
+import io.netty.util.CharsetUtil;
+
+/**
+ * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。
+ *
+ * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post
+ * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段
+ */
+public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
+
+    private final DeviceDataApi deviceDataApi;
+
+    public HttpHandler(DeviceDataApi deviceDataApi) {
+        this.deviceDataApi = deviceDataApi;
+    }
+
+    @Override
+    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
+        // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post
+        // 使用 "/" 拆分路径
+        String uri = request.uri();
+        String[] parts = uri.split("/");
+
+        /*
+          拆分结果示例:
+          parts[0] = ""
+          parts[1] = "sys"
+          parts[2] = productKey
+          parts[3] = deviceName
+          parts[4] = "thing"
+          parts[5] = "event"
+          parts[6] = "property"
+          parts[7] = "post"
+         */
+        boolean isCorrectPath = parts.length == 8
+                && "sys".equals(parts[1])
+                && "thing".equals(parts[4])
+                && "event".equals(parts[5])
+                && "property".equals(parts[6])
+                && "post".equals(parts[7]);
+        if (!isCorrectPath) {
+            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);
+        JSONObject jsonData;
+        try {
+            jsonData = JSONUtil.parseObj(requestBody);
+        } catch (Exception e) {
+            JSONObject res = createResponseJson(
+                    400,
+                    new JSONObject(),
+                    null,
+                    "请求数据不是合法的 JSON 格式: " + e.getMessage(),
+                    "thing.event.property.post",
+                    "1.0"
+            );
+            writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString());
+            return;
+        }
+        String id = jsonData.getStr("id", null);
+
+        try {
+            // 调用主程序的接口保存数据
+            deviceDataApi.saveDeviceData(productKey, deviceName, jsonData.toString());
+
+            // 构造成功响应内容
+            JSONObject successRes = createResponseJson(
+                    200,
+                    new JSONObject(),
+                    id,
+                    "success",
+                    "thing.event.property.post",
+                    "1.0"
+            );
+            writeResponse(ctx, HttpResponseStatus.OK, successRes.toString());
+        } catch (Exception e) {
+            JSONObject errorRes = createResponseJson(
+                    500,
+                    new JSONObject(),
+                    id,
+                    "The format of result is error!",
+                    "thing.event.property.post",
+                    "1.0"
+            );
+            writeResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorRes.toString());
+        }
+    }
+
+    /**
+     * 创建标准化的响应 JSON 对象
+     *
+     * @param code    响应状态码(业务层面的)
+     * @param data    返回的数据对象(JSON)
+     * @param id      请求的 id(可选)
+     * @param message 返回的提示信息
+     * @param method  返回的 method 标识
+     * @param version 返回的版本号
+     * @return 构造好的 JSON 对象
+     */
+    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());
+        res.set("id", id);
+        res.set("message", message);
+        res.set("method", method);
+        res.set("version", version);
+        return res;
+    }
+
+    /**
+     * 向客户端返回 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)
+        );
+        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);
+    }
+
+}

+ 56 - 46
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java

@@ -1,79 +1,89 @@
 package cn.iocoder.yudao.module.iot.plugin;
 
-import cn.iocoder.yudao.module.iot.api.Greeting;
-import com.sun.net.httpserver.HttpServer;
+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.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.*;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang.StringUtils;
-import org.pf4j.Extension;
-import org.pf4j.Plugin;
 import org.pf4j.PluginWrapper;
-import org.pf4j.RuntimeMode;
+import org.pf4j.Plugin;
 
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
-/**
- * 一个启动 HTTP 服务器的简单插件。
- */
 @Slf4j
 public class HttpPlugin extends Plugin {
 
-    private HttpServer server;
+    private static final int PORT = 8092;
+
+    private final ExecutorService executorService;
+    private DeviceDataApi deviceDataApi;
 
     public HttpPlugin(PluginWrapper wrapper) {
         super(wrapper);
+        // 创建单线程池
+        this.executorService = Executors.newSingleThreadExecutor();
     }
 
     @Override
     public void start() {
         log.info("HttpPlugin.start()");
-        // for testing the development mode
-        if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
-            log.info("HttpPlugin in DEVELOPMENT mode");
+
+        // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例
+        deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
+        if (deviceDataApi == null) {
+            log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");
+            return;
         }
-        startHttpServer();
+
+        // 异步启动 Netty 服务器
+        executorService.submit(this::startHttpServer);
     }
 
     @Override
     public void stop() {
         log.info("HttpPlugin.stop()");
-        stopHttpServer();
+        // 停止线程池
+        executorService.shutdownNow();
     }
 
+    /**
+     * 启动 HTTP 服务
+     */
     private void startHttpServer() {
+        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+        EventLoopGroup workerGroup = new NioEventLoopGroup();
+
         try {
-            server = HttpServer.create(new InetSocketAddress(9081), 0);
-            server.createContext("/", exchange -> {
-                String response = "Welcome to PF4J HTTP Server";
-                exchange.sendResponseHeaders(200, response.getBytes().length);
-                OutputStream os = exchange.getResponseBody();
-                os.write(response.getBytes());
-                os.close();
-            });
-            server.setExecutor(null);
-            server.start();
-            log.info("HTTP server started on port 9081");
-        } catch (IOException e) {
-            log.error("Error starting HTTP server", e);
-        }
-    }
+            ServerBootstrap bootstrap = new ServerBootstrap();
+            bootstrap.group(bossGroup, workerGroup)
+                    .channel(NioServerSocketChannel.class)
+                    .childHandler(new ChannelInitializer<>() {
 
-    private void stopHttpServer() {
-        if (server != null) {
-            server.stop(0);
-            log.info("HTTP server stopped");
-        }
-    }
+                        @Override
+                        protected void initChannel(Channel channel) {
+                            channel.pipeline().addLast(new HttpServerCodec());
+                            channel.pipeline().addLast(new HttpObjectAggregator(65536));
+                            // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器
+                            channel.pipeline().addLast(new HttpHandler(deviceDataApi));
+                        }
 
-    @Extension
-    public static class WelcomeGreeting implements Greeting {
+                    });
 
-        @Override
-        public String getGreeting() {
-            return "Welcome to PF4J";
+            // 绑定端口并启动服务器
+            ChannelFuture future = bootstrap.bind(PORT).sync();
+            log.info("HTTP 服务器启动成功,端口为: {}", PORT);
+            future.channel().closeFuture().sync();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("HTTP 服务启动中断", e);
+        } finally {
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
         }
-
     }
 
-}
+}