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/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 a5ecb93796..0000000000 --- a/plugins/enabled.txt +++ /dev/null @@ -1,2 +0,0 @@ -http-plugin -http-plugin@0.0.1 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..fa75769049 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-dependencies/pom.xml b/yudao-dependencies/pom.xml index 6eaa89dfe7..fb3cf8562d 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -67,6 +67,7 @@ 3.0.6 1.2.5 0.9.0 + 4.4.0 3.5.0 4.11.0 @@ -613,6 +614,19 @@ ${pf4j-spring.version} + + + io.vertx + vertx-core + ${vertx.version} + + + + io.vertx + vertx-web + ${vertx.version} + + diff --git a/yudao-module-iot/yudao-module-iot-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/ServiceRegistry.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java index 5603ad8d72..a914e8029f 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.api; import java.util.HashMap; import java.util.Map; +// TODO 芋艿:纠结下 /** * 服务注册表 - 插架模块使用,无法使用 Spring 注入 */ diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java index cb747f5053..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,17 +1,20 @@ package cn.iocoder.yudao.module.iot.api.device; +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; +import jakarta.validation.Valid; + /** * 设备数据 API + * + * @author haohao */ public interface DeviceDataApi { /** * 保存设备数据 * - * @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..11f8a6ef88 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, "独立运行"); + JAR(0, "JAR 部署"), + STANDALONE(1, "独立部署"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); @@ -48,4 +48,5 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable { public int[] array() { return ARRAYS; } + } diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java new file mode 100644 index 0000000000..b2a9f03607 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +// TODO @芋艿:要不要加个 mqtt 值了的前缀 +/** + * MQTT RPC 请求 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RpcRequest { + + /** + * 方法名 + */ + private String method; + + /** + * 参数 + */ + // TODO @haohao:object 对象会不会不好序列化? + private Object[] params; + + /** + * 关联 ID + */ + private String correlationId; + + /** + * 回复地址 + */ + private String replyTo; + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java new file mode 100644 index 0000000000..f3225d08e7 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.common; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * MQTT RPC 响应 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RpcResponse { + + /** + * 关联 ID + */ + private String correlationId; + + /** + * 结果 + */ + // TODO @haohao:object 对象会不会不好反序列化? + private Object result; + + /** + * 错误 + */ + private String error; + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java new file mode 100644 index 0000000000..620b007635 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.common; + +import cn.hutool.json.JSONUtil; + +/** + * 序列化工具类 + * + */ +public class SerializationUtils { + + public static String serialize(Object obj) { + return JSONUtil.toJsonStr(obj); + } + + public static T deserialize(String json, Class clazz) { + return JSONUtil.toBean(json, clazz); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index d0ed0bcacd..66710ae910 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -64,6 +64,16 @@ yudao-spring-boot-starter-excel + + + io.vertx + vertx-core + + + + io.vertx + vertx-web + org.eclipse.paho diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/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/device/IotDeviceDataController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java index 2675f60e5e..be451f17a0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java @@ -3,19 +3,15 @@ package cn.iocoder.yudao.module.iot.controller.admin.device; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.*; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.service.device.IotDeviceLogDataService; import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -36,6 +32,9 @@ public class IotDeviceDataController { @Resource private IotDeviceLogDataService iotDeviceLogDataService; + @Resource // TODO @super:service 之间,不用空行;原因是,这样更简洁;空行,主要是为了“间隔”,提升可读性 + private IotDeviceLogDataService deviceLogDataService; + // TODO @浩浩:这里的 /latest-list,包括方法名。 @GetMapping("/latest") @Operation(summary = "获取设备属性最新数据") @@ -52,12 +51,22 @@ public class IotDeviceDataController { return success(BeanUtils.toBean(list, IotTimeDataRespVO.class)); } + // TODO:数据权限 @PostMapping("/simulator") @Operation(summary = "模拟设备") public CommonResult simulatorDevice(@Valid @RequestBody IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { - //TODO:先生成一下日志 后续完善模拟设备代码逻辑 + //TODO:先生成一下设备日志 后续完善模拟设备代码逻辑 + // TODO @super:应该 deviceDataService 里面有个 simulatorDevice,然后里面去 insert 日志! iotDeviceLogDataService.createDeviceLog(simulatorReqVO); return success(true); } + // TODO:数据权限 + @GetMapping("/log/page") + @Operation(summary = "获得设备日志分页") + public CommonResult> getDeviceLogPage(@Valid IotDeviceLogPageReqVO pageReqVO) { + PageResult pageResult = deviceLogDataService.getDeviceLogPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, IotDeviceLogRespVO.class)); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java index e1268b0035..7d9ac6a0d0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java @@ -14,7 +14,7 @@ public class IotDeviceSaveReqVO { private Long id; @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177") - @Size(max = 50, message = "设备编号长度不能超过50个字符") + @Size(max = 50, message = "设备编号长度不能超过 50 个字符") private String deviceKey; @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java index 8e1ed3c236..c4f9d1f55d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceDataSimulatorSaveReqVO.java @@ -4,24 +4,26 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import lombok.Data; - -import java.time.LocalDateTime; - +// TODO super: SaveReqVO => ReqVO @Schema(description = "管理后台 - IoT 模拟设备数据 Request VO") @Data public class IotDeviceDataSimulatorSaveReqVO { - @Schema(description = "消息ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "msg123") + // TODO @super:感觉后端随机更合适? + @Schema(description = "消息 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "msg123") private String id; + // TODO @super:不用传递 productKey,因为 deviceKey 可以推导出来 @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123") @NotEmpty(message = "产品ID不能为空") private String productKey; + // TODO @super:中文写作规范,中英文之间,要有空格。例如说,设备 ID。ps:这里应该是设备标识 @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123") @NotEmpty(message = "设备ID不能为空") private String deviceKey; + // TODO @super:type、subType,是不是不用传递,因为模拟只有属性??? @Schema(description = "消息/日志类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property") @NotEmpty(message = "消息类型不能为空") private String type; @@ -34,7 +36,8 @@ public class IotDeviceDataSimulatorSaveReqVO { @NotEmpty(message = "数据内容不能为空") private String content; + // TODO @芋艿:需要讨论下,reportTime 到底以那个为准! @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime reportTime; + private Long reportTime; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java new file mode 100644 index 0000000000..a882a6d86a --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - IoT 设备日志分页查询 Request VO") +@Data +public class IotDeviceLogPageReqVO extends PageParam { + + @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123") + @NotEmpty(message = "设备标识不能为空") + private String deviceKey; + + // TODO @super:对应的枚举类 + @Schema(description = "消息类型", example = "property") + private String type; + + @Schema(description = "标识符", example = "temperature") + // TODO @super:对应的枚举类 + private String subType; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java new file mode 100644 index 0000000000..48ea9b6989 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IoT 设备日志 Response VO") +@Data +public class IotDeviceLogRespVO { + + @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123") + private String productKey; + + @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123") + private String deviceKey; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property") + private String type; + + @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature") + private String subType; + + @Schema(description = "日志内容", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime reportTime; + + @Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime ts; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java index e71e4c484d..bc8d6c8fae 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java @@ -9,7 +9,7 @@ import org.springframework.web.multipart.MultipartFile; @Data public class PluginInfoImportReqVO { - @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") + @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") private Long id; @Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoPageReqVO.java index 7d4677dd49..76f30ac99f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoPageReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoPageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; import cn.iocoder.yudao.framework.common.pojo.PageParam; +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.Data; @@ -11,7 +13,8 @@ public class PluginInfoPageReqVO extends PageParam { @Schema(description = "插件名称", example = "http") private String name; - @Schema(description = "状态") + @Schema(description = "状态", example = "1") + @InEnum(IotPluginStatusEnum.class) private 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/controller/admin/plugin/vo/PluginInfoRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java index 514ba4f1f1..4291024699 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java @@ -1,7 +1,5 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; -import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -9,63 +7,48 @@ import java.time.LocalDateTime; @Schema(description = "管理后台 - IoT 插件信息 Response VO") @Data -@ExcelIgnoreUnannotated public class PluginInfoRespVO { @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") - @ExcelProperty("主键 ID") private Long id; @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627") - @ExcelProperty("插件包标识符") private String pluginKey; @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") - @ExcelProperty("插件名称") private String name; @Schema(description = "描述", example = "你猜") - @ExcelProperty("描述") private String description; @Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @ExcelProperty("部署方式") private Integer deployType; @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("插件包文件名") private String fileName; @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("插件版本") private String version; @Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @ExcelProperty("插件类型") private Integer type; @Schema(description = "设备插件协议类型") - @ExcelProperty("设备插件协议类型") private String protocol; @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("状态") private Integer status; @Schema(description = "插件配置项描述信息") - @ExcelProperty("插件配置项描述信息") private String configSchema; @Schema(description = "插件配置信息") - @ExcelProperty("插件配置信息") private String config; @Schema(description = "插件脚本") - @ExcelProperty("插件脚本") private String script; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") private LocalDateTime createTime; } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java index 9a98481306..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,12 +1,18 @@ 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 public class PluginInfoSaveReqVO { + // TODO @haohao:新增的字段有点多,每个都需要哇? + + // TODO @haohao:一些枚举字段,需要加枚举校验。例如说,deployType、status、type 等 + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") private Long id; @@ -35,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/controller/admin/thingmodel/IotThingModelController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java index d213329e29..e4913486d3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java @@ -74,6 +74,7 @@ public class IotThingModelController { return success(IotThingModelConvert.INSTANCE.convertList(list)); } + // TODO @puhui @super:getThingModelListByProductId 和 getThingModelListByProductId 可以融合么? @GetMapping("/list") @Operation(summary = "获得产品物模型列表") @PreAuthorize("@ss.hasPermission('iot:thing-model:query')") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelListReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelListReqVO.java index 3652b36b94..dd77cfc5ee 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelListReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelListReqVO.java @@ -6,11 +6,10 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; - - @Schema(description = "管理后台 - IoT 产品物模型List Request VO") @Data public class IotThingModelListReqVO { + @Schema(description = "功能标识") private String identifier; @@ -21,7 +20,8 @@ public class IotThingModelListReqVO { @InEnum(IotThingModelTypeEnum.class) private Integer type; - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "产品ID不能为空") + @Schema(description = "产品 ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "产品 ID 不能为空") private Long productId; + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java index 4a30ed7915..dd811ee323 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java @@ -1,16 +1,15 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device; -import cn.hutool.core.date.DateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - /** * IoT 设备日志数据 DO * + * 目前使用 TDengine 存储 + * * @author alwayssuper */ @Data @@ -18,45 +17,52 @@ import java.time.LocalDateTime; @NoArgsConstructor @AllArgsConstructor public class IotDeviceLogDO { + + // TODO @芋艿:消息 ID 的生成逻辑 /** - * 消息ID + * 消息 ID */ private String id; + // TODO @super:关联要 @下 /** - * 产品ID + * 产品标识 */ private String productKey; + // TODO @super:关联要 @下 /** - * 设备ID + * 设备标识 */ private String deviceKey; + // TODO @super:枚举类 /** - * 消息/日志类型 + * 日志类型 */ private String type; + // TODO @super:枚举类 /** * 标识符:用于标识具体的属性、事件或服务 */ private String subType; /** - * 数据内容:存储具体的消息数据内容,通常是JSON格式 + * 数据内容 + * + * 存储具体的消息数据内容,通常是 JSON 格式 */ private String content; /** * 上报时间戳 */ - private DateTime reportTime; + private Long reportTime; /** * 时序时间 */ - private DateTime ts; - + private Long ts; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java index 507b91f2a0..8b0ec95d14 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java @@ -33,19 +33,22 @@ public class PluginInstanceDO extends BaseDO { */ private String mainId; /** - * 插件id + * 插件 ID *

