diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java
deleted file mode 100644
index b91146712f..0000000000
--- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java
+++ /dev/null
@@ -1,153 +0,0 @@
-package cn.iocoder.yudao.module.iot.plugin;
-
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
-import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
-import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.ChannelFutureListener;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.handler.codec.http.*;
-import io.netty.util.CharsetUtil;
-
-/**
- * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。
- *
- * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post
- * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段
- */
-public class HttpHandler extends SimpleChannelInboundHandler {
-
- private final DeviceDataApi deviceDataApi;
-
- public HttpHandler(DeviceDataApi deviceDataApi) {
- this.deviceDataApi = deviceDataApi;
- }
-
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
- // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post
- // 使用 "/" 拆分路径
- String uri = request.uri();
- String[] parts = uri.split("/");
-
- /*
- 拆分结果示例:
- parts[0] = ""
- parts[1] = "sys"
- parts[2] = productKey
- parts[3] = deviceName
- parts[4] = "thing"
- parts[5] = "event"
- parts[6] = "property"
- parts[7] = "post"
- */
- boolean isCorrectPath = parts.length == 8
- && "sys".equals(parts[1])
- && "thing".equals(parts[4])
- && "event".equals(parts[5])
- && "property".equals(parts[6])
- && "post".equals(parts[7]);
- if (!isCorrectPath) {
- writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found");
- return;
- }
- String productKey = parts[2];
- String deviceName = parts[3];
-
- // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象
- String requestBody = request.content().toString(CharsetUtil.UTF_8);
- JSONObject jsonData;
- try {
- jsonData = JSONUtil.parseObj(requestBody);
- } catch (Exception e) {
- JSONObject res = createResponseJson(
- 400,
- new JSONObject(),
- null,
- "请求数据不是合法的 JSON 格式: " + e.getMessage(),
- "thing.event.property.post",
- "1.0"
- );
- writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString());
- return;
- }
- String id = jsonData.getStr("id", null);
-
- try {
- // 调用主程序的接口保存数据
- DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder()
- .productKey(productKey)
- .deviceName(deviceName)
- .message(jsonData.toString())
- .build();
- deviceDataApi.saveDeviceData(createDTO);
-
- // 构造成功响应内容
- JSONObject successRes = createResponseJson(
- 200,
- new JSONObject(),
- id,
- "success",
- "thing.event.property.post",
- "1.0"
- );
- writeResponse(ctx, HttpResponseStatus.OK, successRes.toString());
- } catch (Exception e) {
- JSONObject errorRes = createResponseJson(
- 500,
- new JSONObject(),
- id,
- "The format of result is error!",
- "thing.event.property.post",
- "1.0"
- );
- writeResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorRes.toString());
- }
- }
-
- /**
- * 创建标准化的响应 JSON 对象
- *
- * @param code 响应状态码(业务层面的)
- * @param data 返回的数据对象(JSON)
- * @param id 请求的 id(可选)
- * @param message 返回的提示信息
- * @param method 返回的 method 标识
- * @param version 返回的版本号
- * @return 构造好的 JSON 对象
- */
- private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) {
- JSONObject res = new JSONObject();
- res.set("code", code);
- res.set("data", data != null ? data : new JSONObject());
- res.set("id", id);
- res.set("message", message);
- res.set("method", method);
- res.set("version", version);
- return res;
- }
-
- /**
- * 向客户端返回 HTTP 响应的辅助方法
- *
- * @param ctx 通道上下文
- * @param status HTTP 响应状态码(网络层面的)
- * @param content 响应内容(JSON 字符串或其他文本)
- */
- private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) {
- // 设置响应头为 JSON 类型和正确的编码
- FullHttpResponse response = new DefaultFullHttpResponse(
- HttpVersion.HTTP_1_1,
- status,
- Unpooled.copiedBuffer(content, CharsetUtil.UTF_8)
- );
- response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
- response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
-
- // 发送响应并在发送完成后关闭连接
- ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
- }
-
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java
deleted file mode 100644
index 66e0c69a39..0000000000
--- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package cn.iocoder.yudao.module.iot.plugin;
-
-import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
-import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
-import io.netty.bootstrap.ServerBootstrap;
-import io.netty.channel.*;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.nio.NioServerSocketChannel;
-import io.netty.handler.codec.http.*;
-import lombok.extern.slf4j.Slf4j;
-import org.pf4j.PluginWrapper;
-import org.pf4j.Plugin;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-@Slf4j
-public class HttpPlugin extends Plugin {
-
- private static final int PORT = 8092;
-
- private ExecutorService executorService;
- private DeviceDataApi deviceDataApi;
-
- public HttpPlugin(PluginWrapper wrapper) {
- super(wrapper);
- // 初始化线程池
- this.executorService = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public void start() {
- log.info("HttpPlugin.start()");
-
- // 重新初始化线程池,确保它是活跃的
- if (executorService.isShutdown() || executorService.isTerminated()) {
- executorService = Executors.newSingleThreadExecutor();
- }
-
- // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例
- deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
- if (deviceDataApi == null) {
- log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");
- return;
- }
-
- // 异步启动 Netty 服务器
- executorService.submit(this::startHttpServer);
- }
-
- @Override
- public void stop() {
- log.info("HttpPlugin.stop()");
- // 停止线程池
- executorService.shutdownNow();
- }
-
- /**
- * 启动 HTTP 服务
- */
- private void startHttpServer() {
- EventLoopGroup bossGroup = new NioEventLoopGroup(1);
- EventLoopGroup workerGroup = new NioEventLoopGroup();
-
- try {
- ServerBootstrap bootstrap = new ServerBootstrap();
- bootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .childHandler(new ChannelInitializer<>() {
-
- @Override
- protected void initChannel(Channel channel) {
- channel.pipeline().addLast(new HttpServerCodec());
- channel.pipeline().addLast(new HttpObjectAggregator(65536));
- // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器
- channel.pipeline().addLast(new HttpHandler(deviceDataApi));
- }
-
- });
-
- // 绑定端口并启动服务器
- ChannelFuture future = bootstrap.bind(PORT).sync();
- log.info("HTTP 服务器启动成功,端口为: {}", PORT);
- future.channel().closeFuture().sync();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("HTTP 服务启动被中断", e);
- } finally {
- bossGroup.shutdownGracefully();
- workerGroup.shutdownGracefully();
- }
- }
-
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java
new file mode 100644
index 0000000000..335d6c95d2
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java
@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.iot.plugin;
+
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
+import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO;
+import io.vertx.core.Handler;
+import io.vertx.ext.web.RequestBody;
+import io.vertx.ext.web.RoutingContext;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class HttpVertxHandler implements Handler {
+
+ private final DeviceDataApi deviceDataApi;
+
+ public HttpVertxHandler(DeviceDataApi deviceDataApi) {
+ this.deviceDataApi = deviceDataApi;
+ }
+
+ @Override
+ public void handle(RoutingContext ctx) {
+ String productKey = ctx.pathParam("productKey");
+ String deviceName = ctx.pathParam("deviceName");
+ RequestBody requestBody = ctx.body();
+
+ JSONObject jsonData;
+ try {
+ jsonData = JSONUtil.parseObj(requestBody.asJsonObject());
+ } catch (Exception e) {
+ JSONObject res = createResponseJson(
+ 400,
+ new JSONObject(),
+ null,
+ "请求数据不是合法的 JSON 格式: " + e.getMessage(),
+ "thing.event.property.post",
+ "1.0");
+ ctx.response()
+ .setStatusCode(400)
+ .putHeader("Content-Type", "application/json; charset=UTF-8")
+ .end(res.toString());
+ return;
+ }
+
+ String id = jsonData.getStr("id", null);
+
+ try {
+ // 调用主程序的接口保存数据
+ DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder()
+ .productKey(productKey)
+ .deviceName(deviceName)
+ .message(jsonData.toString())
+ .build();
+ deviceDataApi.saveDeviceData(createDTO);
+
+ // 构造成功响应内容
+ JSONObject successRes = createResponseJson(
+ 200,
+ new JSONObject(),
+ id,
+ "success",
+ "thing.event.property.post",
+ "1.0");
+ ctx.response()
+ .setStatusCode(200)
+ .putHeader("Content-Type", "application/json; charset=UTF-8")
+ .end(successRes.toString());
+ } catch (Exception e) {
+ JSONObject errorRes = createResponseJson(
+ 500,
+ new JSONObject(),
+ id,
+ "The format of result is error!",
+ "thing.event.property.post",
+ "1.0");
+ ctx.response()
+ .setStatusCode(500)
+ .putHeader("Content-Type", "application/json; charset=UTF-8")
+ .end(errorRes.toString());
+ }
+ }
+
+ /**
+ * 创建标准化的响应 JSON 对象
+ *
+ * @param code 响应状态码(业务层面的)
+ * @param data 返回的数据对象(JSON)
+ * @param id 请求的 id(可选)
+ * @param message 返回的提示信息
+ * @param method 返回的 method 标识
+ * @param version 返回的版本号
+ * @return 构造好的 JSON 对象
+ */
+ private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method,
+ String version) {
+ JSONObject res = new JSONObject();
+ res.set("code", code);
+ res.set("data", data != null ? data : new JSONObject());
+ res.set("id", id);
+ res.set("message", message);
+ res.set("method", method);
+ res.set("version", version);
+ return res;
+ }
+}
diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java
new file mode 100644
index 0000000000..c1d587489d
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.iot.plugin;
+
+import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
+import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
+import io.vertx.core.Vertx;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.handler.BodyHandler;
+import org.pf4j.Plugin;
+import org.pf4j.PluginWrapper;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class HttpVertxPlugin extends Plugin {
+
+ private static final int PORT = 8092;
+ private Vertx vertx;
+ private DeviceDataApi deviceDataApi;
+
+ public HttpVertxPlugin(PluginWrapper wrapper) {
+ super(wrapper);
+ }
+
+ @Override
+ public void start() {
+ log.info("HttpVertxPlugin.start()");
+
+ // 获取 DeviceDataApi 实例
+ deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
+ if (deviceDataApi == null) {
+ log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");
+ return;
+ }
+
+ // 初始化 Vert.x
+ vertx = Vertx.vertx();
+ Router router = Router.router(vertx);
+
+ // 处理 Body
+ router.route().handler(BodyHandler.create());
+
+ // 设置路由
+ router.post("/sys/:productKey/:deviceName/thing/event/property/post")
+ .handler(new HttpVertxHandler(deviceDataApi));
+
+ // 启动 HTTP 服务器
+ vertx.createHttpServer()
+ .requestHandler(router)
+ .listen(PORT, http -> {
+ if (http.succeeded()) {
+ log.info("HTTP 服务器启动成功,端口为: {}", PORT);
+ } else {
+ log.error("HTTP 服务器启动失败", http.cause());
+ }
+ });
+ }
+
+ @Override
+ public void stop() {
+ log.info("HttpVertxPlugin.stop()");
+ if (vertx != null) {
+ vertx.close(ar -> {
+ if (ar.succeeded()) {
+ log.info("Vert.x 关闭成功");
+ } else {
+ log.error("Vert.x 关闭失败", ar.cause());
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml
index 5457247e6f..c0bd5f64da 100644
--- a/yudao-server/src/main/resources/application-dev.yaml
+++ b/yudao-server/src/main/resources/application-dev.yaml
@@ -212,4 +212,9 @@ iot:
# 保持连接
keepalive: 60
# 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
- clearSession: true
\ No newline at end of file
+ clearSession: true
+
+
+# 插件配置
+pf4j:
+ pluginsDir: ${user.home}/plugins # 插件目录
\ No newline at end of file
diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml
index e5ae6d195a..b6492a1f46 100644
--- a/yudao-server/src/main/resources/application-local.yaml
+++ b/yudao-server/src/main/resources/application-local.yaml
@@ -45,7 +45,7 @@ spring:
primary: master
datasource:
master:
- 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 连接的示例
+ url: jdbc:mysql://chaojiniu.top:23306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# 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 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
@@ -53,8 +53,8 @@ spring:
# url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
# url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例
- username: root
- password: ahh@123456
+ username: ruoyi-vue-pro
+ password: ruoyi-@h2ju02hebp
# username: sa # SQL Server 连接的示例
# password: Yudao@2024 # SQL Server 连接的示例
# username: SYSDBA # DM 连接的示例
@@ -63,17 +63,25 @@ spring:
# password: Yudao@2024 # OpenGauss 连接的示例
slave: # 模拟从库,可根据自己需要修改
lazy: true # 开启懒加载,保证启动速度
- url: jdbc:mysql://127.0.0.1:3307/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+ url: jdbc:mysql://chaojiniu.top:23306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+ username: ruoyi-vue-pro
+ password: ruoyi-@h2ju02hebp
+ tdengine: # IOT 数据库
+# lazy: true # 开启懒加载,保证启动速度
+ url: jdbc:TAOS-RS://chaojiniu.top:6041/ruoyi_vue_pro
+ driver-class-name: com.taosdata.jdbc.rs.RestfulDriver
username: root
- password: ahh@123456
+ password: taosdata
+ druid:
+ validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:
redis:
- host: 127.0.0.1 # 地址
+ host: chaojiniu.top # 地址
port: 6379 # 端口
- database: 0 # 数据库索引
-# password: dev # 密码,建议生产环境开启
+ database: 15 # 数据库索引
+ password: fsknKD7UvQYZsyf2hXXn # 密码,建议生产环境开启
--- #################### 定时任务相关配置 ####################
@@ -175,8 +183,10 @@ logging:
cn.iocoder.yudao.module.crm.dal.mysql: debug
cn.iocoder.yudao.module.erp.dal.mysql: debug
cn.iocoder.yudao.module.iot.dal.mysql: debug
+ cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG
cn.iocoder.yudao.module.ai.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
+ com.taosdata: DEBUG # TDengine 的日志级别
debug: false
@@ -259,7 +269,7 @@ justauth:
iot:
emq:
# 账号
- username: anhaohao
+ username: haohao
# 密码
password: ahh@123456
# 主机地址
@@ -271,4 +281,18 @@ iot:
# 保持连接
keepalive: 60
# 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
- clearSession: true
\ No newline at end of file
+ clearSession: true
+
+# MQTT-RPC 配置
+mqtt:
+ broker: tcp://chaojiniu.top:1883
+ username: haohao
+ password: ahh@123456
+ clientId: mqtt-rpc-server-${random.int}
+ requestTopic: rpc/request
+ responseTopicPrefix: rpc/response/
+
+
+# 插件配置
+pf4j:
+ pluginsDir: /Users/anhaohao/code/gitee/ruoyi-vue-pro/plugins # 插件目录
\ No newline at end of file