Commit f1d887d0e0755c09372e19ed524719357115be9f

Authored by alwayssuper
2 parents d9dda54c deab8c1c

Merge branch 'feature/iot' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/iot

 Conflicts:
	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java
	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java
	yudao-server/src/main/resources/application-local.yaml
Showing 73 changed files with 1617 additions and 518 deletions
.gitignore
@@ -51,3 +51,4 @@ rebel.xml @@ -51,3 +51,4 @@ rebel.xml
51 application-my.yaml 51 application-my.yaml
52 52
53 /yudao-ui-app/unpackage/ 53 /yudao-ui-app/unpackage/
  54 +**/.DS_Store
plugins/disabled.txt deleted
plugins/enabled.txt deleted
1 -http-plugin  
2 -http-plugin@0.0.1  
plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar
No preview for this file type
yudao-dependencies/pom.xml
@@ -67,6 +67,7 @@ @@ -67,6 +67,7 @@
67 <bizlog-sdk.version>3.0.6</bizlog-sdk.version> 67 <bizlog-sdk.version>3.0.6</bizlog-sdk.version>
68 <mqtt.version>1.2.5</mqtt.version> 68 <mqtt.version>1.2.5</mqtt.version>
69 <pf4j-spring.version>0.9.0</pf4j-spring.version> 69 <pf4j-spring.version>0.9.0</pf4j-spring.version>
  70 + <vertx.version>4.4.0</vertx.version>
70 <!-- 三方云服务相关 --> 71 <!-- 三方云服务相关 -->
71 <okio.version>3.5.0</okio.version> 72 <okio.version>3.5.0</okio.version>
72 <okhttp3.version>4.11.0</okhttp3.version> 73 <okhttp3.version>4.11.0</okhttp3.version>
@@ -613,6 +614,19 @@ @@ -613,6 +614,19 @@
613 <version>${pf4j-spring.version}</version> 614 <version>${pf4j-spring.version}</version>
614 </dependency> 615 </dependency>
615 616
  617 + <!-- Vert.x 核心依赖 -->
  618 + <dependency>
  619 + <groupId>io.vertx</groupId>
  620 + <artifactId>vertx-core</artifactId>
  621 + <version>${vertx.version}</version>
  622 + </dependency>
  623 + <!-- Vert.x Web 模块 -->
  624 + <dependency>
  625 + <groupId>io.vertx</groupId>
  626 + <artifactId>vertx-web</artifactId>
  627 + <version>${vertx.version}</version>
  628 + </dependency>
  629 +
616 </dependencies> 630 </dependencies>
617 </dependencyManagement> 631 </dependencyManagement>
618 632
yudao-module-iot/yudao-module-iot-api/pom.xml
@@ -33,6 +33,13 @@ @@ -33,6 +33,13 @@
33 </exclusion> 33 </exclusion>
34 </exclusions> 34 </exclusions>
35 </dependency> 35 </dependency>
  36 +
  37 + <!-- 参数校验 -->
  38 + <dependency>
  39 + <groupId>org.springframework.boot</groupId>
  40 + <artifactId>spring-boot-starter-validation</artifactId>
  41 + <optional>true</optional>
  42 + </dependency>
36 </dependencies> 43 </dependencies>
37 44
38 </project> 45 </project>
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.api; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.api;
3 import java.util.HashMap; 3 import java.util.HashMap;
4 import java.util.Map; 4 import java.util.Map;
5 5
  6 +// TODO 芋艿:纠结下
6 /** 7 /**
7 * 服务注册表 - 插架模块使用,无法使用 Spring 注入 8 * 服务注册表 - 插架模块使用,无法使用 Spring 注入
8 */ 9 */
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java
1 package cn.iocoder.yudao.module.iot.api.device; 1 package cn.iocoder.yudao.module.iot.api.device;
2 2
  3 +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO;
  4 +import jakarta.validation.Valid;
  5 +
3 /** 6 /**
4 * 设备数据 API 7 * 设备数据 API
  8 + *
  9 + * @author haohao
5 */ 10 */
6 public interface DeviceDataApi { 11 public interface DeviceDataApi {
7 12
8 /** 13 /**
9 * 保存设备数据 14 * 保存设备数据
10 * 15 *
11 - * @param productKey 产品 key  
12 - * @param deviceName 设备名称  
13 - * @param message 消息 16 + * @param createDTO 设备数据
14 */ 17 */
15 - void saveDeviceData(String productKey, String deviceName, String message); 18 + void saveDeviceData(@Valid DeviceDataCreateReqDTO createDTO);
16 19
17 } 20 }
18 \ No newline at end of file 21 \ No newline at end of file
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/DeviceDataCreateReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.api.device.dto;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +import jakarta.validation.constraints.NotNull;
  8 +
  9 +@Data
  10 +@NoArgsConstructor
  11 +@AllArgsConstructor
  12 +@Builder
  13 +public class DeviceDataCreateReqDTO {
  14 +
  15 + /**
  16 + * 产品标识
  17 + */
  18 + @NotNull(message = "产品标识不能为空")
  19 + private String productKey;
  20 + /**
  21 + * 设备名称
  22 + */
  23 + @NotNull(message = "设备名称不能为空")
  24 + private String deviceName;
  25 + /**
  26 + * 消息
  27 + */
  28 + @NotNull(message = "消息不能为空")
  29 + private String message;
  30 +
  31 +}
