Commit b5856c4cfc3a55e89928cb313670210af64a5f7c

Authored by YunaiV
1 parent 603649d2

【代码评审】IoT:插件机制

Showing 15 changed files with 86 additions and 75 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.api;
3 3 import java.util.HashMap;
4 4 import java.util.Map;
5 5  
  6 +// TODO 芋艿:纠结下
6 7 /**
7 8 * 服务注册表 - 插架模块使用,无法使用 Spring 注入
8 9 */
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java
... ... @@ -2,9 +2,12 @@ package cn.iocoder.yudao.module.iot.api.device;
2 2  
3 3 /**
4 4 * 设备数据 API
  5 + *
  6 + * @author haohao
5 7 */
6 8 public interface DeviceDataApi {
7 9  
  10 + // TODO @haohao:最好搞成 dto 哈!
8 11 /**
9 12 * 保存设备数据
10 13 *
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java
... ... @@ -5,9 +5,9 @@ import lombok.Builder;
5 5 import lombok.Data;
6 6 import lombok.NoArgsConstructor;
7 7  
  8 +// TODO @芋艿:要不要加个 mqtt 值了的前缀
8 9 /**
9 10 * MQTT RPC 请求
10   - *
11 11 */
12 12 @Data
13 13 @Builder
... ... @@ -23,6 +23,7 @@ public class RpcRequest {
23 23 /**
24 24 * 参数
25 25 */
  26 + // TODO @haohao:object 对象会不会不好序列化?
26 27 private Object[] params;
27 28  
28 29 /**
... ... @@ -34,4 +35,5 @@ public class RpcRequest {
34 35 * 回复地址
35 36 */
36 37 private String replyTo;
  38 +
37 39 }
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java
... ... @@ -7,7 +7,6 @@ import lombok.NoArgsConstructor;
7 7  
8 8 /**
9 9 * MQTT RPC 响应
10   - *
11 10 */
12 11 @Data
13 12 @Builder
... ... @@ -23,10 +22,12 @@ public class RpcResponse {
23 22 /**
24 23 * 结果
25 24 */
  25 + // TODO @haohao:object 对象会不会不好反序列化?
26 26 private Object result;
27 27  
28 28 /**
29 29 * 错误
30 30 */
31 31 private String error;
  32 +
32 33 }
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java
... ... @@ -15,4 +15,5 @@ public class SerializationUtils {
15 15 public static <T> T deserialize(String json, Class<T> clazz) {
16 16 return JSONUtil.toBean(json, clazz);
17 17 }
  18 +
18 19 }
19 20 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java
... ... @@ -9,7 +9,7 @@ import org.springframework.web.multipart.MultipartFile;
9 9 @Data
10 10 public class PluginInfoImportReqVO {
11 11  
12   - @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
  12 + @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
13 13 private Long id;
14 14  
15 15 @Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED)
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java
1 1 package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2  
3   -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4   -import com.alibaba.excel.annotation.ExcelProperty;
5 3 import io.swagger.v3.oas.annotations.media.Schema;
6 4 import lombok.Data;
7 5  
... ... @@ -9,63 +7,48 @@ import java.time.LocalDateTime;
9 7  
10 8 @Schema(description = "管理后台 - IoT 插件信息 Response VO")
11 9 @Data
12   -@ExcelIgnoreUnannotated
13 10 public class PluginInfoRespVO {
14 11  
15 12 @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
16   - @ExcelProperty("主键 ID")
17 13 private Long id;
18 14  
19 15 @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
20   - @ExcelProperty("插件包标识符")
21 16 private String pluginKey;
22 17  
23 18 @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
24   - @ExcelProperty("插件名称")
25 19 private String name;
26 20  
27 21 @Schema(description = "描述", example = "你猜")
28   - @ExcelProperty("描述")
29 22 private String description;
30 23  
31 24 @Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
32   - @ExcelProperty("部署方式")
33 25 private Integer deployType;
34 26  
35 27 @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
36   - @ExcelProperty("插件包文件名")
37 28 private String fileName;
38 29  
39 30 @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
40   - @ExcelProperty("插件版本")
41 31 private String version;
42 32  
43 33 @Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
44   - @ExcelProperty("插件类型")
45 34 private Integer type;
46 35  
47 36 @Schema(description = "设备插件协议类型")
48   - @ExcelProperty("设备插件协议类型")
49 37 private String protocol;
50 38  
51 39 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
52   - @ExcelProperty("状态")
53 40 private Integer status;
54 41  
55 42 @Schema(description = "插件配置项描述信息")
56   - @ExcelProperty("插件配置项描述信息")
57 43 private String configSchema;
58 44  
59 45 @Schema(description = "插件配置信息")
60   - @ExcelProperty("插件配置信息")
61 46 private String config;
62 47  
63 48 @Schema(description = "插件脚本")
64   - @ExcelProperty("插件脚本")
65 49 private String script;
66 50  
67 51 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
68   - @ExcelProperty("创建时间")
69 52 private LocalDateTime createTime;
70 53  
71 54 }
72 55 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java
... ... @@ -7,6 +7,10 @@ import lombok.*;
7 7 @Data
8 8 public class PluginInfoSaveReqVO {
9 9  
  10 + // TODO @haohao:新增的字段有点多,每个都需要哇?
  11 +
  12 + // TODO @haohao:一些枚举字段,需要加枚举校验。例如说,deployType、status、type 等
  13 +
10 14 @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
11 15 private Long id;
12 16  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java
... ... @@ -21,6 +21,7 @@ public interface PluginInstanceMapper extends BaseMapperX&lt;PluginInstanceDO&gt; {
21 21 .eq(PluginInstanceDO::getPluginId, pluginId));
22 22 }
23 23  
  24 + // TODO @haohao:这个还需要么?相关不用的 VO 可以删除
24 25 default PageResult<PluginInstanceDO> selectPage(PluginInstancePageReqVO reqVO) {
25 26 return selectPage(reqVO, new LambdaQueryWrapperX<PluginInstanceDO>()
26 27 .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId())
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java
1 1 package cn.iocoder.yudao.module.iot.job.plugin;
2 2  
3   -
4 3 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
5 4 import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService;
6 5 import org.springframework.scheduling.annotation.Scheduled;
... ... @@ -26,4 +25,5 @@ public class PluginInstancesJob {
26 25 pluginInstanceService.updatePluginInstances();
27 26 });
28 27 }
  28 +
