From 7bfa83062859d0e32e58cae406d60b66f7593e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Sun, 26 Jan 2025 17:29:03 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E3=80=91=E9=87=8D=E6=9E=84=20HTTP=E6=8F=92=E4=BB=B6=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao-module-iot-plugin-http-1.0.0.jar | Bin 12284 -> 12522 bytes .../plugin/PluginInstanceServiceImpl.java | 90 +++++++++--------- .../common/api/DeviceDataApiClient.java | 68 ++++++++++--- .../config/DeviceDataApiInitializer.java | 31 ------ .../YudaoDeviceDataApiAutoConfiguration.java | 51 ++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../http/HttpPluginSpringbootApplication.java | 3 +- .../plugin/http/config/HttpVertxPlugin.java | 50 ++++++---- .../config/HttpVertxPluginConfiguration.java | 14 +++ .../src/main/resources/application.yml | 5 + 10 files changed, 204 insertions(+), 109 deletions(-) delete mode 100644 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 create mode 100644 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 create mode 100644 yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 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 d3c4facae3d94154105193a3a07734640566e65f..c504342cf851e2a8ca4ad345e8f7f090f795960e 100644 GIT binary patch delta 4924 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 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