0 \ No newline at end of file 32 \ No newline at end of file
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java
@@ -13,8 +13,8 @@ import java.util.Arrays; @@ -13,8 +13,8 @@ import java.util.Arrays;
13 @Getter 13 @Getter
14 public enum IotPluginDeployTypeEnum implements IntArrayValuable { 14 public enum IotPluginDeployTypeEnum implements IntArrayValuable {
15 15
16 - UPLOAD(0, "上传 jar"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈  
17 - ALONE(1, "独立运行"); 16 + JAR(0, "JAR 部署"),
  17 + STANDALONE(1, "独立部署");
18 18
19 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); 19 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray();
20 20
@@ -48,4 +48,5 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable { @@ -48,4 +48,5 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable {
48 public int[] array() { 48 public int[] array() {
49 return ARRAYS; 49 return ARRAYS;
50 } 50 }
  51 +
51 } 52 }
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcRequest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mqttrpc.common;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +// TODO @芋艿:要不要加个 mqtt 值了的前缀
  9 +/**
  10 + * MQTT RPC 请求
  11 + */
  12 +@Data
  13 +@Builder
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class RpcRequest {
  17 +
  18 + /**
  19 + * 方法名
  20 + */
  21 + private String method;
  22 +
  23 + /**
  24 + * 参数
  25 + */
  26 + // TODO @haohao:object 对象会不会不好序列化?
  27 + private Object[] params;
  28 +
  29 + /**
  30 + * 关联 ID
  31 + */
  32 + private String correlationId;
  33 +
  34 + /**
  35 + * 回复地址
  36 + */
  37 + private String replyTo;
  38 +
  39 +}
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/RpcResponse.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mqttrpc.common;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +/**
  9 + * MQTT RPC 响应
  10 + */
  11 +@Data
  12 +@Builder
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class RpcResponse {
  16 +
  17 + /**
  18 + * 关联 ID
  19 + */
  20 + private String correlationId;
  21 +
  22 + /**
  23 + * 结果
  24 + */
  25 + // TODO @haohao:object 对象会不会不好反序列化?
  26 + private Object result;
  27 +
  28 + /**
  29 + * 错误
  30 + */
  31 + private String error;
  32 +
  33 +}
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/common/SerializationUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mqttrpc.common;
  2 +
  3 +import cn.hutool.json.JSONUtil;
  4 +
  5 +/**
  6 + * 序列化工具类
  7 + *
  8 + */
  9 +public class SerializationUtils {
  10 +
  11 + public static String serialize(Object obj) {
  12 + return JSONUtil.toJsonStr(obj);
  13 + }
  14 +
  15 + public static <T> T deserialize(String json, Class<T> clazz) {
  16 + return JSONUtil.toBean(json, clazz);
  17 + }
  18 +
  19 +}
0 \ No newline at end of file 20 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -64,6 +64,16 @@ @@ -64,6 +64,16 @@
64 <artifactId>yudao-spring-boot-starter-excel</artifactId> 64 <artifactId>yudao-spring-boot-starter-excel</artifactId>
65 </dependency> 65 </dependency>
66 66
  67 + <!-- Vert.x 核心依赖 -->
  68 + <dependency>
  69 + <groupId>io.vertx</groupId>
  70 + <artifactId>vertx-core</artifactId>
  71 + </dependency>
  72 + <!-- Vert.x Web 模块 -->
  73 + <dependency>
  74 + <groupId>io.vertx</groupId>
  75 + <artifactId>vertx-web</artifactId>
  76 + </dependency>
67 <!-- MQTT --> 77 <!-- MQTT -->
68 <dependency> 78 <dependency>
69 <groupId>org.eclipse.paho</groupId> 79 <groupId>org.eclipse.paho</groupId>
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java
1 package cn.iocoder.yudao.module.iot.api.device; 1 package cn.iocoder.yudao.module.iot.api.device;
2 2
  3 +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO;
3 import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; 4 import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
4 import org.springframework.stereotype.Service; 5 import org.springframework.stereotype.Service;
5 import org.springframework.validation.annotation.Validated; 6 import org.springframework.validation.annotation.Validated;
@@ -17,8 +18,8 @@ public class DeviceDataApiImpl implements DeviceDataApi { @@ -17,8 +18,8 @@ public class DeviceDataApiImpl implements DeviceDataApi {
17 private IotDevicePropertyDataService deviceDataService; 18 private IotDevicePropertyDataService deviceDataService;
18 19
19 @Override 20 @Override
20 - public void saveDeviceData(String productKey, String deviceName, String message) {  
21 - deviceDataService.saveDeviceData(productKey, deviceName, message); 21 + public void saveDeviceData(DeviceDataCreateReqDTO createDTO) {
  22 + deviceDataService.saveDeviceData(createDTO);
22 } 23 }
23 24
24 } 25 }
25 \ No newline at end of file 26 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java
@@ -3,19 +3,15 @@ package cn.iocoder.yudao.module.iot.controller.admin.device; @@ -3,19 +3,15 @@ package cn.iocoder.yudao.module.iot.controller.admin.device;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;  
7 -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;  
8 -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;  
9 -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; 6 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.*;
10 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
11 -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO; 8 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
12 import cn.iocoder.yudao.module.iot.service.device.IotDeviceLogDataService; 9 import cn.iocoder.yudao.module.iot.service.device.IotDeviceLogDataService;
13 import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; 10 import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
14 import io.swagger.v3.oas.annotations.Operation; 11 import io.swagger.v3.oas.annotations.Operation;
15 import io.swagger.v3.oas.annotations.tags.Tag; 12 import io.swagger.v3.oas.annotations.tags.Tag;
16 import jakarta.annotation.Resource; 13 import jakarta.annotation.Resource;
17 import jakarta.validation.Valid; 14 import jakarta.validation.Valid;
18 -import org.springframework.security.access.prepost.PreAuthorize;  
19 import org.springframework.validation.annotation.Validated; 15 import org.springframework.validation.annotation.Validated;
20 import org.springframework.web.bind.annotation.*; 16 import org.springframework.web.bind.annotation.*;
21 17
@@ -36,6 +32,9 @@ public class IotDeviceDataController { @@ -36,6 +32,9 @@ public class IotDeviceDataController {
36 @Resource 32 @Resource
37 private IotDeviceLogDataService iotDeviceLogDataService; 33 private IotDeviceLogDataService iotDeviceLogDataService;
38 34
  35 + @Resource // TODO @super:service 之间,不用空行;原因是,这样更简洁;空行,主要是为了“间隔”,提升可读性
  36 + private IotDeviceLogDataService deviceLogDataService;
  37 +
39 // TODO @浩浩:这里的 /latest-list,包括方法名。 38 // TODO @浩浩:这里的 /latest-list,包括方法名。
40 @GetMapping("/latest") 39 @GetMapping("/latest")
41 @Operation(summary = "获取设备属性最新数据") 40 @Operation(summary = "获取设备属性最新数据")
@@ -52,12 +51,22 @@ public class IotDeviceDataController { @@ -52,12 +51,22 @@ public class IotDeviceDataController {
52 return success(BeanUtils.toBean(list, IotTimeDataRespVO.class)); 51 return success(BeanUtils.toBean(list, IotTimeDataRespVO.class));
53 } 52 }
54 53
  54 + // TODO:数据权限
55 @PostMapping("/simulator") 55 @PostMapping("/simulator")
56 @Operation(summary = "模拟设备") 56 @Operation(summary = "模拟设备")
57 public CommonResult<Boolean> simulatorDevice(@Valid @RequestBody IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { 57 public CommonResult<Boolean> simulatorDevice(@Valid @RequestBody IotDeviceDataSimulatorSaveReqVO simulatorReqVO) {
58 - //TODO:先生成一下日志 后续完善模拟设备代码逻辑 58 + //TODO:先生成一下设备日志 后续完善模拟设备代码逻辑
  59 + // TODO @super:应该 deviceDataService 里面有个 simulatorDevice,然后里面去 insert 日志!
59 iotDeviceLogDataService.createDeviceLog(simulatorReqVO); 60 iotDeviceLogDataService.createDeviceLog(simulatorReqVO);
60 return success(true); 61 return success(true);
61 } 62 }
62 63
  64 + // TODO:数据权限
  65 + @GetMapping("/log/page")
  66 + @Operation(summary = "获得设备日志分页")
  67 + public CommonResult<PageResult<IotDeviceLogRespVO>> getDeviceLogPage(@Valid IotDeviceLogPageReqVO pageReqVO) {
  68 + PageResult<IotDeviceLogDO> pageResult = deviceLogDataService.getDeviceLogPage(pageReqVO);
  69 + return success(BeanUtils.toBean(pageResult, IotDeviceLogRespVO.class));
  70 + }
  71 +
63 } 72 }
64 \ No newline at end of file 73 \ No newline at end of file
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 { @@ -14,7 +14,7 @@ public class IotDeviceSaveReqVO {
14 private Long id; 14 private Long id;
15 15
16 @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177") 16 @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177")
17 - @Size(max = 50, message = "设备编号长度不能超过50个字符") 17 + @Size(max = 50, message = "设备编号长度不能超过 50 个字符")
18 private String deviceKey; 18 private String deviceKey;
19 19
20 @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") 20 @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
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; @@ -4,24 +4,26 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
5 import lombok.Data; 5 import lombok.Data;
6 6
7 -  
8 -import java.time.LocalDateTime;  
9 - 7 +// TODO super: SaveReqVO => ReqVO
10 @Schema(description = "管理后台 - IoT 模拟设备数据 Request VO") 8 @Schema(description = "管理后台 - IoT 模拟设备数据 Request VO")
11 @Data 9 @Data
12 public class IotDeviceDataSimulatorSaveReqVO { 10 public class IotDeviceDataSimulatorSaveReqVO {
13 11
14 - @Schema(description = "消息ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "msg123") 12 + // TODO @super:感觉后端随机更合适?
  13 + @Schema(description = "消息 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "msg123")
15 private String id; 14 private String id;
16 15
  16 + // TODO @super:不用传递 productKey,因为 deviceKey 可以推导出来
17 @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123") 17 @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123")
18 @NotEmpty(message = "产品ID不能为空") 18 @NotEmpty(message = "产品ID不能为空")
19 private String productKey; 19 private String productKey;
20 20
  21 + // TODO @super:中文写作规范,中英文之间,要有空格。例如说,设备 ID。ps:这里应该是设备标识
21 @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123") 22 @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
22 @NotEmpty(message = "设备ID不能为空") 23 @NotEmpty(message = "设备ID不能为空")
23 private String deviceKey; 24 private String deviceKey;
24 25
  26 + // TODO @super:type、subType,是不是不用传递,因为模拟只有属性???
25 @Schema(description = "消息/日志类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property") 27 @Schema(description = "消息/日志类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
26 @NotEmpty(message = "消息类型不能为空") 28 @NotEmpty(message = "消息类型不能为空")
27 private String type; 29 private String type;
@@ -34,7 +36,8 @@ public class IotDeviceDataSimulatorSaveReqVO { @@ -34,7 +36,8 @@ public class IotDeviceDataSimulatorSaveReqVO {
34 @NotEmpty(message = "数据内容不能为空") 36 @NotEmpty(message = "数据内容不能为空")
35 private String content; 37 private String content;
36 38
  39 + // TODO @芋艿:需要讨论下,reportTime 到底以那个为准!
37 @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED) 40 @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED)
38 - private LocalDateTime reportTime; 41 + private Long reportTime;
39 42
40 } 43 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotEmpty;
  6 +import lombok.Data;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IoT 设备日志分页查询 Request VO")
  14 +@Data
  15 +public class IotDeviceLogPageReqVO extends PageParam {
  16 +
  17 + @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
  18 + @NotEmpty(message = "设备标识不能为空")
  19 + private String deviceKey;
  20 +
  21 + // TODO @super:对应的枚举类
  22 + @Schema(description = "消息类型", example = "property")
  23 + private String type;
  24 +
  25 + @Schema(description = "标识符", example = "temperature")
  26 + // TODO @super:对应的枚举类
  27 + private String subType;
  28 +
  29 + @Schema(description = "创建时间")
  30 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  31 + private LocalDateTime[] createTime;
  32 +
  33 +}
0 \ No newline at end of file 34 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/deviceData/IotDeviceLogRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IoT 设备日志 Response VO")
  9 +@Data
  10 +public class IotDeviceLogRespVO {
  11 +
  12 + @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private String id;
  14 +
  15 + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123")
  16 + private String productKey;
  17 +
  18 + @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
  19 + private String deviceKey;
  20 +
  21 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
  22 + private String type;
  23 +
  24 + @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature")
  25 + private String subType;
  26 +
  27 + @Schema(description = "日志内容", requiredMode = Schema.RequiredMode.REQUIRED)
  28 + private String content;
  29 +
  30 + @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED)
  31 + private LocalDateTime reportTime;
  32 +
  33 + @Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED)
  34 + private LocalDateTime ts;
  35 +
  36 +}
0 \ No newline at end of file 37 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java
@@ -9,7 +9,7 @@ import org.springframework.web.multipart.MultipartFile; @@ -9,7 +9,7 @@ import org.springframework.web.multipart.MultipartFile;
9 @Data 9 @Data
10 public class PluginInfoImportReqVO { 10 public class PluginInfoImportReqVO {
11 11
12 - @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") 12 + @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
13 private Long id; 13 private Long id;
14 14
15 @Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED) 15 @Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED)
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoPageReqVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; 1 package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
4 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 7 import lombok.Data;
6 8
@@ -11,7 +13,8 @@ public class PluginInfoPageReqVO extends PageParam { @@ -11,7 +13,8 @@ public class PluginInfoPageReqVO extends PageParam {
11 @Schema(description = "插件名称", example = "http") 13 @Schema(description = "插件名称", example = "http")
12 private String name; 14 private String name;
13 15
14 - @Schema(description = "状态") 16 + @Schema(description = "状态", example = "1")
  17 + @InEnum(IotPluginStatusEnum.class)
15 private Integer status; 18 private Integer status;
16 19
17 } 20 }
18 \ No newline at end of file 21 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; 1 package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2
3 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
4 -import com.alibaba.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
7 5
@@ -9,63 +7,48 @@ import java.time.LocalDateTime; @@ -9,63 +7,48 @@ import java.time.LocalDateTime;
9 7
10 @Schema(description = "管理后台 - IoT 插件信息 Response VO") 8 @Schema(description = "管理后台 - IoT 插件信息 Response VO")
11 @Data 9 @Data
12 -@ExcelIgnoreUnannotated  
13 public class PluginInfoRespVO { 10 public class PluginInfoRespVO {
14 11
15 @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") 12 @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
16 - @ExcelProperty("主键 ID")  
17 private Long id; 13 private Long id;
18 14
19 @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627") 15 @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
20 - @ExcelProperty("插件包标识符")  
21 private String pluginKey; 16 private String pluginKey;
22 17
23 @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") 18 @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
24 - @ExcelProperty("插件名称")  
25 private String name; 19 private String name;
26 20
27 @Schema(description = "描述", example = "你猜") 21 @Schema(description = "描述", example = "你猜")
28 - @ExcelProperty("描述")  
29 private String description; 22 private String description;
30 23
31 @Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 24 @Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
32 - @ExcelProperty("部署方式")  
33 private Integer deployType; 25 private Integer deployType;
34 26
35 @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED) 27 @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
36 - @ExcelProperty("插件包文件名")  
37 private String fileName; 28 private String fileName;
38 29
39 @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED) 30 @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
40 - @ExcelProperty("插件版本")  
41 private String version; 31 private String version;
42 32
43 @Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 33 @Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
44 - @ExcelProperty("插件类型")  
45 private Integer type; 34 private Integer type;
46 35
47 @Schema(description = "设备插件协议类型") 36 @Schema(description = "设备插件协议类型")
48 - @ExcelProperty("设备插件协议类型")  
49 private String protocol; 37 private String protocol;
50 38
51 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) 39 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
52 - @ExcelProperty("状态")  
53 private Integer status; 40 private Integer status;
54 41
55 @Schema(description = "插件配置项描述信息") 42 @Schema(description = "插件配置项描述信息")
56 - @ExcelProperty("插件配置项描述信息")  
57 private String configSchema; 43 private String configSchema;
58 44
59 @Schema(description = "插件配置信息") 45 @Schema(description = "插件配置信息")
60 - @ExcelProperty("插件配置信息")  
61 private String config; 46 private String config;
62 47
63 @Schema(description = "插件脚本") 48 @Schema(description = "插件脚本")
64 - @ExcelProperty("插件脚本")  
65 private String script; 49 private String script;
66 50
67 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 51 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
68 - @ExcelProperty("创建时间")  
69 private LocalDateTime createTime; 52 private LocalDateTime createTime;
70 53
71 } 54 }
72 \ No newline at end of file 55 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; 1 package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
3 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
4 -import lombok.*; 6 +import lombok.Data;
5 7
6 @Schema(description = "管理后台 - IoT 插件信息新增/修改 Request VO") 8 @Schema(description = "管理后台 - IoT 插件信息新增/修改 Request VO")
7 @Data 9 @Data
8 public class PluginInfoSaveReqVO { 10 public class PluginInfoSaveReqVO {
9 11
  12 + // TODO @haohao:新增的字段有点多,每个都需要哇?
  13 +
  14 + // TODO @haohao:一些枚举字段,需要加枚举校验。例如说,deployType、status、type 等
  15 +
10 @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") 16 @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
11 private Long id; 17 private Long id;
12 18
@@ -35,6 +41,7 @@ public class PluginInfoSaveReqVO { @@ -35,6 +41,7 @@ public class PluginInfoSaveReqVO {
35 private String protocol; 41 private String protocol;
36 42
37 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) 43 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
  44 + @InEnum(IotPluginStatusEnum.class)
38 private Integer status; 45 private Integer status;
39 46
40 @Schema(description = "插件配置项描述信息") 47 @Schema(description = "插件配置项描述信息")
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 { @@ -74,6 +74,7 @@ public class IotThingModelController {
74 return success(IotThingModelConvert.INSTANCE.convertList(list)); 74 return success(IotThingModelConvert.INSTANCE.convertList(list));
75 } 75 }
76 76
  77 + // TODO @puhui @super:getThingModelListByProductId 和 getThingModelListByProductId 可以融合么?
77 @GetMapping("/list") 78 @GetMapping("/list")
78 @Operation(summary = "获得产品物模型列表") 79 @Operation(summary = "获得产品物模型列表")
79 @PreAuthorize("@ss.hasPermission('iot:thing-model:query')") 80 @PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
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; @@ -6,11 +6,10 @@ import io.swagger.v3.oas.annotations.media.Schema;
6 import jakarta.validation.constraints.NotNull; 6 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 7 import lombok.Data;
8 8
9 -  
10 -  
11 @Schema(description = "管理后台 - IoT 产品物模型List Request VO") 9 @Schema(description = "管理后台 - IoT 产品物模型List Request VO")
12 @Data 10 @Data
13 public class IotThingModelListReqVO { 11 public class IotThingModelListReqVO {
  12 +
14 @Schema(description = "功能标识") 13 @Schema(description = "功能标识")
15 private String identifier; 14 private String identifier;
16 15
@@ -21,7 +20,8 @@ public class IotThingModelListReqVO { @@ -21,7 +20,8 @@ public class IotThingModelListReqVO {
21 @InEnum(IotThingModelTypeEnum.class) 20 @InEnum(IotThingModelTypeEnum.class)
22 private Integer type; 21 private Integer type;
23 22
24 - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED)  
25 - @NotNull(message = "产品ID不能为空") 23 + @Schema(description = "产品 ID", requiredMode = Schema.RequiredMode.REQUIRED)
  24 + @NotNull(message = "产品 ID 不能为空")
26 private Long productId; 25 private Long productId;
  26 +
27 } 27 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java
1 package cn.iocoder.yudao.module.iot.dal.dataobject.device; 1 package cn.iocoder.yudao.module.iot.dal.dataobject.device;
2 2
3 -import cn.hutool.core.date.DateTime;  
4 import lombok.AllArgsConstructor; 3 import lombok.AllArgsConstructor;
5 import lombok.Builder; 4 import lombok.Builder;
6 import lombok.Data; 5 import lombok.Data;
7 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
8 7
9 -import java.time.LocalDateTime;  
10 -  
11 /** 8 /**
12 * IoT 设备日志数据 DO 9 * IoT 设备日志数据 DO
13 * 10 *
  11 + * 目前使用 TDengine 存储
  12 + *
14 * @author alwayssuper 13 * @author alwayssuper
15 */ 14 */
16 @Data 15 @Data
@@ -18,45 +17,52 @@ import java.time.LocalDateTime; @@ -18,45 +17,52 @@ import java.time.LocalDateTime;
18 @NoArgsConstructor 17 @NoArgsConstructor
19 @AllArgsConstructor 18 @AllArgsConstructor
20 public class IotDeviceLogDO { 19 public class IotDeviceLogDO {
  20 +
  21 + // TODO @芋艿:消息 ID 的生成逻辑
21 /** 22 /**
22 - * 消息ID 23 + * 消息 ID
23 */ 24 */
24 private String id; 25 private String id;
25 26
  27 + // TODO @super:关联要 @下
26 /** 28 /**
27 - * 产品ID 29 + * 产品标识
28 */ 30 */
29 private String productKey; 31 private String productKey;
30 32
  33 + // TODO @super:关联要 @下
31 /** 34 /**
32 - * 设备ID 35 + * 设备标识
33 */ 36 */
34 private String deviceKey; 37 private String deviceKey;
35 38
  39 + // TODO @super:枚举类
36 /** 40 /**
37 - * 消息/日志类型 41 + * 日志类型
38 */ 42 */
39 private String type; 43 private String type;
40 44
  45 + // TODO @super:枚举类
41 /** 46 /**
42 * 标识符:用于标识具体的属性、事件或服务 47 * 标识符:用于标识具体的属性、事件或服务
43 */ 48 */
44 private String subType; 49 private String subType;
45 50
46 /** 51 /**
47 - * 数据内容:存储具体的消息数据内容,通常是JSON格式 52 + * 数据内容
  53 + *
  54 + * 存储具体的消息数据内容,通常是 JSON 格式
48 */ 55 */
49 private String content; 56 private String content;
50 57
51 /** 58 /**
52 * 上报时间戳 59 * 上报时间戳
53 */ 60 */
54 - private DateTime reportTime; 61 + private Long reportTime;
55 62
56 /** 63 /**
57 * 时序时间 64 * 时序时间
58 */ 65 */
59 - private DateTime ts;  
60 - 66 + private Long ts;
61 67
62 } 68 }
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 { @@ -33,19 +33,22 @@ public class PluginInstanceDO extends BaseDO {
33 */ 33 */
34 private String mainId; 34 private String mainId;
35 /** 35 /**
36 - * 插件id 36 + * 插件 ID
37 * <p> 37 * <p>
38 * 关联 {@link PluginInfoDO#getId()} 38 * 关联 {@link PluginInfoDO#getId()}
39 */ 39 */
40 private Long pluginId; 40 private Long pluginId;
  41 +
41 /** 42 /**
42 - * 插件主程序所在ip 43 + * 插件主程序所在 IP
43 */ 44 */
44 private String ip; 45 private String ip;
45 /** 46 /**
46 * 插件主程序端口 47 * 插件主程序端口
47 */ 48 */
48 private Integer port; 49 private Integer port;
  50 +
  51 + // TODO @haohao:字段改成 heartbeatTime,LocalDateTime
49 /** 52 /**
50 * 心跳时间,心路时间超过 30 秒需要剔除 53 * 心跳时间,心路时间超过 30 秒需要剔除
51 */ 54 */
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; @@ -6,7 +6,7 @@ import lombok.Data;
6 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
7 7
8 // TODO @芋艿:纠结下字段 8 // TODO @芋艿:纠结下字段
9 -@Deprecated 9 +@Deprecated // TODO @super:看看啥时候删除下哈。
10 /** 10 /**
11 * TD 物模型消息日志的数据库 11 * TD 物模型消息日志的数据库
12 */ 12 */
@@ -25,7 +25,7 @@ public class ThingModelMessageDO { @@ -25,7 +25,7 @@ public class ThingModelMessageDO {
25 25
26 /** 26 /**
27 * 系统扩展参数 27 * 系统扩展参数
28 - * 28 + *
29 * 例如:设备状态、系统时间、固件版本等系统级信息 29 * 例如:设备状态、系统时间、固件版本等系统级信息
30 */ 30 */
31 private Object system; 31 private Object system;
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; @@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
  7 +
  8 +import java.util.List;
  9 +
7 import org.apache.ibatis.annotations.Mapper; 10 import org.apache.ibatis.annotations.Mapper;
8 import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.*; 11 import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.*;
9 12
@@ -22,4 +25,10 @@ public interface PluginInfoMapper extends BaseMapperX&lt;PluginInfoDO&gt; { @@ -22,4 +25,10 @@ public interface PluginInfoMapper extends BaseMapperX&lt;PluginInfoDO&gt; {
22 .orderByDesc(PluginInfoDO::getId)); 25 .orderByDesc(PluginInfoDO::getId));
23 } 26 }
24 27
  28 + default List<PluginInfoDO> selectListByStatus(Integer status) {
  29 + return selectList(new LambdaQueryWrapperX<PluginInfoDO>()
  30 + .eq(PluginInfoDO::getStatus, status)
  31 + .orderByAsc(PluginInfoDO::getId));
  32 + }
  33 +
25 } 34 }
26 \ No newline at end of file 35 \ No newline at end of file
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&lt;PluginInstanceDO&gt; { @@ -21,6 +21,7 @@ public interface PluginInstanceMapper extends BaseMapperX&lt;PluginInstanceDO&gt; {
21 .eq(PluginInstanceDO::getPluginId, pluginId)); 21 .eq(PluginInstanceDO::getPluginId, pluginId));
22 } 22 }
23 23
  24 + // TODO @haohao:这个还需要么?相关不用的 VO 可以删除
24 default PageResult<PluginInstanceDO> selectPage(PluginInstancePageReqVO reqVO) { 25 default PageResult<PluginInstanceDO> selectPage(PluginInstancePageReqVO reqVO) {
25 return selectPage(reqVO, new LambdaQueryWrapperX<PluginInstanceDO>() 26 return selectPage(reqVO, new LambdaQueryWrapperX<PluginInstanceDO>()
26 .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId()) 27 .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId())
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceLogDataMapper.java
1 package cn.iocoder.yudao.module.iot.dal.tdengine; 1 package cn.iocoder.yudao.module.iot.dal.tdengine;
2 2
  3 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO;
3 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; 4 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
4 import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS; 5 import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
5 import com.baomidou.mybatisplus.annotation.InterceptorIgnore; 6 import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
6 import org.apache.ibatis.annotations.Mapper; 7 import org.apache.ibatis.annotations.Mapper;
7 import org.apache.ibatis.annotations.Param; 8 import org.apache.ibatis.annotations.Param;
8 9
  10 +import java.util.List;
  11 +
9 /** 12 /**
10 - * IoT 设备日志 Mapper 13 + * IOT 设备日志数据 Mapper 接口
11 * 14 *
12 - * @author alwayssuper 15 + * 基于 TDengine 实现设备日志的存储
13 */ 16 */
14 @Mapper 17 @Mapper
15 @TDengineDS 18 @TDengineDS
@@ -18,22 +21,58 @@ public interface IotDeviceLogDataMapper { @@ -18,22 +21,58 @@ public interface IotDeviceLogDataMapper {
18 21
19 /** 22 /**
20 * 创建设备日志超级表 23 * 创建设备日志超级表
  24 +<<<<<<< HEAD
21 * 初始化只创建一次 25 * 初始化只创建一次
22 */ 26 */
23 void createDeviceLogSTable(); 27 void createDeviceLogSTable();
24 28
  29 +=======
  30 + *
  31 + * 注意:初始化时只需创建一次
  32 + */
  33 + void createDeviceLogSTable();
  34 +
  35 + // TODO @super:是不是删除哈
  36 +>>>>>>> deab8c1cc6bb7864d9c40e0c369f649f6f9bfa41
25 /** 37 /**
26 * 创建设备日志子表 38 * 创建设备日志子表
27 * 39 *
28 * @param deviceKey 设备标识 40 * @param deviceKey 设备标识
29 */ 41 */
  42 +<<<<<<< HEAD
30 void createDeviceLogTable( @Param("deviceKey") String deviceKey); 43 void createDeviceLogTable( @Param("deviceKey") String deviceKey);
31 44
32 /** 45 /**
33 * 插入设备日志数据 46 * 插入设备日志数据
34 * 47 *
  48 +=======
  49 + void createDeviceLogTable(@Param("deviceKey") String deviceKey);
  50 +
  51 + // TODO @super:单个参数,不用加 @Param
  52 + /**
  53 + * 插入设备日志数据
  54 + *
  55 + * 如果子表不存在,会自动创建子表
  56 + *
  57 +>>>>>>> deab8c1cc6bb7864d9c40e0c369f649f6f9bfa41
35 * @param log 设备日志数据 58 * @param log 设备日志数据
36 */ 59 */
37 void insert(@Param("log") IotDeviceLogDO log); 60 void insert(@Param("log") IotDeviceLogDO log);
38 61
  62 + /**
  63 + * 获得设备日志分页
  64 + *
  65 + * @param reqVO 分页查询条件
  66 + * @return 设备日志列表
  67 + */
  68 + List<IotDeviceLogDO> selectPage(@Param("reqVO") IotDeviceLogPageReqVO reqVO);
  69 +
  70 + /**
  71 + * 获得设备日志总数
  72 + *
  73 + * @param reqVO 查询条件
  74 + * @return 日志总数
  75 + */
  76 + Long selectCount(@Param("reqVO") IotDeviceLogPageReqVO reqVO);
  77 +
39 } 78 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java
1 package cn.iocoder.yudao.module.iot.dal.tdengine; 1 package cn.iocoder.yudao.module.iot.dal.tdengine;
2 2
3 -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;  
4 -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;  
5 -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessageDO;  
6 import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS; 3 import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
7 -import com.baomidou.dynamic.datasource.annotation.DS;  
8 import com.baomidou.mybatisplus.annotation.InterceptorIgnore; 4 import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
9 import org.apache.ibatis.annotations.Mapper; 5 import org.apache.ibatis.annotations.Mapper;
10 import org.apache.ibatis.annotations.Param; 6 import org.apache.ibatis.annotations.Param;
@@ -13,7 +9,7 @@ import org.apache.ibatis.annotations.Param; @@ -13,7 +9,7 @@ import org.apache.ibatis.annotations.Param;
13 * 处理 TD 中物模型消息日志的操作 9 * 处理 TD 中物模型消息日志的操作
14 */ 10 */
15 @Mapper 11 @Mapper
16 -@Deprecated 12 +@Deprecated // TODO super:什么时候,删除下哈。
17 @TDengineDS 13 @TDengineDS
18 @InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错 14 @InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
19 public interface TdThingModelMessageMapper { 15 public interface TdThingModelMessageMapper {
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; @@ -17,7 +17,7 @@ import org.springframework.stereotype.Component;
17 * @author ahh 17 * @author ahh
18 */ 18 */
19 @Slf4j 19 @Slf4j
20 -@Component 20 +//@Component
21 public class EmqxCallback implements MqttCallbackExtended { 21 public class EmqxCallback implements MqttCallbackExtended {
22 22
23 @Lazy 23 @Lazy
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; @@ -19,7 +19,7 @@ import org.springframework.stereotype.Component;
19 */ 19 */
20 @Slf4j 20 @Slf4j
21 @Data 21 @Data
22 -@Component 22 +//@Component
23 public class EmqxClient { 23 public class EmqxClient {
24 24
25 @Resource 25 @Resource
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; @@ -12,8 +12,8 @@ import org.springframework.stereotype.Component;
12 * @author ahh 12 * @author ahh
13 */ 13 */
14 @Data 14 @Data
15 -@Component  
16 -@ConfigurationProperties("iot.emq") 15 +//@Component
  16 +//@ConfigurationProperties("iot.emq")
17 public class MqttConfig { 17 public class MqttConfig {
18 18
19 /** 19 /**
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java
1 package cn.iocoder.yudao.module.iot.emq.service; 1 package cn.iocoder.yudao.module.iot.emq.service;
2 2
  3 +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO;
3 import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; 4 import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
4 import jakarta.annotation.Resource; 5 import jakarta.annotation.Resource;
5 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
6 import org.eclipse.paho.client.mqttv3.MqttClient; 7 import org.eclipse.paho.client.mqttv3.MqttClient;
7 import org.eclipse.paho.client.mqttv3.MqttMessage; 8 import org.eclipse.paho.client.mqttv3.MqttMessage;
8 import org.springframework.scheduling.annotation.Async; 9 import org.springframework.scheduling.annotation.Async;
9 -import org.springframework.stereotype.Service;  
10 10
11 // TODO @芋艿:在瞅瞅 11 // TODO @芋艿:在瞅瞅
12 12
@@ -16,7 +16,7 @@ import org.springframework.stereotype.Service; @@ -16,7 +16,7 @@ import org.springframework.stereotype.Service;
16 * @author ahh 16 * @author ahh
17 */ 17 */
18 @Slf4j 18 @Slf4j
19 -@Service 19 +// @Service
20 public class EmqxServiceImpl implements EmqxService { 20 public class EmqxServiceImpl implements EmqxService {
21 21
22 @Resource 22 @Resource
@@ -34,7 +34,12 @@ public class EmqxServiceImpl implements EmqxService { @@ -34,7 +34,12 @@ public class EmqxServiceImpl implements EmqxService {
34 String productKey = topic.split("/")[2]; 34 String productKey = topic.split("/")[2];
35 String deviceName = topic.split("/")[3]; 35 String deviceName = topic.split("/")[3];
36 String message = new String(mqttMessage.getPayload()); 36 String message = new String(mqttMessage.getPayload());
37 - iotDeviceDataService.saveDeviceData(productKey, deviceName, message); 37 + DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder()
  38 + .productKey(productKey)
  39 + .deviceName(deviceName)
  40 + .message(message)
  41 + .build();
  42 + iotDeviceDataService.saveDeviceData(createDTO);
38 } 43 }
39 } 44 }
40 45
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java
@@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component;
13 * 13 *
14 * @author ahh 14 * @author ahh
15 */ 15 */
16 -@Component 16 +//@Component
17 public class EmqxStart implements ApplicationRunner { 17 public class EmqxStart implements ApplicationRunner {
18 18
19 @Resource 19 @Resource
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/PluginStart.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.plugin;
  2 +
  3 +import java.util.List;
  4 +
  5 +import javax.annotation.Resource;
  6 +
  7 +import org.pf4j.spring.SpringPluginManager;
  8 +import org.springframework.boot.ApplicationArguments;
  9 +import org.springframework.boot.ApplicationRunner;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +import lombok.extern.slf4j.Slf4j;
  13 +
  14 +import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService;
  15 +import cn.hutool.core.collection.CollUtil;
  16 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
  17 +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
  18 +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
  19 +
  20 +@Component
  21 +@Slf4j
  22 +public class PluginStart implements ApplicationRunner {
  23 +
  24 + @Resource
  25 + private PluginInfoService pluginInfoService;
  26 +
  27 + @Resource
  28 + private SpringPluginManager pluginManager;
  29 +
  30 + @Override
  31 + public void run(ApplicationArguments args) {
  32 + TenantUtils.executeIgnore(() -> { // 1. 忽略租户上下文执行
  33 + List<PluginInfoDO> pluginInfoList = pluginInfoService
  34 + .getPluginInfoListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); // 2. 获取运行中的插件列表
  35 + if (CollUtil.isEmpty(pluginInfoList)) { // 3. 检查插件列表是否为空
  36 + log.info("[run] 没有需要启动的插件"); // 4. 日志记录没有插件需要启动
  37 + return;
  38 + }
  39 + pluginInfoList.forEach(pluginInfo -> { // 5. 使用lambda表达式遍历插件列表
  40 + try {
  41 + log.info("[run][启动插件] pluginKey = {}", pluginInfo.getPluginKey()); // 6. 日志记录插件启动信息
  42 + pluginManager.startPlugin(pluginInfo.getPluginKey()); // 7. 启动插件
  43 + } catch (Exception e) {
  44 + log.error("[run][启动插件失败] pluginKey = {}", pluginInfo.getPluginKey(), e); // 8. 记录启动失败的日志
  45 + }
  46 + });
  47 + });
  48 +
  49 + }
  50 +
  51 +}
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 { @@ -31,7 +31,13 @@ public class UnifiedConfiguration {
31 @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER) 31 @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER)
32 public SpringPluginManager pluginManager() { 32 public SpringPluginManager pluginManager() {
33 log.info("[init][实例化 SpringPluginManager]"); 33 log.info("[init][实例化 SpringPluginManager]");
34 - SpringPluginManager springPluginManager = new SpringPluginManager(); 34 + SpringPluginManager springPluginManager = new SpringPluginManager() {
  35 + @Override
  36 + public void startPlugins() {
  37 + // 禁用插件启动,避免插件启动时,启动所有插件
  38 + log.info("[init][禁用默认启动所有插件]");
  39 + }
  40 + };
35 springPluginManager.addPluginStateListener(new CustomPluginStateListener()); 41 springPluginManager.addPluginStateListener(new CustomPluginStateListener());
36 return springPluginManager; 42 return springPluginManager;
37 } 43 }
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; @@ -16,7 +16,7 @@ import org.springframework.core.annotation.Order;
16 @Slf4j 16 @Slf4j
17 @RequiredArgsConstructor 17 @RequiredArgsConstructor
18 @Configuration 18 @Configuration
19 -@Order(Integer.MAX_VALUE) // 保证在最后执行 19 +@Order
20 public class TDengineTableInitConfiguration implements ApplicationRunner { 20 public class TDengineTableInitConfiguration implements ApplicationRunner {
21 21
22 private final IotDeviceLogDataService deviceLogService; 22 private final IotDeviceLogDataService deviceLogService;
@@ -26,15 +26,18 @@ public class TDengineTableInitConfiguration implements ApplicationRunner { @@ -26,15 +26,18 @@ public class TDengineTableInitConfiguration implements ApplicationRunner {
26 try { 26 try {
27 // 初始化设备日志表 27 // 初始化设备日志表
28 deviceLogService.initTDengineSTable(); 28 deviceLogService.initTDengineSTable();
29 - log.info("初始化 设备日志表 TDengine 表结构成功"); 29 + // TODO @super:这个日志,是不是不用打,不然重复啦!!!
  30 + log.info("[run]初始化 设备日志表 TDengine 表结构成功");
30 } catch (Exception ex) { 31 } catch (Exception ex) {
  32 + // TODO @super:初始化失败,打印 error 日志,退出系统。。不然跑起来,就初始啦!!!
31 if (ex.getMessage().contains("Table already exists")) { 33 if (ex.getMessage().contains("Table already exists")) {
32 log.info("TDengine 设备日志超级表已存在,跳过创建"); 34 log.info("TDengine 设备日志超级表已存在,跳过创建");
33 return; 35 return;
34 - }else{ 36 + } else{
35 log.error("初始化 设备日志表 TDengine 表结构失败", ex); 37 log.error("初始化 设备日志表 TDengine 表结构失败", ex);
36 } 38 }
37 throw ex; 39 throw ex;
38 } 40 }
39 } 41 }
  42 +
40 } 43 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java
1 package cn.iocoder.yudao.module.iot.job.plugin; 1 package cn.iocoder.yudao.module.iot.job.plugin;
2 2
3 -  
4 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 3 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
5 import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService; 4 import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService;
6 import org.springframework.scheduling.annotation.Scheduled; 5 import org.springframework.scheduling.annotation.Scheduled;
@@ -23,7 +22,8 @@ public class PluginInstancesJob { @@ -23,7 +22,8 @@ public class PluginInstancesJob {
23 @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) 22 @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
24 public void updatePluginInstances() { 23 public void updatePluginInstances() {
25 TenantUtils.executeIgnore(() -> { 24 TenantUtils.executeIgnore(() -> {
26 - pluginInstanceService.updatePluginInstances(); 25 + pluginInstanceService.reportPluginInstances();
27 }); 26 });
28 } 27 }
  28 +
29 } 29 }
30 \ No newline at end of file 30 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mqttrpc.config;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.context.annotation.Configuration;
  6 +
  7 +@Data
  8 +@Configuration
  9 +@ConfigurationProperties(prefix = "mqtt")
  10 +public class MqttConfig {
  11 + /**
  12 + * MQTT 代理地址
  13 + */
  14 + private String broker;
  15 +
  16 + /**
  17 + * MQTT 用户名
  18 + */
  19 + private String username;
  20 +
  21 + /**
  22 + * MQTT 密码
  23 + */
  24 + private String password;
  25 +
  26 + /**
  27 + * MQTT 客户端 ID
  28 + */
  29 + private String clientId;
  30 +
  31 + /**
  32 + * MQTT 请求主题
  33 + */
  34 + private String requestTopic;
  35 +
  36 + /**
  37 + * MQTT 响应主题前缀
  38 + */
  39 + private String responseTopicPrefix;
  40 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/server/RpcServer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mqttrpc.server;
  2 +
  3 +import cn.hutool.core.lang.UUID;
  4 +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcRequest;
  5 +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcResponse;
  6 +import cn.iocoder.yudao.module.iot.mqttrpc.common.SerializationUtils;
  7 +import cn.iocoder.yudao.module.iot.mqttrpc.config.MqttConfig;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.eclipse.paho.client.mqttv3.*;
  10 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
  11 +import org.springframework.stereotype.Service;
  12 +
  13 +import javax.annotation.PostConstruct;
  14 +import javax.annotation.PreDestroy;
  15 +import java.util.HashMap;
  16 +import java.util.Map;
  17 +
  18 +// TODO @芋艿:server 逻辑,再瞅瞅;
  19 +// TODO @haohao:如果只写在 iot biz 里,那么后续 server => client 貌似不方便?微信再讨论下~;
  20 +@Service
  21 +@Slf4j
  22 +public class RpcServer {
  23 +
  24 + private final MqttConfig mqttConfig;
  25 + private final MqttClient mqttClient;
  26 + private final Map<String, MethodInvoker> methodRegistry = new HashMap<>();
  27 +
  28 + public RpcServer(MqttConfig mqttConfig) throws MqttException {
  29 + this.mqttConfig = mqttConfig;
  30 + this.mqttClient = new MqttClient(mqttConfig.getBroker(), "rpc-server-" + UUID.randomUUID(), new MemoryPersistence());
  31 + MqttConnectOptions options = new MqttConnectOptions();
  32 + options.setAutomaticReconnect(true);
  33 + options.setCleanSession(true);
  34 + options.setUserName(mqttConfig.getUsername());
  35 + options.setPassword(mqttConfig.getPassword().toCharArray());
  36 + this.mqttClient.connect(options);
  37 + }
  38 +
  39 + @PostConstruct
  40 + public void init() throws MqttException {
  41 + mqttClient.subscribe(mqttConfig.getRequestTopic(), this::handleRequest);
  42 + log.info("RPC Server subscribed to topic: {}", mqttConfig.getRequestTopic());
  43 + }
  44 +
  45 + private void handleRequest(String topic, MqttMessage message) {
  46 + RpcRequest request = SerializationUtils.deserialize(new String(message.getPayload()), RpcRequest.class);
  47 + RpcResponse response = new RpcResponse();
  48 + response.setCorrelationId(request.getCorrelationId());
  49 +
  50 + try {
  51 + MethodInvoker invoker = methodRegistry.get(request.getMethod());
  52 + if (invoker == null) {
  53 + throw new NoSuchMethodException("Unknown method: " + request.getMethod());
  54 + }
  55 + Object result = invoker.invoke(request.getParams());
  56 + response.setResult(result);
  57 + } catch (Exception e) {
  58 + response.setError(e.getMessage());
  59 + log.error("Error processing RPC request: {}", e.getMessage(), e);
  60 + }
  61 +
  62 + String replyPayload = SerializationUtils.serialize(response);
  63 + MqttMessage replyMessage = new MqttMessage(replyPayload.getBytes());
  64 + replyMessage.setQos(1);
  65 + try {
  66 + mqttClient.publish(request.getReplyTo(), replyMessage);
  67 + log.info("Published response to {}", request.getReplyTo());
  68 + } catch (MqttException e) {
  69 + log.error("Failed to publish response: {}", e.getMessage(), e);
  70 + }
  71 + }
  72 +
  73 + /**
  74 + * 注册可调用的方法
  75 + *
  76 + * @param methodName 方法名称
  77 + * @param invoker 方法调用器
  78 + */
  79 + public void registerMethod(String methodName, MethodInvoker invoker) {
  80 + methodRegistry.put(methodName, invoker);
  81 + log.info("Registered method: {}", methodName);
  82 + }
  83 +
  84 + @PreDestroy
  85 + public void cleanup() throws MqttException {
  86 + mqttClient.disconnect();
  87 + log.info("RPC Server disconnected");
  88 + }
  89 +
  90 + /**
  91 + * 方法调用器接口
  92 + */
  93 + @FunctionalInterface
  94 + public interface MethodInvoker {
  95 +
  96 + Object invoke(Object[] params) throws Exception;
  97 +
  98 + }
  99 +
  100 +}
0 \ No newline at end of file 101 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataService.java
1 package cn.iocoder.yudao.module.iot.service.device; 1 package cn.iocoder.yudao.module.iot.service.device;
2 2
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; 4 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
4 7
5 /** 8 /**
6 * IoT 设备日志数据 Service 接口 9 * IoT 设备日志数据 Service 接口
@@ -10,13 +13,27 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDevi @@ -10,13 +13,27 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDevi
10 public interface IotDeviceLogDataService { 13 public interface IotDeviceLogDataService {
11 14
12 /** 15 /**
13 - * 初始化 TDengine 表 16 + * 初始化 TDengine 超级表
  17 + *
  18 + *系统启动时,会自动初始化一次
14 */ 19 */
15 void initTDengineSTable(); 20 void initTDengineSTable();
16 21
17 /** 22 /**
18 - * 模拟设备创建设备日志  
19 - * @param simulatorReqVO 模拟设备信息 23 + * 插入设备日志
  24 + *
  25 + * 当该设备第一次插入日志时,自动创建该设备的设备日志子表
  26 + *
  27 + * @param simulatorReqVO 设备日志模拟数据
20 */ 28 */
21 void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO); 29 void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO);
  30 +
  31 + /**
  32 + * 获得设备日志分页
  33 + *
  34 + * @param pageReqVO 分页查询
  35 + * @return 设备日志分页
  36 + */
  37 + PageResult<IotDeviceLogDO> getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO);
  38 +
22 } 39 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceLogDataServiceImpl.java
1 package cn.iocoder.yudao.module.iot.service.device; 1 package cn.iocoder.yudao.module.iot.service.device;
2 2
3 -import cn.hutool.core.date.DateTime; 3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO; 5 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataSimulatorSaveReqVO;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceLogPageReqVO;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO; 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
7 import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceLogDataMapper; 8 import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceLogDataMapper;
8 import jakarta.annotation.Resource; 9 import jakarta.annotation.Resource;
@@ -10,7 +11,7 @@ import lombok.extern.slf4j.Slf4j; @@ -10,7 +11,7 @@ import lombok.extern.slf4j.Slf4j;
10 import org.springframework.stereotype.Service; 11 import org.springframework.stereotype.Service;
11 import org.springframework.validation.annotation.Validated; 12 import org.springframework.validation.annotation.Validated;
12 13
13 -import java.time.LocalDateTime; 14 +import java.util.List;
14 15
15 /** 16 /**
16 * IoT 设备日志数据 Service 实现了 17 * IoT 设备日志数据 Service 实现了
@@ -19,27 +20,43 @@ import java.time.LocalDateTime; @@ -19,27 +20,43 @@ import java.time.LocalDateTime;
19 */ 20 */
20 @Service 21 @Service
21 @Slf4j 22 @Slf4j
  23 +@Validated
22 public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{ 24 public class IotDeviceLogDataServiceImpl implements IotDeviceLogDataService{
23 25
24 @Resource 26 @Resource
25 private IotDeviceLogDataMapper iotDeviceLogDataMapper; 27 private IotDeviceLogDataMapper iotDeviceLogDataMapper;
26 28
27 - 29 + // TODO @super:方法名。defineDeviceLog。。未来,有可能别人使用别的记录日志,例如说 es 之类的。
28 @Override 30 @Override
29 public void initTDengineSTable() { 31 public void initTDengineSTable() {
30 - try {  
31 - // 创建设备日志超级表  
32 - iotDeviceLogDataMapper.createDeviceLogSTable();  
33 - log.info("创建设备日志超级表成功");  
34 - } catch (Exception ex) {  
35 - throw ex;  
36 - } 32 + // TODO @super:改成不存在才创建。
  33 + iotDeviceLogDataMapper.createDeviceLogSTable();
37 } 34 }
38 35
39 @Override 36 @Override
40 public void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO) { 37 public void createDeviceLog(IotDeviceDataSimulatorSaveReqVO simulatorReqVO) {
  38 + // 1. 转换请求对象为 DO
41 IotDeviceLogDO iotDeviceLogDO = BeanUtils.toBean(simulatorReqVO, IotDeviceLogDO.class); 39 IotDeviceLogDO iotDeviceLogDO = BeanUtils.toBean(simulatorReqVO, IotDeviceLogDO.class);
42 - iotDeviceLogDO.setTs(DateTime.now()); 40 +
  41 + // 2. 处理时间字段
  42 + // TODO @super:一次性的字段,不用单独给个变量
  43 + long currentTime = System.currentTimeMillis();
  44 + // 2.1 设置时序时间为当前时间
  45 + iotDeviceLogDO.setTs(currentTime); // TODO @super:TS在SQL中直接NOW 咱们的TS数据获取是走哪一种;走 now()
  46 +
  47 + // 3. 插入数据
  48 + // TODO @super:不要直接调用对方的 IotDeviceLogDataMapper,通过 service 哈!
43 iotDeviceLogDataMapper.insert(iotDeviceLogDO); 49 iotDeviceLogDataMapper.insert(iotDeviceLogDO);
44 } 50 }
  51 +
  52 + // TODO @super:在 iotDeviceLogDataService 写
  53 + @Override
  54 + public PageResult<IotDeviceLogDO> getDeviceLogPage(IotDeviceLogPageReqVO pageReqVO) {
  55 + // 查询数据
  56 + List<IotDeviceLogDO> list = iotDeviceLogDataMapper.selectPage(pageReqVO);
  57 + Long total = iotDeviceLogDataMapper.selectCount(pageReqVO);
  58 + // 构造分页结果
  59 + return new PageResult<>(list, total);
  60 + }
  61 +
45 } 62 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java
1 package cn.iocoder.yudao.module.iot.service.device; 1 package cn.iocoder.yudao.module.iot.service.device;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO;
4 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; 5 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
5 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
6 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
@@ -25,12 +26,9 @@ public interface IotDevicePropertyDataService { @@ -25,12 +26,9 @@ public interface IotDevicePropertyDataService {
25 /** 26 /**
26 * 保存设备数据 27 * 保存设备数据
27 * 28 *
28 - * @param productKey 产品 key  
29 - * @param deviceName 设备名称  
30 - * @param message 消息  
31 - * <p>参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">JSON 格式</a> 29 + * @param createDTO 设备数据
32 */ 30 */
33 - void saveDeviceData(String productKey, String deviceName, String message); 31 + void saveDeviceData(DeviceDataCreateReqDTO createDTO);
34 32
35 /** 33 /**
36 * 获得设备属性最新数据 34 * 获得设备属性最新数据
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java
@@ -6,6 +6,7 @@ import cn.hutool.core.map.MapUtil; @@ -6,6 +6,7 @@ import cn.hutool.core.map.MapUtil;
6 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
7 import cn.hutool.json.JSONObject; 7 import cn.hutool.json.JSONObject;
8 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO;
9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO; 10 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
10 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs; 11 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
11 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
@@ -14,10 +15,9 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; @@ -14,10 +15,9 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
14 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO; 15 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
15 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage; 16 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
16 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; 17 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
17 -import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper;  
18 import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; 18 import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
  19 +import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper;
19 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper; 20 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
20 -import cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper;  
21 import cn.iocoder.yudao.module.iot.enums.IotConstants; 21 import cn.iocoder.yudao.module.iot.enums.IotConstants;
22 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; 22 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
23 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; 23 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
@@ -57,7 +57,7 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe @@ -57,7 +57,7 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe
57 .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT) 57 .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
58 .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE) 58 .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
59 .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? 59 .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
60 - .put( IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明? 60 + .put(IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
61 .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR) 61 .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR)
62 .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP) 62 .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
63 .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!! 63 .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
@@ -110,7 +110,6 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe @@ -110,7 +110,6 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe
110 return; 110 return;
111 } 111 }
112 newFields.add(0, new TDengineTableField(TDengineTableField.FIELD_TS, TDengineTableField.TYPE_TIMESTAMP)); 112 newFields.add(0, new TDengineTableField(TDengineTableField.FIELD_TS, TDengineTableField.TYPE_TIMESTAMP));
113 - // 2.1.1 创建产品超级表  
114 devicePropertyDataMapper.createProductPropertySTable(product.getProductKey(), newFields); 113 devicePropertyDataMapper.createProductPropertySTable(product.getProductKey(), newFields);
115 return; 114 return;
116 } 115 }
@@ -131,20 +130,20 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe @@ -131,20 +130,20 @@ public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataSe
131 } 130 }
132 131
133 @Override 132 @Override
134 - public void saveDeviceData(String productKey, String deviceName, String message) { 133 + public void saveDeviceData(DeviceDataCreateReqDTO createDTO) {
135 // 1. 根据产品 key 和设备名称,获得设备信息 134 // 1. 根据产品 key 和设备名称,获得设备信息
136 - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName); 135 + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(createDTO.getProductKey(), createDTO.getDeviceName());
137 // 2. 解析消息,保存数据 136 // 2. 解析消息,保存数据
138 - JSONObject jsonObject = new JSONObject(message);  
139 - log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject); 137 + JSONObject jsonObject = new JSONObject(createDTO.getMessage());
  138 + log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", createDTO.getProductKey(), createDTO.getDeviceName(), jsonObject);
140 ThingModelMessage thingModelMessage = ThingModelMessage.builder() 139 ThingModelMessage thingModelMessage = ThingModelMessage.builder()
141 .id(jsonObject.getStr("id")) 140 .id(jsonObject.getStr("id"))
142 .sys(jsonObject.get("sys")) 141 .sys(jsonObject.get("sys"))
143 .method(jsonObject.getStr("method")) 142 .method(jsonObject.getStr("method"))
144 .params(jsonObject.get("params")) 143 .params(jsonObject.get("params"))
145 .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time")) 144 .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time"))
146 - .productKey(productKey)  
147 - .deviceName(deviceName) 145 + .productKey(createDTO.getProductKey())
  146 + .deviceName(createDTO.getDeviceName())
148 .deviceKey(device.getDeviceKey()) 147 .deviceKey(device.getDeviceKey())
149 .build(); 148 .build();
150 thingModelMessageService.saveThingModelMessage(device, thingModelMessage); 149 thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/ExampleService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.plugin;
  2 +
  3 +import cn.iocoder.yudao.module.iot.mqttrpc.server.RpcServer;
  4 +import lombok.RequiredArgsConstructor;
  5 +import org.springframework.stereotype.Service;
  6 +
  7 +import javax.annotation.PostConstruct;
  8 +
  9 +@Service
  10 +@RequiredArgsConstructor
  11 +public class ExampleService {
  12 +
  13 + private final RpcServer rpcServer;
  14 +
  15 + @PostConstruct
  16 + public void registerMethods() {
  17 + rpcServer.registerMethod("add", params -> {
  18 + if (params.length != 2) {
  19 + throw new IllegalArgumentException("add方法需要两个参数");
  20 + }
  21 + int a = ((Number) params[0]).intValue();
  22 + int b = ((Number) params[1]).intValue();
  23 + return add(a, b);
  24 + });
  25 +
  26 + rpcServer.registerMethod("concat", params -> {
  27 + if (params.length != 2) {
  28 + throw new IllegalArgumentException("concat方法需要两个参数");
  29 + }
  30 + String str1 = params[0].toString();
  31 + String str2 = params[1].toString();
  32 + return concat(str1, str2);
  33 + });
  34 + }
  35 +
  36 + private int add(int a, int b) {
  37 + return a + b;
  38 + }
  39 +
  40 + private String concat(String a, String b) {
  41 + return a + b;
  42 + }
  43 +}
0 \ No newline at end of file 44 \ No newline at end of file
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; @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO; 4 import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
5 import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO; 5 import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
  7 +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
7 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
8 import org.springframework.web.multipart.MultipartFile; 9 import org.springframework.web.multipart.MultipartFile;
9 10
@@ -66,7 +67,7 @@ public interface PluginInfoService { @@ -66,7 +67,7 @@ public interface PluginInfoService {
66 * 更新插件的状态 67 * 更新插件的状态
67 * 68 *
68 * @param id 插件id 69 * @param id 插件id
69 - * @param status 状态 70 + * @param status 状态 {@link IotPluginStatusEnum}
70 */ 71 */
71 void updatePluginStatus(Long id, Integer status); 72 void updatePluginStatus(Long id, Integer status);
72 73
@@ -76,4 +77,12 @@ public interface PluginInfoService { @@ -76,4 +77,12 @@ public interface PluginInfoService {
76 * @return 插件信息列表 77 * @return 插件信息列表
77 */ 78 */
78 List<PluginInfoDO> getPluginInfoList(); 79 List<PluginInfoDO> getPluginInfoList();
79 -}  
80 \ No newline at end of file 80 \ No newline at end of file
  81 +
  82 + /**
  83 + * 根据状态获得插件信息列表
  84 + *
  85 + * @param status 状态 {@link IotPluginStatusEnum}
  86 + * @return 插件信息列表
  87 + */
  88 + List<PluginInfoDO> getPluginInfoListByStatus(Integer status);
  89 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java
@@ -9,25 +9,16 @@ import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper; @@ -9,25 +9,16 @@ import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper;
9 import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; 9 import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
10 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
11 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
12 -import org.pf4j.PluginDescriptor;  
13 -import org.pf4j.PluginState;  
14 -import org.pf4j.PluginWrapper;  
15 import org.pf4j.spring.SpringPluginManager; 12 import org.pf4j.spring.SpringPluginManager;
16 -import org.springframework.beans.factory.annotation.Value;  
17 import org.springframework.stereotype.Service; 13 import org.springframework.stereotype.Service;
18 import org.springframework.validation.annotation.Validated; 14 import org.springframework.validation.annotation.Validated;
19 import org.springframework.web.multipart.MultipartFile; 15 import org.springframework.web.multipart.MultipartFile;
20 16
21 -import java.io.File;  
22 -import java.io.IOException;  
23 -import java.nio.file.*;  
24 -import java.util.ArrayList;  
25 import java.util.List; 17 import java.util.List;
26 -import java.util.concurrent.Executors;  
27 -import java.util.concurrent.TimeUnit;  
28 18
29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
30 -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; 20 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_DELETE_FAILED_RUNNING;
  21 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_NOT_EXISTS;
31 22
32 /** 23 /**
33 * IoT 插件信息 Service 实现类 24 * IoT 插件信息 Service 实现类
@@ -41,18 +32,17 @@ public class PluginInfoServiceImpl implements PluginInfoService { @@ -41,18 +32,17 @@ public class PluginInfoServiceImpl implements PluginInfoService {
41 32
42 @Resource 33 @Resource
43 private PluginInfoMapper pluginInfoMapper; 34 private PluginInfoMapper pluginInfoMapper;
  35 +
44 @Resource 36 @Resource
45 - private SpringPluginManager pluginManager; 37 + private PluginInstanceService pluginInstanceService;
46 38
47 - @Value("${pf4j.pluginsDir}")  
48 - private String pluginsDir; 39 + @Resource
  40 + private SpringPluginManager pluginManager;
49 41
50 @Override 42 @Override
51 public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) { 43 public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
52 - // 插入  
53 PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class); 44 PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class);
54 pluginInfoMapper.insert(pluginInfo); 45 pluginInfoMapper.insert(pluginInfo);
55 - // 返回  
56 return pluginInfo.getId(); 46 return pluginInfo.getId();
57 } 47 }
58 48
@@ -67,41 +57,21 @@ public class PluginInfoServiceImpl implements PluginInfoService { @@ -67,41 +57,21 @@ public class PluginInfoServiceImpl implements PluginInfoService {
67 57
68 @Override 58 @Override
69 public void deletePluginInfo(Long id) { 59 public void deletePluginInfo(Long id) {
70 - // 校验存在 60 + // 1.1 校验存在
71 PluginInfoDO pluginInfoDO = validatePluginInfoExists(id); 61 PluginInfoDO pluginInfoDO = validatePluginInfoExists(id);
72 -  
73 - // 停止插件 62 + // 1.2 停止插件
74 if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) { 63 if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
75 throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING); 64 throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING);
76 } 65 }
77 66
78 - // 卸载插件  
79 - PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey());  
80 - if (plugin != null) {  
81 - // 查询插件是否是启动状态  
82 - if (plugin.getPluginState().equals(PluginState.STARTED)) {  
83 - // 停止插件  
84 - pluginManager.stopPlugin(plugin.getPluginId());  
85 - }  
86 - // 卸载插件  
87 - pluginManager.unloadPlugin(plugin.getPluginId());  
88 - } 67 + // 2. 卸载插件
  68 + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDO.getPluginKey());
89 69
90 - // 删除  
91 - pluginInfoMapper.deleteById(id);  
92 - // 删除插件文件  
93 - Executors.newSingleThreadExecutor().submit(() -> {  
94 - try {  
95 - TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕  
96 - File file = new File(pluginsDir, pluginInfoDO.getFileName());  
97 - if (file.exists() && !file.delete()) {  
98 - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());  
99 - }  
100 - } catch (InterruptedException e) {  
101 - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);  
102 - }  
103 - }); 70 + // 3. 删除插件文件
  71 + pluginInstanceService.deletePluginFile(pluginInfoDO);
104 72
  73 + // 4. 删除插件信息
  74 + pluginInfoMapper.deleteById(id);
105 } 75 }
106 76
107 private PluginInfoDO validatePluginInfoExists(Long id) { 77 private PluginInfoDO validatePluginInfoExists(Long id) {
@@ -127,99 +97,37 @@ public class PluginInfoServiceImpl implements PluginInfoService { @@ -127,99 +97,37 @@ public class PluginInfoServiceImpl implements PluginInfoService {
127 // 1. 校验插件信息是否存在 97 // 1. 校验插件信息是否存在
128 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); 98 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
129 99
130 - // 2. 获取插件标识  
131 - String pluginKey = pluginInfoDo.getPluginKey();  
132 -  
133 - // 3. 停止并卸载旧的插件  
134 - stopAndUnloadPlugin(pluginKey); 100 + // 2. 停止并卸载旧的插件
  101 + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey());
135 102
136 - // 4. 上传新的插件文件  
137 - String pluginKeyNew = uploadAndLoadNewPlugin(file); 103 + // 3 上传新的插件文件,更新插件启用状态文件
  104 + String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file);
138 105
139 - // 5. 更新插件启用状态文件  
140 - updatePluginStatusFile(pluginKeyNew, false);  
141 -  
142 - // 6. 更新插件信息 106 + // 4. 更新插件信息
143 updatePluginInfo(pluginInfoDo, pluginKeyNew, file); 107 updatePluginInfo(pluginInfoDo, pluginKeyNew, file);
144 } 108 }
145 109
146 - // 停止并卸载旧的插件  
147 - private void stopAndUnloadPlugin(String pluginKey) {  
148 - PluginWrapper plugin = pluginManager.getPlugin(pluginKey);  
149 - if (plugin != null) {  
150 - if (plugin.getPluginState().equals(PluginState.STARTED)) {  
151 - pluginManager.stopPlugin(pluginKey); // 停止插件  
152 - }  
153 - pluginManager.unloadPlugin(pluginKey); // 卸载插件  
154 - }  
155 - }  
156 -  
157 - // 上传并加载新的插件文件  
158 - private String uploadAndLoadNewPlugin(MultipartFile file) {  
159 - Path pluginsPath = Paths.get(pluginsDir);  
160 - try {  
161 - if (!Files.exists(pluginsPath)) {  
162 - Files.createDirectories(pluginsPath); // 创建插件目录  
163 - }  
164 - String filename = file.getOriginalFilename();  
165 - if (filename != null) {  
166 - Path jarPath = pluginsPath.resolve(filename);  
167 - Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件  
168 - return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件  
169 - } else {  
170 - throw exception(PLUGIN_INSTALL_FAILED);  
171 - }  
172 - } catch (Exception e) {  
173 - throw exception(PLUGIN_INSTALL_FAILED);  
174 - }  
175 - }  
176 -  
177 - // 更新插件状态文件  
178 - private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) {  
179 - Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt");  
180 - Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt");  
181 - Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath;  
182 - Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath;  
183 -  
184 - try {  
185 - PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew);  
186 - if (pluginWrapper == null) {  
187 - throw exception(PLUGIN_INSTALL_FAILED);  
188 - }  
189 - String pluginInfo = pluginKeyNew + "@" + pluginWrapper.getDescriptor().getVersion();  
190 - List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath)  
191 - : new ArrayList<>();  
192 - List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath)  
193 - : new ArrayList<>();  
194 -  
195 - if (!targetLines.contains(pluginInfo)) {  
196 - targetLines.add(pluginInfo);  
197 - Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE,  
198 - StandardOpenOption.TRUNCATE_EXISTING);  
199 - }  
200 -  
201 - if (oppositeLines.contains(pluginInfo)) {  
202 - oppositeLines.remove(pluginInfo);  
203 - Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE,  
204 - StandardOpenOption.TRUNCATE_EXISTING);  
205 - }  
206 - } catch (IOException e) {  
207 - throw exception(PLUGIN_INSTALL_FAILED);  
208 - }  
209 - }  
210 -  
211 - // 更新插件信息 110 + /**
  111 + * 更新插件信息
  112 + *
  113 + * @param pluginInfoDo 插件信息
  114 + * @param pluginKeyNew 插件标识符
  115 + * @param file 文件
  116 + */
212 private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) { 117 private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) {
213 - pluginInfoDo.setPluginKey(pluginKeyNew);  
214 - pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());  
215 - pluginInfoDo.setFileName(file.getOriginalFilename());  
216 - pluginInfoDo.setScript("");  
217 -  
218 - PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor();  
219 - pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription());  
220 - pluginInfoDo.setVersion(pluginDescriptor.getVersion());  
221 - pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());  
222 - pluginInfoMapper.updateById(pluginInfoDo); 118 + // 创建新的插件信息对象并链式设置属性
  119 + PluginInfoDO updatedPluginInfo = new PluginInfoDO()
  120 + .setId(pluginInfoDo.getId())
  121 + .setPluginKey(pluginKeyNew)
  122 + .setStatus(IotPluginStatusEnum.STOPPED.getStatus())
  123 + .setFileName(file.getOriginalFilename())
  124 + .setScript("")
  125 + .setConfigSchema(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription())
  126 + .setVersion(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion())
  127 + .setDescription(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription());
  128 +
  129 + // 执行更新
  130 + pluginInfoMapper.updateById(updatedPluginInfo);
223 } 131 }
224 132
225 @Override 133 @Override
@@ -227,44 +135,24 @@ public class PluginInfoServiceImpl implements PluginInfoService { @@ -227,44 +135,24 @@ public class PluginInfoServiceImpl implements PluginInfoService {
227 // 1. 校验插件信息是否存在 135 // 1. 校验插件信息是否存在
228 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); 136 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
229 137
230 - // 2. 校验插件状态是否有效  
231 - if (!IotPluginStatusEnum.contains(status)) {  
232 - throw exception(PLUGIN_STATUS_INVALID);  
233 - }  
234 -  
235 - // 3. 获取插件标识和插件实例  
236 - String pluginKey = pluginInfoDo.getPluginKey();  
237 - PluginWrapper plugin = pluginManager.getPlugin(pluginKey);  
238 -  
239 - // 4. 根据状态更新插件  
240 - if (plugin != null) {  
241 - // 4.1 如果目标状态是运行且插件未启动,则启动插件  
242 - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())  
243 - && plugin.getPluginState() != PluginState.STARTED) {  
244 - pluginManager.startPlugin(pluginKey);  
245 - updatePluginStatusFile(pluginKey, true); // 更新插件状态文件为启用  
246 - }  
247 - // 4.2 如果目标状态是停止且插件已启动,则停止插件  
248 - else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())  
249 - && plugin.getPluginState() == PluginState.STARTED) {  
250 - pluginManager.stopPlugin(pluginKey);  
251 - updatePluginStatusFile(pluginKey, false); // 更新插件状态文件为禁用  
252 - }  
253 - } else {  
254 - // 5. 插件不存在且状态为停止,抛出异常  
255 - if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {  
256 - throw exception(PLUGIN_STATUS_INVALID);  
257 - }  
258 - } 138 + // 2. 更新插件状态
  139 + pluginInstanceService.updatePluginStatus(pluginInfoDo, status);
259 140
260 - // 6. 更新数据库中的插件状态  
261 - pluginInfoDo.setStatus(status);  
262 - pluginInfoMapper.updateById(pluginInfoDo); 141 + // 3. 更新数据库中的插件状态
  142 + PluginInfoDO updatedPluginInfo = new PluginInfoDO();
  143 + updatedPluginInfo.setId(id);
  144 + updatedPluginInfo.setStatus(status);
  145 + pluginInfoMapper.updateById(updatedPluginInfo);
263 } 146 }
264 147
265 @Override 148 @Override
266 public List<PluginInfoDO> getPluginInfoList() { 149 public List<PluginInfoDO> getPluginInfoList() {
267 - return pluginInfoMapper.selectList(null); 150 + return pluginInfoMapper.selectList();
  151 + }
  152 +
  153 + @Override
  154 + public List<PluginInfoDO> getPluginInfoListByStatus(Integer status) {
  155 + return pluginInfoMapper.selectListByStatus(status);
268 } 156 }
269 157
270 } 158 }
271 \ No newline at end of file 159 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java
1 package cn.iocoder.yudao.module.iot.service.plugin; 1 package cn.iocoder.yudao.module.iot.service.plugin;
2 2
  3 +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
  4 +import org.springframework.web.multipart.MultipartFile;
  5 +
3 /** 6 /**
4 * IoT 插件实例 Service 接口 7 * IoT 插件实例 Service 接口
5 * 8 *
@@ -8,8 +11,38 @@ package cn.iocoder.yudao.module.iot.service.plugin; @@ -8,8 +11,38 @@ package cn.iocoder.yudao.module.iot.service.plugin;
8 public interface PluginInstanceService { 11 public interface PluginInstanceService {
9 12
10 /** 13 /**
11 - * 更新IoT 插件实例 14 + * 上报插件实例(心跳)
  15 + */
  16 + void reportPluginInstances();
  17 +
  18 + /**
  19 + * 停止并卸载插件
  20 + *
  21 + * @param pluginKey 插件标识符
  22 + */
  23 + void stopAndUnloadPlugin(String pluginKey);
  24 +
  25 + /**
  26 + * 删除插件文件
  27 + *
  28 + * @param pluginInfoDo 插件信息
  29 + */
  30 + void deletePluginFile(PluginInfoDO pluginInfoDo);
  31 +
  32 + /**
  33 + * 上传并加载新的插件文件
  34 + *
  35 + * @param file 插件文件
  36 + * @return 插件标识符
  37 + */
  38 + String uploadAndLoadNewPlugin(MultipartFile file);
  39 +
  40 + /**
  41 + * 更新插件状态
  42 + *
  43 + * @param pluginInfoDo 插件信息
  44 + * @param status 新状态
12 */ 45 */
13 - void updatePluginInstances(); 46 + void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status);
14 47
15 } 48 }
16 \ No newline at end of file 49 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java
1 package cn.iocoder.yudao.module.iot.service.plugin; 1 package cn.iocoder.yudao.module.iot.service.plugin;
2 2
  3 +import cn.hutool.core.io.FileUtil;
3 import cn.hutool.core.net.NetUtil; 4 import cn.hutool.core.net.NetUtil;
4 import cn.hutool.core.util.IdUtil; 5 import cn.hutool.core.util.IdUtil;
5 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; 7 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
  8 +import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper;
7 import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper; 9 import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper;
  10 +import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants;
  11 +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
8 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
9 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
  14 +import org.pf4j.PluginState;
10 import org.pf4j.PluginWrapper; 15 import org.pf4j.PluginWrapper;
11 import org.pf4j.spring.SpringPluginManager; 16 import org.pf4j.spring.SpringPluginManager;
12 import org.springframework.beans.factory.annotation.Value; 17 import org.springframework.beans.factory.annotation.Value;
13 import org.springframework.stereotype.Service; 18 import org.springframework.stereotype.Service;
14 import org.springframework.validation.annotation.Validated; 19 import org.springframework.validation.annotation.Validated;
  20 +import org.springframework.web.multipart.MultipartFile;
15 21
  22 +import java.io.File;
  23 +import java.io.IOException;
  24 +import java.nio.file.*;
16 import java.util.List; 25 import java.util.List;
  26 +import java.util.Map;
  27 +import java.util.concurrent.TimeUnit;
  28 +import java.util.function.Function;
  29 +import java.util.stream.Collectors;
  30 +
  31 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
17 32
18 /** 33 /**
19 * IoT 插件实例 Service 实现类 34 * IoT 插件实例 Service 实现类
@@ -25,76 +40,149 @@ import java.util.List; @@ -25,76 +40,149 @@ import java.util.List;
25 @Slf4j 40 @Slf4j
26 public class PluginInstanceServiceImpl implements PluginInstanceService { 41 public class PluginInstanceServiceImpl implements PluginInstanceService {
27 42
28 - /**  
29 - * 主程序id  
30 - */ 43 + // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的
  44 + // 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件;
  45 + // 那就 mac@uuid ?
31 public static final String MAIN_ID = IdUtil.fastSimpleUUID(); 46 public static final String MAIN_ID = IdUtil.fastSimpleUUID();
32 47
33 @Resource 48 @Resource
34 - private PluginInfoService pluginInfoService; 49 + private PluginInfoMapper pluginInfoMapper;
35 @Resource 50 @Resource
36 private PluginInstanceMapper pluginInstanceMapper; 51 private PluginInstanceMapper pluginInstanceMapper;
37 @Resource 52 @Resource
38 private SpringPluginManager pluginManager; 53 private SpringPluginManager pluginManager;
39 54
  55 + @Value("${pf4j.pluginsDir}")
  56 + private String pluginsDir;
40 @Value("${server.port:48080}") 57 @Value("${server.port:48080}")
41 private int port; 58 private int port;
42 59
  60 + @Override
  61 + public void stopAndUnloadPlugin(String pluginKey) {
  62 + PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
  63 + // TODO @haohao:改成 if return 会更简洁一点;
  64 + if (plugin != null) {
  65 + if (plugin.getPluginState().equals(PluginState.STARTED)) {
  66 + pluginManager.stopPlugin(pluginKey); // 停止插件
  67 + log.info("已停止插件: {}", pluginKey);
  68 + }
  69 + pluginManager.unloadPlugin(pluginKey); // 卸载插件
  70 + log.info("已卸载插件: {}", pluginKey);
  71 + } else {
  72 + log.warn("插件不存在或已卸载: {}", pluginKey);
  73 + }
  74 + }
  75 +
  76 + @Override
  77 + public void deletePluginFile(PluginInfoDO pluginInfoDO) {
  78 + File file = new File(pluginsDir, pluginInfoDO.getFileName());
  79 + // TODO @haohao:改成 if return 会更简洁一点;
  80 + if (file.exists()) {
  81 + try {
  82 + TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
  83 + if (!file.delete()) {
  84 + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
  85 + }
  86 + } catch (InterruptedException e) {
  87 + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
  88 + }
  89 + }
  90 + }
  91 +
  92 + @Override
  93 + public String uploadAndLoadNewPlugin(MultipartFile file) {
  94 + String pluginKeyNew;
  95 + // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载
  96 + Path pluginsPath = Paths.get(pluginsDir);
  97 + try {
  98 + FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录
  99 + String filename = file.getOriginalFilename();
  100 + if (filename != null) {
  101 + Path jarPath = pluginsPath.resolve(filename);
  102 + Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
  103 + pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
  104 + log.info("已加载插件: {}", pluginKeyNew);
  105 + } else {
  106 + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED);
  107 + }
  108 + } catch (IOException e) {
  109 + log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e);
  110 + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
  111 + } catch (Exception e) {
  112 + log.error("[uploadAndLoadNewPlugin][加载插件失败]", e);
  113 + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
  114 + }
  115 + return pluginKeyNew;
  116 + }
  117 +
  118 + @Override
  119 + public void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status) {
  120 + String pluginKey = pluginInfoDo.getPluginKey();
  121 + PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
  122 +
  123 + // TODO @haohao:改成 if return 会更简洁一点;
  124 + if (plugin != null) {
  125 + // 启动插件
  126 + if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
  127 + && plugin.getPluginState() != PluginState.STARTED) {
  128 + pluginManager.startPlugin(pluginKey);
  129 + log.info("已启动插件: {}", pluginKey);
  130 + }
  131 + // 停止插件
  132 + else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
  133 + && plugin.getPluginState() == PluginState.STARTED) {
  134 + pluginManager.stopPlugin(pluginKey);
  135 + log.info("已停止插件: {}", pluginKey);
  136 + }
  137 + } else {
  138 + // 插件不存在且状态为停止,抛出异常
  139 + if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
  140 + throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
  141 + }
  142 + }
  143 + }