29 29 }
30 30 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java
... ... @@ -15,6 +15,8 @@ import javax.annotation.PreDestroy;
15 15 import java.util.HashMap;
16 16 import java.util.Map;
17 17  
  18 +// TODO @芋艿:server 逻辑,再瞅瞅;
  19 +// TODO @haohao:如果只写在 iot biz 里,那么后续 server => client 貌似不方便?微信再讨论下~;
18 20 @Service
19 21 @Slf4j
20 22 public class RpcServer {
... ... @@ -90,6 +92,9 @@ public class RpcServer {
90 92 */
91 93 @FunctionalInterface
92 94 public interface MethodInvoker {
  95 +
93 96 Object invoke(Object[] params) throws Exception;
  97 +
94 98 }
  99 +
95 100 }
96 101 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java
... ... @@ -41,18 +41,18 @@ public class PluginInfoServiceImpl implements PluginInfoService {
41 41  
42 42 @Resource
43 43 private PluginInfoMapper pluginInfoMapper;
  44 +
44 45 @Resource
45 46 private SpringPluginManager pluginManager;
46 47  
  48 + // TODO @芋艿:要不要换位置
47 49 @Value("${pf4j.pluginsDir}")
48 50 private String pluginsDir;
49 51  
50 52 @Override
51 53 public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
52   - // 插入
53 54 PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class);
54 55 pluginInfoMapper.insert(pluginInfo);
55   - // 返回
56 56 return pluginInfo.getId();
57 57 }
58 58  
... ... @@ -67,29 +67,29 @@ public class PluginInfoServiceImpl implements PluginInfoService {
67 67  
68 68 @Override
69 69 public void deletePluginInfo(Long id) {
70   - // 校验存在
  70 + // 1.1 校验存在
71 71 PluginInfoDO pluginInfoDO = validatePluginInfoExists(id);
72   -
73   - // 停止插件
  72 + // 1.2 停止插件
74 73 if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
75 74 throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING);
76 75 }
77 76  
78   - // 卸载插件
  77 + // 2. 卸载插件
  78 + // TODO @haohao:可以复用 stopAndUnloadPlugin
79 79 PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey());
80 80 if (plugin != null) {
81   - // 查询插件是否是启动状态
  81 + // 停止插件
82 82 if (plugin.getPluginState().equals(PluginState.STARTED)) {
83   - // 停止插件
84 83 pluginManager.stopPlugin(plugin.getPluginId());
85 84 }
86 85 // 卸载插件
87 86 pluginManager.unloadPlugin(plugin.getPluginId());
88 87 }
89 88  
90   - // 删除
  89 + // 3.1 删除
