Commit 7bfa83062859d0e32e58cae406d60b66f7593e8c

Authored by 安浩浩
1 parent 269dec1b

【代码优化】重构 HTTP插件并添加自动配置

plugins/yudao-module-iot-plugin-http-1.0.0.jar
No preview for this file type
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
40 40 @Slf4j
41 41 public class PluginInstanceServiceImpl implements PluginInstanceService {
42 42  
43   - // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的
44   - // 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件;
45   - // 那就 mac@uuid ?
  43 + // TODO @haohao:mac@uuid
46 44 public static final String MAIN_ID = IdUtil.fastSimpleUUID();
47 45  
48 46 @Resource
... ... @@ -60,32 +58,31 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
60 58 @Override
61 59 public void stopAndUnloadPlugin(String pluginKey) {
62 60 PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
63   - // TODO @haohao:改成 if return 会更简洁一点;
64   - if (plugin != null) {
65   - if (plugin.getPluginState().equals(PluginState.STARTED)) {
66   - pluginManager.stopPlugin(pluginKey); // 停止插件
67   - log.info("已停止插件: {}", pluginKey);
68   - }
69   - pluginManager.unloadPlugin(pluginKey); // 卸载插件
70   - log.info("已卸载插件: {}", pluginKey);
71   - } else {
  61 + if (plugin == null) {
72 62 log.warn("插件不存在或已卸载: {}", pluginKey);
  63 + return;
  64 + }
  65 + if (plugin.getPluginState().equals(PluginState.STARTED)) {
  66 + pluginManager.stopPlugin(pluginKey); // 停止插件
  67 + log.info("已停止插件: {}", pluginKey);
73 68 }
  69 + pluginManager.unloadPlugin(pluginKey); // 卸载插件
  70 + log.info("已卸载插件: {}", pluginKey);
74 71 }
75 72  
76 73 @Override
77 74 public void deletePluginFile(PluginInfoDO pluginInfoDO) {
78 75 File file = new File(pluginsDir, pluginInfoDO.getFileName());
79   - // TODO @haohao:改成 if return 会更简洁一点;
80   - if (file.exists()) {
81   - try {
82   - TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
83   - if (!file.delete()) {
84   - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
85   - }
86   - } catch (InterruptedException e) {
87   - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
  76 + if (!file.exists()) {
  77 + return;
  78 + }
  79 + try {
  80 + TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
  81 + if (!file.delete()) {
  82 + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
88 83 }
  84 + } catch (InterruptedException e) {
  85 + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
89 86 }
90 87 }
91 88  
... ... @@ -120,25 +117,25 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
120 117 String pluginKey = pluginInfoDo.getPluginKey();
121 118 PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
122 119  
123   - // TODO @haohao:改成 if return 会更简洁一点;
124   - if (plugin != null) {
125   - // 启动插件
126   - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
127   - && plugin.getPluginState() != PluginState.STARTED) {
128   - pluginManager.startPlugin(pluginKey);
129   - log.info("已启动插件: {}", pluginKey);
130   - }
131   - // 停止插件
132   - else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
133   - && plugin.getPluginState() == PluginState.STARTED) {
134   - pluginManager.stopPlugin(pluginKey);
135   - log.info("已停止插件: {}", pluginKey);
136   - }
137   - } else {
  120 + if (plugin == null) {
138 121 // 插件不存在且状态为停止,抛出异常
139 122 if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
140 123 throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
141 124 }
  125 + return;
  126 + }
  127 +
  128 + // 启动插件
  129 + if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
  130 + && plugin.getPluginState() != PluginState.STARTED) {
  131 + pluginManager.startPlugin(pluginKey);
  132 + log.info("已启动插件: {}", pluginKey);
  133 + }
  134 + // 停止插件
  135 + else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
  136 + && plugin.getPluginState() == PluginState.STARTED) {
  137 + pluginManager.stopPlugin(pluginKey);
  138 + log.info("已停止插件: {}", pluginKey);
142 139 }
143 140 }
144 141  
... ... @@ -152,10 +149,10 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
152 149 Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream()
153 150 .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity()));
154 151  
155   - // 1.3 获取本机 IP 和 MAC 地址
  152 + // 1.3 获取本机 IP 和 MAC 地址,mac@uuid
156 153 String ip = NetUtil.getLocalhostStr();
157 154 String mac = NetUtil.getLocalMacAddress();
158   - String mainId = MAIN_ID + "-" + mac;
  155 + String mainId = mac + "@" + MAIN_ID;