43 144
44 @Override 145 @Override
45 - public void updatePluginInstances() {  
46 - // 1. 查询 pf4j 插件列表 146 + public void reportPluginInstances() {
  147 + // 1.1 获取 pf4j 插件列表
47 List<PluginWrapper> plugins = pluginManager.getPlugins(); 148 List<PluginWrapper> plugins = pluginManager.getPlugins();
48 149
49 - // 2. 查询插件信息列表  
50 - List<PluginInfoDO> pluginInfos = pluginInfoService.getPluginInfoList(); 150 + // 1.2 获取插件信息列表并转换为 Map 以便快速查找
  151 + List<PluginInfoDO> pluginInfos = pluginInfoMapper.selectList();
  152 + Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream()
  153 + .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity()));
51 154
52 - // 动态获取主程序的 IP 和端口  
53 - String mainIp = getLocalIpAddress(); 155 + // 1.3 获取本机 IP 和 MAC 地址
  156 + String ip = NetUtil.getLocalhostStr();
  157 + String mac = NetUtil.getLocalMacAddress();
  158 + String mainId = MAIN_ID + "-" + mac;
54 159
55 - // 3. 遍历插件列表,并保存为插件实例 160 + // 2. 遍历插件列表,并保存为插件实例
56 for (PluginWrapper plugin : plugins) { 161 for (PluginWrapper plugin : plugins) {
57 String pluginKey = plugin.getPluginId(); 162 String pluginKey = plugin.getPluginId();
58 - PluginInfoDO pluginInfo = pluginInfos.stream()  
59 - .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey))  
60 - .findFirst()  
61 - .orElse(null);  
62 163
63 - // 4. 如果插件信息不存在,则跳过 164 + // 2.1 查找插件信息
  165 + PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey);
