From dc1f9338f153d2351d3c473d08a582c00842745b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Wed, 1 Jan 2025 22:34:26 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91IoT:=20=E6=B7=BB=E5=8A=A0=20MQTT=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=94=AF=E6=8C=81=EF=BC=8C=E9=87=8D=E6=9E=84=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=AE=A1=E7=90=86=EF=BC=8C=E6=96=B0=E5=A2=9E=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=BF=90=E8=A1=8C=E7=8A=B6=E6=80=81=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=8E=A5=E5=8F=A3=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E4=BF=A1=E6=81=AF=E5=AD=98=E5=82=A8=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E7=A7=BB=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81?= =?UTF-8?q?=E7=9A=84=20Spring=20=E6=B3=A8=E8=A7=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/enabled.txt | 1 - .../dal/mysql/plugin/PluginInfoMapper.java | 9 + .../module/iot/emq/callback/EmqxCallback.java | 2 +- .../module/iot/emq/client/EmqxClient.java | 2 +- .../module/iot/emq/config/MqttConfig.java | 4 +- .../iot/emq/service/EmqxServiceImpl.java | 2 +- .../yudao/module/iot/emq/start/EmqxStart.java | 2 +- .../iot/service/plugin/PluginInfoService.java | 9 +- .../service/plugin/PluginInfoServiceImpl.java | 14 +- .../yudao-module-iot-plugin/pom.xml | 6 +- .../plugin.properties | 6 + .../yudao-module-iot-mqtt-plugin/pom.xml | 154 ++++++++++++++++++ .../src/main/assembly/assembly.xml | 31 ++++ .../yudao/module/iot/plugin/MqttPlugin.java | 48 ++++++ 14 files changed, 272 insertions(+), 18 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/assembly/assembly.xml create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java diff --git a/plugins/enabled.txt b/plugins/enabled.txt index a5ecb9379..8cf9b4c87 100644 --- a/plugins/enabled.txt +++ b/plugins/enabled.txt @@ -1,2 +1 @@ http-plugin -http-plugin@0.0.1 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java index 228519fb6..9806ef17f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java @@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; + +import java.util.List; + import org.apache.ibatis.annotations.Mapper; import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.*; @@ -22,4 +25,10 @@ public interface PluginInfoMapper extends BaseMapperX { .orderByDesc(PluginInfoDO::getId)); } + default List selectListByStatus(Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(PluginInfoDO::getStatus, status) + .orderByAsc(PluginInfoDO::getId)); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java index b466113f7..fbc6ffcdb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java @@ -17,7 +17,7 @@ import org.springframework.stereotype.Component; * @author ahh */ @Slf4j -@Component +//@Component public class EmqxCallback implements MqttCallbackExtended { @Lazy diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java index de24585b0..577a808dc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java @@ -19,7 +19,7 @@ import org.springframework.stereotype.Component; */ @Slf4j @Data -@Component +//@Component public class EmqxClient { @Resource diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java index 9d128903c..8e32c185d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java @@ -12,8 +12,8 @@ import org.springframework.stereotype.Component; * @author ahh */ @Data -@Component -@ConfigurationProperties("iot.emq") +//@Component +//@ConfigurationProperties("iot.emq") public class MqttConfig { /** diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java index 46217a22b..2c1553a72 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java @@ -16,7 +16,7 @@ import org.springframework.stereotype.Service; * @author ahh */ @Slf4j -@Service +//@Service public class EmqxServiceImpl implements EmqxService { @Resource diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java index 0c316b66c..64265fdc3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; * * @author ahh */ -@Component +//@Component public class EmqxStart implements ApplicationRunner { @Resource diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java index 56b7e95e1..6a44747a6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java @@ -76,4 +76,11 @@ public interface PluginInfoService { * @return 插件信息列表 */ List getPluginInfoList(); -} \ No newline at end of file + + /** + * 获得运行状态的插件信息列表 + * + * @return 运行状态的插件信息列表 + */ + List getRunningPluginInfoList(); +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java index 4e7f2e996..7c856447b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java @@ -18,6 +18,7 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import org.springframework.web.multipart.MultipartFile; +import javax.annotation.PostConstruct; import java.io.File; import java.io.IOException; import java.nio.file.*; @@ -186,20 +187,19 @@ public class PluginInfoServiceImpl implements PluginInfoService { if (pluginWrapper == null) { throw exception(PLUGIN_INSTALL_FAILED); } - String pluginInfo = pluginKeyNew + "@" + pluginWrapper.getDescriptor().getVersion(); List targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) : new ArrayList<>(); List oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) : new ArrayList<>(); - if (!targetLines.contains(pluginInfo)) { - targetLines.add(pluginInfo); + if (!targetLines.contains(pluginKeyNew)) { + targetLines.add(pluginKeyNew); Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } - if (oppositeLines.contains(pluginInfo)) { - oppositeLines.remove(pluginInfo); + if (oppositeLines.contains(pluginKeyNew)) { + oppositeLines.remove(pluginKeyNew); Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } @@ -267,4 +267,8 @@ public class PluginInfoServiceImpl implements PluginInfoService { return pluginInfoMapper.selectList(null); } + @Override + public List getRunningPluginInfoList() { + return pluginInfoMapper.selectListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); + } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/pom.xml index c8f0ff0fe..4a46b6167 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/pom.xml +++ b/yudao-module-iot/yudao-module-iot-plugin/pom.xml @@ -2,11 +2,6 @@ - - - - - yudao-module-iot cn.iocoder.boot @@ -15,6 +10,7 @@ yudao-module-iot-demo-plugin yudao-module-iot-http-plugin + yudao-module-iot-mqtt-plugin 4.0.0 diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties new file mode 100644 index 000000000..31050c5ba --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties @@ -0,0 +1,6 @@ +plugin.id=mqtt-plugin +plugin.class=cn.iocoder.yudao.module.iot.plugin.MqttPlugin +plugin.version=0.0.1 +plugin.provider=ahh +plugin.dependencies= +plugin.description=mqtt-plugin-0.0.1 diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml new file mode 100644 index 000000000..d5d48d09a --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml @@ -0,0 +1,154 @@ + + + + yudao-module-iot-plugin + cn.iocoder.boot + ${revision} + + 4.0.0 + jar + + yudao-module-iot-mqtt-plugin + + ${project.artifactId} + + 物联网 插件模块 - mqtt 插件 + + + + + mqtt-plugin + cn.iocoder.yudao.module.iot.plugin.MqttPlugin + 0.0.1 + ahh + mqtt-plugin-0.0.1 + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.6 + + + unzip jar file + package + + + + + + + run + + + + + + + maven-assembly-plugin + 2.3 + + + + src/main/assembly/assembly.xml + + + false + + + + make-assembly + package + + attached + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + ${plugin.id} + ${plugin.class} + ${plugin.version} + ${plugin.provider} + ${plugin.description} + ${plugin.dependencies} + + + + + + + maven-deploy-plugin + + true + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.pf4j + pf4j-spring + provided + + + + cn.iocoder.boot + yudao-module-iot-api + ${revision} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + io.netty + netty-all + 4.1.63.Final + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/assembly/assembly.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/assembly/assembly.xml new file mode 100644 index 000000000..daec9e431 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/assembly/assembly.xml @@ -0,0 +1,31 @@ + + plugin + + zip + + false + + + false + runtime + lib + + *:jar:* + + + + + + + target/plugin-classes + classes + + + diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java new file mode 100644 index 000000000..5b50c7124 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java @@ -0,0 +1,48 @@ +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 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 MqttPlugin extends Plugin { + + private ExecutorService executorService; + private DeviceDataApi deviceDataApi; + + public MqttPlugin(PluginWrapper wrapper) { + super(wrapper); + // 初始化线程池 + this.executorService = Executors.newSingleThreadExecutor(); + } + + @Override + public void start() { + log.info("MqttPlugin.start()"); + + // 重新初始化线程池,确保它是活跃的 + if (executorService.isShutdown() || executorService.isTerminated()) { + executorService = Executors.newSingleThreadExecutor(); + } + + // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例 + deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); + if (deviceDataApi == null) { + log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); + return; + } + } + + @Override + public void stop() { + log.info("MqttPlugin.stop()"); + // 停止线程池 + executorService.shutdownNow(); + } + +} \ No newline at end of file From 0249ca9929141c8520e67065b3573aa26fdcf7c0 Mon Sep 17 00:00:00 2001 From: alwayssuper <191763414@qq.com> Date: Mon, 6 Jan 2025 11:04:39 +0800 Subject: [PATCH 02/11] =?UTF-8?q?[=E5=8A=9F=E8=83=BD=E6=B7=BB=E5=8A=A0]?= =?UTF-8?q?=EF=BC=9A=E7=89=A9=E6=A8=A1=E5=9E=8B=E6=97=A5=E5=BF=97=E8=A1=A8?= =?UTF-8?q?=E5=88=9B=E5=BB=BA.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device/IotDeviceDataController.java | 2 +- .../IotDeviceDataSimulatorSaveReqVO.java | 1 - .../dal/dataobject/device/IotDeviceLogDO.java | 4 +- .../dal/tdengine/IotDeviceLogDataMapper.java | 37 +++++++++++++++++++ .../device/IotDeviceLogDataService.java | 28 ++++++++++++++ .../device/IotDeviceLogDataServiceImpl.java | 24 +++++++++++- .../mapper/device/IotDeviceLogDataMapper.xml | 2 +- .../src/main/resources/application-local.yaml | 33 ++++++++++++----- 8 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java index 2675f60e5..9a95eb995 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java @@ -55,7 +55,7 @@ public class IotDeviceDataController { @PostMapping("/simulator") @Operation(summary = "模拟设备") public CommonResult simulatorDevice(@Valid @RequestBody IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { - //TODO:先生成一下日志 后续完善模拟设备代码逻辑 + //TODO:先生成一下设备日志 后续完善模拟设备代码逻辑 iotDeviceLogDataService.createDeviceLog(simulatorReqVO); return success(true); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java index 8e1ed3c23..507998176 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import lombok.Data; - import java.time.LocalDateTime; @Schema(description = "管理后台 - IoT 模拟设备数据 Request VO") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java index 4a30ed791..c82b231c5 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java @@ -51,12 +51,12 @@ public class IotDeviceLogDO { /** * 上报时间戳 */ - private DateTime reportTime; + private Long reportTime; /** * 时序时间 */ - private DateTime ts; + private Long ts; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java new file mode 100644 index 000000000..95fa3a133 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.iot.dal.tdengine; + +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; +import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS; +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * IOT 设备日志数据 Mapper 接口 + * + * 基于 TDengine 实现设备日志的存储 + */ +@Mapper +@TDengineDS +@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错 +public interface IotDeviceLogDataMapper { + + /** + * 创建设备日志超级表 + * + * 注意:初始化时只需创建一次 + */ + void createDeviceLogSTable(); + + + + + /** + * 插入设备日志数据 + * + * 如果子表不存在,会自动创建子表 + * + * @param log 设备日志数据 + */ + void insert(@Param("log") IotDeviceLogDO log); +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java new file mode 100644 index 000000000..3a68ebad8 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.iot.service.device; + +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; + +/** + * IoT 设备日志数据 Service 接口 + * + * @author alwayssuper + */ +public interface IotDeviceLogDataService { + + /** + * 初始化 TDengine 超级表 + * + *系统启动时,会自动初始化一次 + */ + void initTDengineSTable(); + + /** + * 插入设备日志 + * + * 当该设备第一次插入日志时,自动创建该设备的设备日志子表 + * + * @param simulatorReqVO 设备日志模拟数据 + */ + void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO); + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java index 528477cf9..7fe46b050 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java @@ -11,6 +11,7 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; +import java.time.ZoneId; /** * IoT 设备日志数据 Service 实现了 @@ -19,6 +20,7 @@ import java.time.LocalDateTime; */ @Service @Slf4j +@Validated public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{ @Resource @@ -38,8 +40,28 @@ public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{ @Override public void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { + //TODO:讨论一下,iotkit这块TS和上报时间都是外部传入的 但是看TDengine文档 他是建议对TS在SQL中直接NOW 咱们的TS数据获取是走哪一种 + + // 1. 转换请求对象为 DO IotDeviceLogDO iotDeviceLogDO = BeanUtils.toBean(simulatorReqVO, IotDeviceLogDO.class); - iotDeviceLogDO.setTs(DateTime.now()); + + // 2. 处理时间字段 + long currentTime = System.currentTimeMillis(); + // 2.1 设置时序时间为当前时间 + iotDeviceLogDO.setTs(currentTime); + + // 2.2 处理上报时间 + if (simulatorReqVO.getReportTime() != null) { + // 将 LocalDateTime 转换为时间戳 + iotDeviceLogDO.setReportTime( + simulatorReqVO.getReportTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() + ); + } else { + // 如果没有上报时间,使用当前时间 + iotDeviceLogDO.setReportTime(currentTime); + } + + // 3. 插入数据 iotDeviceLogDataMapper.insert(iotDeviceLogDO); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml index db18b3022..71c7dcefe 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml @@ -6,7 +6,7 @@ - CREATE STABLE device_log( + CREATE STABLE device_log ( ts TIMESTAMP, id NCHAR(50), product_key NCHAR(50), diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index e5ae6d195..aac594f40 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,7 @@ iot: # 保持连接 keepalive: 60 # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) - clearSession: true \ No newline at end of file + clearSession: true + +pf4j: + pluginsDir: /Users/anhaohao/code/gitee/ruoyi-vue-pro/plugins \ No newline at end of file From 6aad4545a89faecf91cf4b8f9af442ab4ea44c0e Mon Sep 17 00:00:00 2001 From: alwayssuper <191763414@qq.com> Date: Mon, 6 Jan 2025 16:43:37 +0800 Subject: [PATCH 03/11] =?UTF-8?q?[=E5=8A=9F=E8=83=BD=E6=B7=BB=E5=8A=A0]?= =?UTF-8?q?=EF=BC=9A=E7=89=A9=E6=A8=A1=E5=9E=8B=E6=97=A5=E5=BF=97=E8=A1=A8?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E4=B8=8E=E5=88=9B=E5=BB=BA=20=20=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E8=AE=BE=E5=A4=87=E5=9F=BA=E7=A1=80=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device/IotDeviceDataController.java | 18 +++++++--- .../IotDeviceDataSimulatorSaveReqVO.java | 5 ++- .../vo/deviceData/IotDeviceLogPageReqVO.java | 30 ++++++++++++++++ .../vo/deviceData/IotDeviceLogRespVO.java | 33 ++++++++++++++++++ .../dal/tdengine/IotDeviceLogDataMapper.java | 28 +++++++++++++-- .../device/IotDeviceLogDataService.java | 12 +++++++ .../device/IotDeviceLogDataServiceImpl.java | 25 +++++++------- .../mapper/device/IotDeviceLogDataMapper.xml | 34 +++++++++++++++++++ 8 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java index 9a95eb995..801cbcb21 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java @@ -4,11 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.*; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.service.device.IotDeviceLogDataService; import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; import io.swagger.v3.oas.annotations.Operation; @@ -36,6 +34,9 @@ public class IotDeviceDataController { @Resource private IotDeviceLogDataService iotDeviceLogDataService; + @Resource + private IotDeviceLogDataService deviceLogDataService; + // TODO @浩浩:这里的 /latest-list,包括方法名。 @GetMapping("/latest") @Operation(summary = "获取设备属性最新数据") @@ -51,7 +52,7 @@ public class IotDeviceDataController { PageResult> list = deviceDataService.getHistoryDeviceProperties(deviceDataReqVO); return success(BeanUtils.toBean(list, IotTimeDataRespVO.class)); } - + // TODO:数据权限 @PostMapping("/simulator") @Operation(summary = "模拟设备") public CommonResult simulatorDevice(@Valid @RequestBody IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { @@ -59,5 +60,12 @@ public class IotDeviceDataController { iotDeviceLogDataService.createDeviceLog(simulatorReqVO); return success(true); } + // TODO:数据权限 + @GetMapping("/log/page") + @Operation(summary = "获得设备日志分页") + public CommonResult> getDeviceLogPage(@Valid IotDeviceLogPageReqVO pageReqVO) { + PageResult pageResult = deviceLogDataService.getDeviceLogPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, IotDeviceLogRespVO.class)); + } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java index 507998176..3a4bfface 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java @@ -3,9 +3,12 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + @Schema(description = "管理后台 - IoT 模拟设备数据 Request VO") @Data public class IotDeviceDataSimulatorSaveReqVO { @@ -34,6 +37,6 @@ public class IotDeviceDataSimulatorSaveReqVO { private String content; @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime reportTime; + private Long reportTime; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java new file mode 100644 index 000000000..67099f331 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - IoT 设备日志分页查询 Request VO") +@Data +public class IotDeviceLogPageReqVO extends PageParam { + + @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123") + @NotEmpty(message = "设备标识不能为空") + private String deviceKey; + + @Schema(description = "消息类型", example = "property") + private String type; + + @Schema(description = "标识符", example = "temperature") + private String subType; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java new file mode 100644 index 000000000..1201f7b74 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IoT 设备日志 Response VO") +@Data +public class IotDeviceLogRespVO { + + @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123") + private String productKey; + @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123") + private String deviceKey; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property") + private String type; + + @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature") + private String subType; + + @Schema(description = "日志内容", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime reportTime; + + @Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime ts; +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java index 95fa3a133..51fd625ad 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java @@ -1,11 +1,15 @@ package cn.iocoder.yudao.module.iot.dal.tdengine; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.util.List; + /** * IOT 设备日志数据 Mapper 接口 * @@ -23,8 +27,12 @@ public interface IotDeviceLogDataMapper { */ void createDeviceLogSTable(); - - + /** + * 创建设备日志子表 + * + * @param deviceKey 设备标识 + */ + void createDeviceLogTable(@Param("deviceKey") String deviceKey); /** * 插入设备日志数据 @@ -34,4 +42,20 @@ public interface IotDeviceLogDataMapper { * @param log 设备日志数据 */ void insert(@Param("log") IotDeviceLogDO log); + + /** + * 获得设备日志分页 + * + * @param reqVO 分页查询条件 + * @return 设备日志列表 + */ + List selectPage(@Param("reqVO") IotDeviceLogPageReqVO reqVO); + + /** + * 获得设备日志总数 + * + * @param reqVO 查询条件 + * @return 日志总数 + */ + Long selectCount(@Param("reqVO") IotDeviceLogPageReqVO reqVO); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java index 3a68ebad8..72cbf63e9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java @@ -1,6 +1,10 @@ package cn.iocoder.yudao.module.iot.service.device; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; + /** * IoT 设备日志数据 Service 接口 @@ -25,4 +29,12 @@ public interface IotDeviceLogDataService { */ void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO); + /** + * 获得设备日志分页 + * + * @param pageReqVO 分页查询 + * @return 设备日志分页 + */ + PageResult getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java index 7fe46b050..bf883526d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.module.iot.service.device; import cn.hutool.core.date.DateTime; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceLogDataMapper; import jakarta.annotation.Resource; @@ -12,6 +14,7 @@ import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.List; /** * IoT 设备日志数据 Service 实现了 @@ -49,19 +52,17 @@ public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{ long currentTime = System.currentTimeMillis(); // 2.1 设置时序时间为当前时间 iotDeviceLogDO.setTs(currentTime); - - // 2.2 处理上报时间 - if (simulatorReqVO.getReportTime() != null) { - // 将 LocalDateTime 转换为时间戳 - iotDeviceLogDO.setReportTime( - simulatorReqVO.getReportTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - ); - } else { - // 如果没有上报时间,使用当前时间 - iotDeviceLogDO.setReportTime(currentTime); - } - + // 3. 插入数据 iotDeviceLogDataMapper.insert(iotDeviceLogDO); } + + @Override + public PageResult getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO) { + // 查询数据 + List list = iotDeviceLogDataMapper.selectPage(pageReqVO); + Long total = iotDeviceLogDataMapper.selectCount(pageReqVO); + // 构造分页结果 + return new PageResult<>(list, total); + } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml index 71c7dcefe..dd0f80a94 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml @@ -41,4 +41,38 @@ ) + + + + \ No newline at end of file From 603649d2485ec36f30b77ae2a17957fb4bf28100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Mon, 6 Jan 2025 18:59:26 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91IoT:=20=E6=96=B0=E5=A2=9E=20MQTT=20RPC=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E5=8C=85=E5=90=AB=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=92=8C=E5=93=8D=E5=BA=94=E6=A8=A1=E5=9E=8B=E3=80=81=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E5=B7=A5=E5=85=B7=E3=80=81MQTT=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8F=8A=E5=AE=A2=E6=88=B7=E7=AB=AF/=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E5=AE=9E=E7=8E=B0=EF=BC=8C=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B=E6=9C=8D=E5=8A=A1=E5=92=8C=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E6=8E=A5=E5=8F=A3=EF=BC=8C=E4=BC=98=E5=8C=96=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=BB=93=E6=9E=84=E4=BB=A5=E6=94=AF=E6=8C=81=20HTTP?= =?UTF-8?q?=20=E6=8F=92=E4=BB=B6=E7=9A=84=E9=9B=86=E6=88=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/iot/mqttrpc/common/RpcRequest.java | 37 ++++++++ .../iot/mqttrpc/common/RpcResponse.java | 32 +++++++ .../mqttrpc/common/SerializationUtils.java | 18 ++++ .../module/iot/mqttrpc/config/MqttConfig.java | 40 ++++++++ .../module/iot/mqttrpc/server/RpcServer.java | 95 +++++++++++++++++++ .../iot/service/plugin/ExampleService.java | 43 +++++++++ .../service/plugin/PluginInfoServiceImpl.java | 1 - .../yudao-module-iot-http-plugin/pom.xml | 5 + .../iot/HttpPluginSpringbootApplication.java | 11 +++ .../module/iot/controller/RpcController.java | 31 ++++++ .../module/iot/mqttrpc/client/RpcClient.java | 95 +++++++++++++++++++ .../module/iot/mqttrpc/config/MqttConfig.java | 41 ++++++++ .../src/main/resources/application.yml | 15 +++ .../yudao-module-iot-mqtt-plugin/pom.xml | 6 +- .../yudao/module/iot/plugin/MqttPlugin.java | 13 +-- 15 files changed, 471 insertions(+), 12 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java create mode 100644 yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java create mode 100644 yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/ExampleService.java create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/HttpPluginSpringbootApplication.java create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/resources/application.yml diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java new file mode 100644 index 000000000..14e84175c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * MQTT RPC 请求 + * + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RpcRequest { + + /** + * 方法名 + */ + private String method; + + /** + * 参数 + */ + private Object[] params; + + /** + * 关联 ID + */ + private String correlationId; + + /** + * 回复地址 + */ + private String replyTo; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java new file mode 100644 index 000000000..675a6ee71 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * MQTT RPC 响应 + * + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RpcResponse { + + /** + * 关联 ID + */ + private String correlationId; + + /** + * 结果 + */ + private Object result; + + /** + * 错误 + */ + private String error; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java new file mode 100644 index 000000000..1529e2dba --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.common; + +import cn.hutool.json.JSONUtil; + +/** + * 序列化工具类 + * + */ +public class SerializationUtils { + + public static String serialize(Object obj) { + return JSONUtil.toJsonStr(obj); + } + + public static T deserialize(String json, Class clazz) { + return JSONUtil.toBean(json, clazz); + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java new file mode 100644 index 000000000..c7a050003 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "mqtt") +public class MqttConfig { + /** + * MQTT 代理地址 + */ + private String broker; + + /** + * MQTT 用户名 + */ + private String username; + + /** + * MQTT 密码 + */ + private String password; + + /** + * MQTT 客户端 ID + */ + private String clientId; + + /** + * MQTT 请求主题 + */ + private String requestTopic; + + /** + * MQTT 响应主题前缀 + */ + private String responseTopicPrefix; +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java new file mode 100644 index 000000000..be6ca6f83 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.server; + +import cn.hutool.core.lang.UUID; +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcRequest; +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcResponse; +import cn.iocoder.yudao.module.iot.mqttrpc.common.SerializationUtils; +import cn.iocoder.yudao.module.iot.mqttrpc.config.MqttConfig; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.HashMap; +import java.util.Map; + +@Service +@Slf4j +public class RpcServer { + + private final MqttConfig mqttConfig; + private final MqttClient mqttClient; + private final Map methodRegistry = new HashMap<>(); + + public RpcServer(MqttConfig mqttConfig) throws MqttException { + this.mqttConfig = mqttConfig; + this.mqttClient = new MqttClient(mqttConfig.getBroker(), "rpc-server-" + UUID.randomUUID(), new MemoryPersistence()); + MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setCleanSession(true); + options.setUserName(mqttConfig.getUsername()); + options.setPassword(mqttConfig.getPassword().toCharArray()); + this.mqttClient.connect(options); + } + + @PostConstruct + public void init() throws MqttException { + mqttClient.subscribe(mqttConfig.getRequestTopic(), this::handleRequest); + log.info("RPC Server subscribed to topic: {}", mqttConfig.getRequestTopic()); + } + + private void handleRequest(String topic, MqttMessage message) { + RpcRequest request = SerializationUtils.deserialize(new String(message.getPayload()), RpcRequest.class); + RpcResponse response = new RpcResponse(); + response.setCorrelationId(request.getCorrelationId()); + + try { + MethodInvoker invoker = methodRegistry.get(request.getMethod()); + if (invoker == null) { + throw new NoSuchMethodException("Unknown method: " + request.getMethod()); + } + Object result = invoker.invoke(request.getParams()); + response.setResult(result); + } catch (Exception e) { + response.setError(e.getMessage()); + log.error("Error processing RPC request: {}", e.getMessage(), e); + } + + String replyPayload = SerializationUtils.serialize(response); + MqttMessage replyMessage = new MqttMessage(replyPayload.getBytes()); + replyMessage.setQos(1); + try { + mqttClient.publish(request.getReplyTo(), replyMessage); + log.info("Published response to {}", request.getReplyTo()); + } catch (MqttException e) { + log.error("Failed to publish response: {}", e.getMessage(), e); + } + } + + /** + * 注册可调用的方法 + * + * @param methodName 方法名称 + * @param invoker 方法调用器 + */ + public void registerMethod(String methodName, MethodInvoker invoker) { + methodRegistry.put(methodName, invoker); + log.info("Registered method: {}", methodName); + } + + @PreDestroy + public void cleanup() throws MqttException { + mqttClient.disconnect(); + log.info("RPC Server disconnected"); + } + + /** + * 方法调用器接口 + */ + @FunctionalInterface + public interface MethodInvoker { + Object invoke(Object[] params) throws Exception; + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/ExampleService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/ExampleService.java new file mode 100644 index 000000000..22ebe8b4f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/ExampleService.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.iot.service.plugin; + +import cn.iocoder.yudao.module.iot.mqttrpc.server.RpcServer; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; + +@Service +@RequiredArgsConstructor +public class ExampleService { + + private final RpcServer rpcServer; + + @PostConstruct + public void registerMethods() { + rpcServer.registerMethod("add", params -> { + if (params.length != 2) { + throw new IllegalArgumentException("add方法需要两个参数"); + } + int a = ((Number) params[0]).intValue(); + int b = ((Number) params[1]).intValue(); + return add(a, b); + }); + + rpcServer.registerMethod("concat", params -> { + if (params.length != 2) { + throw new IllegalArgumentException("concat方法需要两个参数"); + } + String str1 = params[0].toString(); + String str2 = params[1].toString(); + return concat(str1, str2); + }); + } + + private int add(int a, int b) { + return a + b; + } + + private String concat(String a, String b) { + return a + b; + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java index 7c856447b..77cf590a0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java @@ -18,7 +18,6 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import org.springframework.web.multipart.MultipartFile; -import javax.annotation.PostConstruct; import java.io.File; import java.io.IOException; import java.nio.file.*; diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml index 1ecf140a4..27c1d19a0 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml @@ -150,5 +150,10 @@ netty-all 4.1.63.Final + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + \ 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/HttpPluginSpringbootApplication.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/HttpPluginSpringbootApplication.java new file mode 100644 index 000000000..6b553f92b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/HttpPluginSpringbootApplication.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.iot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class HttpPluginSpringbootApplication { + public static void main(String[] args) { + SpringApplication.run(HttpPluginSpringbootApplication.class, args); + } +} \ 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/controller/RpcController.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java new file mode 100644 index 000000000..a5175a786 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java @@ -0,0 +1,31 @@ + +package cn.iocoder.yudao.module.iot.controller; + +import cn.iocoder.yudao.module.iot.mqttrpc.client.RpcClient; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.concurrent.CompletableFuture; + +@RestController +@RequestMapping("/rpc") +@RequiredArgsConstructor +public class RpcController { + + @Resource + private RpcClient rpcClient; + + @PostMapping("/add") + public CompletableFuture add(@RequestParam int a, @RequestParam int b) throws Exception { + return rpcClient.call("add", new Object[]{a, b}, 10); + } + + @PostMapping("/concat") + public CompletableFuture concat(@RequestParam String str1, @RequestParam String str2) throws Exception { + return rpcClient.call("concat", new Object[]{str1, str2}, 10); + } +} diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java new file mode 100644 index 000000000..73c1d936c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.client; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcRequest; +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcResponse; +import cn.iocoder.yudao.module.iot.mqttrpc.common.SerializationUtils; +import cn.iocoder.yudao.module.iot.mqttrpc.config.MqttConfig; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.UUID; +import java.util.concurrent.*; + +@Service +@Slf4j +public class RpcClient { + + private final MqttConfig mqttConfig; + private final MqttClient mqttClient; + private final ConcurrentMap> pendingRequests = new ConcurrentHashMap<>(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + public RpcClient(MqttConfig mqttConfig) throws MqttException { + this.mqttConfig = mqttConfig; + this.mqttClient = new MqttClient(mqttConfig.getBroker(), mqttConfig.getClientId(), new MemoryPersistence()); + MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setCleanSession(true); + options.setUserName(mqttConfig.getUsername()); + options.setPassword(mqttConfig.getPassword().toCharArray()); + this.mqttClient.connect(options); + } + + @PostConstruct + public void init() throws MqttException { + mqttClient.subscribe(mqttConfig.getResponseTopicPrefix() + "#", this::handleResponse); + log.info("RPC Client subscribed to topics: {}", mqttConfig.getResponseTopicPrefix() + "#"); + } + + private void handleResponse(String topic, MqttMessage message) { + String correlationId = topic.substring(mqttConfig.getResponseTopicPrefix().length()); + RpcResponse response = SerializationUtils.deserialize(new String(message.getPayload()), RpcResponse.class); + CompletableFuture future = pendingRequests.remove(correlationId); + if (future != null) { + if (response.getError() != null) { + future.completeExceptionally(new RuntimeException(response.getError())); + } else { + future.complete(response); + } + } else { + log.warn("Received response for unknown correlationId: {}", correlationId); + } + } + + public CompletableFuture call(String method, Object[] params, int timeoutSeconds) throws MqttException { + String correlationId = UUID.randomUUID().toString(); + String replyTo = mqttConfig.getResponseTopicPrefix() + correlationId; + + RpcRequest request = new RpcRequest(method, params, correlationId, replyTo); + String payload = SerializationUtils.serialize(request); + MqttMessage message = new MqttMessage(payload.getBytes()); + message.setQos(1); + mqttClient.publish(mqttConfig.getRequestTopic(), message); + + CompletableFuture futureResponse = new CompletableFuture<>(); + pendingRequests.put(correlationId, futureResponse); + + // 设置超时 + scheduler.schedule(() -> { + CompletableFuture removed = pendingRequests.remove(correlationId); + if (removed != null) { + removed.completeExceptionally(new TimeoutException("RPC call timed out")); + } + }, timeoutSeconds, TimeUnit.SECONDS); + + // 返回最终的结果 + return futureResponse.thenApply(RpcResponse::getResult); + } + + @PreDestroy + public void cleanup() throws MqttException { + mqttClient.disconnect(); + scheduler.shutdown(); + log.info("RPC Client disconnected"); + } +} \ 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/mqttrpc/config/MqttConfig.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java new file mode 100644 index 000000000..89569b0c3 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "mqtt") +public class MqttConfig { + + /** + * MQTT 代理地址 + */ + private String broker; + + /** + * MQTT 用户名 + */ + private String username; + + /** + * MQTT 密码 + */ + private String password; + + /** + * MQTT 客户端 ID + */ + private String clientId; + + /** + * MQTT 请求主题 + */ + private String requestTopic; + + /** + * MQTT 响应主题前缀 + */ + private String responseTopicPrefix; +} diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/resources/application.yml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/resources/application.yml new file mode 100644 index 000000000..ea2234f83 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/resources/application.yml @@ -0,0 +1,15 @@ +server: + port: 8092 + +spring: + application: + name: yudao-module-iot-http-plugin + +# MQTT-RPC 配置 +mqtt: + broker: tcp://chaojiniu.top:1883 + username: haohao + password: ahh@123456 + clientId: mqtt-rpc-client-${random.int} + requestTopic: rpc/request + responseTopicPrefix: rpc/response/ diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml index d5d48d09a..9607e0f93 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml @@ -145,10 +145,10 @@ ${lombok.version} provided + - io.netty - netty-all - 4.1.63.Final + org.eclipse.paho + org.eclipse.paho.client.mqttv3 \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java index 5b50c7124..b3749e402 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java @@ -1,11 +1,12 @@ 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 cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; import lombok.extern.slf4j.Slf4j; -import org.pf4j.PluginWrapper; import org.pf4j.Plugin; +import org.pf4j.PluginWrapper; +import javax.annotation.Resource; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -13,11 +14,11 @@ import java.util.concurrent.Executors; public class MqttPlugin extends Plugin { private ExecutorService executorService; + @Resource private DeviceDataApi deviceDataApi; public MqttPlugin(PluginWrapper wrapper) { super(wrapper); - // 初始化线程池 this.executorService = Executors.newSingleThreadExecutor(); } @@ -25,24 +26,20 @@ public class MqttPlugin extends Plugin { public void start() { log.info("MqttPlugin.start()"); - // 重新初始化线程池,确保它是活跃的 if (executorService.isShutdown() || executorService.isTerminated()) { executorService = Executors.newSingleThreadExecutor(); } - // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例 deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); if (deviceDataApi == null) { log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); return; } + } @Override public void stop() { log.info("MqttPlugin.stop()"); - // 停止线程池 - executorService.shutdownNow(); } - } \ No newline at end of file From b5856c4cfc3a55e89928cb313670210af64a5f7c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 6 Jan 2025 20:24:47 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91IoT=EF=BC=9A=E6=8F=92=E4=BB=B6=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/iot/api/ServiceRegistry.java | 1 + .../module/iot/api/device/DeviceDataApi.java | 3 + .../module/iot/mqttrpc/common/RpcRequest.java | 4 +- .../iot/mqttrpc/common/RpcResponse.java | 3 +- .../mqttrpc/common/SerializationUtils.java | 1 + .../plugin/vo/PluginInfoImportReqVO.java | 2 +- .../admin/plugin/vo/PluginInfoRespVO.java | 17 ----- .../admin/plugin/vo/PluginInfoSaveReqVO.java | 4 + .../mysql/plugin/PluginInstanceMapper.java | 1 + .../iot/job/plugin/PluginInstancesJob.java | 2 +- .../module/iot/mqttrpc/server/RpcServer.java | 5 ++ .../service/plugin/PluginInfoServiceImpl.java | 76 ++++++++++--------- .../plugin/PluginInstanceServiceImpl.java | 29 +++---- .../module/iot/controller/RpcController.java | 1 + .../module/iot/mqttrpc/client/RpcClient.java | 12 ++- 15 files changed, 86 insertions(+), 75 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java index 5603ad8d7..a914e8029 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.api; import java.util.HashMap; import java.util.Map; +// TODO 芋艿:纠结下 /** * 服务注册表 - 插架模块使用,无法使用 Spring 注入 */ diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java index cb747f505..076064db8 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java @@ -2,9 +2,12 @@ package cn.iocoder.yudao.module.iot.api.device; /** * 设备数据 API + * + * @author haohao */ public interface DeviceDataApi { + // TODO @haohao:最好搞成 dto 哈! /** * 保存设备数据 * diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java index 14e84175c..b2a9f0360 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java @@ -5,9 +5,9 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +// TODO @芋艿:要不要加个 mqtt 值了的前缀 /** * MQTT RPC 请求 - * */ @Data @Builder @@ -23,6 +23,7 @@ public class RpcRequest { /** * 参数 */ + // TODO @haohao:object 对象会不会不好序列化? private Object[] params; /** @@ -34,4 +35,5 @@ public class RpcRequest { * 回复地址 */ private String replyTo; + } diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java index 675a6ee71..f3225d08e 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java @@ -7,7 +7,6 @@ import lombok.NoArgsConstructor; /** * MQTT RPC 响应 - * */ @Data @Builder @@ -23,10 +22,12 @@ public class RpcResponse { /** * 结果 */ + // TODO @haohao:object 对象会不会不好反序列化? private Object result; /** * 错误 */ private String error; + } diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java index 1529e2dba..620b00763 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java @@ -15,4 +15,5 @@ public class SerializationUtils { public static T deserialize(String json, Class clazz) { return JSONUtil.toBean(json, clazz); } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java index e71e4c484..bc8d6c8fa 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java @@ -9,7 +9,7 @@ import org.springframework.web.multipart.MultipartFile; @Data public class PluginInfoImportReqVO { - @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") + @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") private Long id; @Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java index 514ba4f1f..429102469 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java @@ -1,7 +1,5 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; -import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -9,63 +7,48 @@ import java.time.LocalDateTime; @Schema(description = "管理后台 - IoT 插件信息 Response VO") @Data -@ExcelIgnoreUnannotated public class PluginInfoRespVO { @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") - @ExcelProperty("主键 ID") private Long id; @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627") - @ExcelProperty("插件包标识符") private String pluginKey; @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") - @ExcelProperty("插件名称") private String name; @Schema(description = "描述", example = "你猜") - @ExcelProperty("描述") private String description; @Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @ExcelProperty("部署方式") private Integer deployType; @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("插件包文件名") private String fileName; @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("插件版本") private String version; @Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @ExcelProperty("插件类型") private Integer type; @Schema(description = "设备插件协议类型") - @ExcelProperty("设备插件协议类型") private String protocol; @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("状态") private Integer status; @Schema(description = "插件配置项描述信息") - @ExcelProperty("插件配置项描述信息") private String configSchema; @Schema(description = "插件配置信息") - @ExcelProperty("插件配置信息") private String config; @Schema(description = "插件脚本") - @ExcelProperty("插件脚本") private String script; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") private LocalDateTime createTime; } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java index 9a9848130..ad3b31fc1 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java @@ -7,6 +7,10 @@ import lombok.*; @Data public class PluginInfoSaveReqVO { + // TODO @haohao:新增的字段有点多,每个都需要哇? + + // TODO @haohao:一些枚举字段,需要加枚举校验。例如说,deployType、status、type 等 + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") private Long id; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java index 249082032..4f773aa06 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java @@ -21,6 +21,7 @@ public interface PluginInstanceMapper extends BaseMapperX { .eq(PluginInstanceDO::getPluginId, pluginId)); } + // TODO @haohao:这个还需要么?相关不用的 VO 可以删除 default PageResult selectPage(PluginInstancePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId()) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java index ca8398e51..47e7bf560 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.iot.job.plugin; - import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService; import org.springframework.scheduling.annotation.Scheduled; @@ -26,4 +25,5 @@ public class PluginInstancesJob { pluginInstanceService.updatePluginInstances(); }); } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java index be6ca6f83..90ce2a387 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java @@ -15,6 +15,8 @@ import javax.annotation.PreDestroy; import java.util.HashMap; import java.util.Map; +// TODO @芋艿:server 逻辑,再瞅瞅; +// TODO @haohao:如果只写在 iot biz 里,那么后续 server => client 貌似不方便?微信再讨论下~; @Service @Slf4j public class RpcServer { @@ -90,6 +92,9 @@ public class RpcServer { */ @FunctionalInterface public interface MethodInvoker { + Object invoke(Object[] params) throws Exception; + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java index 77cf590a0..c9030b924 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java @@ -41,18 +41,18 @@ public class PluginInfoServiceImpl implements PluginInfoService { @Resource private PluginInfoMapper pluginInfoMapper; + @Resource private SpringPluginManager pluginManager; + // TODO @芋艿:要不要换位置 @Value("${pf4j.pluginsDir}") private String pluginsDir; @Override public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) { - // 插入 PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class); pluginInfoMapper.insert(pluginInfo); - // 返回 return pluginInfo.getId(); } @@ -67,29 +67,29 @@ public class PluginInfoServiceImpl implements PluginInfoService { @Override public void deletePluginInfo(Long id) { - // 校验存在 + // 1.1 校验存在 PluginInfoDO pluginInfoDO = validatePluginInfoExists(id); - - // 停止插件 + // 1.2 停止插件 if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) { throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING); } - // 卸载插件 + // 2. 卸载插件 + // TODO @haohao:可以复用 stopAndUnloadPlugin PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey()); if (plugin != null) { - // 查询插件是否是启动状态 + // 停止插件 if (plugin.getPluginState().equals(PluginState.STARTED)) { - // 停止插件 pluginManager.stopPlugin(plugin.getPluginId()); } // 卸载插件 pluginManager.unloadPlugin(plugin.getPluginId()); } - // 删除 + // 3.1 删除 pluginInfoMapper.deleteById(id); - // 删除插件文件 + // 3.2 删除插件文件 + // TODO @haohao:这个直接主线程 sleep 就好了,不用单独开线程池哈。原因是,低频操作;另外,只有存在的时候,才 sleep + 删除; Executors.newSingleThreadExecutor().submit(() -> { try { TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 @@ -101,7 +101,6 @@ public class PluginInfoServiceImpl implements PluginInfoService { log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); } }); - } private PluginInfoDO validatePluginInfoExists(Long id) { @@ -127,22 +126,19 @@ public class PluginInfoServiceImpl implements PluginInfoService { // 1. 校验插件信息是否存在 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); - // 2. 获取插件标识 - String pluginKey = pluginInfoDo.getPluginKey(); + // 2. 停止并卸载旧的插件 + stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); - // 3. 停止并卸载旧的插件 - stopAndUnloadPlugin(pluginKey); - - // 4. 上传新的插件文件 + // 3.1 上传新的插件文件 String pluginKeyNew = uploadAndLoadNewPlugin(file); - - // 5. 更新插件启用状态文件 + // 3.2 更新插件启用状态文件 updatePluginStatusFile(pluginKeyNew, false); - // 6. 更新插件信息 + // 4. 更新插件信息 updatePluginInfo(pluginInfoDo, pluginKeyNew, file); } + // TODO @haohao:注释的格式 // 停止并卸载旧的插件 private void stopAndUnloadPlugin(String pluginKey) { PluginWrapper plugin = pluginManager.getPlugin(pluginKey); @@ -154,10 +150,13 @@ public class PluginInfoServiceImpl implements PluginInfoService { } } + // TODO @haohao:注释的格式 // 上传并加载新的插件文件 private String uploadAndLoadNewPlugin(MultipartFile file) { + // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载 Path pluginsPath = Paths.get(pluginsDir); try { + // TODO @haohao:可以使用 FileUtil 简化? if (!Files.exists(pluginsPath)) { Files.createDirectories(pluginsPath); // 创建插件目录 } @@ -166,16 +165,18 @@ public class PluginInfoServiceImpl implements PluginInfoService { Path jarPath = pluginsPath.resolve(filename); Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件 return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件 - } else { - throw exception(PLUGIN_INSTALL_FAILED); } + throw exception(PLUGIN_INSTALL_FAILED); // TODO @haohao:这么抛的话,貌似会被 catch (Exception e) { } catch (Exception e) { + // TODO @haohao:打个 error log,方便排查 throw exception(PLUGIN_INSTALL_FAILED); } } + // TODO @haohao:注释的格式 // 更新插件状态文件 private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) { + // TODO @haohao:疑问,这里写 enabled.txt 和 disabled.txt 的目的是啥哈? Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt"); Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt"); Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath; @@ -186,10 +187,8 @@ public class PluginInfoServiceImpl implements PluginInfoService { if (pluginWrapper == null) { throw exception(PLUGIN_INSTALL_FAILED); } - List targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) - : new ArrayList<>(); - List oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) - : new ArrayList<>(); + List targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) : new ArrayList<>(); + List oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) : new ArrayList<>(); if (!targetLines.contains(pluginKeyNew)) { targetLines.add(pluginKeyNew); @@ -207,26 +206,33 @@ public class PluginInfoServiceImpl implements PluginInfoService { } } + // TODO @haohao:注释的格式 // 更新插件信息 private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) { + // TODO @haohao:更新实体的时候,最好 new 一个新的! + // TODO @haohao:可以链式调用,简化下代码; pluginInfoDo.setPluginKey(pluginKeyNew); pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus()); pluginInfoDo.setFileName(file.getOriginalFilename()); pluginInfoDo.setScript(""); - + // 解析 pf4j 插件 PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor(); pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription()); pluginInfoDo.setVersion(pluginDescriptor.getVersion()); pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription()); + + // 执行更新 pluginInfoMapper.updateById(pluginInfoDo); } + // TODO @haohao:status、state 字段命名,要统一下~ @Override public void updatePluginStatus(Long id, Integer status) { // 1. 校验插件信息是否存在 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); // 2. 校验插件状态是否有效 + // TODO @haohao:直接参数校验掉。通过 @InEnum if (!IotPluginStatusEnum.contains(status)) { throw exception(PLUGIN_STATUS_INVALID); } @@ -237,17 +243,16 @@ public class PluginInfoServiceImpl implements PluginInfoService { // 4. 根据状态更新插件 if (plugin != null) { - // 4.1 如果目标状态是运行且插件未启动,则启动插件 + // 4.1 启动:如果目标状态是运行且插件未启动,则启动插件 if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) && plugin.getPluginState() != PluginState.STARTED) { pluginManager.startPlugin(pluginKey); - updatePluginStatusFile(pluginKey, true); // 更新插件状态文件为启用 - } - // 4.2 如果目标状态是停止且插件已启动,则停止插件 - else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) + updatePluginStatusFile(pluginKey, true); + // 4.2 停止:如果目标状态是停止且插件已启动,则停止插件 + } else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) && plugin.getPluginState() == PluginState.STARTED) { pluginManager.stopPlugin(pluginKey); - updatePluginStatusFile(pluginKey, false); // 更新插件状态文件为禁用 + updatePluginStatusFile(pluginKey, false); } } else { // 5. 插件不存在且状态为停止,抛出异常 @@ -257,17 +262,20 @@ public class PluginInfoServiceImpl implements PluginInfoService { } // 6. 更新数据库中的插件状态 + // TODO @haohao:新建新建 pluginInfoDo 哈! pluginInfoDo.setStatus(status); pluginInfoMapper.updateById(pluginInfoDo); } @Override public List getPluginInfoList() { - return pluginInfoMapper.selectList(null); + return pluginInfoMapper.selectList(); } + // TODO @haohao:可以改成 getPluginInfoListByStatus 更通用哈。 @Override public List getRunningPluginInfoList() { return pluginInfoMapper.selectListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java index 52d79207b..6a65fc026 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java @@ -26,8 +26,9 @@ import java.util.List; public class PluginInstanceServiceImpl implements PluginInstanceService { /** - * 主程序id + * 主程序 ID */ + // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 public static final String MAIN_ID = IdUtil.fastSimpleUUID(); @Resource @@ -40,36 +41,37 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { @Value("${server.port:48080}") private int port; + // TODO @haohao:建议把 PluginInfoServiceImpl 里面,和 instance 相关的逻辑拿过来,可能会更好。info 处理信息,instance 处理实例 + // TODO @haohao:这个改成 reportPluginInstance 会不会更合适哈。 @Override public void updatePluginInstances() { - // 1. 查询 pf4j 插件列表 + // 1.1 查询 pf4j 插件列表 List plugins = pluginManager.getPlugins(); - - // 2. 查询插件信息列表 + // 1.2 查询插件信息列表 List pluginInfos = pluginInfoService.getPluginInfoList(); - - // 动态获取主程序的 IP 和端口 + // 1.3 动态获取主程序的 IP 和端口 String mainIp = getLocalIpAddress(); - // 3. 遍历插件列表,并保存为插件实例 + // 2. 遍历插件列表,并保存为插件实例 for (PluginWrapper plugin : plugins) { + // 2.1 查找插件信息 String pluginKey = plugin.getPluginId(); + // TODO @haohao:CollUtil.findOne() 简化 PluginInfoDO pluginInfo = pluginInfos.stream() .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey)) .findFirst() .orElse(null); - - // 4. 如果插件信息不存在,则跳过 if (pluginInfo == null) { + // TODO @haohao:建议打个 error log continue; } - // 5. 查询插件实例 + // 2.2 查询插件实例 PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId()); - - // 6. 如果插件实例不存在,则创建 + // 2.3.1 如果插件实例不存在,则创建 if (pluginInstance == null) { + // TODO @haohao:可以链式调用;建议新建一个! pluginInstance = new PluginInstanceDO(); pluginInstance.setPluginId(pluginInfo.getId()); pluginInstance.setMainId(MAIN_ID); @@ -78,13 +80,14 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { pluginInstance.setHeartbeatAt(System.currentTimeMillis()); pluginInstanceMapper.insert(pluginInstance); } else { - // 7. 如果插件实例存在,则更新 + // 2.3.2 如果插件实例存在,则更新 pluginInstance.setHeartbeatAt(System.currentTimeMillis()); pluginInstanceMapper.updateById(pluginInstance); } } } + // TODO @haohao:这个目的是,获取到第一个有效 ip 是哇? private String getLocalIpAddress() { try { List ipList = NetUtil.localIpv4s().stream() diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java index a5175a786..0a9ba9ee4 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java @@ -28,4 +28,5 @@ public class RpcController { public CompletableFuture concat(@RequestParam String str1, @RequestParam String str2) throws Exception { return rpcClient.call("concat", new Object[]{str1, str2}, 10); } + } diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java index 73c1d936c..b73f88c53 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java @@ -1,17 +1,14 @@ package cn.iocoder.yudao.module.iot.mqttrpc.client; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcRequest; import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcResponse; import cn.iocoder.yudao.module.iot.mqttrpc.common.SerializationUtils; import cn.iocoder.yudao.module.iot.mqttrpc.config.MqttConfig; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.springframework.stereotype.Service; @@ -20,6 +17,7 @@ import javax.annotation.PreDestroy; import java.util.UUID; import java.util.concurrent.*; +// TODO @芋艿:需要考虑,怎么公用! @Service @Slf4j public class RpcClient { From cde6ebf92189969ace8739724bc6b9ea925d9e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Tue, 7 Jan 2025 17:44:55 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91IoT:=20=E6=9B=B4=E6=96=B0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=20API=EF=BC=8C=E9=87=8D=E6=9E=84=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E8=AE=BE=E5=A4=87=E6=95=B0=E6=8D=AE=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E4=BB=A5=E4=BD=BF=E7=94=A8=20DTO=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=A0=A1=E9=AA=8C=E4=BE=9D=E8=B5=96=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=8F=92=E4=BB=B6=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B=E4=B8=8A=E6=8A=A5=E5=92=8C=E7=8A=B6=E6=80=81=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=90=8C=E6=97=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=8F=92=E4=BB=B6=E4=BF=A1=E6=81=AF=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E5=88=A0=E9=99=A4=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=E6=96=87=E4=BB=B6=E5=92=8C=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .vscode/settings.json | 5 + plugins/disabled.txt | 0 plugins/enabled.txt | 1 - ...-module-iot-http-plugin-2.2.0-snapshot.jar | Bin 8881 -> 16779 bytes yudao-module-iot/yudao-module-iot-api/pom.xml | 7 + .../module/iot/api/device/DeviceDataApi.java | 10 +- .../device/dto/DeviceDataCreateReqDTO.java | 31 +++ .../enums/plugin/IotPluginDeployTypeEnum.java | 4 +- .../iot/api/device/DeviceDataApiImpl.java | 5 +- .../admin/plugin/vo/PluginInfoSaveReqVO.java | 5 +- .../iot/emq/service/EmqxServiceImpl.java | 11 +- .../iot/job/plugin/PluginInstancesJob.java | 2 +- .../device/IotDevicePropertyDataService.java | 8 +- .../IotDevicePropertyDataServiceImpl.java | 17 +- .../iot/service/plugin/PluginInfoService.java | 7 +- .../service/plugin/PluginInfoServiceImpl.java | 204 ++++------------ .../service/plugin/PluginInstanceService.java | 45 +++- .../plugin/PluginInstanceServiceImpl.java | 227 ++++++++++++++---- .../module/iot/controller/RpcController.java | 7 +- .../yudao/module/iot/plugin/HttpHandler.java | 10 +- 21 files changed, 362 insertions(+), 245 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 plugins/disabled.txt delete mode 100644 plugins/enabled.txt create mode 100644 yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/DeviceDataCreateReqDTO.java diff --git a/.gitignore b/.gitignore index 09ec36308..49330ee16 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ rebel.xml application-my.yaml /yudao-ui-app/unpackage/ +**/.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..b7a0b8667 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "java.compile.nullAnalysis.mode": "automatic", + "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable", + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/plugins/disabled.txt b/plugins/disabled.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/enabled.txt b/plugins/enabled.txt deleted file mode 100644 index 8cf9b4c87..000000000 --- a/plugins/enabled.txt +++ /dev/null @@ -1 +0,0 @@ -http-plugin diff --git a/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar b/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar index 25120abe7f4494bd468cca84c8cea531193ff425..7193d630d82b0166448c05c7550933db82624dac 100644 GIT binary patch delta 11746 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 diff --git a/yudao-module-iot/yudao-module-iot-api/pom.xml b/yudao-module-iot/yudao-module-iot-api/pom.xml index d2f83b785..cade52eea 100644 --- a/yudao-module-iot/yudao-module-iot-api/pom.xml +++ b/yudao-module-iot/yudao-module-iot-api/pom.xml @@ -33,6 +33,13 @@ + + + + org.springframework.boot + spring-boot-starter-validation + true + diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java index 076064db8..6eed3592b 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java @@ -1,5 +1,8 @@ package cn.iocoder.yudao.module.iot.api.device; +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; +import jakarta.validation.Valid; + /** * 设备数据 API * @@ -7,14 +10,11 @@ package cn.iocoder.yudao.module.iot.api.device; */ public interface DeviceDataApi { - // TODO @haohao:最好搞成 dto 哈! /** * 保存设备数据 * - * @param productKey 产品 key - * @param deviceName 设备名称 - * @param message 消息 + * @param createDTO 设备数据 */ - void saveDeviceData(String productKey, String deviceName, String message); + void saveDeviceData(@Valid DeviceDataCreateReqDTO createDTO); } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/DeviceDataCreateReqDTO.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/DeviceDataCreateReqDTO.java new file mode 100644 index 000000000..94bc84b80 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/DeviceDataCreateReqDTO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.iot.api.device.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import jakarta.validation.constraints.NotNull; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class DeviceDataCreateReqDTO { + + /** + * 产品标识 + */ + @NotNull(message = "产品标识不能为空") + private String productKey; + /** + * 设备名称 + */ + @NotNull(message = "设备名称不能为空") + private String deviceName; + /** + * 消息 + */ + @NotNull(message = "消息不能为空") + private String message; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java index 9261e4ae1..263873be7 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java @@ -13,8 +13,8 @@ import java.util.Arrays; @Getter public enum IotPluginDeployTypeEnum implements IntArrayValuable { - UPLOAD(0, "上传 jar"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈 - ALONE(1, "独立运行"); + DEPLOY_VIA_JAR(0, "通过 jar 部署"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈 + DEPLOY_STANDALONE(1, "独立部署"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java index b4a2a62db..eea7b2a96 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.api.device; +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -17,8 +18,8 @@ public class DeviceDataApiImpl implements DeviceDataApi { private IotDevicePropertyDataService deviceDataService; @Override - public void saveDeviceData(String productKey, String deviceName, String message) { - deviceDataService.saveDeviceData(productKey, deviceName, message); + public void saveDeviceData(DeviceDataCreateReqDTO createDTO) { + deviceDataService.saveDeviceData(createDTO); } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java index ad3b31fc1..25c0f6bcb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; @Schema(description = "管理后台 - IoT 插件信息新增/修改 Request VO") @Data @@ -39,6 +41,7 @@ public class PluginInfoSaveReqVO { private String protocol; @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) + @InEnum(IotPluginStatusEnum.class) private Integer status; @Schema(description = "插件配置项描述信息") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java index 2c1553a72..3c21a55ca 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.iot.emq.service; +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; // TODO @芋艿:在瞅瞅 @@ -16,7 +16,7 @@ import org.springframework.stereotype.Service; * @author ahh */ @Slf4j -//@Service +// @Service public class EmqxServiceImpl implements EmqxService { @Resource @@ -34,7 +34,12 @@ public class EmqxServiceImpl implements EmqxService { String productKey = topic.split("/")[2]; String deviceName = topic.split("/")[3]; String message = new String(mqttMessage.getPayload()); - iotDeviceDataService.saveDeviceData(productKey, deviceName, message); + DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder() + .productKey(productKey) + .deviceName(deviceName) + .message(message) + .build(); + iotDeviceDataService.saveDeviceData(createDTO); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java index 47e7bf560..d32148b47 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java @@ -22,7 +22,7 @@ public class PluginInstancesJob { @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) public void updatePluginInstances() { TenantUtils.executeIgnore(() -> { - pluginInstanceService.updatePluginInstances(); + pluginInstanceService.reportPluginInstances(); }); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java index 08375cb09..a882b5d6c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.service.device; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; import jakarta.validation.Valid; @@ -25,12 +26,9 @@ public interface IotDevicePropertyDataService { /** * 保存设备数据 * - * @param productKey 产品 key - * @param deviceName 设备名称 - * @param message 消息 - *

