Commit 44b835bd4a488edae25ef51d7bf3d2c1fa92c058

Authored by 安浩浩
1 parent 2954445d

【功能新增】IoT: 新增网络组件服务器,支持设备上行和下行处理,添加健康检查接口,配置心跳任务,更新相关依赖和配置文件。

Showing 13 changed files with 959 additions and 29 deletions
yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -123,16 +123,16 @@ @@ -123,16 +123,16 @@
123 <!-- </dependency>--> 123 <!-- </dependency>-->
124 124
125 <!-- IoT 网络组件:接收来自设备的上行数据 --> 125 <!-- IoT 网络组件:接收来自设备的上行数据 -->
126 - <dependency>  
127 - <groupId>cn.iocoder.boot</groupId>  
128 - <artifactId>yudao-module-iot-net-component-http</artifactId>  
129 - <version>${revision}</version>  
130 - </dependency>  
131 - <dependency>  
132 - <groupId>cn.iocoder.boot</groupId>  
133 - <artifactId>yudao-module-iot-net-component-emqx</artifactId>  
134 - <version>${revision}</version>  
135 - </dependency> 126 + <!-- <dependency>-->
  127 + <!-- <groupId>cn.iocoder.boot</groupId>-->
  128 + <!-- <artifactId>yudao-module-iot-net-component-http</artifactId>-->
  129 + <!-- <version>${revision}</version>-->
  130 + <!-- </dependency>-->
  131 + <!-- <dependency>-->
  132 + <!-- <groupId>cn.iocoder.boot</groupId>-->
  133 + <!-- <artifactId>yudao-module-iot-net-component-emqx</artifactId>-->
  134 + <!-- <version>${revision}</version>-->
  135 + <!-- </dependency>-->
136 136
137 <!-- 脚本相关 --> 137 <!-- 脚本相关 -->
138 <dependency> 138 <dependency>
yudao-module-iot/yudao-module-iot-net-components/pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
3 - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
4 - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 <parent> 4 <parent>
6 <artifactId>yudao-module-iot</artifactId> 5 <artifactId>yudao-module-iot</artifactId>
7 <groupId>cn.iocoder.boot</groupId> 6 <groupId>cn.iocoder.boot</groupId>
@@ -21,6 +20,7 @@ @@ -21,6 +20,7 @@
21 <module>yudao-module-iot-net-component-core</module> 20 <module>yudao-module-iot-net-component-core</module>
22 <module>yudao-module-iot-net-component-http</module> 21 <module>yudao-module-iot-net-component-http</module>
23 <module>yudao-module-iot-net-component-emqx</module> 22 <module>yudao-module-iot-net-component-emqx</module>
  23 + <module>yudao-module-iot-net-component-server</module>
24 </modules> 24 </modules>
25 25
26 </project> 26 </project>
27 \ No newline at end of file 27 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonProperties.java
@@ -31,26 +31,28 @@ public class IotNetComponentCommonProperties { @@ -31,26 +31,28 @@ public class IotNetComponentCommonProperties {
31 /** 31 /**
32 * 网络组件消息转发配置 32 * 网络组件消息转发配置
33 */ 33 */
34 - private ForwardMessage forwardMessage = new ForwardMessage(); 34 + // private ForwardMessage forwardMessage = new ForwardMessage();
35 35
36 /** 36 /**
37 * 消息转发配置 37 * 消息转发配置
38 */ 38 */
39 - @Data  
40 - public static class ForwardMessage {  
41 -  
42 - /**  
43 - * 是否转发所有设备消息到 EMQX  
44 - * <p>  
45 - * 默认为 true 开启  
46 - */  
47 - private boolean forwardAllDeviceMessageToEmqx = true; 39 + /*
  40 + * @Data
  41 + * public static class ForwardMessage {
  42 + *
  43 + * /**
  44 + * 是否转发所有设备消息到 EMQX
  45 + * <p>
  46 + * 默认为 true 开启
  47 + */
  48 + // private boolean forwardAllDeviceMessageToEmqx = true;
48 49
49 - /**  
50 - * 是否转发所有设备消息到 HTTP  
51 - * <p>  
52 - * 默认为 false 关闭  
53 - */  
54 - private boolean forwardAllDeviceMessageToHttp = false;  
55 - } 50 + /**
  51 + * 是否转发所有设备消息到 HTTP
  52 + * <p>
  53 + * 默认为 false 关闭
  54 + */
  55 + // private boolean forwardAllDeviceMessageToHttp = false;
  56 + // }
  57 +
56 } 58 }
57 \ No newline at end of file 59 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4 + <modelVersion>4.0.0</modelVersion>
  5 + <parent>
  6 + <groupId>cn.iocoder.boot</groupId>
  7 + <artifactId>yudao-module-iot-net-components</artifactId>
  8 + <version>${revision}</version>
  9 + </parent>
  10 +
  11 + <artifactId>yudao-module-iot-net-component-server</artifactId>
  12 + <packaging>jar</packaging>
  13 +
  14 + <name>${project.artifactId}</name>
  15 + <description>
  16 + IoT 网络组件的独立运行服务,用于聚合和启动多个网络组件实例。
  17 + </description>
  18 +
  19 + <dependencies>
  20 + <!-- Spring Boot 核心 -->
  21 + <dependency>
  22 + <groupId>org.springframework.boot</groupId>
  23 + <artifactId>spring-boot-starter</artifactId>
  24 + </dependency>
  25 +
  26 + <!-- Web 相关 -->
  27 + <dependency>
  28 + <groupId>org.springframework.boot</groupId>
  29 + <artifactId>spring-boot-starter-web</artifactId>
  30 + </dependency>
  31 +
  32 + <!-- Net Component 核心 -->
  33 + <dependency>
  34 + <groupId>cn.iocoder.boot</groupId>
  35 + <artifactId>yudao-module-iot-net-component-core</artifactId>
  36 + <version>${revision}</version>
  37 + </dependency>
  38 +
  39 + <!-- Net Component HTTP -->
  40 + <dependency>
  41 + <groupId>cn.iocoder.boot</groupId>
  42 + <artifactId>yudao-module-iot-net-component-http</artifactId>
  43 + <version>${revision}</version>
  44 + </dependency>
  45 +
  46 + <!-- Net Component EMQX -->
  47 + <dependency>
  48 + <groupId>cn.iocoder.boot</groupId>
  49 + <artifactId>yudao-module-iot-net-component-emqx</artifactId>
  50 + <version>${revision}</version>
  51 + </dependency>
  52 +
  53 + </dependencies>
  54 +
  55 + <build>
  56 + <!-- 设置构建的 jar 包名 -->
  57 + <finalName>${project.artifactId}</finalName>
  58 + <plugins>
  59 + <!-- 打包 -->
  60 + <plugin>
  61 + <groupId>org.springframework.boot</groupId>
  62 + <artifactId>spring-boot-maven-plugin</artifactId>
  63 + <version>${spring.boot.version}</version> <!-- 需要确认父 POM 中有定义 -->
  64 + <executions>
  65 + <execution>
  66 + <goals>
  67 + <goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
  68 + </goals>
  69 + </execution>
  70 + </executions>
  71 + </plugin>
  72 + </plugins>
  73 + </build>
  74 +
  75 +</project>
