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] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E3=80=91IoT:=20=E6=9B=B4=E6=96=B0=E8=AE=BE=E5=A4=87=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=20API=EF=BC=8C=E9=87=8D=E6=9E=84=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=95=B0=E6=8D=AE=E6=96=B9=E6=B3=95=E4=BB=A5?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20DTO=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E6=A0=A1=E9=AA=8C=E4=BE=9D=E8=B5=96=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=8F=92=E4=BB=B6=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E4=B8=8A=E6=8A=A5=E5=92=8C=E7=8A=B6=E6=80=81=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=90=8C=E6=97=B6=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E4=BF=A1=E6=81=AF=E8=8E=B7=E5=8F=96=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E5=88=A0=E9=99=A4=E4=B8=8D=E5=86=8D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=96=87=E4=BB=B6=E5=92=8C=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=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 09ec36308f..49330ee16f 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 0000000000..b7a0b8667c --- /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 e69de29bb2..0000000000 diff --git a/plugins/enabled.txt b/plugins/enabled.txt deleted file mode 100644 index 8cf9b4c87f..0000000000 --- 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 d2f83b785e..cade52eeaf 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 076064db82..6eed3592b5 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 0000000000..94bc84b804 --- /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 9261e4ae1a..263873be7d 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 b4a2a62dba..eea7b2a963 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 ad3b31fc1c..25c0f6bcb7 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 2c1553a722..3c21a55ca8 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 47e7bf5605..d32148b47c 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 08375cb092..a882b5d6cb 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 eb7fcd430c..b39df35901 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 6a44747a6e..2e920e32cb 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 c9030b9244..8e1fae88ab 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 5655f1d3ad..cd1d5a6547 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 6a65fc0265..618d09c733 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 0a9ba9ee47..8682a549c0 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 6d0908683b..b91146712f 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(