* 关联 {@link PluginInfoDO#getId()} */ private Long pluginId; + /** - * 插件主程序所在ip + * 插件主程序所在 IP */ private String ip; /** * 插件主程序端口 */ private Integer port; + + // TODO @haohao:字段改成 heartbeatTime,LocalDateTime /** * 心跳时间,心路时间超过 30 秒需要剔除 */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessageDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessageDO.java index b647c68730..ae70da37bb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessageDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessageDO.java @@ -6,7 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; // TODO @芋艿:纠结下字段 -@Deprecated +@Deprecated // TODO @super:看看啥时候删除下哈。 /** * TD 物模型消息日志的数据库 */ @@ -25,7 +25,7 @@ public class ThingModelMessageDO { /** * 系统扩展参数 - * + * * 例如:设备状态、系统时间、固件版本等系统级信息 */ private Object system; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java index 228519fb65..9806ef17f3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java @@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; + +import java.util.List; + import org.apache.ibatis.annotations.Mapper; import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.*; @@ -22,4 +25,10 @@ public interface PluginInfoMapper extends BaseMapperX { .orderByDesc(PluginInfoDO::getId)); } + default List selectListByStatus(Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(PluginInfoDO::getStatus, status) + .orderByAsc(PluginInfoDO::getId)); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java index 249082032d..4f773aa064 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java @@ -21,6 +21,7 @@ public interface PluginInstanceMapper extends BaseMapperX { .eq(PluginInstanceDO::getPluginId, pluginId)); } + // TODO @haohao:这个还需要么?相关不用的 VO 可以删除 default PageResult selectPage(PluginInstancePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId()) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java index 194218b7f8..9584262dff 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java @@ -1,15 +1,18 @@ package cn.iocoder.yudao.module.iot.dal.tdengine; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.util.List; + /** - * IoT 设备日志 Mapper + * IOT 设备日志数据 Mapper 接口 * - * @author alwayssuper + * 基于 TDengine 实现设备日志的存储 */ @Mapper @TDengineDS @@ -18,22 +21,58 @@ public interface IotDeviceLogDataMapper { /** * 创建设备日志超级表 +<<<<<<< HEAD * 初始化只创建一次 */ void createDeviceLogSTable(); +======= + * + * 注意:初始化时只需创建一次 + */ + void createDeviceLogSTable(); + + // TODO @super:是不是删除哈 +>>>>>>> deab8c1cc6bb7864d9c40e0c369f649f6f9bfa41 /** * 创建设备日志子表 * * @param deviceKey 设备标识 */ +<<<<<<< HEAD void createDeviceLogTable( @Param("deviceKey") String deviceKey); /** * 插入设备日志数据 * +======= + void createDeviceLogTable(@Param("deviceKey") String deviceKey); + + // TODO @super:单个参数,不用加 @Param + /** + * 插入设备日志数据 + * + * 如果子表不存在,会自动创建子表 + * +>>>>>>> deab8c1cc6bb7864d9c40e0c369f649f6f9bfa41 * @param log 设备日志数据 */ void insert(@Param("log") IotDeviceLogDO log); + /** + * 获得设备日志分页 + * + * @param reqVO 分页查询条件 + * @return 设备日志列表 + */ + List selectPage(@Param("reqVO") IotDeviceLogPageReqVO reqVO); + + /** + * 获得设备日志总数 + * + * @param reqVO 查询条件 + * @return 日志总数 + */ + Long selectCount(@Param("reqVO") IotDeviceLogPageReqVO reqVO); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java index b04be11991..cbc6e88366 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java @@ -1,10 +1,6 @@ package cn.iocoder.yudao.module.iot.dal.tdengine; -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessageDO; import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS; -import com.baomidou.dynamic.datasource.annotation.DS; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -13,7 +9,7 @@ import org.apache.ibatis.annotations.Param; * 处理 TD 中物模型消息日志的操作 */ @Mapper -@Deprecated +@Deprecated // TODO super:什么时候,删除下哈。 @TDengineDS @InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错 public interface TdThingModelMessageMapper { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java index b466113f70..fbc6ffcdb3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java @@ -17,7 +17,7 @@ import org.springframework.stereotype.Component; * @author ahh */ @Slf4j -@Component +//@Component public class EmqxCallback implements MqttCallbackExtended { @Lazy diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java index de24585b09..577a808dc0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java @@ -19,7 +19,7 @@ import org.springframework.stereotype.Component; */ @Slf4j @Data -@Component +//@Component public class EmqxClient { @Resource diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java index 9d128903c4..8e32c185d4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java @@ -12,8 +12,8 @@ import org.springframework.stereotype.Component; * @author ahh */ @Data -@Component -@ConfigurationProperties("iot.emq") +//@Component +//@ConfigurationProperties("iot.emq") public class MqttConfig { /** diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java index 46217a22bb..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/emq/start/EmqxStart.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java index 0c316b66c9..64265fdc3c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; * * @author ahh */ -@Component +//@Component public class EmqxStart implements ApplicationRunner { @Resource diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/PluginStart.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/PluginStart.java new file mode 100644 index 0000000000..2cb688cfa5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/PluginStart.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.iot.framework.plugin; + +import java.util.List; + +import javax.annotation.Resource; + +import org.pf4j.spring.SpringPluginManager; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService; +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; + +@Component +@Slf4j +public class PluginStart implements ApplicationRunner { + + @Resource + private PluginInfoService pluginInfoService; + + @Resource + private SpringPluginManager pluginManager; + + @Override + public void run(ApplicationArguments args) { + TenantUtils.executeIgnore(() -> { // 1. 忽略租户上下文执行 + List pluginInfoList = pluginInfoService + .getPluginInfoListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); // 2. 获取运行中的插件列表 + if (CollUtil.isEmpty(pluginInfoList)) { // 3. 检查插件列表是否为空 + log.info("[run] 没有需要启动的插件"); // 4. 日志记录没有插件需要启动 + return; + } + pluginInfoList.forEach(pluginInfo -> { // 5. 使用lambda表达式遍历插件列表 + try { + log.info("[run][启动插件] pluginKey = {}", pluginInfo.getPluginKey()); // 6. 日志记录插件启动信息 + pluginManager.startPlugin(pluginInfo.getPluginKey()); // 7. 启动插件 + } catch (Exception e) { + log.error("[run][启动插件失败] pluginKey = {}", pluginInfo.getPluginKey(), e); // 8. 记录启动失败的日志 + } + }); + }); + + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java index 4f1d5e3e29..374e3856a1 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java @@ -31,7 +31,13 @@ public class UnifiedConfiguration { @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER) public SpringPluginManager pluginManager() { log.info("[init][实例化 SpringPluginManager]"); - SpringPluginManager springPluginManager = new SpringPluginManager(); + SpringPluginManager springPluginManager = new SpringPluginManager() { + @Override + public void startPlugins() { + // 禁用插件启动,避免插件启动时,启动所有插件 + log.info("[init][禁用默认启动所有插件]"); + } + }; springPluginManager.addPluginStateListener(new CustomPluginStateListener()); return springPluginManager; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitConfiguration.java index e14fa2948c..e0057e1808 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/config/TDengineTableInitConfiguration.java @@ -16,7 +16,7 @@ import org.springframework.core.annotation.Order; @Slf4j @RequiredArgsConstructor @Configuration -@Order(Integer.MAX_VALUE) // 保证在最后执行 +@Order public class TDengineTableInitConfiguration implements ApplicationRunner { private final IotDeviceLogDataService deviceLogService; @@ -26,15 +26,18 @@ public class TDengineTableInitConfiguration implements ApplicationRunner { try { // 初始化设备日志表 deviceLogService.initTDengineSTable(); - log.info("初始化 设备日志表 TDengine 表结构成功"); + // TODO @super:这个日志,是不是不用打,不然重复啦!!! + log.info("[run]初始化 设备日志表 TDengine 表结构成功"); } catch (Exception ex) { + // TODO @super:初始化失败,打印 error 日志,退出系统。。不然跑起来,就初始啦!!! if (ex.getMessage().contains("Table already exists")) { log.info("TDengine 设备日志超级表已存在,跳过创建"); return; - }else{ + } else{ log.error("初始化 设备日志表 TDengine 表结构失败", ex); } throw ex; } } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/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 ca8398e51c..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 @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.iot.job.plugin; - import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService; import org.springframework.scheduling.annotation.Scheduled; @@ -23,7 +22,8 @@ public class PluginInstancesJob { @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) public void updatePluginInstances() { TenantUtils.executeIgnore(() -> { - pluginInstanceService.updatePluginInstances(); + pluginInstanceService.reportPluginInstances(); }); } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java new file mode 100644 index 0000000000..c7a0500030 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "mqtt") +public class MqttConfig { + /** + * MQTT 代理地址 + */ + private String broker; + + /** + * MQTT 用户名 + */ + private String username; + + /** + * MQTT 密码 + */ + private String password; + + /** + * MQTT 客户端 ID + */ + private String clientId; + + /** + * MQTT 请求主题 + */ + private String requestTopic; + + /** + * MQTT 响应主题前缀 + */ + private String responseTopicPrefix; +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java new file mode 100644 index 0000000000..90ce2a3875 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java @@ -0,0 +1,100 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.server; + +import cn.hutool.core.lang.UUID; +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcRequest; +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcResponse; +import cn.iocoder.yudao.module.iot.mqttrpc.common.SerializationUtils; +import cn.iocoder.yudao.module.iot.mqttrpc.config.MqttConfig; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.HashMap; +import java.util.Map; + +// TODO @芋艿:server 逻辑,再瞅瞅; +// TODO @haohao:如果只写在 iot biz 里,那么后续 server => client 貌似不方便?微信再讨论下~; +@Service +@Slf4j +public class RpcServer { + + private final MqttConfig mqttConfig; + private final MqttClient mqttClient; + private final Map methodRegistry = new HashMap<>(); + + public RpcServer(MqttConfig mqttConfig) throws MqttException { + this.mqttConfig = mqttConfig; + this.mqttClient = new MqttClient(mqttConfig.getBroker(), "rpc-server-" + UUID.randomUUID(), new MemoryPersistence()); + MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setCleanSession(true); + options.setUserName(mqttConfig.getUsername()); + options.setPassword(mqttConfig.getPassword().toCharArray()); + this.mqttClient.connect(options); + } + + @PostConstruct + public void init() throws MqttException { + mqttClient.subscribe(mqttConfig.getRequestTopic(), this::handleRequest); + log.info("RPC Server subscribed to topic: {}", mqttConfig.getRequestTopic()); + } + + private void handleRequest(String topic, MqttMessage message) { + RpcRequest request = SerializationUtils.deserialize(new String(message.getPayload()), RpcRequest.class); + RpcResponse response = new RpcResponse(); + response.setCorrelationId(request.getCorrelationId()); + + try { + MethodInvoker invoker = methodRegistry.get(request.getMethod()); + if (invoker == null) { + throw new NoSuchMethodException("Unknown method: " + request.getMethod()); + } + Object result = invoker.invoke(request.getParams()); + response.setResult(result); + } catch (Exception e) { + response.setError(e.getMessage()); + log.error("Error processing RPC request: {}", e.getMessage(), e); + } + + String replyPayload = SerializationUtils.serialize(response); + MqttMessage replyMessage = new MqttMessage(replyPayload.getBytes()); + replyMessage.setQos(1); + try { + mqttClient.publish(request.getReplyTo(), replyMessage); + log.info("Published response to {}", request.getReplyTo()); + } catch (MqttException e) { + log.error("Failed to publish response: {}", e.getMessage(), e); + } + } + + /** + * 注册可调用的方法 + * + * @param methodName 方法名称 + * @param invoker 方法调用器 + */ + public void registerMethod(String methodName, MethodInvoker invoker) { + methodRegistry.put(methodName, invoker); + log.info("Registered method: {}", methodName); + } + + @PreDestroy + public void cleanup() throws MqttException { + mqttClient.disconnect(); + log.info("RPC Server disconnected"); + } + + /** + * 方法调用器接口 + */ + @FunctionalInterface + public interface MethodInvoker { + + Object invoke(Object[] params) throws Exception; + + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java index 166727dec7..1a3b48503c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java @@ -1,6 +1,9 @@ package cn.iocoder.yudao.module.iot.service.device; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; /** * IoT 设备日志数据 Service 接口 @@ -10,13 +13,27 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDevi public interface IotDeviceLogDataService { /** - * 初始化 TDengine 表 + * 初始化 TDengine 超级表 + * + *系统启动时,会自动初始化一次 */ void initTDengineSTable(); /** - * 模拟设备创建设备日志 - * @param simulatorReqVO 模拟设备信息 + * 插入设备日志 + * + * 当该设备第一次插入日志时,自动创建该设备的设备日志子表 + * + * @param simulatorReqVO 设备日志模拟数据 */ void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO); + + /** + * 获得设备日志分页 + * + * @param pageReqVO 分页查询 + * @return 设备日志分页 + */ + PageResult getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java index 528477cf90..d0659e5073 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java @@ -1,8 +1,9 @@ package cn.iocoder.yudao.module.iot.service.device; -import cn.hutool.core.date.DateTime; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceLogDataMapper; import jakarta.annotation.Resource; @@ -10,7 +11,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.time.LocalDateTime; +import java.util.List; /** * IoT 设备日志数据 Service 实现了 @@ -19,27 +20,43 @@ import java.time.LocalDateTime; */ @Service @Slf4j +@Validated public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{ @Resource private IotDeviceLogDataMapper iotDeviceLogDataMapper; - + // TODO @super:方法名。defineDeviceLog。。未来,有可能别人使用别的记录日志,例如说 es 之类的。 @Override public void initTDengineSTable() { - try { - // 创建设备日志超级表 - iotDeviceLogDataMapper.createDeviceLogSTable(); - log.info("创建设备日志超级表成功"); - } catch (Exception ex) { - throw ex; - } + // TODO @super:改成不存在才创建。 + iotDeviceLogDataMapper.createDeviceLogSTable(); } @Override public void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { + // 1. 转换请求对象为 DO IotDeviceLogDO iotDeviceLogDO = BeanUtils.toBean(simulatorReqVO, IotDeviceLogDO.class); - iotDeviceLogDO.setTs(DateTime.now()); + + // 2. 处理时间字段 + // TODO @super:一次性的字段,不用单独给个变量 + long currentTime = System.currentTimeMillis(); + // 2.1 设置时序时间为当前时间 + iotDeviceLogDO.setTs(currentTime); // TODO @super:TS在SQL中直接NOW 咱们的TS数据获取是走哪一种;走 now() + + // 3. 插入数据 + // TODO @super:不要直接调用对方的 IotDeviceLogDataMapper,通过 service 哈! iotDeviceLogDataMapper.insert(iotDeviceLogDO); } + + // TODO @super:在 iotDeviceLogDataService 写 + @Override + public PageResult getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO) { + // 查询数据 + List list = iotDeviceLogDataMapper.selectPage(pageReqVO); + Long total = iotDeviceLogDataMapper.selectCount(pageReqVO); + // 构造分页结果 + return new PageResult<>(list, total); + } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/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 9d2f43ba7b..0f9523414e 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,10 +15,9 @@ 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.dal.tdengine.TdThingModelMessageMapper; import cn.iocoder.yudao.module.iot.enums.IotConstants; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; @@ -57,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 芋艿:怎么映射!!!! @@ -110,7 +110,6 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe return; } newFields.add(0, new TDengineTableField(TDengineTableField.FIELD_TS, TDengineTableField.TYPE_TIMESTAMP)); - // 2.1.1 创建产品超级表 devicePropertyDataMapper.createProductPropertySTable(product.getProductKey(), newFields); return; } @@ -131,20 +130,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/ExampleService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/ExampleService.java new file mode 100644 index 0000000000..22ebe8b4f2 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/ExampleService.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.iot.service.plugin; + +import cn.iocoder.yudao.module.iot.mqttrpc.server.RpcServer; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; + +@Service +@RequiredArgsConstructor +public class ExampleService { + + private final RpcServer rpcServer; + + @PostConstruct + public void registerMethods() { + rpcServer.registerMethod("add", params -> { + if (params.length != 2) { + throw new IllegalArgumentException("add方法需要两个参数"); + } + int a = ((Number) params[0]).intValue(); + int b = ((Number) params[1]).intValue(); + return add(a, b); + }); + + rpcServer.registerMethod("concat", params -> { + if (params.length != 2) { + throw new IllegalArgumentException("concat方法需要两个参数"); + } + String str1 = params[0].toString(); + String str2 = params[1].toString(); + return concat(str1, str2); + }); + } + + private int add(int a, int b) { + return a + b; + } + + private String concat(String a, String b) { + return a + b; + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java index 56b7e95e1f..3a1529674f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; import jakarta.validation.Valid; import org.springframework.web.multipart.MultipartFile; @@ -66,7 +67,7 @@ public interface PluginInfoService { * 更新插件的状态 * * @param id 插件id - * @param status 状态 + * @param status 状态 {@link IotPluginStatusEnum} */ void updatePluginStatus(Long id, Integer status); @@ -76,4 +77,12 @@ public interface PluginInfoService { * @return 插件信息列表 */ List getPluginInfoList(); -} \ No newline at end of file + + /** + * 根据状态获得插件信息列表 + * + * @param status 状态 {@link IotPluginStatusEnum} + * @return 插件信息列表 + */ + List getPluginInfoListByStatus(Integer status); +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java index 4e7f2e9961..37b6328450 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 实现类 @@ -41,18 +32,17 @@ public class PluginInfoServiceImpl implements PluginInfoService { @Resource private PluginInfoMapper pluginInfoMapper; + + @Resource + private PluginInstanceService pluginInstanceService; + @Resource private SpringPluginManager pluginManager; - @Value("${pf4j.pluginsDir}") - private String pluginsDir; - @Override public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) { - // 插入 PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class); pluginInfoMapper.insert(pluginInfo); - // 返回 return pluginInfo.getId(); } @@ -67,41 +57,21 @@ public class PluginInfoServiceImpl implements PluginInfoService { @Override public void deletePluginInfo(Long id) { - // 校验存在 + // 1.1 校验存在 PluginInfoDO pluginInfoDO = validatePluginInfoExists(id); - - // 停止插件 + // 1.2 停止插件 if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) { throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING); } - // 卸载插件 - PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey()); - if (plugin != null) { - // 查询插件是否是启动状态 - if (plugin.getPluginState().equals(PluginState.STARTED)) { - // 停止插件 - pluginManager.stopPlugin(plugin.getPluginId()); - } - // 卸载插件 - pluginManager.unloadPlugin(plugin.getPluginId()); - } + // 2. 卸载插件 + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDO.getPluginKey()); - // 删除 + // 3. 删除插件文件 + pluginInstanceService.deletePluginFile(pluginInfoDO); + + // 4. 删除插件信息 pluginInfoMapper.deleteById(id); - // 删除插件文件 - 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,99 +97,37 @@ public class PluginInfoServiceImpl implements PluginInfoService { // 1. 校验插件信息是否存在 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); - // 2. 获取插件标识 - String pluginKey = pluginInfoDo.getPluginKey(); + // 2. 停止并卸载旧的插件 + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); - // 3. 停止并卸载旧的插件 - stopAndUnloadPlugin(pluginKey); + // 3 上传新的插件文件,更新插件启用状态文件 + String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); - // 4. 上传新的插件文件 - String pluginKeyNew = uploadAndLoadNewPlugin(file); - - // 5. 更新插件启用状态文件 - updatePluginStatusFile(pluginKeyNew, false); - - // 6. 更新插件信息 + // 4. 更新插件信息 updatePluginInfo(pluginInfoDo, pluginKeyNew, file); } - // 停止并卸载旧的插件 - 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); // 卸载插件 - } - } - - // 上传并加载新的插件文件 - private String uploadAndLoadNewPlugin(MultipartFile file) { - Path pluginsPath = Paths.get(pluginsDir); - try { - 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()); // 加载插件 - } else { - throw exception(PLUGIN_INSTALL_FAILED); - } - } catch (Exception e) { - throw exception(PLUGIN_INSTALL_FAILED); - } - } - - // 更新插件状态文件 - private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) { - 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); - } - String pluginInfo = pluginKeyNew + "@" + pluginWrapper.getDescriptor().getVersion(); - List targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) - : new ArrayList<>(); - List oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) - : new ArrayList<>(); - - if (!targetLines.contains(pluginInfo)) { - targetLines.add(pluginInfo); - Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING); - } - - if (oppositeLines.contains(pluginInfo)) { - oppositeLines.remove(pluginInfo); - Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING); - } - } catch (IOException e) { - throw exception(PLUGIN_INSTALL_FAILED); - } - } - - // 更新插件信息 + /** + * 更新插件信息 + * + * @param pluginInfoDo 插件信息 + * @param pluginKeyNew 插件标识符 + * @param file 文件 + */ private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) { - pluginInfoDo.setPluginKey(pluginKeyNew); - pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus()); - pluginInfoDo.setFileName(file.getOriginalFilename()); - pluginInfoDo.setScript(""); + // 创建新的插件信息对象并链式设置属性 + 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()); - PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor(); - pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription()); - pluginInfoDo.setVersion(pluginDescriptor.getVersion()); - pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription()); - pluginInfoMapper.updateById(pluginInfoDo); + // 执行更新 + pluginInfoMapper.updateById(updatedPluginInfo); } @Override @@ -227,44 +135,24 @@ public class PluginInfoServiceImpl implements PluginInfoService { // 1. 校验插件信息是否存在 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); - // 2. 校验插件状态是否有效 - 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. 更新数据库中的插件状态 - pluginInfoDo.setStatus(status); - pluginInfoMapper.updateById(pluginInfoDo); + // 3. 更新数据库中的插件状态 + PluginInfoDO updatedPluginInfo = new PluginInfoDO(); + updatedPluginInfo.setId(id); + updatedPluginInfo.setStatus(status); + pluginInfoMapper.updateById(updatedPluginInfo); } @Override public List getPluginInfoList() { - return pluginInfoMapper.selectList(null); + return pluginInfoMapper.selectList(); + } + + @Override + 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..4df9b10319 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,38 @@ 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 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 52d79207b8..65a6cf32ba 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,34 @@ 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.nio.file.*; 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,76 +40,149 @@ import java.util.List; @Slf4j public class PluginInstanceServiceImpl implements PluginInstanceService { - /** - * 主程序id - */ + // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 + // 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件; + // 那就 mac@uuid ? 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; + @Override + public void stopAndUnloadPlugin(String pluginKey) { + PluginWrapper plugin = pluginManager.getPlugin(pluginKey); + // TODO @haohao:改成 if return 会更简洁一点; + if (plugin != null) { + if (plugin.getPluginState().equals(PluginState.STARTED)) { + pluginManager.stopPlugin(pluginKey); // 停止插件 + log.info("已停止插件: {}", pluginKey); + } + pluginManager.unloadPlugin(pluginKey); // 卸载插件 + log.info("已卸载插件: {}", pluginKey); + } else { + log.warn("插件不存在或已卸载: {}", pluginKey); + } + } @Override - public void updatePluginInstances() { - // 1. 查询 pf4j 插件列表 + public void deletePluginFile(PluginInfoDO pluginInfoDO) { + File file = new File(pluginsDir, pluginInfoDO.getFileName()); + // TODO @haohao:改成 if return 会更简洁一点; + if (file.exists()) { + try { + TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 + if (!file.delete()) { + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); + } + } catch (InterruptedException e) { + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); + } + } + } + + @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 updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status) { + String pluginKey = pluginInfoDo.getPluginKey(); + PluginWrapper plugin = pluginManager.getPlugin(pluginKey); + + // TODO @haohao:改成 if return 会更简洁一点; + if (plugin != null) { + // 启动插件 + if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) + && plugin.getPluginState() != PluginState.STARTED) { + pluginManager.startPlugin(pluginKey); + log.info("已启动插件: {}", pluginKey); + } + // 停止插件 + else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) + && plugin.getPluginState() == PluginState.STARTED) { + pluginManager.stopPlugin(pluginKey); + log.info("已停止插件: {}", pluginKey); + } + } else { + // 插件不存在且状态为停止,抛出异常 + if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { + throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID); + } + } + } + + @Override + public void reportPluginInstances() { + // 1.1 获取 pf4j 插件列表 List plugins = pluginManager.getPlugins(); - // 2. 查询插件信息列表 - List pluginInfos = pluginInfoService.getPluginInfoList(); + // 1.2 获取插件信息列表并转换为 Map 以便快速查找 + List pluginInfos = pluginInfoMapper.selectList(); + Map pluginInfoMap = pluginInfos.stream() + .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); - // 动态获取主程序的 IP 和端口 - String mainIp = getLocalIpAddress(); + // 1.3 获取本机 IP 和 MAC 地址 + String ip = NetUtil.getLocalhostStr(); + String mac = NetUtil.getLocalMacAddress(); + String mainId = MAIN_ID + "-" + mac; - // 3. 遍历插件列表,并保存为插件实例 + // 2. 遍历插件列表,并保存为插件实例 for (PluginWrapper plugin : plugins) { String pluginKey = plugin.getPluginId(); - PluginInfoDO pluginInfo = pluginInfos.stream() - .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey)) - .findFirst() - .orElse(null); - // 4. 如果插件信息不存在,则跳过 + // 2.1 查找插件信息 + PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); if (pluginInfo == null) { + log.error("插件信息不存在,pluginKey = {}", pluginKey); continue; } - // 5. 查询插件实例 - PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId()); - - // 6. 如果插件实例不存在,则创建 + // 2.2 情况一:如果插件实例不存在,则创建 + PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId, + pluginInfo.getId()); if (pluginInstance == null) { - 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 { - // 7. 如果插件实例存在,则更新 + // 2.2 情况二:如果存在,则更新 heartbeatAt + // TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有) pluginInstance.setHeartbeatAt(System.currentTimeMillis()); pluginInstanceMapper.updateById(pluginInstance); } } } - 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-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java index 41537664b0..ad3ff94e2b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper; import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; -import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService; import com.baomidou.dynamic.datasource.annotation.DSTransactional; import jakarta.annotation.Resource; import org.springframework.context.annotation.Lazy; @@ -116,14 +115,15 @@ public class IotProductServiceImpl implements IotProductService { public void updateProductStatus(Long id, Integer status) { // 1. 校验存在 validateProductExists(id); - // 2. 更新 - IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build(); - // 3. 产品是发布状态 - if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) { - // 3.1 创建产品超级表数据模型 - devicePropertyDataService.defineDevicePropertyData(id); + // 2. 产品是发布状态 + if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) { + // 创建产品超级表数据模型 + devicePropertyDataService.defineDevicePropertyData(id); } + + // 3. 更新 + IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build(); productMapper.updateById(updateObj); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java index 52e90f30f7..e094b34cb6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java @@ -7,20 +7,20 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.*; +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser; +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper; import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper; -import cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper; import cn.iocoder.yudao.module.iot.enums.IotConstants; import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; -import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; import cn.iocoder.yudao.module.iot.service.product.IotProductService; -import cn.iocoder.yudao.module.iot.util.IotTdDatabaseUtils; +import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -61,13 +61,9 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ @Resource private TdEngineDMLMapper tdEngineDMLMapper; - @Resource - private TdThingModelMessageMapper tdThingModelMessageMapper; - @Resource private DeviceDataRedisDAO deviceDataRedisDAO; - - + // TODO @haohao:这个方法,可以考虑加下 1. 2. 3. 更有层次感 @Override @TenantIgnore diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml index db18b3022d..09e5dd4681 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml @@ -6,20 +6,20 @@ - CREATE STABLE device_log( - ts TIMESTAMP, - id NCHAR(50), - product_key NCHAR(50), - type NCHAR(50), - subType NCHAR(50), - content NCHAR(1024), - report_time TIMESTAMP - )TAGS ( - device_key NCHAR(50) - ) + CREATE STABLE device_log ( + ts TIMESTAMP, + id NCHAR(50), + product_key NCHAR(50), + type NCHAR(50), + + subType NCHAR(50), + content NCHAR(1024), + report_time TIMESTAMP + ) TAGS ( + device_key NCHAR(50) + ) - CREATE TABLE device_log_${deviceKey} USING device_log TAGS('${deviceKey}') @@ -41,4 +41,38 @@ ) + + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/pom.xml index c8f0ff0fe8..4a46b61672 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/pom.xml +++ b/yudao-module-iot/yudao-module-iot-plugin/pom.xml @@ -2,11 +2,6 @@ - - - - - yudao-module-iot cn.iocoder.boot @@ -15,6 +10,7 @@ yudao-module-iot-demo-plugin yudao-module-iot-http-plugin + yudao-module-iot-mqtt-plugin 4.0.0 diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml new file mode 100644 index 0000000000..f4ec60d961 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml @@ -0,0 +1,81 @@ + + + + yudao-module-iot-plugin + cn.iocoder.boot + 2.2.0-snapshot + + 4.0.0 + yudao-module-iot-http-plugin + ${project.artifactId} + 2.2.0-snapshot + 物联网 插件模块 - http 插件 + + + + maven-jar-plugin + 2.4 + + + + ${plugin.id} + ${plugin.class} + ${plugin.version} + ${plugin.provider} + ${plugin.description} + ${plugin.dependencies} + + + + + + maven-deploy-plugin + + true + + + + maven-shade-plugin + 3.4.1 + + + package + + shade + + + true + shaded + + + cn.iocoder.yudao.module.iot.HttpPluginSpringbootApplication + + + + + + + + + + + org.pf4j + pf4j-spring + 0.9.0 + provided + + + org.projectlombok + lombok + 1.18.34 + provided + + + + cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin + 0.0.1 + http-plugin + http-plugin-0.0.1 + ahh + + diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties index 4e1199acfc..44f221cb15 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties @@ -1,5 +1,5 @@ plugin.id=http-plugin -plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpPlugin +plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin plugin.version=0.0.1 plugin.provider=ahh plugin.dependencies= diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml index 1ecf140a47..29c0200f1c 100644 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml @@ -21,7 +21,7 @@ http-plugin - cn.iocoder.yudao.module.iot.plugin.HttpPlugin + cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin 0.0.1 ahh http-plugin-0.0.1 @@ -30,27 +30,6 @@ - - org.apache.maven.plugins maven-antrun-plugin @@ -118,6 +97,29 @@ true + + + + + + + + + + + + + + + + + + + + + + + @@ -145,10 +147,20 @@ ${lombok.version} provided + - io.netty - netty-all - 4.1.63.Final + io.vertx + vertx-core + + + + io.vertx + vertx-web + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/HttpPluginSpringbootApplication.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/HttpPluginSpringbootApplication.java new file mode 100644 index 0000000000..6b553f92bf --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/HttpPluginSpringbootApplication.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.iot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class HttpPluginSpringbootApplication { + public static void main(String[] args) { + SpringApplication.run(HttpPluginSpringbootApplication.class, args); + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java new file mode 100644 index 0000000000..4615dcf96f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java @@ -0,0 +1,38 @@ + +package cn.iocoder.yudao.module.iot.controller; + +import cn.iocoder.yudao.module.iot.mqttrpc.client.RpcClient; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.concurrent.CompletableFuture; + +// TODO 芋艿:后续 review 下 +/** + * 插件实例 RPC 接口 + * + * @author 芋道源码 + */ +@RestController +@RequestMapping("/rpc") +@RequiredArgsConstructor +public class RpcController { + + @Resource + private RpcClient rpcClient; + + @PostMapping("/add") + public CompletableFuture add(@RequestParam int a, @RequestParam int b) throws Exception { + return rpcClient.call("add", new Object[]{a, b}, 10); + } + + @PostMapping("/concat") + public CompletableFuture concat(@RequestParam String str1, @RequestParam String str2) throws Exception { + return rpcClient.call("concat", new Object[]{str1, str2}, 10); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java new file mode 100644 index 0000000000..b73f88c537 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.client; + +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcRequest; +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcResponse; +import cn.iocoder.yudao.module.iot.mqttrpc.common.SerializationUtils; +import cn.iocoder.yudao.module.iot.mqttrpc.config.MqttConfig; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.UUID; +import java.util.concurrent.*; + +// TODO @芋艿:需要考虑,怎么公用! +@Service +@Slf4j +public class RpcClient { + + private final MqttConfig mqttConfig; + private final MqttClient mqttClient; + private final ConcurrentMap> pendingRequests = new ConcurrentHashMap<>(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + public RpcClient(MqttConfig mqttConfig) throws MqttException { + this.mqttConfig = mqttConfig; + this.mqttClient = new MqttClient(mqttConfig.getBroker(), mqttConfig.getClientId(), new MemoryPersistence()); + MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setCleanSession(true); + options.setUserName(mqttConfig.getUsername()); + options.setPassword(mqttConfig.getPassword().toCharArray()); + this.mqttClient.connect(options); + } + + @PostConstruct + public void init() throws MqttException { + mqttClient.subscribe(mqttConfig.getResponseTopicPrefix() + "#", this::handleResponse); + log.info("RPC Client subscribed to topics: {}", mqttConfig.getResponseTopicPrefix() + "#"); + } + + private void handleResponse(String topic, MqttMessage message) { + String correlationId = topic.substring(mqttConfig.getResponseTopicPrefix().length()); + RpcResponse response = SerializationUtils.deserialize(new String(message.getPayload()), RpcResponse.class); + CompletableFuture future = pendingRequests.remove(correlationId); + if (future != null) { + if (response.getError() != null) { + future.completeExceptionally(new RuntimeException(response.getError())); + } else { + future.complete(response); + } + } else { + log.warn("Received response for unknown correlationId: {}", correlationId); + } + } + + public CompletableFuture call(String method, Object[] params, int timeoutSeconds) throws MqttException { + String correlationId = UUID.randomUUID().toString(); + String replyTo = mqttConfig.getResponseTopicPrefix() + correlationId; + + RpcRequest request = new RpcRequest(method, params, correlationId, replyTo); + String payload = SerializationUtils.serialize(request); + MqttMessage message = new MqttMessage(payload.getBytes()); + message.setQos(1); + mqttClient.publish(mqttConfig.getRequestTopic(), message); + + CompletableFuture futureResponse = new CompletableFuture<>(); + pendingRequests.put(correlationId, futureResponse); + + // 设置超时 + scheduler.schedule(() -> { + CompletableFuture removed = pendingRequests.remove(correlationId); + if (removed != null) { + removed.completeExceptionally(new TimeoutException("RPC call timed out")); + } + }, timeoutSeconds, TimeUnit.SECONDS); + + // 返回最终的结果 + return futureResponse.thenApply(RpcResponse::getResult); + } + + @PreDestroy + public void cleanup() throws MqttException { + mqttClient.disconnect(); + scheduler.shutdown(); + log.info("RPC Client disconnected"); + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java new file mode 100644 index 0000000000..89569b0c3d --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.iot.mqttrpc.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "mqtt") +public class MqttConfig { + + /** + * MQTT 代理地址 + */ + private String broker; + + /** + * MQTT 用户名 + */ + private String username; + + /** + * MQTT 密码 + */ + private String password; + + /** + * MQTT 客户端 ID + */ + private String clientId; + + /** + * MQTT 请求主题 + */ + private String requestTopic; + + /** + * MQTT 响应主题前缀 + */ + private String responseTopicPrefix; +} diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java deleted file mode 100644 index 6d0908683b..0000000000 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java +++ /dev/null @@ -1,147 +0,0 @@ -package cn.iocoder.yudao.module.iot.plugin; - -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.codec.http.*; -import io.netty.util.CharsetUtil; - -/** - * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。 - * - * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post - * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段 - */ -public class HttpHandler extends SimpleChannelInboundHandler { - - private final DeviceDataApi deviceDataApi; - - public HttpHandler(DeviceDataApi deviceDataApi) { - this.deviceDataApi = deviceDataApi; - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { - // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post - // 使用 "/" 拆分路径 - String uri = request.uri(); - String[] parts = uri.split("/"); - - /* - 拆分结果示例: - parts[0] = "" - parts[1] = "sys" - parts[2] = productKey - parts[3] = deviceName - parts[4] = "thing" - parts[5] = "event" - parts[6] = "property" - parts[7] = "post" - */ - boolean isCorrectPath = parts.length == 8 - && "sys".equals(parts[1]) - && "thing".equals(parts[4]) - && "event".equals(parts[5]) - && "property".equals(parts[6]) - && "post".equals(parts[7]); - if (!isCorrectPath) { - writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found"); - return; - } - String productKey = parts[2]; - String deviceName = parts[3]; - - // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象 - String requestBody = request.content().toString(CharsetUtil.UTF_8); - JSONObject jsonData; - try { - jsonData = JSONUtil.parseObj(requestBody); - } catch (Exception e) { - JSONObject res = createResponseJson( - 400, - new JSONObject(), - null, - "请求数据不是合法的 JSON 格式: " + e.getMessage(), - "thing.event.property.post", - "1.0" - ); - writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString()); - return; - } - String id = jsonData.getStr("id", null); - - try { - // 调用主程序的接口保存数据 - deviceDataApi.saveDeviceData(productKey, deviceName, jsonData.toString()); - - // 构造成功响应内容 - JSONObject successRes = createResponseJson( - 200, - new JSONObject(), - id, - "success", - "thing.event.property.post", - "1.0" - ); - writeResponse(ctx, HttpResponseStatus.OK, successRes.toString()); - } catch (Exception e) { - JSONObject errorRes = createResponseJson( - 500, - new JSONObject(), - id, - "The format of result is error!", - "thing.event.property.post", - "1.0" - ); - writeResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorRes.toString()); - } - } - - /** - * 创建标准化的响应 JSON 对象 - * - * @param code 响应状态码(业务层面的) - * @param data 返回的数据对象(JSON) - * @param id 请求的 id(可选) - * @param message 返回的提示信息 - * @param method 返回的 method 标识 - * @param version 返回的版本号 - * @return 构造好的 JSON 对象 - */ - private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) { - JSONObject res = new JSONObject(); - res.set("code", code); - res.set("data", data != null ? data : new JSONObject()); - res.set("id", id); - res.set("message", message); - res.set("method", method); - res.set("version", version); - return res; - } - - /** - * 向客户端返回 HTTP 响应的辅助方法 - * - * @param ctx 通道上下文 - * @param status HTTP 响应状态码(网络层面的) - * @param content 响应内容(JSON 字符串或其他文本) - */ - private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) { - // 设置响应头为 JSON 类型和正确的编码 - FullHttpResponse response = new DefaultFullHttpResponse( - HttpVersion.HTTP_1_1, - status, - Unpooled.copiedBuffer(content, CharsetUtil.UTF_8) - ); - response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8"); - response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); - - // 发送响应并在发送完成后关闭连接 - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java deleted file mode 100644 index 66e0c69a39..0000000000 --- a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java +++ /dev/null @@ -1,94 +0,0 @@ -package cn.iocoder.yudao.module.iot.plugin; - -import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; -import cn.iocoder.yudao.module.iot.api.ServiceRegistry; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.*; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.codec.http.*; -import lombok.extern.slf4j.Slf4j; -import org.pf4j.PluginWrapper; -import org.pf4j.Plugin; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -@Slf4j -public class HttpPlugin extends Plugin { - - private static final int PORT = 8092; - - private ExecutorService executorService; - private DeviceDataApi deviceDataApi; - - public HttpPlugin(PluginWrapper wrapper) { - super(wrapper); - // 初始化线程池 - this.executorService = Executors.newSingleThreadExecutor(); - } - - @Override - public void start() { - log.info("HttpPlugin.start()"); - - // 重新初始化线程池,确保它是活跃的 - if (executorService.isShutdown() || executorService.isTerminated()) { - executorService = Executors.newSingleThreadExecutor(); - } - - // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例 - deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); - if (deviceDataApi == null) { - log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); - return; - } - - // 异步启动 Netty 服务器 - executorService.submit(this::startHttpServer); - } - - @Override - public void stop() { - log.info("HttpPlugin.stop()"); - // 停止线程池 - executorService.shutdownNow(); - } - - /** - * 启动 HTTP 服务 - */ - private void startHttpServer() { - EventLoopGroup bossGroup = new NioEventLoopGroup(1); - EventLoopGroup workerGroup = new NioEventLoopGroup(); - - try { - ServerBootstrap bootstrap = new ServerBootstrap(); - bootstrap.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - .childHandler(new ChannelInitializer<>() { - - @Override - protected void initChannel(Channel channel) { - channel.pipeline().addLast(new HttpServerCodec()); - channel.pipeline().addLast(new HttpObjectAggregator(65536)); - // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器 - channel.pipeline().addLast(new HttpHandler(deviceDataApi)); - } - - }); - - // 绑定端口并启动服务器 - ChannelFuture future = bootstrap.bind(PORT).sync(); - log.info("HTTP 服务器启动成功,端口为: {}", PORT); - future.channel().closeFuture().sync(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.warn("HTTP 服务启动被中断", e); - } finally { - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); - } - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java new file mode 100644 index 0000000000..335d6c95d2 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.iot.plugin; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO; +import io.vertx.core.Handler; +import io.vertx.ext.web.RequestBody; +import io.vertx.ext.web.RoutingContext; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HttpVertxHandler implements Handler { + + private final DeviceDataApi deviceDataApi; + + public HttpVertxHandler(DeviceDataApi deviceDataApi) { + this.deviceDataApi = deviceDataApi; + } + + @Override + public void handle(RoutingContext ctx) { + String productKey = ctx.pathParam("productKey"); + String deviceName = ctx.pathParam("deviceName"); + RequestBody requestBody = ctx.body(); + + JSONObject jsonData; + try { + jsonData = JSONUtil.parseObj(requestBody.asJsonObject()); + } catch (Exception e) { + JSONObject res = createResponseJson( + 400, + new JSONObject(), + null, + "请求数据不是合法的 JSON 格式: " + e.getMessage(), + "thing.event.property.post", + "1.0"); + ctx.response() + .setStatusCode(400) + .putHeader("Content-Type", "application/json; charset=UTF-8") + .end(res.toString()); + return; + } + + String id = jsonData.getStr("id", null); + + try { + // 调用主程序的接口保存数据 + DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder() + .productKey(productKey) + .deviceName(deviceName) + .message(jsonData.toString()) + .build(); + deviceDataApi.saveDeviceData(createDTO); + + // 构造成功响应内容 + JSONObject successRes = createResponseJson( + 200, + new JSONObject(), + id, + "success", + "thing.event.property.post", + "1.0"); + ctx.response() + .setStatusCode(200) + .putHeader("Content-Type", "application/json; charset=UTF-8") + .end(successRes.toString()); + } catch (Exception e) { + JSONObject errorRes = createResponseJson( + 500, + new JSONObject(), + id, + "The format of result is error!", + "thing.event.property.post", + "1.0"); + ctx.response() + .setStatusCode(500) + .putHeader("Content-Type", "application/json; charset=UTF-8") + .end(errorRes.toString()); + } + } + + /** + * 创建标准化的响应 JSON 对象 + * + * @param code 响应状态码(业务层面的) + * @param data 返回的数据对象(JSON) + * @param id 请求的 id(可选) + * @param message 返回的提示信息 + * @param method 返回的 method 标识 + * @param version 返回的版本号 + * @return 构造好的 JSON 对象 + */ + private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, + String version) { + JSONObject res = new JSONObject(); + res.set("code", code); + res.set("data", data != null ? data : new JSONObject()); + res.set("id", id); + res.set("message", message); + res.set("method", method); + res.set("version", version); + return res; + } +} diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java new file mode 100644 index 0000000000..1d6fcad92b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java @@ -0,0 +1,82 @@ +package cn.iocoder.yudao.module.iot.plugin; + +import cn.iocoder.yudao.module.iot.api.ServiceRegistry; +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; +import io.vertx.core.Vertx; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import org.pf4j.PluginWrapper; +import org.pf4j.spring.SpringPlugin; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HttpVertxPlugin extends SpringPlugin { + + private static final int PORT = 8092; + private Vertx vertx; + private DeviceDataApi deviceDataApi; + + public HttpVertxPlugin(PluginWrapper wrapper) { + super(wrapper); + } + + @Override + public void start() { + log.info("HttpVertxPlugin.start()"); + + // 获取 DeviceDataApi 实例 + deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); + if (deviceDataApi == null) { + log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); + return; + } + + // 初始化 Vert.x + vertx = Vertx.vertx(); + Router router = Router.router(vertx); + + // 处理 Body + router.route().handler(BodyHandler.create()); + + // 设置路由 + router.post("/sys/:productKey/:deviceName/thing/event/property/post") + .handler(new HttpVertxHandler(deviceDataApi)); + + // 启动 HTTP 服务器 + vertx.createHttpServer() + .requestHandler(router) + .listen(PORT, http -> { + if (http.succeeded()) { + log.info("HTTP 服务器启动成功,端口为: {}", PORT); + } else { + log.error("HTTP 服务器启动失败", http.cause()); + } + }); + } + + @Override + public void stop() { + log.info("HttpVertxPlugin.stop()"); + if (vertx != null) { + vertx.close(ar -> { + if (ar.succeeded()) { + log.info("Vert.x 关闭成功"); + } else { + log.error("Vert.x 关闭失败", ar.cause()); + } + }); + } + } + + @Override + protected ApplicationContext createApplicationContext() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.setClassLoader(getWrapper().getPluginClassLoader()); + applicationContext.refresh(); + + return applicationContext; + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/resources/application.yml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/resources/application.yml new file mode 100644 index 0000000000..ea2234f83e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/resources/application.yml @@ -0,0 +1,15 @@ +server: + port: 8092 + +spring: + application: + name: yudao-module-iot-http-plugin + +# MQTT-RPC 配置 +mqtt: + broker: tcp://chaojiniu.top:1883 + username: haohao + password: ahh@123456 + clientId: mqtt-rpc-client-${random.int} + requestTopic: rpc/request + responseTopicPrefix: rpc/response/ diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties new file mode 100644 index 0000000000..31050c5bac --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties @@ -0,0 +1,6 @@ +plugin.id=mqtt-plugin +plugin.class=cn.iocoder.yudao.module.iot.plugin.MqttPlugin +plugin.version=0.0.1 +plugin.provider=ahh +plugin.dependencies= +plugin.description=mqtt-plugin-0.0.1 diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml new file mode 100644 index 0000000000..9607e0f93c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml @@ -0,0 +1,154 @@ + + + + yudao-module-iot-plugin + cn.iocoder.boot + ${revision} + + 4.0.0 + jar + + yudao-module-iot-mqtt-plugin + + ${project.artifactId} + + 物联网 插件模块 - mqtt 插件 + + + + + mqtt-plugin + cn.iocoder.yudao.module.iot.plugin.MqttPlugin + 0.0.1 + ahh + mqtt-plugin-0.0.1 + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.6 + + + unzip jar file + package + + + + + + + run + + + + + + + maven-assembly-plugin + 2.3 + + + + src/main/assembly/assembly.xml + + + false + + + + make-assembly + package + + attached + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + ${plugin.id} + ${plugin.class} + ${plugin.version} + ${plugin.provider} + ${plugin.description} + ${plugin.dependencies} + + + + + + + maven-deploy-plugin + + true + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.pf4j + pf4j-spring + provided + + + + cn.iocoder.boot + yudao-module-iot-api + ${revision} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/assembly/assembly.xml b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..daec9e4315 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/assembly/assembly.xml @@ -0,0 +1,31 @@ + + plugin + + zip + + false + + + false + runtime + lib + + *:jar:* + + + + + + + target/plugin-classes + classes + + + diff --git a/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java new file mode 100644 index 0000000000..b3749e4025 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.iot.plugin; + +import cn.iocoder.yudao.module.iot.api.ServiceRegistry; +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.Plugin; +import org.pf4j.PluginWrapper; + +import javax.annotation.Resource; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Slf4j +public class MqttPlugin extends Plugin { + + private ExecutorService executorService; + @Resource + private DeviceDataApi deviceDataApi; + + public MqttPlugin(PluginWrapper wrapper) { + super(wrapper); + this.executorService = Executors.newSingleThreadExecutor(); + } + + @Override + public void start() { + log.info("MqttPlugin.start()"); + + if (executorService.isShutdown() || executorService.isTerminated()) { + executorService = Executors.newSingleThreadExecutor(); + } + + deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); + if (deviceDataApi == null) { + log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); + return; + } + + } + + @Override + public void stop() { + log.info("MqttPlugin.stop()"); + } +} \ No newline at end of file diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 5457247e6f..c0bd5f64da 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -212,4 +212,9 @@ iot: # 保持连接 keepalive: 60 # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) - clearSession: true \ No newline at end of file + clearSession: true + + +# 插件配置 +pf4j: + pluginsDir: ${user.home}/plugins # 插件目录 \ No newline at end of file diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index fec1ad3506..518aa69a4c 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -183,10 +183,8 @@ logging: cn.iocoder.yudao.module.crm.dal.mysql: debug cn.iocoder.yudao.module.erp.dal.mysql: debug cn.iocoder.yudao.module.iot.dal.mysql: debug - cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG cn.iocoder.yudao.module.ai.dal.mysql: debug org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 - com.taosdata: DEBUG # TDengine 的日志级别 debug: false @@ -283,3 +281,16 @@ iot: # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) clearSession: true +# MQTT-RPC 配置 +mqtt: + broker: tcp://127.0.0.1:1883 + username: root + password: 123456 + clientId: mqtt-rpc-server-${random.int} + requestTopic: rpc/request + responseTopicPrefix: rpc/response/ + + +# 插件配置 +pf4j: + pluginsDir: /Users/anhaohao/code/gitee/ruoyi-vue-pro/plugins # 插件目录 \ No newline at end of file