0 \ No newline at end of file 76 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/NetComponentServerApplication.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.server;
  2 +
  3 +import org.springframework.boot.SpringApplication;
  4 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  5 +
  6 +/**
  7 + * IoT 网络组件聚合启动服务
  8 + *
  9 + * @author haohao
  10 + */
  11 +@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}.module.iot.net.component"})
  12 +public class NetComponentServerApplication {
  13 +
  14 + public static void main(String[] args) {
  15 + SpringApplication.run(NetComponentServerApplication.class, args);
  16 + }
  17 +
  18 +}
0 \ No newline at end of file 19 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/config/IotNetComponentServerConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.server.config;
  2 +
  3 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  4 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
  5 +import cn.iocoder.yudao.module.iot.net.component.server.downstream.IotComponentDownstreamHandlerImpl;
  6 +import cn.iocoder.yudao.module.iot.net.component.server.downstream.IotComponentDownstreamServer;
  7 +import cn.iocoder.yudao.module.iot.net.component.server.heartbeat.IotComponentHeartbeatJob;
  8 +import cn.iocoder.yudao.module.iot.net.component.server.upstream.IotComponentUpstreamClient;
  9 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  10 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  11 +import org.springframework.boot.web.client.RestTemplateBuilder;
  12 +import org.springframework.context.annotation.Bean;
  13 +import org.springframework.context.annotation.Configuration;
  14 +import org.springframework.context.annotation.Primary;
  15 +import org.springframework.scheduling.annotation.EnableScheduling;
  16 +import org.springframework.web.client.RestTemplate;
  17 +
  18 +/**
  19 + * IoT 网络组件服务器配置类
  20 + *
  21 + * @author haohao
  22 + */
  23 +@Configuration
  24 +@EnableConfigurationProperties(IotNetComponentServerProperties.class)
  25 +@EnableScheduling
  26 +public class IotNetComponentServerConfiguration {
  27 +
  28 + /**
  29 + * 配置 RestTemplate
  30 + *
  31 + * @param properties 配置
  32 + * @return RestTemplate
  33 + */
  34 + @Bean
  35 + public RestTemplate restTemplate(IotNetComponentServerProperties properties) {
  36 + return new RestTemplateBuilder()
  37 + .connectTimeout(properties.getUpstreamConnectTimeout())
  38 + .readTimeout(properties.getUpstreamReadTimeout())
  39 + .build();
  40 + }
  41 +
  42 + /**
  43 + * 配置设备上行客户端
  44 + *
  45 + * @param properties 配置
  46 + * @param restTemplate RestTemplate
  47 + * @return 上行客户端
  48 + */
  49 + @Bean
  50 + @Primary
  51 + public IotDeviceUpstreamApi deviceUpstreamApi(IotNetComponentServerProperties properties,
  52 + RestTemplate restTemplate) {
  53 + return new IotComponentUpstreamClient(properties, restTemplate);
  54 + }
  55 +
  56 + /**
  57 + * 配置设备下行处理器
  58 + *
  59 + * @return 下行处理器
  60 + */
  61 + @Bean
  62 + @Primary
  63 + public IotDeviceDownstreamHandler deviceDownstreamHandler() {
  64 + return new IotComponentDownstreamHandlerImpl();
  65 + }
  66 +
  67 + /**
  68 + * 配置下行服务器
  69 + *
  70 + * @param properties 配置
  71 + * @param downstreamHandler 下行处理器
  72 + * @return 下行服务器
  73 + */
  74 + @Bean(initMethod = "start", destroyMethod = "stop")
  75 + public IotComponentDownstreamServer deviceDownstreamServer(IotNetComponentServerProperties properties,
  76 + @org.springframework.beans.factory.annotation.Qualifier("deviceDownstreamHandler") IotDeviceDownstreamHandler downstreamHandler) {
  77 + return new IotComponentDownstreamServer(properties, downstreamHandler);
  78 + }
  79 +
  80 + /**
  81 + * 配置心跳任务
  82 + *
  83 + * @param deviceUpstreamApi 上行接口
  84 + * @param downstreamServer 下行服务器
  85 + * @param properties 配置
  86 + * @return 心跳任务
  87 + */
  88 + @Bean(initMethod = "init", destroyMethod = "stop")
  89 + public IotComponentHeartbeatJob heartbeatJob(IotDeviceUpstreamApi deviceUpstreamApi,
  90 + IotComponentDownstreamServer downstreamServer,
  91 + IotNetComponentServerProperties properties) {
  92 + return new IotComponentHeartbeatJob(deviceUpstreamApi, downstreamServer, properties);
  93 + }
  94 +
  95 + /**
  96 + * 配置默认的设备上行客户端,避免在独立运行模式下的循环依赖问题
  97 + *
  98 + * @return 设备上行客户端
  99 + */
  100 + @Bean
  101 + @ConditionalOnMissingBean(name = "serverDeviceUpstreamClient")
  102 + public Object serverDeviceUpstreamClient() {
  103 + // 返回一个空对象,避免找不到类的问题
  104 + return new Object();
  105 + }
  106 +
  107 + /**
  108 + * 配置默认的组件实例注册客户端
  109 + *
  110 + * @return 插件实例注册客户端
  111 + */
  112 + @Bean
  113 + @ConditionalOnMissingBean(name = "serverPluginInstanceRegistryClient")
  114 + public Object serverPluginInstanceRegistryClient() {
  115 + // 返回一个空对象,避免找不到类的问题
  116 + return new Object();
  117 + }
  118 +}