159 156  
160 157 // 2. 遍历插件列表,并保存为插件实例
161 158 for (PluginWrapper plugin : plugins) {
... ... @@ -173,14 +170,21 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
173 170 pluginInfo.getId());
174 171 if (pluginInstance == null) {
175 172 // 4.4 如果插件实例不存在,则创建
176   - pluginInstance = PluginInstanceDO.builder().pluginId(pluginInfo.getId()).mainId(MAIN_ID + "-" + mac)
177   - .ip(ip).port(port).heartbeatAt(System.currentTimeMillis()).build();
  173 + pluginInstance = PluginInstanceDO.builder()
  174 + .pluginId(pluginInfo.getId())
  175 + .mainId(MAIN_ID + "-" + mac)
  176 + .ip(ip)
  177 + .port(port)
  178 + .heartbeatAt(System.currentTimeMillis())
  179 + .build();
178 180 pluginInstanceMapper.insert(pluginInstance);
179 181 } else {
180 182 // 2.2 情况二:如果存在,则更新 heartbeatAt
181   - // TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有)
182   - pluginInstance.setHeartbeatAt(System.currentTimeMillis());
183   - pluginInstanceMapper.updateById(pluginInstance);
  183 + PluginInstanceDO updatePluginInstance = PluginInstanceDO.builder()
  184 + .id(pluginInstance.getId())
  185 + .heartbeatAt(System.currentTimeMillis())
  186 + .build();
  187 + pluginInstanceMapper.updateById(updatePluginInstance);
184 188 }
185 189 }
186 190 }
... ...
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;
5 5 import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO;
6 6 import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
7 7 import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
  8 +import lombok.RequiredArgsConstructor;
8 9 import lombok.extern.slf4j.Slf4j;
9 10 import org.springframework.web.client.RestTemplate;
10 11  
11 12 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
12 13  
13   -// TODO @haohao:类注释,写一下,比较好
  14 +/**
  15 + * 用于通过 {@link RestTemplate} 向远程 IoT 服务发送设备数据相关的请求,
  16 + * 包括设备状态更新、事件数据上报、属性数据上报等操作。
  17 + */
14 18 @Slf4j
  19 +@RequiredArgsConstructor
15 20 public class DeviceDataApiClient implements DeviceDataApi {
16 21  
  22 + /**
  23 + * 用于发送 HTTP 请求的工具
  24 + */
17 25 private final RestTemplate restTemplate;
18   - private final String deviceDataUrl;
19 26  
20   - // 可以通过构造器把 RestTemplate 和 baseUrl 注入进来
21   - // TODO @haohao:可以用 lombok 简化
22   - public DeviceDataApiClient(RestTemplate restTemplate, String deviceDataUrl) {
23   - this.restTemplate = restTemplate;
24   - this.deviceDataUrl = deviceDataUrl;
25   - }
  27 + /**
  28 + * 远程 IoT 服务的基础 URL
  29 + * 例如:http://127.0.0.1:8080
  30 + */
  31 + private final String deviceDataUrl;
26 32  
27 33 // TODO @haohao:返回结果,不用 CommonResult 哈。
28 34 @Override
... ... @@ -43,17 +49,51 @@ public class DeviceDataApiClient implements DeviceDataApi {
43 49 return doPost(url, reportReqDTO, "reportDevicePropertyData");
44 50 }
45 51  
46   - // TODO @haohao:未来可能有 get 类型哈
  52 +
  53 + /**
  54 + * 发送 GET 请求
  55 + *
  56 + * @param <T> 请求体类型
  57 + * @param url 请求 URL
  58 + * @param requestBody 请求体
  59 + * @param actionName 操作名称
  60 + * @return 响应结果
  61 + */
  62 + private <T> CommonResult<Boolean> doGet(String url, T requestBody, String actionName) {
  63 + log.info("[{}] Sending request to URL: {}", actionName, url);
  64 + try {
  65 + CommonResult<?> response = restTemplate.getForObject(url, CommonResult.class);
  66 + if (response != null && response.isSuccess()) {
  67 + return success(true);
  68 + } else {
  69 + log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response);
  70 + return CommonResult.error(500, "Request failed");
  71 + }
  72 + } catch (Exception e) {
  73 + log.error("[{}] Error sending request to URL: {}", actionName, url, e);
  74 + return CommonResult.error(400, "Request error: " + e.getMessage());
  75 + }
  76 + }
  77 +