64 if (pluginInfo == null) { 166 if (pluginInfo == null) {
  167 + log.error("插件信息不存在,pluginKey = {}", pluginKey);
65 continue; 168 continue;
66 } 169 }
67 170
68 - // 5. 查询插件实例  
69 - PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId());  
70 -  
71 - // 6. 如果插件实例不存在,则创建 171 + // 2.2 情况一:如果插件实例不存在,则创建
  172 + PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId,
  173 + pluginInfo.getId());
72 if (pluginInstance == null) { 174 if (pluginInstance == null) {
73 - pluginInstance = new PluginInstanceDO();  
74 - pluginInstance.setPluginId(pluginInfo.getId());  
75 - pluginInstance.setMainId(MAIN_ID);  
76 - pluginInstance.setIp(mainIp);  
77 - pluginInstance.setPort(port);  
78 - pluginInstance.setHeartbeatAt(System.currentTimeMillis()); 175 + // 4.4 如果插件实例不存在,则创建
  176 + pluginInstance = PluginInstanceDO.builder().pluginId(pluginInfo.getId()).mainId(MAIN_ID + "-" + mac)
  177 + .ip(ip).port(port).heartbeatAt(System.currentTimeMillis()).build();
79 pluginInstanceMapper.insert(pluginInstance); 178 pluginInstanceMapper.insert(pluginInstance);
80 } else { 179 } else {
81 - // 7. 如果插件实例存在,则更新 180 + // 2.2 情况二:如果存在,则更新 heartbeatAt
  181 + // TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有)
82 pluginInstance.setHeartbeatAt(System.currentTimeMillis()); 182 pluginInstance.setHeartbeatAt(System.currentTimeMillis());
83 pluginInstanceMapper.updateById(pluginInstance); 183 pluginInstanceMapper.updateById(pluginInstance);
84 } 184 }
85 } 185 }
86 } 186 }
87 187
88 - private String getLocalIpAddress() {  
89 - try {  
90 - List<String> ipList = NetUtil.localIpv4s().stream()  
91 - .filter(ip -> !ip.startsWith("0.0") && !ip.startsWith("127.") && !ip.startsWith("169.254") && !ip.startsWith("255.255.255.255"))  
92 - .toList();  
93 - return ipList.isEmpty() ? "127.0.0.1" : ipList.get(0);  
94 - } catch (Exception e) {  
95 - log.error("获取本地IP地址失败", e);  
96 - return "127.0.0.1"; // 默认值  
97 - }  
98 - }  
99 -  
100 } 188 }
101 \ No newline at end of file 189 \ No newline at end of file
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; @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
9 import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper; 9 import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
10 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; 10 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
11 import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService; 11 import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
12 -import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;  
13 import com.baomidou.dynamic.datasource.annotation.DSTransactional; 12 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
14 import jakarta.annotation.Resource; 13 import jakarta.annotation.Resource;
15 import org.springframework.context.annotation.Lazy; 14 import org.springframework.context.annotation.Lazy;
@@ -116,14 +115,15 @@ public class IotProductServiceImpl implements IotProductService { @@ -116,14 +115,15 @@ public class IotProductServiceImpl implements IotProductService {
116 public void updateProductStatus(Long id, Integer status) { 115 public void updateProductStatus(Long id, Integer status) {
117 // 1. 校验存在 116 // 1. 校验存在
118 validateProductExists(id); 117 validateProductExists(id);
119 - // 2. 更新  
120 - IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build();  
121 - // 3. 产品是发布状态 118 +
  119 + // 2. 产品是发布状态
122 if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) { 120 if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) {
123 - // 3.1 创建产品超级表数据模型 121 + // 创建产品超级表数据模型
124 devicePropertyDataService.defineDevicePropertyData(id); 122 devicePropertyDataService.defineDevicePropertyData(id);
125 -  
126 } 123 }
  124 +
  125 + // 3. 更新
  126 + IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build();
127 productMapper.updateById(updateObj); 127 productMapper.updateById(updateObj);
128 } 128 }
129 129
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; @@ -7,20 +7,20 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO; 7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
9 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO; 9 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
10 -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.*; 10 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
  12 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
  13 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
