Commit ae96ff4a25beb188f0b4ca527793721308b86e60

Authored by 安浩浩
1 parent 72d8511d

【功能修改】IoT: 修改网络组件模块,包含 HTTP 和 EMQX 组件,重构相关配置和处理逻辑,更新文档说明。

Showing 51 changed files with 1526 additions and 1042 deletions
yudao-module-iot/pom.xml
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 <modules> 10 <modules>
11 <module>yudao-module-iot-api</module> 11 <module>yudao-module-iot-api</module>
12 <module>yudao-module-iot-biz</module> 12 <module>yudao-module-iot-biz</module>
13 - <module>yudao-module-iot-components</module> 13 + <module>yudao-module-iot-net-components</module>
14 <!-- <module>yudao-module-iot-plugins</module>--> 14 <!-- <module>yudao-module-iot-plugins</module>-->
15 </modules> 15 </modules>
16 <modelVersion>4.0.0</modelVersion> 16 <modelVersion>4.0.0</modelVersion>
yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -93,10 +93,10 @@ @@ -93,10 +93,10 @@
93 <optional>true</optional> 93 <optional>true</optional>
94 </dependency> 94 </dependency>
95 95
96 - <dependency>  
97 - <groupId>org.pf4j</groupId> <!-- PF4J:内置插件机制 -->  
98 - <artifactId>pf4j-spring</artifactId>  
99 - </dependency> 96 + <!-- <dependency>-->
  97 + <!-- <groupId>org.pf4j</groupId> &lt;!&ndash; PF4J:内置插件机制 &ndash;&gt;-->
  98 + <!-- <artifactId>pf4j-spring</artifactId>-->
  99 + <!-- </dependency>-->