47 78 /**
48   - * 将与远程服务交互的通用逻辑抽取成一个私有方法
  79 + * 发送 POST 请求
  80 + *
  81 + * @param <T> 请求体类型
  82 + * @param url 请求 URL
  83 + * @param requestBody 请求体
  84 + * @param actionName 操作名称
  85 + * @return 响应结果
49 86 */
50 87 private <T> CommonResult<Boolean> doPost(String url, T requestBody, String actionName) {
51 88 log.info("[{}] Sending request to URL: {}", actionName, url);
52 89 try {
53   - // 这里指定返回类型为 CommonResult<?>,根据后台服务返回的实际结构做调整
54   - restTemplate.postForObject(url, requestBody, CommonResult.class);
55   - // TODO @haohao:check 结果,是否成功
56   - return success(true);
  90 + CommonResult<?> response = restTemplate.postForObject(url, requestBody, CommonResult.class);
  91 + if (response != null && response.isSuccess()) {
  92 + return success(true);
  93 + } else {
  94 + log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response);
  95 + return CommonResult.error(500, "Request failed");
  96 + }
57 97 } catch (Exception e) {
58 98 log.error("[{}] Error sending request to URL: {}", actionName, url, e);
59 99 return CommonResult.error(400, "Request error: " + e.getMessage());
... ...
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 renamed to 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
... ... @@ -3,29 +3,49 @@ package cn.iocoder.yudao.module.iot.plugin.common.config;
3 3 import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
4 4 import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient;
5 5 import org.springframework.beans.factory.annotation.Value;
  6 +import org.springframework.boot.autoconfigure.AutoConfiguration;
6 7 import org.springframework.boot.web.client.RestTemplateBuilder;
7 8 import org.springframework.context.annotation.Bean;
8   -import org.springframework.context.annotation.Configuration;
9 9 import org.springframework.web.client.RestTemplate;
10 10  
11   -// TODO @haohao:这个最好是 autoconfiguration
12   -@Configuration
13   -public class DeviceDataApiInitializer {
  11 +import java.time.Duration;
  12 +
  13 +/**
  14 + * 设备数据 API 初始化器
  15 + *
  16 + * @author haohao
  17 + */
  18 +@AutoConfiguration
  19 +public class YudaoDeviceDataApiAutoConfiguration {
  20 +
14 21  
15 22 // TODO @haohao:这个要不搞个配置类哈
16 23 @Value("${iot.device-data.url}")
17 24 private String deviceDataUrl;
18 25  
  26 + /**
  27 + * 创建 RestTemplate 实例
  28 + *
  29 + * @return RestTemplate 实例
  30 + */
19 31 @Bean
20 32 public RestTemplate restTemplate() {
21   - // TODO haohao:如果你有更多的自定义需求,比如连接池、超时时间等,可以在这里设置
22   - return new RestTemplateBuilder().build();
  33 + // 如果你有更多的自定义需求,比如连接池、超时时间等,可以在这里设置
  34 + return new RestTemplateBuilder()
  35 + .setConnectTimeout(Duration.ofMillis(5000)) // 设置连接超时时间
  36 + .setReadTimeout(Duration.ofMillis(5000)) // 设置读取超时时间
  37 + .build();
23 38 }
24 39  
25   - // TODO @haohao:不存在时,才构建
  40 + /**
  41 + * 创建 DeviceDataApi 实例
  42 + *
  43 + * @param restTemplate RestTemplate 实例
  44 + * @return DeviceDataApi 实例
  45 + */
26 46 @Bean
27 47 public DeviceDataApi deviceDataApi(RestTemplate restTemplate) {
28 48 return new DeviceDataApiClient(restTemplate, deviceDataUrl);
29 49 }
30 50  
31 51 -}
  52 +}
32 53 \ No newline at end of file
... ...
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 → 100644
  1 +cn.iocoder.yudao.module.iot.plugin.common.config.YudaoDeviceDataApiAutoConfiguration
0 2 \ No newline at end of file
... ...
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;
11 11 * 独立运行入口
12 12 */
13 13 @Slf4j
14   -@SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin") // TODO @haohao:建议不扫描 cn.iocoder.yudao.module.iot.plugin;而是通过自动配置,初始化 common 的
  14 +@SpringBootApplication
15 15 public class HttpPluginSpringbootApplication {
16 16  
17 17 public static void main(String[] args) {
... ... @@ -21,6 +21,7 @@ public class HttpPluginSpringbootApplication {
21 21  
22 22 // 手动获取 VertxService 并启动
23 23 // TODO @haohao:可以放在 bean 的 init 里么?
  24 + // 会和插件模式冲突
24 25 VertxService vertxService = context.getBean(VertxService.class);
25 26 vertxService.startServer();
26 27  
... ...
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 {
21 21  
22 22 @Override
23 23 public void start() {
24   - // TODO @haohao:这种最好启动中,启动完成,成对打印日志,方便定位问题
25   - log.info("[HttpVertxPlugin][start ...]");
  24 + log.info("[HttpVertxPlugin][start][begin] 开始启动 HttpVertxPlugin 插件...");
26 25  
27   - // 1. 获取插件上下文
28   - ApplicationContext pluginContext = getApplicationContext();
29   - if (pluginContext == null) {
30   - log.error("[HttpVertxPlugin] pluginContext is null, start failed.");
31   - return;
32   - }
  26 + try {
  27 + // 1. 获取插件上下文
  28 + ApplicationContext pluginContext = getApplicationContext();
  29 + if (pluginContext == null) {
  30 + log.error("[HttpVertxPlugin][start][fail] pluginContext is null, 启动失败!");
  31 + return;
  32 + }
33 33  
34   - // 2. 启动 Vertx
35   - VertxService vertxService = pluginContext.getBean(VertxService.class);
36   - vertxService.startServer();
37   - }
  34 + // 2. 启动 Vert.x
  35 + VertxService vertxService = pluginContext.getBean(VertxService.class);
  36 + vertxService.startServer();
38 37  
  38 + log.info("[HttpVertxPlugin][start][end] 启动完成");
  39 + } catch (Exception e) {
  40 + log.error("[HttpVertxPlugin][start][exception] 启动过程出现异常!", e);
  41 + }
  42 + }
39 43  
40 44 @Override
41 45 public void stop() {
42   - log.info("[HttpVertxPlugin][stop ...]");
43   - ApplicationContext pluginContext = getApplicationContext();
44   - if (pluginContext != null) {
45   - // 停止服务器
46   - VertxService vertxService = pluginContext.getBean(VertxService.class);
47   - vertxService.stopServer();
  46 + log.info("[HttpVertxPlugin][stop][begin] 开始停止 HttpVertxPlugin 插件...");
  47 +
  48 + try {
  49 + ApplicationContext pluginContext = getApplicationContext();
  50 + if (pluginContext != null) {
  51 + // 停止服务器
  52 + VertxService vertxService = pluginContext.getBean(VertxService.class);
  53 + vertxService.stopServer();
  54 + }
  55 +
  56 + log.info("[HttpVertxPlugin][stop][end] 停止完成");
  57 + } catch (Exception e) {
  58 + log.error("[HttpVertxPlugin][stop][exception] 停止过程出现异常!", e);
48 59 }
49 60 }
50 61  
... ... @@ -68,5 +79,4 @@ public class HttpVertxPlugin extends SpringPlugin {
68 79  
69 80 return pluginContext;
70 81 }
71   -
72   -}
  82 +}
73 83 \ No newline at end of file
... ...
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 {
34 34  
35 35 /**
36 36 * 创建路由
  37 + *
  38 + * @param vertx Vertx 实例
  39 + * @param httpVertxHandler HttpVertxHandler 实例
  40 + * @return Router 实例
37 41 */
38 42 @Bean
39 43 public Router router(Vertx vertx, HttpVertxHandler httpVertxHandler) {
... ... @@ -50,6 +54,12 @@ public class HttpVertxPluginConfiguration {
50 54 return router;
51 55 }
52 56  
  57 + /**
  58 + * 创建 HttpVertxHandler 实例
  59 + *
  60 + * @param deviceDataApi DeviceDataApi 实例
  61 + * @return HttpVertxHandler 实例
  62 + */
53 63 @Bean
54 64 public HttpVertxHandler httpVertxHandler(DeviceDataApi deviceDataApi) {
55 65 return new HttpVertxHandler(deviceDataApi);
... ... @@ -58,6 +68,10 @@ public class HttpVertxPluginConfiguration {
58 68 /**
59 69 * 定义一个 VertxService 来管理服务器启动逻辑
60 70 * 无论是独立运行还是插件方式,都可以共用此类
  71 + *
  72 + * @param vertx Vertx 实例
  73 + * @param router Router 实例
  74 + * @return VertxService 实例
61 75 */
62 76 @Bean
63 77 public VertxService vertxService(Vertx vertx, Router router) {
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml
... ... @@ -5,3 +5,8 @@ spring:
5 5 iot:
6 6 device-data:
7 7 url: http://127.0.0.1:48080
  8 +
  9 +plugin:
  10 + http:
  11 + server:
  12 + port: 8092
... ...