11 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; 14 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
12 -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;  
13 import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO; 15 import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
14 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper; 16 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
15 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper; 17 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
16 -import cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper;  
17 import cn.iocoder.yudao.module.iot.enums.IotConstants; 18 import cn.iocoder.yudao.module.iot.enums.IotConstants;
18 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; 19 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
19 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; 20 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 21 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
21 -import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;  
22 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 22 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
23 -import cn.iocoder.yudao.module.iot.util.IotTdDatabaseUtils; 23 +import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
24 import jakarta.annotation.Resource; 24 import jakarta.annotation.Resource;
25 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
26 import org.springframework.beans.factory.annotation.Value; 26 import org.springframework.beans.factory.annotation.Value;
@@ -62,12 +62,8 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ @@ -62,12 +62,8 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
62 private TdEngineDMLMapper tdEngineDMLMapper; 62 private TdEngineDMLMapper tdEngineDMLMapper;
63 63
64 @Resource 64 @Resource
65 - private TdThingModelMessageMapper tdThingModelMessageMapper;  
66 -  
67 - @Resource  
68 private DeviceDataRedisDAO deviceDataRedisDAO; 65 private DeviceDataRedisDAO deviceDataRedisDAO;
69 -  
70 - 66 +
71 // TODO @haohao:这个方法,可以考虑加下 1. 2. 3. 更有层次感 67 // TODO @haohao:这个方法,可以考虑加下 1. 2. 3. 更有层次感
72 @Override 68 @Override
73 @TenantIgnore 69 @TenantIgnore
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogDataMapper.xml
@@ -6,20 +6,20 @@ @@ -6,20 +6,20 @@
6 6
7 <!-- 创建设备日志超级表 初始化只创建一次--> 7 <!-- 创建设备日志超级表 初始化只创建一次-->
8 <update id="createDeviceLogSTable"> 8 <update id="createDeviceLogSTable">
9 - CREATE STABLE device_log(  
10 - ts TIMESTAMP,  
11 - id NCHAR(50),  
12 - product_key NCHAR(50),  
13 - type NCHAR(50),  
14 - subType NCHAR(50),  
15 - content NCHAR(1024),  
16 - report_time TIMESTAMP  
17 - )TAGS (  
18 - device_key NCHAR(50)  
19 - ) 9 + CREATE STABLE device_log (
  10 + ts TIMESTAMP,
  11 + id NCHAR(50),
  12 + product_key NCHAR(50),
  13 + type NCHAR(50),
  14 + <!-- TODO @super:下划线 sub_type -->
  15 + subType NCHAR(50),
  16 + content NCHAR(1024),
  17 + report_time TIMESTAMP
  18 + ) TAGS (
  19 + device_key NCHAR(50)
  20 + )