91 90 pluginInfoMapper.deleteById(id);
92   - // 删除插件文件
  91 + // 3.2 删除插件文件
  92 + // TODO @haohao:这个直接主线程 sleep 就好了,不用单独开线程池哈。原因是,低频操作;另外,只有存在的时候,才 sleep + 删除;
93 93 Executors.newSingleThreadExecutor().submit(() -> {
94 94 try {
95 95 TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
... ... @@ -101,7 +101,6 @@ public class PluginInfoServiceImpl implements PluginInfoService {
101 101 log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
102 102 }
103 103 });
104   -
105 104 }
106 105  
107 106 private PluginInfoDO validatePluginInfoExists(Long id) {
... ... @@ -127,22 +126,19 @@ public class PluginInfoServiceImpl implements PluginInfoService {
127 126 // 1. 校验插件信息是否存在
128 127 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
129 128  
130   - // 2. 获取插件标识
131   - String pluginKey = pluginInfoDo.getPluginKey();
132   -
133   - // 3. 停止并卸载旧的插件
134   - stopAndUnloadPlugin(pluginKey);
  129 + // 2. 停止并卸载旧的插件
  130 + stopAndUnloadPlugin(pluginInfoDo.getPluginKey());
135 131  
136   - // 4. 上传新的插件文件
  132 + // 3.1 上传新的插件文件
137 133 String pluginKeyNew = uploadAndLoadNewPlugin(file);
138   -
139   - // 5. 更新插件启用状态文件
  134 + // 3.2 更新插件启用状态文件
140 135 updatePluginStatusFile(pluginKeyNew, false);
141 136  
142   - // 6. 更新插件信息
  137 + // 4. 更新插件信息
143 138 updatePluginInfo(pluginInfoDo, pluginKeyNew, file);
144 139 }
145 140  
  141 + // TODO @haohao:注释的格式
146 142 // 停止并卸载旧的插件
147 143 private void stopAndUnloadPlugin(String pluginKey) {
148 144 PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
... ... @@ -154,10 +150,13 @@ public class PluginInfoServiceImpl implements PluginInfoService {
154 150 }
155 151 }
156 152  
  153 + // TODO @haohao:注释的格式
157 154 // 上传并加载新的插件文件
158 155 private String uploadAndLoadNewPlugin(MultipartFile file) {
  156 + // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载
159 157 Path pluginsPath = Paths.get(pluginsDir);
160 158 try {
  159 + // TODO @haohao:可以使用 FileUtil 简化?
161 160 if (!Files.exists(pluginsPath)) {
162 161 Files.createDirectories(pluginsPath); // 创建插件目录
163 162 }
... ... @@ -166,16 +165,18 @@ public class PluginInfoServiceImpl implements PluginInfoService {
166 165 Path jarPath = pluginsPath.resolve(filename);
167 166 Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
168 167 return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
169   - } else {
170   - throw exception(PLUGIN_INSTALL_FAILED);
171 168 }
  169 + throw exception(PLUGIN_INSTALL_FAILED); // TODO @haohao:这么抛的话,貌似会被 catch (Exception e) {
172 170 } catch (Exception e) {
  171 + // TODO @haohao:打个 error log,方便排查
173 172 throw exception(PLUGIN_INSTALL_FAILED);
174 173 }
175 174 }
176 175  
  176 + // TODO @haohao:注释的格式
177 177 // 更新插件状态文件
178 178 private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) {
  179 + // TODO @haohao:疑问,这里写 enabled.txt 和 disabled.txt 的目的是啥哈?
179 180 Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt");
180 181 Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt");
181 182 Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath;
... ... @@ -186,10 +187,8 @@ public class PluginInfoServiceImpl implements PluginInfoService {
186 187 if (pluginWrapper == null) {
187 188 throw exception(PLUGIN_INSTALL_FAILED);
188 189 }
189   - List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath)
190   - : new ArrayList<>();
191   - List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath)
192   - : new ArrayList<>();
  190 + List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) : new ArrayList<>();
  191 + List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) : new ArrayList<>();
193 192  
194 193 if (!targetLines.contains(pluginKeyNew)) {
195 194 targetLines.add(pluginKeyNew);
... ... @@ -207,26 +206,33 @@ public class PluginInfoServiceImpl implements PluginInfoService {
207 206 }
208 207 }
209 208  
  209 + // TODO @haohao:注释的格式
