Commit cde6ebf92189969ace8739724bc6b9ea925d9e1d
1 parent
b5856c4c
【功能完善】IoT: 更新设备数据 API,重构保存设备数据方法以使用 DTO,新增参数校验依赖,优化插件管理功能,添加插件实例上报和状态更新接口,同时更新插件信息获取逻辑,删除不再使用的文件和配置。
Showing
21 changed files
with
360 additions
and
243 deletions
.gitignore
.vscode/settings.json
0 → 100644
| 1 | +{ | ||
| 2 | + "java.compile.nullAnalysis.mode": "automatic", | ||
| 3 | + "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable", | ||
| 4 | + "java.configuration.updateBuildConfiguration": "interactive" | ||
| 5 | +} | ||
| 0 | \ No newline at end of file | 6 | \ No newline at end of file |
plugins/disabled.txt deleted
plugins/enabled.txt deleted
| 1 | -http-plugin |
plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar
No preview for this file type
yudao-module-iot/yudao-module-iot-api/pom.xml
| @@ -33,6 +33,13 @@ | @@ -33,6 +33,13 @@ | ||
| 33 | </exclusion> | 33 | </exclusion> |
| 34 | </exclusions> | 34 | </exclusions> |
| 35 | </dependency> | 35 | </dependency> |
| 36 | + | ||
| 37 | + <!-- 参数校验 --> | ||
| 38 | + <dependency> | ||
| 39 | + <groupId>org.springframework.boot</groupId> | ||
| 40 | + <artifactId>spring-boot-starter-validation</artifactId> | ||
| 41 | + <optional>true</optional> | ||
| 42 | + </dependency> | ||
| 36 | </dependencies> | 43 | </dependencies> |
| 37 | 44 | ||
| 38 | </project> | 45 | </project> |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java
| 1 | package cn.iocoder.yudao.module.iot.api.device; | 1 | package cn.iocoder.yudao.module.iot.api.device; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; | ||
| 4 | +import jakarta.validation.Valid; | ||
| 5 | + | ||
| 3 | /** | 6 | /** |
| 4 | * 设备数据 API | 7 | * 设备数据 API |
| 5 | * | 8 | * |
| @@ -7,14 +10,11 @@ package cn.iocoder.yudao.module.iot.api.device; | @@ -7,14 +10,11 @@ package cn.iocoder.yudao.module.iot.api.device; | ||
| 7 | */ | 10 | */ |
| 8 | public interface DeviceDataApi { | 11 | public interface DeviceDataApi { |
| 9 | 12 | ||
| 10 | - // TODO @haohao:最好搞成 dto 哈! | ||
| 11 | /** | 13 | /** |
| 12 | * 保存设备数据 | 14 | * 保存设备数据 |
| 13 | * | 15 | * |
| 14 | - * @param productKey 产品 key | ||
| 15 | - * @param deviceName 设备名称 | ||
| 16 | - * @param message 消息 | 16 | + * @param createDTO 设备数据 |
| 17 | */ | 17 | */ |
| 18 | - void saveDeviceData(String productKey, String deviceName, String message); | 18 | + void saveDeviceData(@Valid DeviceDataCreateReqDTO createDTO); |
| 19 | 19 | ||
| 20 | } | 20 | } |
| 21 | \ No newline at end of file | 21 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/DeviceDataCreateReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.api.device.dto; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import lombok.Builder; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import lombok.NoArgsConstructor; | ||
| 7 | +import jakarta.validation.constraints.NotNull; | ||
| 8 | + | ||
| 9 | +@Data | ||
| 10 | +@NoArgsConstructor | ||
| 11 | +@AllArgsConstructor | ||
| 12 | +@Builder | ||
| 13 | +public class DeviceDataCreateReqDTO { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 产品标识 | ||
| 17 | + */ | ||
| 18 | + @NotNull(message = "产品标识不能为空") | ||
| 19 | + private String productKey; | ||
| 20 | + /** | ||
| 21 | + * 设备名称 | ||
| 22 | + */ | ||
| 23 | + @NotNull(message = "设备名称不能为空") | ||
| 24 | + private String deviceName; | ||
| 25 | + /** | ||
| 26 | + * 消息 | ||
| 27 | + */ | ||
| 28 | + @NotNull(message = "消息不能为空") | ||
| 29 | + private String message; | ||
| 30 | + | ||
| 31 | +} | ||
| 0 | \ No newline at end of file | 32 | \ No newline at end of file |
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; | @@ -13,8 +13,8 @@ import java.util.Arrays; | ||
| 13 | @Getter | 13 | @Getter |
| 14 | public enum IotPluginDeployTypeEnum implements IntArrayValuable { | 14 | public enum IotPluginDeployTypeEnum implements IntArrayValuable { |
| 15 | 15 | ||
| 16 | - UPLOAD(0, "上传 jar"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈 | ||
| 17 | - ALONE(1, "独立运行"); | 16 | + DEPLOY_VIA_JAR(0, "通过 jar 部署"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈 |
| 17 | + DEPLOY_STANDALONE(1, "独立部署"); | ||
| 18 | 18 | ||
| 19 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); | 19 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); |
| 20 | 20 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java
| 1 | package cn.iocoder.yudao.module.iot.api.device; | 1 | package cn.iocoder.yudao.module.iot.api.device; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; | ||
| 3 | import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; | 4 | import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; |
| 4 | import org.springframework.stereotype.Service; | 5 | import org.springframework.stereotype.Service; |
| 5 | import org.springframework.validation.annotation.Validated; | 6 | import org.springframework.validation.annotation.Validated; |
| @@ -17,8 +18,8 @@ public class DeviceDataApiImpl implements DeviceDataApi { | @@ -17,8 +18,8 @@ public class DeviceDataApiImpl implements DeviceDataApi { | ||
| 17 | private IotDevicePropertyDataService deviceDataService; | 18 | private IotDevicePropertyDataService deviceDataService; |
| 18 | 19 | ||
| 19 | @Override | 20 | @Override |
| 20 | - public void saveDeviceData(String productKey, String deviceName, String message) { | ||
| 21 | - deviceDataService.saveDeviceData(productKey, deviceName, message); | 21 | + public void saveDeviceData(DeviceDataCreateReqDTO createDTO) { |
| 22 | + deviceDataService.saveDeviceData(createDTO); | ||
| 22 | } | 23 | } |
| 23 | 24 | ||
| 24 | } | 25 | } |
| 25 | \ No newline at end of file | 26 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java
| 1 | package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; | 1 | package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | ||
| 4 | +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; | ||
| 3 | import io.swagger.v3.oas.annotations.media.Schema; | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | -import lombok.*; | 6 | +import lombok.Data; |
| 5 | 7 | ||
| 6 | @Schema(description = "管理后台 - IoT 插件信息新增/修改 Request VO") | 8 | @Schema(description = "管理后台 - IoT 插件信息新增/修改 Request VO") |
| 7 | @Data | 9 | @Data |
| @@ -39,6 +41,7 @@ public class PluginInfoSaveReqVO { | @@ -39,6 +41,7 @@ public class PluginInfoSaveReqVO { | ||
| 39 | private String protocol; | 41 | private String protocol; |
| 40 | 42 | ||
| 41 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) | 43 | @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) |
| 44 | + @InEnum(IotPluginStatusEnum.class) | ||
| 42 | private Integer status; | 45 | private Integer status; |
| 43 | 46 | ||
| 44 | @Schema(description = "插件配置项描述信息") | 47 | @Schema(description = "插件配置项描述信息") |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java
| 1 | package cn.iocoder.yudao.module.iot.emq.service; | 1 | package cn.iocoder.yudao.module.iot.emq.service; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; | ||
| 3 | import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; | 4 | import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; |
| 4 | import jakarta.annotation.Resource; | 5 | import jakarta.annotation.Resource; |
| 5 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| 6 | import org.eclipse.paho.client.mqttv3.MqttClient; | 7 | import org.eclipse.paho.client.mqttv3.MqttClient; |
| 7 | import org.eclipse.paho.client.mqttv3.MqttMessage; | 8 | import org.eclipse.paho.client.mqttv3.MqttMessage; |
| 8 | import org.springframework.scheduling.annotation.Async; | 9 | import org.springframework.scheduling.annotation.Async; |
| 9 | -import org.springframework.stereotype.Service; | ||
| 10 | 10 | ||
| 11 | // TODO @芋艿:在瞅瞅 | 11 | // TODO @芋艿:在瞅瞅 |
| 12 | 12 | ||
| @@ -16,7 +16,7 @@ import org.springframework.stereotype.Service; | @@ -16,7 +16,7 @@ import org.springframework.stereotype.Service; | ||
| 16 | * @author ahh | 16 | * @author ahh |
| 17 | */ | 17 | */ |
| 18 | @Slf4j | 18 | @Slf4j |
| 19 | -//@Service | 19 | +// @Service |
| 20 | public class EmqxServiceImpl implements EmqxService { | 20 | public class EmqxServiceImpl implements EmqxService { |
| 21 | 21 | ||
| 22 | @Resource | 22 | @Resource |
| @@ -34,7 +34,12 @@ public class EmqxServiceImpl implements EmqxService { | @@ -34,7 +34,12 @@ public class EmqxServiceImpl implements EmqxService { | ||
| 34 | String productKey = topic.split("/")[2]; | 34 | String productKey = topic.split("/")[2]; |
| 35 | String deviceName = topic.split("/")[3]; | 35 | String deviceName = topic.split("/")[3]; |
| 36 | String message = new String(mqttMessage.getPayload()); | 36 | String message = new String(mqttMessage.getPayload()); |
| 37 | - iotDeviceDataService.saveDeviceData(productKey, deviceName, message); | 37 | + DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder() |
| 38 | + .productKey(productKey) | ||
| 39 | + .deviceName(deviceName) | ||
| 40 | + .message(message) | ||
| 41 | + .build(); | ||
| 42 | + iotDeviceDataService.saveDeviceData(createDTO); | ||
| 38 | } | 43 | } |
| 39 | } | 44 | } |
| 40 | 45 |
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 { | @@ -22,7 +22,7 @@ public class PluginInstancesJob { | ||
| 22 | @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) | 22 | @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) |
| 23 | public void updatePluginInstances() { | 23 | public void updatePluginInstances() { |
| 24 | TenantUtils.executeIgnore(() -> { | 24 | TenantUtils.executeIgnore(() -> { |
| 25 | - pluginInstanceService.updatePluginInstances(); | 25 | + pluginInstanceService.reportPluginInstances(); |
| 26 | }); | 26 | }); |
| 27 | } | 27 | } |
| 28 | 28 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java
| 1 | package cn.iocoder.yudao.module.iot.service.device; | 1 | package cn.iocoder.yudao.module.iot.service.device; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; | ||
| 4 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; | 5 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; |
| 6 | import jakarta.validation.Valid; | 7 | import jakarta.validation.Valid; |
| @@ -25,12 +26,9 @@ public interface IotDevicePropertyDataService { | @@ -25,12 +26,9 @@ public interface IotDevicePropertyDataService { | ||
| 25 | /** | 26 | /** |
| 26 | * 保存设备数据 | 27 | * 保存设备数据 |
| 27 | * | 28 | * |
| 28 | - * @param productKey 产品 key | ||
| 29 | - * @param deviceName 设备名称 | ||
| 30 | - * @param message 消息 | ||
| 31 | - * <p>参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">JSON 格式</a> | 29 | + * @param createDTO 设备数据 |
| 32 | */ | 30 | */ |
| 33 | - void saveDeviceData(String productKey, String deviceName, String message); | 31 | + void saveDeviceData(DeviceDataCreateReqDTO createDTO); |
| 34 | 32 | ||
| 35 | /** | 33 | /** |
| 36 | * 获得设备属性最新数据 | 34 | * 获得设备属性最新数据 |
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; | @@ -6,6 +6,7 @@ import cn.hutool.core.map.MapUtil; | ||
| 6 | import cn.hutool.core.util.StrUtil; | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | import cn.hutool.json.JSONObject; | 7 | import cn.hutool.json.JSONObject; |
| 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; | ||
| 9 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; | 10 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; |
| 10 | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs; | 11 | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs; |
| 11 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 12 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| @@ -14,8 +15,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | @@ -14,8 +15,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | ||
| 14 | import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO; | 15 | import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO; |
| 15 | import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; | 16 | import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; |
| 16 | import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; | 17 | import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; |
| 17 | -import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper; | ||
| 18 | import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; | 18 | import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; |
| 19 | +import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper; | ||
| 19 | import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper; | 20 | import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper; |
| 20 | import cn.iocoder.yudao.module.iot.enums.IotConstants; | 21 | import cn.iocoder.yudao.module.iot.enums.IotConstants; |
| 21 | import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; | 22 | import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; |
| @@ -56,7 +57,7 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe | @@ -56,7 +57,7 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe | ||
| 56 | .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT) | 57 | .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT) |
| 57 | .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE) | 58 | .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE) |
| 58 | .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? | 59 | .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? |
| 59 | - .put( IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? | 60 | + .put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? |
| 60 | .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR) | 61 | .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR) |
| 61 | .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP) | 62 | .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP) |
| 62 | .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!! | 63 | .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!! |
| @@ -128,20 +129,20 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe | @@ -128,20 +129,20 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe | ||
| 128 | } | 129 | } |
| 129 | 130 | ||
| 130 | @Override | 131 | @Override |
| 131 | - public void saveDeviceData(String productKey, String deviceName, String message) { | 132 | + public void saveDeviceData(DeviceDataCreateReqDTO createDTO) { |
| 132 | // 1. 根据产品 key 和设备名称,获得设备信息 | 133 | // 1. 根据产品 key 和设备名称,获得设备信息 |
| 133 | - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName); | 134 | + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(createDTO.getProductKey(), createDTO.getDeviceName()); |
| 134 | // 2. 解析消息,保存数据 | 135 | // 2. 解析消息,保存数据 |
| 135 | - JSONObject jsonObject = new JSONObject(message); | ||
| 136 | - log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject); | 136 | + JSONObject jsonObject = new JSONObject(createDTO.getMessage()); |
| 137 | + log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", createDTO.getProductKey(), createDTO.getDeviceName(), jsonObject); | ||
| 137 | ThingModelMessage thingModelMessage = ThingModelMessage.builder() | 138 | ThingModelMessage thingModelMessage = ThingModelMessage.builder() |
| 138 | .id(jsonObject.getStr("id")) | 139 | .id(jsonObject.getStr("id")) |
| 139 | .sys(jsonObject.get("sys")) | 140 | .sys(jsonObject.get("sys")) |
| 140 | .method(jsonObject.getStr("method")) | 141 | .method(jsonObject.getStr("method")) |
| 141 | .params(jsonObject.get("params")) | 142 | .params(jsonObject.get("params")) |
| 142 | .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time")) | 143 | .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time")) |
| 143 | - .productKey(productKey) | ||
| 144 | - .deviceName(deviceName) | 144 | + .productKey(createDTO.getProductKey()) |
| 145 | + .deviceName(createDTO.getDeviceName()) | ||
| 145 | .deviceKey(device.getDeviceKey()) | 146 | .deviceKey(device.getDeviceKey()) |
| 146 | .build(); | 147 | .build(); |
| 147 | thingModelMessageService.saveThingModelMessage(device, thingModelMessage); | 148 | thingModelMessageService.saveThingModelMessage(device, thingModelMessage); |
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 { | @@ -78,9 +78,10 @@ public interface PluginInfoService { | ||
| 78 | List<PluginInfoDO> getPluginInfoList(); | 78 | List<PluginInfoDO> getPluginInfoList(); |
| 79 | 79 | ||
| 80 | /** | 80 | /** |
| 81 | - * 获得运行状态的插件信息列表 | 81 | + * 根据状态获得插件信息列表 |
| 82 | * | 82 | * |
| 83 | - * @return 运行状态的插件信息列表 | 83 | + * @param status 状态 |
| 84 | + * @return 插件信息列表 | ||
| 84 | */ | 85 | */ |
| 85 | - List<PluginInfoDO> getRunningPluginInfoList(); | 86 | + List<PluginInfoDO> getPluginInfoListByStatus(Integer status); |
| 86 | } | 87 | } |
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; | @@ -9,25 +9,16 @@ import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper; | ||
| 9 | import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; | 9 | import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; |
| 10 | import jakarta.annotation.Resource; | 10 | import jakarta.annotation.Resource; |
| 11 | import lombok.extern.slf4j.Slf4j; | 11 | import lombok.extern.slf4j.Slf4j; |
| 12 | -import org.pf4j.PluginDescriptor; | ||
| 13 | -import org.pf4j.PluginState; | ||
| 14 | -import org.pf4j.PluginWrapper; | ||
| 15 | import org.pf4j.spring.SpringPluginManager; | 12 | import org.pf4j.spring.SpringPluginManager; |
| 16 | -import org.springframework.beans.factory.annotation.Value; | ||
| 17 | import org.springframework.stereotype.Service; | 13 | import org.springframework.stereotype.Service; |
| 18 | import org.springframework.validation.annotation.Validated; | 14 | import org.springframework.validation.annotation.Validated; |
| 19 | import org.springframework.web.multipart.MultipartFile; | 15 | import org.springframework.web.multipart.MultipartFile; |
| 20 | 16 | ||
| 21 | -import java.io.File; | ||
| 22 | -import java.io.IOException; | ||
| 23 | -import java.nio.file.*; | ||
| 24 | -import java.util.ArrayList; | ||
| 25 | import java.util.List; | 17 | import java.util.List; |
| 26 | -import java.util.concurrent.Executors; | ||
| 27 | -import java.util.concurrent.TimeUnit; | ||
| 28 | 18 | ||
| 29 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 19 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 30 | -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; | 20 | +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_DELETE_FAILED_RUNNING; |
| 21 | +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_NOT_EXISTS; | ||
| 31 | 22 | ||
| 32 | /** | 23 | /** |
| 33 | * IoT 插件信息 Service 实现类 | 24 | * IoT 插件信息 Service 实现类 |
| @@ -43,11 +34,10 @@ public class PluginInfoServiceImpl implements PluginInfoService { | @@ -43,11 +34,10 @@ public class PluginInfoServiceImpl implements PluginInfoService { | ||
| 43 | private PluginInfoMapper pluginInfoMapper; | 34 | private PluginInfoMapper pluginInfoMapper; |
| 44 | 35 | ||
| 45 | @Resource | 36 | @Resource |
| 46 | - private SpringPluginManager pluginManager; | 37 | + private PluginInstanceService pluginInstanceService; |
| 47 | 38 | ||
| 48 | - // TODO @芋艿:要不要换位置 | ||
| 49 | - @Value("${pf4j.pluginsDir}") | ||
| 50 | - private String pluginsDir; | 39 | + @Resource |
| 40 | + private SpringPluginManager pluginManager; | ||
| 51 | 41 | ||
| 52 | @Override | 42 | @Override |
| 53 | public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) { | 43 | public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) { |
| @@ -75,32 +65,13 @@ public class PluginInfoServiceImpl implements PluginInfoService { | @@ -75,32 +65,13 @@ public class PluginInfoServiceImpl implements PluginInfoService { | ||
| 75 | } | 65 | } |
| 76 | 66 | ||
| 77 | // 2. 卸载插件 | 67 | // 2. 卸载插件 |
| 78 | - // TODO @haohao:可以复用 stopAndUnloadPlugin | ||
| 79 | - PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey()); | ||
| 80 | - if (plugin != null) { | ||
| 81 | - // 停止插件 | ||
| 82 | - if (plugin.getPluginState().equals(PluginState.STARTED)) { | ||
| 83 | - pluginManager.stopPlugin(plugin.getPluginId()); | ||
| 84 | - } | ||
| 85 | - // 卸载插件 | ||
| 86 | - pluginManager.unloadPlugin(plugin.getPluginId()); | ||
| 87 | - } | 68 | + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDO.getPluginKey()); |
| 69 | + | ||
| 70 | + // 3. 删除插件文件 | ||
| 71 | + pluginInstanceService.deletePluginFile(pluginInfoDO); | ||
| 88 | 72 | ||
| 89 | - // 3.1 删除 | 73 | + // 4. 删除插件信息 |
| 90 | pluginInfoMapper.deleteById(id); | 74 | pluginInfoMapper.deleteById(id); |
| 91 | - // 3.2 删除插件文件 | ||
| 92 | - // TODO @haohao:这个直接主线程 sleep 就好了,不用单独开线程池哈。原因是,低频操作;另外,只有存在的时候,才 sleep + 删除; | ||
| 93 | - Executors.newSingleThreadExecutor().submit(() -> { | ||
| 94 | - try { | ||
| 95 | - TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 | ||
| 96 | - File file = new File(pluginsDir, pluginInfoDO.getFileName()); | ||
| 97 | - if (file.exists() && !file.delete()) { | ||
| 98 | - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); | ||
| 99 | - } | ||
| 100 | - } catch (InterruptedException e) { | ||
| 101 | - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); | ||
| 102 | - } | ||
| 103 | - }); | ||
| 104 | } | 75 | } |
| 105 | 76 | ||
| 106 | private PluginInfoDO validatePluginInfoExists(Long id) { | 77 | private PluginInfoDO validatePluginInfoExists(Long id) { |
| @@ -127,144 +98,52 @@ public class PluginInfoServiceImpl implements PluginInfoService { | @@ -127,144 +98,52 @@ public class PluginInfoServiceImpl implements PluginInfoService { | ||
| 127 | PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); | 98 | PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); |
| 128 | 99 | ||
| 129 | // 2. 停止并卸载旧的插件 | 100 | // 2. 停止并卸载旧的插件 |
| 130 | - stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); | 101 | + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); |
| 131 | 102 | ||
| 132 | - // 3.1 上传新的插件文件 | ||
| 133 | - String pluginKeyNew = uploadAndLoadNewPlugin(file); | ||
| 134 | - // 3.2 更新插件启用状态文件 | ||
| 135 | - updatePluginStatusFile(pluginKeyNew, false); | 103 | + // 3 上传新的插件文件,更新插件启用状态文件 |
| 104 | + String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); | ||
| 105 | + pluginInstanceService.updatePluginStatusFile(pluginKeyNew, false); | ||
| 136 | 106 | ||
| 137 | // 4. 更新插件信息 | 107 | // 4. 更新插件信息 |
| 138 | updatePluginInfo(pluginInfoDo, pluginKeyNew, file); | 108 | updatePluginInfo(pluginInfoDo, pluginKeyNew, file); |
| 139 | } | 109 | } |
| 140 | 110 | ||
| 141 | - // TODO @haohao:注释的格式 | ||
| 142 | - // 停止并卸载旧的插件 | ||
| 143 | - private void stopAndUnloadPlugin(String pluginKey) { | ||
| 144 | - PluginWrapper plugin = pluginManager.getPlugin(pluginKey); | ||
| 145 | - if (plugin != null) { | ||
| 146 | - if (plugin.getPluginState().equals(PluginState.STARTED)) { | ||
| 147 | - pluginManager.stopPlugin(pluginKey); // 停止插件 | ||
| 148 | - } | ||
| 149 | - pluginManager.unloadPlugin(pluginKey); // 卸载插件 | ||
| 150 | - } | ||
| 151 | - } | ||
| 152 | - | ||
| 153 | - // TODO @haohao:注释的格式 | ||
| 154 | - // 上传并加载新的插件文件 | ||
| 155 | - private String uploadAndLoadNewPlugin(MultipartFile file) { | ||
| 156 | - // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载 | ||
| 157 | - Path pluginsPath = Paths.get(pluginsDir); | ||
| 158 | - try { | ||
| 159 | - // TODO @haohao:可以使用 FileUtil 简化? | ||
| 160 | - if (!Files.exists(pluginsPath)) { | ||
| 161 | - Files.createDirectories(pluginsPath); // 创建插件目录 | ||
| 162 | - } | ||
| 163 | - String filename = file.getOriginalFilename(); | ||
| 164 | - if (filename != null) { | ||
| 165 | - Path jarPath = pluginsPath.resolve(filename); | ||
| 166 | - Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件 | ||
| 167 | - return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件 | ||
| 168 | - } | ||
| 169 | - throw exception(PLUGIN_INSTALL_FAILED); // TODO @haohao:这么抛的话,貌似会被 catch (Exception e) { | ||
| 170 | - } catch (Exception e) { | ||
| 171 | - // TODO @haohao:打个 error log,方便排查 | ||
| 172 | - throw exception(PLUGIN_INSTALL_FAILED); | ||
| 173 | - } | ||
| 174 | - } | ||
| 175 | - | ||
| 176 | - // TODO @haohao:注释的格式 | ||
| 177 | - // 更新插件状态文件 | ||
| 178 | - private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) { | ||
| 179 | - // TODO @haohao:疑问,这里写 enabled.txt 和 disabled.txt 的目的是啥哈? | ||
| 180 | - Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt"); | ||
| 181 | - Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt"); | ||
| 182 | - Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath; | ||
| 183 | - Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath; | ||
| 184 | - | ||
| 185 | - try { | ||
| 186 | - PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew); | ||
| 187 | - if (pluginWrapper == null) { | ||
| 188 | - throw exception(PLUGIN_INSTALL_FAILED); | ||
| 189 | - } | ||
| 190 | - List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) : new ArrayList<>(); | ||
| 191 | - List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) : new ArrayList<>(); | ||
| 192 | - | ||
| 193 | - if (!targetLines.contains(pluginKeyNew)) { | ||
| 194 | - targetLines.add(pluginKeyNew); | ||
| 195 | - Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, | ||
| 196 | - StandardOpenOption.TRUNCATE_EXISTING); | ||
| 197 | - } | ||
| 198 | - | ||
| 199 | - if (oppositeLines.contains(pluginKeyNew)) { | ||
| 200 | - oppositeLines.remove(pluginKeyNew); | ||
| 201 | - Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, | ||
| 202 | - StandardOpenOption.TRUNCATE_EXISTING); | ||
| 203 | - } | ||
| 204 | - } catch (IOException e) { | ||
| 205 | - throw exception(PLUGIN_INSTALL_FAILED); | ||
| 206 | - } | ||
| 207 | - } | ||
| 208 | - | ||
| 209 | - // TODO @haohao:注释的格式 | ||
| 210 | - // 更新插件信息 | 111 | + /** |
| 112 | + * 更新插件信息 | ||
| 113 | + * | ||
| 114 | + * @param pluginInfoDo 插件信息 | ||
| 115 | + * @param pluginKeyNew 插件标识符 | ||
| 116 | + * @param file 文件 | ||
| 117 | + */ | ||
| 211 | private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) { | 118 | private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) { |
| 212 | - // TODO @haohao:更新实体的时候,最好 new 一个新的! | ||
| 213 | - // TODO @haohao:可以链式调用,简化下代码; | ||
| 214 | - pluginInfoDo.setPluginKey(pluginKeyNew); | ||
| 215 | - pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus()); | ||
| 216 | - pluginInfoDo.setFileName(file.getOriginalFilename()); | ||
| 217 | - pluginInfoDo.setScript(""); | ||
| 218 | - // 解析 pf4j 插件 | ||
| 219 | - PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor(); | ||
| 220 | - pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription()); | ||
| 221 | - pluginInfoDo.setVersion(pluginDescriptor.getVersion()); | ||
| 222 | - pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription()); | 119 | + // 创建新的插件信息对象并链式设置属性 |
| 120 | + PluginInfoDO updatedPluginInfo = new PluginInfoDO() | ||
| 121 | + .setId(pluginInfoDo.getId()) | ||
| 122 | + .setPluginKey(pluginKeyNew) | ||
| 123 | + .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) | ||
| 124 | + .setFileName(file.getOriginalFilename()) | ||
| 125 | + .setScript("") | ||
| 126 | + .setConfigSchema(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription()) | ||
| 127 | + .setVersion(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion()) | ||
| 128 | + .setDescription(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription()); | ||
| 223 | 129 | ||
| 224 | // 执行更新 | 130 | // 执行更新 |
| 225 | - pluginInfoMapper.updateById(pluginInfoDo); | 131 | + pluginInfoMapper.updateById(updatedPluginInfo); |
| 226 | } | 132 | } |
| 227 | 133 | ||
| 228 | - // TODO @haohao:status、state 字段命名,要统一下~ | ||
| 229 | @Override | 134 | @Override |
| 230 | public void updatePluginStatus(Long id, Integer status) { | 135 | public void updatePluginStatus(Long id, Integer status) { |
| 231 | // 1. 校验插件信息是否存在 | 136 | // 1. 校验插件信息是否存在 |
| 232 | PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); | 137 | PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); |
| 233 | 138 | ||
| 234 | - // 2. 校验插件状态是否有效 | ||
| 235 | - // TODO @haohao:直接参数校验掉。通过 @InEnum | ||
| 236 | - if (!IotPluginStatusEnum.contains(status)) { | ||
| 237 | - throw exception(PLUGIN_STATUS_INVALID); | ||
| 238 | - } | ||
| 239 | - | ||
| 240 | - // 3. 获取插件标识和插件实例 | ||
| 241 | - String pluginKey = pluginInfoDo.getPluginKey(); | ||
| 242 | - PluginWrapper plugin = pluginManager.getPlugin(pluginKey); | ||
| 243 | - | ||
| 244 | - // 4. 根据状态更新插件 | ||
| 245 | - if (plugin != null) { | ||
| 246 | - // 4.1 启动:如果目标状态是运行且插件未启动,则启动插件 | ||
| 247 | - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) | ||
| 248 | - && plugin.getPluginState() != PluginState.STARTED) { | ||
| 249 | - pluginManager.startPlugin(pluginKey); | ||
| 250 | - updatePluginStatusFile(pluginKey, true); | ||
| 251 | - // 4.2 停止:如果目标状态是停止且插件已启动,则停止插件 | ||
| 252 | - } else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) | ||
| 253 | - && plugin.getPluginState() == PluginState.STARTED) { | ||
| 254 | - pluginManager.stopPlugin(pluginKey); | ||
| 255 | - updatePluginStatusFile(pluginKey, false); | ||
| 256 | - } | ||
| 257 | - } else { | ||
| 258 | - // 5. 插件不存在且状态为停止,抛出异常 | ||
| 259 | - if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { | ||
| 260 | - throw exception(PLUGIN_STATUS_INVALID); | ||
| 261 | - } | ||
| 262 | - } | 139 | + // 2. 更新插件状态 |
| 140 | + pluginInstanceService.updatePluginStatus(pluginInfoDo, status); | ||
| 263 | 141 | ||
| 264 | - // 6. 更新数据库中的插件状态 | ||
| 265 | - // TODO @haohao:新建新建 pluginInfoDo 哈! | ||
| 266 | - pluginInfoDo.setStatus(status); | ||
| 267 | - pluginInfoMapper.updateById(pluginInfoDo); | 142 | + // 3. 更新数据库中的插件状态 |
| 143 | + PluginInfoDO updatedPluginInfo = new PluginInfoDO(); | ||
| 144 | + updatedPluginInfo.setId(id); | ||
| 145 | + updatedPluginInfo.setStatus(status); | ||
| 146 | + pluginInfoMapper.updateById(updatedPluginInfo); | ||
| 268 | } | 147 | } |
| 269 | 148 | ||
| 270 | @Override | 149 | @Override |
| @@ -272,10 +151,9 @@ public class PluginInfoServiceImpl implements PluginInfoService { | @@ -272,10 +151,9 @@ public class PluginInfoServiceImpl implements PluginInfoService { | ||
| 272 | return pluginInfoMapper.selectList(); | 151 | return pluginInfoMapper.selectList(); |
| 273 | } | 152 | } |
| 274 | 153 | ||
| 275 | - // TODO @haohao:可以改成 getPluginInfoListByStatus 更通用哈。 | ||
| 276 | @Override | 154 | @Override |
| 277 | - public List<PluginInfoDO> getRunningPluginInfoList() { | ||
| 278 | - return pluginInfoMapper.selectListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); | 155 | + public List<PluginInfoDO> getPluginInfoListByStatus(Integer status) { |
| 156 | + return pluginInfoMapper.selectListByStatus(status); | ||
| 279 | } | 157 | } |
| 280 | 158 | ||
| 281 | } | 159 | } |
| 282 | \ No newline at end of file | 160 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java
| 1 | package cn.iocoder.yudao.module.iot.service.plugin; | 1 | package cn.iocoder.yudao.module.iot.service.plugin; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; | ||
| 4 | +import org.springframework.web.multipart.MultipartFile; | ||
| 5 | + | ||
| 3 | /** | 6 | /** |
| 4 | * IoT 插件实例 Service 接口 | 7 | * IoT 插件实例 Service 接口 |
| 5 | * | 8 | * |
| @@ -8,8 +11,46 @@ package cn.iocoder.yudao.module.iot.service.plugin; | @@ -8,8 +11,46 @@ package cn.iocoder.yudao.module.iot.service.plugin; | ||
| 8 | public interface PluginInstanceService { | 11 | public interface PluginInstanceService { |
| 9 | 12 | ||
| 10 | /** | 13 | /** |
| 11 | - * 更新IoT 插件实例 | 14 | + * 上报插件实例 |
| 15 | + */ | ||
| 16 | + void reportPluginInstances(); | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 停止并卸载插件 | ||
| 20 | + * | ||
| 21 | + * @param pluginKey 插件标识符 | ||
| 22 | + */ | ||
| 23 | + void stopAndUnloadPlugin(String pluginKey); | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 删除插件文件 | ||
| 27 | + * | ||
| 28 | + * @param pluginInfoDo 插件信息 | ||
| 29 | + */ | ||
| 30 | + void deletePluginFile(PluginInfoDO pluginInfoDo); | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 上传并加载新的插件文件 | ||
| 34 | + * | ||
| 35 | + * @param file 插件文件 | ||
| 36 | + * @return 插件标识符 | ||
| 37 | + */ | ||
| 38 | + String uploadAndLoadNewPlugin(MultipartFile file); | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * 更新插件状态文件 | ||
| 42 | + * | ||
| 43 | + * @param pluginKeyNew 插件标识符 | ||
| 44 | + * @param isEnabled 是否启用 | ||
| 45 | + */ | ||
| 46 | + void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled); | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 更新插件状态 | ||
| 50 | + * | ||
| 51 | + * @param pluginInfoDo 插件信息 | ||
| 52 | + * @param status 新状态 | ||
| 12 | */ | 53 | */ |
| 13 | - void updatePluginInstances(); | 54 | + void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status); |
| 14 | 55 | ||
| 15 | } | 56 | } |
| 16 | \ No newline at end of file | 57 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java
| 1 | package cn.iocoder.yudao.module.iot.service.plugin; | 1 | package cn.iocoder.yudao.module.iot.service.plugin; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.io.FileUtil; | ||
| 3 | import cn.hutool.core.net.NetUtil; | 4 | import cn.hutool.core.net.NetUtil; |
| 4 | import cn.hutool.core.util.IdUtil; | 5 | import cn.hutool.core.util.IdUtil; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; |
| 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; | 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; |
| 8 | +import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper; | ||
| 7 | import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper; | 9 | import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper; |
| 10 | +import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants; | ||
| 11 | +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; | ||
| 8 | import jakarta.annotation.Resource; | 12 | import jakarta.annotation.Resource; |
| 9 | import lombok.extern.slf4j.Slf4j; | 13 | import lombok.extern.slf4j.Slf4j; |
| 14 | +import org.pf4j.PluginState; | ||
| 10 | import org.pf4j.PluginWrapper; | 15 | import org.pf4j.PluginWrapper; |
| 11 | import org.pf4j.spring.SpringPluginManager; | 16 | import org.pf4j.spring.SpringPluginManager; |
| 12 | import org.springframework.beans.factory.annotation.Value; | 17 | import org.springframework.beans.factory.annotation.Value; |
| 13 | import org.springframework.stereotype.Service; | 18 | import org.springframework.stereotype.Service; |
| 14 | import org.springframework.validation.annotation.Validated; | 19 | import org.springframework.validation.annotation.Validated; |
| 20 | +import org.springframework.web.multipart.MultipartFile; | ||
| 15 | 21 | ||
| 22 | +import java.io.File; | ||
| 23 | +import java.io.IOException; | ||
| 24 | +import java.net.Inet4Address; | ||
| 25 | +import java.net.InetAddress; | ||
| 26 | +import java.nio.file.*; | ||
| 27 | +import java.util.ArrayList; | ||
| 28 | +import java.util.LinkedHashSet; | ||
| 16 | import java.util.List; | 29 | import java.util.List; |
| 30 | +import java.util.Map; | ||
| 31 | +import java.util.concurrent.TimeUnit; | ||
| 32 | +import java.util.function.Function; | ||
| 33 | +import java.util.stream.Collectors; | ||
| 34 | + | ||
| 35 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 17 | 36 | ||
| 18 | /** | 37 | /** |
| 19 | * IoT 插件实例 Service 实现类 | 38 | * IoT 插件实例 Service 实现类 |
| @@ -25,79 +44,195 @@ import java.util.List; | @@ -25,79 +44,195 @@ import java.util.List; | ||
| 25 | @Slf4j | 44 | @Slf4j |
| 26 | public class PluginInstanceServiceImpl implements PluginInstanceService { | 45 | public class PluginInstanceServiceImpl implements PluginInstanceService { |
| 27 | 46 | ||
| 28 | - /** | ||
| 29 | - * 主程序 ID | ||
| 30 | - */ | ||
| 31 | // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 | 47 | // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 |
| 48 | + // 简化的UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件 | ||
| 32 | public static final String MAIN_ID = IdUtil.fastSimpleUUID(); | 49 | public static final String MAIN_ID = IdUtil.fastSimpleUUID(); |
| 33 | 50 | ||
| 34 | @Resource | 51 | @Resource |
| 35 | - private PluginInfoService pluginInfoService; | 52 | + private PluginInfoMapper pluginInfoMapper; |
| 36 | @Resource | 53 | @Resource |
| 37 | private PluginInstanceMapper pluginInstanceMapper; | 54 | private PluginInstanceMapper pluginInstanceMapper; |
| 38 | @Resource | 55 | @Resource |
| 39 | private SpringPluginManager pluginManager; | 56 | private SpringPluginManager pluginManager; |
| 40 | 57 | ||
| 58 | + @Value("${pf4j.pluginsDir}") | ||
| 59 | + private String pluginsDir; | ||
| 60 | + | ||
| 41 | @Value("${server.port:48080}") | 61 | @Value("${server.port:48080}") |
| 42 | private int port; | 62 | private int port; |
| 43 | 63 | ||
| 44 | - // TODO @haohao:建议把 PluginInfoServiceImpl 里面,和 instance 相关的逻辑拿过来,可能会更好。info 处理信息,instance 处理实例 | 64 | + @Override |
| 65 | + public void stopAndUnloadPlugin(String pluginKey) { | ||
| 66 | + PluginWrapper plugin = pluginManager.getPlugin(pluginKey); | ||
| 67 | + if (plugin != null) { | ||
| 68 | + if (plugin.getPluginState().equals(PluginState.STARTED)) { | ||
| 69 | + pluginManager.stopPlugin(pluginKey); // 停止插件 | ||
| 70 | + log.info("已停止插件: {}", pluginKey); | ||
| 71 | + } | ||
| 72 | + pluginManager.unloadPlugin(pluginKey); // 卸载插件 | ||
| 73 | + log.info("已卸载插件: {}", pluginKey); | ||
| 74 | + } else { | ||
| 75 | + log.warn("插件不存在或已卸载: {}", pluginKey); | ||
| 76 | + } | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + @Override | ||
| 80 | + public void deletePluginFile(PluginInfoDO pluginInfoDO) { | ||
| 81 | + File file = new File(pluginsDir, pluginInfoDO.getFileName()); | ||
| 82 | + if (file.exists()) { | ||
| 83 | + try { | ||
| 84 | + TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 | ||
| 85 | + if (!file.delete()) { | ||
| 86 | + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); | ||
| 87 | + } | ||
| 88 | + } catch (InterruptedException e) { | ||
| 89 | + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), | ||
| 90 | + e); | ||
| 91 | + Thread.currentThread().interrupt(); // 恢复中断状态 | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + @Override | ||
| 97 | + public String uploadAndLoadNewPlugin(MultipartFile file) { | ||
| 98 | + String pluginKeyNew; | ||
| 99 | + // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载 | ||
| 100 | + Path pluginsPath = Paths.get(pluginsDir); | ||
| 101 | + try { | ||
| 102 | + FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录 | ||
| 103 | + String filename = file.getOriginalFilename(); | ||
| 104 | + if (filename != null) { | ||
| 105 | + Path jarPath = pluginsPath.resolve(filename); | ||
| 106 | + Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件 | ||
| 107 | + pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件 | ||
| 108 | + log.info("已加载插件: {}", pluginKeyNew); | ||
| 109 | + } else { | ||
| 110 | + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED); | ||
| 111 | + } | ||
| 112 | + } catch (IOException e) { | ||
| 113 | + log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e); | ||
| 114 | + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); | ||
| 115 | + } catch (Exception e) { | ||
| 116 | + log.error("[uploadAndLoadNewPlugin][加载插件失败]", e); | ||
| 117 | + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); | ||
| 118 | + } | ||
| 119 | + return pluginKeyNew; | ||
| 120 | + } | ||
| 45 | 121 | ||
| 46 | - // TODO @haohao:这个改成 reportPluginInstance 会不会更合适哈。 | ||
| 47 | @Override | 122 | @Override |
| 48 | - public void updatePluginInstances() { | ||
| 49 | - // 1.1 查询 pf4j 插件列表 | 123 | + public void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) { |
| 124 | + // TODO @haohao:疑问,这里写 enabled.txt 和 disabled.txt 的目的是啥哈? | ||
| 125 | + // pf4j 的插件状态文件,需要 2 个文件,一个 enabled.txt 一个 disabled.txt | ||
| 126 | + Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt"); | ||
| 127 | + Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt"); | ||
| 128 | + Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath; | ||
| 129 | + Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath; | ||
| 130 | + | ||
| 131 | + try { | ||
| 132 | + PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew); | ||
| 133 | + if (pluginWrapper == null) { | ||
| 134 | + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED); | ||
| 135 | + } | ||
| 136 | + List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) | ||
| 137 | + : new ArrayList<>(); | ||
| 138 | + List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) | ||
| 139 | + : new ArrayList<>(); | ||
| 140 | + | ||
| 141 | + if (!targetLines.contains(pluginKeyNew)) { | ||
| 142 | + targetLines.add(pluginKeyNew); | ||
| 143 | + Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, | ||
| 144 | + StandardOpenOption.TRUNCATE_EXISTING); | ||
| 145 | + log.info("已添加插件 {} 到 {}", pluginKeyNew, targetFilePath.getFileName()); | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + if (oppositeLines.contains(pluginKeyNew)) { | ||
| 149 | + oppositeLines.remove(pluginKeyNew); | ||
| 150 | + Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, | ||
| 151 | + StandardOpenOption.TRUNCATE_EXISTING); | ||
| 152 | + log.info("已从 {} 移除插件 {}", oppositeFilePath.getFileName(), pluginKeyNew); | ||
| 153 | + } | ||
| 154 | + } catch (IOException e) { | ||
| 155 | + log.error("[updatePluginStatusFile][更新插件状态文件失败]", e); | ||
| 156 | + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); | ||
| 157 | + } | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + @Override | ||
| 161 | + public void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status) { | ||
| 162 | + String pluginKey = pluginInfoDo.getPluginKey(); | ||
| 163 | + PluginWrapper plugin = pluginManager.getPlugin(pluginKey); | ||
| 164 | + | ||
| 165 | + if (plugin != null) { | ||
| 166 | + // 启动插件 | ||
| 167 | + if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) | ||
| 168 | + && plugin.getPluginState() != PluginState.STARTED) { | ||
| 169 | + pluginManager.startPlugin(pluginKey); | ||
| 170 | + updatePluginStatusFile(pluginKey, true); | ||
| 171 | + log.info("已启动插件: {}", pluginKey); | ||
| 172 | + } | ||
| 173 | + // 停止插件 | ||
| 174 | + else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) | ||
| 175 | + && plugin.getPluginState() == PluginState.STARTED) { | ||
| 176 | + pluginManager.stopPlugin(pluginKey); | ||
| 177 | + updatePluginStatusFile(pluginKey, false); | ||
| 178 | + log.info("已停止插件: {}", pluginKey); | ||
| 179 | + } | ||
| 180 | + } else { | ||
| 181 | + // 插件不存在且状态为停止,抛出异常 | ||
| 182 | + if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { | ||
| 183 | + throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID); | ||
| 184 | + } | ||
| 185 | + } | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + @Override | ||
| 189 | + public void reportPluginInstances() { | ||
| 190 | + // 1. 获取 pf4j 插件列表 | ||
| 50 | List<PluginWrapper> plugins = pluginManager.getPlugins(); | 191 | List<PluginWrapper> plugins = pluginManager.getPlugins(); |
| 51 | - // 1.2 查询插件信息列表 | ||
| 52 | - List<PluginInfoDO> pluginInfos = pluginInfoService.getPluginInfoList(); | ||
| 53 | - // 1.3 动态获取主程序的 IP 和端口 | ||
| 54 | - String mainIp = getLocalIpAddress(); | ||
| 55 | 192 | ||
| 56 | - // 2. 遍历插件列表,并保存为插件实例 | 193 | + // 2. 获取插件信息列表并转换为 Map 以便快速查找 |
| 194 | + List<PluginInfoDO> pluginInfos = pluginInfoMapper.selectList(); | ||
| 195 | + Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream() | ||
| 196 | + .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); | ||
| 197 | + | ||
| 198 | + // 3. 获取本机 IP 和 MAC 地址 | ||
| 199 | + LinkedHashSet<InetAddress> localAddressList = NetUtil.localAddressList(t -> t instanceof Inet4Address); | ||
| 200 | + LinkedHashSet<String> ipList = NetUtil.toIpList(localAddressList); | ||
| 201 | + String ip = ipList.stream().findFirst().orElse("127.0.0.1"); | ||
| 202 | + String mac = NetUtil.getMacAddress(localAddressList.stream().findFirst().orElse(null)); | ||
| 203 | + String mainId = MAIN_ID + "-" + mac; | ||
| 204 | + | ||
| 205 | + // 4. 遍历插件列表,并保存为插件实例 | ||
| 57 | for (PluginWrapper plugin : plugins) { | 206 | for (PluginWrapper plugin : plugins) { |
| 58 | - // 2.1 查找插件信息 | ||
| 59 | String pluginKey = plugin.getPluginId(); | 207 | String pluginKey = plugin.getPluginId(); |
| 60 | - // TODO @haohao:CollUtil.findOne() 简化 | ||
| 61 | - PluginInfoDO pluginInfo = pluginInfos.stream() | ||
| 62 | - .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey)) | ||
| 63 | - .findFirst() | ||
| 64 | - .orElse(null); | 208 | + |
| 209 | + // 4.1 查找插件信息 | ||
| 210 | + PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); | ||
| 65 | if (pluginInfo == null) { | 211 | if (pluginInfo == null) { |
| 66 | - // TODO @haohao:建议打个 error log | 212 | + // 4.2 插件信息不存在,记录错误并跳过 |
| 213 | + log.error("插件信息不存在,插件包标识符 = {}", pluginKey); | ||
| 67 | continue; | 214 | continue; |
| 68 | } | 215 | } |
| 69 | 216 | ||
| 70 | - // 2.2 查询插件实例 | ||
| 71 | - PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId()); | ||
| 72 | - // 2.3.1 如果插件实例不存在,则创建 | 217 | + // 4.3 查询插件实例 |
| 218 | + PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId, | ||
| 219 | + pluginInfo.getId()); | ||
| 73 | if (pluginInstance == null) { | 220 | if (pluginInstance == null) { |
| 74 | - // TODO @haohao:可以链式调用;建议新建一个! | ||
| 75 | - pluginInstance = new PluginInstanceDO(); | ||
| 76 | - pluginInstance.setPluginId(pluginInfo.getId()); | ||
| 77 | - pluginInstance.setMainId(MAIN_ID); | ||
| 78 | - pluginInstance.setIp(mainIp); | ||
| 79 | - pluginInstance.setPort(port); | ||
| 80 | - pluginInstance.setHeartbeatAt(System.currentTimeMillis()); | 221 | + // 4.4 如果插件实例不存在,则创建 |
| 222 | + pluginInstance = PluginInstanceDO.builder() | ||
| 223 | + .pluginId(pluginInfo.getId()) | ||
| 224 | + .mainId(MAIN_ID + "-" + mac) | ||
| 225 | + .ip(ip) | ||
| 226 | + .port(port) | ||
| 227 | + .heartbeatAt(System.currentTimeMillis()) | ||
| 228 | + .build(); | ||
| 81 | pluginInstanceMapper.insert(pluginInstance); | 229 | pluginInstanceMapper.insert(pluginInstance); |
| 82 | } else { | 230 | } else { |
| 83 | - // 2.3.2 如果插件实例存在,则更新 | 231 | + // 4.5 如果插件实例存在,则更新心跳时间 |
| 84 | pluginInstance.setHeartbeatAt(System.currentTimeMillis()); | 232 | pluginInstance.setHeartbeatAt(System.currentTimeMillis()); |
| 85 | pluginInstanceMapper.updateById(pluginInstance); | 233 | pluginInstanceMapper.updateById(pluginInstance); |
| 86 | } | 234 | } |
| 87 | } | 235 | } |
| 88 | } | 236 | } |
| 89 | 237 | ||
| 90 | - // TODO @haohao:这个目的是,获取到第一个有效 ip 是哇? | ||
| 91 | - private String getLocalIpAddress() { | ||
| 92 | - try { | ||
| 93 | - List<String> ipList = NetUtil.localIpv4s().stream() | ||
| 94 | - .filter(ip -> !ip.startsWith("0.0") && !ip.startsWith("127.") && !ip.startsWith("169.254") && !ip.startsWith("255.255.255.255")) | ||
| 95 | - .toList(); | ||
| 96 | - return ipList.isEmpty() ? "127.0.0.1" : ipList.get(0); | ||
| 97 | - } catch (Exception e) { | ||
| 98 | - log.error("获取本地IP地址失败", e); | ||
| 99 | - return "127.0.0.1"; // 默认值 | ||
| 100 | - } | ||
| 101 | - } | ||
| 102 | - | ||
| 103 | } | 238 | } |
| 104 | \ No newline at end of file | 239 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java
| @@ -11,6 +11,11 @@ import org.springframework.web.bind.annotation.RestController; | @@ -11,6 +11,11 @@ import org.springframework.web.bind.annotation.RestController; | ||
| 11 | import javax.annotation.Resource; | 11 | import javax.annotation.Resource; |
| 12 | import java.util.concurrent.CompletableFuture; | 12 | import java.util.concurrent.CompletableFuture; |
| 13 | 13 | ||
| 14 | +/** | ||
| 15 | + * 插件实例 RPC 接口 | ||
| 16 | + * | ||
| 17 | + * @author 芋道源码 | ||
| 18 | + */ | ||
| 14 | @RestController | 19 | @RestController |
| 15 | @RequestMapping("/rpc") | 20 | @RequestMapping("/rpc") |
| 16 | @RequiredArgsConstructor | 21 | @RequiredArgsConstructor |
| @@ -29,4 +34,4 @@ public class RpcController { | @@ -29,4 +34,4 @@ public class RpcController { | ||
| 29 | return rpcClient.call("concat", new Object[]{str1, str2}, 10); | 34 | return rpcClient.call("concat", new Object[]{str1, str2}, 10); |
| 30 | } | 35 | } |
| 31 | 36 | ||
| 32 | -} | 37 | -} |
| 38 | +} | ||
| 33 | \ No newline at end of file | 39 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java
| @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.plugin; | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.plugin; | ||
| 3 | import cn.hutool.json.JSONObject; | 3 | import cn.hutool.json.JSONObject; |
| 4 | import cn.hutool.json.JSONUtil; | 4 | import cn.hutool.json.JSONUtil; |
| 5 | import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | 5 | import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; |
| 6 | +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; | ||
| 6 | import io.netty.buffer.Unpooled; | 7 | import io.netty.buffer.Unpooled; |
| 7 | import io.netty.channel.ChannelFutureListener; | 8 | import io.netty.channel.ChannelFutureListener; |
| 8 | import io.netty.channel.ChannelHandlerContext; | 9 | import io.netty.channel.ChannelHandlerContext; |
| @@ -12,7 +13,7 @@ import io.netty.util.CharsetUtil; | @@ -12,7 +13,7 @@ import io.netty.util.CharsetUtil; | ||
| 12 | 13 | ||
| 13 | /** | 14 | /** |
| 14 | * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。 | 15 | * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。 |
| 15 | - * | 16 | + * <p> |
| 16 | * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post | 17 | * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post |
| 17 | * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段 | 18 | * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段 |
| 18 | */ | 19 | */ |
| @@ -76,7 +77,12 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | @@ -76,7 +77,12 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { | ||
| 76 | 77 | ||
| 77 | try { | 78 | try { |
| 78 | // 调用主程序的接口保存数据 | 79 | // 调用主程序的接口保存数据 |
| 79 | - deviceDataApi.saveDeviceData(productKey, deviceName, jsonData.toString()); | 80 | + DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder() |
| 81 | + .productKey(productKey) | ||
| 82 | + .deviceName(deviceName) | ||
| 83 | + .message(jsonData.toString()) | ||
| 84 | + .build(); | ||
| 85 | + deviceDataApi.saveDeviceData(createDTO); | ||
| 80 | 86 | ||
| 81 | // 构造成功响应内容 | 87 | // 构造成功响应内容 |
| 82 | JSONObject successRes = createResponseJson( | 88 | JSONObject successRes = createResponseJson( |