20 </update> 21 </update>
21 22
22 -  
23 <!-- 创建设备日志子表 讨论:TDengine 在子表不存在的情况下 可在数据插入时 自动建表 要不要去掉创建子表的逻辑 由第一次插入数据时自动创建--> 23 <!-- 创建设备日志子表 讨论:TDengine 在子表不存在的情况下 可在数据插入时 自动建表 要不要去掉创建子表的逻辑 由第一次插入数据时自动创建-->
24 <update id="createDeviceLogTable"> 24 <update id="createDeviceLogTable">
25 CREATE TABLE device_log_${deviceKey} USING device_log TAGS('${deviceKey}') 25 CREATE TABLE device_log_${deviceKey} USING device_log TAGS('${deviceKey}')
@@ -41,4 +41,38 @@ @@ -41,4 +41,38 @@
41 ) 41 )
42 </insert> 42 </insert>
43 43
  44 + <select id="selectPage" resultType="cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO">
  45 + SELECT ts, id, device_key, product_key, type, subType, content, report_time
  46 + FROM device_log_${reqVO.deviceKey}
  47 + <where>
  48 + <if test="reqVO.type != null and reqVO.type != ''">
  49 + AND type = #{reqVO.type}
  50 + </if>
  51 + <if test="reqVO.subType != null and reqVO.subType != ''">
  52 + AND subType = #{reqVO.subType}
  53 + </if>
  54 + <if test="reqVO.createTime != null">
  55 + AND ts BETWEEN #{reqVO.createTime[0]} AND #{reqVO.createTime[1]}
  56 + </if>
  57 + </where>
  58 + ORDER BY ts DESC
  59 + LIMIT #{reqVO.pageSize} OFFSET #{reqVO.pageNo}
  60 + </select>
  61 +
  62 + <select id="selectCount" resultType="Long">
  63 + SELECT COUNT(*)
  64 + FROM device_log_${reqVO.deviceKey}
  65 + <where>
  66 + <if test="reqVO.type != null and reqVO.type != ''">
  67 + AND type = #{reqVO.type}
  68 + </if>
  69 + <if test="reqVO.subType != null and reqVO.subType != ''">
  70 + AND subType = #{reqVO.subType}
  71 + </if>
  72 + <if test="reqVO.createTime != null">
  73 + AND ts BETWEEN #{reqVO.createTime[0]} AND #{reqVO.createTime[1]}
  74 + </if>
  75 + </where>
  76 + </select>
  77 +
44 </mapper> 78 </mapper>
45 \ No newline at end of file 79 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugin/pom.xml
@@ -2,11 +2,6 @@ @@ -2,11 +2,6 @@
2 <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns="http://maven.apache.org/POM/4.0.0"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 -<!-- <modelVersion>4.0.0</modelVersion>-->  
6 -<!-- <groupId>cn.iocoder.boot</groupId>-->  
7 -<!-- <artifactId>yudao-module-iot-plugin</artifactId>-->  
8 -<!-- <version>0.0.1</version>-->  
9 -<!-- <packaging>pom</packaging>-->  
10 <parent> 5 <parent>
11 <artifactId>yudao-module-iot</artifactId> 6 <artifactId>yudao-module-iot</artifactId>
12 <groupId>cn.iocoder.boot</groupId> 7 <groupId>cn.iocoder.boot</groupId>
@@ -15,6 +10,7 @@ @@ -15,6 +10,7 @@
15 <modules> 10 <modules>
16 <module>yudao-module-iot-demo-plugin</module> 11 <module>yudao-module-iot-demo-plugin</module>
17 <module>yudao-module-iot-http-plugin</module> 12 <module>yudao-module-iot-http-plugin</module>
  13 + <module>yudao-module-iot-mqtt-plugin</module>
18 </modules> 14 </modules>
19 15
20 <modelVersion>4.0.0</modelVersion> 16 <modelVersion>4.0.0</modelVersion>
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/dependency-reduced-pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3 + <parent>
  4 + <artifactId>yudao-module-iot-plugin</artifactId>
  5 + <groupId>cn.iocoder.boot</groupId>
  6 + <version>2.2.0-snapshot</version>
  7 + </parent>
  8 + <modelVersion>4.0.0</modelVersion>
  9 + <artifactId>yudao-module-iot-http-plugin</artifactId>
  10 + <name>${project.artifactId}</name>
  11 + <version>2.2.0-snapshot</version>
  12 + <description>物联网 插件模块 - http 插件</description>
  13 + <build>
  14 + <plugins>
  15 + <plugin>
  16 + <artifactId>maven-jar-plugin</artifactId>
  17 + <version>2.4</version>
  18 + <configuration>
  19 + <archive>
  20 + <manifestEntries>
  21 + <Plugin-Id>${plugin.id}</Plugin-Id>
  22 + <Plugin-Class>${plugin.class}</Plugin-Class>
  23 + <Plugin-Version>${plugin.version}</Plugin-Version>
  24 + <Plugin-Provider>${plugin.provider}</Plugin-Provider>
  25 + <Plugin-Description>${plugin.description}</Plugin-Description>
  26 + <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
  27 + </manifestEntries>
  28 + </archive>
  29 + </configuration>
  30 + </plugin>
  31 + <plugin>
  32 + <artifactId>maven-deploy-plugin</artifactId>
  33 + <configuration>
  34 + <skip>true</skip>
  35 + </configuration>
  36 + </plugin>
  37 + <plugin>
  38 + <artifactId>maven-shade-plugin</artifactId>
  39 + <version>3.4.1</version>
  40 + <executions>
  41 + <execution>
  42 + <phase>package</phase>
  43 + <goals>
  44 + <goal>shade</goal>
  45 + </goals>
  46 + <configuration>
  47 + <shadedArtifactAttached>true</shadedArtifactAttached>
  48 + <shadedClassifierName>shaded</shadedClassifierName>
  49 + <transformers>
  50 + <transformer>
  51 + <mainClass>cn.iocoder.yudao.module.iot.HttpPluginSpringbootApplication</mainClass>
  52 + </transformer>
  53 + </transformers>
  54 + </configuration>
  55 + </execution>
  56 + </executions>
  57 + </plugin>
  58 + </plugins>
  59 + </build>
  60 + <dependencies>
  61 + <dependency>
  62 + <groupId>org.pf4j</groupId>
  63 + <artifactId>pf4j-spring</artifactId>
  64 + <version>0.9.0</version>
  65 + <scope>provided</scope>
  66 + </dependency>
  67 + <dependency>
  68 + <groupId>org.projectlombok</groupId>
  69 + <artifactId>lombok</artifactId>
  70 + <version>1.18.34</version>
  71 + <scope>provided</scope>
  72 + </dependency>
  73 + </dependencies>
  74 + <properties>
  75 + <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin</plugin.class>
  76 + <plugin.version>0.0.1</plugin.version>
  77 + <plugin.id>http-plugin</plugin.id>
  78 + <plugin.description>http-plugin-0.0.1</plugin.description>
  79 + <plugin.provider>ahh</plugin.provider>
  80 + </properties>
  81 +</project>
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties
1 plugin.id=http-plugin 1 plugin.id=http-plugin
2 -plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpPlugin 2 +plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin
3 plugin.version=0.0.1 3 plugin.version=0.0.1
4 plugin.provider=ahh 4 plugin.provider=ahh
5 plugin.dependencies= 5 plugin.dependencies=
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 <properties> 21 <properties>
22 <!-- 插件相关 --> 22 <!-- 插件相关 -->
23 <plugin.id>http-plugin</plugin.id> 23 <plugin.id>http-plugin</plugin.id>
24 - <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpPlugin</plugin.class> 24 + <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpVertxPlugin</plugin.class>
25 <plugin.version>0.0.1</plugin.version> 25 <plugin.version>0.0.1</plugin.version>
26 <plugin.provider>ahh</plugin.provider> 26 <plugin.provider>ahh</plugin.provider>
27 <plugin.description>http-plugin-0.0.1</plugin.description> 27 <plugin.description>http-plugin-0.0.1</plugin.description>
@@ -30,27 +30,6 @@ @@ -30,27 +30,6 @@
30 30
31 <build> 31 <build>
32 <plugins> 32 <plugins>
33 - <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)  
34 - <plugin>  
35 - <groupId>org.codehaus.mojo</groupId>  
36 - <artifactId>properties-maven-plugin</artifactId>  
37 - <version>1.0-alpha-2</version>  
38 - <executions>  
39 - <execution>  
40 - <phase>initialize</phase>  
41 - <goals>  
42 - <goal>read-project-properties</goal>  
43 - </goals>  
44 - <configuration>  
45 - <files>  
46 - <file>plugin.properties</file>  
47 - </files>  
48 - </configuration>  
49 - </execution>  
50 - </executions>  
51 - </plugin>  
52 - -->  
53 -  
54 <plugin> 33 <plugin>
55 <groupId>org.apache.maven.plugins</groupId> 34 <groupId>org.apache.maven.plugins</groupId>
56 <artifactId>maven-antrun-plugin</artifactId> 35 <artifactId>maven-antrun-plugin</artifactId>
@@ -118,6 +97,29 @@ @@ -118,6 +97,29 @@
118 <skip>true</skip> 97 <skip>true</skip>
119 </configuration> 98 </configuration>
120 </plugin> 99 </plugin>
  100 +
  101 + <!-- <plugin>-->
  102 + <!-- <groupId>org.apache.maven.plugins</groupId>-->
  103 + <!-- <artifactId>maven-shade-plugin</artifactId>-->
  104 + <!-- <version>3.4.1</version>-->
  105 + <!-- <executions>-->
  106 + <!-- <execution>-->
  107 + <!-- <phase>package</phase>-->
  108 + <!-- <goals>-->
  109 + <!-- <goal>shade</goal>-->
  110 + <!-- </goals>-->
  111 + <!-- <configuration>-->
  112 + <!-- <shadedArtifactAttached>true</shadedArtifactAttached>-->
  113 + <!-- <shadedClassifierName>shaded</shadedClassifierName>-->
  114 + <!-- <transformers>-->
  115 + <!-- <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">-->
  116 + <!-- <mainClass>cn.iocoder.yudao.module.iot.HttpPluginSpringbootApplication</mainClass>-->
  117 + <!-- </transformer>-->
  118 + <!-- </transformers>-->
  119 + <!-- </configuration>-->
  120 + <!-- </execution>-->
  121 + <!-- </executions>-->
  122 + <!-- </plugin>-->
