diff --git a/plugins/disabled.txt b/plugins/disabled.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/enabled.txt b/plugins/enabled.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar b/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar new file mode 100644 index 0000000000..0300a632ae Binary files /dev/null and b/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar differ diff --git a/yudao-module-iot/pom.xml b/yudao-module-iot/pom.xml index ecfefab64d..d9002abea5 100644 --- a/yudao-module-iot/pom.xml +++ b/yudao-module-iot/pom.xml @@ -10,7 +10,6 @@ yudao-module-iot-api yudao-module-iot-biz - yudao-module-iot-plugin-api yudao-module-iot-plugin 4.0.0 diff --git a/yudao-module-iot/yudao-module-iot-api/pom.xml b/yudao-module-iot/yudao-module-iot-api/pom.xml index 8a75b09bf9..d2f83b785e 100644 --- a/yudao-module-iot/yudao-module-iot-api/pom.xml +++ b/yudao-module-iot/yudao-module-iot-api/pom.xml @@ -21,6 +21,18 @@ cn.iocoder.boot yudao-common + + + + org.pf4j + pf4j-spring + + + org.slf4j + slf4j-log4j12 + + + diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java new file mode 100644 index 0000000000..5603ad8d72 --- /dev/null +++ b/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, Object> services = new HashMap<>(); + + /** + * 注册服务 + * + * @param serviceClass 服务类 + * @param serviceImpl 服务实现 + * @param 服务类 + */ + public static void registerService(Class serviceClass, T serviceImpl) { + services.put(serviceClass, serviceImpl); + } + + /** + * 获得服务 + * + * @param serviceClass 服务类 + * @param 服务类 + * @return 服务实现 + */ + @SuppressWarnings("unchecked") + public static T getService(Class serviceClass) { + return (T) services.get(serviceClass); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java new file mode 100644 index 0000000000..cb747f5053 --- /dev/null +++ b/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); + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java index ef1432804a..8a5aaf74bf 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java +++ b/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, "网关设备"); /** diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index 774d353809..19b50ec215 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -81,6 +81,13 @@ org.pf4j pf4j-spring + + + + org.slf4j + slf4j-log4j12 + + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java new file mode 100644 index 0000000000..d62c20cb54 --- /dev/null +++ b/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); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java similarity index 55% rename from yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java index 7da0c665ba..07852180d4 100644 --- a/yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java +++ b/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; \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/Greetings.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/Greetings.java deleted file mode 100644 index 1b29a34475..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/Greetings.java +++ /dev/null @@ -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 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(); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java index 4cd22bccaf..321eef9b6f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java +++ b/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 printGreetings() { - Integer count = greetings.printGreetings(); - return ResponseEntity.ok(count); - } -} +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/WhazzupGreeting.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/WhazzupGreeting.java deleted file mode 100644 index b6ca7f322b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/WhazzupGreeting.java +++ /dev/null @@ -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"; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java index e71ff29045..92edca821d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java +++ b/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.*; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java index 8d927045d5..95db299d19 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java +++ b/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") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThinkModelMessageMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java similarity index 93% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThinkModelMessageMapper.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java index da9730e74b..0a735be03a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThinkModelMessageMapper.java +++ b/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 { /** * 创建物模型消息日志超级表超级表 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java new file mode 100644 index 0000000000..3450b67fb9 --- /dev/null +++ b/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(); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java index db03332d90..d87a94f318 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java +++ b/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(); - } - } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/simulatesend/SimulateSendConsumer.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/simulatesend/SimulateSendConsumer.java new file mode 100644 index 0000000000..111cf50073 --- /dev/null +++ b/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 { +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java index 70fd23014e..1c246e2c70 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java +++ b/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 消息 + *

参见 JSON 格式 */ void saveDeviceData(String productKey, String deviceName, String message); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java index daa76acc63..bd7efa8d0c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java +++ b/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); +// } +// } +// } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java index 405efe1636..afb05ef2f8 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java +++ b/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 实现类 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java index 568cad5020..ad909c437f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java +++ b/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 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 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 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)); } /** diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java index 8feef7bf60..a409c80692 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java +++ b/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()); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml index aeecd5dc1e..a0cc12712f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml @@ -2,10 +2,9 @@ - + - CREATE STABLE ${dataBaseName}.${superTableName}( ts TIMESTAMP, @@ -14,7 +13,7 @@ method VARCHAR(255), params VARCHAR(2048) )TAGS ( - deviceKey VARCHAR(255) + device_key VARCHAR(255) ) @@ -28,7 +27,7 @@ method , params )TAGS( - #{deviceKey} + #{device_key} ) \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin-api/pom.xml b/yudao-module-iot/yudao-module-iot-plugin-api/pom.xml deleted file mode 100644 index 6d5eb765b1..0000000000 --- a/yudao-module-iot/yudao-module-iot-plugin-api/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - cn.iocoder.boot - yudao-module-iot-plugin-api - 0.0.1 - jar - - ${project.artifactId} - - 物联网 模块插件 API,暴露给其它模块调用 - - - - 0.9.0 - - - - - - org.pf4j - pf4j-spring - ${pf4j-spring.version} - provided - - - - diff --git a/yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/Greeting.java b/yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/Greeting.java deleted file mode 100644 index b284549373..0000000000 --- a/yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/Greeting.java +++ /dev/null @@ -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(); - -} diff --git a/yudao-module-iot/yudao-module-iot-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/pom.xml index 8ec68638f3..c8f0ff0fe8 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/pom.xml +++ b/yudao-module-iot/yudao-module-iot-plugin/pom.xml @@ -2,19 +2,29 @@ - 4.0.0 - cn.iocoder.boot - yudao-module-iot-plugin - 0.0.1 - pom - + + + + + + + yudao-module-iot + cn.iocoder.boot + ${revision} + + yudao-module-iot-demo-plugin yudao-module-iot-http-plugin + 4.0.0 + + yudao-module-iot-plugin + pom + ${project.artifactId} - 物联网模块 - 插件模块 + 物联网 插件 模块 \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/plugin.properties b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/plugin.properties new file mode 100644 index 0000000000..5a67270bb0 --- /dev/null +++ b/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 diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/pom.xml new file mode 100644 index 0000000000..3d58a1a75e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/pom.xml @@ -0,0 +1,148 @@ + + + + yudao-module-iot-plugin + cn.iocoder.boot + ${revision} + + 4.0.0 + jar + + yudao-module-iot-demo-plugin + + ${project.artifactId} + + 物联网 插件模块 - demo 插件 + + + + + demo-plugin + cn.iocoder.yudao.module.iot.plugin.DemoPlugin + 0.0.1 + ahh + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.6 + + + unzip jar file + package + + + + + + + run + + + + + + + maven-assembly-plugin + 2.3 + + + + src/main/assembly/assembly.xml + + + false + + + + make-assembly + package + + attached + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + ${plugin.id} + ${plugin.class} + ${plugin.version} + ${plugin.provider} + ${plugin.dependencies} + + + + + + + maven-deploy-plugin + + true + + + + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + provided + + + + org.pf4j + pf4j-spring + provided + + + + cn.iocoder.boot + yudao-module-iot-api + ${revision} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/assembly/assembly.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..daec9e4315 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/assembly/assembly.xml @@ -0,0 +1,31 @@ + + plugin + + zip + + false + + + false + runtime + lib + + *:jar:* + + + + + + + target/plugin-classes + classes + + + diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/DemoPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/DemoPlugin.java new file mode 100644 index 0000000000..c97a5b9b5e --- /dev/null +++ b/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"; +// } +// +// } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties index 694c97ba5f..4e1199acfc 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties +++ b/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 diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml index eccf47febd..200a451b62 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml +++ b/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"> + + yudao-module-iot-plugin + cn.iocoder.boot + ${revision} + 4.0.0 - cn.iocoder.boot - yudao-module-iot-http-plugin - 0.0.1 jar + yudao-module-iot-http-plugin + ${project.artifactId} - 物联网 模块 - http 插件 + + 物联网 插件模块 - http 插件 + @@ -19,50 +25,8 @@ 0.0.1 ahh - - - 17 - ${java.version} - ${java.version} - 1.6 - 2.3 - 2.4 - 0.9.0 - - 1.18.34 - 3.3.1 - UTF-8 - - - - org.springframework.boot - spring-boot-starter-web - ${spring.boot.version} - provided - - - - org.pf4j - pf4j-spring - ${pf4j-spring.version} - provided - - - - cn.iocoder.boot - yudao-module-iot-plugin-api - ${project.version} - - - org.projectlombok - lombok - ${lombok.version} - provided - - - + + org.springframework.boot + spring-boot-starter-web + + + + org.pf4j + pf4j-spring + provided + + + + cn.iocoder.boot + yudao-module-iot-api + ${revision} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + io.netty + netty-all + 4.1.63.Final + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/assembly/assembly.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/assembly/assembly.xml index ce2e92cf95..daec9e4315 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/assembly/assembly.xml +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/assembly/assembly.xml @@ -1,9 +1,3 @@ - plugin diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java new file mode 100644 index 0000000000..6d0908683b --- /dev/null +++ b/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 { + + 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); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java index 32926be452..70da0131ce 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java +++ b/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<>() { + + @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)); + } + + }); + + // 绑定端口并启动服务器 + 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(); } } - private void stopHttpServer() { - if (server != null) { - server.stop(0); - log.info("HTTP server stopped"); - } - } - - @Extension - public static class WelcomeGreeting implements Greeting { - - @Override - public String getGreeting() { - return "Welcome to PF4J"; - } - - } - -} \ No newline at end of file +}