100 100
101 <!-- TODO @芋艿:bom 管理 --> 101 <!-- TODO @芋艿:bom 管理 -->
102 <dependency> 102 <dependency>
@@ -137,12 +137,12 @@ @@ -137,12 +137,12 @@
137 <!-- IoT 网络组件:接收来自设备的上行数据 --> 137 <!-- IoT 网络组件:接收来自设备的上行数据 -->
138 <dependency> 138 <dependency>
139 <groupId>cn.iocoder.boot</groupId> 139 <groupId>cn.iocoder.boot</groupId>
140 - <artifactId>yudao-module-iot-component-http</artifactId> 140 + <artifactId>yudao-module-iot-net-component-http</artifactId>
141 <version>${revision}</version> 141 <version>${revision}</version>
142 </dependency> 142 </dependency>
143 <dependency> 143 <dependency>
144 <groupId>cn.iocoder.boot</groupId> 144 <groupId>cn.iocoder.boot</groupId>
145 - <artifactId>yudao-module-iot-component-emqx</artifactId> 145 + <artifactId>yudao-module-iot-net-component-emqx</artifactId>
146 <version>${revision}</version> 146 <version>${revision}</version>
147 </dependency> 147 </dependency>
148 </dependencies> 148 </dependencies>
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
16 */ 16 */
17 @RestController 17 @RestController
18 @Validated 18 @Validated
19 -@Primary // 保证优先匹配,因为 yudao-module-iot-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入 19 +@Primary // 保证优先匹配,因为 yudao-module-iot-net-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入
20 public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi { 20 public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
21 21
22 @Resource 22 @Resource
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/config/IotPluginConfiguration.java deleted
1 -package cn.iocoder.yudao.module.iot.framework.plugin.config;  
2 -  
3 -import cn.iocoder.yudao.module.iot.framework.plugin.core.IotPluginStartRunner;  
4 -import cn.iocoder.yudao.module.iot.framework.plugin.core.IotPluginStateListener;  
5 -import cn.iocoder.yudao.module.iot.service.plugin.IotPluginConfigService;  
6 -import lombok.extern.slf4j.Slf4j;  
7 -import org.pf4j.spring.SpringPluginManager;  
8 -import org.springframework.beans.factory.annotation.Value;  
9 -import org.springframework.context.annotation.Bean;  
10 -import org.springframework.context.annotation.Configuration;  
11 -  
12 -import java.nio.file.Paths;  
13 -  
14 -/**  
15 - * IoT 插件配置类  
16 - *  
17 - * @author haohao  
18 - */  
19 -@Configuration  
20 -@Slf4j  
21 -public class IotPluginConfiguration {  
22 -  
23 - @Bean  
24 - public IotPluginStartRunner pluginStartRunner(SpringPluginManager pluginManager,  
25 - IotPluginConfigService pluginConfigService) {  
26 - return new IotPluginStartRunner(pluginManager, pluginConfigService);  
27 - }  
28 -  
29 - // TODO @芋艿:需要 review 下  
30 - @Bean  
31 - public SpringPluginManager pluginManager(@Value("${pf4j.pluginsDir:pluginsDir}") String pluginsDir) {  
32 - log.info("[init][实例化 SpringPluginManager]");  
33 - SpringPluginManager springPluginManager = new SpringPluginManager(Paths.get(pluginsDir)) {  
34 -  
35 - @Override  
36 - public void startPlugins() {  
37 - // 禁用插件启动,避免插件启动时,启动所有插件  
38 - log.info("[init][禁用默认启动所有插件]");  
39 - }  
40 -  
41 - };  
42 - springPluginManager.addPluginStateListener(new IotPluginStateListener());  
43 - return springPluginManager;  
44 - }  
45 -  
46 -}  
47 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStartRunner.java deleted
1 -package cn.iocoder.yudao.module.iot.framework.plugin.core;  
2 -  
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;  
5 -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginConfigDO;  
6 -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum;  
7 -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;  
8 -import cn.iocoder.yudao.module.iot.service.plugin.IotPluginConfigService;  
9 -import lombok.RequiredArgsConstructor;  
10 -import lombok.extern.slf4j.Slf4j;  
11 -import org.pf4j.spring.SpringPluginManager;  
12 -import org.springframework.boot.ApplicationArguments;  
13 -import org.springframework.boot.ApplicationRunner;  
14 -  
15 -import java.util.List;  
16 -  
17 -/**  
18 - * IoT 插件启动 Runner  
19 - *  
20 - * 用于 Spring Boot 启动时,启动 {@link IotPluginDeployTypeEnum#JAR} 部署类型的插件  
21 - */  
22 -@RequiredArgsConstructor  
23 -@Slf4j  
24 -public class IotPluginStartRunner implements ApplicationRunner {  
25 -  
26 - private final SpringPluginManager springPluginManager;  
27 -  
28 - private final IotPluginConfigService pluginConfigService;  
29 -  
30 - @Override  
31 - public void run(ApplicationArguments args) {  
32 - List<IotPluginConfigDO> pluginConfigList = TenantUtils.executeIgnore(  
33 - () -> pluginConfigService.getPluginConfigListByStatusAndDeployType(  
34 - IotPluginStatusEnum.RUNNING.getStatus(), IotPluginDeployTypeEnum.JAR.getDeployType()));  
35 - if (CollUtil.isEmpty(pluginConfigList)) {  
36 - log.info("[run][没有需要启动的插件]");  
37 - return;  
38 - }  
39 -  
40 - // 遍历插件列表,逐个启动  
41 - pluginConfigList.forEach(pluginConfig -> {  
42 - try {  
43 - log.info("[run][插件({}) 启动开始]", pluginConfig.getPluginKey());  
44 - springPluginManager.startPlugin(pluginConfig.getPluginKey());  
45 - log.info("[run][插件({}) 启动完成]", pluginConfig.getPluginKey());  
46 - } catch (Exception e) {  
47 - log.error("[run][插件({}) 启动异常]", pluginConfig.getPluginKey(), e);  
48 - }  
49 - });  
50 - }  
51 -  
52 -}  
53 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStateListener.java deleted
1 -package cn.iocoder.yudao.module.iot.framework.plugin.core;  
2 -  
3 -import lombok.extern.slf4j.Slf4j;  
4 -import org.pf4j.PluginStateEvent;  
5 -import org.pf4j.PluginStateListener;  
6 -  
7 -/**  
8 - * IoT 插件状态监听器,用于 log 插件的状态变化  
9 - *  
10 - * @author haohao  
11 - */  
12 -@Slf4j  
13 -public class IotPluginStateListener implements PluginStateListener {  
14 -  
15 - @Override  
16 - public void pluginStateChanged(PluginStateEvent event) {  
17 - log.info("[pluginStateChanged][插件({}) 状态变化,从 {} 变为 {}]", event.getPlugin().getPluginId(),  
18 - event.getOldState().toString(), event.getPluginState().toString());  
19 - }  
20 -  
21 -}  
22 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginConfigServiceImpl.java
@@ -9,8 +9,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginConfigMapper; @@ -9,8 +9,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginConfigMapper;
9 import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; 9 import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
10 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
11 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
12 -import org.pf4j.PluginWrapper;  
13 -import org.pf4j.spring.SpringPluginManager;  
14 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
15 import org.springframework.validation.annotation.Validated; 13 import org.springframework.validation.annotation.Validated;
16 import org.springframework.web.multipart.MultipartFile; 14 import org.springframework.web.multipart.MultipartFile;
@@ -35,8 +33,8 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService { @@ -35,8 +33,8 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
35 @Resource 33 @Resource
36 private IotPluginInstanceService pluginInstanceService; 34 private IotPluginInstanceService pluginInstanceService;
37 35
38 - @Resource  
39 - private SpringPluginManager springPluginManager; 36 +// @Resource
  37 +// private SpringPluginManager springPluginManager;
40 38
41 @Override 39 @Override
42 public Long createPluginConfig(PluginConfigSaveReqVO createReqVO) { 40 public Long createPluginConfig(PluginConfigSaveReqVO createReqVO) {
@@ -130,16 +128,16 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService { @@ -130,16 +128,16 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
130 validatePluginConfigFile(pluginKeyNew); 128 validatePluginConfigFile(pluginKeyNew);
131 129
132 // 4. 更新插件配置 130 // 4. 更新插件配置
133 - IotPluginConfigDO updatedPluginConfig = new IotPluginConfigDO()  
134 - .setId(pluginConfigDO.getId())  
135 - .setPluginKey(pluginKeyNew)  
136 - .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) // TODO @haohao:这个状态,是不是非 stop 哈?  
137 - .setFileName(file.getOriginalFilename())  
138 - .setScript("") // TODO @haohao:这个设置为 "" 会不会覆盖数据里的哈?应该从插件里读取?未来?  
139 - .setConfigSchema(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription())  
140 - .setVersion(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion())  
141 - .setDescription(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription());  
142 - pluginConfigMapper.updateById(updatedPluginConfig); 131 +// IotPluginConfigDO updatedPluginConfig = new IotPluginConfigDO()
  132 +// .setId(pluginConfigDO.getId())
  133 +// .setPluginKey(pluginKeyNew)
  134 +// .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) // TODO @haohao:这个状态,是不是非 stop 哈?
  135 +// .setFileName(file.getOriginalFilename())
  136 +// .setScript("") // TODO @haohao:这个设置为 "" 会不会覆盖数据里的哈?应该从插件里读取?未来?
  137 +// .setConfigSchema(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription())
  138 +// .setVersion(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion())
  139 +// .setDescription(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription());
  140 +// pluginConfigMapper.updateById(updatedPluginConfig);
143 } 141 }
144 142
145 /** 143 /**
@@ -149,13 +147,13 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService { @@ -149,13 +147,13 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
149 */ 147 */
150 private void validatePluginConfigFile(String pluginKeyNew) { 148 private void validatePluginConfigFile(String pluginKeyNew) {
151 // TODO @haohao:校验 file 相关参数,是否完整,类似:version 之类是不是可以解析到 149 // TODO @haohao:校验 file 相关参数,是否完整,类似:version 之类是不是可以解析到
152 - PluginWrapper plugin = springPluginManager.getPlugin(pluginKeyNew);  
153 - if (plugin == null) {  
154 - throw exception(PLUGIN_INSTALL_FAILED);  
155 - }  
156 - if (plugin.getDescriptor().getVersion() == null) {  
157 - throw exception(PLUGIN_INSTALL_FAILED);  
158 - } 150 +// PluginWrapper plugin = springPluginManager.getPlugin(pluginKeyNew);
  151 +// if (plugin == null) {
  152 +// throw exception(PLUGIN_INSTALL_FAILED);
  153 +// }
  154 +// if (plugin.getDescriptor().getVersion() == null) {
  155 +// throw exception(PLUGIN_INSTALL_FAILED);
  156 +// }
159 } 157 }
160 158
161 @Override 159 @Override
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java
1 package cn.iocoder.yudao.module.iot.service.plugin; 1 package cn.iocoder.yudao.module.iot.service.plugin;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.hutool.core.io.FileUtil;  
5 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 5 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
7 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO; 6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
@@ -9,13 +8,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginConfigDO; @@ -9,13 +8,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginConfigDO;
9 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO; 8 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
10 import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInstanceMapper; 9 import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInstanceMapper;
11 import cn.iocoder.yudao.module.iot.dal.redis.plugin.DevicePluginProcessIdRedisDAO; 10 import cn.iocoder.yudao.module.iot.dal.redis.plugin.DevicePluginProcessIdRedisDAO;
12 -import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants;  
13 -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;  
14 import jakarta.annotation.Resource; 11 import jakarta.annotation.Resource;
15 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
16 -import org.pf4j.PluginState;  
17 -import org.pf4j.PluginWrapper;  
18 -import org.pf4j.spring.SpringPluginManager;  
19 import org.springframework.beans.factory.annotation.Value; 13 import org.springframework.beans.factory.annotation.Value;
20 import org.springframework.context.annotation.Lazy; 14 import org.springframework.context.annotation.Lazy;
21 import org.springframework.stereotype.Service; 15 import org.springframework.stereotype.Service;
@@ -23,17 +17,10 @@ import org.springframework.validation.annotation.Validated; @@ -23,17 +17,10 @@ import org.springframework.validation.annotation.Validated;
23 import org.springframework.web.multipart.MultipartFile; 17 import org.springframework.web.multipart.MultipartFile;
24 18
25 import java.io.File; 19 import java.io.File;
26 -import java.io.IOException;  
27 -import java.nio.file.Files;  
28 -import java.nio.file.Path;  
29 -import java.nio.file.Paths;  
30 -import java.nio.file.StandardCopyOption;  
31 import java.time.LocalDateTime; 20 import java.time.LocalDateTime;
32 import java.util.List; 21 import java.util.List;
33 import java.util.concurrent.TimeUnit; 22 import java.util.concurrent.TimeUnit;
34 23
35 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
36 -  
37 /** 24 /**
38 * IoT 插件实例 Service 实现类 25 * IoT 插件实例 Service 实现类
39 * 26 *
@@ -54,8 +41,8 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService { @@ -54,8 +41,8 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
54 @Resource 41 @Resource
55 private DevicePluginProcessIdRedisDAO devicePluginProcessIdRedisDAO; 42 private DevicePluginProcessIdRedisDAO devicePluginProcessIdRedisDAO;
56 43
57 - @Resource  
58 - private SpringPluginManager pluginManager; 44 +// @Resource
  45 +// private SpringPluginManager pluginManager;
59 46
60 @Value("${pf4j.pluginsDir}") 47 @Value("${pf4j.pluginsDir}")
61 private String pluginsDir; 48 private String pluginsDir;
@@ -120,17 +107,17 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService { @@ -120,17 +107,17 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
120 107
121 @Override 108 @Override
122 public void stopAndUnloadPlugin(String pluginKey) { 109 public void stopAndUnloadPlugin(String pluginKey) {
123 - PluginWrapper plugin = pluginManager.getPlugin(pluginKey);  
124 - if (plugin == null) {  
125 - log.warn("插件不存在或已卸载: {}", pluginKey);  
126 - return;  
127 - }  
128 - if (plugin.getPluginState().equals(PluginState.STARTED)) {  
129 - pluginManager.stopPlugin(pluginKey); // 停止插件  
130 - log.info("已停止插件: {}", pluginKey);  
131 - }  
132 - pluginManager.unloadPlugin(pluginKey); // 卸载插件  
133 - log.info("已卸载插件: {}", pluginKey); 110 +// PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
  111 +// if (plugin == null) {
  112 +// log.warn("插件不存在或已卸载: {}", pluginKey);
  113 +// return;
  114 +// }
  115 +// if (plugin.getPluginState().equals(PluginState.STARTED)) {
  116 +// pluginManager.stopPlugin(pluginKey); // 停止插件
  117 +// log.info("已停止插件: {}", pluginKey);
  118 +// }
  119 +// pluginManager.unloadPlugin(pluginKey); // 卸载插件
  120 +// log.info("已卸载插件: {}", pluginKey);
134 } 121 }
135 122
136 @Override 123 @Override
@@ -151,65 +138,66 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService { @@ -151,65 +138,66 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
151 138
152 @Override 139 @Override
153 public String uploadAndLoadNewPlugin(MultipartFile file) { 140 public String uploadAndLoadNewPlugin(MultipartFile file) {
154 - String pluginKeyNew;  
155 - // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载  
156 - Path pluginsPath = Paths.get(pluginsDir);  
157 - try {  
158 - FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录  
159 - String filename = file.getOriginalFilename();  
160 - if (filename != null) {  
161 - Path jarPath = pluginsPath.resolve(filename);  
162 - Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件  
163 - pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件  
164 - log.info("已加载插件: {}", pluginKeyNew);  
165 - } else {  
166 - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED);  
167 - }  
168 - } catch (IOException e) {  
169 - log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e);  
170 - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);  
171 - } catch (Exception e) {  
172 - log.error("[uploadAndLoadNewPlugin][加载插件失败]", e);  
173 - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);  
174 - }  
175 - return pluginKeyNew; 141 +// String pluginKeyNew;
  142 +// // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载
  143 +// Path pluginsPath = Paths.get(pluginsDir);
  144 +// try {
  145 +// FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录
  146 +// String filename = file.getOriginalFilename();
  147 +// if (filename != null) {
  148 +// Path jarPath = pluginsPath.resolve(filename);
  149 +// Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
  150 +//// pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
  151 +//// log.info("已加载插件: {}", pluginKeyNew);
  152 +// } else {
  153 +// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED);
  154 +// }
  155 +// } catch (IOException e) {
  156 +// log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e);
  157 +// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
  158 +// } catch (Exception e) {
  159 +// log.error("[uploadAndLoadNewPlugin][加载插件失败]", e);
  160 +// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
  161 +// }
  162 +// return pluginKeyNew;
  163 + return null;
176 } 164 }
177 165
178 @Override 166 @Override
179 public void updatePluginStatus(IotPluginConfigDO pluginConfigDO, Integer status) { 167 public void updatePluginStatus(IotPluginConfigDO pluginConfigDO, Integer status) {
180 - String pluginKey = pluginConfigDO.getPluginKey();  
181 - PluginWrapper plugin = pluginManager.getPlugin(pluginKey);  
182 -  
183 - if (plugin == null) {  
184 - // 插件不存在且状态为停止,抛出异常  
185 - if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginConfigDO.getStatus())) {  
186 - throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);  
187 - }  
188 - return;  
189 - }  
190 -  
191 - // 启动插件  
192 - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())  
193 - && plugin.getPluginState() != PluginState.STARTED) {  
194 - try {  
195 - pluginManager.startPlugin(pluginKey);  
196 - } catch (Exception e) {  
197 - log.error("[updatePluginStatus][启动插件({}) 失败]", pluginKey, e);  
198 - throw exception(ErrorCodeConstants.PLUGIN_START_FAILED, e);  
199 - }  
200 - log.info("已启动插件: {}", pluginKey);  
201 - }  
202 - // 停止插件  
203 - else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())  
204 - && plugin.getPluginState() == PluginState.STARTED) {  
205 - try {  
206 - pluginManager.stopPlugin(pluginKey);  
207 - } catch (Exception e) {  
208 - log.error("[updatePluginStatus][停止插件({}) 失败]", pluginKey, e);  
209 - throw exception(ErrorCodeConstants.PLUGIN_STOP_FAILED, e);  
210 - }  
211 - log.info("已停止插件: {}", pluginKey);  
212 - } 168 +// String pluginKey = pluginConfigDO.getPluginKey();
  169 +// PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
  170 +//
  171 +// if (plugin == null) {
  172 +// // 插件不存在且状态为停止,抛出异常
  173 +// if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginConfigDO.getStatus())) {
  174 +// throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
  175 +// }
  176 +// return;
  177 +// }
  178 +//
  179 +// // 启动插件
  180 +// if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
  181 +// && plugin.getPluginState() != PluginState.STARTED) {
  182 +// try {
  183 +// pluginManager.startPlugin(pluginKey);
  184 +// } catch (Exception e) {
  185 +// log.error("[updatePluginStatus][启动插件({}) 失败]", pluginKey, e);
  186 +// throw exception(ErrorCodeConstants.PLUGIN_START_FAILED, e);
  187 +// }
  188 +// log.info("已启动插件: {}", pluginKey);
  189 +// }
  190 +// // 停止插件
  191 +// else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
  192 +// && plugin.getPluginState() == PluginState.STARTED) {
  193 +// try {
  194 +// pluginManager.stopPlugin(pluginKey);
  195 +// } catch (Exception e) {
  196 +// log.error("[updatePluginStatus][停止插件({}) 失败]", pluginKey, e);
  197 +// throw exception(ErrorCodeConstants.PLUGIN_STOP_FAILED, e);
  198 +// }
  199 +// log.info("已停止插件: {}", pluginKey);
  200 +// }
213 } 201 }
214 202
215 // ========== 设备与插件的映射操作 ========== 203 // ========== 设备与插件的映射操作 ==========
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/config/IotComponentCommonProperties.java deleted
1 -package cn.iocoder.yudao.module.iot.component.core.config;  
2 -  
3 -import lombok.Data;  
4 -import org.springframework.boot.context.properties.ConfigurationProperties;  
5 -import org.springframework.validation.annotation.Validated;  
6 -  
7 -/**  
8 - * IoT 组件通用配置属性  
9 - *  
10 - * @author haohao  
11 - */  
12 -@ConfigurationProperties(prefix = "yudao.iot.component.core")  
13 -@Validated  
14 -@Data  
15 -public class IotComponentCommonProperties {  
16 -  
17 - /**  
18 - * 组件的唯一标识  
19 - * <p>  
20 - * 注意:该值将在运行时由各组件设置,不再从配置读取  
21 - */  
22 - private String pluginKey;  
23 -  
24 -}  
25 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring.factories deleted
1 -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\  
2 - cn.iocoder.yudao.module.iot.component.core.config.IotPluginCommonAutoConfiguration  
3 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted
1 -cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonAutoConfiguration  
2 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted
1 -cn.iocoder.yudao.module.iot.component.emqx.config.IotComponentEmqxAutoConfiguration  
2 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/config/IotComponentHttpAutoConfiguration.java deleted
1 -package cn.iocoder.yudao.module.iot.component.http.config;  
2 -  
3 -import cn.hutool.system.SystemUtil;  
4 -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;  
5 -import cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonProperties;  
6 -import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler;  
7 -import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;  
8 -import cn.iocoder.yudao.module.iot.component.http.downstream.IotDeviceDownstreamHandlerImpl;  
9 -import cn.iocoder.yudao.module.iot.component.http.upstream.IotDeviceUpstreamServer;  
10 -import lombok.extern.slf4j.Slf4j;  
11 -import org.springframework.boot.autoconfigure.AutoConfiguration;  
12 -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;  
13 -import org.springframework.boot.context.event.ApplicationStartedEvent;  
14 -import org.springframework.boot.context.properties.EnableConfigurationProperties;  
15 -import org.springframework.context.ApplicationContext;  
16 -import org.springframework.context.annotation.Bean;  
17 -import org.springframework.context.annotation.ComponentScan;  
18 -import org.springframework.context.event.EventListener;  
19 -  
20 -import java.lang.management.ManagementFactory;  
21 -  
22 -// TODO @haohao:类似 IotComponentEmqxAutoConfiguration 的建议  
23 -/**  
24 - * IoT 组件 HTTP 的自动配置类  
25 - *  
26 - * @author haohao  
27 - */  
28 -@Slf4j  
29 -@AutoConfiguration  
30 -@EnableConfigurationProperties(IotComponentHttpProperties.class)  
31 -@ConditionalOnProperty(prefix = "yudao.iot.component.http", name = "enabled", havingValue = "true", matchIfMissing = false)  
32 -@ComponentScan(basePackages = {  
33 - "cn.iocoder.yudao.module.iot.component.core", // 核心包  
34 - "cn.iocoder.yudao.module.iot.component.http" // HTTP组件包  
35 -})  
36 -public class IotComponentHttpAutoConfiguration {  
37 -  
38 - /**  
39 - * 组件key  
40 - */  
41 - private static final String PLUGIN_KEY = "http";  
42 -  
43 - public IotComponentHttpAutoConfiguration() {  
44 - log.info("[IotComponentHttpAutoConfiguration][已启动]");  
45 - }  
46 -  
47 - @EventListener(ApplicationStartedEvent.class)  
48 - public void initialize(ApplicationStartedEvent event) {  
49 - // 从应用上下文中获取需要的Bean  
50 - IotComponentRegistry componentRegistry = event.getApplicationContext().getBean(IotComponentRegistry.class);  
51 - IotComponentCommonProperties commonProperties = event.getApplicationContext()  
52 - .getBean(IotComponentCommonProperties.class);  
53 -  
54 - // 设置当前组件的核心标识  
55 - commonProperties.setPluginKey(PLUGIN_KEY);  
56 -  
57 - // 将HTTP组件注册到组件注册表  
58 - componentRegistry.registerComponent(  
59 - PLUGIN_KEY,  
60 - SystemUtil.getHostInfo().getAddress(),  
61 - 0, // 内嵌模式固定为0  
62 - getProcessId());  
63 -  
64 - log.info("[initialize][IoT HTTP 组件初始化完成]");  
65 - }  
66 -  
67 - @Bean(name = "httpDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop")  
68 - public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,  
69 - IotComponentHttpProperties properties,  
70 - ApplicationContext applicationContext,  
71 - IotComponentRegistry componentRegistry) {  
72 - return new IotDeviceUpstreamServer(properties, deviceUpstreamApi, applicationContext, componentRegistry);  
73 - }  
74 -  
75 - @Bean(name = "httpDeviceDownstreamHandler")  
76 - public IotDeviceDownstreamHandler deviceDownstreamHandler() {  
77 - return new IotDeviceDownstreamHandlerImpl();  
78 - }  
79 -  
80 - /**  
81 - * 获取当前进程ID  
82 - *  
83 - * @return 进程ID  
84 - */  
85 - private String getProcessId() {  
86 - // 获取进程的 name  
87 - String name = ManagementFactory.getRuntimeMXBean().getName();  
88 - // 分割名称,格式为 pid@hostname  
89 - String pid = name.split("@")[0];  
90 - return pid;  
91 - }  
92 -}  
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/upstream/IotDeviceUpstreamServer.java deleted
1 -package cn.iocoder.yudao.module.iot.component.http.upstream;  
2 -  
3 -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;  
4 -import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;  
5 -import cn.iocoder.yudao.module.iot.component.http.config.IotComponentHttpProperties;  
6 -import cn.iocoder.yudao.module.iot.component.http.upstream.router.IotDeviceUpstreamVertxHandler;  
7 -import io.vertx.core.Vertx;  
8 -import io.vertx.core.http.HttpServer;  
9 -import io.vertx.ext.web.Router;  
10 -import io.vertx.ext.web.handler.BodyHandler;  
11 -import lombok.extern.slf4j.Slf4j;  
12 -import org.springframework.context.ApplicationContext;  
13 -  
14 -/**  
15 - * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器  
16 - * <p>  
17 - * 协议:HTTP  
18 - *  
19 - * @author haohao  
20 - */  
21 -@Slf4j  
22 -public class IotDeviceUpstreamServer {  
23 -  
24 - private final Vertx vertx;  
25 - private final HttpServer server;  
26 - private final IotComponentHttpProperties properties;  
27 - private final IotComponentRegistry componentRegistry;  
28 -  
29 - public IotDeviceUpstreamServer(IotComponentHttpProperties properties,  
30 - IotDeviceUpstreamApi deviceUpstreamApi,  
31 - ApplicationContext applicationContext,  
32 - IotComponentRegistry componentRegistry) {  
33 - this.properties = properties;  
34 - this.componentRegistry = componentRegistry;  
35 -  
36 - // 创建 Vertx 实例  
37 - this.vertx = Vertx.vertx();  
38 - // 创建 Router 实例  
39 - Router router = Router.router(vertx);  
40 - router.route().handler(BodyHandler.create()); // 处理 Body  
41 -  
42 - // 使用统一的 Handler 处理所有上行请求  
43 - IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(deviceUpstreamApi,  
44 - applicationContext);  
45 - router.post(IotDeviceUpstreamVertxHandler.PROPERTY_PATH).handler(upstreamHandler);  
46 - router.post(IotDeviceUpstreamVertxHandler.EVENT_PATH).handler(upstreamHandler);  
47 -  
48 - // 创建 HttpServer 实例  
49 - this.server = vertx.createHttpServer().requestHandler(router);  
50 - }  
51 -  
52 - /**  
53 - * 启动 HTTP 服务器  
54 - */  
55 - public void start() {  
56 - log.info("[start][开始启动]");  
57 - server.listen(properties.getServerPort())  
58 - .toCompletionStage()  
59 - .toCompletableFuture()  
60 - .join();  
61 - log.info("[start][启动完成,端口({})]", this.server.actualPort());  
62 - }  
63 -  
64 - /**  
65 - * 停止所有  
66 - */  
67 - public void stop() {  
68 - log.info("[stop][开始关闭]");  
69 - try {  
70 - // 关闭 HTTP 服务器  
71 - if (server != null) {  
72 - server.close()  
73 - .toCompletionStage()  
74 - .toCompletableFuture()  
75 - .join();  
76 - }  
77 -  
78 - // 关闭 Vertx 实例  
79 - if (vertx != null) {  
80 - vertx.close()  
81 - .toCompletionStage()  
82 - .toCompletableFuture()  
83 - .join();  
84 - }  
85 - log.info("[stop][关闭完成]");  
86 - } catch (Exception e) {  
87 - log.error("[stop][关闭异常]", e);  
88 - throw new RuntimeException(e);  
89 - }  
90 - }  
91 -}  
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/upstream/router/IotDeviceUpstreamVertxHandler.java deleted
1 -package cn.iocoder.yudao.module.iot.component.http.upstream.router;  
2 -  
3 -import cn.hutool.core.util.IdUtil;  
4 -import cn.hutool.core.util.ObjUtil;  
5 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
6 -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;  
7 -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;  
8 -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;  
9 -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;  
10 -import cn.iocoder.yudao.module.iot.component.core.pojo.IotStandardResponse;  
11 -import cn.iocoder.yudao.module.iot.component.core.util.IotPluginCommonUtils;  
12 -import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;  
13 -import io.vertx.core.Handler;  
14 -import io.vertx.core.json.JsonObject;  
15 -import io.vertx.ext.web.RoutingContext;  
16 -import lombok.extern.slf4j.Slf4j;  
17 -import org.springframework.context.ApplicationContext;  
18 -  
19 -import java.time.LocalDateTime;  
20 -import java.util.HashMap;  
21 -import java.util.Map;  
22 -  
23 -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;  
24 -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;  
25 -  
26 -/**  
27 - * IoT 设备上行统一处理的 Vert.x Handler  
28 - * <p>  
29 - * 统一处理设备属性上报和事件上报的请求  
30 - *  
31 - * @author haohao  
32 - */  
33 -@Slf4j  
34 -public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {  
35 -  
36 - /**  
37 - * 属性上报路径  
38 - */  
39 - public static final String PROPERTY_PATH = "/sys/:productKey/:deviceName/thing/event/property/post";  
40 - /**  
41 - * 事件上报路径  
42 - */  
43 - public static final String EVENT_PATH = "/sys/:productKey/:deviceName/thing/event/:identifier/post";  
44 -  
45 - private static final String PROPERTY_METHOD = "thing.event.property.post";  
46 - private static final String EVENT_METHOD_PREFIX = "thing.event.";  
47 - private static final String EVENT_METHOD_SUFFIX = ".post";  
48 -  
49 - private final IotDeviceUpstreamApi deviceUpstreamApi;  
50 -// private final HttpScriptService scriptService;  
51 -  
52 - public IotDeviceUpstreamVertxHandler(IotDeviceUpstreamApi deviceUpstreamApi,  
53 - ApplicationContext applicationContext) {  
54 - this.deviceUpstreamApi = deviceUpstreamApi;  
55 -// this.scriptService = applicationContext.getBean(HttpScriptService.class);  
56 - }  
57 -  
58 - @Override  
59 - public void handle(RoutingContext routingContext) {  
60 - String path = routingContext.request().path();  
61 - String requestId = IdUtil.fastSimpleUUID();  
62 -  
63 - try {  
64 - // 1. 解析通用参数  
65 - String productKey = routingContext.pathParam("productKey");  
66 - String deviceName = routingContext.pathParam("deviceName");  
67 - JsonObject body = routingContext.body().asJsonObject();  
68 - requestId = ObjUtil.defaultIfBlank(body.getString("id"), requestId);  
69 -  
70 - // 2. 根据路径模式处理不同类型的请求  
71 - CommonResult<Boolean> result;  
72 - String method;  
73 - if (path.matches(".*/thing/event/property/post")) {  
74 - // 处理属性上报  
75 - IotDevicePropertyReportReqDTO reportReqDTO = parsePropertyReportRequest(productKey, deviceName,  
76 - requestId, body);  
77 -  
78 - // 设备上线  
79 - updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());  
80 -  
81 - // 属性上报  
82 - result = deviceUpstreamApi.reportDeviceProperty(reportReqDTO);  
83 - method = PROPERTY_METHOD;  
84 - } else if (path.matches(".*/thing/event/.+/post")) {  
85 - // 处理事件上报  
86 - String identifier = routingContext.pathParam("identifier");  
87 - IotDeviceEventReportReqDTO reportReqDTO = parseEventReportRequest(productKey, deviceName, identifier,  
88 - requestId, body);  
89 -  
90 - // 设备上线  
91 - updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());  
92 -  
93 - // 事件上报  
94 - result = deviceUpstreamApi.reportDeviceEvent(reportReqDTO);  
95 - method = EVENT_METHOD_PREFIX + identifier + EVENT_METHOD_SUFFIX;  
96 - } else {  
97 - // 不支持的请求路径  
98 - IotStandardResponse errorResponse = IotStandardResponse.error(requestId, "unknown",  
99 - BAD_REQUEST.getCode(), "不支持的请求路径");  
100 - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);  
101 - return;  
102 - }  
103 -  
104 - // 3. 返回标准响应  
105 - IotStandardResponse response;  
106 - if (result.isSuccess()) {  
107 - response = IotStandardResponse.success(requestId, method, result.getData());  
108 - } else {  
109 - response = IotStandardResponse.error(requestId, method, result.getCode(), result.getMsg());  
110 - }  
111 - IotPluginCommonUtils.writeJsonResponse(routingContext, response);  
112 - } catch (Exception e) {  
113 - log.error("[handle][处理上行请求异常] path={}", path, e);  
114 - String method = path.contains("/property/") ? PROPERTY_METHOD  
115 - : EVENT_METHOD_PREFIX + (routingContext.pathParams().containsKey("identifier")  
116 - ? routingContext.pathParam("identifier")  
117 - : "unknown") + EVENT_METHOD_SUFFIX;  
118 - IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method,  
119 - INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());  
120 - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);  
121 - }  
122 - }  
123 -  
124 - /**  
125 - * 更新设备状态  
126 - *  
127 - * @param productKey 产品 Key  
128 - * @param deviceName 设备名称  
129 - */  
130 - private void updateDeviceState(String productKey, String deviceName) {  
131 - deviceUpstreamApi.updateDeviceState(((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO()  
132 - .setRequestId(IdUtil.fastSimpleUUID()).setProcessId(IotPluginCommonUtils.getProcessId())  
133 - .setReportTime(LocalDateTime.now())  
134 - .setProductKey(productKey).setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState()));  
135 - }  
136 -  
137 - /**  
138 - * 解析属性上报请求  
139 - *  
140 - * @param productKey 产品 Key  
141 - * @param deviceName 设备名称  
142 - * @param requestId 请求 ID  
143 - * @param body 请求体  
144 - * @return 属性上报请求 DTO  
145 - */  
146 - private IotDevicePropertyReportReqDTO parsePropertyReportRequest(String productKey, String deviceName,  
147 - String requestId, JsonObject body) {  
148 - // 使用脚本解析数据  
149 -// Map<String, Object> properties = scriptService.parsePropertyData(productKey, deviceName, body);  
150 -  
151 -  
152 - // 如果脚本解析结果为空,使用默认解析逻辑  
153 - // TODO @芋艿:注释说明一下,为什么要这么处理?  
154 -// if (CollUtil.isNotEmpty(properties)) {  
155 - Map<String, Object> properties = new HashMap<>();  
156 - Map<String, Object> params = body.getJsonObject("params") != null ?  
157 - body.getJsonObject("params").getMap() : null;  
158 - if (params != null) {  
159 - // 将标准格式的 params 转换为平台需要的 properties 格式  
160 - for (Map.Entry<String, Object> entry : params.entrySet()) {  
161 - String key = entry.getKey();  
162 - Object valueObj = entry.getValue();  
163 - // 如果是复杂结构(包含 value 和 time)  
164 - if (valueObj instanceof Map) {  
165 - @SuppressWarnings("unchecked")  
166 - Map<String, Object> valueMap = (Map<String, Object>) valueObj;  
167 - properties.put(key, valueMap.getOrDefault("value", valueObj));  
168 - } else {  
169 - properties.put(key, valueObj);  
170 - }  
171 - }  
172 - }  
173 -// }  
174 -  
175 - // 构建属性上报请求 DTO  
176 - return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO().setRequestId(requestId)  
177 - .setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())  
178 - .setProductKey(productKey).setDeviceName(deviceName)).setProperties(properties);  
179 - }  
180 -  
181 - /**  
182 - * 解析事件上报请求  
183 - *  
184 - * @param productKey 产品K ey  
185 - * @param deviceName 设备名称  
186 - * @param identifier 事件标识符  
187 - * @param requestId 请求 ID  
188 - * @param body 请求体  
189 - * @return 事件上报请求 DTO  
190 - */  
191 - private IotDeviceEventReportReqDTO parseEventReportRequest(String productKey, String deviceName, String identifier,  
192 - String requestId, JsonObject body) {  
193 - // 使用脚本解析事件数据  
194 -// Map<String, Object> params = scriptService.parseEventData(productKey, deviceName, identifier, body);  
195 - Map<String, Object> params = null;  
196 -  
197 - // 如果脚本解析结果为空,使用默认解析逻辑  
198 -// if (CollUtil.isNotEmpty(params)) {  
199 - if (body.containsKey("params")) {  
200 - params = body.getJsonObject("params").getMap();  
201 - } else {  
202 - // 兼容旧格式  
203 - params = new HashMap<>();  
204 - }  
205 -// }  
206 -  
207 - // 构建事件上报请求 DTO  
208 - return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO().setRequestId(requestId)  
209 - .setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())  
210 - .setProductKey(productKey).setDeviceName(deviceName)).setIdentifier(identifier).setParams(params);  
211 - }  
212 -}  
213 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted
1 -cn.iocoder.yudao.module.iot.component.http.config.IotComponentHttpAutoConfiguration  
2 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/README.md renamed to yudao-module-iot/yudao-module-iot-net-components/README.md
@@ -4,9 +4,9 @@ @@ -4,9 +4,9 @@
4 4
5 该模块包含多个 IoT 设备连接组件,提供不同的通信协议支持: 5 该模块包含多个 IoT 设备连接组件,提供不同的通信协议支持:
6 6
7 -- `yudao-module-iot-component-core`: 核心接口和通用类  
8 -- `yudao-module-iot-component-http`: 基于 HTTP 协议的设备通信组件  
9 -- `yudao-module-iot-component-emqx`: 基于 MQTT/EMQX 的设备通信组件 7 +- `yudao-module-iot-net-component-core`: 核心接口和通用类
  8 +- `yudao-module-iot-net-component-http`: 基于 HTTP 协议的设备通信组件
  9 +- `yudao-module-iot-net-component-emqx`: 基于 MQTT/EMQX 的设备通信组件
10 10
11 ## 组件架构 11 ## 组件架构
12 12
yudao-module-iot/yudao-module-iot-components/pom.xml renamed to yudao-module-iot/yudao-module-iot-net-components/pom.xml
@@ -9,18 +9,18 @@ @@ -9,18 +9,18 @@
9 </parent> 9 </parent>
10 <modelVersion>4.0.0</modelVersion> 10 <modelVersion>4.0.0</modelVersion>
11 11
12 - <artifactId>yudao-module-iot-components</artifactId> 12 + <artifactId>yudao-module-iot-net-components</artifactId>
13 <packaging>pom</packaging> 13 <packaging>pom</packaging>
14 14
15 <name>${project.artifactId}</name> 15 <name>${project.artifactId}</name>
16 <description> 16 <description>
17 - 物联网组件模块,提供与物联网设备通讯、管理的组件实现 17 + 物联网网络组件模块,提供与物联网设备通讯、管理的网络组件实现
18 </description> 18 </description>
19 19
20 <modules> 20 <modules>
21 - <module>yudao-module-iot-component-core</module>  
22 - <module>yudao-module-iot-component-http</module>  
23 - <module>yudao-module-iot-component-emqx</module> 21 + <module>yudao-module-iot-net-component-core</module>
  22 + <module>yudao-module-iot-net-component-http</module>
  23 + <module>yudao-module-iot-net-component-emqx</module>