121 </plugins> 123 </plugins>
122 </build> 124 </build>
123 125
@@ -145,10 +147,20 @@ @@ -145,10 +147,20 @@
145 <version>${lombok.version}</version> 147 <version>${lombok.version}</version>
146 <scope>provided</scope> 148 <scope>provided</scope>
147 </dependency> 149 </dependency>
  150 + <!-- Vert.x 核心依赖 -->
  151 + <dependency>
  152 + <groupId>io.vertx</groupId>
  153 + <artifactId>vertx-core</artifactId>
  154 + </dependency>
  155 + <!-- Vert.x Web 模块 -->
  156 + <dependency>
  157 + <groupId>io.vertx</groupId>
  158 + <artifactId>vertx-web</artifactId>
  159 + </dependency>
  160 + <!-- MQTT -->
148 <dependency> 161 <dependency>
149 - <groupId>io.netty</groupId>  
150 - <artifactId>netty-all</artifactId>  
151 - <version>4.1.63.Final</version> <!-- 版本可根据需要调整 --> 162 + <groupId>org.eclipse.paho</groupId>
  163 + <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
152 </dependency> 164 </dependency>
153 </dependencies> 165 </dependencies>
154 </project> 166 </project>
155 \ No newline at end of file 167 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/HttpPluginSpringbootApplication.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot;
  2 +
  3 +import org.springframework.boot.SpringApplication;
  4 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  5 +
  6 +@SpringBootApplication
  7 +public class HttpPluginSpringbootApplication {
  8 + public static void main(String[] args) {
  9 + SpringApplication.run(HttpPluginSpringbootApplication.class, args);
  10 + }
  11 +}
0 \ No newline at end of file 12 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java 0 → 100644
  1 +
  2 +package cn.iocoder.yudao.module.iot.controller;
  3 +
  4 +import cn.iocoder.yudao.module.iot.mqttrpc.client.RpcClient;
  5 +import lombok.RequiredArgsConstructor;
  6 +import org.springframework.web.bind.annotation.PostMapping;
  7 +import org.springframework.web.bind.annotation.RequestMapping;
  8 +import org.springframework.web.bind.annotation.RequestParam;
  9 +import org.springframework.web.bind.annotation.RestController;
  10 +
  11 +import javax.annotation.Resource;
  12 +import java.util.concurrent.CompletableFuture;
  13 +
  14 +// TODO 芋艿:后续 review 下
  15 +/**
  16 + * 插件实例 RPC 接口
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@RestController
  21 +@RequestMapping("/rpc")
  22 +@RequiredArgsConstructor
  23 +public class RpcController {
  24 +
  25 + @Resource
  26 + private RpcClient rpcClient;
  27 +
  28 + @PostMapping("/add")
  29 + public CompletableFuture<Object> add(@RequestParam int a, @RequestParam int b) throws Exception {
  30 + return rpcClient.call("add", new Object[]{a, b}, 10);
  31 + }
  32 +
  33 + @PostMapping("/concat")
  34 + public CompletableFuture<Object> concat(@RequestParam String str1, @RequestParam String str2) throws Exception {
  35 + return rpcClient.call("concat", new Object[]{str1, str2}, 10);
  36 + }
  37 +
  38 +}
0 \ No newline at end of file 39 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/client/RpcClient.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mqttrpc.client;
  2 +
  3 +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcRequest;
  4 +import cn.iocoder.yudao.module.iot.mqttrpc.common.RpcResponse;
  5 +import cn.iocoder.yudao.module.iot.mqttrpc.common.SerializationUtils;
  6 +import cn.iocoder.yudao.module.iot.mqttrpc.config.MqttConfig;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.eclipse.paho.client.mqttv3.MqttClient;
  9 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  10 +import org.eclipse.paho.client.mqttv3.MqttException;
  11 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  12 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
  13 +import org.springframework.stereotype.Service;
  14 +
  15 +import javax.annotation.PostConstruct;
  16 +import javax.annotation.PreDestroy;
  17 +import java.util.UUID;
  18 +import java.util.concurrent.*;
  19 +
  20 +// TODO @芋艿:需要考虑,怎么公用!
  21 +@Service
  22 +@Slf4j
  23 +public class RpcClient {
  24 +
  25 + private final MqttConfig mqttConfig;
  26 + private final MqttClient mqttClient;
  27 + private final ConcurrentMap<String, CompletableFuture<RpcResponse>> pendingRequests = new ConcurrentHashMap<>();
  28 + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
  29 +
  30 + public RpcClient(MqttConfig mqttConfig) throws MqttException {
  31 + this.mqttConfig = mqttConfig;
  32 + this.mqttClient = new MqttClient(mqttConfig.getBroker(), mqttConfig.getClientId(), new MemoryPersistence());
  33 + MqttConnectOptions options = new MqttConnectOptions();
  34 + options.setAutomaticReconnect(true);
  35 + options.setCleanSession(true);
  36 + options.setUserName(mqttConfig.getUsername());
  37 + options.setPassword(mqttConfig.getPassword().toCharArray());
  38 + this.mqttClient.connect(options);
  39 + }
  40 +
  41 + @PostConstruct
  42 + public void init() throws MqttException {
  43 + mqttClient.subscribe(mqttConfig.getResponseTopicPrefix() + "#", this::handleResponse);
  44 + log.info("RPC Client subscribed to topics: {}", mqttConfig.getResponseTopicPrefix() + "#");
  45 + }
  46 +
  47 + private void handleResponse(String topic, MqttMessage message) {
  48 + String correlationId = topic.substring(mqttConfig.getResponseTopicPrefix().length());
  49 + RpcResponse response = SerializationUtils.deserialize(new String(message.getPayload()), RpcResponse.class);
  50 + CompletableFuture<RpcResponse> future = pendingRequests.remove(correlationId);
  51 + if (future != null) {
  52 + if (response.getError() != null) {
  53 + future.completeExceptionally(new RuntimeException(response.getError()));
  54 + } else {
  55 + future.complete(response);
  56 + }
  57 + } else {
  58 + log.warn("Received response for unknown correlationId: {}", correlationId);
  59 + }
  60 + }
  61 +
  62 + public CompletableFuture<Object> call(String method, Object[] params, int timeoutSeconds) throws MqttException {
  63 + String correlationId = UUID.randomUUID().toString();
  64 + String replyTo = mqttConfig.getResponseTopicPrefix() + correlationId;
  65 +
  66 + RpcRequest request = new RpcRequest(method, params, correlationId, replyTo);
  67 + String payload = SerializationUtils.serialize(request);
  68 + MqttMessage message = new MqttMessage(payload.getBytes());
  69 + message.setQos(1);
  70 + mqttClient.publish(mqttConfig.getRequestTopic(), message);
  71 +
  72 + CompletableFuture<RpcResponse> futureResponse = new CompletableFuture<>();
  73 + pendingRequests.put(correlationId, futureResponse);
  74 +
  75 + // 设置超时
  76 + scheduler.schedule(() -> {
  77 + CompletableFuture<RpcResponse> removed = pendingRequests.remove(correlationId);
  78 + if (removed != null) {
  79 + removed.completeExceptionally(new TimeoutException("RPC call timed out"));
  80 + }
  81 + }, timeoutSeconds, TimeUnit.SECONDS);
  82 +
  83 + // 返回最终的结果
  84 + return futureResponse.thenApply(RpcResponse::getResult);
  85 + }
  86 +
  87 + @PreDestroy
  88 + public void cleanup() throws MqttException {
  89 + mqttClient.disconnect();
  90 + scheduler.shutdown();
  91 + log.info("RPC Client disconnected");
  92 + }
  93 +}
0 \ No newline at end of file 94 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/mqttrpc/config/MqttConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mqttrpc.config;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.context.annotation.Configuration;
  6 +
  7 +@Data
  8 +@Configuration
  9 +@ConfigurationProperties(prefix = "mqtt")
  10 +public class MqttConfig {
  11 +
  12 + /**
  13 + * MQTT 代理地址
  14 + */
  15 + private String broker;
  16 +
  17 + /**
  18 + * MQTT 用户名
  19 + */
  20 + private String username;
  21 +
  22 + /**
  23 + * MQTT 密码
  24 + */
  25 + private String password;
  26 +
  27 + /**
  28 + * MQTT 客户端 ID
  29 + */
  30 + private String clientId;
  31 +
  32 + /**
  33 + * MQTT 请求主题
  34 + */
  35 + private String requestTopic;
  36 +
  37 + /**
  38 + * MQTT 响应主题前缀
  39 + */
  40 + private String responseTopicPrefix;
  41 +}
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java renamed to yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxHandler.java
@@ -3,63 +3,30 @@ package cn.iocoder.yudao.module.iot.plugin; @@ -3,63 +3,30 @@ package cn.iocoder.yudao.module.iot.plugin;
3 import cn.hutool.json.JSONObject; 3 import cn.hutool.json.JSONObject;
4 import cn.hutool.json.JSONUtil; 4 import cn.hutool.json.JSONUtil;
5 import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; 5 import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
6 -import io.netty.buffer.Unpooled;  
7 -import io.netty.channel.ChannelFutureListener;  
8 -import io.netty.channel.ChannelHandlerContext;  
9 -import io.netty.channel.SimpleChannelInboundHandler;  
10 -import io.netty.handler.codec.http.*;  
11 -import io.netty.util.CharsetUtil; 6 +import cn.iocoder.yudao.module.iot.api.device.dto.DeviceDataCreateReqDTO;
  7 +import io.vertx.core.Handler;
  8 +import io.vertx.ext.web.RequestBody;
  9 +import io.vertx.ext.web.RoutingContext;
  10 +import lombok.extern.slf4j.Slf4j;
12 11
13 -/**  
14 - * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。  
15 - *  
16 - * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post  
17 - * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段  
18 - */  
19 -public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { 12 +@Slf4j
  13 +public class HttpVertxHandler implements Handler<RoutingContext> {
20 14
21 private final DeviceDataApi deviceDataApi; 15 private final DeviceDataApi deviceDataApi;
22 16
23 - public HttpHandler(DeviceDataApi deviceDataApi) { 17 + public HttpVertxHandler(DeviceDataApi deviceDataApi) {
24 this.deviceDataApi = deviceDataApi; 18 this.deviceDataApi = deviceDataApi;
25 } 19 }
26 20
27 @Override 21 @Override
28 - protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {  
29 - // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post  
30 - // 使用 "/" 拆分路径  
31 - String uri = request.uri();  
32 - String[] parts = uri.split("/"); 22 + public void handle(RoutingContext ctx) {
  23 + String productKey = ctx.pathParam("productKey");
  24 + String deviceName = ctx.pathParam("deviceName");
  25 + RequestBody requestBody = ctx.body();
33 26
34 - /*  
35 - 拆分结果示例:  
36 - parts[0] = ""  
37 - parts[1] = "sys"  
38 - parts[2] = productKey  
39 - parts[3] = deviceName  
40 - parts[4] = "thing"  
41 - parts[5] = "event"  
42 - parts[6] = "property"  
43 - parts[7] = "post"  
44 - */  
45 - boolean isCorrectPath = parts.length == 8  
46 - && "sys".equals(parts[1])  
47 - && "thing".equals(parts[4])  
48 - && "event".equals(parts[5])  
49 - && "property".equals(parts[6])  
50 - && "post".equals(parts[7]);  
51 - if (!isCorrectPath) {  
52 - writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found");  
53 - return;  
54 - }  
55 - String productKey = parts[2];  
56 - String deviceName = parts[3];  
57 -  
58 - // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象  
59 - String requestBody = request.content().toString(CharsetUtil.UTF_8);  
60 JSONObject jsonData; 27 JSONObject jsonData;
61 try { 28 try {
62 - jsonData = JSONUtil.parseObj(requestBody); 29 + jsonData = JSONUtil.parseObj(requestBody.asJsonObject());
63 } catch (Exception e) { 30 } catch (Exception e) {
64 JSONObject res = createResponseJson( 31 JSONObject res = createResponseJson(
65 400, 32 400,
@@ -67,16 +34,24 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -67,16 +34,24 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
67 null, 34 null,
68 "请求数据不是合法的 JSON 格式: " + e.getMessage(), 35 "请求数据不是合法的 JSON 格式: " + e.getMessage(),
69 "thing.event.property.post", 36 "thing.event.property.post",
70 - "1.0"  
71 - );  
72 - writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString()); 37 + "1.0");
  38 + ctx.response()
  39 + .setStatusCode(400)
  40 + .putHeader("Content-Type", "application/json; charset=UTF-8")
  41 + .end(res.toString());
73 return; 42 return;
74 } 43 }
  44 +
75 String id = jsonData.getStr("id", null); 45 String id = jsonData.getStr("id", null);
76 46
77 try { 47 try {
78 // 调用主程序的接口保存数据 48 // 调用主程序的接口保存数据
79 - deviceDataApi.saveDeviceData(productKey, deviceName, jsonData.toString()); 49 + DeviceDataCreateReqDTO createDTO = DeviceDataCreateReqDTO.builder()
  50 + .productKey(productKey)
  51 + .deviceName(deviceName)
  52 + .message(jsonData.toString())
  53 + .build();
  54 + deviceDataApi.saveDeviceData(createDTO);
80 55
81 // 构造成功响应内容 56 // 构造成功响应内容
82 JSONObject successRes = createResponseJson( 57 JSONObject successRes = createResponseJson(
@@ -85,9 +60,11 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -85,9 +60,11 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
85 id, 60 id,
86 "success", 61 "success",
87 "thing.event.property.post", 62 "thing.event.property.post",
88 - "1.0"  
89 - );  
90 - writeResponse(ctx, HttpResponseStatus.OK, successRes.toString()); 63 + "1.0");
  64 + ctx.response()
  65 + .setStatusCode(200)
  66 + .putHeader("Content-Type", "application/json; charset=UTF-8")
  67 + .end(successRes.toString());
91 } catch (Exception e) { 68 } catch (Exception e) {
92 JSONObject errorRes = createResponseJson( 69 JSONObject errorRes = createResponseJson(
93 500, 70 500,
@@ -95,9 +72,11 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -95,9 +72,11 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
95 id, 72 id,
96 "The format of result is error!", 73 "The format of result is error!",
97 "thing.event.property.post", 74 "thing.event.property.post",
98 - "1.0"  
99 - );  
100 - writeResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorRes.toString()); 75 + "1.0");
  76 + ctx.response()
  77 + .setStatusCode(500)
  78 + .putHeader("Content-Type", "application/json; charset=UTF-8")
  79 + .end(errorRes.toString());
101 } 80 }
102 } 81 }
103 82
@@ -112,7 +91,8 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -112,7 +91,8 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
112 * @param version 返回的版本号 91 * @param version 返回的版本号
113 * @return 构造好的 JSON 对象 92 * @return 构造好的 JSON 对象
114 */ 93 */
115 - private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) { 94 + private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method,
  95 + String version) {
116 JSONObject res = new JSONObject(); 96 JSONObject res = new JSONObject();
117 res.set("code", code); 97 res.set("code", code);
118 res.set("data", data != null ? data : new JSONObject()); 98 res.set("data", data != null ? data : new JSONObject());
@@ -122,26 +102,4 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -122,26 +102,4 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
122 res.set("version", version); 102 res.set("version", version);
123 return res; 103 return res;
124 } 104 }
125 -  
126 - /**  
127 - * 向客户端返回 HTTP 响应的辅助方法  
128 - *  
129 - * @param ctx 通道上下文  
130 - * @param status HTTP 响应状态码(网络层面的)  
131 - * @param content 响应内容(JSON 字符串或其他文本)  
132 - */  
133 - private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) {  
134 - // 设置响应头为 JSON 类型和正确的编码  
135 - FullHttpResponse response = new DefaultFullHttpResponse(  
136 - HttpVersion.HTTP_1_1,  
137 - status,  
138 - Unpooled.copiedBuffer(content, CharsetUtil.UTF_8)  
139 - );  
140 - response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");  
141 - response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());  
142 -  
143 - // 发送响应并在发送完成后关闭连接  
144 - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
145 - }  
146 -  
147 -}  
148 \ No newline at end of file 105 \ No newline at end of file
  106 +}
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin;
  2 +
  3 +import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
  4 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
  5 +import io.vertx.core.Vertx;
  6 +import io.vertx.ext.web.Router;
  7 +import io.vertx.ext.web.handler.BodyHandler;
  8 +import org.pf4j.PluginWrapper;
  9 +import org.pf4j.spring.SpringPlugin;
  10 +import org.springframework.context.ApplicationContext;
  11 +import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  12 +
  13 +import lombok.extern.slf4j.Slf4j;
  14 +
  15 +@Slf4j
  16 +public class HttpVertxPlugin extends SpringPlugin {
  17 +
  18 + private static final int PORT = 8092;
  19 + private Vertx vertx;
  20 + private DeviceDataApi deviceDataApi;
  21 +
  22 + public HttpVertxPlugin(PluginWrapper wrapper) {
  23 + super(wrapper);
  24 + }
  25 +
  26 + @Override
  27 + public void start() {
  28 + log.info("HttpVertxPlugin.start()");
  29 +
  30 + // 获取 DeviceDataApi 实例
  31 + deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
  32 + if (deviceDataApi == null) {
  33 + log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");
  34 + return;
  35 + }
  36 +
  37 + // 初始化 Vert.x
  38 + vertx = Vertx.vertx();
  39 + Router router = Router.router(vertx);
  40 +
  41 + // 处理 Body
  42 + router.route().handler(BodyHandler.create());
  43 +
  44 + // 设置路由
  45 + router.post("/sys/:productKey/:deviceName/thing/event/property/post")
  46 + .handler(new HttpVertxHandler(deviceDataApi));
  47 +
  48 + // 启动 HTTP 服务器
  49 + vertx.createHttpServer()
  50 + .requestHandler(router)
  51 + .listen(PORT, http -> {
  52 + if (http.succeeded()) {
  53 + log.info("HTTP 服务器启动成功,端口为: {}", PORT);
  54 + } else {
  55 + log.error("HTTP 服务器启动失败", http.cause());
  56 + }
  57 + });
  58 + }
  59 +
  60 + @Override
  61 + public void stop() {
  62 + log.info("HttpVertxPlugin.stop()");
  63 + if (vertx != null) {
  64 + vertx.close(ar -> {
  65 + if (ar.succeeded()) {
  66 + log.info("Vert.x 关闭成功");
  67 + } else {
  68 + log.error("Vert.x 关闭失败", ar.cause());
  69 + }
  70 + });
  71 + }
  72 + }
  73 +
  74 + @Override
  75 + protected ApplicationContext createApplicationContext() {
  76 + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
  77 + applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
  78 + applicationContext.refresh();
  79 +
  80 + return applicationContext;
  81 + }
  82 +}