0 \ No newline at end of file 119 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/config/IotNetComponentServerProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.server.config;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +import java.time.Duration;
  8 +
  9 +/**
  10 + * IoT 网络组件服务配置属性
  11 + *
  12 + * @author haohao
  13 + */
  14 +@ConfigurationProperties(prefix = "yudao.iot.component.server")
  15 +@Validated
  16 +@Data
  17 +public class IotNetComponentServerProperties {
  18 +
  19 + /**
  20 + * 上行 URL,用于向主应用程序上报数据
  21 + * <p>
  22 + * 默认:http://127.0.0.1:48080
  23 + */
  24 + private String upstreamUrl = "http://127.0.0.1:48080";
  25 +
  26 + /**
  27 + * 上行连接超时时间
  28 + */
  29 + private Duration upstreamConnectTimeout = Duration.ofSeconds(30);
  30 +
  31 + /**
  32 + * 上行读取超时时间
  33 + */
  34 + private Duration upstreamReadTimeout = Duration.ofSeconds(30);
  35 +
  36 + /**
  37 + * 下行服务端口,用于接收主应用程序的请求
  38 + * <p>
  39 + * 默认:18888
  40 + */
  41 + private Integer downstreamPort = 18888;
  42 +
  43 + /**
  44 + * 组件服务器唯一标识
  45 + * <p>
  46 + * 默认:yudao-module-iot-net-component-server
  47 + */
  48 + private String serverKey = "yudao-module-iot-net-component-server";
  49 +
  50 + /**
  51 + * 心跳发送频率,单位:毫秒
  52 + * <p>
  53 + * 默认:30 秒
  54 + */
  55 + private Long heartbeatInterval = 30000L;
  56 +}
0 \ No newline at end of file 57 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/controller/HealthController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.server.controller;
  2 +
  3 +import org.springframework.web.bind.annotation.GetMapping;
  4 +import org.springframework.web.bind.annotation.RequestMapping;
  5 +import org.springframework.web.bind.annotation.RestController;
  6 +
  7 +import java.util.HashMap;
  8 +import java.util.Map;
  9 +
  10 +/**
  11 + * 健康检查接口
  12 + *
  13 + * @author haohao
  14 + */
  15 +@RestController
  16 +@RequestMapping("/health")
  17 +public class HealthController {
  18 +
  19 + /**
  20 + * 健康检查接口
  21 + *
  22 + * @return 返回服务状态信息
  23 + */
  24 + @GetMapping("/status")
  25 + public Map<String, Object> status() {
  26 + Map<String, Object> result = new HashMap<>();
  27 + result.put("status", "UP");
  28 + result.put("message", "IoT 网络组件服务运行正常");
  29 + result.put("timestamp", System.currentTimeMillis());
  30 + return result;
  31 + }
  32 +}