24 </modules> 24 </modules>
25 25
26 </project> 26 </project>
27 \ No newline at end of file 27 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/pom.xml renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/pom.xml
@@ -3,19 +3,18 @@ @@ -3,19 +3,18 @@
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 <parent> 5 <parent>
6 - <artifactId>yudao-module-iot-components</artifactId> 6 + <artifactId>yudao-module-iot-net-components</artifactId>
7 <groupId>cn.iocoder.boot</groupId> 7 <groupId>cn.iocoder.boot</groupId>
8 <version>${revision}</version> 8 <version>${revision}</version>
9 </parent> 9 </parent>
10 <modelVersion>4.0.0</modelVersion> 10 <modelVersion>4.0.0</modelVersion>
11 11
12 - <artifactId>yudao-module-iot-component-core</artifactId> 12 + <artifactId>yudao-module-iot-net-component-core</artifactId>
13 <packaging>jar</packaging> 13 <packaging>jar</packaging>
14 14
15 <name>${project.artifactId}</name> 15 <name>${project.artifactId}</name>
16 - <!-- TODO @芋艿:description 后续统一优化一波 -->  
17 <description> 16 <description>
18 - 物联网组件核心模块 17 + 物联网网络组件核心模块
19 </description> 18 </description>
20 19
21 <dependencies> 20 <dependencies>
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/config/IotComponentCommonAutoConfiguration.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonAutoConfiguration.java
1 -package cn.iocoder.yudao.module.iot.component.core.config; 1 +package cn.iocoder.yudao.module.iot.net.component.core.config;
2 2
3 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 3 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
4 -import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler;  
5 -import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamServer;  
6 -import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentInstanceHeartbeatJob;  
7 -import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;  
8 -import cn.iocoder.yudao.module.iot.component.core.upstream.IotDeviceUpstreamClient; 4 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
  5 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamServer;
  6 +import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentInstanceHeartbeatJob;
  7 +import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry;
  8 +import cn.iocoder.yudao.module.iot.net.component.core.upstream.IotDeviceUpstreamClient;