0 \ No newline at end of file 83 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/resources/application.yml 0 → 100644
  1 +server:
  2 + port: 8092
  3 +
  4 +spring:
  5 + application:
  6 + name: yudao-module-iot-http-plugin
  7 +
  8 +# MQTT-RPC 配置
  9 +mqtt:
  10 + broker: tcp://chaojiniu.top:1883
  11 + username: haohao
  12 + password: ahh@123456
  13 + clientId: mqtt-rpc-client-${random.int}
  14 + requestTopic: rpc/request
  15 + responseTopicPrefix: rpc/response/
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties 0 → 100644
  1 +plugin.id=mqtt-plugin
  2 +plugin.class=cn.iocoder.yudao.module.iot.plugin.MqttPlugin
  3 +plugin.version=0.0.1
  4 +plugin.provider=ahh
  5 +plugin.dependencies=
  6 +plugin.description=mqtt-plugin-0.0.1
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3 + xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
  4 + http://maven.apache.org/POM/4.0.0
  5 + http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6 + <parent>
  7 + <artifactId>yudao-module-iot-plugin</artifactId>
  8 + <groupId>cn.iocoder.boot</groupId>
  9 + <version>${revision}</version>
  10 + </parent>
  11 + <modelVersion>4.0.0</modelVersion>
  12 + <packaging>jar</packaging>
  13 +
  14 + <artifactId>yudao-module-iot-mqtt-plugin</artifactId>
  15 +
  16 + <name>${project.artifactId}</name>
  17 + <description>
  18 + 物联网 插件模块 - mqtt 插件
  19 + </description>
  20 +
  21 + <properties>
  22 + <!-- 插件相关 -->
  23 + <plugin.id>mqtt-plugin</plugin.id>
  24 + <plugin.class>cn.iocoder.yudao.module.iot.plugin.MqttPlugin</plugin.class>
  25 + <plugin.version>0.0.1</plugin.version>
  26 + <plugin.provider>ahh</plugin.provider>
  27 + <plugin.description>mqtt-plugin-0.0.1</plugin.description>
  28 + <plugin.dependencies/>
  29 + </properties>
  30 +
  31 + <build>
  32 + <plugins>
  33 + <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
  34 + <plugin>
  35 + <groupId>org.codehaus.mojo</groupId>
  36 + <artifactId>properties-maven-plugin</artifactId>
  37 + <version>1.0-alpha-2</version>
  38 + <executions>
  39 + <execution>
  40 + <phase>initialize</phase>
  41 + <goals>
  42 + <goal>read-project-properties</goal>
  43 + </goals>
  44 + <configuration>
  45 + <files>
  46 + <file>plugin.properties</file>
  47 + </files>
  48 + </configuration>
  49 + </execution>
  50 + </executions>
  51 + </plugin>
  52 + -->
  53 +
  54 + <plugin>
  55 + <groupId>org.apache.maven.plugins</groupId>
  56 + <artifactId>maven-antrun-plugin</artifactId>
  57 + <version>1.6</version>
  58 + <executions>
  59 + <execution>
  60 + <id>unzip jar file</id>
  61 + <phase>package</phase>
  62 + <configuration>
  63 + <target>
  64 + <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}"
  65 + dest="target/plugin-classes"/>
  66 + </target>
  67 + </configuration>
  68 + <goals>
  69 + <goal>run</goal>
  70 + </goals>
  71 + </execution>
  72 + </executions>
  73 + </plugin>
  74 +
  75 + <plugin>
  76 + <artifactId>maven-assembly-plugin</artifactId>
  77 + <version>2.3</version>
  78 + <configuration>
  79 + <descriptors>
  80 + <descriptor>
  81 + src/main/assembly/assembly.xml
  82 + </descriptor>
  83 + </descriptors>
  84 + <appendAssemblyId>false</appendAssemblyId>
  85 + </configuration>
  86 + <executions>
  87 + <execution>
  88 + <id>make-assembly</id>
  89 + <phase>package</phase>
  90 + <goals>
  91 + <goal>attached</goal>
  92 + </goals>
  93 + </execution>
  94 + </executions>
  95 + </plugin>
  96 +
  97 + <plugin>
  98 + <groupId>org.apache.maven.plugins</groupId>
  99 + <artifactId>maven-jar-plugin</artifactId>
  100 + <version>2.4</version>
  101 + <configuration>
  102 + <archive>
  103 + <manifestEntries>
  104 + <Plugin-Id>${plugin.id}</Plugin-Id>
  105 + <Plugin-Class>${plugin.class}</Plugin-Class>
  106 + <Plugin-Version>${plugin.version}</Plugin-Version>
  107 + <Plugin-Provider>${plugin.provider}</Plugin-Provider>
  108 + <Plugin-Description>${plugin.description}</Plugin-Description>
  109 + <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
  110 + </manifestEntries>
  111 + </archive>
  112 + </configuration>
  113 + </plugin>
  114 +
  115 + <plugin>
  116 + <artifactId>maven-deploy-plugin</artifactId>
  117 + <configuration>
  118 + <skip>true</skip>
  119 + </configuration>
  120 + </plugin>
  121 + </plugins>
  122 + </build>
  123 +
  124 + <dependencies>
  125 + <!-- 其他依赖项 -->
  126 + <dependency>
  127 + <groupId>org.springframework.boot</groupId>
  128 + <artifactId>spring-boot-starter-web</artifactId>
  129 + </dependency>
  130 + <!-- PF4J Spring 集成 -->
  131 + <dependency>
  132 + <groupId>org.pf4j</groupId>
  133 + <artifactId>pf4j-spring</artifactId>
  134 + <scope>provided</scope>
  135 + </dependency>
  136 + <!-- 项目依赖 -->
  137 + <dependency>
  138 + <groupId>cn.iocoder.boot</groupId>
  139 + <artifactId>yudao-module-iot-api</artifactId>
  140 + <version>${revision}</version>
  141 + </dependency>
  142 + <dependency>
  143 + <groupId>org.projectlombok</groupId>
  144 + <artifactId>lombok</artifactId>
  145 + <version>${lombok.version}</version>
  146 + <scope>provided</scope>
  147 + </dependency>
  148 + <!-- MQTT -->
  149 + <dependency>
  150 + <groupId>org.eclipse.paho</groupId>
  151 + <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
  152 + </dependency>
  153 + </dependencies>
  154 +</project>
0 \ No newline at end of file 155 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/assembly/assembly.xml 0 → 100644
  1 +<assembly>
  2 + <id>plugin</id>
  3 + <formats>
  4 + <format>zip</format>
  5 + </formats>
  6 + <includeBaseDirectory>false</includeBaseDirectory>
  7 + <dependencySets>
  8 + <dependencySet>
  9 + <useProjectArtifact>false</useProjectArtifact>
  10 + <scope>runtime</scope>
  11 + <outputDirectory>lib</outputDirectory>
  12 + <includes>
  13 + <include>*:jar:*</include>
  14 + </includes>
  15 + </dependencySet>
  16 + </dependencySets>
  17 + <!--
  18 + <fileSets>
  19 + <fileSet>
  20 + <directory>target/classes</directory>
  21 + <outputDirectory>classes</outputDirectory>
  22 + </fileSet>
  23 + </fileSets>
  24 + -->
  25 + <fileSets>
  26 + <fileSet>
  27 + <directory>target/plugin-classes</directory>
  28 + <outputDirectory>classes</outputDirectory>
  29 + </fileSet>
  30 + </fileSets>
  31 +</assembly>
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java renamed to yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java
1 package cn.iocoder.yudao.module.iot.plugin; 1 package cn.iocoder.yudao.module.iot.plugin;
2 2
3 -import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;  
4 import cn.iocoder.yudao.module.iot.api.ServiceRegistry; 3 import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
5 -import io.netty.bootstrap.ServerBootstrap;  
6 -import io.netty.channel.*;  
7 -import io.netty.channel.nio.NioEventLoopGroup;  
8 -import io.netty.channel.socket.nio.NioServerSocketChannel;  
9 -import io.netty.handler.codec.http.*; 4 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
10 import lombok.extern.slf4j.Slf4j; 5 import lombok.extern.slf4j.Slf4j;
11 -import org.pf4j.PluginWrapper;  
12 import org.pf4j.Plugin; 6 import org.pf4j.Plugin;
  7 +import org.pf4j.PluginWrapper;
13 8
  9 +import javax.annotation.Resource;
14 import java.util.concurrent.ExecutorService; 10 import java.util.concurrent.ExecutorService;
15 import java.util.concurrent.Executors; 11 import java.util.concurrent.Executors;
16 12
17 @Slf4j 13 @Slf4j
18 -public class HttpPlugin extends Plugin {  
19 -  
20 - private static final int PORT = 8092; 14 +public class MqttPlugin extends Plugin {
21 15
22 private ExecutorService executorService; 16 private ExecutorService executorService;
  17 + @Resource
23 private DeviceDataApi deviceDataApi; 18 private DeviceDataApi deviceDataApi;
24 19
25 - public HttpPlugin(PluginWrapper wrapper) { 20 + public MqttPlugin(PluginWrapper wrapper) {
26 super(wrapper); 21 super(wrapper);
27 - // 初始化线程池  
28 this.executorService = Executors.newSingleThreadExecutor(); 22 this.executorService = Executors.newSingleThreadExecutor();
29 } 23 }
30 24
31 @Override 25 @Override
32 public void start() { 26 public void start() {
33 - log.info("HttpPlugin.start()"); 27 + log.info("MqttPlugin.start()");
34 28
35 - // 重新初始化线程池,确保它是活跃的  
36 if (executorService.isShutdown() || executorService.isTerminated()) { 29 if (executorService.isShutdown() || executorService.isTerminated()) {
37 executorService = Executors.newSingleThreadExecutor(); 30 executorService = Executors.newSingleThreadExecutor();
38 } 31 }
39 32
40 - // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例  
41 deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); 33 deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
42 if (deviceDataApi == null) { 34 if (deviceDataApi == null) {
43 log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); 35 log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");
44 return; 36 return;
45 } 37 }
46 38
47 - // 异步启动 Netty 服务器  
48 - executorService.submit(this::startHttpServer);  
49 } 39 }
50 40
51 @Override 41 @Override
52 public void stop() { 42 public void stop() {
53 - log.info("HttpPlugin.stop()");  
54 - // 停止线程池  
55 - executorService.shutdownNow(); 43 + log.info("MqttPlugin.stop()");
56 } 44 }
57 -  
58 - /**  
59 - * 启动 HTTP 服务  
60 - */  
61 - private void startHttpServer() {  
62 - EventLoopGroup bossGroup = new NioEventLoopGroup(1);  
63 - EventLoopGroup workerGroup = new NioEventLoopGroup();  
64 -  
65 - try {  
66 - ServerBootstrap bootstrap = new ServerBootstrap();  
67 - bootstrap.group(bossGroup, workerGroup)  
68 - .channel(NioServerSocketChannel.class)  
69 - .childHandler(new ChannelInitializer<>() {  
70 -  
71 - @Override  
72 - protected void initChannel(Channel channel) {  
73 - channel.pipeline().addLast(new HttpServerCodec());  
74 - channel.pipeline().addLast(new HttpObjectAggregator(65536));  
75 - // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器  
76 - channel.pipeline().addLast(new HttpHandler(deviceDataApi));  
77 - }  
78 -  
79 - });  
80 -  
81 - // 绑定端口并启动服务器  
82 - ChannelFuture future = bootstrap.bind(PORT).sync();  
83 - log.info("HTTP 服务器启动成功,端口为: {}", PORT);  
84 - future.channel().closeFuture().sync();  
85 - } catch (InterruptedException e) {  
86 - Thread.currentThread().interrupt();  
87 - log.warn("HTTP 服务启动被中断", e);  
88 - } finally {  
89 - bossGroup.shutdownGracefully();  
90 - workerGroup.shutdownGracefully();  
91 - }  
92 - }  
93 -  
94 } 45 }
95 \ No newline at end of file 46 \ No newline at end of file
yudao-server/src/main/resources/application-dev.yaml
@@ -212,4 +212,9 @@ iot: @@ -212,4 +212,9 @@ iot:
212 # 保持连接 212 # 保持连接
213 keepalive: 60 213 keepalive: 60
214 # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) 214 # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
215 - clearSession: true  
216 \ No newline at end of file 215 \ No newline at end of file
  216 + clearSession: true
  217 +
  218 +
  219 +# 插件配置
  220 +pf4j:
  221 + pluginsDir: ${user.home}/plugins # 插件目录
217 \ No newline at end of file 222 \ No newline at end of file
yudao-server/src/main/resources/application-local.yaml
@@ -183,10 +183,8 @@ logging: @@ -183,10 +183,8 @@ logging:
183 cn.iocoder.yudao.module.crm.dal.mysql: debug 183 cn.iocoder.yudao.module.crm.dal.mysql: debug
184 cn.iocoder.yudao.module.erp.dal.mysql: debug 184 cn.iocoder.yudao.module.erp.dal.mysql: debug
185 cn.iocoder.yudao.module.iot.dal.mysql: debug 185 cn.iocoder.yudao.module.iot.dal.mysql: debug
186 - cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG  
187 cn.iocoder.yudao.module.ai.dal.mysql: debug 186 cn.iocoder.yudao.module.ai.dal.mysql: debug
188 org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 187 org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
189 - com.taosdata: DEBUG # TDengine 的日志级别  
190 188
191 debug: false 189 debug: false
192 190
@@ -283,3 +281,16 @@ iot: @@ -283,3 +281,16 @@ iot:
283 # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) 281 # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
284 clearSession: true 282 clearSession: true
285 283
  284 +# MQTT-RPC 配置
  285 +mqtt:
  286 + broker: tcp://127.0.0.1:1883
  287 + username: root
  288 + password: 123456
  289 + clientId: mqtt-rpc-server-${random.int}
  290 + requestTopic: rpc/request
  291 + responseTopicPrefix: rpc/response/
  292 +
  293 +
  294 +# 插件配置
  295 +pf4j:
  296 + pluginsDir: /Users/anhaohao/code/gitee/ruoyi-vue-pro/plugins # 插件目录
286 \ No newline at end of file 297 \ No newline at end of file