Commit 77b89aad773e021b2a7f9d0c85bdbbec468af000

Authored by 安浩浩
1 parent cde6ebf9

【功能完善】IoT: 集成 Vert.x 支持,重构 HTTP 插件为 Vert.x 插件

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&lt;FullHttpRequest&gt; { @@ -68,11 +34,14 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
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&lt;FullHttpRequest&gt; { @@ -91,9 +60,11 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
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&lt;FullHttpRequest&gt; { @@ -101,9 +72,11 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
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&lt;FullHttpRequest&gt; { @@ -118,7 +91,8 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
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&lt;FullHttpRequest&gt; { @@ -128,26 +102,4 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
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