9 import org.springframework.beans.factory.annotation.Qualifier; 9 import org.springframework.beans.factory.annotation.Qualifier;
10 import org.springframework.boot.autoconfigure.AutoConfiguration; 10 import org.springframework.boot.autoconfigure.AutoConfiguration;
11 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -14,37 +14,45 @@ import org.springframework.context.annotation.Bean; @@ -14,37 +14,45 @@ import org.springframework.context.annotation.Bean;
14 import org.springframework.scheduling.annotation.EnableScheduling; 14 import org.springframework.scheduling.annotation.EnableScheduling;
15 15
16 /** 16 /**
17 - * IoT 组件的通用自动配置类 17 + * IoT 网络组件的通用自动配置类
18 * 18 *
19 * @author haohao 19 * @author haohao
20 */ 20 */
21 @AutoConfiguration 21 @AutoConfiguration
22 -@EnableConfigurationProperties(IotComponentCommonProperties.class)  
23 -@EnableScheduling // 开启定时任务,因为 IotComponentInstanceHeartbeatJob 是一个定时任务  
24 -public class IotComponentCommonAutoConfiguration { 22 +@EnableConfigurationProperties(IotNetComponentCommonProperties.class)
  23 +@EnableScheduling // 开启定时任务,因为 IotNetComponentInstanceHeartbeatJob 是一个定时任务
  24 +public class IotNetComponentCommonAutoConfiguration {
25 25
26 /** 26 /**
27 * 创建 EMQX 设备下行服务器 27 * 创建 EMQX 设备下行服务器
28 - * 28 + * <p>
29 * 当 yudao.iot.component.emqx.enabled = true 时,优先使用 emqxDeviceDownstreamHandler 29 * 当 yudao.iot.component.emqx.enabled = true 时,优先使用 emqxDeviceDownstreamHandler
30 */ 30 */
31 @Bean 31 @Bean
32 @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true") 32 @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true")
33 public IotDeviceDownstreamServer emqxDeviceDownstreamServer( 33 public IotDeviceDownstreamServer emqxDeviceDownstreamServer(
34 - IotComponentCommonProperties properties, 34 + IotNetComponentCommonProperties properties,
35 @Qualifier("emqxDeviceDownstreamHandler") IotDeviceDownstreamHandler deviceDownstreamHandler) { 35 @Qualifier("emqxDeviceDownstreamHandler") IotDeviceDownstreamHandler deviceDownstreamHandler) {
36 return new IotDeviceDownstreamServer(properties, deviceDownstreamHandler); 36 return new IotDeviceDownstreamServer(properties, deviceDownstreamHandler);
37 } 37 }
38 38
  39 + /**
  40 + * 创建网络组件实例心跳任务
  41 + */
39 @Bean(initMethod = "init", destroyMethod = "stop") 42 @Bean(initMethod = "init", destroyMethod = "stop")
40 - public IotComponentInstanceHeartbeatJob pluginInstanceHeartbeatJob(IotDeviceUpstreamApi deviceUpstreamApi,  
41 - IotDeviceDownstreamServer deviceDownstreamServer,  
42 - IotComponentCommonProperties commonProperties,  
43 - IotComponentRegistry componentRegistry) {  
44 - return new IotComponentInstanceHeartbeatJob(deviceUpstreamApi, deviceDownstreamServer, commonProperties, 43 + public IotNetComponentInstanceHeartbeatJob pluginInstanceHeartbeatJob(
  44 + IotDeviceUpstreamApi deviceUpstreamApi,
  45 + IotNetComponentCommonProperties commonProperties,
  46 + IotNetComponentRegistry componentRegistry) {
  47 + return new IotNetComponentInstanceHeartbeatJob(
  48 + deviceUpstreamApi,
  49 + commonProperties,
45 componentRegistry); 50 componentRegistry);
46 } 51 }
47 52
  53 + /**
  54 + * 创建设备上行客户端
  55 + */
48 @Bean 56 @Bean
49 public IotDeviceUpstreamClient deviceUpstreamClient() { 57 public IotDeviceUpstreamClient deviceUpstreamClient() {
50 return new IotDeviceUpstreamClient(); 58 return new IotDeviceUpstreamClient();
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.core.config;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +/**
  8 + * IoT 网络组件通用配置属性
  9 + *
  10 + * @author haohao
  11 + */
  12 +@ConfigurationProperties(prefix = "yudao.iot.component")
  13 +@Validated
  14 +@Data
  15 +public class IotNetComponentCommonProperties {
  16 +
  17 + /**
  18 + * 组件的唯一标识
  19 + * <p>
  20 + * 注意:该值将在运行时由各组件设置,不再从配置读取
  21 + */
  22 + private String pluginKey;
  23 +
  24 + /**
  25 + * 组件实例心跳超时时间,单位:毫秒
  26 + * <p>
  27 + * 默认值:30 秒
  28 + */
  29 + private Long instanceHeartbeatTimeout = 30000L;
  30 +
  31 + /**
  32 + * 网络组件消息转发配置
  33 + */
  34 + private ForwardMessage forwardMessage = new ForwardMessage();
  35 +
  36 + /**
  37 + * 消息转发配置
  38 + */
  39 + @Data
  40 + public static class ForwardMessage {
  41 +
  42 + /**
  43 + * 是否转发所有设备消息到 EMQX
  44 + * <p>
  45 + * 默认为 true 开启
  46 + */
  47 + private boolean forwardAllDeviceMessageToEmqx = true;
  48 +
  49 + /**
  50 + * 是否转发所有设备消息到 HTTP
  51 + * <p>
  52 + * 默认为 false 关闭
  53 + */
  54 + private boolean forwardAllDeviceMessageToHttp = false;
  55 + }
  56 +}
0 \ No newline at end of file 57 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/constants/IotDeviceTopicEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.core.constants;
  2 +
  3 +import lombok.Getter;
  4 +
  5 +/**
  6 + * IoT 设备主题枚举
  7 + * <p>
  8 + * 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范
  9 + *
  10 + * @author haohao
  11 + */
  12 +@Getter
  13 +public enum IotDeviceTopicEnum {
  14 +
  15 + /**
  16 + * 系统主题前缀
  17 + */
  18 + SYS_TOPIC_PREFIX("/sys/", "系统主题前缀"),
  19 +
  20 + /**
  21 + * 服务调用主题前缀
  22 + */
  23 + SERVICE_TOPIC_PREFIX("/thing/service/", "服务调用主题前缀"),
  24 +
  25 + /**
  26 + * 设备属性设置主题
  27 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set
  28 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply
  29 + */
  30 + PROPERTY_SET_TOPIC("/thing/service/property/set", "设备属性设置主题"),
  31 +
  32 + /**
  33 + * 设备属性获取主题
  34 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/get
  35 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/get_reply
  36 + */
  37 + PROPERTY_GET_TOPIC("/thing/service/property/get", "设备属性获取主题"),
  38 +
  39 + /**
  40 + * 设备配置设置主题
  41 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/config/set
  42 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/config/set_reply
  43 + */
  44 + CONFIG_SET_TOPIC("/thing/service/config/set", "设备配置设置主题"),
  45 +
  46 + /**
  47 + * 设备OTA升级主题
  48 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade
  49 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade_reply
  50 + */
  51 + OTA_UPGRADE_TOPIC("/thing/service/ota/upgrade", "设备OTA升级主题"),
  52 +
  53 + /**
  54 + * 设备属性上报主题
  55 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
  56 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
  57 + */
  58 + PROPERTY_POST_TOPIC("/thing/event/property/post", "设备属性上报主题"),
  59 +
  60 + /**
  61 + * 设备事件上报主题前缀
  62 + */
  63 + EVENT_POST_TOPIC_PREFIX("/thing/event/", "设备事件上报主题前缀"),
  64 +
  65 + /**
  66 + * 设备事件上报主题后缀
  67 + */
  68 + EVENT_POST_TOPIC_SUFFIX("/post", "设备事件上报主题后缀"),
  69 +
  70 + /**
  71 + * 响应主题后缀
  72 + */
  73 + REPLY_SUFFIX("_reply", "响应主题后缀");
  74 +
  75 + private final String topic;
  76 + private final String description;
  77 +
  78 + IotDeviceTopicEnum(String topic, String description) {
  79 + this.topic = topic;
  80 + this.description = description;
  81 + }
  82 +
  83 + /**
  84 + * 构建设备服务调用主题
  85 + *
  86 + * @param productKey 产品Key
  87 + * @param deviceName 设备名称
  88 + * @param serviceIdentifier 服务标识符
  89 + * @return 完整的主题路径
  90 + */
  91 + public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
  92 + return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName +
  93 + SERVICE_TOPIC_PREFIX.getTopic() + serviceIdentifier;
  94 + }
  95 +
  96 + /**
  97 + * 构建设备属性设置主题
  98 + *
  99 + * @param productKey 产品Key
  100 + * @param deviceName 设备名称
  101 + * @return 完整的主题路径
  102 + */
  103 + public static String buildPropertySetTopic(String productKey, String deviceName) {
  104 + return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_SET_TOPIC.getTopic();
  105 + }
  106 +
  107 + /**
  108 + * 构建设备属性获取主题
  109 + *
  110 + * @param productKey 产品Key
  111 + * @param deviceName 设备名称
  112 + * @return 完整的主题路径
  113 + */
  114 + public static String buildPropertyGetTopic(String productKey, String deviceName) {
  115 + return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_GET_TOPIC.getTopic();
  116 + }
  117 +
  118 + /**
  119 + * 构建设备配置设置主题
  120 + *
  121 + * @param productKey 产品Key
  122 + * @param deviceName 设备名称
  123 + * @return 完整的主题路径
  124 + */
  125 + public static String buildConfigSetTopic(String productKey, String deviceName) {
  126 + return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + CONFIG_SET_TOPIC.getTopic();
  127 + }
  128 +
  129 + /**
  130 + * 构建设备OTA升级主题
  131 + *
  132 + * @param productKey 产品Key
  133 + * @param deviceName 设备名称
  134 + * @return 完整的主题路径
  135 + */
  136 + public static String buildOtaUpgradeTopic(String productKey, String deviceName) {
  137 + return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + OTA_UPGRADE_TOPIC.getTopic();
  138 + }
  139 +
  140 + /**
  141 + * 构建设备属性上报主题
  142 + *
  143 + * @param productKey 产品Key
  144 + * @param deviceName 设备名称
  145 + * @return 完整的主题路径
  146 + */
  147 + public static String buildPropertyPostTopic(String productKey, String deviceName) {
  148 + return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_POST_TOPIC.getTopic();
  149 + }
  150 +
  151 + /**
  152 + * 构建设备事件上报主题
  153 + *
  154 + * @param productKey 产品Key
  155 + * @param deviceName 设备名称
  156 + * @param eventIdentifier 事件标识符
  157 + * @return 完整的主题路径
  158 + */
  159 + public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) {
  160 + return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName +
  161 + EVENT_POST_TOPIC_PREFIX.getTopic() + eventIdentifier + EVENT_POST_TOPIC_SUFFIX.getTopic();
  162 + }
  163 +
  164 + /**
  165 + * 获取响应主题
  166 + *
  167 + * @param requestTopic 请求主题
  168 + * @return 响应主题
  169 + */
  170 + public static String getReplyTopic(String requestTopic) {
  171 + return requestTopic + REPLY_SUFFIX.getTopic();
  172 + }
  173 +}
0 \ No newline at end of file 174 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/downstream/IotDeviceDownstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/downstream/IotDeviceDownstreamHandler.java
1 -package cn.iocoder.yudao.module.iot.component.core.downstream; 1 +package cn.iocoder.yudao.module.iot.net.component.core.downstream;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*; 4 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/downstream/IotDeviceDownstreamServer.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/downstream/IotDeviceDownstreamServer.java
1 -package cn.iocoder.yudao.module.iot.component.core.downstream; 1 +package cn.iocoder.yudao.module.iot.net.component.core.downstream;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*; 4 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
5 -import cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonProperties; 5 +import cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonProperties;
6 import lombok.RequiredArgsConstructor; 6 import lombok.RequiredArgsConstructor;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
@@ -15,7 +15,7 @@ import lombok.extern.slf4j.Slf4j; @@ -15,7 +15,7 @@ import lombok.extern.slf4j.Slf4j;
15 @RequiredArgsConstructor 15 @RequiredArgsConstructor
16 public class IotDeviceDownstreamServer { 16 public class IotDeviceDownstreamServer {
17 17
18 - private final IotComponentCommonProperties properties; 18 + private final IotNetComponentCommonProperties properties;
19 private final IotDeviceDownstreamHandler deviceDownstreamHandler; 19 private final IotDeviceDownstreamHandler deviceDownstreamHandler;
20 20
21 /** 21 /**
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/heartbeat/IotComponentInstanceHeartbeatJob.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/heartbeat/IotNetComponentInstanceHeartbeatJob.java
1 -package cn.iocoder.yudao.module.iot.component.core.heartbeat; 1 +package cn.iocoder.yudao.module.iot.net.component.core.heartbeat;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.system.SystemUtil; 4 import cn.hutool.system.SystemUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 6 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO; 7 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
7 -import cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonProperties;  
8 -import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamServer;  
9 -import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry.IotComponentInfo; 8 +import cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonProperties;
  9 +import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry.IotNetComponentInfo;
  10 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
10 import lombok.RequiredArgsConstructor; 11 import lombok.RequiredArgsConstructor;
11 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
12 import org.springframework.scheduling.annotation.Scheduled; 13 import org.springframework.scheduling.annotation.Scheduled;
13 14
14 -import java.lang.management.ManagementFactory; 15 +import java.util.Collection;
15 import java.util.concurrent.TimeUnit; 16 import java.util.concurrent.TimeUnit;
16 17
17 /** 18 /**
18 - * IoT 组件实例心跳定时任务 19 + * IoT 网络组件实例心跳定时任务
19 * <p> 20 * <p>
20 * 将组件的状态,定时上报给 server 服务器 21 * 将组件的状态,定时上报给 server 服务器
  22 + *
  23 + * @author haohao
21 */ 24 */
22 @RequiredArgsConstructor 25 @RequiredArgsConstructor
23 @Slf4j 26 @Slf4j
24 -public class IotComponentInstanceHeartbeatJob {  
25 -  
26 - /**  
27 - * 内嵌模式的端口值(固定为 0)  
28 - */  
29 - private static final Integer EMBEDDED_PORT = 0; 27 +public class IotNetComponentInstanceHeartbeatJob {
30 28
31 private final IotDeviceUpstreamApi deviceUpstreamApi; 29 private final IotDeviceUpstreamApi deviceUpstreamApi;
32 - private final IotDeviceDownstreamServer deviceDownstreamServer; // TODO @haohao:这个变量还需要哇?  
33 - private final IotComponentCommonProperties commonProperties;  
34 - private final IotComponentRegistry componentRegistry; 30 + private final IotNetComponentCommonProperties commonProperties;
  31 + private final IotNetComponentRegistry componentRegistry;
35 32
36 /** 33 /**
37 - * 初始化方法,由 Spring调 用:注册当前组件并发送上线心跳 34 + * 初始化方法,由 Spring 调用:注册当前组件并发送上线心跳
38 */ 35 */
39 public void init() { 36 public void init() {
40 - // 将当前组件注册到注册表  
41 - String processId = getProcessId();  
42 - String hostIp = SystemUtil.getHostInfo().getAddress();  
43 -  
44 - // 注册当前组件  
45 - componentRegistry.registerComponent(  
46 - commonProperties.getPluginKey(),  
47 - hostIp,  
48 - EMBEDDED_PORT,  
49 - processId);  
50 -  
51 // 发送所有组件的上线心跳 37 // 发送所有组件的上线心跳
52 - for (IotComponentInfo component : componentRegistry.getAllComponents()) { 38 + Collection<IotNetComponentInfo> components = componentRegistry.getAllComponents();
  39 + if (CollUtil.isEmpty(components)) {
  40 + return;
  41 + }
  42 + for (IotNetComponentInfo component : components) {
53 try { 43 try {
54 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance( 44 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(
55 buildPluginInstanceHeartbeatReqDTO(component, true)); 45 buildPluginInstanceHeartbeatReqDTO(component, true));
56 - log.info("[init][组件({})上线结果:{})]", component.getPluginKey(), result); 46 + log.info("[init][组件({})上线结果:{}]", component.getPluginKey(), result);
57 } catch (Exception e) { 47 } catch (Exception e) {
58 log.error("[init][组件({})上线发送异常]", component.getPluginKey(), e); 48 log.error("[init][组件({})上线发送异常]", component.getPluginKey(), e);
59 } 49 }
@@ -65,11 +55,15 @@ public class IotComponentInstanceHeartbeatJob { @@ -65,11 +55,15 @@ public class IotComponentInstanceHeartbeatJob {
65 */ 55 */
66 public void stop() { 56 public void stop() {
67 // 发送所有组件的下线心跳 57 // 发送所有组件的下线心跳
68 - for (IotComponentInfo component : componentRegistry.getAllComponents()) { 58 + Collection<IotNetComponentInfo> components = componentRegistry.getAllComponents();
  59 + if (CollUtil.isEmpty(components)) {
  60 + return;
  61 + }
  62 + for (IotNetComponentInfo component : components) {
69 try { 63 try {
70 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance( 64 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(
71 buildPluginInstanceHeartbeatReqDTO(component, false)); 65 buildPluginInstanceHeartbeatReqDTO(component, false));
72 - log.info("[stop][组件({})下线结果:{})]", component.getPluginKey(), result); 66 + log.info("[stop][组件({})下线结果:{}]", component.getPluginKey(), result);
73 } catch (Exception e) { 67 } catch (Exception e) {
74 log.error("[stop][组件({})下线发送异常]", component.getPluginKey(), e); 68 log.error("[stop][组件({})下线发送异常]", component.getPluginKey(), e);
75 } 69 }
@@ -85,11 +79,15 @@ public class IotComponentInstanceHeartbeatJob { @@ -85,11 +79,15 @@ public class IotComponentInstanceHeartbeatJob {
85 @Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.MINUTES) // 1 分钟执行一次 79 @Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.MINUTES) // 1 分钟执行一次
86 public void execute() { 80 public void execute() {
87 // 发送所有组件的心跳 81 // 发送所有组件的心跳
88 - for (IotComponentInfo component : componentRegistry.getAllComponents()) { 82 + Collection<IotNetComponentInfo> components = componentRegistry.getAllComponents();
  83 + if (CollUtil.isEmpty(components)) {
  84 + return;
  85 + }
  86 + for (IotNetComponentInfo component : components) {
89 try { 87 try {
90 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance( 88 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(
91 buildPluginInstanceHeartbeatReqDTO(component, true)); 89 buildPluginInstanceHeartbeatReqDTO(component, true));
92 - log.info("[execute][组件({})心跳结果:{})]", component.getPluginKey(), result); 90 + log.info("[execute][组件({})心跳结果:{}]", component.getPluginKey(), result);
93 } catch (Exception e) { 91 } catch (Exception e) {
94 log.error("[execute][组件({})心跳发送异常]", component.getPluginKey(), e); 92 log.error("[execute][组件({})心跳发送异常]", component.getPluginKey(), e);
95 } 93 }
@@ -103,23 +101,11 @@ public class IotComponentInstanceHeartbeatJob { @@ -103,23 +101,11 @@ public class IotComponentInstanceHeartbeatJob {
103 * @param online 是否在线 101 * @param online 是否在线
104 * @return 心跳 DTO 102 * @return 心跳 DTO
105 */ 103 */
106 - private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(IotComponentInfo component, 104 + private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(IotNetComponentInfo component,
107 Boolean online) { 105 Boolean online) {
108 return new IotPluginInstanceHeartbeatReqDTO() 106 return new IotPluginInstanceHeartbeatReqDTO()
109 .setPluginKey(component.getPluginKey()).setProcessId(component.getProcessId()) 107 .setPluginKey(component.getPluginKey()).setProcessId(component.getProcessId())
110 .setHostIp(component.getHostIp()).setDownstreamPort(component.getDownstreamPort()) 108 .setHostIp(component.getHostIp()).setDownstreamPort(component.getDownstreamPort())
111 .setOnline(online); 109 .setOnline(online);
112 } 110 }
113 -  
114 - // TODO @haohao:要和 IotPluginCommonUtils 保持一致么?  
115 - /**  
116 - * 获取当前进程 ID  
117 - *  
118 - * @return 进程 ID  
119 - */  
120 - private String getProcessId() {  
121 - String name = ManagementFactory.getRuntimeMXBean().getName();  
122 - // TODO @haohao:是不是 SystemUtil.getCurrentPID(); 直接获取 pid 哈?  
123 - return name.split("@")[0];  
124 - }  
125 -} 111 +}
126 \ No newline at end of file 112 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/heartbeat/IotComponentRegistry.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/heartbeat/IotNetComponentRegistry.java
1 -package cn.iocoder.yudao.module.iot.component.core.heartbeat; 1 +package cn.iocoder.yudao.module.iot.net.component.core.heartbeat;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
3 import lombok.Data; 5 import lombok.Data;
4 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
5 import org.springframework.stereotype.Component; 7 import org.springframework.stereotype.Component;
@@ -8,49 +10,51 @@ import java.util.Collection; @@ -8,49 +10,51 @@ import java.util.Collection;
8 import java.util.Map; 10 import java.util.Map;
9 import java.util.concurrent.ConcurrentHashMap; 11 import java.util.concurrent.ConcurrentHashMap;
10 12
11 -// TODO @haohao:组件相关的注释,要不把 组件 => 网络组件?可能更容易理解?  
12 -// TODO @haohao:yudao-module-iot-components => yudao-module-iot-net-components 增加一个 net 如何?虽然会长一点,但是意思更精准?  
13 /** 13 /**
14 - * IoT 组件注册表 14 + * IoT 网络组件注册表
15 * <p> 15 * <p>
16 - * 用于管理多个组件的注册信息,解决多组件心跳问题 16 + * 用于管理多个网络组件的注册信息,解决多组件心跳问题
  17 + *
  18 + * @author haohao
17 */ 19 */
18 @Component 20 @Component
19 @Slf4j 21 @Slf4j
20 -public class IotComponentRegistry { 22 +public class IotNetComponentRegistry {
21 23
22 /** 24 /**
23 - * 组件信息 25 + * 网络组件信息
24 */ 26 */
25 @Data 27 @Data
26 - public static class IotComponentInfo { 28 + public static class IotNetComponentInfo {
27 29
28 /** 30 /**
29 * 组件 Key 31 * 组件 Key
30 */ 32 */
31 private final String pluginKey; 33 private final String pluginKey;
  34 +
32 /** 35 /**
33 * 主机 IP 36 * 主机 IP
34 */ 37 */
35 private final String hostIp; 38 private final String hostIp;
  39 +
36 /** 40 /**
37 * 下游端口 41 * 下游端口
38 */ 42 */
39 private final Integer downstreamPort; 43 private final Integer downstreamPort;
  44 +
40 /** 45 /**
41 * 进程 ID 46 * 进程 ID
42 */ 47 */
43 private final String processId; 48 private final String processId;
44 -  
45 } 49 }
46 50
47 /** 51 /**
48 * 组件映射表:key 为组件 Key 52 * 组件映射表:key 为组件 Key
49 */ 53 */
50 - private final Map<String, IotComponentInfo> components = new ConcurrentHashMap<>(); 54 + private final Map<String, IotNetComponentInfo> components = new ConcurrentHashMap<>();
51 55
52 /** 56 /**
53 - * 注册组件 57 + * 注册网络组件
54 * 58 *
55 * @param pluginKey 组件 Key 59 * @param pluginKey 组件 Key
56 * @param hostIp 主机 IP 60 * @param hostIp 主机 IP
@@ -58,38 +62,37 @@ public class IotComponentRegistry { @@ -58,38 +62,37 @@ public class IotComponentRegistry {
58 * @param processId 进程 ID 62 * @param processId 进程 ID
59 */ 63 */
60 public void registerComponent(String pluginKey, String hostIp, Integer downstreamPort, String processId) { 64 public void registerComponent(String pluginKey, String hostIp, Integer downstreamPort, String processId) {
61 - log.info("[registerComponent][注册组件, pluginKey={}, hostIp={}, downstreamPort={}, processId={}]", 65 + log.info("[registerComponent][注册网络组件, pluginKey={}, hostIp={}, downstreamPort={}, processId={}]",
62 pluginKey, hostIp, downstreamPort, processId); 66 pluginKey, hostIp, downstreamPort, processId);
63 - components.put(pluginKey, new IotComponentInfo(pluginKey, hostIp, downstreamPort, processId)); 67 + components.put(pluginKey, new IotNetComponentInfo(pluginKey, hostIp, downstreamPort, processId));
64 } 68 }
65 69
66 /** 70 /**
67 - * 注销组件 71 + * 注销网络组件
68 * 72 *
69 * @param pluginKey 组件 Key 73 * @param pluginKey 组件 Key
70 */ 74 */
71 public void unregisterComponent(String pluginKey) { 75 public void unregisterComponent(String pluginKey) {
72 - log.info("[unregisterComponent][注销组件, pluginKey={}]", pluginKey); 76 + log.info("[unregisterComponent][注销网络组件, pluginKey={}]", pluginKey);
73 components.remove(pluginKey); 77 components.remove(pluginKey);
74 } 78 }
75 79
76 /** 80 /**
77 - * 获取所有组件 81 + * 获取所有网络组件
78 * 82 *
79 * @return 所有组件集合 83 * @return 所有组件集合
80 */ 84 */
81 - public Collection<IotComponentInfo> getAllComponents() {  
82 - return components.values(); 85 + public Collection<IotNetComponentInfo> getAllComponents() {
  86 + return CollUtil.isEmpty(components) ? CollUtil.newArrayList() : components.values();
83 } 87 }
84 88
85 /** 89 /**
86 - * 获取指定组件 90 + * 获取指定网络组件
87 * 91 *
88 * @param pluginKey 组件 Key 92 * @param pluginKey 组件 Key
89 * @return 组件信息 93 * @return 组件信息
90 */ 94 */
91 - public IotComponentInfo getComponent(String pluginKey) {  
92 - return components.get(pluginKey); 95 + public IotNetComponentInfo getComponent(String pluginKey) {
  96 + return MapUtil.isEmpty(components) ? null : components.get(pluginKey);
93 } 97 }
94 -  
95 } 98 }
96 \ No newline at end of file 99 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/message/IotAlinkMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.core.message;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.json.JSONObject;
  5 +import lombok.Builder;
  6 +import lombok.Data;
  7 +
  8 +import java.util.Map;
  9 +
  10 +/**
  11 + * IoT Alink 消息模型
  12 + * <p>
  13 + * 基于阿里云 Alink 协议规范实现的标准消息格式
  14 + *
  15 + * @author haohao
  16 + */
  17 +@Data
  18 +@Builder
  19 +public class IotAlinkMessage {
  20 +
  21 + /**
  22 + * 消息 ID
  23 + */
  24 + private String id;
  25 +
  26 + /**
  27 + * 协议版本
  28 + */
  29 + @Builder.Default
  30 + private String version = "1.0";
  31 +
  32 + /**
  33 + * 消息方法
  34 + */
  35 + private String method;
  36 +
  37 + /**
  38 + * 消息参数
  39 + */
  40 + private Map<String, Object> params;
  41 +
  42 + /**
  43 + * 转换为 JSONObject
  44 + *
  45 + * @return JSONObject 对象
  46 + */
  47 + public JSONObject toJsonObject() {
  48 + JSONObject json = new JSONObject();
  49 + json.set("id", id);
  50 + json.set("version", version);
  51 + json.set("method", method);
  52 + json.set("params", params != null ? params : new JSONObject());
  53 + return json;
  54 + }
  55 +
  56 + /**
  57 + * 转换为 JSON 字符串
  58 + *
  59 + * @return JSON 字符串
  60 + */
  61 + public String toJsonString() {
  62 + return toJsonObject().toString();
  63 + }
  64 +
  65 + /**
  66 + * 创建设备服务调用消息
  67 + *
  68 + * @param requestId 请求 ID,为空时自动生成
  69 + * @param serviceIdentifier 服务标识符
  70 + * @param params 服务参数
  71 + * @return Alink 消息对象
  72 + */
  73 + public static IotAlinkMessage createServiceInvokeMessage(String requestId, String serviceIdentifier,
  74 + Map<String, Object> params) {
  75 + return IotAlinkMessage.builder()
  76 + .id(requestId != null ? requestId : generateRequestId())
  77 + .method("thing.service." + serviceIdentifier)
  78 + .params(params)
  79 + .build();
  80 + }
  81 +
  82 + /**
  83 + * 创建设备属性设置消息
  84 + *
  85 + * @param requestId 请求 ID,为空时自动生成
  86 + * @param properties 设备属性
  87 + * @return Alink 消息对象
  88 + */
  89 + public static IotAlinkMessage createPropertySetMessage(String requestId, Map<String, Object> properties) {
  90 + return IotAlinkMessage.builder()
  91 + .id(requestId != null ? requestId : generateRequestId())
  92 + .method("thing.service.property.set")
  93 + .params(properties)
  94 + .build();
  95 + }
  96 +
  97 + /**
  98 + * 创建设备属性获取消息
  99 + *
  100 + * @param requestId 请求 ID,为空时自动生成
  101 + * @param identifiers 要获取的属性标识符列表
  102 + * @return Alink 消息对象
  103 + */
  104 + public static IotAlinkMessage createPropertyGetMessage(String requestId, String[] identifiers) {
  105 + JSONObject params = new JSONObject();
  106 + params.set("identifiers", identifiers);
  107 +
  108 + return IotAlinkMessage.builder()
  109 + .id(requestId != null ? requestId : generateRequestId())
  110 + .method("thing.service.property.get")
  111 + .params(params)
  112 + .build();
  113 + }
  114 +
  115 + /**
  116 + * 创建设备配置设置消息
  117 + *
  118 + * @param requestId 请求 ID,为空时自动生成
  119 + * @param configs 设备配置
  120 + * @return Alink 消息对象
  121 + */
  122 + public static IotAlinkMessage createConfigSetMessage(String requestId, Map<String, Object> configs) {
  123 + return IotAlinkMessage.builder()
  124 + .id(requestId != null ? requestId : generateRequestId())
  125 + .method("thing.service.config.set")
  126 + .params(configs)
  127 + .build();
  128 + }
  129 +
  130 + /**
  131 + * 创建设备 OTA 升级消息
  132 + *
  133 + * @param requestId 请求 ID,为空时自动生成
  134 + * @param otaInfo OTA 升级信息
  135 + * @return Alink 消息对象
  136 + */
  137 + public static IotAlinkMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) {
  138 + return IotAlinkMessage.builder()
  139 + .id(requestId != null ? requestId : generateRequestId())
  140 + .method("thing.service.ota.upgrade")
  141 + .params(otaInfo)
  142 + .build();
  143 + }
  144 +
  145 + /**
  146 + * 生成请求 ID
  147 + *
  148 + * @return 请求 ID
  149 + */
  150 + public static String generateRequestId() {
  151 + return IdUtil.fastSimpleUUID();
  152 + }
  153 +}
0 \ No newline at end of file 154 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/pojo/IotStandardResponse.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/pojo/IotStandardResponse.java
1 -package cn.iocoder.yudao.module.iot.component.core.pojo; 1 +package cn.iocoder.yudao.module.iot.net.component.core.pojo;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import lombok.Data; 4 import lombok.Data;
  5 +import lombok.experimental.Accessors;
4 6
5 /** 7 /**
6 * IoT 标准协议响应实体类 8 * IoT 标准协议响应实体类
@@ -10,10 +12,11 @@ import lombok.Data; @@ -10,10 +12,11 @@ import lombok.Data;
10 * @author haohao 12 * @author haohao
11 */ 13 */
12 @Data 14 @Data
  15 +@Accessors(chain = true)
13 public class IotStandardResponse { 16 public class IotStandardResponse {
14 17
15 /** 18 /**
16 - * 消息ID 19 + * 消息 ID
17 */ 20 */
18 private String id; 21 private String id;
19 22
@@ -45,7 +48,7 @@ public class IotStandardResponse { @@ -45,7 +48,7 @@ public class IotStandardResponse {
45 /** 48 /**
46 * 创建成功响应 49 * 创建成功响应
47 * 50 *
48 - * @param id 消息ID 51 + * @param id 消息 ID
49 * @param method 方法名 52 * @param method 方法名
50 * @return 成功响应 53 * @return 成功响应
51 */ 54 */
@@ -56,28 +59,37 @@ public class IotStandardResponse { @@ -56,28 +59,37 @@ public class IotStandardResponse {
56 /** 59 /**
57 * 创建成功响应 60 * 创建成功响应
58 * 61 *
59 - * @param id 消息ID 62 + * @param id 消息 ID
60 * @param method 方法名 63 * @param method 方法名
61 * @param data 响应数据 64 * @param data 响应数据
62 * @return 成功响应 65 * @return 成功响应
63 */ 66 */
64 public static IotStandardResponse success(String id, String method, Object data) { 67 public static IotStandardResponse success(String id, String method, Object data) {
65 - return new IotStandardResponse().setId(id).setCode(200).setData(data).setMessage("success")  
66 - .setMethod(method).setVersion("1.0"); 68 + return new IotStandardResponse()
  69 + .setId(id)
  70 + .setCode(200)
  71 + .setData(data)
  72 + .setMessage("success")
  73 + .setMethod(method)
  74 + .setVersion("1.0");
67 } 75 }
68 76
69 /** 77 /**
70 * 创建错误响应 78 * 创建错误响应
71 * 79 *
72 - * @param id 消息ID 80 + * @param id 消息 ID
73 * @param method 方法名 81 * @param method 方法名
74 * @param code 错误码 82 * @param code 错误码
75 * @param message 错误消息 83 * @param message 错误消息
76 * @return 错误响应 84 * @return 错误响应
77 */ 85 */
78 public static IotStandardResponse error(String id, String method, Integer code, String message) { 86 public static IotStandardResponse error(String id, String method, Integer code, String message) {
79 - return new IotStandardResponse().setId(id).setCode(code).setData(null).setMessage(message)  
80 - .setMethod(method).setVersion("1.0"); 87 + return new IotStandardResponse()
  88 + .setId(id)
  89 + .setCode(code)
  90 + .setData(null)
  91 + .setMessage(StrUtil.blankToDefault(message, "error"))
  92 + .setMethod(method)
  93 + .setVersion("1.0");
81 } 94 }
82 -  
83 } 95 }
84 \ No newline at end of file 96 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/upstream/IotDeviceUpstreamClient.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/upstream/IotDeviceUpstreamClient.java
1 -package cn.iocoder.yudao.module.iot.component.core.upstream; 1 +package cn.iocoder.yudao.module.iot.net.component.core.upstream;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 4 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/util/IotPluginCommonUtils.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/util/IotNetComponentCommonUtils.java
1 -package cn.iocoder.yudao.module.iot.component.core.util; 1 +package cn.iocoder.yudao.module.iot.net.component.core.util;
2 2
3 import cn.hutool.core.util.IdUtil; 3 import cn.hutool.core.util.IdUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.hutool.system.SystemUtil; 5 import cn.hutool.system.SystemUtil;
6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 -import cn.iocoder.yudao.module.iot.component.core.pojo.IotStandardResponse; 7 +import cn.iocoder.yudao.module.iot.net.component.core.pojo.IotStandardResponse;
8 import io.vertx.core.http.HttpHeaders; 8 import io.vertx.core.http.HttpHeaders;
9 import io.vertx.ext.web.RoutingContext; 9 import io.vertx.ext.web.RoutingContext;
10 import org.springframework.http.MediaType; 10 import org.springframework.http.MediaType;
11 11
12 -// TODO @haohao:名字要改下哈。  
13 /** 12 /**
14 - * IoT 插件的通用工具类 13 + * IoT 网络组件的通用工具类
15 * 14 *
16 * @author 芋道源码 15 * @author 芋道源码
17 */ 16 */
18 -public class IotPluginCommonUtils { 17 +public class IotNetComponentCommonUtils {
19 18
20 /** 19 /**
21 * 流程实例的进程编号 20 * 流程实例的进程编号
22 */ 21 */
23 private static String processId; 22 private static String processId;
24 23
  24 + /**
  25 + * 获取进程ID
  26 + *
  27 + * @return 进程ID
  28 + */
25 public static String getProcessId() { 29 public static String getProcessId() {
26 if (StrUtil.isEmpty(processId)) { 30 if (StrUtil.isEmpty(processId)) {
27 initProcessId(); 31 initProcessId();
@@ -29,12 +33,24 @@ public class IotPluginCommonUtils { @@ -29,12 +33,24 @@ public class IotPluginCommonUtils {
29 return processId; 33 return processId;
30 } 34 }
31 35
  36 + /**
  37 + * 初始化进程ID
  38 + */
32 private synchronized static void initProcessId() { 39 private synchronized static void initProcessId() {
33 processId = String.format("%s@%d@%s", // IP@PID@${uuid} 40 processId = String.format("%s@%d@%s", // IP@PID@${uuid}
34 SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID(), IdUtil.fastSimpleUUID()); 41 SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID(), IdUtil.fastSimpleUUID());
35 } 42 }
36 43
37 /** 44 /**
  45 + * 生成请求ID
  46 + *
  47 + * @return 生成的唯一请求ID
  48 + */
  49 + public static String generateRequestId() {
  50 + return IdUtil.fastSimpleUUID();
  51 + }
  52 +
  53 + /**
38 * 将对象转换为JSON字符串后写入HTTP响应 54 * 将对象转换为JSON字符串后写入HTTP响应
39 * 55 *
40 * @param routingContext 路由上下文 56 * @param routingContext 路由上下文
@@ -51,20 +67,20 @@ public class IotPluginCommonUtils { @@ -51,20 +67,20 @@ public class IotPluginCommonUtils {
51 /** 67 /**
52 * 生成标准JSON格式的响应并写入HTTP响应(基于IotStandardResponse) 68 * 生成标准JSON格式的响应并写入HTTP响应(基于IotStandardResponse)
53 * <p> 69 * <p>
54 - * 推荐使用此方法,统一MQTT和HTTP的响应格式。使用方式: 70 + * 推荐使用此方法,统一 MQTT 和 HTTP 的响应格式。使用方式:
55 * 71 *
56 * <pre> 72 * <pre>
57 * // 成功响应 73 * // 成功响应
58 * IotStandardResponse response = IotStandardResponse.success(requestId, method, data); 74 * IotStandardResponse response = IotStandardResponse.success(requestId, method, data);
59 - * IotPluginCommonUtils.writeJsonResponse(routingContext, response); 75 + * IotNetComponentCommonUtils.writeJsonResponse(routingContext, response);
60 * 76 *
61 * // 错误响应 77 * // 错误响应
62 * IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message); 78 * IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
63 - * IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse); 79 + * IotNetComponentCommonUtils.writeJsonResponse(routingContext, errorResponse);
64 * </pre> 80 * </pre>
65 * 81 *
66 * @param routingContext 路由上下文 82 * @param routingContext 路由上下文
67 - * @param response IotStandardResponse响应对象 83 + * @param response IotStandardResponse 响应对象
68 */ 84 */
69 @SuppressWarnings("deprecation") 85 @SuppressWarnings("deprecation")
70 public static void writeJsonResponse(RoutingContext routingContext, IotStandardResponse response) { 86 public static void writeJsonResponse(RoutingContext routingContext, IotStandardResponse response) {
@@ -73,5 +89,4 @@ public class IotPluginCommonUtils { @@ -73,5 +89,4 @@ public class IotPluginCommonUtils {
73 .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE) 89 .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
74 .end(JsonUtils.toJsonString(response)); 90 .end(JsonUtils.toJsonString(response));
75 } 91 }
76 -  
77 -} 92 +}
78 \ No newline at end of file 93 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2 + cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonAutoConfiguration
0 \ No newline at end of file 3 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 0 → 100644
  1 +cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonAutoConfiguration
0 \ No newline at end of file 2 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/pom.xml renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/pom.xml
@@ -3,23 +3,23 @@ @@ -3,23 +3,23 @@
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 <parent> 5 <parent>
6 - <artifactId>yudao-module-iot-components</artifactId> 6 + <artifactId>yudao-module-iot-net-components</artifactId>
7 <groupId>cn.iocoder.boot</groupId> 7 <groupId>cn.iocoder.boot</groupId>
8 <version>${revision}</version> 8 <version>${revision}</version>
9 </parent> 9 </parent>
10 <modelVersion>4.0.0</modelVersion> 10 <modelVersion>4.0.0</modelVersion>
11 - <artifactId>yudao-module-iot-component-emqx</artifactId> 11 + <artifactId>yudao-module-iot-net-component-emqx</artifactId>
12 <packaging>jar</packaging> 12 <packaging>jar</packaging>
13 13
14 <name>${project.artifactId}</name> 14 <name>${project.artifactId}</name>
15 <description> 15 <description>
16 - 物联网组件 EMQX 模块 16 + 物联网网络组件 EMQX 模块
17 </description> 17 </description>
18 18
19 <dependencies> 19 <dependencies>
20 <dependency> 20 <dependency>
21 <groupId>cn.iocoder.boot</groupId> 21 <groupId>cn.iocoder.boot</groupId>
22 - <artifactId>yudao-module-iot-component-core</artifactId> 22 + <artifactId>yudao-module-iot-net-component-core</artifactId>
23 <version>${revision}</version> 23 <version>${revision}</version>
24 </dependency> 24 </dependency>
25 25
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/config/IotComponentEmqxAutoConfiguration.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/config/IotNetComponentEmqxAutoConfiguration.java
1 -package cn.iocoder.yudao.module.iot.component.emqx.config; 1 +package cn.iocoder.yudao.module.iot.net.component.emqx.config;
2 2
3 import cn.hutool.core.util.IdUtil; 3 import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.core.util.ObjUtil;
4 import cn.hutool.system.SystemUtil; 5 import cn.hutool.system.SystemUtil;
5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 6 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 -import cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonProperties;  
7 -import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler;  
8 -import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;  
9 -import cn.iocoder.yudao.module.iot.component.emqx.downstream.IotDeviceDownstreamHandlerImpl;  
10 -import cn.iocoder.yudao.module.iot.component.emqx.upstream.IotDeviceUpstreamServer; 7 +import cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonProperties;
  8 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
  9 +import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry;
  10 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
  11 +import cn.iocoder.yudao.module.iot.net.component.emqx.downstream.IotDeviceDownstreamHandlerImpl;
  12 +import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.IotDeviceUpstreamServer;
11 import io.vertx.core.Vertx; 13 import io.vertx.core.Vertx;
12 import io.vertx.mqtt.MqttClient; 14 import io.vertx.mqtt.MqttClient;
13 import io.vertx.mqtt.MqttClientOptions; 15 import io.vertx.mqtt.MqttClientOptions;
14 import lombok.extern.slf4j.Slf4j; 16 import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.beans.factory.annotation.Qualifier;
15 import org.springframework.boot.autoconfigure.AutoConfiguration; 18 import org.springframework.boot.autoconfigure.AutoConfiguration;
16 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
17 import org.springframework.boot.context.event.ApplicationStartedEvent; 20 import org.springframework.boot.context.event.ApplicationStartedEvent;
@@ -20,42 +23,46 @@ import org.springframework.context.annotation.Bean; @@ -20,42 +23,46 @@ import org.springframework.context.annotation.Bean;
20 import org.springframework.context.annotation.ComponentScan; 23 import org.springframework.context.annotation.ComponentScan;
21 import org.springframework.context.event.EventListener; 24 import org.springframework.context.event.EventListener;
22 25
23 -import java.lang.management.ManagementFactory;  
24 -  
25 /** 26 /**
26 - * IoT 组件 EMQX 的自动配置类 27 + * IoT 网络组件 EMQX 的自动配置类
27 * 28 *
28 * @author haohao 29 * @author haohao
29 */ 30 */
30 @Slf4j 31 @Slf4j
31 @AutoConfiguration 32 @AutoConfiguration
32 -@EnableConfigurationProperties(IotComponentEmqxProperties.class) 33 +@EnableConfigurationProperties(IotNetComponentEmqxProperties.class)
33 @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true", matchIfMissing = false) 34 @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true", matchIfMissing = false)
34 -// TODO @haohao:是不是不用扫 cn.iocoder.yudao.module.iot.component.core 拉,它尽量靠自动配置  
35 @ComponentScan(basePackages = { 35 @ComponentScan(basePackages = {
36 - "cn.iocoder.yudao.module.iot.component.core", // 核心包  
37 - "cn.iocoder.yudao.module.iot.component.emqx" // EMQX 组件包 36 + "cn.iocoder.yudao.module.iot.net.component.emqx" // 只扫描 EMQX 组件包
38 }) 37 })
39 -public class IotComponentEmqxAutoConfiguration { 38 +public class IotNetComponentEmqxAutoConfiguration {
40 39
41 /** 40 /**
42 * 组件 key 41 * 组件 key
43 */ 42 */
44 private static final String PLUGIN_KEY = "emqx"; 43 private static final String PLUGIN_KEY = "emqx";
45 44
46 - public IotComponentEmqxAutoConfiguration() {  
47 - // TODO @haohao:这个日志,融合到 initialize ?  
48 - log.info("[IotComponentEmqxAutoConfiguration][已启动]"); 45 + public IotNetComponentEmqxAutoConfiguration() {
  46 + // 构造函数中不输出日志,移到 initialize 方法中
49 } 47 }
50 48
  49 + /**
  50 + * 初始化 EMQX 组件
  51 + *
  52 + * @param event 应用启动事件
  53 + */
51 @EventListener(ApplicationStartedEvent.class) 54 @EventListener(ApplicationStartedEvent.class)
52 public void initialize(ApplicationStartedEvent event) { 55 public void initialize(ApplicationStartedEvent event) {
  56 + log.info("[IotNetComponentEmqxAutoConfiguration][开始初始化]");
  57 +
53 // 从应用上下文中获取需要的 Bean 58 // 从应用上下文中获取需要的 Bean
54 - IotComponentRegistry componentRegistry = event.getApplicationContext().getBean(IotComponentRegistry.class);  
55 - IotComponentCommonProperties commonProperties = event.getApplicationContext().getBean(IotComponentCommonProperties.class); 59 + IotNetComponentRegistry componentRegistry = event.getApplicationContext()
  60 + .getBean(IotNetComponentRegistry.class);
  61 + IotNetComponentCommonProperties commonProperties = event.getApplicationContext()
  62 + .getBean(IotNetComponentCommonProperties.class);
56 63
57 // 设置当前组件的核心标识 64 // 设置当前组件的核心标识
58 - // TODO @haohao:如果多个组件,都去设置,会不会冲突哈? 65 + // 注意:这里只为当前 EMQX 组件设置 pluginKey,不影响其他组件
59 commonProperties.setPluginKey(PLUGIN_KEY); 66 commonProperties.setPluginKey(PLUGIN_KEY);
60 67
61 // 将 EMQX 组件注册到组件注册表 68 // 将 EMQX 组件注册到组件注册表
@@ -63,64 +70,60 @@ public class IotComponentEmqxAutoConfiguration { @@ -63,64 +70,60 @@ public class IotComponentEmqxAutoConfiguration {
63 PLUGIN_KEY, 70 PLUGIN_KEY,
64 SystemUtil.getHostInfo().getAddress(), 71 SystemUtil.getHostInfo().getAddress(),
65 0, // 内嵌模式固定为 0 72 0, // 内嵌模式固定为 0
66 - getProcessId()  
67 - ); 73 + IotNetComponentCommonUtils.getProcessId());
68 74
69 log.info("[initialize][IoT EMQX 组件初始化完成]"); 75 log.info("[initialize][IoT EMQX 组件初始化完成]");
70 } 76 }
71 77
72 - // TODO @haohao:这个可能要注意,可能会有多个?冲突?  
73 - @Bean 78 + /**
  79 + * 创建 Vert.x 实例
  80 + */
  81 + @Bean(name = "emqxVertx")
74 public Vertx vertx() { 82 public Vertx vertx() {
75 return Vertx.vertx(); 83 return Vertx.vertx();
76 } 84 }
77 85
  86 + /**
  87 + * 创建 MQTT 客户端
  88 + */
78 @Bean 89 @Bean
79 - public MqttClient mqttClient(Vertx vertx, IotComponentEmqxProperties emqxProperties) {  
80 - // TODO @haohao:这个日志,要不要去掉,避免过多哈  
81 - log.info("MQTT配置: host={}, port={}, username={}, ssl={}",  
82 - emqxProperties.getMqttHost(), emqxProperties.getMqttPort(),  
83 - emqxProperties.getMqttUsername(), emqxProperties.getMqttSsl()); 90 + public MqttClient mqttClient(@Qualifier("emqxVertx") Vertx vertx, IotNetComponentEmqxProperties emqxProperties) {
  91 + // 使用 debug 级别记录详细配置,减少生产环境日志
  92 + if (log.isDebugEnabled()) {
  93 + log.debug("MQTT 配置: host={}, port={}, username={}, ssl={}",
  94 + emqxProperties.getMqttHost(), emqxProperties.getMqttPort(),
  95 + emqxProperties.getMqttUsername(), emqxProperties.getMqttSsl());
  96 + } else {
  97 + log.info("MQTT 连接至: {}:{}", emqxProperties.getMqttHost(), emqxProperties.getMqttPort());
  98 + }
84 99
85 MqttClientOptions options = new MqttClientOptions() 100 MqttClientOptions options = new MqttClientOptions()
86 .setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID()) 101 .setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID())
87 .setUsername(emqxProperties.getMqttUsername()) 102 .setUsername(emqxProperties.getMqttUsername())
88 .setPassword(emqxProperties.getMqttPassword()); 103 .setPassword(emqxProperties.getMqttPassword());
89 - // TODO @haohao:可以用 ObjUtil.default  
90 - if (emqxProperties.getMqttSsl() != null) {  
91 - options.setSsl(emqxProperties.getMqttSsl());  
92 - } else {  
93 - options.setSsl(false);  
94 - } 104 + // 设置 SSL 选项
  105 + options.setSsl(ObjUtil.defaultIfNull(emqxProperties.getMqttSsl(), false));
95 return MqttClient.create(vertx, options); 106 return MqttClient.create(vertx, options);
96 } 107 }
97 108
  109 + /**
  110 + * 创建设备上行服务器
  111 + */
98 @Bean(name = "emqxDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop") 112 @Bean(name = "emqxDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop")
99 - public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,  
100 - IotComponentEmqxProperties emqxProperties,  
101 - Vertx vertx,  
102 - MqttClient mqttClient,  
103 - IotComponentRegistry componentRegistry) { 113 + public IotDeviceUpstreamServer deviceUpstreamServer(
  114 + IotDeviceUpstreamApi deviceUpstreamApi,
  115 + IotNetComponentEmqxProperties emqxProperties,
  116 + @Qualifier("emqxVertx") Vertx vertx,
  117 + MqttClient mqttClient,
  118 + IotNetComponentRegistry componentRegistry) {
104 return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi, vertx, mqttClient, componentRegistry); 119 return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi, vertx, mqttClient, componentRegistry);
105 } 120 }
106 121
  122 + /**
  123 + * 创建设备下行处理器
  124 + */
107 @Bean(name = "emqxDeviceDownstreamHandler") 125 @Bean(name = "emqxDeviceDownstreamHandler")
108 public IotDeviceDownstreamHandler deviceDownstreamHandler(MqttClient mqttClient) { 126 public IotDeviceDownstreamHandler deviceDownstreamHandler(MqttClient mqttClient) {
109 return new IotDeviceDownstreamHandlerImpl(mqttClient); 127 return new IotDeviceDownstreamHandlerImpl(mqttClient);
110 } 128 }
111 -  
112 - // TODO @haohao:这个通用下一下哈。  
113 - /**  
114 - * 获取当前进程ID  
115 - *  
116 - * @return 进程ID  
117 - */  
118 - private String getProcessId() {  
119 - // 获取进程的 name  
120 - String name = ManagementFactory.getRuntimeMXBean().getName();  
121 - // 分割名称,格式为 pid@hostname  
122 - String pid = name.split("@")[0];  
123 - return pid;  
124 - }  
125 -  
126 } 129 }
127 \ No newline at end of file 130 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/config/IotComponentEmqxProperties.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/config/IotNetComponentEmqxProperties.java
1 -package cn.iocoder.yudao.module.iot.component.emqx.config; 1 +package cn.iocoder.yudao.module.iot.net.component.emqx.config;
2 2
3 import jakarta.validation.constraints.NotBlank; 3 import jakarta.validation.constraints.NotBlank;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
5 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
6 import lombok.Data; 6 import lombok.Data;
7 import org.springframework.boot.context.properties.ConfigurationProperties; 7 import org.springframework.boot.context.properties.ConfigurationProperties;
  8 +import org.springframework.validation.annotation.Validated;
8 9
9 /** 10 /**
10 - * IoT EMQX 组件配置属性 11 + * IoT EMQX 网络组件配置属性
  12 + *
  13 + * @author haohao
11 */ 14 */
12 @ConfigurationProperties(prefix = "yudao.iot.component.emqx") 15 @ConfigurationProperties(prefix = "yudao.iot.component.emqx")
13 @Data 16 @Data
14 -public class IotComponentEmqxProperties { 17 +@Validated
  18 +public class IotNetComponentEmqxProperties {
15 19
16 /** 20 /**
17 * 是否启用 EMQX 组件 21 * 是否启用 EMQX 组件
18 */ 22 */
19 private Boolean enabled; 23 private Boolean enabled;
20 24
21 - // TODO @haohao:一般中英文之间,加个空格哈,写作(注释)习惯。类似 MQTT 密码;  
22 /** 25 /**
23 - * 服务主机 26 + * MQTT 服务主机
24 */ 27 */
25 @NotBlank(message = "MQTT 服务器主机不能为空") 28 @NotBlank(message = "MQTT 服务器主机不能为空")
26 private String mqttHost; 29 private String mqttHost;
  30 +
27 /** 31 /**
28 - * 服务端口 32 + * MQTT 服务端口
29 */ 33 */
30 @NotNull(message = "MQTT 服务器端口不能为空") 34 @NotNull(message = "MQTT 服务器端口不能为空")
31 private Integer mqttPort; 35 private Integer mqttPort;
  36 +
32 /** 37 /**
33 - * 服务用户名 38 + * MQTT 服务用户名
34 */ 39 */
35 @NotBlank(message = "MQTT 服务器用户名不能为空") 40 @NotBlank(message = "MQTT 服务器用户名不能为空")
36 private String mqttUsername; 41 private String mqttUsername;
  42 +
37 /** 43 /**
38 - * 服务密码 44 + * MQTT 服务密码
39 */ 45 */
40 @NotBlank(message = "MQTT 服务器密码不能为空") 46 @NotBlank(message = "MQTT 服务器密码不能为空")
41 private String mqttPassword; 47 private String mqttPassword;
  48 +
42 /** 49 /**
43 * 是否启用 SSL 50 * 是否启用 SSL
44 */ 51 */
@@ -57,4 +64,17 @@ public class IotComponentEmqxProperties { @@ -57,4 +64,17 @@ public class IotComponentEmqxProperties {
57 @NotNull(message = "认证端口不能为空") 64 @NotNull(message = "认证端口不能为空")
58 private Integer authPort; 65 private Integer authPort;
59 66
  67 + /**
  68 + * 重连延迟时间(毫秒)
  69 + * <p>
  70 + * 默认值:5000 毫秒
  71 + */
  72 + private Integer reconnectDelayMs = 5000;
  73 +
  74 + /**
  75 + * 连接超时时间(毫秒)
  76 + * <p>
  77 + * 默认值:10000 毫秒
  78 + */
  79 + private Integer connectionTimeoutMs = 10000;
60 } 80 }
61 \ No newline at end of file 81 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
1 -package cn.iocoder.yudao.module.iot.component.emqx.downstream; 1 +package cn.iocoder.yudao.module.iot.net.component.emqx.downstream;
2 2
3 -import cn.hutool.core.util.IdUtil; 3 +import cn.hutool.core.util.StrUtil;
4 import cn.hutool.json.JSONObject; 4 import cn.hutool.json.JSONObject;
5 import cn.hutool.json.JSONUtil; 5 import cn.hutool.json.JSONUtil;
6 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*; 7 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
8 -import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler; 8 +import cn.iocoder.yudao.module.iot.net.component.core.constants.IotDeviceTopicEnum;
  9 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
  10 +import cn.iocoder.yudao.module.iot.net.component.core.message.IotAlinkMessage;
  11 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
9 import io.netty.handler.codec.mqtt.MqttQoS; 12 import io.netty.handler.codec.mqtt.MqttQoS;
10 import io.vertx.core.buffer.Buffer; 13 import io.vertx.core.buffer.Buffer;
11 import io.vertx.mqtt.MqttClient; 14 import io.vertx.mqtt.MqttClient;
12 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
13 16
14 -import java.util.Map;  
15 -  
16 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.MQTT_TOPIC_ILLEGAL; 17 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.MQTT_TOPIC_ILLEGAL;
17 18
18 /** 19 /**
19 - * EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类 20 + * EMQX 网络组件的 {@link IotDeviceDownstreamHandler} 实现类
20 * 21 *
21 * @author 芋道源码 22 * @author 芋道源码
22 */ 23 */
23 @Slf4j 24 @Slf4j
24 public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler { 25 public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
25 26
26 - private static final String SYS_TOPIC_PREFIX = "/sys/";  
27 -  
28 - // TODO @haohao:是不是可以类似 IotDeviceConfigSetVertxHandler 的建议,抽到统一的枚举类  
29 - // TODO @haohao:讨论,感觉 mqtt 和 http,可以做个相对统一的格式哈。;回复 都使用 Alink 格式,方便后续扩展。  
30 - // 设备服务调用 标准 JSON  
31 - // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}  
32 - // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}_reply  
33 - private static final String SERVICE_TOPIC_PREFIX = "/thing/service/";  
34 -  
35 - // 设置设备属性 标准 JSON  
36 - // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set  
37 - // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply  
38 - private static final String PROPERTY_SET_TOPIC = "/thing/service/property/set";  
39 - 27 + /**
  28 + * MQTT 客户端
  29 + */
40 private final MqttClient mqttClient; 30 private final MqttClient mqttClient;
41 31
42 /** 32 /**
43 * 构造函数 33 * 构造函数
44 * 34 *
45 - * @param mqttClient MQTT客户端 35 + * @param mqttClient MQTT 客户端
46 */ 36 */
47 public IotDeviceDownstreamHandlerImpl(MqttClient mqttClient) { 37 public IotDeviceDownstreamHandlerImpl(MqttClient mqttClient) {
48 this.mqttClient = mqttClient; 38 this.mqttClient = mqttClient;
@@ -60,12 +50,17 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle @@ -60,12 +50,17 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
60 50
61 try { 51 try {
62 // 构建请求主题 52 // 构建请求主题
63 - String topic = buildServiceTopic(reqDTO.getProductKey(), reqDTO.getDeviceName(), reqDTO.getIdentifier()); 53 + String topic = IotDeviceTopicEnum.buildServiceTopic(reqDTO.getProductKey(), reqDTO.getDeviceName(),
  54 + reqDTO.getIdentifier());
  55 +
64 // 构建请求消息 56 // 构建请求消息
65 - String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId();  
66 - JSONObject request = buildServiceRequest(requestId, reqDTO.getIdentifier(), reqDTO.getParams()); 57 + String requestId = StrUtil.isNotEmpty(reqDTO.getRequestId()) ? reqDTO.getRequestId()
  58 + : IotNetComponentCommonUtils.generateRequestId();
  59 + IotAlinkMessage message = IotAlinkMessage.createServiceInvokeMessage(
  60 + requestId, reqDTO.getIdentifier(), reqDTO.getParams());
  61 +
67 // 发送消息 62 // 发送消息
68 - publishMessage(topic, request); 63 + publishMessage(topic, message.toJsonObject());
69 64
70 log.info("[invokeService][调用设备服务成功][requestId: {}][topic: {}]", requestId, topic); 65 log.info("[invokeService][调用设备服务成功][requestId: {}][topic: {}]", requestId, topic);
71 return CommonResult.success(true); 66 return CommonResult.success(true);
@@ -77,13 +72,15 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle @@ -77,13 +72,15 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
77 72
78 @Override 73 @Override
79 public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) { 74 public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
  75 + // 暂未实现,返回成功
80 return CommonResult.success(true); 76 return CommonResult.success(true);
81 } 77 }
82 78
83 @Override 79 @Override
84 public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO reqDTO) { 80 public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO reqDTO) {
85 - // 验证参数  
86 log.info("[setProperty][开始设置设备属性][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO)); 81 log.info("[setProperty][开始设置设备属性][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
  82 +
  83 + // 验证参数
87 if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null) { 84 if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null) {
88 log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO)); 85 log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
89 return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg()); 86 return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg());
@@ -91,12 +88,15 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle @@ -91,12 +88,15 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
91 88
92 try { 89 try {
93 // 构建请求主题 90 // 构建请求主题
94 - String topic = buildPropertySetTopic(reqDTO.getProductKey(), reqDTO.getDeviceName()); 91 + String topic = IotDeviceTopicEnum.buildPropertySetTopic(reqDTO.getProductKey(), reqDTO.getDeviceName());
  92 +
95 // 构建请求消息 93 // 构建请求消息
96 - String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId();  
97 - JSONObject request = buildPropertySetRequest(requestId, reqDTO.getProperties()); 94 + String requestId = StrUtil.isNotEmpty(reqDTO.getRequestId()) ? reqDTO.getRequestId()
  95 + : IotNetComponentCommonUtils.generateRequestId();
  96 + IotAlinkMessage message = IotAlinkMessage.createPropertySetMessage(requestId, reqDTO.getProperties());
  97 +
98 // 发送消息 98 // 发送消息
99 - publishMessage(topic, request); 99 + publishMessage(topic, message.toJsonObject());
100 100
101 log.info("[setProperty][设置设备属性成功][requestId: {}][topic: {}]", requestId, topic); 101 log.info("[setProperty][设置设备属性成功][requestId: {}][topic: {}]", requestId, topic);
102 return CommonResult.success(true); 102 return CommonResult.success(true);
@@ -108,54 +108,21 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle @@ -108,54 +108,21 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
108 108
109 @Override 109 @Override
110 public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) { 110 public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
  111 + // 暂未实现,返回成功
111 return CommonResult.success(true); 112 return CommonResult.success(true);
112 } 113 }
113 114
114 @Override 115 @Override
115 public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) { 116 public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
  117 + // 暂未实现,返回成功
116 return CommonResult.success(true); 118 return CommonResult.success(true);
117 } 119 }
118 120
119 /** 121 /**
120 - * 构建服务调用主题  
121 - */  
122 - private String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {  
123 - return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + SERVICE_TOPIC_PREFIX + serviceIdentifier;  
124 - }  
125 -  
126 - /**  
127 - * 构建属性设置主题  
128 - */  
129 - private String buildPropertySetTopic(String productKey, String deviceName) {  
130 - return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + PROPERTY_SET_TOPIC;  
131 - }  
132 -  
133 - // TODO @haohao:这个,后面搞个对象,会不会好点哈?  
134 -  
135 - /**  
136 - * 构建服务调用请求  
137 - */  
138 - private JSONObject buildServiceRequest(String requestId, String serviceIdentifier, Map<String, Object> params) {  
139 - return new JSONObject()  
140 - .set("id", requestId)  
141 - .set("version", "1.0")  
142 - .set("method", "thing.service." + serviceIdentifier)  
143 - .set("params", params != null ? params : new JSONObject());  
144 - }  
145 -  
146 - /**  
147 - * 构建属性设置请求  
148 - */  
149 - private JSONObject buildPropertySetRequest(String requestId, Map<String, Object> properties) {  
150 - return new JSONObject()  
151 - .set("id", requestId)  
152 - .set("version", "1.0")  
153 - .set("method", "thing.service.property.set")  
154 - .set("params", properties);  
155 - }  
156 -  
157 - /**  
158 * 发布 MQTT 消息 122 * 发布 MQTT 消息
  123 + *
  124 + * @param topic 主题
  125 + * @param payload 消息内容
159 */ 126 */
160 private void publishMessage(String topic, JSONObject payload) { 127 private void publishMessage(String topic, JSONObject payload) {
161 mqttClient.publish( 128 mqttClient.publish(
@@ -166,13 +133,4 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle @@ -166,13 +133,4 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
166 false); 133 false);
167 log.info("[publishMessage][发送消息成功][topic: {}][payload: {}]", topic, payload); 134 log.info("[publishMessage][发送消息成功][topic: {}][payload: {}]", topic, payload);
168 } 135 }
169 -  
170 - // TODO @haohao:这个要不抽到 IotPluginCommonUtils 里?  
171 - /**  
172 - * 生成请求 ID  
173 - */  
174 - private String generateRequestId() {  
175 - return IdUtil.fastSimpleUUID();  
176 - }  
177 -  
178 } 136 }
179 \ No newline at end of file 137 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/IotDeviceUpstreamServer.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/IotDeviceUpstreamServer.java
1 -package cn.iocoder.yudao.module.iot.component.emqx.upstream; 1 +package cn.iocoder.yudao.module.iot.net.component.emqx.upstream;
2 2
3 import cn.hutool.core.util.ArrayUtil; 3 import cn.hutool.core.util.ArrayUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 -import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;  
7 -import cn.iocoder.yudao.module.iot.component.emqx.config.IotComponentEmqxProperties;  
8 -import cn.iocoder.yudao.module.iot.component.emqx.upstream.router.IotDeviceAuthVertxHandler;  
9 -import cn.iocoder.yudao.module.iot.component.emqx.upstream.router.IotDeviceMqttMessageHandler;  
10 -import cn.iocoder.yudao.module.iot.component.emqx.upstream.router.IotDeviceWebhookVertxHandler; 6 +import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry;
  7 +import cn.iocoder.yudao.module.iot.net.component.emqx.config.IotNetComponentEmqxProperties;
  8 +import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceAuthVertxHandler;
  9 +import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceMqttMessageHandler;
  10 +import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceWebhookVertxHandler;
11 import io.netty.handler.codec.mqtt.MqttQoS; 11 import io.netty.handler.codec.mqtt.MqttQoS;
12 import io.vertx.core.Future; 12 import io.vertx.core.Future;
13 import io.vertx.core.Vertx; 13 import io.vertx.core.Vertx;
@@ -21,7 +21,7 @@ import java.util.concurrent.CompletableFuture; @@ -21,7 +21,7 @@ import java.util.concurrent.CompletableFuture;
21 import java.util.concurrent.TimeUnit; 21 import java.util.concurrent.TimeUnit;
22 22
23 /** 23 /**
24 - * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器 24 + * IoT 设备上行服务端,接收来自 device 设备的请求,转发给 server 服务器
25 * <p> 25 * <p>
26 * 协议:HTTP、MQTT 26 * 协议:HTTP、MQTT
27 * 27 *
@@ -30,15 +30,6 @@ import java.util.concurrent.TimeUnit; @@ -30,15 +30,6 @@ import java.util.concurrent.TimeUnit;
30 @Slf4j 30 @Slf4j
31 public class IotDeviceUpstreamServer { 31 public class IotDeviceUpstreamServer {
32 32
33 - // TODO @haohao:抽到 IotComponentEmqxProperties 里?  
34 - /**  
35 - * 重连延迟时间(毫秒)  
36 - */  
37 - private static final int RECONNECT_DELAY_MS = 5000;  
38 - /**  
39 - * 连接超时时间(毫秒)  
40 - */  
41 - private static final int CONNECTION_TIMEOUT_MS = 10000;  
42 /** 33 /**
43 * 默认 QoS 级别 34 * 默认 QoS 级别
44 */ 35 */
@@ -47,20 +38,20 @@ public class IotDeviceUpstreamServer { @@ -47,20 +38,20 @@ public class IotDeviceUpstreamServer {
47 private final Vertx vertx; 38 private final Vertx vertx;
48 private final HttpServer server; 39 private final HttpServer server;
49 private final MqttClient client; 40 private final MqttClient client;
50 - private final IotComponentEmqxProperties emqxProperties; 41 + private final IotNetComponentEmqxProperties emqxProperties;
51 private final IotDeviceMqttMessageHandler mqttMessageHandler; 42 private final IotDeviceMqttMessageHandler mqttMessageHandler;
52 - private final IotComponentRegistry componentRegistry; 43 + private final IotNetComponentRegistry componentRegistry;
53 44
54 /** 45 /**
55 * 服务运行状态标志 46 * 服务运行状态标志
56 */ 47 */
57 private volatile boolean isRunning = false; 48 private volatile boolean isRunning = false;
58 49
59 - public IotDeviceUpstreamServer(IotComponentEmqxProperties emqxProperties, 50 + public IotDeviceUpstreamServer(IotNetComponentEmqxProperties emqxProperties,
60 IotDeviceUpstreamApi deviceUpstreamApi, 51 IotDeviceUpstreamApi deviceUpstreamApi,
61 Vertx vertx, 52 Vertx vertx,
62 MqttClient client, 53 MqttClient client,
63 - IotComponentRegistry componentRegistry) { 54 + IotNetComponentRegistry componentRegistry) {
64 this.vertx = vertx; 55 this.vertx = vertx;
65 this.emqxProperties = emqxProperties; 56 this.emqxProperties = emqxProperties;
66 this.client = client; 57 this.client = client;
@@ -70,8 +61,7 @@ public class IotDeviceUpstreamServer { @@ -70,8 +61,7 @@ public class IotDeviceUpstreamServer {
70 Router router = Router.router(vertx); 61 Router router = Router.router(vertx);
71 router.route().handler(BodyHandler.create()); // 处理 Body 62 router.route().handler(BodyHandler.create()); // 处理 Body
72 router.post(IotDeviceAuthVertxHandler.PATH) 63 router.post(IotDeviceAuthVertxHandler.PATH)
73 - // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀?  
74 - // 回复:MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式 64 + // MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式
75 .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi)); 65 .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
76 // 添加 Webhook 处理器,用于处理设备连接和断开连接事件 66 // 添加 Webhook 处理器,用于处理设备连接和断开连接事件
77 router.post(IotDeviceWebhookVertxHandler.PATH) 67 router.post(IotDeviceWebhookVertxHandler.PATH)
@@ -91,15 +81,20 @@ public class IotDeviceUpstreamServer { @@ -91,15 +81,20 @@ public class IotDeviceUpstreamServer {
91 } 81 }
92 log.info("[start][开始启动服务]"); 82 log.info("[start][开始启动服务]");
93 83
94 - // 检查authPort是否为null 84 + // 检查 authPort 是否为 null
95 Integer authPort = emqxProperties.getAuthPort(); 85 Integer authPort = emqxProperties.getAuthPort();
96 if (authPort == null) { 86 if (authPort == null) {
97 - log.warn("[start][authPort为null,使用默认端口8080]"); 87 + log.warn("[start][authPort 为 null,使用默认端口 8080]");
98 authPort = 8080; // 默认端口 88 authPort = 8080; // 默认端口
99 } 89 }
100 90
  91 + // 获取连接超时时间
  92 + int connectionTimeoutMs = emqxProperties.getConnectionTimeoutMs() != null
  93 + ? emqxProperties.getConnectionTimeoutMs()
  94 + : 10000;
  95 +
101 // 1. 启动 HTTP 服务器 96 // 1. 启动 HTTP 服务器
102 - final Integer finalAuthPort = authPort; // 为lambda表达式创建final变量 97 + final Integer finalAuthPort = authPort; // 为 lambda 表达式创建 final 变量
103 CompletableFuture<Void> httpFuture = server.listen(finalAuthPort) 98 CompletableFuture<Void> httpFuture = server.listen(finalAuthPort)
104 .toCompletionStage() 99 .toCompletionStage()
105 .toCompletableFuture() 100 .toCompletableFuture()
@@ -115,13 +110,13 @@ public class IotDeviceUpstreamServer { @@ -115,13 +110,13 @@ public class IotDeviceUpstreamServer {
115 log.warn("[closeHandler][MQTT 连接已断开,准备重连]"); 110 log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
116 reconnectWithDelay(); 111 reconnectWithDelay();
117 }); 112 });
118 - // 2. 设置 MQTT 消息处理器 113 + // 2.2 设置 MQTT 消息处理器
119 setupMessageHandler(); 114 setupMessageHandler();
120 }); 115 });
121 116
122 // 3. 等待所有服务启动完成 117 // 3. 等待所有服务启动完成
123 CompletableFuture.allOf(httpFuture, mqttFuture) 118 CompletableFuture.allOf(httpFuture, mqttFuture)
124 - .orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS) 119 + .orTimeout(connectionTimeoutMs, TimeUnit.MILLISECONDS)
125 .whenComplete((result, error) -> { 120 .whenComplete((result, error) -> {
126 if (error != null) { 121 if (error != null) {
127 log.error("[start][服务启动失败]", error); 122 log.error("[start][服务启动失败]", error);
@@ -149,7 +144,12 @@ public class IotDeviceUpstreamServer { @@ -149,7 +144,12 @@ public class IotDeviceUpstreamServer {
149 return; 144 return;
150 } 145 }
151 146
152 - vertx.setTimer(RECONNECT_DELAY_MS, id -> { 147 + // 获取重连延迟时间
  148 + int reconnectDelayMs = emqxProperties.getReconnectDelayMs() != null
  149 + ? emqxProperties.getReconnectDelayMs()
  150 + : 5000;
  151 +
  152 + vertx.setTimer(reconnectDelayMs, id -> {
153 log.info("[reconnectWithDelay][开始重新连接 MQTT]"); 153 log.info("[reconnectWithDelay][开始重新连接 MQTT]");
154 connectMqtt(); 154 connectMqtt();
155 }); 155 });
@@ -158,14 +158,14 @@ public class IotDeviceUpstreamServer { @@ -158,14 +158,14 @@ public class IotDeviceUpstreamServer {
158 /** 158 /**
159 * 连接 MQTT Broker 并订阅主题 159 * 连接 MQTT Broker 并订阅主题
160 * 160 *
161 - * @return 连接结果的Future 161 + * @return 连接结果的 Future
162 */ 162 */
163 private Future<Void> connectMqtt() { 163 private Future<Void> connectMqtt() {
164 // 检查必要的 MQTT 配置 164 // 检查必要的 MQTT 配置
165 String host = emqxProperties.getMqttHost(); 165 String host = emqxProperties.getMqttHost();
166 Integer port = emqxProperties.getMqttPort(); 166 Integer port = emqxProperties.getMqttPort();
167 - if (host == null) {  
168 - String msg = "[connectMqtt][MQTT Host 为 null,无法连接]"; 167 + if (StrUtil.isBlank(host)) {
  168 + String msg = "[connectMqtt][MQTT Host 为空,无法连接]";
169 log.error(msg); 169 log.error(msg);
170 return Future.failedFuture(new IllegalStateException(msg)); 170 return Future.failedFuture(new IllegalStateException(msg));
171 } 171 }
@@ -177,11 +177,11 @@ public class IotDeviceUpstreamServer { @@ -177,11 +177,11 @@ public class IotDeviceUpstreamServer {
177 final Integer finalPort = port; 177 final Integer finalPort = port;
178 return client.connect(finalPort, host) 178 return client.connect(finalPort, host)
179 .compose(connAck -> { 179 .compose(connAck -> {
180 - log.info("[connectMqtt][MQTT客户端连接成功]"); 180 + log.info("[connectMqtt][MQTT 客户端连接成功]");
181 return subscribeToTopics(); 181 return subscribeToTopics();
182 }) 182 })
183 .recover(error -> { 183 .recover(error -> {
184 - log.error("[connectMqtt][连接MQTT Broker失败:]", error); 184 + log.error("[connectMqtt][连接 MQTT Broker 失败:]", error);
185 reconnectWithDelay(); 185 reconnectWithDelay();
186 return Future.failedFuture(error); 186 return Future.failedFuture(error);
187 }); 187 });
@@ -198,62 +198,67 @@ public class IotDeviceUpstreamServer { @@ -198,62 +198,67 @@ public class IotDeviceUpstreamServer {
198 log.warn("[subscribeToTopics][未配置 MQTT 主题或为 null,使用默认主题]"); 198 log.warn("[subscribeToTopics][未配置 MQTT 主题或为 null,使用默认主题]");
199 topics = new String[]{"/device/#"}; // 默认订阅所有设备上下行主题 199 topics = new String[]{"/device/#"}; // 默认订阅所有设备上下行主题
200 } 200 }
201 - log.info("[subscribeToTopics][开始订阅设备上行消息主题]");  
202 201
203 - Future<Void> compositeFuture = Future.succeededFuture(); 202 + // 使用协调器追踪多个 Future 的完成状态
  203 + Future<Void> result = Future.succeededFuture();
204 for (String topic : topics) { 204 for (String topic : topics) {
205 - String trimmedTopic = StrUtil.trim(topic);  
206 - if (StrUtil.isBlank(trimmedTopic)) { 205 + if (StrUtil.isBlank(topic)) {
  206 + log.warn("[subscribeToTopics][跳过空主题]");
207 continue; 207 continue;
208 } 208 }
209 - compositeFuture = compositeFuture.compose(v -> client.subscribe(trimmedTopic, DEFAULT_QOS.value()) 209 +
  210 + result = result.compose(v -> client.subscribe(topic, DEFAULT_QOS.value())
210 .<Void>map(ack -> { 211 .<Void>map(ack -> {
211 - log.info("[subscribeToTopics][成功订阅主题: {}]", trimmedTopic); 212 + log.info("[subscribeToTopics][订阅主题成功: {}]", topic);
212 return null; 213 return null;
213 }) 214 })
214 - .recover(error -> {  
215 - log.error("[subscribeToTopics][订阅主题失败: {}]", trimmedTopic, error);  
216 - return Future.<Void>succeededFuture(); // 继续订阅其他主题 215 + .recover(err -> {
  216 + log.error("[subscribeToTopics][订阅主题失败: {}]", topic, err);
  217 + return Future.failedFuture(err);
217 })); 218 }));
218 } 219 }
219 - return compositeFuture; 220 + return result;
220 } 221 }
221 222
222 /** 223 /**
223 - * 停止所有服务 224 + * 停止服务
224 */ 225 */
225 public void stop() { 226 public void stop() {
226 if (!isRunning) { 227 if (!isRunning) {
227 - log.warn("[stop][服务未运行,无需停止]"); 228 + log.warn("[stop][服务已经停止,无需再次停止]");
228 return; 229 return;
229 } 230 }
230 - log.info("[stop][开始关闭服务]");  
231 - isRunning = false; 231 + log.info("[stop][开始停止服务]");
  232 +
  233 + // 1. 取消 MQTT 主题订阅
  234 + if (client.isConnected()) {
  235 + for (String topic : emqxProperties.getMqttTopics()) {
  236 + try {
  237 + client.unsubscribe(topic);
  238 + } catch (Exception e) {
  239 + log.warn("[stop][取消订阅主题异常: {}]", topic, e);
  240 + }
  241 + }
  242 + }
232 243
  244 + // 2. 关闭 MQTT 客户端
233 try { 245 try {
234 - CompletableFuture<Void> serverFuture = server != null  
235 - ? server.close().toCompletionStage().toCompletableFuture()  
236 - : CompletableFuture.completedFuture(null);  
237 - CompletableFuture<Void> clientFuture = client != null  
238 - ? client.disconnect().toCompletionStage().toCompletableFuture()  
239 - : CompletableFuture.completedFuture(null);  
240 - CompletableFuture<Void> vertxFuture = vertx != null  
241 - ? vertx.close().toCompletionStage().toCompletableFuture()  
242 - : CompletableFuture.completedFuture(null); 246 + if (client.isConnected()) {
  247 + client.disconnect();
  248 + }
  249 + } catch (Exception e) {
  250 + log.warn("[stop][关闭 MQTT 客户端异常]", e);
  251 + }
243 252
244 - // 等待所有资源关闭  
245 - CompletableFuture.allOf(serverFuture, clientFuture, vertxFuture)  
246 - .orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)  
247 - .whenComplete((result, error) -> {  
248 - if (error != null) {  
249 - log.error("[stop][服务关闭过程中发生异常]", error);  
250 - } else {  
251 - log.info("[stop][所有服务关闭完成]");  
252 - }  
253 - }); 253 + // 3. 关闭 HTTP 服务器
  254 + try {
  255 + server.close();
254 } catch (Exception e) { 256 } catch (Exception e) {
255 - log.error("[stop][关闭服务异常]", e);  
256 - throw new RuntimeException("关闭 IoT 设备上行服务失败", e); 257 + log.warn("[stop][关闭 HTTP 服务器异常]", e);
257 } 258 }
  259 +
  260 + // 4. 更新状态
  261 + isRunning = false;
  262 + log.info("[stop][服务已停止]");
258 } 263 }
259 } 264 }
260 \ No newline at end of file 265 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceAuthVertxHandler.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceAuthVertxHandler.java
1 -package cn.iocoder.yudao.module.iot.component.emqx.upstream.router; 1 +package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 4 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
5 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEmqxAuthReqDTO; 5 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEmqxAuthReqDTO;
6 -import cn.iocoder.yudao.module.iot.component.core.util.IotPluginCommonUtils; 6 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
7 import io.vertx.core.Handler; 7 import io.vertx.core.Handler;
8 import io.vertx.core.json.JsonObject; 8 import io.vertx.core.json.JsonObject;
9 import io.vertx.ext.web.RoutingContext; 9 import io.vertx.ext.web.RoutingContext;
@@ -47,17 +47,17 @@ public class IotDeviceAuthVertxHandler implements Handler&lt;RoutingContext&gt; { @@ -47,17 +47,17 @@ public class IotDeviceAuthVertxHandler implements Handler&lt;RoutingContext&gt; {
47 CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO); 47 CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);
48 if (authResult.getCode() != 0 || !authResult.getData()) { 48 if (authResult.getCode() != 0 || !authResult.getData()) {
49 // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求 49 // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求
50 - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny")); 50 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
51 return; 51 return;
52 } 52 }
53 53
54 // 响应结果 54 // 响应结果
55 // 注意:这里必须返回 {"result": "allow"} 格式,以符合 EMQX 认证插件的要求 55 // 注意:这里必须返回 {"result": "allow"} 格式,以符合 EMQX 认证插件的要求
56 - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "allow")); 56 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "allow"));
57 } catch (Exception e) { 57 } catch (Exception e) {
58 log.error("[handle][EMQX 认证异常]", e); 58 log.error("[handle][EMQX 认证异常]", e);
59 // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求 59 // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求
60 - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny")); 60 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
61 } 61 }
62 } 62 }
63 63
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceMqttMessageHandler.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceMqttMessageHandler.java
1 -package cn.iocoder.yudao.module.iot.component.emqx.upstream.router; 1 +package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.hutool.json.JSONObject; 4 import cn.hutool.json.JSONObject;
@@ -7,8 +7,9 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -7,8 +7,9 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 7 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
8 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO; 8 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
9 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO; 9 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
10 -import cn.iocoder.yudao.module.iot.component.core.pojo.IotStandardResponse;  
11 -import cn.iocoder.yudao.module.iot.component.core.util.IotPluginCommonUtils; 10 +import cn.iocoder.yudao.module.iot.net.component.core.constants.IotDeviceTopicEnum;
  11 +import cn.iocoder.yudao.module.iot.net.component.core.pojo.IotStandardResponse;
  12 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
12 import io.netty.handler.codec.mqtt.MqttQoS; 13 import io.netty.handler.codec.mqtt.MqttQoS;
13 import io.vertx.core.buffer.Buffer; 14 import io.vertx.core.buffer.Buffer;
14 import io.vertx.mqtt.MqttClient; 15 import io.vertx.mqtt.MqttClient;
@@ -23,25 +24,12 @@ import java.util.Map; @@ -23,25 +24,12 @@ import java.util.Map;
23 /** 24 /**
24 * IoT 设备 MQTT 消息处理器 25 * IoT 设备 MQTT 消息处理器
25 * <p> 26 * <p>
26 - * 参考:<a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">设备属性、事件、服务</a> 27 + * 参考:<a href=
  28 + * "https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">设备属性、事件、服务</a>
27 */ 29 */
28 @Slf4j 30 @Slf4j
29 public class IotDeviceMqttMessageHandler { 31 public class IotDeviceMqttMessageHandler {
30 32
31 - // TODO @haohao:讨论,感觉 mqtt 和 http,可以做个相对统一的格式哈;回复 都使用 Alink 格式,方便后续扩展。  
32 - // 设备上报属性 标准 JSON  
33 - // 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post  
34 - // 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply  
35 -  
36 - // 设备上报事件 标准 JSON  
37 - // 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post  
38 - // 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply  
39 -  
40 - private static final String SYS_TOPIC_PREFIX = "/sys/";  
41 - private static final String PROPERTY_POST_TOPIC = "/thing/event/property/post";  
42 - private static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/";  
43 - private static final String EVENT_POST_TOPIC_SUFFIX = "/post";  
44 - private static final String REPLY_SUFFIX = "_reply";  
45 private static final String PROPERTY_METHOD = "thing.event.property.post"; 33 private static final String PROPERTY_METHOD = "thing.event.property.post";
46 private static final String EVENT_METHOD_PREFIX = "thing.event."; 34 private static final String EVENT_METHOD_PREFIX = "thing.event.";
47 private static final String EVENT_METHOD_SUFFIX = ".post"; 35 private static final String EVENT_METHOD_SUFFIX = ".post";
@@ -83,20 +71,21 @@ public class IotDeviceMqttMessageHandler { @@ -83,20 +71,21 @@ public class IotDeviceMqttMessageHandler {
83 */ 71 */
84 private void handleMessage(String topic, String payload) { 72 private void handleMessage(String topic, String payload) {
85 // 校验前缀 73 // 校验前缀
86 - if (!topic.startsWith(SYS_TOPIC_PREFIX)) { 74 + if (!topic.startsWith(IotDeviceTopicEnum.SYS_TOPIC_PREFIX.getTopic())) {
87 log.warn("[handleMessage][未知的消息类型][topic: {}]", topic); 75 log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
88 return; 76 return;
89 } 77 }
90 78
91 // 处理设备属性上报消息 79 // 处理设备属性上报消息
92 - if (topic.endsWith(PROPERTY_POST_TOPIC)) { 80 + if (topic.endsWith(IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic())) {
93 log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic); 81 log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic);
94 handlePropertyPost(topic, payload); 82 handlePropertyPost(topic, payload);
95 return; 83 return;
96 } 84 }
97 85
98 // 处理设备事件上报消息 86 // 处理设备事件上报消息
99 - if (topic.contains(EVENT_POST_TOPIC_PREFIX) && topic.endsWith(EVENT_POST_TOPIC_SUFFIX)) { 87 + if (topic.contains(IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic()) &&
  88 + topic.endsWith(IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic())) {
100 log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic); 89 log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic);
101 handleEventPost(topic, payload); 90 handleEventPost(topic, payload);
102 return; 91 return;
@@ -212,7 +201,7 @@ public class IotDeviceMqttMessageHandler { @@ -212,7 +201,7 @@ public class IotDeviceMqttMessageHandler {
212 * @param customData 自定义数据,可为 null 201 * @param customData 自定义数据,可为 null
213 */ 202 */
214 private void sendResponse(String topic, JSONObject jsonObject, String method, Object customData) { 203 private void sendResponse(String topic, JSONObject jsonObject, String method, Object customData) {
215 - String replyTopic = topic + REPLY_SUFFIX; 204 + String replyTopic = IotDeviceTopicEnum.getReplyTopic(topic);
216 205
217 // 响应结果 206 // 响应结果
218 IotStandardResponse response = IotStandardResponse.success( 207 IotStandardResponse response = IotStandardResponse.success(
@@ -236,7 +225,7 @@ public class IotDeviceMqttMessageHandler { @@ -236,7 +225,7 @@ public class IotDeviceMqttMessageHandler {
236 private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, String[] topicParts) { 225 private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, String[] topicParts) {
237 IotDevicePropertyReportReqDTO reportReqDTO = new IotDevicePropertyReportReqDTO(); 226 IotDevicePropertyReportReqDTO reportReqDTO = new IotDevicePropertyReportReqDTO();
238 reportReqDTO.setRequestId(jsonObject.getStr("id")); 227 reportReqDTO.setRequestId(jsonObject.getStr("id"));
239 - reportReqDTO.setProcessId(IotPluginCommonUtils.getProcessId()); 228 + reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
240 reportReqDTO.setReportTime(LocalDateTime.now()); 229 reportReqDTO.setReportTime(LocalDateTime.now());
241 reportReqDTO.setProductKey(topicParts[2]); 230 reportReqDTO.setProductKey(topicParts[2]);
242 reportReqDTO.setDeviceName(topicParts[3]); 231 reportReqDTO.setDeviceName(topicParts[3]);
@@ -276,7 +265,7 @@ public class IotDeviceMqttMessageHandler { @@ -276,7 +265,7 @@ public class IotDeviceMqttMessageHandler {
276 private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) { 265 private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) {
277 IotDeviceEventReportReqDTO reportReqDTO = new IotDeviceEventReportReqDTO(); 266 IotDeviceEventReportReqDTO reportReqDTO = new IotDeviceEventReportReqDTO();
278 reportReqDTO.setRequestId(jsonObject.getStr("id")); 267 reportReqDTO.setRequestId(jsonObject.getStr("id"));
279 - reportReqDTO.setProcessId(IotPluginCommonUtils.getProcessId()); 268 + reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
280 reportReqDTO.setReportTime(LocalDateTime.now()); 269 reportReqDTO.setReportTime(LocalDateTime.now());
281 reportReqDTO.setProductKey(topicParts[2]); 270 reportReqDTO.setProductKey(topicParts[2]);
282 reportReqDTO.setDeviceName(topicParts[3]); 271 reportReqDTO.setDeviceName(topicParts[3]);
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceWebhookVertxHandler.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceWebhookVertxHandler.java
1 -package cn.iocoder.yudao.module.iot.component.emqx.upstream.router; 1 +package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO; 6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
7 -import cn.iocoder.yudao.module.iot.component.core.util.IotPluginCommonUtils;  
8 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; 7 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
  8 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
9 import io.vertx.core.Handler; 9 import io.vertx.core.Handler;
10 import io.vertx.core.json.JsonObject; 10 import io.vertx.core.json.JsonObject;
11 import io.vertx.ext.web.RoutingContext; 11 import io.vertx.ext.web.RoutingContext;
@@ -57,11 +57,11 @@ public class IotDeviceWebhookVertxHandler implements Handler&lt;RoutingContext&gt; { @@ -57,11 +57,11 @@ public class IotDeviceWebhookVertxHandler implements Handler&lt;RoutingContext&gt; {
57 57
58 // 返回成功响应 58 // 返回成功响应
59 // 注意:这里必须返回 {"result": "success"} 格式,以符合 EMQX Webhook 插件的要求 59 // 注意:这里必须返回 {"result": "success"} 格式,以符合 EMQX Webhook 插件的要求
60 - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "success")); 60 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "success"));
61 } catch (Exception e) { 61 } catch (Exception e) {
62 log.error("[handle][处理 Webhook 事件异常]", e); 62 log.error("[handle][处理 Webhook 事件异常]", e);
63 // 注意:这里必须返回 {"result": "error"} 格式,以符合 EMQX Webhook 插件的要求 63 // 注意:这里必须返回 {"result": "error"} 格式,以符合 EMQX Webhook 插件的要求
64 - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "error")); 64 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "error"));
65 } 65 }
66 } 66 }
67 67
@@ -87,7 +87,7 @@ public class IotDeviceWebhookVertxHandler implements Handler&lt;RoutingContext&gt; { @@ -87,7 +87,7 @@ public class IotDeviceWebhookVertxHandler implements Handler&lt;RoutingContext&gt; {
87 updateReqDTO.setProductKey(parts[1]); 87 updateReqDTO.setProductKey(parts[1]);
88 updateReqDTO.setDeviceName(parts[0]); 88 updateReqDTO.setDeviceName(parts[0]);
89 updateReqDTO.setState(IotDeviceStateEnum.ONLINE.getState()); 89 updateReqDTO.setState(IotDeviceStateEnum.ONLINE.getState());
90 - updateReqDTO.setProcessId(IotPluginCommonUtils.getProcessId()); 90 + updateReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
91 updateReqDTO.setReportTime(LocalDateTime.now()); 91 updateReqDTO.setReportTime(LocalDateTime.now());
92 CommonResult<Boolean> result = deviceUpstreamApi.updateDeviceState(updateReqDTO); 92 CommonResult<Boolean> result = deviceUpstreamApi.updateDeviceState(updateReqDTO);
93 if (result.getCode() != 0 || !result.getData()) { 93 if (result.getCode() != 0 || !result.getData()) {
@@ -120,7 +120,7 @@ public class IotDeviceWebhookVertxHandler implements Handler&lt;RoutingContext&gt; { @@ -120,7 +120,7 @@ public class IotDeviceWebhookVertxHandler implements Handler&lt;RoutingContext&gt; {
120 offlineReqDTO.setProductKey(parts[1]); 120 offlineReqDTO.setProductKey(parts[1]);
121 offlineReqDTO.setDeviceName(parts[0]); 121 offlineReqDTO.setDeviceName(parts[0]);
122 offlineReqDTO.setState(IotDeviceStateEnum.OFFLINE.getState()); 122 offlineReqDTO.setState(IotDeviceStateEnum.OFFLINE.getState());
123 - offlineReqDTO.setProcessId(IotPluginCommonUtils.getProcessId()); 123 + offlineReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
124 offlineReqDTO.setReportTime(LocalDateTime.now()); 124 offlineReqDTO.setReportTime(LocalDateTime.now());
125 CommonResult<Boolean> offlineResult = deviceUpstreamApi.updateDeviceState(offlineReqDTO); 125 CommonResult<Boolean> offlineResult = deviceUpstreamApi.updateDeviceState(offlineReqDTO);
126 if (offlineResult.getCode() != 0 || !offlineResult.getData()) { 126 if (offlineResult.getCode() != 0 || !offlineResult.getData()) {
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 0 → 100644
  1 +cn.iocoder.yudao.module.iot.net.component.emqx.config.IotNetComponentEmqxAutoConfiguration
0 \ No newline at end of file 2 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/application.yml renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/application.yml
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/pom.xml renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/pom.xml
@@ -3,23 +3,23 @@ @@ -3,23 +3,23 @@
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 <parent> 5 <parent>
6 - <artifactId>yudao-module-iot-components</artifactId> 6 + <artifactId>yudao-module-iot-net-components</artifactId>
7 <groupId>cn.iocoder.boot</groupId> 7 <groupId>cn.iocoder.boot</groupId>
8 <version>${revision}</version> 8 <version>${revision}</version>
9 </parent> 9 </parent>
10 <modelVersion>4.0.0</modelVersion> 10 <modelVersion>4.0.0</modelVersion>
11 - <artifactId>yudao-module-iot-component-http</artifactId> 11 + <artifactId>yudao-module-iot-net-component-http</artifactId>
12 <packaging>jar</packaging> 12 <packaging>jar</packaging>
13 13
14 <name>${project.artifactId}</name> 14 <name>${project.artifactId}</name>
15 <description> 15 <description>
16 - 物联网组件 HTTP 模块 16 + 物联网网络组件 HTTP 模块
17 </description> 17 </description>
18 18
19 <dependencies> 19 <dependencies>
20 <dependency> 20 <dependency>
21 <groupId>cn.iocoder.boot</groupId> 21 <groupId>cn.iocoder.boot</groupId>
22 - <artifactId>yudao-module-iot-component-core</artifactId> 22 + <artifactId>yudao-module-iot-net-component-core</artifactId>
23 <version>${revision}</version> 23 <version>${revision}</version>
24 </dependency> 24 </dependency>
25 25
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/config/IotNetComponentHttpAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.http.config;
  2 +
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  5 +import cn.iocoder.yudao.module.iot.net.component.http.downstream.IotDeviceDownstreamHandlerImpl;
  6 +import cn.iocoder.yudao.module.iot.net.component.http.upstream.IotDeviceUpstreamServer;
  7 +import cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonProperties;
  8 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
  9 +import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry;
  10 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
  11 +import io.vertx.core.Vertx;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.springframework.beans.factory.annotation.Qualifier;
  14 +import org.springframework.boot.autoconfigure.AutoConfiguration;
  15 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  16 +import org.springframework.boot.context.event.ApplicationStartedEvent;
  17 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  18 +import org.springframework.context.ApplicationContext;
  19 +import org.springframework.context.annotation.Bean;
  20 +import org.springframework.context.annotation.ComponentScan;
  21 +import org.springframework.context.annotation.Lazy;
  22 +import org.springframework.context.event.EventListener;
  23 +
  24 +/**
  25 + * IoT 网络组件 HTTP 的自动配置类
  26 + *
  27 + * @author haohao
  28 + */
  29 +@Slf4j
  30 +@AutoConfiguration
  31 +@EnableConfigurationProperties(IotNetComponentHttpProperties.class)
  32 +@ConditionalOnProperty(prefix = "yudao.iot.component.http", name = "enabled", havingValue = "true", matchIfMissing = false)
  33 +@ComponentScan(basePackages = {
  34 + "cn.iocoder.yudao.module.iot.net.component.http" // 只扫描 HTTP 组件包
  35 +})
  36 +public class IotNetComponentHttpAutoConfiguration {
  37 +
  38 + /**
  39 + * 组件 key
  40 + */
  41 + private static final String PLUGIN_KEY = "http";
  42 +
  43 + public IotNetComponentHttpAutoConfiguration() {
  44 + // 构造函数中不输出日志,移到 initialize 方法中
  45 + }
  46 +
  47 + /**
  48 + * 初始化 HTTP 组件
  49 + *
  50 + * @param event 应用启动事件
  51 + */
  52 + @EventListener(ApplicationStartedEvent.class)
  53 + public void initialize(ApplicationStartedEvent event) {
  54 + log.info("[IotNetComponentHttpAutoConfiguration][开始初始化]");
  55 +
  56 + // 从应用上下文中获取需要的 Bean
  57 + IotNetComponentRegistry componentRegistry = event.getApplicationContext()
  58 + .getBean(IotNetComponentRegistry.class);
  59 + IotNetComponentCommonProperties commonProperties = event.getApplicationContext()
  60 + .getBean(IotNetComponentCommonProperties.class);
  61 +
  62 + // 设置当前组件的核心标识
  63 + // 注意:这里只为当前 HTTP 组件设置 pluginKey,不影响其他组件
  64 + commonProperties.setPluginKey(PLUGIN_KEY);
  65 +
  66 + // 将 HTTP 组件注册到组件注册表
  67 + componentRegistry.registerComponent(
  68 + PLUGIN_KEY,
  69 + SystemUtil.getHostInfo().getAddress(),
  70 + 0, // 内嵌模式固定为 0
  71 + IotNetComponentCommonUtils.getProcessId());
  72 +
  73 + log.info("[initialize][IoT HTTP 组件初始化完成]");
  74 + }
  75 +
  76 + /**
  77 + * 创建 Vert.x 实例
  78 + *
  79 + * @return Vert.x 实例
  80 + */
  81 + @Bean(name = "httpVertx")
  82 + public Vertx vertx() {
  83 + return Vertx.vertx();
  84 + }
  85 +
  86 + /**
  87 + * 创建设备上行服务器
  88 + *
  89 + * @param vertx Vert.x 实例
  90 + * @param deviceUpstreamApi 设备上行 API
  91 + * @param properties HTTP 组件配置属性
  92 + * @param applicationContext 应用上下文
  93 + * @return 设备上行服务器
  94 + */
  95 + @Bean(name = "httpDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop")
  96 + public IotDeviceUpstreamServer deviceUpstreamServer(
  97 + @Lazy @Qualifier("httpVertx") Vertx vertx,
  98 + IotDeviceUpstreamApi deviceUpstreamApi,
  99 + IotNetComponentHttpProperties properties,
  100 + ApplicationContext applicationContext) {
  101 + if (log.isDebugEnabled()) {
  102 + log.debug("HTTP 服务器配置: port={}", properties.getServerPort());
  103 + } else {
  104 + log.info("HTTP 服务器将监听端口: {}", properties.getServerPort());
  105 + }
  106 + return new IotDeviceUpstreamServer(vertx, properties, deviceUpstreamApi, applicationContext);
  107 + }
  108 +
  109 + /**
  110 + * 创建设备下行处理器
  111 + *
  112 + * @return 设备下行处理器
  113 + */
  114 + @Bean(name = "httpDeviceDownstreamHandler")
  115 + public IotDeviceDownstreamHandler deviceDownstreamHandler() {
  116 + return new IotDeviceDownstreamHandlerImpl();
  117 + }
  118 +}
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/config/IotComponentHttpProperties.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/config/IotNetComponentHttpProperties.java
1 -package cn.iocoder.yudao.module.iot.component.http.config; 1 +package cn.iocoder.yudao.module.iot.net.component.http.config;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import org.springframework.boot.context.properties.ConfigurationProperties; 4 import org.springframework.boot.context.properties.ConfigurationProperties;
5 import org.springframework.validation.annotation.Validated; 5 import org.springframework.validation.annotation.Validated;
6 6
7 /** 7 /**
8 - * IoT HTTP 组件配置属性 8 + * IoT HTTP 网络组件配置属性
  9 + *
  10 + * @author haohao
9 */ 11 */
10 @ConfigurationProperties(prefix = "yudao.iot.component.http") 12 @ConfigurationProperties(prefix = "yudao.iot.component.http")
11 @Validated 13 @Validated
12 @Data 14 @Data
13 -public class IotComponentHttpProperties { 15 +public class IotNetComponentHttpProperties {
14 16
15 /** 17 /**
16 - * 是否启用 18 + * 是否启用 HTTP 组件
17 */ 19 */
18 private Boolean enabled; 20 private Boolean enabled;
19 21
@@ -22,4 +24,10 @@ public class IotComponentHttpProperties { @@ -22,4 +24,10 @@ public class IotComponentHttpProperties {
22 */ 24 */
23 private Integer serverPort; 25 private Integer serverPort;
24 26
  27 + /**
  28 + * 连接超时时间(毫秒)
  29 + * <p>
  30 + * 默认值:10000 毫秒
  31 + */
  32 + private Integer connectionTimeoutMs = 10000;
25 } 33 }
26 \ No newline at end of file 34 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/downstream/IotDeviceDownstreamHandlerImpl.java renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/downstream/IotDeviceDownstreamHandlerImpl.java
1 -package cn.iocoder.yudao.module.iot.component.http.downstream; 1 +package cn.iocoder.yudao.module.iot.net.component.http.downstream;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*; 4 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
5 -import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler; 5 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
  6 +import lombok.extern.slf4j.Slf4j;
6 7
7 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED; 8 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
8 9
9 /** 10 /**
10 - * HTTP 插件的 {@link IotDeviceDownstreamHandler} 实现类 11 + * HTTP 网络组件的 {@link IotDeviceDownstreamHandler} 实现类
11 * <p> 12 * <p>
12 * 但是:由于设备通过 HTTP 短链接接入,导致其实无法下行指导给 device 设备,所以基本都是直接返回失败!!! 13 * 但是:由于设备通过 HTTP 短链接接入,导致其实无法下行指导给 device 设备,所以基本都是直接返回失败!!!
13 - * 类似 MQTT、WebSocket、TCP 插件,是可以实现下行指令的。 14 + * 类似 MQTT、WebSocket、TCP 网络组件,是可以实现下行指令的。
14 * 15 *
15 * @author 芋道源码 16 * @author 芋道源码
16 */ 17 */
  18 +@Slf4j
17 public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler { 19 public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
18 20
  21 + /**
  22 + * 不支持的错误消息
  23 + */
  24 + private static final String NOT_SUPPORTED_MSG = "HTTP 不支持设备下行通信";
  25 +
19 @Override 26 @Override
20 public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) { 27 public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
21 - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持调用设备服务"); 28 + return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
22 } 29 }
23 30
24 @Override 31 @Override
25 public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) { 32 public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
26 - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持获取设备属性"); 33 + return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
27 } 34 }
28 35
29 @Override 36 @Override
30 public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) { 37 public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
31 - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性"); 38 + return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
32 } 39 }
33 40
34 @Override 41 @Override
35 public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) { 42 public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
36 - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性"); 43 + return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
37 } 44 }
38 45
39 @Override 46 @Override
40 public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) { 47 public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
41 - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性"); 48 + return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
42 } 49 }
43 -  
44 } 50 }
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/IotDeviceUpstreamServer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.http.upstream;
  2 +
  3 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  4 +import cn.iocoder.yudao.module.iot.net.component.http.config.IotNetComponentHttpProperties;
  5 +import cn.iocoder.yudao.module.iot.net.component.http.upstream.router.IotDeviceUpstreamVertxHandler;
  6 +import io.vertx.core.AbstractVerticle;
  7 +import io.vertx.core.Promise;
  8 +import io.vertx.core.Vertx;
  9 +import io.vertx.ext.web.Router;
  10 +import io.vertx.ext.web.handler.BodyHandler;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.context.ApplicationContext;
  13 +import org.springframework.context.annotation.Lazy;
  14 +
  15 +/**
  16 + * IoT 设备上行服务器
  17 + * <p>
  18 + * 处理设备通过 HTTP 方式接入的上行消息
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Slf4j
  23 +public class IotDeviceUpstreamServer extends AbstractVerticle {
  24 +
  25 + /**
  26 + * Vert.x 实例
  27 + */
  28 + private final Vertx vertx;
  29 +
  30 + /**
  31 + * HTTP 组件配置属性
  32 + */
  33 + private final IotNetComponentHttpProperties httpProperties;
  34 +
  35 + /**
  36 + * 设备上行 API
  37 + */
  38 + private final IotDeviceUpstreamApi deviceUpstreamApi;
  39 +
  40 + /**
  41 + * Spring 应用上下文
  42 + */
  43 + private final ApplicationContext applicationContext;
  44 +
  45 + /**
  46 + * 构造函数
  47 + *
  48 + * @param vertx Vert.x 实例
  49 + * @param httpProperties HTTP 组件配置属性
  50 + * @param deviceUpstreamApi 设备上行 API
  51 + * @param applicationContext Spring 应用上下文
  52 + */
  53 + public IotDeviceUpstreamServer(
  54 + @Lazy Vertx vertx,
  55 + IotNetComponentHttpProperties httpProperties,
  56 + IotDeviceUpstreamApi deviceUpstreamApi,
  57 + ApplicationContext applicationContext) {
  58 + this.vertx = vertx;
  59 + this.httpProperties = httpProperties;
  60 + this.deviceUpstreamApi = deviceUpstreamApi;
  61 + this.applicationContext = applicationContext;
  62 + }
  63 +
  64 + @Override
  65 + public void start(Promise<Void> startPromise) {
  66 + // 创建路由
  67 + Router router = Router.router(vertx);
  68 + router.route().handler(BodyHandler.create());
  69 +
  70 + // 创建处理器
  71 + IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(
  72 + deviceUpstreamApi, applicationContext);
  73 +
  74 + // 添加路由处理器
  75 + router.post(IotDeviceUpstreamVertxHandler.PROPERTY_PATH).handler(upstreamHandler::handle);
  76 + router.post(IotDeviceUpstreamVertxHandler.EVENT_PATH).handler(upstreamHandler::handle);
  77 +
  78 + // 启动 HTTP 服务器
  79 + vertx.createHttpServer()
  80 + .requestHandler(router)
  81 + .listen(httpProperties.getServerPort(), result -> {
  82 + if (result.succeeded()) {
  83 + log.info("[start][IoT 设备上行服务器启动成功,端口:{}]", httpProperties.getServerPort());
  84 + startPromise.complete();
  85 + } else {
  86 + log.error("[start][IoT 设备上行服务器启动失败]", result.cause());
  87 + startPromise.fail(result.cause());
  88 + }
  89 + });
  90 + }
  91 +
  92 + @Override
  93 + public void stop(Promise<Void> stopPromise) {
  94 + log.info("[stop][IoT 设备上行服务器已停止]");
  95 + stopPromise.complete();
  96 + }
  97 +}
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/auth/IotDeviceAuthProvider.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.http.upstream.auth;
  2 +
  3 +import io.vertx.core.Future;
  4 +import io.vertx.ext.web.RoutingContext;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.context.ApplicationContext;
  7 +
  8 +/**
  9 + * IoT 设备认证提供者
  10 + * <p>
  11 + * 用于 HTTP 设备接入时的身份认证
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Slf4j
  16 +public class IotDeviceAuthProvider {
  17 +
  18 + private final ApplicationContext applicationContext;
  19 +
  20 + /**
  21 + * 构造函数
  22 + *
  23 + * @param applicationContext Spring 应用上下文
  24 + */
  25 + public IotDeviceAuthProvider(ApplicationContext applicationContext) {
  26 + this.applicationContext = applicationContext;
  27 + }
  28 +
  29 + /**
  30 + * 认证设备
  31 + *
  32 + * @param context 路由上下文
  33 + * @param clientId 设备唯一标识
  34 + * @return 认证结果 Future 对象
  35 + */
  36 + public Future<Void> authenticate(RoutingContext context, String clientId) {
  37 + if (clientId == null || clientId.isEmpty()) {
  38 + return Future.failedFuture("clientId 不能为空");
  39 + }
  40 +
  41 + try {
  42 + log.info("[authenticate][设备认证成功,clientId={}]", clientId);
  43 + return Future.succeededFuture();
  44 + } catch (Exception e) {
  45 + log.error("[authenticate][设备认证异常,clientId={}]", clientId, e);
  46 + return Future.failedFuture(e);
  47 + }
  48 + }
  49 +}
0 \ No newline at end of file 50 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/router/IotDeviceUpstreamVertxHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.http.upstream.router;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.ObjUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  8 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  9 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
  10 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
  11 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
  12 +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
  13 +import cn.iocoder.yudao.module.iot.net.component.core.constants.IotDeviceTopicEnum;
  14 +import cn.iocoder.yudao.module.iot.net.component.core.pojo.IotStandardResponse;
  15 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
  16 +import io.vertx.core.Handler;
  17 +import io.vertx.core.json.JsonObject;
  18 +import io.vertx.ext.web.RoutingContext;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.context.ApplicationContext;
  21 +
  22 +import java.time.LocalDateTime;
  23 +import java.util.Map;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
  26 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  27 +
  28 +/**
  29 + * IoT 设备上行统一处理的 Vert.x Handler
  30 + * <p>
  31 + * 统一处理设备属性上报和事件上报的请求。
  32 + *
  33 + * @author 芋道源码
  34 + */
  35 +@Slf4j
  36 +public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
  37 +
  38 + /**
  39 + * 属性上报路径
  40 + */
  41 + public static final String PROPERTY_PATH = "/sys/:productKey/:deviceName"
  42 + + IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic();
  43 +
  44 + /**
  45 + * 事件上报路径
  46 + */
  47 + public static final String EVENT_PATH = "/sys/:productKey/:deviceName"
  48 + + IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic() + ":identifier"
  49 + + IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic();
  50 +
  51 + /**
  52 + * 属性上报方法标识
  53 + */
  54 + private static final String PROPERTY_METHOD = "thing.event.property.post";
  55 +
  56 + /**
  57 + * 事件上报方法前缀
  58 + */
  59 + private static final String EVENT_METHOD_PREFIX = "thing.event.";
  60 +
  61 + /**
  62 + * 事件上报方法后缀
  63 + */
  64 + private static final String EVENT_METHOD_SUFFIX = ".post";
  65 +
  66 + /**
  67 + * 设备上行 API
  68 + */
  69 + private final IotDeviceUpstreamApi deviceUpstreamApi;
  70 +
  71 + /**
  72 + * 构造函数
  73 + *
  74 + * @param deviceUpstreamApi 设备上行 API
  75 + * @param applicationContext 应用上下文
  76 + */
  77 + public IotDeviceUpstreamVertxHandler(IotDeviceUpstreamApi deviceUpstreamApi,
  78 + ApplicationContext applicationContext) {
  79 + this.deviceUpstreamApi = deviceUpstreamApi;
  80 + }
  81 +
  82 + @Override
  83 + public void handle(RoutingContext routingContext) {
  84 + String path = routingContext.request().path();
  85 + String requestId = IdUtil.fastSimpleUUID();
  86 +
  87 + try {
  88 + // 1. 解析通用参数
  89 + Map<String, String> params = parseCommonParams(routingContext, requestId);
  90 + String productKey = params.get("productKey");
  91 + String deviceName = params.get("deviceName");
  92 + JsonObject body = routingContext.body().asJsonObject();
  93 + requestId = params.get("requestId");
  94 +
  95 + // 2. 根据路径模式处理不同类型的请求
  96 + if (isPropertyPostPath(path)) {
  97 + // 处理属性上报
  98 + handlePropertyPost(routingContext, productKey, deviceName, requestId, body);
  99 + return;
  100 + }
  101 +
  102 + if (isEventPostPath(path)) {
  103 + // 处理事件上报
  104 + String identifier = routingContext.pathParam("identifier");
  105 + handleEventPost(routingContext, productKey, deviceName, identifier, requestId, body);
  106 + return;
  107 + }
  108 +
  109 + // 不支持的请求路径
  110 + sendErrorResponse(routingContext, requestId, "unknown", BAD_REQUEST.getCode(), "不支持的请求路径");
  111 + } catch (Exception e) {
  112 + log.error("[handle][处理上行请求异常] path={}", path, e);
  113 + String method = determineMethodFromPath(path, routingContext);
  114 + sendErrorResponse(routingContext, requestId, method, INTERNAL_SERVER_ERROR.getCode(),
  115 + INTERNAL_SERVER_ERROR.getMsg());
  116 + }
  117 + }
  118 +
  119 + /**
  120 + * 解析通用参数
  121 + *
  122 + * @param routingContext 路由上下文
  123 + * @param defaultRequestId 默认请求 ID
  124 + * @return 参数映射
  125 + */
  126 + private Map<String, String> parseCommonParams(RoutingContext routingContext, String defaultRequestId) {
  127 + Map<String, String> params = MapUtil.newHashMap();
  128 + params.put("productKey", routingContext.pathParam("productKey"));
  129 + params.put("deviceName", routingContext.pathParam("deviceName"));
  130 +
  131 + JsonObject body = routingContext.body().asJsonObject();
  132 + String requestId = ObjUtil.defaultIfNull(body.getString("id"), defaultRequestId);
  133 + params.put("requestId", requestId);
  134 +
  135 + return params;
  136 + }
  137 +
  138 + /**
  139 + * 判断是否是属性上报路径
  140 + *
  141 + * @param path 路径
  142 + * @return 是否是属性上报路径
  143 + */
  144 + private boolean isPropertyPostPath(String path) {
  145 + return StrUtil.endWith(path, IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic());
  146 + }
  147 +
  148 + /**
  149 + * 判断是否是事件上报路径
  150 + *
  151 + * @param path 路径
  152 + * @return 是否是事件上报路径
  153 + */
  154 + private boolean isEventPostPath(String path) {
  155 + return StrUtil.contains(path, IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic())
  156 + && StrUtil.endWith(path, IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic());
  157 + }
  158 +
  159 + /**
  160 + * 处理属性上报请求
  161 + *
  162 + * @param routingContext 路由上下文
  163 + * @param productKey 产品 Key
  164 + * @param deviceName 设备名称
  165 + * @param requestId 请求 ID
  166 + * @param body 请求体
  167 + */
  168 + private void handlePropertyPost(RoutingContext routingContext, String productKey, String deviceName,
  169 + String requestId, JsonObject body) {
  170 + // 处理属性上报
  171 + IotDevicePropertyReportReqDTO reportReqDTO = parsePropertyReportRequest(productKey, deviceName,
  172 + requestId, body);
  173 +
  174 + // 设备上线
  175 + updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
  176 +
  177 + // 属性上报
  178 + CommonResult<Boolean> result = deviceUpstreamApi.reportDeviceProperty(reportReqDTO);
  179 +
  180 + // 返回响应
  181 + sendResponse(routingContext, requestId, PROPERTY_METHOD, result);
  182 + }
  183 +
  184 + /**
  185 + * 处理事件上报请求
  186 + *
  187 + * @param routingContext 路由上下文
  188 + * @param productKey 产品 Key
  189 + * @param deviceName 设备名称
  190 + * @param identifier 事件标识符
  191 + * @param requestId 请求 ID
  192 + * @param body 请求体
  193 + */
  194 + private void handleEventPost(RoutingContext routingContext, String productKey, String deviceName,
  195 + String identifier, String requestId, JsonObject body) {
  196 + // 处理事件上报
  197 + IotDeviceEventReportReqDTO reportReqDTO = parseEventReportRequest(productKey, deviceName, identifier,
  198 + requestId, body);
  199 +
  200 + // 设备上线
  201 + updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
  202 +
  203 + // 事件上报
  204 + CommonResult<Boolean> result = deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
  205 + String method = EVENT_METHOD_PREFIX + identifier + EVENT_METHOD_SUFFIX;
  206 +
  207 + // 返回响应
  208 + sendResponse(routingContext, requestId, method, result);
  209 + }
  210 +
  211 + /**
  212 + * 发送响应
  213 + *
  214 + * @param routingContext 路由上下文
  215 + * @param requestId 请求 ID
  216 + * @param method 方法名
  217 + * @param result 结果
  218 + */
  219 + private void sendResponse(RoutingContext routingContext, String requestId, String method,
  220 + CommonResult<Boolean> result) {
  221 + IotStandardResponse response;
  222 + if (result.isSuccess()) {
  223 + response = IotStandardResponse.success(requestId, method, result.getData());
  224 + } else {
  225 + response = IotStandardResponse.error(requestId, method, result.getCode(), result.getMsg());
  226 + }
  227 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, response);
  228 + }
  229 +
  230 + /**
  231 + * 发送错误响应
  232 + *
  233 + * @param routingContext 路由上下文
  234 + * @param requestId 请求 ID
  235 + * @param method 方法名
  236 + * @param code 错误代码
  237 + * @param message 错误消息
  238 + */
  239 + private void sendErrorResponse(RoutingContext routingContext, String requestId, String method, Integer code,
  240 + String message) {
  241 + IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
  242 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, errorResponse);
  243 + }
  244 +
  245 + /**
  246 + * 从路径确定方法名
  247 + *
  248 + * @param path 路径
  249 + * @param routingContext 路由上下文
  250 + * @return 方法名
  251 + */
  252 + private String determineMethodFromPath(String path, RoutingContext routingContext) {
  253 + if (StrUtil.contains(path, "/property/")) {
  254 + return PROPERTY_METHOD;
  255 + }
  256 +
  257 + return EVENT_METHOD_PREFIX +
  258 + (routingContext.pathParams().containsKey("identifier")
  259 + ? routingContext.pathParam("identifier")
  260 + : "unknown")
  261 + +
  262 + EVENT_METHOD_SUFFIX;
  263 + }
  264 +
  265 + /**
  266 + * 更新设备状态
  267 + *
  268 + * @param productKey 产品 Key
  269 + * @param deviceName 设备名称
  270 + */
  271 + private void updateDeviceState(String productKey, String deviceName) {
  272 + IotDeviceStateUpdateReqDTO reqDTO = ((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO()
  273 + .setRequestId(IdUtil.fastSimpleUUID())
  274 + .setProcessId(IotNetComponentCommonUtils.getProcessId())
  275 + .setReportTime(LocalDateTime.now())
  276 + .setProductKey(productKey)
  277 + .setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState());
  278 +
  279 + deviceUpstreamApi.updateDeviceState(reqDTO);
  280 + }
  281 +
  282 + /**
  283 + * 解析属性上报请求
  284 + *
  285 + * @param productKey 产品 Key
  286 + * @param deviceName 设备名称
  287 + * @param requestId 请求 ID
  288 + * @param body 请求体
  289 + * @return 属性上报请求 DTO
  290 + */
  291 + private IotDevicePropertyReportReqDTO parsePropertyReportRequest(String productKey, String deviceName,
  292 + String requestId, JsonObject body) {
  293 + // 解析属性
  294 + Map<String, Object> properties = parsePropertiesFromBody(body);
  295 +
  296 + // 构建属性上报请求 DTO
  297 + return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
  298 + .setRequestId(requestId)
  299 + .setProcessId(IotNetComponentCommonUtils.getProcessId())
  300 + .setReportTime(LocalDateTime.now())
  301 + .setProductKey(productKey)
  302 + .setDeviceName(deviceName)).setProperties(properties);
  303 + }
  304 +
  305 + /**
  306 + * 从请求体解析属性
  307 + *
  308 + * @param body 请求体
  309 + * @return 属性映射
  310 + */
  311 + private Map<String, Object> parsePropertiesFromBody(JsonObject body) {
  312 + Map<String, Object> properties = MapUtil.newHashMap();
  313 + JsonObject params = body.getJsonObject("params");
  314 +
  315 + if (params == null) {
  316 + return properties;
  317 + }
  318 +
  319 + // 将标准格式的 params 转换为平台需要的 properties 格式
  320 + for (String key : params.fieldNames()) {
  321 + Object valueObj = params.getValue(key);
  322 + // 如果是复杂结构(包含 value 和 time)
  323 + if (valueObj instanceof JsonObject) {
  324 + JsonObject valueJson = (JsonObject) valueObj;
  325 + properties.put(key, valueJson.containsKey("value") ? valueJson.getValue("value") : valueObj);
  326 + } else {
  327 + properties.put(key, valueObj);
  328 + }
  329 + }
  330 +
  331 + return properties;
  332 + }
  333 +
  334 + /**
  335 + * 解析事件上报请求
  336 + *
  337 + * @param productKey 产品 Key
  338 + * @param deviceName 设备名称
  339 + * @param identifier 事件标识符
  340 + * @param requestId 请求 ID
  341 + * @param body 请求体
  342 + * @return 事件上报请求 DTO
  343 + */
  344 + private IotDeviceEventReportReqDTO parseEventReportRequest(String productKey, String deviceName, String identifier,
  345 + String requestId, JsonObject body) {
  346 + // 解析参数
  347 + Map<String, Object> params = parseParamsFromBody(body);
  348 +
  349 + // 构建事件上报请求 DTO
  350 + return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO()
  351 + .setRequestId(requestId)
  352 + .setProcessId(IotNetComponentCommonUtils.getProcessId())
  353 + .setReportTime(LocalDateTime.now())
  354 + .setProductKey(productKey)
  355 + .setDeviceName(deviceName)).setIdentifier(identifier).setParams(params);
  356 + }
  357 +
  358 + /**
  359 + * 从请求体解析参数
  360 + *
  361 + * @param body 请求体
  362 + * @return 参数映射
  363 + */
  364 + private Map<String, Object> parseParamsFromBody(JsonObject body) {
  365 + Map<String, Object> params = MapUtil.newHashMap();
  366 + JsonObject paramsJson = body.getJsonObject("params");
  367 +
  368 + if (paramsJson == null) {
  369 + return params;
  370 + }
  371 +
  372 + for (String key : paramsJson.fieldNames()) {
  373 + params.put(key, paramsJson.getValue(key));
  374 + }
  375 +
  376 + return params;
  377 + }
  378 +}
0 \ No newline at end of file 379 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 0 → 100644
  1 +cn.iocoder.yudao.module.iot.net.component.http.config.IotNetComponentHttpAutoConfiguration
0 \ No newline at end of file 2 \ No newline at end of file
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/application.yml renamed to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/application.yml