0 \ No newline at end of file 33 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/downstream/IotComponentDownstreamHandlerImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.server.downstream;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
  5 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +
  8 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
  9 +
  10 +/**
  11 + * 网络组件下行处理器实现
  12 + * <p>
  13 + * 处理来自主程序的设备控制指令
  14 + *
  15 + * @author haohao
  16 + */
  17 +@Slf4j
  18 +public class IotComponentDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
  19 +
  20 + @Override
  21 + public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
  22 + log.info("[invokeDeviceService][收到服务调用请求:{}]", invokeReqDTO);
  23 + // 在这里处理服务调用,可以根据设备类型转发到对应的处理器
  24 + // 如 MQTT 设备、HTTP 设备等的具体实现
  25 +
  26 + // 这里仅作为示例,实际应根据接入的组件进行转发
  27 + return CommonResult.success(true);
  28 + }
  29 +
  30 + @Override
  31 + public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
  32 + log.info("[getDeviceProperty][收到属性获取请求:{}]", getReqDTO);
  33 + // 在这里处理属性获取请求
  34 +
  35 + // 这里仅作为示例,实际应根据接入的组件进行转发
  36 + return CommonResult.success(true);
  37 + }
  38 +
  39 + @Override
  40 + public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
  41 + log.info("[setDeviceProperty][收到属性设置请求:{}]", setReqDTO);
  42 + // 在这里处理属性设置请求
  43 +
  44 + // 这里仅作为示例,实际应根据接入的组件进行转发
  45 + return CommonResult.success(true);
  46 + }
  47 +
  48 + @Override
  49 + public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
  50 + log.info("[setDeviceConfig][收到配置设置请求:{}]", setReqDTO);
  51 + // 在这里处理配置设置请求
  52 +
  53 + // 这里仅作为示例,实际应根据接入的组件进行转发
  54 + return CommonResult.success(true);
  55 + }
  56 +
  57 + @Override
  58 + public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
  59 + log.info("[upgradeDeviceOta][收到OTA升级请求:{}]", upgradeReqDTO);
  60 + // 在这里处理OTA升级请求
  61 +
  62 + // 这里仅作为示例,实际应根据接入的组件进行转发
  63 + return CommonResult.success(true);
  64 + }
  65 +}
