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 10 <modules>
11 11 <module>yudao-module-iot-api</module>
12 12 <module>yudao-module-iot-biz</module>
13   - <module>yudao-module-iot-components</module>
  13 + <module>yudao-module-iot-net-components</module>
14 14 <!-- <module>yudao-module-iot-plugins</module>-->
15 15 </modules>
16 16 <modelVersion>4.0.0</modelVersion>
... ...
yudao-module-iot/yudao-module-iot-biz/pom.xml
... ... @@ -93,10 +93,10 @@
93 93 <optional>true</optional>
94 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 101 <!-- TODO @芋艿:bom 管理 -->
102 102 <dependency>
... ... @@ -137,12 +137,12 @@
137 137 <!-- IoT 网络组件:接收来自设备的上行数据 -->
138 138 <dependency>
139 139 <groupId>cn.iocoder.boot</groupId>
140   - <artifactId>yudao-module-iot-component-http</artifactId>
  140 + <artifactId>yudao-module-iot-net-component-http</artifactId>
141 141 <version>${revision}</version>
142 142 </dependency>
143 143 <dependency>
144 144 <groupId>cn.iocoder.boot</groupId>
145   - <artifactId>yudao-module-iot-component-emqx</artifactId>
  145 + <artifactId>yudao-module-iot-net-component-emqx</artifactId>
146 146 <version>${revision}</version>
147 147 </dependency>
148 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 16 */
17 17 @RestController
18 18 @Validated
19   -@Primary // 保证优先匹配,因为 yudao-module-iot-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入
  19 +@Primary // 保证优先匹配,因为 yudao-module-iot-net-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入
20 20 public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
21 21  
22 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 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 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 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 9 import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
10 10 import jakarta.annotation.Resource;
11 11 import lombok.extern.slf4j.Slf4j;
12   -import org.pf4j.PluginWrapper;
13   -import org.pf4j.spring.SpringPluginManager;
14 12 import org.springframework.stereotype.Service;
15 13 import org.springframework.validation.annotation.Validated;
16 14 import org.springframework.web.multipart.MultipartFile;
... ... @@ -35,8 +33,8 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
35 33 @Resource
36 34 private IotPluginInstanceService pluginInstanceService;
37 35  
38   - @Resource
39   - private SpringPluginManager springPluginManager;
  36 +// @Resource
  37 +// private SpringPluginManager springPluginManager;
40 38  
41 39 @Override
42 40 public Long createPluginConfig(PluginConfigSaveReqVO createReqVO) {
... ... @@ -130,16 +128,16 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
130 128 validatePluginConfigFile(pluginKeyNew);
131 129  
132 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 147 */
150 148 private void validatePluginConfigFile(String pluginKeyNew) {
151 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 159 @Override
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.service.plugin;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.io.FileUtil;
5 4 import cn.hutool.core.util.StrUtil;
6 5 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
7 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 8 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
10 9 import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInstanceMapper;
11 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 11 import jakarta.annotation.Resource;
15 12 import lombok.extern.slf4j.Slf4j;
16   -import org.pf4j.PluginState;
17   -import org.pf4j.PluginWrapper;
18   -import org.pf4j.spring.SpringPluginManager;
19 13 import org.springframework.beans.factory.annotation.Value;
20 14 import org.springframework.context.annotation.Lazy;
21 15 import org.springframework.stereotype.Service;
... ... @@ -23,17 +17,10 @@ import org.springframework.validation.annotation.Validated;
23 17 import org.springframework.web.multipart.MultipartFile;
24 18  
25 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 20 import java.time.LocalDateTime;
32 21 import java.util.List;
33 22 import java.util.concurrent.TimeUnit;
34 23  
35   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36   -
37 24 /**
38 25 * IoT 插件实例 Service 实现类
39 26 *
... ... @@ -54,8 +41,8 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
54 41 @Resource
55 42 private DevicePluginProcessIdRedisDAO devicePluginProcessIdRedisDAO;
56 43  
57   - @Resource
58   - private SpringPluginManager pluginManager;
  44 +// @Resource
  45 +// private SpringPluginManager pluginManager;
59 46  
60 47 @Value("${pf4j.pluginsDir}")
61 48 private String pluginsDir;
... ... @@ -120,17 +107,17 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
120 107  
121 108 @Override
122 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 123 @Override
... ... @@ -151,65 +138,66 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
151 138  
152 139 @Override
153 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 166 @Override
179 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 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 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 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 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 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 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 4  
5 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 9 </parent>
10 10 <modelVersion>4.0.0</modelVersion>
11 11  
12   - <artifactId>yudao-module-iot-components</artifactId>
  12 + <artifactId>yudao-module-iot-net-components</artifactId>
13 13 <packaging>pom</packaging>
14 14  
15 15 <name>${project.artifactId}</name>
16 16 <description>
17   - 物联网组件模块,提供与物联网设备通讯、管理的组件实现
  17 + 物联网网络组件模块,提供与物联网设备通讯、管理的网络组件实现
18 18 </description>
19 19  
20 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 24 </modules>
25 25  
26 26 </project>
27 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 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 5 <parent>
6   - <artifactId>yudao-module-iot-components</artifactId>
  6 + <artifactId>yudao-module-iot-net-components</artifactId>
7 7 <groupId>cn.iocoder.boot</groupId>
8 8 <version>${revision}</version>
9 9 </parent>
10 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 13 <packaging>jar</packaging>
14 14  
15 15 <name>${project.artifactId}</name>
16   - <!-- TODO @芋艿:description 后续统一优化一波 -->
17 16 <description>
18   - 物联网组件核心模块
  17 + 物联网网络组件核心模块
19 18 </description>
20 19  
21 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 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 9 import org.springframework.beans.factory.annotation.Qualifier;
10 10 import org.springframework.boot.autoconfigure.AutoConfiguration;
11 11 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
... ... @@ -14,37 +14,45 @@ import org.springframework.context.annotation.Bean;
14 14 import org.springframework.scheduling.annotation.EnableScheduling;
15 15  
16 16 /**
17   - * IoT 组件的通用自动配置类
  17 + * IoT 网络组件的通用自动配置类
18 18 *
19 19 * @author haohao
20 20 */
21 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 27 * 创建 EMQX 设备下行服务器
28   - *
  28 + * <p>
29 29 * 当 yudao.iot.component.emqx.enabled = true 时,优先使用 emqxDeviceDownstreamHandler
30 30 */
31 31 @Bean
32 32 @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true")
33 33 public IotDeviceDownstreamServer emqxDeviceDownstreamServer(
34   - IotComponentCommonProperties properties,
  34 + IotNetComponentCommonProperties properties,
35 35 @Qualifier("emqxDeviceDownstreamHandler") IotDeviceDownstreamHandler deviceDownstreamHandler) {
36 36 return new IotDeviceDownstreamServer(properties, deviceDownstreamHandler);
37 37 }
38 38  
  39 + /**
  40 + * 创建网络组件实例心跳任务
  41 + */
39 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 50 componentRegistry);
46 51 }
47 52  
  53 + /**
  54 + * 创建设备上行客户端
  55 + */
