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 25120abe7f..7193d630d8 100644 Binary files a/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar and b/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar differ 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(