0 \ No newline at end of file 66 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/downstream/IotComponentDownstreamServer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.server.downstream;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
  6 +import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
  7 +import cn.iocoder.yudao.module.iot.net.component.server.config.IotNetComponentServerProperties;
  8 +import io.vertx.core.Vertx;
  9 +import io.vertx.core.http.HttpServer;
  10 +import io.vertx.core.json.Json;
  11 +import io.vertx.core.json.JsonObject;
  12 +import io.vertx.ext.web.Router;
  13 +import io.vertx.ext.web.RoutingContext;
  14 +import io.vertx.ext.web.handler.BodyHandler;
  15 +import lombok.extern.slf4j.Slf4j;
  16 +
  17 +import java.util.List;
  18 +import java.util.Map;
  19 +
  20 +/**
  21 + * 组件下行服务器,接收来自主程序的控制指令
  22 + *
  23 + * @author haohao
  24 + */
  25 +@Slf4j
  26 +public class IotComponentDownstreamServer {
  27 +
  28 + public static final String SERVICE_INVOKE_PATH = "/sys/:productKey/:deviceName/thing/service/:identifier";
  29 + public static final String PROPERTY_SET_PATH = "/sys/:productKey/:deviceName/thing/service/property/set";
  30 + public static final String PROPERTY_GET_PATH = "/sys/:productKey/:deviceName/thing/service/property/get";
  31 + public static final String CONFIG_SET_PATH = "/sys/:productKey/:deviceName/thing/service/config/set";
  32 + public static final String OTA_UPGRADE_PATH = "/sys/:productKey/:deviceName/thing/service/ota/upgrade";
  33 +
  34 + private final Vertx vertx;
  35 + private final HttpServer server;
  36 + private final IotNetComponentServerProperties properties;
  37 + private final IotDeviceDownstreamHandler downstreamHandler;
  38 +
  39 + public IotComponentDownstreamServer(IotNetComponentServerProperties properties,
  40 + IotDeviceDownstreamHandler downstreamHandler) {
  41 + this.properties = properties;
  42 + this.downstreamHandler = downstreamHandler;
  43 + // 创建 Vertx 实例
  44 + this.vertx = Vertx.vertx();
  45 + // 创建 Router 实例
  46 + Router router = Router.router(vertx);
  47 + router.route().handler(BodyHandler.create()); // 处理 Body
  48 +
  49 + // 服务调用路由
  50 + router.post(SERVICE_INVOKE_PATH).handler(this::handleServiceInvoke);
  51 + // 属性设置路由
  52 + router.post(PROPERTY_SET_PATH).handler(this::handlePropertySet);
  53 + // 属性获取路由
  54 + router.post(PROPERTY_GET_PATH).handler(this::handlePropertyGet);
  55 + // 配置设置路由
  56 + router.post(CONFIG_SET_PATH).handler(this::handleConfigSet);
  57 + // OTA 升级路由
  58 + router.post(OTA_UPGRADE_PATH).handler(this::handleOtaUpgrade);
  59 +
  60 + // 创建 HttpServer 实例
  61 + this.server = vertx.createHttpServer().requestHandler(router);
  62 + }
  63 +
  64 + /**
  65 + * 启动服务器
  66 + */
  67 + public void start() {
  68 + log.info("[start][开始启动下行服务器]");
  69 + server.listen(properties.getDownstreamPort())
  70 + .toCompletionStage()
  71 + .toCompletableFuture()
  72 + .join();
  73 + log.info("[start][下行服务器启动完成,端口({})]", server.actualPort());
  74 + }
  75 +
  76 + /**
  77 + * 停止服务器
  78 + */
  79 + public void stop() {
  80 + log.info("[stop][开始关闭下行服务器]");
  81 + try {
  82 + // 关闭 HTTP 服务器
  83 + if (server != null) {
  84 + server.close()
  85 + .toCompletionStage()
  86 + .toCompletableFuture()
  87 + .join();
  88 + }
  89 +
  90 + // 关闭 Vertx 实例
  91 + if (vertx != null) {
  92 + vertx.close()
  93 + .toCompletionStage()
  94 + .toCompletableFuture()
  95 + .join();
  96 + }
  97 + log.info("[stop][下行服务器关闭完成]");
  98 + } catch (Exception e) {
  99 + log.error("[stop][下行服务器关闭异常]", e);
  100 + throw new RuntimeException(e);
  101 + }
  102 + }
  103 +
  104 + /**
  105 + * 获取服务器端口
  106 + *
  107 + * @return 端口号
  108 + */
  109 + public int getPort() {
  110 + return server.actualPort();
  111 + }
  112 +
  113 + /**
  114 + * 处理服务调用请求
  115 + */
  116 + private void handleServiceInvoke(RoutingContext ctx) {
  117 + try {
  118 + // 解析路径参数
  119 + String productKey = ctx.pathParam("productKey");
  120 + String deviceName = ctx.pathParam("deviceName");
  121 + String identifier = ctx.pathParam("identifier");
  122 +
  123 + // 解析请求体
  124 + JsonObject body = ctx.body().asJsonObject();
  125 + String requestId = body.getString("requestId", IdUtil.fastSimpleUUID());
  126 + Object params = body.getMap().get("params");
  127 +
  128 + // 创建请求对象
  129 + IotDeviceServiceInvokeReqDTO reqDTO = new IotDeviceServiceInvokeReqDTO();
  130 + reqDTO.setRequestId(requestId);
  131 + reqDTO.setProductKey(productKey);
  132 + reqDTO.setDeviceName(deviceName);
  133 + reqDTO.setIdentifier(identifier);
  134 + reqDTO.setParams((Map<String, Object>) params);
  135 +
  136 + // 调用处理器
  137 + CommonResult<Boolean> result = downstreamHandler.invokeDeviceService(reqDTO);
  138 +
  139 + // 响应结果
  140 + ctx.response()
  141 + .putHeader("Content-Type", "application/json")
  142 + .end(Json.encode(result));
  143 + } catch (Exception e) {
  144 + log.error("[handleServiceInvoke][处理服务调用请求失败]", e);
  145 + ctx.response()
  146 + .setStatusCode(500)
  147 + .putHeader("Content-Type", "application/json")
  148 + .end(Json.encode(CommonResult.error(500, "处理服务调用请求失败:" + e.getMessage())));
  149 + }
  150 + }
  151 +
  152 + /**
  153 + * 处理属性设置请求
  154 + */
  155 + private void handlePropertySet(RoutingContext ctx) {
  156 + try {
  157 + // 解析路径参数
  158 + String productKey = ctx.pathParam("productKey");
  159 + String deviceName = ctx.pathParam("deviceName");
  160 +
  161 + // 解析请求体
  162 + JsonObject body = ctx.body().asJsonObject();
  163 + String requestId = body.getString("requestId", IdUtil.fastSimpleUUID());
  164 + Object properties = body.getMap().get("properties");
  165 +
  166 + // 创建请求对象
  167 + IotDevicePropertySetReqDTO reqDTO = new IotDevicePropertySetReqDTO();
  168 + reqDTO.setRequestId(requestId);
  169 + reqDTO.setProductKey(productKey);
  170 + reqDTO.setDeviceName(deviceName);
  171 + reqDTO.setProperties((Map<String, Object>) properties);
  172 +
  173 + // 调用处理器
  174 + CommonResult<Boolean> result = downstreamHandler.setDeviceProperty(reqDTO);
  175 +
  176 + // 响应结果
  177 + ctx.response()
  178 + .putHeader("Content-Type", "application/json")
  179 + .end(Json.encode(result));
  180 + } catch (Exception e) {
  181 + log.error("[handlePropertySet][处理属性设置请求失败]", e);
  182 + ctx.response()
  183 + .setStatusCode(500)
  184 + .putHeader("Content-Type", "application/json")
  185 + .end(Json.encode(CommonResult.error(500, "处理属性设置请求失败:" + e.getMessage())));
  186 + }
  187 + }
  188 +
  189 + /**
  190 + * 处理属性获取请求
  191 + */
  192 + private void handlePropertyGet(RoutingContext ctx) {
  193 + try {
  194 + // 解析路径参数
  195 + String productKey = ctx.pathParam("productKey");
  196 + String deviceName = ctx.pathParam("deviceName");
  197 +
  198 + // 解析请求体
  199 + JsonObject body = ctx.body().asJsonObject();
  200 + String requestId = body.getString("requestId", IdUtil.fastSimpleUUID());
  201 + Object identifiers = body.getMap().get("identifiers");
  202 +
  203 + // 创建请求对象
  204 + IotDevicePropertyGetReqDTO reqDTO = new IotDevicePropertyGetReqDTO();
  205 + reqDTO.setRequestId(requestId);
  206 + reqDTO.setProductKey(productKey);
  207 + reqDTO.setDeviceName(deviceName);
  208 + reqDTO.setIdentifiers((List<String>) identifiers);
  209 +
  210 + // 调用处理器
  211 + CommonResult<Boolean> result = downstreamHandler.getDeviceProperty(reqDTO);
  212 +
  213 + // 响应结果
  214 + ctx.response()
  215 + .putHeader("Content-Type", "application/json")
  216 + .end(Json.encode(result));
  217 + } catch (Exception e) {
  218 + log.error("[handlePropertyGet][处理属性获取请求失败]", e);
  219 + ctx.response()
  220 + .setStatusCode(500)
  221 + .putHeader("Content-Type", "application/json")
  222 + .end(Json.encode(CommonResult.error(500, "处理属性获取请求失败:" + e.getMessage())));
  223 + }
  224 + }
  225 +
  226 + /**
  227 + * 处理配置设置请求
  228 + */
  229 + private void handleConfigSet(RoutingContext ctx) {
  230 + try {
  231 + // 解析路径参数
  232 + String productKey = ctx.pathParam("productKey");
  233 + String deviceName = ctx.pathParam("deviceName");
  234 +
  235 + // 解析请求体
  236 + JsonObject body = ctx.body().asJsonObject();
  237 + String requestId = body.getString("requestId", IdUtil.fastSimpleUUID());
  238 + Object config = body.getMap().get("config");
  239 +
  240 + // 创建请求对象
  241 + IotDeviceConfigSetReqDTO reqDTO = new IotDeviceConfigSetReqDTO();
  242 + reqDTO.setRequestId(requestId);
  243 + reqDTO.setProductKey(productKey);
  244 + reqDTO.setDeviceName(deviceName);
  245 + reqDTO.setConfig((Map<String, Object>) config);
  246 +
  247 + // 调用处理器
  248 + CommonResult<Boolean> result = downstreamHandler.setDeviceConfig(reqDTO);
  249 +
  250 + // 响应结果
  251 + ctx.response()
  252 + .putHeader("Content-Type", "application/json")
  253 + .end(Json.encode(result));
  254 + } catch (Exception e) {
  255 + log.error("[handleConfigSet][处理配置设置请求失败]", e);
  256 + ctx.response()
  257 + .setStatusCode(500)
  258 + .putHeader("Content-Type", "application/json")
  259 + .end(Json.encode(CommonResult.error(500, "处理配置设置请求失败:" + e.getMessage())));
  260 + }
  261 + }
  262 +
  263 + /**
  264 + * 处理 OTA 升级请求
  265 + */
  266 + private void handleOtaUpgrade(RoutingContext ctx) {
  267 + try {
  268 + // 解析路径参数
  269 + String productKey = ctx.pathParam("productKey");
  270 + String deviceName = ctx.pathParam("deviceName");
  271 +
  272 + // 解析请求体
  273 + JsonObject body = ctx.body().asJsonObject();
  274 + String requestId = body.getString("requestId", IdUtil.fastSimpleUUID());
  275 + Object data = body.getMap().get("data");
  276 +
  277 + // 创建请求对象
  278 + IotDeviceOtaUpgradeReqDTO reqDTO = new IotDeviceOtaUpgradeReqDTO();
  279 + reqDTO.setRequestId(requestId);
  280 + reqDTO.setProductKey(productKey);
  281 + reqDTO.setDeviceName(deviceName);
  282 +
  283 + // 数据采用 IotDeviceOtaUpgradeReqDTO.build 方法转换
  284 + if (data instanceof Map) {
  285 + IotDeviceOtaUpgradeReqDTO builtDTO = IotDeviceOtaUpgradeReqDTO.build((Map<?, ?>) data);
  286 + reqDTO.setFirmwareId(builtDTO.getFirmwareId());
  287 + reqDTO.setVersion(builtDTO.getVersion());
  288 + reqDTO.setSignMethod(builtDTO.getSignMethod());
  289 + reqDTO.setFileSign(builtDTO.getFileSign());
  290 + reqDTO.setFileSize(builtDTO.getFileSize());
  291 + reqDTO.setFileUrl(builtDTO.getFileUrl());
  292 + reqDTO.setInformation(builtDTO.getInformation());
  293 + }
  294 +
  295 + // 调用处理器
  296 + CommonResult<Boolean> result = downstreamHandler.upgradeDeviceOta(reqDTO);
  297 +
  298 + // 响应结果
  299 + ctx.response()
  300 + .putHeader("Content-Type", "application/json")
  301 + .end(Json.encode(result));
  302 + } catch (Exception e) {
  303 + log.error("[handleOtaUpgrade][处理OTA升级请求失败]", e);
  304 + ctx.response()
  305 + .setStatusCode(500)
  306 + .putHeader("Content-Type", "application/json")
  307 + .end(Json.encode(CommonResult.error(500, "处理OTA升级请求失败:" + e.getMessage())));
  308 + }
  309 + }
  310 +}
