Commit 77b89aad773e021b2a7f9d0c85bdbbec468af000
1 parent
cde6ebf9
【功能完善】IoT: 集成 Vert.x 支持,重构 HTTP 插件为 Vert.x 插件
Showing
11 changed files
with
281 additions
and
214 deletions
yudao-dependencies/pom.xml
| @@ -67,6 +67,7 @@ | @@ -67,6 +67,7 @@ | ||
| 67 | <bizlog-sdk.version>3.0.6</bizlog-sdk.version> | 67 | <bizlog-sdk.version>3.0.6</bizlog-sdk.version> |
| 68 | <mqtt.version>1.2.5</mqtt.version> | 68 | <mqtt.version>1.2.5</mqtt.version> |
| 69 | <pf4j-spring.version>0.9.0</pf4j-spring.version> | 69 | <pf4j-spring.version>0.9.0</pf4j-spring.version> |
| 70 | + <vertx.version>4.4.0</vertx.version> | ||
| 70 | <!-- 三方云服务相关 --> | 71 | <!-- 三方云服务相关 --> |
| 71 | <okio.version>3.5.0</okio.version> | 72 | <okio.version>3.5.0</okio.version> |
| 72 | <okhttp3.version>4.11.0</okhttp3.version> | 73 | <okhttp3.version>4.11.0</okhttp3.version> |
| @@ -613,6 +614,19 @@ | @@ -613,6 +614,19 @@ | ||
| 613 | <version>${pf4j-spring.version}</version> | 614 | <version>${pf4j-spring.version}</version> |
| 614 | </dependency> | 615 | </dependency> |
| 615 | 616 | ||
| 617 | + <!-- Vert.x 核心依赖 --> | ||
| 618 | + <dependency> | ||
| 619 | + <groupId>io.vertx</groupId> | ||
| 620 | + <artifactId>vertx-core</artifactId> | ||
| 621 | + <version>${vertx.version}</version> | ||
| 622 | + </dependency> | ||
| 623 | + <!-- Vert.x Web 模块 --> | ||
| 624 | + <dependency> | ||
| 625 | + <groupId>io.vertx</groupId> | ||
| 626 | + <artifactId>vertx-web</artifactId> | ||
| 627 | + <version>${vertx.version}</version> | ||
| 628 | + </dependency> | ||
| 629 | + | ||
| 616 | </dependencies> | 630 | </dependencies> |
| 617 | </dependencyManagement> | 631 | </dependencyManagement> |
| 618 | 632 |
yudao-module-iot/yudao-module-iot-biz/pom.xml
| @@ -64,6 +64,16 @@ | @@ -64,6 +64,16 @@ | ||
| 64 | <artifactId>yudao-spring-boot-starter-excel</artifactId> | 64 | <artifactId>yudao-spring-boot-starter-excel</artifactId> |
| 65 | </dependency> | 65 | </dependency> |
| 66 | 66 | ||
| 67 | + <!-- Vert.x 核心依赖 --> | ||
| 68 | + <dependency> | ||
| 69 | + <groupId>io.vertx</groupId> | ||
| 70 | + <artifactId>vertx-core</artifactId> | ||
| 71 | + </dependency> | ||
| 72 | + <!-- Vert.x Web 模块 --> | ||
| 73 | + <dependency> | ||
| 74 | + <groupId>io.vertx</groupId> | ||
| 75 | + <artifactId>vertx-web</artifactId> | ||
| 76 | + </dependency> | ||
| 67 | <!-- MQTT --> | 77 | <!-- MQTT --> |
| 68 | <dependency> | 78 | <dependency> |
| 69 | <groupId>org.eclipse.paho</groupId> | 79 | <groupId>org.eclipse.paho</groupId> |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java
| @@ -196,10 +196,8 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | @@ -196,10 +196,8 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | ||
| 196 | .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); | 196 | .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); |
| 197 | 197 | ||
| 198 | // 3. 获取本机 IP 和 MAC 地址 | 198 | // 3. 获取本机 IP 和 MAC 地址 |
| 199 | - LinkedHashSet<InetAddress> localAddressList = NetUtil.localAddressList(t -> t instanceof Inet4Address); | ||
| 200 | - LinkedHashSet<String> ipList = NetUtil.toIpList(localAddressList); | ||
| 201 | - String ip = ipList.stream().findFirst().orElse("127.0.0.1"); | ||
| 202 | - String mac = NetUtil.getMacAddress(localAddressList.stream().findFirst().orElse(null)); | 199 | + String ip = NetUtil.getLocalhostStr(); |
| 200 | + String mac = NetUtil.getLocalMacAddress(); | ||
| 203 | String mainId = MAIN_ID + "-" + mac; | 201 | String mainId = MAIN_ID + "-" + mac; |
| 204 | 202 | ||
| 205 | // 4. 遍历插件列表,并保存为插件实例 | 203 | // 4. 遍历插件列表,并保存为插件实例 |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
| 3 | + <parent> | ||
| 4 | + <artifactId>yudao-module-iot-plugin</artifactId> | ||
| 5 | + <groupId>cn.iocoder.boot</groupId> | ||
| 6 | + <version>2.2.0-snapshot</version> | ||
| 7 | + </parent> | ||
| 8 | + <modelVersion>4.0.0</modelVersion> | ||
| 9 | + <artifactId>yudao-module-iot-http-plugin</artifactId> | ||
| 10 | + <name>${project.artifactId}</name> | ||
| 11 | + <version>2.2.0-snapshot</version> | ||
| 12 | + <description>物联网 插件模块 - http 插件</description> | ||
| 13 | + <build> | ||
| 14 | + <plugins> | ||
| 15 | + <plugin> | ||
| 16 | + <artifactId>maven-jar-plugin</artifactId> | ||
| 17 | + <version>2.4</version> | ||
| 18 | + <configuration> | ||
| 19 | + <archive> | ||
| 20 | + <manifestEntries> | ||
| 21 | + <Plugin-Id>${plugin.id}</Plugin-Id> | ||
| 22 | + <Plugin-Class>${plugin.class}</Plugin-Class> | ||
| 23 | + <Plugin-Version>${plugin.version}</Plugin-Version> | ||
| 24 | + <Plugin-Provider>${plugin.provider}</Plugin-Provider> | ||
| 25 | + <Plugin-Description>${plugin.description}</Plugin-Description> | ||
| 26 | + <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies> | ||
| 27 | + </manifestEntries> | ||
| 28 | + </archive> | ||
| 29 | + </configuration> | ||
| 30 | + </plugin> | ||
| 31 | + <plugin> | ||
| 32 | + <artifactId>maven-deploy-plugin</artifactId> | ||
| 33 | + <configuration> | ||
| 34 | + <skip>true</skip> | ||
| 35 | + </configuration> | ||
| 36 | + </plugin> | ||
| 37 | + <plugin> | ||
| 38 | + <artifactId>maven-shade-plugin</artifactId> | ||
| 39 | + <version>3.4.1</version> | ||
| 40 | + <executions> | ||
| 41 | + <execution> | ||
| 42 | + <phase>package</phase> | ||
| 43 | + <goals> | ||
| 44 | + <goal>shade</goal> | ||
| 45 | + </goals> | ||
| 46 | + <configuration> | ||
| 47 | + <shadedArtifactAttached>true</shadedArtifactAttached> | ||
| 48 | + <shadedClassifierName>shaded</shadedClassifierName> | ||
| 49 | + <transformers> | ||
| 50 | + <transformer> | ||
| 51 | + <mainClass>cn.iocoder.yudao.module.iot.HttpPluginSpringbootApplication</mainClass> | ||
| 52 | + </transformer> | ||
| 53 | + </transformers> | ||
| 54 | + </configuration> | ||
| 55 | + </execution> | ||
| 56 | + </executions> | ||
| 57 | + </plugin> | ||
| 58 | + </plugins> | ||
| 59 | + </build> | ||
| 60 | + <dependencies> | ||
| 61 | + <dependency> | ||
| 62 | + <groupId>org.pf4j</groupId> | ||
| 63 | + <artifactId>pf4j-spring</artifactId> | ||
| 64 | + <version>0.9.0</version> | ||
| 65 | + <scope>provided</scope> | ||
| 66 | + </dependency> | ||
| 67 | + <dependency> | ||
| 68 | + <groupId>org.projectlombok</groupId> | ||
| 69 | + <artifactId>lombok</artifactId> | ||
| 70 | + <version>1.18.34</version> | ||
| 71 | + <scope>provided</scope> | ||
| 72 | + </dependency> | ||
| 73 | + </dependencies> | ||
| 74 | + <properties> | ||
| 75 | + <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin</plugin.class> | ||
| 76 | + <plugin.version>0.0.1</plugin.version> | ||
| 77 | + <plugin.id>http-plugin</plugin.id> | ||
| 78 | + <plugin.description>http-plugin-0.0.1</plugin.description> | ||
| 79 | + <plugin.provider>ahh</plugin.provider> | ||
| 80 | + </properties> | ||
| 81 | +</project> |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties
| 1 | plugin.id=http-plugin | 1 | plugin.id=http-plugin |
| 2 | -plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpPlugin | 2 | +plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin |
| 3 | plugin.version=0.0.1 | 3 | plugin.version=0.0.1 |
| 4 | plugin.provider=ahh | 4 | plugin.provider=ahh |
| 5 | plugin.dependencies= | 5 | plugin.dependencies= |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml
| @@ -21,7 +21,7 @@ | @@ -21,7 +21,7 @@ | ||
| 21 | <properties> | 21 | <properties> |
| 22 | <!-- 插件相关 --> | 22 | <!-- 插件相关 --> |
| 23 | <plugin.id>http-plugin</plugin.id> | 23 | <plugin.id>http-plugin</plugin.id> |
| 24 | - <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpPlugin</plugin.class> | 24 | + <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin</plugin.class> |
| 25 | <plugin.version>0.0.1</plugin.version> | 25 | <plugin.version>0.0.1</plugin.version> |
| 26 | <plugin.provider>ahh</plugin.provider> | 26 | <plugin.provider>ahh</plugin.provider> |
| 27 | <plugin.description>http-plugin-0.0.1</plugin.description> | 27 | <plugin.description>http-plugin-0.0.1</plugin.description> |
| @@ -30,27 +30,6 @@ | @@ -30,27 +30,6 @@ | ||
| 30 | 30 | ||
| 31 | <build> | 31 | <build> |
| 32 | <plugins> | 32 | <plugins> |
| 33 | - <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section) | ||
| 34 | - <plugin> | ||
| 35 | - <groupId>org.codehaus.mojo</groupId> | ||
| 36 | - <artifactId>properties-maven-plugin</artifactId> | ||
| 37 | - <version>1.0-alpha-2</version> | ||
| 38 | - <executions> | ||
| 39 | - <execution> | ||
| 40 | - <phase>initialize</phase> | ||
| 41 | - <goals> | ||
| 42 | - <goal>read-project-properties</goal> | ||
| 43 | - </goals> | ||
| 44 | - <configuration> | ||
| 45 | - <files> | ||
| 46 | - <file>plugin.properties</file> | ||
| 47 | - </files> | ||
| 48 | - </configuration> | ||
| 49 | - </execution> | ||
| 50 | - </executions> | ||
| 51 | - </plugin> | ||
| 52 | - --> | ||
| 53 | - | ||
| 54 | <plugin> | 33 | <plugin> |
| 55 | <groupId>org.apache.maven.plugins</groupId> | 34 | <groupId>org.apache.maven.plugins</groupId> |
| 56 | <artifactId>maven-antrun-plugin</artifactId> | 35 | <artifactId>maven-antrun-plugin</artifactId> |
| @@ -118,6 +97,29 @@ | @@ -118,6 +97,29 @@ | ||
| 118 | <skip>true</skip> | 97 | <skip>true</skip> |
| 119 | </configuration> | 98 | </configuration> |
| 120 | </plugin> | 99 | </plugin> |
| 100 | + | ||
| 101 | + <!-- <plugin>--> | ||
| 102 | + <!-- <groupId>org.apache.maven.plugins</groupId>--> | ||
| 103 | + <!-- <artifactId>maven-shade-plugin</artifactId>--> | ||
| 104 | + <!-- <version>3.4.1</version>--> | ||
| 105 | + <!-- <executions>--> | ||
| 106 | + <!-- <execution>--> | ||
| 107 | + <!-- <phase>package</phase>--> | ||
| 108 | + <!-- <goals>--> | ||
| 109 | + <!-- <goal>shade</goal>--> | ||
| 110 | + <!-- </goals>--> | ||
| 111 | + <!-- <configuration>--> | ||
| 112 | + <!-- <shadedArtifactAttached>true</shadedArtifactAttached>--> | ||
| 113 | + <!-- <shadedClassifierName>shaded</shadedClassifierName>--> | ||
| 114 | + <!-- <transformers>--> | ||
| 115 | + <!-- <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">--> | ||
| 116 | + <!-- <mainClass>cn.iocoder.yudao.module.iot.HttpPluginSpringbootApplication</mainClass>--> | ||
| 117 | + <!-- </transformer>--> | ||
| 118 | + <!-- </transformers>--> | ||
| 119 | + <!-- </configuration>--> | ||
| 120 | + <!-- </execution>--> | ||
| 121 | + <!-- </executions>--> | ||
| 122 | + <!-- </plugin>--> | ||
| 121 | </plugins> | 123 | </plugins> |
| 122 | </build> | 124 | </build> |
| 123 | 125 | ||
| @@ -145,10 +147,15 @@ | @@ -145,10 +147,15 @@ | ||
| 145 | <version>${lombok.version}</version> | 147 | <version>${lombok.version}</version> |
| 146 | <scope>provided</scope> | 148 | <scope>provided</scope> |
| 147 | </dependency> | 149 | </dependency> |
| 150 | + <!-- Vert.x 核心依赖 --> | ||
| 151 | + <dependency> | ||
| 152 | + <groupId>io.vertx</groupId> | ||
| 153 | + <artifactId>vertx-core</artifactId> | ||
| 154 | + </dependency> | ||
| 155 | + <!-- Vert.x Web 模块 --> | ||
| 148 | <dependency> | 156 | <dependency> |
| 149 | - <groupId>io.netty</groupId> | ||
| 150 | - <artifactId>netty-all</artifactId> | ||
| 151 | - <version>4.1.63.Final</version> <!-- 版本可根据需要调整 --> | 157 | + <groupId>io.vertx</groupId> |
| 158 | + <artifactId>vertx-web</artifactId> | ||
| 152 | </dependency> | 159 | </dependency> |
| 153 | <!-- MQTT --> | 160 | <!-- MQTT --> |
| 154 | <dependency> | 161 | <dependency> |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.plugin; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | ||
| 4 | -import cn.iocoder.yudao.module.iot.api.ServiceRegistry; | ||
| 5 | -import io.netty.bootstrap.ServerBootstrap; | ||
| 6 | -import io.netty.channel.*; | ||
| 7 | -import io.netty.channel.nio.NioEventLoopGroup; | ||
| 8 | -import io.netty.channel.socket.nio.NioServerSocketChannel; | ||
| 9 | -import io.netty.handler.codec.http.*; | ||
| 10 | -import lombok.extern.slf4j.Slf4j; | ||
| 11 | -import org.pf4j.PluginWrapper; | ||
| 12 | -import org.pf4j.Plugin; | ||
| 13 | - | ||
| 14 | -import java.util.concurrent.ExecutorService; | ||
| 15 | -import java.util.concurrent.Executors; | ||
| 16 | - | ||
| 17 | -@Slf4j | ||
| 18 | -public class HttpPlugin extends Plugin { | ||
| 19 | - | ||
| 20 | - private static final int PORT = 8092; | ||
| 21 | - | ||
| 22 | - private ExecutorService executorService; | ||
| 23 | - private DeviceDataApi deviceDataApi; | ||
| 24 | - | ||
| 25 | - public HttpPlugin(PluginWrapper wrapper) { | ||
| 26 | - super(wrapper); | ||
| 27 | - // 初始化线程池 | ||
| 28 | - this.executorService = Executors.newSingleThreadExecutor(); | ||
| 29 | - } | ||
| 30 | - | ||
| 31 | - @Override | ||
| 32 | - public void start() { | ||
| 33 | - log.info("HttpPlugin.start()"); | ||
| 34 | - | ||
| 35 | - // 重新初始化线程池,确保它是活跃的 | ||
| 36 | - if (executorService.isShutdown() || executorService.isTerminated()) { | ||
| 37 | - executorService = Executors.newSingleThreadExecutor(); | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例 | ||
| 41 | - deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); | ||
| 42 | - if (deviceDataApi == null) { | ||
| 43 | - log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); | ||
| 44 | - return; | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - // 异步启动 Netty 服务器 | ||
| 48 | - executorService.submit(this::startHttpServer); | ||
| 49 | - } | ||
| 50 | - | ||
| 51 | - @Override | ||
| 52 | - public void stop() { | ||
| 53 | - log.info("HttpPlugin.stop()"); | ||
| 54 | - // 停止线程池 | ||
| 55 | - executorService.shutdownNow(); | ||
| 56 | - } | ||
| 57 | - | ||
| 58 | - /** | ||
| 59 | - * 启动 HTTP 服务 | ||
| 60 | - */ | ||
| 61 | - private void startHttpServer() { | ||
| 62 | - EventLoopGroup bossGroup = new NioEventLoopGroup(1); | ||
| 63 | - EventLoopGroup workerGroup = new NioEventLoopGroup(); | ||
| 64 | - | ||
| 65 | - try { | ||
| 66 | - ServerBootstrap bootstrap = new ServerBootstrap(); | ||
| 67 | - bootstrap.group(bossGroup, workerGroup) | ||
| 68 | - .channel(NioServerSocketChannel.class) | ||
| 69 | - .childHandler(new ChannelInitializer<>() { | ||
| 70 | - | ||
| 71 | - @Override | ||
| 72 | - protected void initChannel(Channel channel) { | ||
| 73 | - channel.pipeline().addLast(new HttpServerCodec()); | ||
| 74 | - channel.pipeline().addLast(new HttpObjectAggregator(65536)); | ||
| 75 | - // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器 | ||
| 76 | - channel.pipeline().addLast(new HttpHandler(deviceDataApi)); | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | - }); | ||
| 80 | - | ||
| 81 | - // 绑定端口并启动服务器 | ||
| 82 | - ChannelFuture future = bootstrap.bind(PORT).sync(); | ||
| 83 | - log.info("HTTP 服务器启动成功,端口为: {}", PORT); | ||
| 84 | - future.channel().closeFuture().sync(); | ||
| 85 | - } catch (InterruptedException e) { | ||
| 86 | - Thread.currentThread().interrupt(); | ||
| 87 | - log.warn("HTTP 服务启动被中断", e); | ||
| 88 | - } finally { | ||
| 89 | - bossGroup.shutdownGracefully(); | ||
| 90 | - workerGroup.shutdownGracefully(); | ||
| 91 | - } | ||
| 92 | - } | ||
| 93 | - | ||
| 94 | -} | ||
| 95 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java renamed to yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java
| @@ -4,63 +4,29 @@ import cn.hutool.json.JSONObject; | @@ -4,63 +4,29 @@ import cn.hutool.json.JSONObject; | ||
| 4 | import cn.hutool.json.JSONUtil; | 4 | import cn.hutool.json.JSONUtil; |
| 5 | import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | 5 | import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; |
| 6 | import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; | 6 | import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; |
| 7 | -import io.netty.buffer.Unpooled; | ||
| 8 | -import io.netty.channel.ChannelFutureListener; | ||
| 9 | -import io.netty.channel.ChannelHandlerContext; | ||
| 10 | -import io.netty.channel.SimpleChannelInboundHandler; | ||
| 11 | -import io.netty.handler.codec.http.*; | ||
| 12 | -import io.netty.util.CharsetUtil; | 7 | +import io.vertx.core.Handler; |
| 8 | +import io.vertx.ext.web.RequestBody; | ||
| 9 | +import io.vertx.ext.web.RoutingContext; | ||
| 10 | +import lombok.extern.slf4j.Slf4j; | ||
| 13 | 11 | ||
| 14 | -/** | ||
| 15 | - * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。 | ||
| 16 | - * <p> | ||
| 17 | - * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post | ||
| 18 | - * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段 | ||
| 19 | - */ | ||
| 20 | -public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | 12 | +@Slf4j |
| 13 | +public class HttpVertxHandler implements Handler<RoutingContext> { | ||
| 21 | 14 | ||
| 22 | private final DeviceDataApi deviceDataApi; | 15 | private final DeviceDataApi deviceDataApi; |
| 23 | 16 | ||
| 24 | - public HttpHandler(DeviceDataApi deviceDataApi) { | 17 | + public HttpVertxHandler(DeviceDataApi deviceDataApi) { |
| 25 | this.deviceDataApi = deviceDataApi; | 18 | this.deviceDataApi = deviceDataApi; |
| 26 | } | 19 | } |
| 27 | 20 | ||
| 28 | @Override | 21 | @Override |
| 29 | - protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { | ||
| 30 | - // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post | ||
| 31 | - // 使用 "/" 拆分路径 | ||
| 32 | - String uri = request.uri(); | ||
| 33 | - String[] parts = uri.split("/"); | 22 | + public void handle(RoutingContext ctx) { |
| 23 | + String productKey = ctx.pathParam("productKey"); | ||
| 24 | + String deviceName = ctx.pathParam("deviceName"); | ||
| 25 | + RequestBody requestBody = ctx.body(); | ||
| 34 | 26 | ||
| 35 | - /* | ||
| 36 | - 拆分结果示例: | ||
| 37 | - parts[0] = "" | ||
| 38 | - parts[1] = "sys" | ||
| 39 | - parts[2] = productKey | ||
| 40 | - parts[3] = deviceName | ||
| 41 | - parts[4] = "thing" | ||
| 42 | - parts[5] = "event" | ||
| 43 | - parts[6] = "property" | ||
| 44 | - parts[7] = "post" | ||
| 45 | - */ | ||
| 46 | - boolean isCorrectPath = parts.length == 8 | ||
| 47 | - && "sys".equals(parts[1]) | ||
| 48 | - && "thing".equals(parts[4]) | ||
| 49 | - && "event".equals(parts[5]) | ||
| 50 | - && "property".equals(parts[6]) | ||
| 51 | - && "post".equals(parts[7]); | ||
| 52 | - if (!isCorrectPath) { | ||
| 53 | - writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found"); | ||
| 54 | - return; | ||
| 55 | - } | ||
| 56 | - String productKey = parts[2]; | ||
| 57 | - String deviceName = parts[3]; | ||
| 58 | - | ||
| 59 | - // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象 | ||
| 60 | - String requestBody = request.content().toString(CharsetUtil.UTF_8); | ||
| 61 | JSONObject jsonData; | 27 | JSONObject jsonData; |
| 62 | try { | 28 | try { |
| 63 | - jsonData = JSONUtil.parseObj(requestBody); | 29 | + jsonData = JSONUtil.parseObj(requestBody.asJsonObject()); |
| 64 | } catch (Exception e) { | 30 | } catch (Exception e) { |
| 65 | JSONObject res = createResponseJson( | 31 | JSONObject res = createResponseJson( |
| 66 | 400, | 32 | 400, |
| @@ -68,11 +34,14 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | @@ -68,11 +34,14 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | ||
| 68 | null, | 34 | null, |
| 69 | "请求数据不是合法的 JSON 格式: " + e.getMessage(), | 35 | "请求数据不是合法的 JSON 格式: " + e.getMessage(), |
| 70 | "thing.event.property.post", | 36 | "thing.event.property.post", |
| 71 | - "1.0" | ||
| 72 | - ); | ||
| 73 | - writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString()); | 37 | + "1.0"); |
| 38 | + ctx.response() | ||
| 39 | + .setStatusCode(400) | ||
| 40 | + .putHeader("Content-Type", "application/json; charset=UTF-8") | ||
| 41 | + .end(res.toString()); | ||
| 74 | return; | 42 | return; |
| 75 | } | 43 | } |
| 44 | + | ||
| 76 | String id = jsonData.getStr("id", null); | 45 | String id = jsonData.getStr("id", null); |
| 77 | 46 | ||
| 78 | try { | 47 | try { |
| @@ -91,9 +60,11 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | @@ -91,9 +60,11 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | ||
| 91 | id, | 60 | id, |
| 92 | "success", | 61 | "success", |
| 93 | "thing.event.property.post", | 62 | "thing.event.property.post", |
| 94 | - "1.0" | ||
| 95 | - ); | ||
| 96 | - writeResponse(ctx, HttpResponseStatus.OK, successRes.toString()); | 63 | + "1.0"); |
| 64 | + ctx.response() | ||
| 65 | + .setStatusCode(200) | ||
| 66 | + .putHeader("Content-Type", "application/json; charset=UTF-8") | ||
| 67 | + .end(successRes.toString()); | ||
| 97 | } catch (Exception e) { | 68 | } catch (Exception e) { |
| 98 | JSONObject errorRes = createResponseJson( | 69 | JSONObject errorRes = createResponseJson( |
| 99 | 500, | 70 | 500, |
| @@ -101,9 +72,11 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | @@ -101,9 +72,11 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | ||
| 101 | id, | 72 | id, |
| 102 | "The format of result is error!", | 73 | "The format of result is error!", |
| 103 | "thing.event.property.post", | 74 | "thing.event.property.post", |
| 104 | - "1.0" | ||
| 105 | - ); | ||
| 106 | - writeResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorRes.toString()); | 75 | + "1.0"); |
| 76 | + ctx.response() | ||
| 77 | + .setStatusCode(500) | ||
| 78 | + .putHeader("Content-Type", "application/json; charset=UTF-8") | ||
| 79 | + .end(errorRes.toString()); | ||
| 107 | } | 80 | } |
| 108 | } | 81 | } |
| 109 | 82 | ||
| @@ -118,7 +91,8 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | @@ -118,7 +91,8 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | ||
| 118 | * @param version 返回的版本号 | 91 | * @param version 返回的版本号 |
| 119 | * @return 构造好的 JSON 对象 | 92 | * @return 构造好的 JSON 对象 |
| 120 | */ | 93 | */ |
| 121 | - private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) { | 94 | + private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, |
| 95 | + String version) { | ||
| 122 | JSONObject res = new JSONObject(); | 96 | JSONObject res = new JSONObject(); |
| 123 | res.set("code", code); | 97 | res.set("code", code); |
| 124 | res.set("data", data != null ? data : new JSONObject()); | 98 | res.set("data", data != null ? data : new JSONObject()); |
| @@ -128,26 +102,4 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | @@ -128,26 +102,4 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | ||
| 128 | res.set("version", version); | 102 | res.set("version", version); |
| 129 | return res; | 103 | return res; |
| 130 | } | 104 | } |
| 131 | - | ||
| 132 | - /** | ||
| 133 | - * 向客户端返回 HTTP 响应的辅助方法 | ||
| 134 | - * | ||
| 135 | - * @param ctx 通道上下文 | ||
| 136 | - * @param status HTTP 响应状态码(网络层面的) | ||
| 137 | - * @param content 响应内容(JSON 字符串或其他文本) | ||
| 138 | - */ | ||
| 139 | - private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) { | ||
| 140 | - // 设置响应头为 JSON 类型和正确的编码 | ||
| 141 | - FullHttpResponse response = new DefaultFullHttpResponse( | ||
| 142 | - HttpVersion.HTTP_1_1, | ||
| 143 | - status, | ||
| 144 | - Unpooled.copiedBuffer(content, CharsetUtil.UTF_8) | ||
| 145 | - ); | ||
| 146 | - response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8"); | ||
| 147 | - response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); | ||
| 148 | - | ||
| 149 | - // 发送响应并在发送完成后关闭连接 | ||
| 150 | - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); | ||
| 151 | - } | ||
| 152 | - | ||
| 153 | -} | ||
| 154 | \ No newline at end of file | 105 | \ No newline at end of file |
| 106 | +} |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.plugin; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.iot.api.ServiceRegistry; | ||
| 4 | +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | ||
| 5 | +import io.vertx.core.Vertx; | ||
| 6 | +import io.vertx.ext.web.Router; | ||
| 7 | +import io.vertx.ext.web.handler.BodyHandler; | ||
| 8 | +import org.pf4j.Plugin; | ||
| 9 | +import org.pf4j.PluginWrapper; | ||
| 10 | +import lombok.extern.slf4j.Slf4j; | ||
| 11 | + | ||
| 12 | +@Slf4j | ||
| 13 | +public class HttpVertxPlugin extends Plugin { | ||
| 14 | + | ||
| 15 | + private static final int PORT = 8092; | ||
| 16 | + private Vertx vertx; | ||
| 17 | + private DeviceDataApi deviceDataApi; | ||
| 18 | + | ||
| 19 | + public HttpVertxPlugin(PluginWrapper wrapper) { | ||
| 20 | + super(wrapper); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + @Override | ||
| 24 | + public void start() { | ||
| 25 | + log.info("HttpVertxPlugin.start()"); | ||
| 26 | + | ||
| 27 | + // 获取 DeviceDataApi 实例 | ||
| 28 | + deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); | ||
| 29 | + if (deviceDataApi == null) { | ||
| 30 | + log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); | ||
| 31 | + return; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + // 初始化 Vert.x | ||
| 35 | + vertx = Vertx.vertx(); | ||
| 36 | + Router router = Router.router(vertx); | ||
| 37 | + | ||
| 38 | + // 处理 Body | ||
| 39 | + router.route().handler(BodyHandler.create()); | ||
| 40 | + | ||
| 41 | + // 设置路由 | ||
| 42 | + router.post("/sys/:productKey/:deviceName/thing/event/property/post") | ||
| 43 | + .handler(new HttpVertxHandler(deviceDataApi)); | ||
| 44 | + | ||
| 45 | + // 启动 HTTP 服务器 | ||
| 46 | + vertx.createHttpServer() | ||
| 47 | + .requestHandler(router) | ||
| 48 | + .listen(PORT, http -> { | ||
| 49 | + if (http.succeeded()) { | ||
| 50 | + log.info("HTTP 服务器启动成功,端口为: {}", PORT); | ||
| 51 | + } else { | ||
| 52 | + log.error("HTTP 服务器启动失败", http.cause()); | ||
| 53 | + } | ||
| 54 | + }); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + @Override | ||
| 58 | + public void stop() { | ||
| 59 | + log.info("HttpVertxPlugin.stop()"); | ||
| 60 | + if (vertx != null) { | ||
| 61 | + vertx.close(ar -> { | ||
| 62 | + if (ar.succeeded()) { | ||
| 63 | + log.info("Vert.x 关闭成功"); | ||
| 64 | + } else { | ||
| 65 | + log.error("Vert.x 关闭失败", ar.cause()); | ||
| 66 | + } | ||
| 67 | + }); | ||
| 68 | + } | ||
| 69 | + } | ||
| 70 | +} | ||
| 0 | \ No newline at end of file | 71 | \ No newline at end of file |
yudao-server/src/main/resources/application-dev.yaml
| @@ -212,4 +212,9 @@ iot: | @@ -212,4 +212,9 @@ iot: | ||
| 212 | # 保持连接 | 212 | # 保持连接 |
| 213 | keepalive: 60 | 213 | keepalive: 60 |
| 214 | # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) | 214 | # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) |
| 215 | - clearSession: true | ||
| 216 | \ No newline at end of file | 215 | \ No newline at end of file |
| 216 | + clearSession: true | ||
| 217 | + | ||
| 218 | + | ||
| 219 | +# 插件配置 | ||
| 220 | +pf4j: | ||
| 221 | + pluginsDir: ${user.home}/plugins # 插件目录 | ||
| 217 | \ No newline at end of file | 222 | \ No newline at end of file |
yudao-server/src/main/resources/application-local.yaml
| @@ -45,7 +45,7 @@ spring: | @@ -45,7 +45,7 @@ spring: | ||
| 45 | primary: master | 45 | primary: master |
| 46 | datasource: | 46 | datasource: |
| 47 | master: | 47 | master: |
| 48 | - url: jdbc:mysql://127.0.0.1:3307/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 | 48 | + url: jdbc:mysql://chaojiniu.top:23306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 |
| 49 | # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai # MySQL Connector/J 5.X 连接的示例 | 49 | # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai # MySQL Connector/J 5.X 连接的示例 |
| 50 | # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 | 50 | # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 |
| 51 | # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 | 51 | # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 |
| @@ -53,8 +53,8 @@ spring: | @@ -53,8 +53,8 @@ spring: | ||
| 53 | # url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例 | 53 | # url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例 |
| 54 | # url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例 | 54 | # url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例 |
| 55 | # url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例 | 55 | # url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例 |
| 56 | - username: root | ||
| 57 | - password: ahh@123456 | 56 | + username: ruoyi-vue-pro |
| 57 | + password: ruoyi-@h2ju02hebp | ||
| 58 | # username: sa # SQL Server 连接的示例 | 58 | # username: sa # SQL Server 连接的示例 |
| 59 | # password: Yudao@2024 # SQL Server 连接的示例 | 59 | # password: Yudao@2024 # SQL Server 连接的示例 |
| 60 | # username: SYSDBA # DM 连接的示例 | 60 | # username: SYSDBA # DM 连接的示例 |
| @@ -63,17 +63,25 @@ spring: | @@ -63,17 +63,25 @@ spring: | ||
| 63 | # password: Yudao@2024 # OpenGauss 连接的示例 | 63 | # password: Yudao@2024 # OpenGauss 连接的示例 |
| 64 | slave: # 模拟从库,可根据自己需要修改 | 64 | slave: # 模拟从库,可根据自己需要修改 |
| 65 | lazy: true # 开启懒加载,保证启动速度 | 65 | lazy: true # 开启懒加载,保证启动速度 |
| 66 | - url: jdbc:mysql://127.0.0.1:3307/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true | 66 | + url: jdbc:mysql://chaojiniu.top:23306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true |
| 67 | + username: ruoyi-vue-pro | ||
| 68 | + password: ruoyi-@h2ju02hebp | ||
| 69 | + tdengine: # IOT 数据库 | ||
| 70 | +# lazy: true # 开启懒加载,保证启动速度 | ||
| 71 | + url: jdbc:TAOS-RS://chaojiniu.top:6041/ruoyi_vue_pro | ||
| 72 | + driver-class-name: com.taosdata.jdbc.rs.RestfulDriver | ||
| 67 | username: root | 73 | username: root |
| 68 | - password: ahh@123456 | 74 | + password: taosdata |
| 75 | + druid: | ||
| 76 | + validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL | ||
| 69 | 77 | ||
| 70 | # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 | 78 | # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 |
| 71 | data: | 79 | data: |
| 72 | redis: | 80 | redis: |
| 73 | - host: 127.0.0.1 # 地址 | 81 | + host: chaojiniu.top # 地址 |
| 74 | port: 6379 # 端口 | 82 | port: 6379 # 端口 |
| 75 | - database: 0 # 数据库索引 | ||
| 76 | -# password: dev # 密码,建议生产环境开启 | 83 | + database: 15 # 数据库索引 |
| 84 | + password: fsknKD7UvQYZsyf2hXXn # 密码,建议生产环境开启 | ||
| 77 | 85 | ||
| 78 | --- #################### 定时任务相关配置 #################### | 86 | --- #################### 定时任务相关配置 #################### |
| 79 | 87 | ||
| @@ -175,8 +183,10 @@ logging: | @@ -175,8 +183,10 @@ logging: | ||
| 175 | cn.iocoder.yudao.module.crm.dal.mysql: debug | 183 | cn.iocoder.yudao.module.crm.dal.mysql: debug |
| 176 | cn.iocoder.yudao.module.erp.dal.mysql: debug | 184 | cn.iocoder.yudao.module.erp.dal.mysql: debug |
| 177 | cn.iocoder.yudao.module.iot.dal.mysql: debug | 185 | cn.iocoder.yudao.module.iot.dal.mysql: debug |
| 186 | + cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG | ||
| 178 | cn.iocoder.yudao.module.ai.dal.mysql: debug | 187 | cn.iocoder.yudao.module.ai.dal.mysql: debug |
| 179 | org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 | 188 | org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 |
| 189 | + com.taosdata: DEBUG # TDengine 的日志级别 | ||
| 180 | 190 | ||
| 181 | debug: false | 191 | debug: false |
| 182 | 192 | ||
| @@ -259,7 +269,7 @@ justauth: | @@ -259,7 +269,7 @@ justauth: | ||
| 259 | iot: | 269 | iot: |
| 260 | emq: | 270 | emq: |
| 261 | # 账号 | 271 | # 账号 |
| 262 | - username: anhaohao | 272 | + username: haohao |
| 263 | # 密码 | 273 | # 密码 |
| 264 | password: ahh@123456 | 274 | password: ahh@123456 |
| 265 | # 主机地址 | 275 | # 主机地址 |
| @@ -271,4 +281,18 @@ iot: | @@ -271,4 +281,18 @@ iot: | ||
| 271 | # 保持连接 | 281 | # 保持连接 |
| 272 | keepalive: 60 | 282 | keepalive: 60 |
| 273 | # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) | 283 | # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) |
| 274 | - clearSession: true | ||
| 275 | \ No newline at end of file | 284 | \ No newline at end of file |
| 285 | + clearSession: true | ||
| 286 | + | ||
| 287 | +# MQTT-RPC 配置 | ||
| 288 | +mqtt: | ||
| 289 | + broker: tcp://chaojiniu.top:1883 | ||
| 290 | + username: haohao | ||
| 291 | + password: ahh@123456 | ||
| 292 | + clientId: mqtt-rpc-server-${random.int} | ||
| 293 | + requestTopic: rpc/request | ||
| 294 | + responseTopicPrefix: rpc/response/ | ||
| 295 | + | ||
| 296 | + | ||
| 297 | +# 插件配置 | ||
| 298 | +pf4j: | ||
| 299 | + pluginsDir: /Users/anhaohao/code/gitee/ruoyi-vue-pro/plugins # 插件目录 | ||
| 276 | \ No newline at end of file | 300 | \ No newline at end of file |