210 210 // 更新插件信息
211 211 private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) {
  212 + // TODO @haohao:更新实体的时候,最好 new 一个新的!
  213 + // TODO @haohao:可以链式调用,简化下代码;
212 214 pluginInfoDo.setPluginKey(pluginKeyNew);
213 215 pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
214 216 pluginInfoDo.setFileName(file.getOriginalFilename());
215 217 pluginInfoDo.setScript("");
216   -
  218 + // 解析 pf4j 插件
217 219 PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor();
218 220 pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription());
219 221 pluginInfoDo.setVersion(pluginDescriptor.getVersion());
220 222 pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
  223 +
  224 + // 执行更新
221 225 pluginInfoMapper.updateById(pluginInfoDo);
222 226 }
223 227  
  228 + // TODO @haohao:status、state 字段命名,要统一下~
224 229 @Override
225 230 public void updatePluginStatus(Long id, Integer status) {
226 231 // 1. 校验插件信息是否存在
227 232 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
228 233  
229 234 // 2. 校验插件状态是否有效
  235 + // TODO @haohao:直接参数校验掉。通过 @InEnum
230 236 if (!IotPluginStatusEnum.contains(status)) {
231 237 throw exception(PLUGIN_STATUS_INVALID);
232 238 }
... ... @@ -237,17 +243,16 @@ public class PluginInfoServiceImpl implements PluginInfoService {
237 243  
238 244 // 4. 根据状态更新插件
239 245 if (plugin != null) {
240   - // 4.1 如果目标状态是运行且插件未启动,则启动插件
  246 + // 4.1 启动:如果目标状态是运行且插件未启动,则启动插件
241 247 if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
242 248 && plugin.getPluginState() != PluginState.STARTED) {
243 249 pluginManager.startPlugin(pluginKey);
244   - updatePluginStatusFile(pluginKey, true); // 更新插件状态文件为启用
245   - }
246   - // 4.2 如果目标状态是停止且插件已启动,则停止插件
247   - else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
  250 + updatePluginStatusFile(pluginKey, true);
  251 + // 4.2 停止:如果目标状态是停止且插件已启动,则停止插件
  252 + } else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
248 253 && plugin.getPluginState() == PluginState.STARTED) {
249 254 pluginManager.stopPlugin(pluginKey);
250   - updatePluginStatusFile(pluginKey, false); // 更新插件状态文件为禁用
  255 + updatePluginStatusFile(pluginKey, false);
251 256 }
252 257 } else {
253 258 // 5. 插件不存在且状态为停止,抛出异常
... ... @@ -257,17 +262,20 @@ public class PluginInfoServiceImpl implements PluginInfoService {
257 262 }
258 263  
259 264 // 6. 更新数据库中的插件状态
  265 + // TODO @haohao:新建新建 pluginInfoDo 哈!
260 266 pluginInfoDo.setStatus(status);
261 267 pluginInfoMapper.updateById(pluginInfoDo);
262 268 }
263 269  
264 270 @Override
265 271 public List<PluginInfoDO> getPluginInfoList() {
266   - return pluginInfoMapper.selectList(null);
  272 + return pluginInfoMapper.selectList();
267 273 }
268 274  
  275 + // TODO @haohao:可以改成 getPluginInfoListByStatus 更通用哈。
269 276 @Override
270 277 public List<PluginInfoDO> getRunningPluginInfoList() {
271 278 return pluginInfoMapper.selectListByStatus(IotPluginStatusEnum.RUNNING.getStatus());
272 279 }
  280 +
