diff --git a/plugins/yudao-module-iot-plugin-http-1.0.0.jar b/plugins/yudao-module-iot-plugin-http-1.0.0.jar index d3c4facae3..c504342cf8 100644 Binary files a/plugins/yudao-module-iot-plugin-http-1.0.0.jar and b/plugins/yudao-module-iot-plugin-http-1.0.0.jar differ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java index 65a6cf32ba..cf10d2e3d3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java @@ -40,9 +40,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU @Slf4j public class PluginInstanceServiceImpl implements PluginInstanceService { - // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 - // 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件; - // 那就 mac@uuid ? + // TODO @haohao:mac@uuid public static final String MAIN_ID = IdUtil.fastSimpleUUID(); @Resource @@ -60,32 +58,31 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { @Override public void stopAndUnloadPlugin(String pluginKey) { PluginWrapper plugin = pluginManager.getPlugin(pluginKey); - // TODO @haohao:改成 if return 会更简洁一点; - if (plugin != null) { - if (plugin.getPluginState().equals(PluginState.STARTED)) { - pluginManager.stopPlugin(pluginKey); // 停止插件 - log.info("已停止插件: {}", pluginKey); - } - pluginManager.unloadPlugin(pluginKey); // 卸载插件 - log.info("已卸载插件: {}", pluginKey); - } else { + if (plugin == null) { log.warn("插件不存在或已卸载: {}", pluginKey); + return; } + if (plugin.getPluginState().equals(PluginState.STARTED)) { + pluginManager.stopPlugin(pluginKey); // 停止插件 + log.info("已停止插件: {}", pluginKey); + } + pluginManager.unloadPlugin(pluginKey); // 卸载插件 + log.info("已卸载插件: {}", pluginKey); } @Override public void deletePluginFile(PluginInfoDO pluginInfoDO) { File file = new File(pluginsDir, pluginInfoDO.getFileName()); - // TODO @haohao:改成 if return 会更简洁一点; - if (file.exists()) { - try { - TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 - if (!file.delete()) { - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); - } - } catch (InterruptedException e) { - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); + if (!file.exists()) { + return; + } + try { + TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 + if (!file.delete()) { + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); } + } catch (InterruptedException e) { + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); } } @@ -120,25 +117,25 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { String pluginKey = pluginInfoDo.getPluginKey(); PluginWrapper plugin = pluginManager.getPlugin(pluginKey); - // TODO @haohao:改成 if return 会更简洁一点; - if (plugin != null) { - // 启动插件 - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) - && plugin.getPluginState() != PluginState.STARTED) { - pluginManager.startPlugin(pluginKey); - log.info("已启动插件: {}", pluginKey); - } - // 停止插件 - else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) - && plugin.getPluginState() == PluginState.STARTED) { - pluginManager.stopPlugin(pluginKey); - log.info("已停止插件: {}", pluginKey); - } - } else { + if (plugin == null) { // 插件不存在且状态为停止,抛出异常 if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID); } + return; + } + + // 启动插件 + if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) + && plugin.getPluginState() != PluginState.STARTED) { + pluginManager.startPlugin(pluginKey); + log.info("已启动插件: {}", pluginKey); + } + // 停止插件 + else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) + && plugin.getPluginState() == PluginState.STARTED) { + pluginManager.stopPlugin(pluginKey); + log.info("已停止插件: {}", pluginKey); } } @@ -152,10 +149,10 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { Map pluginInfoMap = pluginInfos.stream() .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); - // 1.3 获取本机 IP 和 MAC 地址 + // 1.3 获取本机 IP 和 MAC 地址,mac@uuid String ip = NetUtil.getLocalhostStr(); String mac = NetUtil.getLocalMacAddress(); - String mainId = MAIN_ID + "-" + mac; + String mainId = mac + "@" + MAIN_ID; // 2. 遍历插件列表,并保存为插件实例 for (PluginWrapper plugin : plugins) { @@ -173,14 +170,21 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { pluginInfo.getId()); if (pluginInstance == null) { // 4.4 如果插件实例不存在,则创建 - pluginInstance = PluginInstanceDO.builder().pluginId(pluginInfo.getId()).mainId(MAIN_ID + "-" + mac) - .ip(ip).port(port).heartbeatAt(System.currentTimeMillis()).build(); + pluginInstance = PluginInstanceDO.builder() + .pluginId(pluginInfo.getId()) + .mainId(MAIN_ID + "-" + mac) + .ip(ip) + .port(port) + .heartbeatAt(System.currentTimeMillis()) + .build(); pluginInstanceMapper.insert(pluginInstance); } else { // 2.2 情况二:如果存在,则更新 heartbeatAt - // TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有) - pluginInstance.setHeartbeatAt(System.currentTimeMillis()); - pluginInstanceMapper.updateById(pluginInstance); + PluginInstanceDO updatePluginInstance = PluginInstanceDO.builder() + .id(pluginInstance.getId()) + .heartbeatAt(System.currentTimeMillis()) + .build(); + pluginInstanceMapper.updateById(updatePluginInstance); } } } diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/api/DeviceDataApiClient.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/api/DeviceDataApiClient.java index f63267b27b..9fdb29ea85 100644 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/api/DeviceDataApiClient.java +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/api/DeviceDataApiClient.java @@ -5,24 +5,30 @@ import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.client.RestTemplate; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -// TODO @haohao:类注释,写一下,比较好 +/** + * 用于通过 {@link RestTemplate} 向远程 IoT 服务发送设备数据相关的请求, + * 包括设备状态更新、事件数据上报、属性数据上报等操作。 + */ @Slf4j +@RequiredArgsConstructor public class DeviceDataApiClient implements DeviceDataApi { + /** + * 用于发送 HTTP 请求的工具 + */ private final RestTemplate restTemplate; - private final String deviceDataUrl; - // 可以通过构造器把 RestTemplate 和 baseUrl 注入进来 - // TODO @haohao:可以用 lombok 简化 - public DeviceDataApiClient(RestTemplate restTemplate, String deviceDataUrl) { - this.restTemplate = restTemplate; - this.deviceDataUrl = deviceDataUrl; - } + /** + * 远程 IoT 服务的基础 URL + * 例如:http://127.0.0.1:8080 + */ + private final String deviceDataUrl; // TODO @haohao:返回结果,不用 CommonResult 哈。 @Override @@ -43,17 +49,51 @@ public class DeviceDataApiClient implements DeviceDataApi { return doPost(url, reportReqDTO, "reportDevicePropertyData"); } - // TODO @haohao:未来可能有 get 类型哈 + /** - * 将与远程服务交互的通用逻辑抽取成一个私有方法 + * 发送 GET 请求 + * + * @param 请求体类型 + * @param url 请求 URL + * @param requestBody 请求体 + * @param actionName 操作名称 + * @return 响应结果 + */ + private CommonResult doGet(String url, T requestBody, String actionName) { + log.info("[{}] Sending request to URL: {}", actionName, url); + try { + CommonResult response = restTemplate.getForObject(url, CommonResult.class); + if (response != null && response.isSuccess()) { + return success(true); + } else { + log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response); + return CommonResult.error(500, "Request failed"); + } + } catch (Exception e) { + log.error("[{}] Error sending request to URL: {}", actionName, url, e); + return CommonResult.error(400, "Request error: " + e.getMessage()); + } + } + + /** + * 发送 POST 请求 + * + * @param 请求体类型 + * @param url 请求 URL + * @param requestBody 请求体 + * @param actionName 操作名称 + * @return 响应结果 */ private CommonResult doPost(String url, T requestBody, String actionName) { log.info("[{}] Sending request to URL: {}", actionName, url); try { - // 这里指定返回类型为 CommonResult,根据后台服务返回的实际结构做调整 - restTemplate.postForObject(url, requestBody, CommonResult.class); - // TODO @haohao:check 结果,是否成功 - return success(true); + CommonResult response = restTemplate.postForObject(url, requestBody, CommonResult.class); + if (response != null && response.isSuccess()) { + return success(true); + } else { + log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response); + return CommonResult.error(500, "Request failed"); + } } catch (Exception e) { log.error("[{}] Error sending request to URL: {}", actionName, url, e); return CommonResult.error(400, "Request error: " + e.getMessage()); diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/DeviceDataApiInitializer.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/DeviceDataApiInitializer.java deleted file mode 100644 index ed39449306..0000000000 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/DeviceDataApiInitializer.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.iot.plugin.common.config; - -import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; -import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.client.RestTemplate; - -// TODO @haohao:这个最好是 autoconfiguration -@Configuration -public class DeviceDataApiInitializer { - - // TODO @haohao:这个要不搞个配置类哈 - @Value("${iot.device-data.url}") - private String deviceDataUrl; - - @Bean - public RestTemplate restTemplate() { - // TODO haohao:如果你有更多的自定义需求,比如连接池、超时时间等,可以在这里设置 - return new RestTemplateBuilder().build(); - } - - // TODO @haohao:不存在时,才构建 - @Bean - public DeviceDataApi deviceDataApi(RestTemplate restTemplate) { - return new DeviceDataApiClient(restTemplate, deviceDataUrl); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/YudaoDeviceDataApiAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/YudaoDeviceDataApiAutoConfiguration.java new file mode 100644 index 0000000000..6ba82ed5dd --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/YudaoDeviceDataApiAutoConfiguration.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.iot.plugin.common.config; + +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; +import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +import java.time.Duration; + +/** + * 设备数据 API 初始化器 + * + * @author haohao + */ +@AutoConfiguration +public class YudaoDeviceDataApiAutoConfiguration { + + + // TODO @haohao:这个要不搞个配置类哈 + @Value("${iot.device-data.url}") + private String deviceDataUrl; + + /** + * 创建 RestTemplate 实例 + * + * @return RestTemplate 实例 + */ + @Bean + public RestTemplate restTemplate() { + // 如果你有更多的自定义需求,比如连接池、超时时间等,可以在这里设置 + return new RestTemplateBuilder() + .setConnectTimeout(Duration.ofMillis(5000)) // 设置连接超时时间 + .setReadTimeout(Duration.ofMillis(5000)) // 设置读取超时时间 + .build(); + } + + /** + * 创建 DeviceDataApi 实例 + * + * @param restTemplate RestTemplate 实例 + * @return DeviceDataApi 实例 + */ + @Bean + public DeviceDataApi deviceDataApi(RestTemplate restTemplate) { + return new DeviceDataApiClient(restTemplate, deviceDataUrl); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..65bd7ad7dd --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +cn.iocoder.yudao.module.iot.plugin.common.config.YudaoDeviceDataApiAutoConfiguration \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java index 91be33097d..062b01808b 100644 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java @@ -11,7 +11,7 @@ import org.springframework.context.ConfigurableApplicationContext; * 独立运行入口 */ @Slf4j -@SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin") // TODO @haohao:建议不扫描 cn.iocoder.yudao.module.iot.plugin;而是通过自动配置,初始化 common 的 +@SpringBootApplication public class HttpPluginSpringbootApplication { public static void main(String[] args) { @@ -21,6 +21,7 @@ public class HttpPluginSpringbootApplication { // 手动获取 VertxService 并启动 // TODO @haohao:可以放在 bean 的 init 里么? + // 会和插件模式冲突 VertxService vertxService = context.getBean(VertxService.class); vertxService.startServer(); diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPlugin.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPlugin.java index 40694cf40c..9cc96ef102 100644 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPlugin.java +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPlugin.java @@ -21,30 +21,41 @@ public class HttpVertxPlugin extends SpringPlugin { @Override public void start() { - // TODO @haohao:这种最好启动中,启动完成,成对打印日志,方便定位问题 - log.info("[HttpVertxPlugin][start ...]"); + log.info("[HttpVertxPlugin][start][begin] 开始启动 HttpVertxPlugin 插件..."); - // 1. 获取插件上下文 - ApplicationContext pluginContext = getApplicationContext(); - if (pluginContext == null) { - log.error("[HttpVertxPlugin] pluginContext is null, start failed."); - return; + try { + // 1. 获取插件上下文 + ApplicationContext pluginContext = getApplicationContext(); + if (pluginContext == null) { + log.error("[HttpVertxPlugin][start][fail] pluginContext is null, 启动失败!"); + return; + } + + // 2. 启动 Vert.x + VertxService vertxService = pluginContext.getBean(VertxService.class); + vertxService.startServer(); + + log.info("[HttpVertxPlugin][start][end] 启动完成"); + } catch (Exception e) { + log.error("[HttpVertxPlugin][start][exception] 启动过程出现异常!", e); } - - // 2. 启动 Vertx - VertxService vertxService = pluginContext.getBean(VertxService.class); - vertxService.startServer(); } - @Override public void stop() { - log.info("[HttpVertxPlugin][stop ...]"); - ApplicationContext pluginContext = getApplicationContext(); - if (pluginContext != null) { - // 停止服务器 - VertxService vertxService = pluginContext.getBean(VertxService.class); - vertxService.stopServer(); + log.info("[HttpVertxPlugin][stop][begin] 开始停止 HttpVertxPlugin 插件..."); + + try { + ApplicationContext pluginContext = getApplicationContext(); + if (pluginContext != null) { + // 停止服务器 + VertxService vertxService = pluginContext.getBean(VertxService.class); + vertxService.stopServer(); + } + + log.info("[HttpVertxPlugin][stop][end] 停止完成"); + } catch (Exception e) { + log.error("[HttpVertxPlugin][stop][exception] 停止过程出现异常!", e); } } @@ -68,5 +79,4 @@ public class HttpVertxPlugin extends SpringPlugin { return pluginContext; } - -} +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPluginConfiguration.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPluginConfiguration.java index 5c221e795a..e61a4cf8ff 100644 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPluginConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPluginConfiguration.java @@ -34,6 +34,10 @@ public class HttpVertxPluginConfiguration { /** * 创建路由 + * + * @param vertx Vertx 实例 + * @param httpVertxHandler HttpVertxHandler 实例 + * @return Router 实例 */ @Bean public Router router(Vertx vertx, HttpVertxHandler httpVertxHandler) { @@ -50,6 +54,12 @@ public class HttpVertxPluginConfiguration { return router; } + /** + * 创建 HttpVertxHandler 实例 + * + * @param deviceDataApi DeviceDataApi 实例 + * @return HttpVertxHandler 实例 + */ @Bean public HttpVertxHandler httpVertxHandler(DeviceDataApi deviceDataApi) { return new HttpVertxHandler(deviceDataApi); @@ -58,6 +68,10 @@ public class HttpVertxPluginConfiguration { /** * 定义一个 VertxService 来管理服务器启动逻辑 * 无论是独立运行还是插件方式,都可以共用此类 + * + * @param vertx Vertx 实例 + * @param router Router 实例 + * @return VertxService 实例 */ @Bean public VertxService vertxService(Vertx vertx, Router router) { diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml index e98d46eebe..3a64c0f37e 100644 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml @@ -5,3 +5,8 @@ spring: iot: device-data: url: http://127.0.0.1:48080 + +plugin: + http: + server: + port: 8092