参见 JSON 格式 + * @param createDTO 设备数据 */ - void saveDeviceData(String productKey, String deviceName, String message); + void saveDeviceData(DeviceDataCreateReqDTO createDTO); /** * 获得设备属性最新数据 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java index eb7fcd430..b39df3590 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java @@ -6,6 +6,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; @@ -14,8 +15,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO; import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; -import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper; import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; +import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper; import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper; import cn.iocoder.yudao.module.iot.enums.IotConstants; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; @@ -56,7 +57,7 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT) .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE) .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? - .put( IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? + .put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR) .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP) .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!! @@ -128,20 +129,20 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe } @Override - public void saveDeviceData(String productKey, String deviceName, String message) { + public void saveDeviceData(DeviceDataCreateReqDTO createDTO) { // 1. 根据产品 key 和设备名称,获得设备信息 - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName); + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(createDTO.getProductKey(), createDTO.getDeviceName()); // 2. 解析消息,保存数据 - JSONObject jsonObject = new JSONObject(message); - log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject); + JSONObject jsonObject = new JSONObject(createDTO.getMessage()); + log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", createDTO.getProductKey(), createDTO.getDeviceName(), jsonObject); ThingModelMessage thingModelMessage = ThingModelMessage.builder() .id(jsonObject.getStr("id")) .sys(jsonObject.get("sys")) .method(jsonObject.getStr("method")) .params(jsonObject.get("params")) .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time")) - .productKey(productKey) - .deviceName(deviceName) + .productKey(createDTO.getProductKey()) + .deviceName(createDTO.getDeviceName()) .deviceKey(device.getDeviceKey()) .build(); thingModelMessageService.saveThingModelMessage(device, thingModelMessage); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java index 6a44747a6..2e920e32c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java @@ -78,9 +78,10 @@ public interface PluginInfoService { List getPluginInfoList(); /** - * 获得运行状态的插件信息列表 + * 根据状态获得插件信息列表 * - * @return 运行状态的插件信息列表 + * @param status 状态 + * @return 插件信息列表 */ - List getRunningPluginInfoList(); + List getPluginInfoListByStatus(Integer status); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java index c9030b924..8e1fae88a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java @@ -9,25 +9,16 @@ import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper; import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.pf4j.PluginDescriptor; -import org.pf4j.PluginState; -import org.pf4j.PluginWrapper; import org.pf4j.spring.SpringPluginManager; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import org.springframework.web.multipart.MultipartFile; -import java.io.File; -import java.io.IOException; -import java.nio.file.*; -import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_DELETE_FAILED_RUNNING; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_NOT_EXISTS; /** * IoT 插件信息 Service 实现类 @@ -43,11 +34,10 @@ public class PluginInfoServiceImpl implements PluginInfoService { private PluginInfoMapper pluginInfoMapper; @Resource - private SpringPluginManager pluginManager; + private PluginInstanceService pluginInstanceService; - // TODO @芋艿:要不要换位置 - @Value("${pf4j.pluginsDir}") - private String pluginsDir; + @Resource + private SpringPluginManager pluginManager; @Override public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) { @@ -75,32 +65,13 @@ public class PluginInfoServiceImpl implements PluginInfoService { } // 2. 卸载插件 - // TODO @haohao:可以复用 stopAndUnloadPlugin - PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey()); - if (plugin != null) { - // 停止插件 - if (plugin.getPluginState().equals(PluginState.STARTED)) { - pluginManager.stopPlugin(plugin.getPluginId()); - } - // 卸载插件 - pluginManager.unloadPlugin(plugin.getPluginId()); - } + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDO.getPluginKey()); - // 3.1 删除 + // 3. 删除插件文件 + pluginInstanceService.deletePluginFile(pluginInfoDO); + + // 4. 删除插件信息 pluginInfoMapper.deleteById(id); - // 3.2 删除插件文件 - // TODO @haohao:这个直接主线程 sleep 就好了,不用单独开线程池哈。原因是,低频操作;另外,只有存在的时候,才 sleep + 删除; - Executors.newSingleThreadExecutor().submit(() -> { - try { - TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 - File file = new File(pluginsDir, pluginInfoDO.getFileName()); - if (file.exists() && !file.delete()) { - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); - } - } catch (InterruptedException e) { - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); - } - }); } private PluginInfoDO validatePluginInfoExists(Long id) { @@ -127,144 +98,52 @@ public class PluginInfoServiceImpl implements PluginInfoService { PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); // 2. 停止并卸载旧的插件 - stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); - // 3.1 上传新的插件文件 - String pluginKeyNew = uploadAndLoadNewPlugin(file); - // 3.2 更新插件启用状态文件 - updatePluginStatusFile(pluginKeyNew, false); + // 3 上传新的插件文件,更新插件启用状态文件 + String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); + pluginInstanceService.updatePluginStatusFile(pluginKeyNew, false); // 4. 更新插件信息 updatePluginInfo(pluginInfoDo, pluginKeyNew, file); } - // TODO @haohao:注释的格式 - // 停止并卸载旧的插件 - private void stopAndUnloadPlugin(String pluginKey) { - PluginWrapper plugin = pluginManager.getPlugin(pluginKey); - if (plugin != null) { - if (plugin.getPluginState().equals(PluginState.STARTED)) { - pluginManager.stopPlugin(pluginKey); // 停止插件 - } - pluginManager.unloadPlugin(pluginKey); // 卸载插件 - } - } - - // TODO @haohao:注释的格式 - // 上传并加载新的插件文件 - private String uploadAndLoadNewPlugin(MultipartFile file) { - // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载 - Path pluginsPath = Paths.get(pluginsDir); - try { - // TODO @haohao:可以使用 FileUtil 简化? - if (!Files.exists(pluginsPath)) { - Files.createDirectories(pluginsPath); // 创建插件目录 - } - String filename = file.getOriginalFilename(); - if (filename != null) { - Path jarPath = pluginsPath.resolve(filename); - Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件 - return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件 - } - throw exception(PLUGIN_INSTALL_FAILED); // TODO @haohao:这么抛的话,貌似会被 catch (Exception e) { - } catch (Exception e) { - // TODO @haohao:打个 error log,方便排查 - throw exception(PLUGIN_INSTALL_FAILED); - } - } - - // TODO @haohao:注释的格式 - // 更新插件状态文件 - private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) { - // TODO @haohao:疑问,这里写 enabled.txt 和 disabled.txt 的目的是啥哈? - Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt"); - Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt"); - Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath; - Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath; - - try { - PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew); - if (pluginWrapper == null) { - throw exception(PLUGIN_INSTALL_FAILED); - } - List targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) : new ArrayList<>(); - List oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) : new ArrayList<>(); - - if (!targetLines.contains(pluginKeyNew)) { - targetLines.add(pluginKeyNew); - Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING); - } - - if (oppositeLines.contains(pluginKeyNew)) { - oppositeLines.remove(pluginKeyNew); - Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING); - } - } catch (IOException e) { - throw exception(PLUGIN_INSTALL_FAILED); - } - } - - // TODO @haohao:注释的格式 - // 更新插件信息 + /** + * 更新插件信息 + * + * @param pluginInfoDo 插件信息 + * @param pluginKeyNew 插件标识符 + * @param file 文件 + */ private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) { - // TODO @haohao:更新实体的时候,最好 new 一个新的! - // TODO @haohao:可以链式调用,简化下代码; - pluginInfoDo.setPluginKey(pluginKeyNew); - pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus()); - pluginInfoDo.setFileName(file.getOriginalFilename()); - pluginInfoDo.setScript(""); - // 解析 pf4j 插件 - PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor(); - pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription()); - pluginInfoDo.setVersion(pluginDescriptor.getVersion()); - pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription()); + // 创建新的插件信息对象并链式设置属性 + PluginInfoDO updatedPluginInfo = new PluginInfoDO() + .setId(pluginInfoDo.getId()) + .setPluginKey(pluginKeyNew) + .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) + .setFileName(file.getOriginalFilename()) + .setScript("") + .setConfigSchema(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription()) + .setVersion(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion()) + .setDescription(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription()); // 执行更新 - pluginInfoMapper.updateById(pluginInfoDo); + pluginInfoMapper.updateById(updatedPluginInfo); } - // TODO @haohao:status、state 字段命名,要统一下~ @Override public void updatePluginStatus(Long id, Integer status) { // 1. 校验插件信息是否存在 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); - // 2. 校验插件状态是否有效 - // TODO @haohao:直接参数校验掉。通过 @InEnum - if (!IotPluginStatusEnum.contains(status)) { - throw exception(PLUGIN_STATUS_INVALID); - } + // 2. 更新插件状态 + pluginInstanceService.updatePluginStatus(pluginInfoDo, status); - // 3. 获取插件标识和插件实例 - String pluginKey = pluginInfoDo.getPluginKey(); - PluginWrapper plugin = pluginManager.getPlugin(pluginKey); - - // 4. 根据状态更新插件 - if (plugin != null) { - // 4.1 启动:如果目标状态是运行且插件未启动,则启动插件 - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) - && plugin.getPluginState() != PluginState.STARTED) { - pluginManager.startPlugin(pluginKey); - updatePluginStatusFile(pluginKey, true); - // 4.2 停止:如果目标状态是停止且插件已启动,则停止插件 - } else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) - && plugin.getPluginState() == PluginState.STARTED) { - pluginManager.stopPlugin(pluginKey); - updatePluginStatusFile(pluginKey, false); - } - } else { - // 5. 插件不存在且状态为停止,抛出异常 - if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { - throw exception(PLUGIN_STATUS_INVALID); - } - } - - // 6. 更新数据库中的插件状态 - // TODO @haohao:新建新建 pluginInfoDo 哈! - pluginInfoDo.setStatus(status); - pluginInfoMapper.updateById(pluginInfoDo); + // 3. 更新数据库中的插件状态 + PluginInfoDO updatedPluginInfo = new PluginInfoDO(); + updatedPluginInfo.setId(id); + updatedPluginInfo.setStatus(status); + pluginInfoMapper.updateById(updatedPluginInfo); } @Override @@ -272,10 +151,9 @@ public class PluginInfoServiceImpl implements PluginInfoService { return pluginInfoMapper.selectList(); } - // TODO @haohao:可以改成 getPluginInfoListByStatus 更通用哈。 @Override - public List getRunningPluginInfoList() { - return pluginInfoMapper.selectListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); + public List getPluginInfoListByStatus(Integer status) { + return pluginInfoMapper.selectListByStatus(status); } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java index 5655f1d3a..cd1d5a654 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java @@ -1,5 +1,8 @@ package cn.iocoder.yudao.module.iot.service.plugin; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import org.springframework.web.multipart.MultipartFile; + /** * IoT 插件实例 Service 接口 * @@ -8,8 +11,46 @@ package cn.iocoder.yudao.module.iot.service.plugin; public interface PluginInstanceService { /** - * 更新IoT 插件实例 + * 上报插件实例 */ - void updatePluginInstances(); + void reportPluginInstances(); + + /** + * 停止并卸载插件 + * + * @param pluginKey 插件标识符 + */ + void stopAndUnloadPlugin(String pluginKey); + + /** + * 删除插件文件 + * + * @param pluginInfoDo 插件信息 + */ + void deletePluginFile(PluginInfoDO pluginInfoDo); + + /** + * 上传并加载新的插件文件 + * + * @param file 插件文件 + * @return 插件标识符 + */ + String uploadAndLoadNewPlugin(MultipartFile file); + + /** + * 更新插件状态文件 + * + * @param pluginKeyNew 插件标识符 + * @param isEnabled 是否启用 + */ + void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled); + + /** + * 更新插件状态 + * + * @param pluginInfoDo 插件信息 + * @param status 新状态 + */ + void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status); } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java index 6a65fc026..618d09c73 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java @@ -1,19 +1,38 @@ package cn.iocoder.yudao.module.iot.service.plugin; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.net.NetUtil; import cn.hutool.core.util.IdUtil; import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; +import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper; import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper; +import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginState; import org.pf4j.PluginWrapper; import org.pf4j.spring.SpringPluginManager; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; /** * IoT 插件实例 Service 实现类 @@ -25,79 +44,195 @@ import java.util.List; @Slf4j public class PluginInstanceServiceImpl implements PluginInstanceService { - /** - * 主程序 ID - */ // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 + // 简化的UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件 public static final String MAIN_ID = IdUtil.fastSimpleUUID(); @Resource - private PluginInfoService pluginInfoService; + private PluginInfoMapper pluginInfoMapper; @Resource private PluginInstanceMapper pluginInstanceMapper; @Resource private SpringPluginManager pluginManager; + @Value("${pf4j.pluginsDir}") + private String pluginsDir; + @Value("${server.port:48080}") private int port; - // TODO @haohao:建议把 PluginInfoServiceImpl 里面,和 instance 相关的逻辑拿过来,可能会更好。info 处理信息,instance 处理实例 - - // TODO @haohao:这个改成 reportPluginInstance 会不会更合适哈。 @Override - public void updatePluginInstances() { - // 1.1 查询 pf4j 插件列表 - List plugins = pluginManager.getPlugins(); - // 1.2 查询插件信息列表 - List pluginInfos = pluginInfoService.getPluginInfoList(); - // 1.3 动态获取主程序的 IP 和端口 - String mainIp = getLocalIpAddress(); + public void stopAndUnloadPlugin(String pluginKey) { + PluginWrapper plugin = pluginManager.getPlugin(pluginKey); + if (plugin != null) { + if (plugin.getPluginState().equals(PluginState.STARTED)) { + pluginManager.stopPlugin(pluginKey); // 停止插件 + log.info("已停止插件: {}", pluginKey); + } + pluginManager.unloadPlugin(pluginKey); // 卸载插件 + log.info("已卸载插件: {}", pluginKey); + } else { + log.warn("插件不存在或已卸载: {}", pluginKey); + } + } - // 2. 遍历插件列表,并保存为插件实例 + @Override + public void deletePluginFile(PluginInfoDO pluginInfoDO) { + File file = new File(pluginsDir, pluginInfoDO.getFileName()); + if (file.exists()) { + try { + TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 + if (!file.delete()) { + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); + } + } catch (InterruptedException e) { + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), + e); + Thread.currentThread().interrupt(); // 恢复中断状态 + } + } + } + + @Override + public String uploadAndLoadNewPlugin(MultipartFile file) { + String pluginKeyNew; + // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载 + Path pluginsPath = Paths.get(pluginsDir); + try { + FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录 + String filename = file.getOriginalFilename(); + if (filename != null) { + Path jarPath = pluginsPath.resolve(filename); + Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件 + pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件 + log.info("已加载插件: {}", pluginKeyNew); + } else { + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED); + } + } catch (IOException e) { + log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e); + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); + } catch (Exception e) { + log.error("[uploadAndLoadNewPlugin][加载插件失败]", e); + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); + } + return pluginKeyNew; + } + + @Override + public void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) { + // TODO @haohao:疑问,这里写 enabled.txt 和 disabled.txt 的目的是啥哈? + // pf4j 的插件状态文件,需要 2 个文件,一个 enabled.txt 一个 disabled.txt + Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt"); + Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt"); + Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath; + Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath; + + try { + PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew); + if (pluginWrapper == null) { + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED); + } + List targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) + : new ArrayList<>(); + List oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) + : new ArrayList<>(); + + if (!targetLines.contains(pluginKeyNew)) { + targetLines.add(pluginKeyNew); + Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + log.info("已添加插件 {} 到 {}", pluginKeyNew, targetFilePath.getFileName()); + } + + if (oppositeLines.contains(pluginKeyNew)) { + oppositeLines.remove(pluginKeyNew); + Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + log.info("已从 {} 移除插件 {}", oppositeFilePath.getFileName(), pluginKeyNew); + } + } catch (IOException e) { + log.error("[updatePluginStatusFile][更新插件状态文件失败]", e); + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); + } + } + + @Override + public void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status) { + String pluginKey = pluginInfoDo.getPluginKey(); + PluginWrapper plugin = pluginManager.getPlugin(pluginKey); + + if (plugin != null) { + // 启动插件 + if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) + && plugin.getPluginState() != PluginState.STARTED) { + pluginManager.startPlugin(pluginKey); + updatePluginStatusFile(pluginKey, true); + log.info("已启动插件: {}", pluginKey); + } + // 停止插件 + else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) + && plugin.getPluginState() == PluginState.STARTED) { + pluginManager.stopPlugin(pluginKey); + updatePluginStatusFile(pluginKey, false); + log.info("已停止插件: {}", pluginKey); + } + } else { + // 插件不存在且状态为停止,抛出异常 + if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { + throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID); + } + } + } + + @Override + public void reportPluginInstances() { + // 1. 获取 pf4j 插件列表 + List plugins = pluginManager.getPlugins(); + + // 2. 获取插件信息列表并转换为 Map 以便快速查找 + List pluginInfos = pluginInfoMapper.selectList(); + Map pluginInfoMap = pluginInfos.stream() + .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); + + // 3. 获取本机 IP 和 MAC 地址 + LinkedHashSet localAddressList = NetUtil.localAddressList(t -> t instanceof Inet4Address); + LinkedHashSet ipList = NetUtil.toIpList(localAddressList); + String ip = ipList.stream().findFirst().orElse("127.0.0.1"); + String mac = NetUtil.getMacAddress(localAddressList.stream().findFirst().orElse(null)); + String mainId = MAIN_ID + "-" + mac; + + // 4. 遍历插件列表,并保存为插件实例 for (PluginWrapper plugin : plugins) { - // 2.1 查找插件信息 String pluginKey = plugin.getPluginId(); - // TODO @haohao:CollUtil.findOne() 简化 - PluginInfoDO pluginInfo = pluginInfos.stream() - .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey)) - .findFirst() - .orElse(null); + + // 4.1 查找插件信息 + PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); if (pluginInfo == null) { - // TODO @haohao:建议打个 error log + // 4.2 插件信息不存在,记录错误并跳过 + log.error("插件信息不存在,插件包标识符 = {}", pluginKey); continue; } - // 2.2 查询插件实例 - PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId()); - // 2.3.1 如果插件实例不存在,则创建 + // 4.3 查询插件实例 + PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId, + pluginInfo.getId()); if (pluginInstance == null) { - // TODO @haohao:可以链式调用;建议新建一个! - pluginInstance = new PluginInstanceDO(); - pluginInstance.setPluginId(pluginInfo.getId()); - pluginInstance.setMainId(MAIN_ID); - pluginInstance.setIp(mainIp); - pluginInstance.setPort(port); - pluginInstance.setHeartbeatAt(System.currentTimeMillis()); + // 4.4 如果插件实例不存在,则创建 + pluginInstance = PluginInstanceDO.builder() + .pluginId(pluginInfo.getId()) + .mainId(MAIN_ID + "-" + mac) + .ip(ip) + .port(port) + .heartbeatAt(System.currentTimeMillis()) + .build(); pluginInstanceMapper.insert(pluginInstance); } else { - // 2.3.2 如果插件实例存在,则更新 + // 4.5 如果插件实例存在,则更新心跳时间 pluginInstance.setHeartbeatAt(System.currentTimeMillis()); pluginInstanceMapper.updateById(pluginInstance); } } } - // TODO @haohao:这个目的是,获取到第一个有效 ip 是哇? - private String getLocalIpAddress() { - try { - List ipList = NetUtil.localIpv4s().stream() - .filter(ip -> !ip.startsWith("0.0") && !ip.startsWith("127.") && !ip.startsWith("169.254") && !ip.startsWith("255.255.255.255")) - .toList(); - return ipList.isEmpty() ? "127.0.0.1" : ipList.get(0); - } catch (Exception e) { - log.error("获取本地IP地址失败", e); - return "127.0.0.1"; // 默认值 - } - } - } \ 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/controller/RpcController.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java index 0a9ba9ee4..8682a549c 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java @@ -11,6 +11,11 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.concurrent.CompletableFuture; +/** + * 插件实例 RPC 接口 + * + * @author 芋道源码 + */ @RestController @RequestMapping("/rpc") @RequiredArgsConstructor @@ -29,4 +34,4 @@ public class RpcController { return rpcClient.call("concat", new Object[]{str1, str2}, 10); } -} +} \ 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/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 index 6d0908683..b91146712 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -12,7 +13,7 @@ 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 字段 */ @@ -76,7 +77,12 @@ public class HttpHandler extends SimpleChannelInboundHandler { try { // 调用主程序的接口保存数据 - deviceDataApi.saveDeviceData(productKey, deviceName, jsonData.toString()); + DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder() + .productKey(productKey) + .deviceName(deviceName) + .message(jsonData.toString()) + .build(); + deviceDataApi.saveDeviceData(createDTO); // 构造成功响应内容 JSONObject successRes = createResponseJson( From 77b89aad773e021b2a7f9d0c85bdbbec468af000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Tue, 7 Jan 2025 23:13:57 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91IoT:=20=E9=9B=86=E6=88=90=20Vert.x=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E9=87=8D=E6=9E=84=20HTTP=20?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E4=B8=BA=20Vert.x=20=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-dependencies/pom.xml | 14 ++ yudao-module-iot/yudao-module-iot-biz/pom.xml | 10 ++ .../plugin/PluginInstanceServiceImpl.java | 6 +- .../dependency-reduced-pom.xml | 81 ++++++++++ .../plugin.properties | 2 +- .../yudao-module-iot-http-plugin/pom.xml | 57 ++++--- .../yudao/module/iot/plugin/HttpHandler.java | 153 ------------------ .../yudao/module/iot/plugin/HttpPlugin.java | 94 ----------- .../module/iot/plugin/HttpVertxHandler.java | 105 ++++++++++++ .../module/iot/plugin/HttpVertxPlugin.java | 70 ++++++++ .../src/main/resources/application-dev.yaml | 7 +- .../src/main/resources/application-local.yaml | 44 +++-- 12 files changed, 355 insertions(+), 288 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml delete mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java delete mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java create mode 100644 yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 6eaa89dfe..fb3cf8562 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -67,6 +67,7 @@ 3.0.6 1.2.5 0.9.0 + 4.4.0 3.5.0 4.11.0 @@ -613,6 +614,19 @@ ${pf4j-spring.version} + + + io.vertx + vertx-core + ${vertx.version} + + + + io.vertx + vertx-web + ${vertx.version} + + diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index d0ed0bcac..66710ae91 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -64,6 +64,16 @@ yudao-spring-boot-starter-excel + + + io.vertx + vertx-core + + + + io.vertx + vertx-web + org.eclipse.paho diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java index 618d09c73..a4bae8964 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java +++ b/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 { .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); // 3. 获取本机 IP 和 MAC 地址 - LinkedHashSet localAddressList = NetUtil.localAddressList(t -> t instanceof Inet4Address); - LinkedHashSet ipList = NetUtil.toIpList(localAddressList); - String ip = ipList.stream().findFirst().orElse("127.0.0.1"); - String mac = NetUtil.getMacAddress(localAddressList.stream().findFirst().orElse(null)); + String ip = NetUtil.getLocalhostStr(); + String mac = NetUtil.getLocalMacAddress(); String mainId = MAIN_ID + "-" + mac; // 4. 遍历插件列表,并保存为插件实例 diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml new file mode 100644 index 000000000..f4ec60d96 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml @@ -0,0 +1,81 @@ + + + + yudao-module-iot-plugin + cn.iocoder.boot + 2.2.0-snapshot + + 4.0.0 + yudao-module-iot-http-plugin + ${project.artifactId} + 2.2.0-snapshot + 物联网 插件模块 - http 插件 + + + + maven-jar-plugin + 2.4 + + + + ${plugin.id} + ${plugin.class} + ${plugin.version} + ${plugin.provider} + ${plugin.description} + ${plugin.dependencies} + + + + + + maven-deploy-plugin + + true + + + + maven-shade-plugin + 3.4.1 + + + package + + shade + + + true + shaded + + + cn.iocoder.yudao.module.iot.HttpPluginSpringbootApplication + + + + + + + + + + + org.pf4j + pf4j-spring + 0.9.0 + provided + + + org.projectlombok + lombok + 1.18.34 + provided + + + + cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin + 0.0.1 + http-plugin + http-plugin-0.0.1 + ahh + + diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties index 4e1199acf..44f221cb1 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties @@ -1,5 +1,5 @@ plugin.id=http-plugin -plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpPlugin +plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin plugin.version=0.0.1 plugin.provider=ahh plugin.dependencies= diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml index 27c1d19a0..29c0200f1 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml @@ -21,7 +21,7 @@ http-plugin - cn.iocoder.yudao.module.iot.plugin.HttpPlugin + cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin 0.0.1 ahh http-plugin-0.0.1 @@ -30,27 +30,6 @@ - - org.apache.maven.plugins maven-antrun-plugin @@ -118,6 +97,29 @@ true + + + + + + + + + + + + + + + + + + + + + + + @@ -145,10 +147,15 @@ ${lombok.version} provided + - io.netty - netty-all - 4.1.63.Final + io.vertx + vertx-core + + + + io.vertx + vertx-web 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 b91146712..000000000 --- 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 66e0c69a3..000000000 --- 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 000000000..335d6c95d --- /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 000000000..c1d587489 --- /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 5457247e6..c0bd5f64d 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 e5ae6d195..b6492a1f4 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 From d39e2c1bc4127ffc7b4b17cb9b4a080bc2898b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Tue, 7 Jan 2025 23:21:49 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-local.yaml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index b6492a1f4..ebfc4faa1 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://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=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: ruoyi-vue-pro - password: ruoyi-@h2ju02hebp + username: root + password: 123456 # username: sa # SQL Server 连接的示例 # password: Yudao@2024 # SQL Server 连接的示例 # username: SYSDBA # DM 连接的示例 @@ -63,12 +63,12 @@ spring: # password: Yudao@2024 # OpenGauss 连接的示例 slave: # 模拟从库,可根据自己需要修改 lazy: 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 + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + username: root + password: 123456 tdengine: # IOT 数据库 # lazy: true # 开启懒加载,保证启动速度 - url: jdbc:TAOS-RS://chaojiniu.top:6041/ruoyi_vue_pro + url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro driver-class-name: com.taosdata.jdbc.rs.RestfulDriver username: root password: taosdata @@ -78,10 +78,10 @@ spring: # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 data: redis: - host: chaojiniu.top # 地址 + host: 400-infra.server.iocoder.cn # 地址 port: 6379 # 端口 - database: 15 # 数据库索引 - password: fsknKD7UvQYZsyf2hXXn # 密码,建议生产环境开启 + database: 1 # 数据库索引 + # password: 123456 # 密码,建议生产环境开启 --- #################### 定时任务相关配置 #################### @@ -269,11 +269,11 @@ justauth: iot: emq: # 账号 - username: haohao + username: root # 密码 - password: ahh@123456 + password: 123456 # 主机地址 - hostUrl: tcp://chaojiniu.top:1883 + hostUrl: tcp://127.0.0.1:1883 # 客户端Id,不能相同,采用随机数 ${random.value} client-id: ${random.int} # 默认主题 @@ -285,9 +285,9 @@ iot: # MQTT-RPC 配置 mqtt: - broker: tcp://chaojiniu.top:1883 - username: haohao - password: ahh@123456 + broker: tcp://127.0.0.1:1883 + username: root + password: 123456 clientId: mqtt-rpc-server-${random.int} requestTopic: rpc/request responseTopicPrefix: rpc/response/ From 0af6d5a7581532b4658160a19680c09ffd427279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Wed, 8 Jan 2025 17:59:32 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91IoT:=20=E6=96=B0=E5=A2=9E=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E9=80=BB=E8=BE=91=EF=BC=8C=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=8D=E5=86=8D=E4=BD=BF=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=96=87=E4=BB=B6=E6=9B=B4=E6=96=B0=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-module-iot-http-plugin-2.2.0-snapshot.jar | Bin 16779 -> 15105 bytes .../admin/plugin/vo/PluginInfoPageReqVO.java | 5 +- .../iot/framework/plugin/PluginStart.java | 51 ++++++++++++++++++ .../plugin/UnifiedConfiguration.java | 8 ++- .../iot/service/plugin/PluginInfoService.java | 5 +- .../service/plugin/PluginInfoServiceImpl.java | 1 - .../service/plugin/PluginInstanceService.java | 8 --- .../plugin/PluginInstanceServiceImpl.java | 46 +--------------- .../module/iot/plugin/HttpVertxPlugin.java | 16 +++++- 9 files changed, 80 insertions(+), 60 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/PluginStart.java diff --git a/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar b/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar index 7193d630d82b0166448c05c7550933db82624dac..fa75769049a1e0036436209788e16c216edd4891 100644 GIT binary patch delta 6325 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 P-~qDX0DuLa=f?j77 { // 1. 忽略租户上下文执行 + List pluginInfoList = pluginInfoService + .getPluginInfoListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); // 2. 获取运行中的插件列表 + if (CollUtil.isEmpty(pluginInfoList)) { // 3. 检查插件列表是否为空 + log.info("[run] 没有需要启动的插件"); // 4. 日志记录没有插件需要启动 + return; + } + pluginInfoList.forEach(pluginInfo -> { // 5. 使用lambda表达式遍历插件列表 + try { + log.info("[run][启动插件] pluginKey = {}", pluginInfo.getPluginKey()); // 6. 日志记录插件启动信息 + pluginManager.startPlugin(pluginInfo.getPluginKey()); // 7. 启动插件 + } catch (Exception e) { + log.error("[run][启动插件失败] pluginKey = {}", pluginInfo.getPluginKey(), e); // 8. 记录启动失败的日志 + } + }); + }); + + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java index 4f1d5e3e2..374e3856a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java @@ -31,7 +31,13 @@ public class UnifiedConfiguration { @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER) public SpringPluginManager pluginManager() { log.info("[init][实例化 SpringPluginManager]"); - SpringPluginManager springPluginManager = new SpringPluginManager(); + SpringPluginManager springPluginManager = new SpringPluginManager() { + @Override + public void startPlugins() { + // 禁用插件启动,避免插件启动时,启动所有插件 + log.info("[init][禁用默认启动所有插件]"); + } + }; springPluginManager.addPluginStateListener(new CustomPluginStateListener()); return springPluginManager; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java index 2e920e32c..3a1529674 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; import jakarta.validation.Valid; import org.springframework.web.multipart.MultipartFile; @@ -66,7 +67,7 @@ public interface PluginInfoService { * 更新插件的状态 * * @param id 插件id - * @param status 状态 + * @param status 状态 {@link IotPluginStatusEnum} */ void updatePluginStatus(Long id, Integer status); @@ -80,7 +81,7 @@ public interface PluginInfoService { /** * 根据状态获得插件信息列表 * - * @param status 状态 + * @param status 状态 {@link IotPluginStatusEnum} * @return 插件信息列表 */ List getPluginInfoListByStatus(Integer status); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java index 8e1fae88a..4cf922c20 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java @@ -102,7 +102,6 @@ public class PluginInfoServiceImpl implements PluginInfoService { // 3 上传新的插件文件,更新插件启用状态文件 String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); - pluginInstanceService.updatePluginStatusFile(pluginKeyNew, false); // 4. 更新插件信息 updatePluginInfo(pluginInfoDo, pluginKeyNew, file); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java index cd1d5a654..9c360606a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java @@ -37,14 +37,6 @@ public interface PluginInstanceService { */ String uploadAndLoadNewPlugin(MultipartFile file); - /** - * 更新插件状态文件 - * - * @param pluginKeyNew 插件标识符 - * @param isEnabled 是否启用 - */ - void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled); - /** * 更新插件状态 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java index a4bae8964..4a62dbcb9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java @@ -21,11 +21,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; -import java.net.Inet4Address; -import java.net.InetAddress; import java.nio.file.*; -import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -119,44 +115,6 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { return pluginKeyNew; } - @Override - public void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) { - // TODO @haohao:疑问,这里写 enabled.txt 和 disabled.txt 的目的是啥哈? - // pf4j 的插件状态文件,需要 2 个文件,一个 enabled.txt 一个 disabled.txt - Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt"); - Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt"); - Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath; - Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath; - - try { - PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew); - if (pluginWrapper == null) { - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED); - } - List targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) - : new ArrayList<>(); - List oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) - : new ArrayList<>(); - - if (!targetLines.contains(pluginKeyNew)) { - targetLines.add(pluginKeyNew); - Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING); - log.info("已添加插件 {} 到 {}", pluginKeyNew, targetFilePath.getFileName()); - } - - if (oppositeLines.contains(pluginKeyNew)) { - oppositeLines.remove(pluginKeyNew); - Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING); - log.info("已从 {} 移除插件 {}", oppositeFilePath.getFileName(), pluginKeyNew); - } - } catch (IOException e) { - log.error("[updatePluginStatusFile][更新插件状态文件失败]", e); - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); - } - } - @Override public void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status) { String pluginKey = pluginInfoDo.getPluginKey(); @@ -167,14 +125,12 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) && plugin.getPluginState() != PluginState.STARTED) { pluginManager.startPlugin(pluginKey); - updatePluginStatusFile(pluginKey, true); log.info("已启动插件: {}", pluginKey); } // 停止插件 else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) && plugin.getPluginState() == PluginState.STARTED) { pluginManager.stopPlugin(pluginKey); - updatePluginStatusFile(pluginKey, false); log.info("已停止插件: {}", pluginKey); } } else { @@ -208,7 +164,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); if (pluginInfo == null) { // 4.2 插件信息不存在,记录错误并跳过 - log.error("插件信息不存在,插件包标识符 = {}", pluginKey); + log.error("插件信息不存在,pluginKey = {}", pluginKey); continue; } 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 index c1d587489..1d6fcad92 100644 --- 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 @@ -5,12 +5,15 @@ 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 org.pf4j.spring.SpringPlugin; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + import lombok.extern.slf4j.Slf4j; @Slf4j -public class HttpVertxPlugin extends Plugin { +public class HttpVertxPlugin extends SpringPlugin { private static final int PORT = 8092; private Vertx vertx; @@ -67,4 +70,13 @@ public class HttpVertxPlugin extends Plugin { }); } } + + @Override + protected ApplicationContext createApplicationContext() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.setClassLoader(getWrapper().getPluginClassLoader()); + applicationContext.refresh(); + + return applicationContext; + } } \ No newline at end of file From aad05817773d465c5fc335bc914c04ac0ad011c7 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 8 Jan 2025 22:36:38 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91IoT=EF=BC=9A=E6=8F=92=E4=BB=B6=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 5 --- .../enums/plugin/IotPluginDeployTypeEnum.java | 5 +-- .../plugininstance/PluginInstanceDO.java | 7 ++-- .../service/plugin/PluginInfoServiceImpl.java | 2 +- .../service/plugin/PluginInstanceService.java | 2 +- .../plugin/PluginInstanceServiceImpl.java | 36 +++++++++---------- .../module/iot/controller/RpcController.java | 1 + 7 files changed, 27 insertions(+), 31 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b7a0b8667..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "java.compile.nullAnalysis.mode": "automatic", - "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable", - "java.configuration.updateBuildConfiguration": "interactive" -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java index 263873be7..11f8a6ef8 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java @@ -13,8 +13,8 @@ import java.util.Arrays; @Getter public enum IotPluginDeployTypeEnum implements IntArrayValuable { - DEPLOY_VIA_JAR(0, "通过 jar 部署"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈 - DEPLOY_STANDALONE(1, "独立部署"); + JAR(0, "JAR 部署"), + STANDALONE(1, "独立部署"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); @@ -48,4 +48,5 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable { public int[] array() { return ARRAYS; } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java index 507b91f2a..8b0ec95d1 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java @@ -33,19 +33,22 @@ public class PluginInstanceDO extends BaseDO { */ private String mainId; /** - * 插件id + * 插件 ID *

* 关联 {@link PluginInfoDO#getId()} */ private Long pluginId; + /** - * 插件主程序所在ip + * 插件主程序所在 IP */ private String ip; /** * 插件主程序端口 */ private Integer port; + + // TODO @haohao:字段改成 heartbeatTime,LocalDateTime /** * 心跳时间,心路时间超过 30 秒需要剔除 */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java index 4cf922c20..37b632845 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java @@ -100,7 +100,7 @@ public class PluginInfoServiceImpl implements PluginInfoService { // 2. 停止并卸载旧的插件 pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); - // 3 上传新的插件文件,更新插件启用状态文件 + // 3 上传新的插件文件,更新插件启用状态文件 String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); // 4. 更新插件信息 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java index 9c360606a..4df9b1031 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java @@ -11,7 +11,7 @@ import org.springframework.web.multipart.MultipartFile; public interface PluginInstanceService { /** - * 上报插件实例 + * 上报插件实例(心跳) */ void reportPluginInstances(); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java index 4a62dbcb9..65a6cf32b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java @@ -41,7 +41,8 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU public class PluginInstanceServiceImpl implements PluginInstanceService { // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 - // 简化的UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件 + // 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件; + // 那就 mac@uuid ? public static final String MAIN_ID = IdUtil.fastSimpleUUID(); @Resource @@ -53,13 +54,13 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { @Value("${pf4j.pluginsDir}") private String pluginsDir; - @Value("${server.port:48080}") private int port; @Override public void stopAndUnloadPlugin(String pluginKey) { PluginWrapper plugin = pluginManager.getPlugin(pluginKey); + // TODO @haohao:改成 if return 会更简洁一点; if (plugin != null) { if (plugin.getPluginState().equals(PluginState.STARTED)) { pluginManager.stopPlugin(pluginKey); // 停止插件 @@ -75,6 +76,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { @Override public void deletePluginFile(PluginInfoDO pluginInfoDO) { File file = new File(pluginsDir, pluginInfoDO.getFileName()); + // TODO @haohao:改成 if return 会更简洁一点; if (file.exists()) { try { TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 @@ -82,9 +84,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); } } catch (InterruptedException e) { - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), - e); - Thread.currentThread().interrupt(); // 恢复中断状态 + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); } } } @@ -120,6 +120,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { String pluginKey = pluginInfoDo.getPluginKey(); PluginWrapper plugin = pluginManager.getPlugin(pluginKey); + // TODO @haohao:改成 if return 会更简洁一点; if (plugin != null) { // 启动插件 if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) @@ -143,46 +144,41 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { @Override public void reportPluginInstances() { - // 1. 获取 pf4j 插件列表 + // 1.1 获取 pf4j 插件列表 List plugins = pluginManager.getPlugins(); - // 2. 获取插件信息列表并转换为 Map 以便快速查找 + // 1.2 获取插件信息列表并转换为 Map 以便快速查找 List pluginInfos = pluginInfoMapper.selectList(); Map pluginInfoMap = pluginInfos.stream() .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); - // 3. 获取本机 IP 和 MAC 地址 + // 1.3 获取本机 IP 和 MAC 地址 String ip = NetUtil.getLocalhostStr(); String mac = NetUtil.getLocalMacAddress(); String mainId = MAIN_ID + "-" + mac; - // 4. 遍历插件列表,并保存为插件实例 + // 2. 遍历插件列表,并保存为插件实例 for (PluginWrapper plugin : plugins) { String pluginKey = plugin.getPluginId(); - // 4.1 查找插件信息 + // 2.1 查找插件信息 PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); if (pluginInfo == null) { - // 4.2 插件信息不存在,记录错误并跳过 log.error("插件信息不存在,pluginKey = {}", pluginKey); continue; } - // 4.3 查询插件实例 + // 2.2 情况一:如果插件实例不存在,则创建 PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId, pluginInfo.getId()); if (pluginInstance == null) { // 4.4 如果插件实例不存在,则创建 - pluginInstance = PluginInstanceDO.builder() - .pluginId(pluginInfo.getId()) - .mainId(MAIN_ID + "-" + mac) - .ip(ip) - .port(port) - .heartbeatAt(System.currentTimeMillis()) - .build(); + pluginInstance = PluginInstanceDO.builder().pluginId(pluginInfo.getId()).mainId(MAIN_ID + "-" + mac) + .ip(ip).port(port).heartbeatAt(System.currentTimeMillis()).build(); pluginInstanceMapper.insert(pluginInstance); } else { - // 4.5 如果插件实例存在,则更新心跳时间 + // 2.2 情况二:如果存在,则更新 heartbeatAt + // TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有) pluginInstance.setHeartbeatAt(System.currentTimeMillis()); pluginInstanceMapper.updateById(pluginInstance); } diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java index 8682a549c..4615dcf96 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.concurrent.CompletableFuture; +// TODO 芋艿:后续 review 下 /** * 插件实例 RPC 接口 * From deab8c1cc6bb7864d9c40e0c369f649f6f9bfa41 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 9 Jan 2025 12:36:30 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E6=97=A5?= =?UTF-8?q?=E5=BF=97=20TDengine=20=E8=A1=A8=E4=B8=8E=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device/IotDeviceDataController.java | 7 +++--- .../device/vo/device/IotDeviceSaveReqVO.java | 2 +- .../IotDeviceDataSimulatorSaveReqVO.java | 13 +++++----- .../vo/deviceData/IotDeviceLogPageReqVO.java | 5 +++- .../vo/deviceData/IotDeviceLogRespVO.java | 5 +++- .../thingmodel/IotThingModelController.java | 1 + .../thingmodel/vo/IotThingModelListReqVO.java | 8 +++---- .../dal/dataobject/device/IotDeviceLogDO.java | 24 ++++++++++++------- .../tdengine/ThingModelMessageDO.java | 4 ++-- .../dal/tdengine/IotDeviceLogDataMapper.java | 12 ++++++---- .../tdengine/TdThingModelMessageMapper.java | 6 +---- .../TDengineTableInitConfiguration.java | 9 ++++--- .../device/IotDeviceLogDataService.java | 3 +-- .../device/IotDeviceLogDataServiceImpl.java | 24 +++++++------------ .../IotDevicePropertyDataServiceImpl.java | 2 -- .../product/IotProductServiceImpl.java | 14 +++++------ .../IotThingModelMessageServiceImpl.java | 16 +++++-------- .../mapper/device/IotDeviceLogDataMapper.xml | 22 ++++++++--------- 18 files changed, 90 insertions(+), 87 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java index 801cbcb21..be451f17a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.controller.admin.device; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.*; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; @@ -13,7 +12,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -34,7 +32,7 @@ public class IotDeviceDataController { @Resource private IotDeviceLogDataService iotDeviceLogDataService; - @Resource + @Resource // TODO @super:service 之间,不用空行;原因是,这样更简洁;空行,主要是为了“间隔”,提升可读性 private IotDeviceLogDataService deviceLogDataService; // TODO @浩浩:这里的 /latest-list,包括方法名。 @@ -52,14 +50,17 @@ public class IotDeviceDataController { PageResult> list = deviceDataService.getHistoryDeviceProperties(deviceDataReqVO); return success(BeanUtils.toBean(list, IotTimeDataRespVO.class)); } + // TODO:数据权限 @PostMapping("/simulator") @Operation(summary = "模拟设备") public CommonResult simulatorDevice(@Valid @RequestBody IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { //TODO:先生成一下设备日志 后续完善模拟设备代码逻辑 + // TODO @super:应该 deviceDataService 里面有个 simulatorDevice,然后里面去 insert 日志! iotDeviceLogDataService.createDeviceLog(simulatorReqVO); return success(true); } + // TODO:数据权限 @GetMapping("/log/page") @Operation(summary = "获得设备日志分页") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java index e1268b003..7d9ac6a0d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java @@ -14,7 +14,7 @@ public class IotDeviceSaveReqVO { private Long id; @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177") - @Size(max = 50, message = "设备编号长度不能超过50个字符") + @Size(max = 50, message = "设备编号长度不能超过 50 个字符") private String deviceKey; @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java index 3a4bfface..c4f9d1f55 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java @@ -3,27 +3,27 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +// TODO super: SaveReqVO => ReqVO @Schema(description = "管理后台 - IoT 模拟设备数据 Request VO") @Data public class IotDeviceDataSimulatorSaveReqVO { - @Schema(description = "消息ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "msg123") + // TODO @super:感觉后端随机更合适? + @Schema(description = "消息 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "msg123") private String id; + // TODO @super:不用传递 productKey,因为 deviceKey 可以推导出来 @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123") @NotEmpty(message = "产品ID不能为空") private String productKey; + // TODO @super:中文写作规范,中英文之间,要有空格。例如说,设备 ID。ps:这里应该是设备标识 @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123") @NotEmpty(message = "设备ID不能为空") private String deviceKey; + // TODO @super:type、subType,是不是不用传递,因为模拟只有属性??? @Schema(description = "消息/日志类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property") @NotEmpty(message = "消息类型不能为空") private String type; @@ -36,6 +36,7 @@ public class IotDeviceDataSimulatorSaveReqVO { @NotEmpty(message = "数据内容不能为空") private String content; + // TODO @芋艿:需要讨论下,reportTime 到底以那个为准! @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED) private Long reportTime; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java index 67099f331..a882a6d86 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java @@ -18,13 +18,16 @@ public class IotDeviceLogPageReqVO extends PageParam { @NotEmpty(message = "设备标识不能为空") private String deviceKey; + // TODO @super:对应的枚举类 @Schema(description = "消息类型", example = "property") private String type; @Schema(description = "标识符", example = "temperature") + // TODO @super:对应的枚举类 private String subType; @Schema(description = "创建时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime[] createTime; -} \ No newline at end of file + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java index 1201f7b74..48ea9b698 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java @@ -11,8 +11,10 @@ public class IotDeviceLogRespVO { @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String id; + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123") private String productKey; + @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123") private String deviceKey; @@ -30,4 +32,5 @@ public class IotDeviceLogRespVO { @Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime ts; -} \ No newline at end of file + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java index d213329e2..e4913486d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java @@ -74,6 +74,7 @@ public class IotThingModelController { return success(IotThingModelConvert.INSTANCE.convertList(list)); } + // TODO @puhui @super:getThingModelListByProductId 和 getThingModelListByProductId 可以融合么? @GetMapping("/list") @Operation(summary = "获得产品物模型列表") @PreAuthorize("@ss.hasPermission('iot:thing-model:query')") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelListReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelListReqVO.java index 3652b36b9..dd77cfc5e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelListReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelListReqVO.java @@ -6,11 +6,10 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; - - @Schema(description = "管理后台 - IoT 产品物模型List Request VO") @Data public class IotThingModelListReqVO { + @Schema(description = "功能标识") private String identifier; @@ -21,7 +20,8 @@ public class IotThingModelListReqVO { @InEnum(IotThingModelTypeEnum.class) private Integer type; - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "产品ID不能为空") + @Schema(description = "产品 ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "产品 ID 不能为空") private Long productId; + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java index c82b231c5..dd811ee32 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java @@ -1,16 +1,15 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device; -import cn.hutool.core.date.DateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - /** * IoT 设备日志数据 DO * + * 目前使用 TDengine 存储 + * * @author alwayssuper */ @Data @@ -18,33 +17,41 @@ import java.time.LocalDateTime; @NoArgsConstructor @AllArgsConstructor public class IotDeviceLogDO { + + // TODO @芋艿:消息 ID 的生成逻辑 /** - * 消息ID + * 消息 ID */ private String id; + // TODO @super:关联要 @下 /** - * 产品ID + * 产品标识 */ private String productKey; + // TODO @super:关联要 @下 /** - * 设备ID + * 设备标识 */ private String deviceKey; + // TODO @super:枚举类 /** - * 消息/日志类型 + * 日志类型 */ private String type; + // TODO @super:枚举类 /** * 标识符:用于标识具体的属性、事件或服务 */ private String subType; /** - * 数据内容:存储具体的消息数据内容,通常是JSON格式 + * 数据内容 + * + * 存储具体的消息数据内容,通常是 JSON 格式 */ private String content; @@ -58,5 +65,4 @@ public class IotDeviceLogDO { */ private Long ts; - } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessageDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessageDO.java index b647c6873..ae70da37b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessageDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessageDO.java @@ -6,7 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; // TODO @芋艿:纠结下字段 -@Deprecated +@Deprecated // TODO @super:看看啥时候删除下哈。 /** * TD 物模型消息日志的数据库 */ @@ -25,7 +25,7 @@ public class ThingModelMessageDO { /** * 系统扩展参数 - * + * * 例如:设备状态、系统时间、固件版本等系统级信息 */ private Object system; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java index 51fd625ad..7d3a06633 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.iot.dal.tdengine; -import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS; @@ -12,7 +11,7 @@ import java.util.List; /** * IOT 设备日志数据 Mapper 接口 - * + * * 基于 TDengine 实现设备日志的存储 */ @Mapper @@ -22,11 +21,12 @@ public interface IotDeviceLogDataMapper { /** * 创建设备日志超级表 - * + * * 注意:初始化时只需创建一次 */ void createDeviceLogSTable(); + // TODO @super:是不是删除哈 /** * 创建设备日志子表 * @@ -34,11 +34,12 @@ public interface IotDeviceLogDataMapper { */ void createDeviceLogTable(@Param("deviceKey") String deviceKey); + // TODO @super:单个参数,不用加 @Param /** * 插入设备日志数据 - * + * * 如果子表不存在,会自动创建子表 - * + * * @param log 设备日志数据 */ void insert(@Param("log") IotDeviceLogDO log); @@ -58,4 +59,5 @@ public interface IotDeviceLogDataMapper { * @return 日志总数 */ Long selectCount(@Param("reqVO") IotDeviceLogPageReqVO reqVO); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java index b04be1199..cbc6e8836 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java @@ -1,10 +1,6 @@ package cn.iocoder.yudao.module.iot.dal.tdengine; -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessageDO; import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS; -import com.baomidou.dynamic.datasource.annotation.DS; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -13,7 +9,7 @@ import org.apache.ibatis.annotations.Param; * 处理 TD 中物模型消息日志的操作 */ @Mapper -@Deprecated +@Deprecated // TODO super:什么时候,删除下哈。 @TDengineDS @InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错 public interface TdThingModelMessageMapper { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitConfiguration.java index e14fa2948..e0057e180 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitConfiguration.java @@ -16,7 +16,7 @@ import org.springframework.core.annotation.Order; @Slf4j @RequiredArgsConstructor @Configuration -@Order(Integer.MAX_VALUE) // 保证在最后执行 +@Order public class TDengineTableInitConfiguration implements ApplicationRunner { private final IotDeviceLogDataService deviceLogService; @@ -26,15 +26,18 @@ public class TDengineTableInitConfiguration implements ApplicationRunner { try { // 初始化设备日志表 deviceLogService.initTDengineSTable(); - log.info("初始化 设备日志表 TDengine 表结构成功"); + // TODO @super:这个日志,是不是不用打,不然重复啦!!! + log.info("[run]初始化 设备日志表 TDengine 表结构成功"); } catch (Exception ex) { + // TODO @super:初始化失败,打印 error 日志,退出系统。。不然跑起来,就初始啦!!! if (ex.getMessage().contains("Table already exists")) { log.info("TDengine 设备日志超级表已存在,跳过创建"); return; - }else{ + } else{ log.error("初始化 设备日志表 TDengine 表结构失败", ex); } throw ex; } } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java index 72cbf63e9..1a3b48503 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDevi import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; - /** * IoT 设备日志数据 Service 接口 * @@ -15,7 +14,7 @@ public interface IotDeviceLogDataService { /** * 初始化 TDengine 超级表 - * + * *系统启动时,会自动初始化一次 */ void initTDengineSTable(); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java index bf883526d..d0659e507 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.iot.service.device; -import cn.hutool.core.date.DateTime; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; @@ -12,8 +11,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.time.LocalDateTime; -import java.time.ZoneId; import java.util.List; /** @@ -29,34 +26,30 @@ public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{ @Resource private IotDeviceLogDataMapper iotDeviceLogDataMapper; - + // TODO @super:方法名。defineDeviceLog。。未来,有可能别人使用别的记录日志,例如说 es 之类的。 @Override public void initTDengineSTable() { - try { - // 创建设备日志超级表 - iotDeviceLogDataMapper.createDeviceLogSTable(); - log.info("创建设备日志超级表成功"); - } catch (Exception ex) { - throw ex; - } + // TODO @super:改成不存在才创建。 + iotDeviceLogDataMapper.createDeviceLogSTable(); } @Override public void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { - //TODO:讨论一下,iotkit这块TS和上报时间都是外部传入的 但是看TDengine文档 他是建议对TS在SQL中直接NOW 咱们的TS数据获取是走哪一种 - // 1. 转换请求对象为 DO IotDeviceLogDO iotDeviceLogDO = BeanUtils.toBean(simulatorReqVO, IotDeviceLogDO.class); - + // 2. 处理时间字段 + // TODO @super:一次性的字段,不用单独给个变量 long currentTime = System.currentTimeMillis(); // 2.1 设置时序时间为当前时间 - iotDeviceLogDO.setTs(currentTime); + iotDeviceLogDO.setTs(currentTime); // TODO @super:TS在SQL中直接NOW 咱们的TS数据获取是走哪一种;走 now() // 3. 插入数据 + // TODO @super:不要直接调用对方的 IotDeviceLogDataMapper,通过 service 哈! iotDeviceLogDataMapper.insert(iotDeviceLogDO); } + // TODO @super:在 iotDeviceLogDataService 写 @Override public PageResult getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO) { // 查询数据 @@ -65,4 +58,5 @@ public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{ // 构造分页结果 return new PageResult<>(list, total); } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java index 5ca4cffe7..0f9523414 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java @@ -18,7 +18,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper; import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper; -import cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper; import cn.iocoder.yudao.module.iot.enums.IotConstants; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; @@ -111,7 +110,6 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe return; } newFields.add(0, new TDengineTableField(TDengineTableField.FIELD_TS, TDengineTableField.TYPE_TIMESTAMP)); - // 2.1.1 创建产品超级表 devicePropertyDataMapper.createProductPropertySTable(product.getProductKey(), newFields); return; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java index 41537664b..ad3ff94e2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper; import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; -import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService; import com.baomidou.dynamic.datasource.annotation.DSTransactional; import jakarta.annotation.Resource; import org.springframework.context.annotation.Lazy; @@ -116,14 +115,15 @@ public class IotProductServiceImpl implements IotProductService { public void updateProductStatus(Long id, Integer status) { // 1. 校验存在 validateProductExists(id); - // 2. 更新 - IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build(); - // 3. 产品是发布状态 - if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) { - // 3.1 创建产品超级表数据模型 - devicePropertyDataService.defineDevicePropertyData(id); + // 2. 产品是发布状态 + if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) { + // 创建产品超级表数据模型 + devicePropertyDataService.defineDevicePropertyData(id); } + + // 3. 更新 + IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build(); productMapper.updateById(updateObj); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java index 52e90f30f..e094b34cb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java @@ -7,20 +7,20 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.*; +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser; +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper; import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper; -import cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper; import cn.iocoder.yudao.module.iot.enums.IotConstants; import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; -import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; import cn.iocoder.yudao.module.iot.service.product.IotProductService; -import cn.iocoder.yudao.module.iot.util.IotTdDatabaseUtils; +import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -61,13 +61,9 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ @Resource private TdEngineDMLMapper tdEngineDMLMapper; - @Resource - private TdThingModelMessageMapper tdThingModelMessageMapper; - @Resource private DeviceDataRedisDAO deviceDataRedisDAO; - - + // TODO @haohao:这个方法,可以考虑加下 1. 2. 3. 更有层次感 @Override @TenantIgnore diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml index dd0f80a94..09e5dd468 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml @@ -7,19 +7,19 @@ CREATE STABLE device_log ( - ts TIMESTAMP, - id NCHAR(50), - product_key NCHAR(50), - type NCHAR(50), - subType NCHAR(50), - content NCHAR(1024), - report_time TIMESTAMP - )TAGS ( - device_key NCHAR(50) - ) + ts TIMESTAMP, + id NCHAR(50), + product_key NCHAR(50), + type NCHAR(50), + + subType NCHAR(50), + content NCHAR(1024), + report_time TIMESTAMP + ) TAGS ( + device_key NCHAR(50) + ) - CREATE TABLE device_log_${deviceKey} USING device_log TAGS('${deviceKey}')