273 281 }
274 282 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java
... ... @@ -26,8 +26,9 @@ import java.util.List;
26 26 public class PluginInstanceServiceImpl implements PluginInstanceService {
27 27  
28 28 /**
29   - * 主程序id
  29 + * 主程序 ID
30 30 */
  31 + // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的
31 32 public static final String MAIN_ID = IdUtil.fastSimpleUUID();
32 33  
33 34 @Resource
... ... @@ -40,36 +41,37 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
40 41 @Value("${server.port:48080}")
41 42 private int port;
42 43  
  44 + // TODO @haohao:建议把 PluginInfoServiceImpl 里面,和 instance 相关的逻辑拿过来,可能会更好。info 处理信息,instance 处理实例
43 45  
  46 + // TODO @haohao:这个改成 reportPluginInstance 会不会更合适哈。
44 47 @Override
45 48 public void updatePluginInstances() {
46   - // 1. 查询 pf4j 插件列表
  49 + // 1.1 查询 pf4j 插件列表
47 50 List<PluginWrapper> plugins = pluginManager.getPlugins();
48   -
49   - // 2. 查询插件信息列表
  51 + // 1.2 查询插件信息列表
50 52 List<PluginInfoDO> pluginInfos = pluginInfoService.getPluginInfoList();
51   -
52   - // 动态获取主程序的 IP 和端口
  53 + // 1.3 动态获取主程序的 IP 和端口
53 54 String mainIp = getLocalIpAddress();
54 55  
55   - // 3. 遍历插件列表,并保存为插件实例
  56 + // 2. 遍历插件列表,并保存为插件实例
56 57 for (PluginWrapper plugin : plugins) {
  58 + // 2.1 查找插件信息
57 59 String pluginKey = plugin.getPluginId();
  60 + // TODO @haohao:CollUtil.findOne() 简化
58 61 PluginInfoDO pluginInfo = pluginInfos.stream()
59 62 .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey))
60 63 .findFirst()
61 64 .orElse(null);
62   -
63   - // 4. 如果插件信息不存在,则跳过
64 65 if (pluginInfo == null) {
  66 + // TODO @haohao:建议打个 error log
65 67 continue;
66 68 }
67 69  
68   - // 5. 查询插件实例
  70 + // 2.2 查询插件实例
69 71 PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId());
70   -
71   - // 6. 如果插件实例不存在,则创建
  72 + // 2.3.1 如果插件实例不存在,则创建
72 73 if (pluginInstance == null) {
  74 + // TODO @haohao:可以链式调用;建议新建一个!
73 75 pluginInstance = new PluginInstanceDO();
74 76 pluginInstance.setPluginId(pluginInfo.getId());
75 77 pluginInstance.setMainId(MAIN_ID);
... ... @@ -78,13 +80,14 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
78 80 pluginInstance.setHeartbeatAt(System.currentTimeMillis());
79 81 pluginInstanceMapper.insert(pluginInstance);
80 82 } else {
81   - // 7. 如果插件实例存在,则更新
  83 + // 2.3.2 如果插件实例存在,则更新
82 84 pluginInstance.setHeartbeatAt(System.currentTimeMillis());
83 85 pluginInstanceMapper.updateById(pluginInstance);
84 86 }
85 87 }
86 88 }
87 89  
  90 + // TODO @haohao:这个目的是,获取到第一个有效 ip 是哇?
88 91 private String getLocalIpAddress() {
89 92 try {
90 93 List<String> ipList = NetUtil.localIpv4s().stream()
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java
... ... @@ -28,4 +28,5 @@ public class RpcController {
28 28 public CompletableFuture<Object> concat(@RequestParam String str1, @RequestParam String str2) throws Exception {
29 29 return rpcClient.call("concat", new Object[]{str1, str2}, 10);
30 30 }
  31 +
31 32 }
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java
1 1 package cn.iocoder.yudao.module.iot.mqttrpc.client;
2 2  
3   -import java.util.concurrent.CompletableFuture;
4   -import java.util.concurrent.ConcurrentHashMap;
5   -import java.util.concurrent.ConcurrentMap;
6   -import java.util.concurrent.Executors;
7   -import java.util.concurrent.ScheduledExecutorService;
8   -import java.util.concurrent.TimeUnit;
9 3 import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcRequest;
10 4 import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcResponse;
11 5 import cn.iocoder.yudao.module.iot.mqttrpc.common.SerializationUtils;
12 6 import cn.iocoder.yudao.module.iot.mqttrpc.config.MqttConfig;
13 7 import lombok.extern.slf4j.Slf4j;
14   -import org.eclipse.paho.client.mqttv3.*;
  8 +import org.eclipse.paho.client.mqttv3.MqttClient;
  9 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  10 +import org.eclipse.paho.client.mqttv3.MqttException;
  11 +import org.eclipse.paho.client.mqttv3.MqttMessage;
15 12 import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
16 13 import org.springframework.stereotype.Service;
17 14  
... ... @@ -20,6 +17,7 @@ import javax.annotation.PreDestroy;
20 17 import java.util.UUID;
21 18 import java.util.concurrent.*;
22 19  
  20 +// TODO @芋艿:需要考虑,怎么公用!
23 21 @Service
24 22 @Slf4j
25 23 public class RpcClient {
... ...