0 \ No newline at end of file 311 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/heartbeat/IotComponentHeartbeatJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.server.heartbeat;
  2 +
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  6 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
  7 +import cn.iocoder.yudao.module.iot.net.component.server.config.IotNetComponentServerProperties;
  8 +import cn.iocoder.yudao.module.iot.net.component.server.downstream.IotComponentDownstreamServer;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.springframework.scheduling.annotation.Scheduled;
  11 +
  12 +import java.time.LocalDateTime;
  13 +import java.util.concurrent.ScheduledExecutorService;
  14 +import java.util.concurrent.ScheduledThreadPoolExecutor;
  15 +import java.util.concurrent.TimeUnit;
  16 +import java.lang.ProcessHandle;
  17 +
  18 +/**
  19 + * IoT 组件心跳任务
  20 + * <p>
  21 + * 定期向主程序发送心跳,报告组件服务状态
  22 + *
  23 + * @author haohao
  24 + */
  25 +@Slf4j
  26 +public class IotComponentHeartbeatJob {
  27 +
  28 + private final IotDeviceUpstreamApi deviceUpstreamApi;
  29 + private final IotComponentDownstreamServer downstreamServer;
  30 + private final IotNetComponentServerProperties properties;
  31 + private ScheduledExecutorService executorService;
  32 +
  33 + public IotComponentHeartbeatJob(IotDeviceUpstreamApi deviceUpstreamApi,
  34 + IotComponentDownstreamServer downstreamServer,
  35 + IotNetComponentServerProperties properties) {
  36 + this.deviceUpstreamApi = deviceUpstreamApi;
  37 + this.downstreamServer = downstreamServer;
  38 + this.properties = properties;
  39 + }
  40 +
  41 + /**
  42 + * 初始化心跳任务
  43 + */
  44 + public void init() {
  45 + log.info("[init][开始初始化心跳任务]");
  46 + // 创建一个单线程的调度线程池
  47 + executorService = new ScheduledThreadPoolExecutor(1);
  48 + // 延迟 5 秒后开始执行,避免服务刚启动就发送心跳
  49 + executorService.scheduleAtFixedRate(this::sendHeartbeat,
  50 + 5000, properties.getHeartbeatInterval(), TimeUnit.MILLISECONDS);
  51 + log.info("[init][心跳任务初始化完成]");
  52 + }
  53 +
  54 + /**
  55 + * 停止心跳任务
  56 + */
  57 + public void stop() {
  58 + log.info("[stop][开始停止心跳任务]");
  59 + if (executorService != null) {
  60 + executorService.shutdown();
  61 + executorService = null;
  62 + }
  63 + log.info("[stop][心跳任务已停止]");
  64 + }
  65 +
  66 + /**
  67 + * 发送心跳
  68 + */
  69 + private void sendHeartbeat() {
  70 + try {
  71 + // 创建心跳请求
  72 + IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO = new IotPluginInstanceHeartbeatReqDTO();
  73 + // 设置插件标识
  74 + heartbeatReqDTO.setPluginKey(properties.getServerKey());
  75 + // 设置进程ID
  76 + heartbeatReqDTO.setProcessId(String.valueOf(ProcessHandle.current().pid()));
  77 + // 设置IP和端口
  78 + try {
  79 + String hostIp = SystemUtil.getHostInfo().getAddress();
  80 + heartbeatReqDTO.setHostIp(hostIp);
  81 + heartbeatReqDTO.setDownstreamPort(downstreamServer.getPort());
  82 + } catch (Exception e) {
  83 + log.warn("[sendHeartbeat][获取本地主机信息异常]", e);
  84 + }
  85 + // 设置在线状态
  86 + heartbeatReqDTO.setOnline(true);
  87 +
  88 + // 发送心跳
  89 + CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(heartbeatReqDTO);
  90 + if (result != null && result.isSuccess()) {
  91 + log.debug("[sendHeartbeat][发送心跳成功:{}]", heartbeatReqDTO);
  92 + } else {
  93 + log.error("[sendHeartbeat][发送心跳失败:{}, 结果:{}]", heartbeatReqDTO, result);
  94 + }
  95 + } catch (Exception e) {
  96 + log.error("[sendHeartbeat][发送心跳异常]", e);
  97 + }
  98 + }
  99 +
  100 +}