48 56 @Bean
49 57 public IotDeviceUpstreamClient deviceUpstreamClient() {
50 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 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 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 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 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 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 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 6 import lombok.RequiredArgsConstructor;
7 7 import lombok.extern.slf4j.Slf4j;
8 8  
... ... @@ -15,7 +15,7 @@ import lombok.extern.slf4j.Slf4j;
15 15 @RequiredArgsConstructor
16 16 public class IotDeviceDownstreamServer {
17 17  
18   - private final IotComponentCommonProperties properties;
  18 + private final IotNetComponentCommonProperties properties;
19 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 4 import cn.hutool.system.SystemUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 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 11 import lombok.RequiredArgsConstructor;
11 12 import lombok.extern.slf4j.Slf4j;
12 13 import org.springframework.scheduling.annotation.Scheduled;
13 14  
14   -import java.lang.management.ManagementFactory;
  15 +import java.util.Collection;
15 16 import java.util.concurrent.TimeUnit;
16 17  
17 18 /**
18   - * IoT 组件实例心跳定时任务
  19 + * IoT 网络组件实例心跳定时任务
19 20 * <p>
20 21 * 将组件的状态,定时上报给 server 服务器
  22 + *
  23 + * @author haohao
21 24 */
22 25 @RequiredArgsConstructor
23 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 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 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 43 try {
54 44 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(
55 45 buildPluginInstanceHeartbeatReqDTO(component, true));
56   - log.info("[init][组件({})上线结果:{})]", component.getPluginKey(), result);
  46 + log.info("[init][组件({})上线结果:{}]", component.getPluginKey(), result);
57 47 } catch (Exception e) {
58 48 log.error("[init][组件({})上线发送异常]", component.getPluginKey(), e);
59 49 }
... ... @@ -65,11 +55,15 @@ public class IotComponentInstanceHeartbeatJob {
65 55 */
66 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 63 try {
70 64 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(
71 65 buildPluginInstanceHeartbeatReqDTO(component, false));
72   - log.info("[stop][组件({})下线结果:{})]", component.getPluginKey(), result);
  66 + log.info("[stop][组件({})下线结果:{}]", component.getPluginKey(), result);
73 67 } catch (Exception e) {
74 68 log.error("[stop][组件({})下线发送异常]", component.getPluginKey(), e);
75 69 }
... ... @@ -85,11 +79,15 @@ public class IotComponentInstanceHeartbeatJob {
85 79 @Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.MINUTES) // 1 分钟执行一次
86 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 87 try {
90 88 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(
91 89 buildPluginInstanceHeartbeatReqDTO(component, true));
92   - log.info("[execute][组件({})心跳结果:{})]", component.getPluginKey(), result);
  90 + log.info("[execute][组件({})心跳结果:{}]", component.getPluginKey(), result);
93 91 } catch (Exception e) {
94 92 log.error("[execute][组件({})心跳发送异常]", component.getPluginKey(), e);
95 93 }
... ... @@ -103,23 +101,11 @@ public class IotComponentInstanceHeartbeatJob {
103 101 * @param online 是否在线
104 102 * @return 心跳 DTO
105 103 */
106   - private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(IotComponentInfo component,
  104 + private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(IotNetComponentInfo component,
107 105 Boolean online) {
108 106 return new IotPluginInstanceHeartbeatReqDTO()
109 107 .setPluginKey(component.getPluginKey()).setProcessId(component.getProcessId())
110 108 .setHostIp(component.getHostIp()).setDownstreamPort(component.getDownstreamPort())
111 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 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 5 import lombok.Data;
4 6 import lombok.extern.slf4j.Slf4j;
5 7 import org.springframework.stereotype.Component;
... ... @@ -8,49 +10,51 @@ import java.util.Collection;
8 10 import java.util.Map;
9 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 15 * <p>
16   - * 用于管理多个组件的注册信息,解决多组件心跳问题
  16 + * 用于管理多个网络组件的注册信息,解决多组件心跳问题
  17 + *
  18 + * @author haohao
17 19 */
18 20 @Component
19 21 @Slf4j
20   -public class IotComponentRegistry {
  22 +public class IotNetComponentRegistry {
21 23  
22 24 /**
23   - * 组件信息
  25 + * 网络组件信息
24 26 */
25 27 @Data
26   - public static class IotComponentInfo {
  28 + public static class IotNetComponentInfo {
27 29  
28 30 /**
29 31 * 组件 Key
30 32 */
31 33 private final String pluginKey;
  34 +
32 35 /**
33 36 * 主机 IP
34 37 */
35 38 private final String hostIp;
  39 +
36 40 /**
37 41 * 下游端口
38 42 */
39 43 private final Integer downstreamPort;
  44 +
40 45 /**
41 46 * 进程 ID
42 47 */
43 48 private final String processId;
44   -
45 49 }
46 50  
47 51 /**
48 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 59 * @param pluginKey 组件 Key
56 60 * @param hostIp 主机 IP
... ... @@ -58,38 +62,37 @@ public class IotComponentRegistry {
58 62 * @param processId 进程 ID
59 63 */
60 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 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 73 * @param pluginKey 组件 Key
70 74 */
71 75 public void unregisterComponent(String pluginKey) {
72   - log.info("[unregisterComponent][注销组件, pluginKey={}]", pluginKey);
  76 + log.info("[unregisterComponent][注销网络组件, pluginKey={}]", pluginKey);
73 77 components.remove(pluginKey);
74 78 }
75 79  
76 80 /**
77   - * 获取所有组件
  81 + * 获取所有网络组件
78 82 *
79 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 92 * @param pluginKey 组件 Key
89 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 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 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 4 import lombok.Data;
  5 +import lombok.experimental.Accessors;
4 6  
5 7 /**
6 8 * IoT 标准协议响应实体类
... ... @@ -10,10 +12,11 @@ import lombok.Data;
10 12 * @author haohao
11 13 */
12 14 @Data
  15 +@Accessors(chain = true)
13 16 public class IotStandardResponse {
14 17  
15 18 /**
16   - * 消息ID
  19 + * 消息 ID
17 20 */
18 21 private String id;
19 22  
... ... @@ -45,7 +48,7 @@ public class IotStandardResponse {
45 48 /**
46 49 * 创建成功响应
47 50 *
48   - * @param id 消息ID
  51 + * @param id 消息 ID
49 52 * @param method 方法名
50 53 * @return 成功响应
51 54 */
... ... @@ -56,28 +59,37 @@ public class IotStandardResponse {
56 59 /**
57 60 * 创建成功响应
58 61 *
59   - * @param id 消息ID
  62 + * @param id 消息 ID
60 63 * @param method 方法名
61 64 * @param data 响应数据
62 65 * @return 成功响应
63 66 */
64 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 81 * @param method 方法名
74 82 * @param code 错误码
75 83 * @param message 错误消息
76 84 * @return 错误响应
77 85 */
78 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 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 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 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 3 import cn.hutool.core.util.IdUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.hutool.system.SystemUtil;
6 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 8 import io.vertx.core.http.HttpHeaders;
9 9 import io.vertx.ext.web.RoutingContext;
10 10 import org.springframework.http.MediaType;
11 11  
12   -// TODO @haohao:名字要改下哈。
13 12 /**
14   - * IoT 插件的通用工具类
  13 + * IoT 网络组件的通用工具类
15 14 *
16 15 * @author 芋道源码
17 16 */
18   -public class IotPluginCommonUtils {
  17 +public class IotNetComponentCommonUtils {
19 18  
20 19 /**
21 20 * 流程实例的进程编号
22 21 */
23 22 private static String processId;
24 23  
  24 + /**
  25 + * 获取进程ID
  26 + *
  27 + * @return 进程ID
  28 + */
25 29 public static String getProcessId() {
26 30 if (StrUtil.isEmpty(processId)) {
27 31 initProcessId();
... ... @@ -29,12 +33,24 @@ public class IotPluginCommonUtils {
29 33 return processId;
30 34 }
31 35  
  36 + /**
  37 + * 初始化进程ID
  38 + */
32 39 private synchronized static void initProcessId() {
33 40 processId = String.format("%s@%d@%s", // IP@PID@${uuid}
34 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 54 * 将对象转换为JSON字符串后写入HTTP响应
39 55 *
40 56 * @param routingContext 路由上下文
... ... @@ -51,20 +67,20 @@ public class IotPluginCommonUtils {
51 67 /**
52 68 * 生成标准JSON格式的响应并写入HTTP响应(基于IotStandardResponse)
53 69 * <p>
54   - * 推荐使用此方法,统一MQTT和HTTP的响应格式。使用方式:
  70 + * 推荐使用此方法,统一 MQTT 和 HTTP 的响应格式。使用方式:
55 71 *
56 72 * <pre>
57 73 * // 成功响应
58 74 * IotStandardResponse response = IotStandardResponse.success(requestId, method, data);
59   - * IotPluginCommonUtils.writeJsonResponse(routingContext, response);
  75 + * IotNetComponentCommonUtils.writeJsonResponse(routingContext, response);
60 76 *
61 77 * // 错误响应
62 78 * IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
63   - * IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
  79 + * IotNetComponentCommonUtils.writeJsonResponse(routingContext, errorResponse);
64 80 * </pre>
65 81 *
66 82 * @param routingContext 路由上下文
67   - * @param response IotStandardResponse响应对象
  83 + * @param response IotStandardResponse 响应对象
68 84 */
69 85 @SuppressWarnings("deprecation")
70 86 public static void writeJsonResponse(RoutingContext routingContext, IotStandardResponse response) {
... ... @@ -73,5 +89,4 @@ public class IotPluginCommonUtils {
73 89 .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
74 90 .end(JsonUtils.toJsonString(response));
75 91 }
76   -
77   -}
  92 +}
78 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 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 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 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 5 <parent>
6   - <artifactId>yudao-module-iot-components</artifactId>
  6 + <artifactId>yudao-module-iot-net-components</artifactId>
7 7 <groupId>cn.iocoder.boot</groupId>
8 8 <version>${revision}</version>
9 9 </parent>
10 10 <modelVersion>4.0.0</modelVersion>
11   - <artifactId>yudao-module-iot-component-emqx</artifactId>
  11 + <artifactId>yudao-module-iot-net-component-emqx</artifactId>
12 12 <packaging>jar</packaging>
13 13  
14 14 <name>${project.artifactId}</name>
15 15 <description>
16   - 物联网组件 EMQX 模块
  16 + 物联网网络组件 EMQX 模块
17 17 </description>
18 18  
19 19 <dependencies>
20 20 <dependency>
21 21 <groupId>cn.iocoder.boot</groupId>
22   - <artifactId>yudao-module-iot-component-core</artifactId>
  22 + <artifactId>yudao-module-iot-net-component-core</artifactId>
23 23 <version>${revision}</version>
24 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 3 import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.core.util.ObjUtil;
4 5 import cn.hutool.system.SystemUtil;
5 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 13 import io.vertx.core.Vertx;
12 14 import io.vertx.mqtt.MqttClient;
13 15 import io.vertx.mqtt.MqttClientOptions;
14 16 import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.beans.factory.annotation.Qualifier;
15 18 import org.springframework.boot.autoconfigure.AutoConfiguration;
16 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
17 20 import org.springframework.boot.context.event.ApplicationStartedEvent;
... ... @@ -20,42 +23,46 @@ import org.springframework.context.annotation.Bean;
20 23 import org.springframework.context.annotation.ComponentScan;
21 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 29 * @author haohao
29 30 */
30 31 @Slf4j
31 32 @AutoConfiguration
32   -@EnableConfigurationProperties(IotComponentEmqxProperties.class)
  33 +@EnableConfigurationProperties(IotNetComponentEmqxProperties.class)
33 34 @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true", matchIfMissing = false)
34   -// TODO @haohao:是不是不用扫 cn.iocoder.yudao.module.iot.component.core 拉,它尽量靠自动配置
35 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 41 * 组件 key
43 42 */
44 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 54 @EventListener(ApplicationStartedEvent.class)
52 55 public void initialize(ApplicationStartedEvent event) {
  56 + log.info("[IotNetComponentEmqxAutoConfiguration][开始初始化]");
  57 +
53 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 66 commonProperties.setPluginKey(PLUGIN_KEY);
60 67  
61 68 // 将 EMQX 组件注册到组件注册表
... ... @@ -63,64 +70,60 @@ public class IotComponentEmqxAutoConfiguration {
63 70 PLUGIN_KEY,
64 71 SystemUtil.getHostInfo().getAddress(),
65 72 0, // 内嵌模式固定为 0
66   - getProcessId()
67   - );
  73 + IotNetComponentCommonUtils.getProcessId());
68 74  
69 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 82 public Vertx vertx() {
75 83 return Vertx.vertx();
76 84 }
77 85  
  86 + /**
  87 + * 创建 MQTT 客户端
  88 + */
78 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 100 MqttClientOptions options = new MqttClientOptions()
86 101 .setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID())
87 102 .setUsername(emqxProperties.getMqttUsername())
88 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 106 return MqttClient.create(vertx, options);
96 107 }
97 108  
  109 + /**
  110 + * 创建设备上行服务器
  111 + */
98 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 119 return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi, vertx, mqttClient, componentRegistry);
105 120 }
106 121  
  122 + /**
  123 + * 创建设备下行处理器
  124 + */
107 125 @Bean(name = "emqxDeviceDownstreamHandler")
108 126 public IotDeviceDownstreamHandler deviceDownstreamHandler(MqttClient mqttClient) {
109 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 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 3 import jakarta.validation.constraints.NotBlank;
4 4 import jakarta.validation.constraints.NotEmpty;
5 5 import jakarta.validation.constraints.NotNull;
6 6 import lombok.Data;
7 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 15 @ConfigurationProperties(prefix = "yudao.iot.component.emqx")
13 16 @Data
14   -public class IotComponentEmqxProperties {
  17 +@Validated
  18 +public class IotNetComponentEmqxProperties {
15 19  
16 20 /**
17 21 * 是否启用 EMQX 组件
18 22 */
19 23 private Boolean enabled;
20 24  
21   - // TODO @haohao:一般中英文之间,加个空格哈,写作(注释)习惯。类似 MQTT 密码;
22 25 /**
23   - * 服务主机
  26 + * MQTT 服务主机
24 27 */
25 28 @NotBlank(message = "MQTT 服务器主机不能为空")
26 29 private String mqttHost;
  30 +
27 31 /**
28   - * 服务端口
  32 + * MQTT 服务端口
29 33 */
30 34 @NotNull(message = "MQTT 服务器端口不能为空")
31 35 private Integer mqttPort;
  36 +
32 37 /**
33   - * 服务用户名
  38 + * MQTT 服务用户名
34 39 */
35 40 @NotBlank(message = "MQTT 服务器用户名不能为空")
36 41 private String mqttUsername;
  42 +
37 43 /**
38   - * 服务密码
  44 + * MQTT 服务密码
39 45 */
40 46 @NotBlank(message = "MQTT 服务器密码不能为空")
41 47 private String mqttPassword;
  48 +
42 49 /**
43 50 * 是否启用 SSL
44 51 */
... ... @@ -57,4 +64,17 @@ public class IotComponentEmqxProperties {
57 64 @NotNull(message = "认证端口不能为空")
58 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 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 4 import cn.hutool.json.JSONObject;
5 5 import cn.hutool.json.JSONUtil;
6 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 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 12 import io.netty.handler.codec.mqtt.MqttQoS;
10 13 import io.vertx.core.buffer.Buffer;
11 14 import io.vertx.mqtt.MqttClient;
12 15 import lombok.extern.slf4j.Slf4j;
13 16  
14   -import java.util.Map;
15   -
16 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 22 * @author 芋道源码
22 23 */
23 24 @Slf4j
24 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 30 private final MqttClient mqttClient;
41 31  
42 32 /**
43 33 * 构造函数
44 34 *
45   - * @param mqttClient MQTT客户端
  35 + * @param mqttClient MQTT 客户端
46 36 */
47 37 public IotDeviceDownstreamHandlerImpl(MqttClient mqttClient) {
48 38 this.mqttClient = mqttClient;
... ... @@ -60,12 +50,17 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
60 50  
61 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 65 log.info("[invokeService][调用设备服务成功][requestId: {}][topic: {}]", requestId, topic);
71 66 return CommonResult.success(true);
... ... @@ -77,13 +72,15 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
77 72  
78 73 @Override
79 74 public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
  75 + // 暂未实现,返回成功
80 76 return CommonResult.success(true);
81 77 }
82 78  
83 79 @Override
84 80 public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO reqDTO) {
85   - // 验证参数
86 81 log.info("[setProperty][开始设置设备属性][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
  82 +
  83 + // 验证参数
87 84 if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null) {
88 85 log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
89 86 return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg());
... ... @@ -91,12 +88,15 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
91 88  
92 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 101 log.info("[setProperty][设置设备属性成功][requestId: {}][topic: {}]", requestId, topic);
102 102 return CommonResult.success(true);
... ... @@ -108,54 +108,21 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
108 108  
109 109 @Override
110 110 public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
  111 + // 暂未实现,返回成功
111 112 return CommonResult.success(true);
112 113 }
113 114  
114 115 @Override
115 116 public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
  117 + // 暂未实现,返回成功
116 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 122 * 发布 MQTT 消息
  123 + *
  124 + * @param topic 主题
  125 + * @param payload 消息内容
159 126 */
160 127 private void publishMessage(String topic, JSONObject payload) {
161 128 mqttClient.publish(
... ... @@ -166,13 +133,4 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
166 133 false);
167 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 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 3 import cn.hutool.core.util.ArrayUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 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 11 import io.netty.handler.codec.mqtt.MqttQoS;
12 12 import io.vertx.core.Future;
13 13 import io.vertx.core.Vertx;
... ... @@ -21,7 +21,7 @@ import java.util.concurrent.CompletableFuture;
21 21 import java.util.concurrent.TimeUnit;
22 22  
23 23 /**
24   - * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
  24 + * IoT 设备上行服务端,接收来自 device 设备的请求,转发给 server 服务器
25 25 * <p>
26 26 * 协议:HTTP、MQTT
27 27 *
... ... @@ -30,15 +30,6 @@ import java.util.concurrent.TimeUnit;
30 30 @Slf4j
31 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 34 * 默认 QoS 级别
44 35 */
... ... @@ -47,20 +38,20 @@ public class IotDeviceUpstreamServer {
47 38 private final Vertx vertx;
48 39 private final HttpServer server;
49 40 private final MqttClient client;
50   - private final IotComponentEmqxProperties emqxProperties;
  41 + private final IotNetComponentEmqxProperties emqxProperties;
51 42 private final IotDeviceMqttMessageHandler mqttMessageHandler;
52   - private final IotComponentRegistry componentRegistry;
  43 + private final IotNetComponentRegistry componentRegistry;
53 44  
54 45 /**
55 46 * 服务运行状态标志
56 47 */
57 48 private volatile boolean isRunning = false;
58 49  
59   - public IotDeviceUpstreamServer(IotComponentEmqxProperties emqxProperties,
  50 + public IotDeviceUpstreamServer(IotNetComponentEmqxProperties emqxProperties,
60 51 IotDeviceUpstreamApi deviceUpstreamApi,
61 52 Vertx vertx,
62 53 MqttClient client,
63   - IotComponentRegistry componentRegistry) {
  54 + IotNetComponentRegistry componentRegistry) {
64 55 this.vertx = vertx;
65 56 this.emqxProperties = emqxProperties;
66 57 this.client = client;
... ... @@ -70,8 +61,7 @@ public class IotDeviceUpstreamServer {
70 61 Router router = Router.router(vertx);
71 62 router.route().handler(BodyHandler.create()); // 处理 Body
72 63 router.post(IotDeviceAuthVertxHandler.PATH)
73   - // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀?
74   - // 回复:MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式
  64 + // MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式
75 65 .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
76 66 // 添加 Webhook 处理器,用于处理设备连接和断开连接事件
77 67 router.post(IotDeviceWebhookVertxHandler.PATH)
... ... @@ -91,15 +81,20 @@ public class IotDeviceUpstreamServer {
91 81 }
92 82 log.info("[start][开始启动服务]");
93 83  
94   - // 检查authPort是否为null
  84 + // 检查 authPort 是否为 null
95 85 Integer authPort = emqxProperties.getAuthPort();
96 86 if (authPort == null) {
97   - log.warn("[start][authPort为null,使用默认端口8080]");
  87 + log.warn("[start][authPort 为 null,使用默认端口 8080]");
98 88 authPort = 8080; // 默认端口
99 89 }
100 90  
  91 + // 获取连接超时时间
  92 + int connectionTimeoutMs = emqxProperties.getConnectionTimeoutMs() != null
  93 + ? emqxProperties.getConnectionTimeoutMs()
  94 + : 10000;
  95 +
101 96 // 1. 启动 HTTP 服务器
102   - final Integer finalAuthPort = authPort; // 为lambda表达式创建final变量
  97 + final Integer finalAuthPort = authPort; // 为 lambda 表达式创建 final 变量
103 98 CompletableFuture<Void> httpFuture = server.listen(finalAuthPort)
104 99 .toCompletionStage()
105 100 .toCompletableFuture()
... ... @@ -115,13 +110,13 @@ public class IotDeviceUpstreamServer {
115 110 log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
116 111 reconnectWithDelay();
117 112 });
118   - // 2. 设置 MQTT 消息处理器
  113 + // 2.2 设置 MQTT 消息处理器
119 114 setupMessageHandler();
120 115 });
121 116  
122 117 // 3. 等待所有服务启动完成
123 118 CompletableFuture.allOf(httpFuture, mqttFuture)
124   - .orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
  119 + .orTimeout(connectionTimeoutMs, TimeUnit.MILLISECONDS)
125 120 .whenComplete((result, error) -> {
126 121 if (error != null) {
127 122 log.error("[start][服务启动失败]", error);
... ... @@ -149,7 +144,12 @@ public class IotDeviceUpstreamServer {
149 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 153 log.info("[reconnectWithDelay][开始重新连接 MQTT]");
154 154 connectMqtt();
155 155 });
... ... @@ -158,14 +158,14 @@ public class IotDeviceUpstreamServer {
158 158 /**
159 159 * 连接 MQTT Broker 并订阅主题
160 160 *
161   - * @return 连接结果的Future
  161 + * @return 连接结果的 Future
162 162 */
163 163 private Future<Void> connectMqtt() {
164 164 // 检查必要的 MQTT 配置
165 165 String host = emqxProperties.getMqttHost();
166 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 169 log.error(msg);
170 170 return Future.failedFuture(new IllegalStateException(msg));
171 171 }
... ... @@ -177,11 +177,11 @@ public class IotDeviceUpstreamServer {
177 177 final Integer finalPort = port;
178 178 return client.connect(finalPort, host)
179 179 .compose(connAck -> {
180   - log.info("[connectMqtt][MQTT客户端连接成功]");
  180 + log.info("[connectMqtt][MQTT 客户端连接成功]");
181 181 return subscribeToTopics();
182 182 })
183 183 .recover(error -> {
184   - log.error("[connectMqtt][连接MQTT Broker失败:]", error);
  184 + log.error("[connectMqtt][连接 MQTT Broker 失败:]", error);
185 185 reconnectWithDelay();
186 186 return Future.failedFuture(error);
187 187 });
... ... @@ -198,62 +198,67 @@ public class IotDeviceUpstreamServer {
198 198 log.warn("[subscribeToTopics][未配置 MQTT 主题或为 null,使用默认主题]");
199 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 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 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 211 .<Void>map(ack -> {
211   - log.info("[subscribeToTopics][成功订阅主题: {}]", trimmedTopic);
  212 + log.info("[subscribeToTopics][订阅主题成功: {}]", topic);
212 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 226 public void stop() {
226 227 if (!isRunning) {
227   - log.warn("[stop][服务未运行,无需停止]");
  228 + log.warn("[stop][服务已经停止,无需再次停止]");
228 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 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 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 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 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
5 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 7 import io.vertx.core.Handler;
8 8 import io.vertx.core.json.JsonObject;
9 9 import io.vertx.ext.web.RoutingContext;
... ... @@ -47,17 +47,17 @@ public class IotDeviceAuthVertxHandler implements Handler&lt;RoutingContext&gt; {
47 47 CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);
48 48 if (authResult.getCode() != 0 || !authResult.getData()) {
49 49 // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求
50   - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
  50 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
51 51 return;
52 52 }
53 53  
54 54 // 响应结果
55 55 // 注意:这里必须返回 {"result": "allow"} 格式,以符合 EMQX 认证插件的要求
56   - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "allow"));
  56 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "allow"));
57 57 } catch (Exception e) {
58 58 log.error("[handle][EMQX 认证异常]", e);
59 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 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.hutool.json.JSONObject;
... ... @@ -7,8 +7,9 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 7 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
8 8 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
9 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 13 import io.netty.handler.codec.mqtt.MqttQoS;
13 14 import io.vertx.core.buffer.Buffer;
14 15 import io.vertx.mqtt.MqttClient;
... ... @@ -23,25 +24,12 @@ import java.util.Map;
23 24 /**
24 25 * IoT 设备 MQTT 消息处理器
25 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 30 @Slf4j
29 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 33 private static final String PROPERTY_METHOD = "thing.event.property.post";
46 34 private static final String EVENT_METHOD_PREFIX = "thing.event.";
47 35 private static final String EVENT_METHOD_SUFFIX = ".post";
... ... @@ -83,20 +71,21 @@ public class IotDeviceMqttMessageHandler {
83 71 */
84 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 75 log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
88 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 81 log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic);
94 82 handlePropertyPost(topic, payload);
95 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 89 log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic);
101 90 handleEventPost(topic, payload);
102 91 return;
... ... @@ -212,7 +201,7 @@ public class IotDeviceMqttMessageHandler {
212 201 * @param customData 自定义数据,可为 null
213 202 */
214 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 207 IotStandardResponse response = IotStandardResponse.success(
... ... @@ -236,7 +225,7 @@ public class IotDeviceMqttMessageHandler {
236 225 private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, String[] topicParts) {
237 226 IotDevicePropertyReportReqDTO reportReqDTO = new IotDevicePropertyReportReqDTO();
238 227 reportReqDTO.setRequestId(jsonObject.getStr("id"));
239   - reportReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
  228 + reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
240 229 reportReqDTO.setReportTime(LocalDateTime.now());
241 230 reportReqDTO.setProductKey(topicParts[2]);
242 231 reportReqDTO.setDeviceName(topicParts[3]);
... ... @@ -276,7 +265,7 @@ public class IotDeviceMqttMessageHandler {
276 265 private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) {
277 266 IotDeviceEventReportReqDTO reportReqDTO = new IotDeviceEventReportReqDTO();
278 267 reportReqDTO.setRequestId(jsonObject.getStr("id"));
279   - reportReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
  268 + reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
280 269 reportReqDTO.setReportTime(LocalDateTime.now());
281 270 reportReqDTO.setProductKey(topicParts[2]);
282 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 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 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 7 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
  8 +import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
9 9 import io.vertx.core.Handler;
10 10 import io.vertx.core.json.JsonObject;
11 11 import io.vertx.ext.web.RoutingContext;
... ... @@ -57,11 +57,11 @@ public class IotDeviceWebhookVertxHandler implements Handler&lt;RoutingContext&gt; {
57 57  
58 58 // 返回成功响应
59 59 // 注意:这里必须返回 {"result": "success"} 格式,以符合 EMQX Webhook 插件的要求
60   - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "success"));
  60 + IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "success"));
61 61 } catch (Exception e) {
62 62 log.error("[handle][处理 Webhook 事件异常]", e);
63 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 87 updateReqDTO.setProductKey(parts[1]);
88 88 updateReqDTO.setDeviceName(parts[0]);
89 89 updateReqDTO.setState(IotDeviceStateEnum.ONLINE.getState());
90   - updateReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
  90 + updateReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
91 91 updateReqDTO.setReportTime(LocalDateTime.now());
92 92 CommonResult<Boolean> result = deviceUpstreamApi.updateDeviceState(updateReqDTO);
93 93 if (result.getCode() != 0 || !result.getData()) {
... ... @@ -120,7 +120,7 @@ public class IotDeviceWebhookVertxHandler implements Handler&lt;RoutingContext&gt; {
120 120 offlineReqDTO.setProductKey(parts[1]);
121 121 offlineReqDTO.setDeviceName(parts[0]);
122 122 offlineReqDTO.setState(IotDeviceStateEnum.OFFLINE.getState());
123   - offlineReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
  123 + offlineReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
124 124 offlineReqDTO.setReportTime(LocalDateTime.now());
125 125 CommonResult<Boolean> offlineResult = deviceUpstreamApi.updateDeviceState(offlineReqDTO);
126 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 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 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 5 <parent>
6   - <artifactId>yudao-module-iot-components</artifactId>
  6 + <artifactId>yudao-module-iot-net-components</artifactId>
7 7 <groupId>cn.iocoder.boot</groupId>
8 8 <version>${revision}</version>
9 9 </parent>
10 10 <modelVersion>4.0.0</modelVersion>
11   - <artifactId>yudao-module-iot-component-http</artifactId>
  11 + <artifactId>yudao-module-iot-net-component-http</artifactId>
12 12 <packaging>jar</packaging>
13 13  
14 14 <name>${project.artifactId}</name>
15 15 <description>
16   - 物联网组件 HTTP 模块
  16 + 物联网网络组件 HTTP 模块
17 17 </description>
18 18  
19 19 <dependencies>
20 20 <dependency>
21 21 <groupId>cn.iocoder.boot</groupId>
22   - <artifactId>yudao-module-iot-component-core</artifactId>
  22 + <artifactId>yudao-module-iot-net-component-core</artifactId>
23 23 <version>${revision}</version>
24 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 3 import lombok.Data;
4 4 import org.springframework.boot.context.properties.ConfigurationProperties;
5 5 import org.springframework.validation.annotation.Validated;
6 6  
7 7 /**
8   - * IoT HTTP 组件配置属性
  8 + * IoT HTTP 网络组件配置属性
  9 + *
  10 + * @author haohao
9 11 */
10 12 @ConfigurationProperties(prefix = "yudao.iot.component.http")
11 13 @Validated
12 14 @Data
13   -public class IotComponentHttpProperties {
  15 +public class IotNetComponentHttpProperties {
14 16  
15 17 /**
16   - * 是否启用
  18 + * 是否启用 HTTP 组件
17 19 */
18 20 private Boolean enabled;
19 21  
... ... @@ -22,4 +24,10 @@ public class IotComponentHttpProperties {
22 24 */
23 25 private Integer serverPort;
24 26  
  27 + /**
  28 + * 连接超时时间(毫秒)
  29 + * <p>
  30 + * 默认值:10000 毫秒
  31 + */
  32 + private Integer connectionTimeoutMs = 10000;
25 33 }
26 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 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 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 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 12 * <p>
12 13 * 但是:由于设备通过 HTTP 短链接接入,导致其实无法下行指导给 device 设备,所以基本都是直接返回失败!!!
13   - * 类似 MQTT、WebSocket、TCP 插件,是可以实现下行指令的。
  14 + * 类似 MQTT、WebSocket、TCP 网络组件,是可以实现下行指令的。
14 15 *
15 16 * @author 芋道源码
16 17 */
  18 +@Slf4j
17 19 public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
18 20  
  21 + /**
  22 + * 不支持的错误消息
  23 + */
  24 + private static final String NOT_SUPPORTED_MSG = "HTTP 不支持设备下行通信";
  25 +
19 26 @Override
20 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 31 @Override
25 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 36 @Override
30 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 41 @Override
35 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 46 @Override
40 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 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 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 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