0 \ No newline at end of file 101 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/java/cn/iocoder/yudao/module/iot/net/component/server/upstream/IotComponentUpstreamClient.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.net.component.server.upstream;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  5 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
  6 +import cn.iocoder.yudao.module.iot.net.component.server.config.IotNetComponentServerProperties;
  7 +import lombok.RequiredArgsConstructor;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.web.client.RestTemplate;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  12 +
  13 +/**
  14 + * 组件上行客户端,用于向主程序上报设备数据
  15 + * <p>
  16 + * 通过 HTTP 调用远程的 IotDeviceUpstreamApi 接口
  17 + *
  18 + * @author haohao
  19 + */
  20 +@RequiredArgsConstructor
  21 +@Slf4j
  22 +public class IotComponentUpstreamClient implements IotDeviceUpstreamApi {
  23 +
  24 + public static final String URL_PREFIX = "/rpc-api/iot/device/upstream";
  25 +
  26 + private final IotNetComponentServerProperties properties;
  27 +
  28 + private final RestTemplate restTemplate;
  29 +
  30 + @Override
  31 + public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
  32 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/update-state";
  33 + return doPost(url, updateReqDTO);
  34 + }
  35 +
  36 + @Override
  37 + public CommonResult<Boolean> reportDeviceEvent(IotDeviceEventReportReqDTO reportReqDTO) {
  38 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-event";
  39 + return doPost(url, reportReqDTO);
  40 + }
  41 +
  42 + @Override
  43 + public CommonResult<Boolean> registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
  44 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/register-device";
  45 + return doPost(url, registerReqDTO);
  46 + }
  47 +
  48 + @Override
  49 + public CommonResult<Boolean> registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
  50 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/register-sub-device";
  51 + return doPost(url, registerReqDTO);
  52 + }
  53 +
  54 + @Override
  55 + public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
  56 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/add-device-topology";
  57 + return doPost(url, addReqDTO);
  58 + }
  59 +
  60 + @Override
  61 + public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
  62 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/authenticate-emqx-connection";
  63 + return doPost(url, authReqDTO);
  64 + }
  65 +
  66 + @Override
  67 + public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
  68 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property";
  69 + return doPost(url, reportReqDTO);
  70 + }
  71 +
  72 + @Override
  73 + public CommonResult<Boolean> heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO) {
  74 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/heartbeat-plugin-instance";
  75 + return doPost(url, heartbeatReqDTO);
  76 + }
  77 +
  78 + @SuppressWarnings("unchecked")
  79 + private <T> CommonResult<Boolean> doPost(String url, T requestBody) {
  80 + try {
  81 + CommonResult<Boolean> result = restTemplate.postForObject(url, requestBody,
  82 + (Class<CommonResult<Boolean>>) (Class<?>) CommonResult.class);
  83 + log.info("[doPost][url({}) requestBody({}) result({})]", url, requestBody, result);
  84 + return result;
  85 + } catch (Exception e) {
  86 + log.error("[doPost][url({}) requestBody({}) 发生异常]", url, requestBody, e);
  87 + return CommonResult.error(INTERNAL_SERVER_ERROR);
  88 + }
  89 + }
  90 +}
0 \ No newline at end of file 91 \ No newline at end of file
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-server/src/main/resources/application.yml 0 → 100644
  1 +# 服务器配置
  2 +server:
  3 + port: 18080 # 修改端口,避免与主应用的8080端口冲突
  4 +
  5 +# Spring 配置
  6 +spring:
  7 + application:
  8 + name: iot-component-server
  9 + # 允许循环引用
  10 + main:
  11 + allow-circular-references: true
  12 + allow-bean-definition-overriding: true
  13 +
  14 +# Yudao 配置
  15 +yudao:
  16 + info:
  17 + base-package: cn.iocoder.yudao # 主项目包路径,确保正确
  18 + iot:
  19 + component:
  20 + # 这里可以覆盖或添加 component-core 中的通用配置
  21 + instance-heartbeat-timeout: 30000 # 心跳超时时间
  22 +
  23 + # 网络组件服务器专用配置
  24 + server:
  25 + # 上行通信配置,用于向主程序上报数据
  26 + upstream-url: http://127.0.0.1:48080 # 主程序 API 地址
  27 + upstream-connect-timeout: 30s # 连接超时
  28 + upstream-read-timeout: 30s # 读取超时
  29 +
  30 + # 下行通信配置,用于接收主程序的控制指令
  31 + downstream-port: 18888 # 下行服务器端口
  32 +
  33 + # 组件服务唯一标识
  34 + server-key: yudao-module-iot-net-component-server
  35 +
  36 + # 心跳频率,单位:毫秒
  37 + heartbeat-interval: 30000
  38 +
  39 + # ====================================
  40 + # 针对引入的 HTTP 组件的配置
  41 + # ====================================
  42 + http:
  43 + enabled: true # 启用HTTP组件
  44 + server-port: 8092 # HTTP组件服务端口
  45 +
  46 + # ====================================
  47 + # 针对引入的 EMQX 组件的配置
  48 + # ====================================
  49 + emqx:
  50 + enabled: true # 启用EMQX组件
  51 + mqtt-host: 127.0.0.1 # MQTT服务器主机地址
  52 + mqtt-port: 1883 # MQTT服务器端口
  53 + mqtt-username: yudao # MQTT服务器用户名
  54 + mqtt-password: 123456 # MQTT服务器密码
  55 + mqtt-ssl: false # 是否启用SSL
  56 + mqtt-topics: # 订阅的主题列表
  57 + - "/sys/#"
  58 + auth-port: 8101 # 认证端口
  59 +
  60 +# 日志配置
  61 +logging:
  62 + level:
  63 + cn.iocoder.yudao: INFO
  64 + root: INFO
0 \ No newline at end of file 65 \ No newline at end of file